// Copyright 2014 PDFium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "xfa/fxfa/parser/cxfa_document.h" #include "core/fxcrt/fx_extension.h" #include "fxjs/cfxjse_engine.h" #include "xfa/fxfa/cxfa_ffnotify.h" #include "xfa/fxfa/parser/cscript_datawindow.h" #include "xfa/fxfa/parser/cscript_eventpseudomodel.h" #include "xfa/fxfa/parser/cscript_hostpseudomodel.h" #include "xfa/fxfa/parser/cscript_layoutpseudomodel.h" #include "xfa/fxfa/parser/cscript_logpseudomodel.h" #include "xfa/fxfa/parser/cscript_signaturepseudomodel.h" #include "xfa/fxfa/parser/cxfa_document_parser.h" #include "xfa/fxfa/parser/cxfa_layoutprocessor.h" #include "xfa/fxfa/parser/cxfa_localemgr.h" #include "xfa/fxfa/parser/cxfa_node.h" #include "xfa/fxfa/parser/cxfa_traversestrategy_xfanode.h" #include "xfa/fxfa/parser/xfa_resolvenode_rs.h" #include "xfa/fxfa/parser/xfa_utils.h" namespace { constexpr const wchar_t kTemplateNS[] = L"http://www.xfa.org/schema/xfa-template/"; void MergeNodeRecurse(CXFA_Document* pDocument, CXFA_Node* pDestNodeParent, CXFA_Node* pProtoNode) { CXFA_Node* pExistingNode = nullptr; for (CXFA_Node* pFormChild = pDestNodeParent->GetNodeItem(XFA_NODEITEM_FirstChild); pFormChild; pFormChild = pFormChild->GetNodeItem(XFA_NODEITEM_NextSibling)) { if (pFormChild->GetElementType() == pProtoNode->GetElementType() && pFormChild->GetNameHash() == pProtoNode->GetNameHash() && pFormChild->IsUnusedNode()) { pFormChild->ClearFlag(XFA_NodeFlag_UnusedNode); pExistingNode = pFormChild; break; } } if (pExistingNode) { pExistingNode->SetTemplateNode(pProtoNode); for (CXFA_Node* pTemplateChild = pProtoNode->GetNodeItem(XFA_NODEITEM_FirstChild); pTemplateChild; pTemplateChild = pTemplateChild->GetNodeItem( XFA_NODEITEM_NextSibling)) { MergeNodeRecurse(pDocument, pExistingNode, pTemplateChild); } return; } CXFA_Node* pNewNode = pProtoNode->Clone(true); pNewNode->SetTemplateNode(pProtoNode); pDestNodeParent->InsertChild(pNewNode, nullptr); } void MergeNode(CXFA_Document* pDocument, CXFA_Node* pDestNode, CXFA_Node* pProtoNode) { { CXFA_NodeIterator sIterator(pDestNode); for (CXFA_Node* pNode = sIterator.GetCurrent(); pNode; pNode = sIterator.MoveToNext()) { pNode->SetFlag(XFA_NodeFlag_UnusedNode, true); } } pDestNode->SetTemplateNode(pProtoNode); for (CXFA_Node* pTemplateChild = pProtoNode->GetNodeItem(XFA_NODEITEM_FirstChild); pTemplateChild; pTemplateChild = pTemplateChild->GetNodeItem(XFA_NODEITEM_NextSibling)) { MergeNodeRecurse(pDocument, pDestNode, pTemplateChild); } { CXFA_NodeIterator sIterator(pDestNode); for (CXFA_Node* pNode = sIterator.GetCurrent(); pNode; pNode = sIterator.MoveToNext()) { pNode->ClearFlag(XFA_NodeFlag_UnusedNode); } } } } // namespace CXFA_Document::CXFA_Document(CXFA_DocumentParser* pParser) : m_pParser(pParser), m_pRootNode(nullptr), m_eCurVersionMode(XFA_VERSION_DEFAULT), m_dwDocFlags(0) { ASSERT(m_pParser); } CXFA_Document::~CXFA_Document() { // Remove all the bindings before freeing the node as the ownership is wonky. if (m_pRootNode) m_pRootNode->ReleaseBindingNodes(); delete m_pRootNode; for (CXFA_Node* pNode : m_PurgeNodes) delete pNode; m_PurgeNodes.clear(); } CXFA_LayoutProcessor* CXFA_Document::GetLayoutProcessor() { if (!m_pLayoutProcessor) m_pLayoutProcessor = pdfium::MakeUnique(this); return m_pLayoutProcessor.get(); } CXFA_LayoutProcessor* CXFA_Document::GetDocLayout() { return GetLayoutProcessor(); } void CXFA_Document::ClearLayoutData() { m_pLayoutProcessor.reset(); m_pScriptContext.reset(); m_pLocalMgr.reset(); m_pScriptDataWindow.reset(); m_pScriptEvent.reset(); m_pScriptHost.reset(); m_pScriptLog.reset(); m_pScriptLayout.reset(); m_pScriptSignature.reset(); } void CXFA_Document::SetRoot(CXFA_Node* pNewRoot) { if (m_pRootNode) AddPurgeNode(m_pRootNode); m_pRootNode = pNewRoot; RemovePurgeNode(pNewRoot); } CFX_XMLDoc* CXFA_Document::GetXMLDoc() const { return m_pParser->GetXMLDoc(); } CXFA_FFNotify* CXFA_Document::GetNotify() const { return m_pParser->GetNotify(); } CXFA_Object* CXFA_Document::GetXFAObject(XFA_HashCode dwNodeNameHash) { switch (dwNodeNameHash) { case XFA_HASHCODE_Data: { CXFA_Node* pDatasetsNode = ToNode(GetXFAObject(XFA_HASHCODE_Datasets)); if (!pDatasetsNode) return nullptr; for (CXFA_Node* pDatasetsChild = pDatasetsNode->GetFirstChildByClass(XFA_Element::DataGroup); pDatasetsChild; pDatasetsChild = pDatasetsChild->GetNextSameClassSibling( XFA_Element::DataGroup)) { if (pDatasetsChild->GetNameHash() != XFA_HASHCODE_Data) continue; pdfium::Optional namespaceURI = pDatasetsChild->JSNode()->TryNamespace(); if (!namespaceURI) continue; pdfium::Optional datasetsURI = pDatasetsNode->JSNode()->TryNamespace(); if (!datasetsURI) continue; if (*namespaceURI == *datasetsURI) return pDatasetsChild; } return nullptr; } case XFA_HASHCODE_Record: { CXFA_Node* pData = ToNode(GetXFAObject(XFA_HASHCODE_Data)); return pData ? pData->GetFirstChildByClass(XFA_Element::DataGroup) : nullptr; } case XFA_HASHCODE_DataWindow: { if (!m_pScriptDataWindow) m_pScriptDataWindow = pdfium::MakeUnique(this); return m_pScriptDataWindow.get(); } case XFA_HASHCODE_Event: { if (!m_pScriptEvent) m_pScriptEvent = pdfium::MakeUnique(this); return m_pScriptEvent.get(); } case XFA_HASHCODE_Host: { if (!m_pScriptHost) m_pScriptHost = pdfium::MakeUnique(this); return m_pScriptHost.get(); } case XFA_HASHCODE_Log: { if (!m_pScriptLog) m_pScriptLog = pdfium::MakeUnique(this); return m_pScriptLog.get(); } case XFA_HASHCODE_Signature: { if (!m_pScriptSignature) m_pScriptSignature = pdfium::MakeUnique(this); return m_pScriptSignature.get(); } case XFA_HASHCODE_Layout: { if (!m_pScriptLayout) m_pScriptLayout = pdfium::MakeUnique(this); return m_pScriptLayout.get(); } default: return m_pRootNode->GetFirstChildByName(dwNodeNameHash); } } CXFA_Node* CXFA_Document::CreateNode(XFA_PacketType packet, XFA_Element eElement) { if (eElement == XFA_Element::Unknown) return nullptr; std::unique_ptr pNode = CXFA_Node::Create(this, eElement, packet); if (!pNode) return nullptr; // TODO(dsinclair): AddPrugeNode should take ownership of the pointer. AddPurgeNode(pNode.get()); return pNode.release(); } void CXFA_Document::AddPurgeNode(CXFA_Node* pNode) { m_PurgeNodes.insert(pNode); } bool CXFA_Document::RemovePurgeNode(CXFA_Node* pNode) { return !!m_PurgeNodes.erase(pNode); } void CXFA_Document::SetFlag(uint32_t dwFlag, bool bOn) { if (bOn) m_dwDocFlags |= dwFlag; else m_dwDocFlags &= ~dwFlag; } bool CXFA_Document::IsInteractive() { if (m_dwDocFlags & XFA_DOCFLAG_HasInteractive) return !!(m_dwDocFlags & XFA_DOCFLAG_Interactive); CXFA_Node* pConfig = ToNode(GetXFAObject(XFA_HASHCODE_Config)); if (!pConfig) return false; CXFA_Node* pPresent = pConfig->GetFirstChildByClass(XFA_Element::Present); if (!pPresent) return false; CXFA_Node* pPDF = pPresent->GetFirstChildByClass(XFA_Element::Pdf); if (!pPDF) return false; CXFA_Node* pFormFiller = pPDF->GetChild(0, XFA_Element::Interactive, false); if (pFormFiller) { m_dwDocFlags |= XFA_DOCFLAG_HasInteractive; WideString wsInteractive = pFormFiller->JSNode()->GetContent(false); if (wsInteractive == L"1") { m_dwDocFlags |= XFA_DOCFLAG_Interactive; return true; } } return false; } CXFA_LocaleMgr* CXFA_Document::GetLocalMgr() { if (!m_pLocalMgr) { m_pLocalMgr = pdfium::MakeUnique( ToNode(GetXFAObject(XFA_HASHCODE_LocaleSet)), GetNotify()->GetAppProvider()->GetLanguage()); } return m_pLocalMgr.get(); } CFXJSE_Engine* CXFA_Document::InitScriptContext(v8::Isolate* pIsolate) { ASSERT(!m_pScriptContext); m_pScriptContext = pdfium::MakeUnique(this, pIsolate); return m_pScriptContext.get(); } // We have to call |InitScriptContext| before any calls to |GetScriptContext| // or the context won't have an isolate set into it. CFXJSE_Engine* CXFA_Document::GetScriptContext() { ASSERT(m_pScriptContext); return m_pScriptContext.get(); } XFA_VERSION CXFA_Document::RecognizeXFAVersionNumber( const WideString& wsTemplateNS) { WideStringView wsTemplateURIPrefix(kTemplateNS); size_t nPrefixLength = wsTemplateURIPrefix.GetLength(); if (WideStringView(wsTemplateNS.c_str(), wsTemplateNS.GetLength()) != wsTemplateURIPrefix) { return XFA_VERSION_UNKNOWN; } auto nDotPos = wsTemplateNS.Find('.', nPrefixLength); if (!nDotPos.has_value()) return XFA_VERSION_UNKNOWN; int8_t iMajor = FXSYS_wtoi( wsTemplateNS.Mid(nPrefixLength, nDotPos.value() - nPrefixLength).c_str()); int8_t iMinor = FXSYS_wtoi(wsTemplateNS .Mid(nDotPos.value() + 1, wsTemplateNS.GetLength() - nDotPos.value() - 2) .c_str()); XFA_VERSION eVersion = (XFA_VERSION)((int32_t)iMajor * 100 + iMinor); if (eVersion < XFA_VERSION_MIN || eVersion > XFA_VERSION_MAX) return XFA_VERSION_UNKNOWN; m_eCurVersionMode = eVersion; return eVersion; } CXFA_Node* CXFA_Document::GetNodeByID(CXFA_Node* pRoot, const WideStringView& wsID) { if (!pRoot || wsID.IsEmpty()) return nullptr; CXFA_NodeIterator sIterator(pRoot); for (CXFA_Node* pNode = sIterator.GetCurrent(); pNode; pNode = sIterator.MoveToNext()) { WideString wsIDVal = pNode->JSNode()->GetCData(XFA_Attribute::Id); if (!wsIDVal.IsEmpty() && wsIDVal == wsID) return pNode; } return nullptr; } void CXFA_Document::DoProtoMerge() { CXFA_Node* pTemplateRoot = ToNode(GetXFAObject(XFA_HASHCODE_Template)); if (!pTemplateRoot) return; std::map mIDMap; std::set sUseNodes; CXFA_NodeIterator sIterator(pTemplateRoot); for (CXFA_Node* pNode = sIterator.GetCurrent(); pNode; pNode = sIterator.MoveToNext()) { WideString wsIDVal = pNode->JSNode()->GetCData(XFA_Attribute::Id); if (!wsIDVal.IsEmpty()) mIDMap[FX_HashCode_GetW(wsIDVal.AsStringView(), false)] = pNode; WideString wsUseVal = pNode->JSNode()->GetCData(XFA_Attribute::Use); if (!wsUseVal.IsEmpty()) { sUseNodes.insert(pNode); } else { wsUseVal = pNode->JSNode()->GetCData(XFA_Attribute::Usehref); if (!wsUseVal.IsEmpty()) sUseNodes.insert(pNode); } } for (CXFA_Node* pUseHrefNode : sUseNodes) { WideStringView wsURI; WideStringView wsID; WideStringView wsSOM; WideString wsUseVal = pUseHrefNode->JSNode()->GetCData(XFA_Attribute::Usehref); if (!wsUseVal.IsEmpty()) { auto uSharpPos = wsUseVal.Find('#'); if (!uSharpPos.has_value()) { wsURI = wsUseVal.AsStringView(); } else { wsURI = WideStringView(wsUseVal.c_str(), uSharpPos.value()); size_t uLen = wsUseVal.GetLength(); if (uLen >= uSharpPos.value() + 5 && WideStringView(wsUseVal.c_str() + uSharpPos.value(), 5) == L"#som(" && wsUseVal[uLen - 1] == ')') { wsSOM = WideStringView(wsUseVal.c_str() + uSharpPos.value() + 5, uLen - 1 - uSharpPos.value() - 5); } else { wsID = WideStringView(wsUseVal.c_str() + uSharpPos.value() + 1, uLen - uSharpPos.value() - 1); } } } else { wsUseVal = pUseHrefNode->JSNode()->GetCData(XFA_Attribute::Use); if (!wsUseVal.IsEmpty()) { if (wsUseVal[0] == '#') wsID = WideStringView(wsUseVal.c_str() + 1, wsUseVal.GetLength() - 1); else wsSOM = WideStringView(wsUseVal.c_str(), wsUseVal.GetLength()); } } if (!wsURI.IsEmpty() && wsURI != L".") continue; CXFA_Node* pProtoNode = nullptr; if (!wsSOM.IsEmpty()) { uint32_t dwFlag = XFA_RESOLVENODE_Children | XFA_RESOLVENODE_Attributes | XFA_RESOLVENODE_Properties | XFA_RESOLVENODE_Parent | XFA_RESOLVENODE_Siblings; XFA_RESOLVENODE_RS resolveNodeRS; int32_t iRet = m_pScriptContext->ResolveObjects( pUseHrefNode, wsSOM, &resolveNodeRS, dwFlag, nullptr); if (iRet > 0 && resolveNodeRS.objects.front()->IsNode()) pProtoNode = resolveNodeRS.objects.front()->AsNode(); } else if (!wsID.IsEmpty()) { auto it = mIDMap.find(FX_HashCode_GetW(wsID, false)); if (it == mIDMap.end()) continue; pProtoNode = it->second; } if (!pProtoNode) continue; MergeNode(this, pUseHrefNode, pProtoNode); } }