| // 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 "public/fpdf_save.h" | |
| #include <vector> | |
| #include "core/fpdfapi/fpdf_edit/include/cpdf_creator.h" | |
| #include "core/fpdfapi/fpdf_parser/include/cpdf_array.h" | |
| #include "core/fpdfapi/fpdf_parser/include/cpdf_document.h" | |
| #include "core/fpdfapi/fpdf_parser/include/cpdf_reference.h" | |
| #include "core/fpdfapi/fpdf_parser/include/cpdf_stream_acc.h" | |
| #include "core/fpdfapi/fpdf_parser/include/cpdf_string.h" | |
| #include "core/fxcrt/include/fx_ext.h" | |
| #include "fpdfsdk/include/fsdk_define.h" | |
| #include "public/fpdf_edit.h" | |
| #ifdef PDF_ENABLE_XFA | |
| #include "fpdfsdk/fpdfxfa/include/fpdfxfa_app.h" | |
| #include "fpdfsdk/fpdfxfa/include/fpdfxfa_doc.h" | |
| #include "fpdfsdk/fpdfxfa/include/fpdfxfa_util.h" | |
| #include "public/fpdf_formfill.h" | |
| #include "xfa/fxfa/include/cxfa_eventparam.h" | |
| #include "xfa/fxfa/include/xfa_checksum.h" | |
| #include "xfa/fxfa/include/xfa_ffapp.h" | |
| #include "xfa/fxfa/include/xfa_ffdocview.h" | |
| #include "xfa/fxfa/include/xfa_ffwidgethandler.h" | |
| #endif | |
| #if _FX_OS_ == _FX_ANDROID_ | |
| #include "time.h" | |
| #else | |
| #include <ctime> | |
| #endif | |
| class CFX_IFileWrite final : public IFX_StreamWrite { | |
| public: | |
| CFX_IFileWrite(); | |
| FX_BOOL Init(FPDF_FILEWRITE* pFileWriteStruct); | |
| FX_BOOL WriteBlock(const void* pData, size_t size) override; | |
| void Release() override; | |
| protected: | |
| ~CFX_IFileWrite() override {} | |
| FPDF_FILEWRITE* m_pFileWriteStruct; | |
| }; | |
| CFX_IFileWrite::CFX_IFileWrite() { | |
| m_pFileWriteStruct = nullptr; | |
| } | |
| FX_BOOL CFX_IFileWrite::Init(FPDF_FILEWRITE* pFileWriteStruct) { | |
| if (!pFileWriteStruct) | |
| return FALSE; | |
| m_pFileWriteStruct = pFileWriteStruct; | |
| return TRUE; | |
| } | |
| FX_BOOL CFX_IFileWrite::WriteBlock(const void* pData, size_t size) { | |
| if (!m_pFileWriteStruct) | |
| return FALSE; | |
| m_pFileWriteStruct->WriteBlock(m_pFileWriteStruct, pData, size); | |
| return TRUE; | |
| } | |
| void CFX_IFileWrite::Release() { | |
| delete this; | |
| } | |
| namespace { | |
| #ifdef PDF_ENABLE_XFA | |
| bool SaveXFADocumentData(CPDFXFA_Document* pDocument, | |
| std::vector<ScopedFileStream>* fileList) { | |
| if (!pDocument) | |
| return false; | |
| if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA && | |
| pDocument->GetDocType() != DOCTYPE_STATIC_XFA) | |
| return true; | |
| if (!CPDFXFA_App::GetInstance()->GetXFAApp()) | |
| return true; | |
| CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView(); | |
| if (!pXFADocView) | |
| return true; | |
| CPDF_Document* pPDFDocument = pDocument->GetPDFDoc(); | |
| if (!pDocument) | |
| return false; | |
| CPDF_Dictionary* pRoot = pPDFDocument->GetRoot(); | |
| if (!pRoot) | |
| return false; | |
| CPDF_Dictionary* pAcroForm = pRoot->GetDictBy("AcroForm"); | |
| if (!pAcroForm) | |
| return false; | |
| CPDF_Object* pXFA = pAcroForm->GetObjectBy("XFA"); | |
| if (!pXFA) | |
| return true; | |
| CPDF_Array* pArray = pXFA->AsArray(); | |
| if (!pArray) | |
| return false; | |
| int size = pArray->GetCount(); | |
| int iFormIndex = -1; | |
| int iDataSetsIndex = -1; | |
| int iTemplate = -1; | |
| int iLast = size - 2; | |
| for (int i = 0; i < size - 1; i++) { | |
| CPDF_Object* pPDFObj = pArray->GetObjectAt(i); | |
| if (!pPDFObj->IsString()) | |
| continue; | |
| if (pPDFObj->GetString() == "form") | |
| iFormIndex = i + 1; | |
| else if (pPDFObj->GetString() == "datasets") | |
| iDataSetsIndex = i + 1; | |
| else if (pPDFObj->GetString() == "template") | |
| iTemplate = i + 1; | |
| } | |
| std::unique_ptr<CXFA_ChecksumContext> pContext(new CXFA_ChecksumContext); | |
| pContext->StartChecksum(); | |
| // template | |
| if (iTemplate > -1) { | |
| CPDF_Stream* pTemplateStream = pArray->GetStreamAt(iTemplate); | |
| CPDF_StreamAcc streamAcc; | |
| streamAcc.LoadAllData(pTemplateStream); | |
| uint8_t* pData = (uint8_t*)streamAcc.GetData(); | |
| uint32_t dwSize2 = streamAcc.GetSize(); | |
| ScopedFileStream pTemplate(FX_CreateMemoryStream(pData, dwSize2)); | |
| pContext->UpdateChecksum(pTemplate.get()); | |
| } | |
| CPDF_Stream* pFormStream = nullptr; | |
| CPDF_Stream* pDataSetsStream = nullptr; | |
| if (iFormIndex != -1) { | |
| // Get form CPDF_Stream | |
| CPDF_Object* pFormPDFObj = pArray->GetObjectAt(iFormIndex); | |
| if (pFormPDFObj->IsReference()) { | |
| CPDF_Object* pFormDirectObj = pFormPDFObj->GetDirect(); | |
| if (pFormDirectObj && pFormDirectObj->IsStream()) { | |
| pFormStream = (CPDF_Stream*)pFormDirectObj; | |
| } | |
| } else if (pFormPDFObj->IsStream()) { | |
| pFormStream = (CPDF_Stream*)pFormPDFObj; | |
| } | |
| } | |
| if (iDataSetsIndex != -1) { | |
| // Get datasets CPDF_Stream | |
| CPDF_Object* pDataSetsPDFObj = pArray->GetObjectAt(iDataSetsIndex); | |
| if (pDataSetsPDFObj->IsReference()) { | |
| CPDF_Reference* pDataSetsRefObj = (CPDF_Reference*)pDataSetsPDFObj; | |
| CPDF_Object* pDataSetsDirectObj = pDataSetsRefObj->GetDirect(); | |
| if (pDataSetsDirectObj && pDataSetsDirectObj->IsStream()) { | |
| pDataSetsStream = (CPDF_Stream*)pDataSetsDirectObj; | |
| } | |
| } else if (pDataSetsPDFObj->IsStream()) { | |
| pDataSetsStream = (CPDF_Stream*)pDataSetsPDFObj; | |
| } | |
| } | |
| // L"datasets" | |
| { | |
| ScopedFileStream pDsfileWrite(FX_CreateMemoryStream()); | |
| if (pXFADocView->GetDoc()->SavePackage(XFA_HASHCODE_Datasets, | |
| pDsfileWrite.get(), nullptr) && | |
| pDsfileWrite->GetSize() > 0) { | |
| // Datasets | |
| pContext->UpdateChecksum(pDsfileWrite.get()); | |
| pContext->FinishChecksum(); | |
| CPDF_Dictionary* pDataDict = new CPDF_Dictionary; | |
| if (iDataSetsIndex != -1) { | |
| if (pDataSetsStream) | |
| pDataSetsStream->InitStreamFromFile(pDsfileWrite.get(), pDataDict); | |
| } else { | |
| CPDF_Stream* pData = new CPDF_Stream(nullptr, 0, nullptr); | |
| pData->InitStreamFromFile(pDsfileWrite.get(), pDataDict); | |
| pPDFDocument->AddIndirectObject(pData); | |
| iLast = pArray->GetCount() - 2; | |
| pArray->InsertAt(iLast, new CPDF_String("datasets", FALSE)); | |
| pArray->InsertAt(iLast + 1, pData, pPDFDocument); | |
| } | |
| fileList->push_back(std::move(pDsfileWrite)); | |
| } | |
| } | |
| // L"form" | |
| { | |
| ScopedFileStream pfileWrite(FX_CreateMemoryStream()); | |
| if (pXFADocView->GetDoc()->SavePackage(XFA_HASHCODE_Form, pfileWrite.get(), | |
| pContext.get()) && | |
| pfileWrite->GetSize() > 0) { | |
| CPDF_Dictionary* pDataDict = new CPDF_Dictionary; | |
| if (iFormIndex != -1) { | |
| if (pFormStream) | |
| pFormStream->InitStreamFromFile(pfileWrite.get(), pDataDict); | |
| } else { | |
| CPDF_Stream* pData = new CPDF_Stream(nullptr, 0, nullptr); | |
| pData->InitStreamFromFile(pfileWrite.get(), pDataDict); | |
| pPDFDocument->AddIndirectObject(pData); | |
| iLast = pArray->GetCount() - 2; | |
| pArray->InsertAt(iLast, new CPDF_String("form", FALSE)); | |
| pArray->InsertAt(iLast + 1, pData, pPDFDocument); | |
| } | |
| fileList->push_back(std::move(pfileWrite)); | |
| } | |
| } | |
| return true; | |
| } | |
| bool SendPostSaveToXFADoc(CPDFXFA_Document* pDocument) { | |
| if (!pDocument) | |
| return false; | |
| if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA && | |
| pDocument->GetDocType() != DOCTYPE_STATIC_XFA) | |
| return true; | |
| CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView(); | |
| if (!pXFADocView) | |
| return false; | |
| CXFA_FFWidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler(); | |
| std::unique_ptr<CXFA_WidgetAccIterator> pWidgetAccIterator( | |
| pXFADocView->CreateWidgetAccIterator()); | |
| while (CXFA_WidgetAcc* pWidgetAcc = pWidgetAccIterator->MoveToNext()) { | |
| CXFA_EventParam preParam; | |
| preParam.m_eType = XFA_EVENT_PostSave; | |
| pWidgetHander->ProcessEvent(pWidgetAcc, &preParam); | |
| } | |
| pXFADocView->UpdateDocView(); | |
| pDocument->_ClearChangeMark(); | |
| return true; | |
| } | |
| bool SendPreSaveToXFADoc(CPDFXFA_Document* pDocument, | |
| std::vector<ScopedFileStream>* fileList) { | |
| if (pDocument->GetDocType() != DOCTYPE_DYNAMIC_XFA && | |
| pDocument->GetDocType() != DOCTYPE_STATIC_XFA) | |
| return true; | |
| CXFA_FFDocView* pXFADocView = pDocument->GetXFADocView(); | |
| if (!pXFADocView) | |
| return true; | |
| CXFA_FFWidgetHandler* pWidgetHander = pXFADocView->GetWidgetHandler(); | |
| std::unique_ptr<CXFA_WidgetAccIterator> pWidgetAccIterator( | |
| pXFADocView->CreateWidgetAccIterator()); | |
| while (CXFA_WidgetAcc* pWidgetAcc = pWidgetAccIterator->MoveToNext()) { | |
| CXFA_EventParam preParam; | |
| preParam.m_eType = XFA_EVENT_PreSave; | |
| pWidgetHander->ProcessEvent(pWidgetAcc, &preParam); | |
| } | |
| pXFADocView->UpdateDocView(); | |
| return SaveXFADocumentData(pDocument, fileList); | |
| } | |
| #endif // PDF_ENABLE_XFA | |
| bool FPDF_Doc_Save(FPDF_DOCUMENT document, | |
| FPDF_FILEWRITE* pFileWrite, | |
| FPDF_DWORD flags, | |
| FPDF_BOOL bSetVersion, | |
| int fileVerion) { | |
| CPDF_Document* pPDFDoc = CPDFDocumentFromFPDFDocument(document); | |
| if (!pPDFDoc) | |
| return 0; | |
| #ifdef PDF_ENABLE_XFA | |
| CPDFXFA_Document* pDoc = static_cast<CPDFXFA_Document*>(document); | |
| std::vector<ScopedFileStream> fileList; | |
| SendPreSaveToXFADoc(pDoc, &fileList); | |
| #endif // PDF_ENABLE_XFA | |
| if (flags < FPDF_INCREMENTAL || flags > FPDF_REMOVE_SECURITY) | |
| flags = 0; | |
| CPDF_Creator FileMaker(pPDFDoc); | |
| if (bSetVersion) | |
| FileMaker.SetFileVersion(fileVerion); | |
| if (flags == FPDF_REMOVE_SECURITY) { | |
| flags = 0; | |
| FileMaker.RemoveSecurity(); | |
| } | |
| CFX_IFileWrite* pStreamWrite = new CFX_IFileWrite; | |
| pStreamWrite->Init(pFileWrite); | |
| bool bRet = FileMaker.Create(pStreamWrite, flags); | |
| #ifdef PDF_ENABLE_XFA | |
| SendPostSaveToXFADoc(pDoc); | |
| #endif // PDF_ENABLE_XFA | |
| pStreamWrite->Release(); | |
| return bRet; | |
| } | |
| } // namespace | |
| DLLEXPORT FPDF_BOOL STDCALL FPDF_SaveAsCopy(FPDF_DOCUMENT document, | |
| FPDF_FILEWRITE* pFileWrite, | |
| FPDF_DWORD flags) { | |
| return FPDF_Doc_Save(document, pFileWrite, flags, FALSE, 0); | |
| } | |
| DLLEXPORT FPDF_BOOL STDCALL FPDF_SaveWithVersion(FPDF_DOCUMENT document, | |
| FPDF_FILEWRITE* pFileWrite, | |
| FPDF_DWORD flags, | |
| int fileVersion) { | |
| return FPDF_Doc_Save(document, pFileWrite, flags, TRUE, fileVersion); | |
| } |