| // 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); |
| } |