| // Copyright 2014 The PDFium Authors |
| // 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 <utility> |
| #include <vector> |
| |
| #include "build/build_config.h" |
| #include "core/fpdfapi/edit/cpdf_creator.h" |
| #include "core/fpdfapi/parser/cpdf_array.h" |
| #include "core/fpdfapi/parser/cpdf_dictionary.h" |
| #include "core/fpdfapi/parser/cpdf_document.h" |
| #include "core/fpdfapi/parser/cpdf_reference.h" |
| #include "core/fpdfapi/parser/cpdf_stream_acc.h" |
| #include "core/fpdfapi/parser/cpdf_string.h" |
| #include "core/fxcrt/fx_extension.h" |
| #include "core/fxcrt/stl_util.h" |
| #include "fpdfsdk/cpdfsdk_filewriteadapter.h" |
| #include "fpdfsdk/cpdfsdk_helpers.h" |
| #include "public/fpdf_edit.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| #ifdef PDF_ENABLE_XFA |
| #include "core/fpdfapi/parser/cpdf_stream.h" |
| #include "core/fxcrt/cfx_memorystream.h" |
| #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" |
| #include "public/fpdf_formfill.h" |
| #endif |
| |
| namespace { |
| |
| #ifdef PDF_ENABLE_XFA |
| bool SaveXFADocumentData(CPDFXFA_Context* pContext, |
| std::vector<RetainPtr<IFX_SeekableStream>>* fileList) { |
| if (!pContext) |
| return false; |
| |
| if (!pContext->ContainsExtensionForm()) |
| return true; |
| |
| CPDF_Document* pPDFDocument = pContext->GetPDFDoc(); |
| if (!pPDFDocument) |
| return false; |
| |
| RetainPtr<CPDF_Dictionary> pRoot = pPDFDocument->GetMutableRoot(); |
| if (!pRoot) |
| return false; |
| |
| RetainPtr<CPDF_Dictionary> pAcroForm = pRoot->GetMutableDictFor("AcroForm"); |
| if (!pAcroForm) |
| return false; |
| |
| RetainPtr<CPDF_Object> pXFA = pAcroForm->GetMutableObjectFor("XFA"); |
| if (!pXFA) |
| return true; |
| |
| CPDF_Array* pArray = pXFA->AsMutableArray(); |
| if (!pArray) |
| return false; |
| |
| int size = fxcrt::CollectionSize<int>(*pArray); |
| int iFormIndex = -1; |
| int iDataSetsIndex = -1; |
| for (int i = 0; i < size - 1; i++) { |
| RetainPtr<const 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; |
| } |
| |
| RetainPtr<CPDF_Stream> pFormStream; |
| if (iFormIndex != -1) { |
| // Get form CPDF_Stream |
| RetainPtr<CPDF_Object> pFormPDFObj = pArray->GetMutableObjectAt(iFormIndex); |
| if (pFormPDFObj->IsReference()) { |
| RetainPtr<CPDF_Object> pFormDirectObj = pFormPDFObj->GetMutableDirect(); |
| if (pFormDirectObj && pFormDirectObj->IsStream()) { |
| pFormStream.Reset(pFormDirectObj->AsMutableStream()); |
| } |
| } else if (pFormPDFObj->IsStream()) { |
| pFormStream.Reset(pFormPDFObj->AsMutableStream()); |
| } |
| } |
| |
| RetainPtr<CPDF_Stream> pDataSetsStream; |
| if (iDataSetsIndex != -1) { |
| // Get datasets CPDF_Stream |
| RetainPtr<CPDF_Object> pDataSetsPDFObj = |
| pArray->GetMutableObjectAt(iDataSetsIndex); |
| if (pDataSetsPDFObj->IsReference()) { |
| CPDF_Reference* pDataSetsRefObj = pDataSetsPDFObj->AsMutableReference(); |
| RetainPtr<CPDF_Object> pDataSetsDirectObj = |
| pDataSetsRefObj->GetMutableDirect(); |
| if (pDataSetsDirectObj && pDataSetsDirectObj->IsStream()) { |
| pDataSetsStream.Reset(pDataSetsDirectObj->AsMutableStream()); |
| } |
| } else if (pDataSetsPDFObj->IsStream()) { |
| pDataSetsStream.Reset(pDataSetsPDFObj->AsMutableStream()); |
| } |
| } |
| // L"datasets" |
| { |
| RetainPtr<IFX_SeekableStream> pFileWrite = |
| pdfium::MakeRetain<CFX_MemoryStream>(); |
| if (pContext->SaveDatasetsPackage(pFileWrite) && |
| pFileWrite->GetSize() > 0) { |
| auto pDataDict = pPDFDocument->New<CPDF_Dictionary>(); |
| if (iDataSetsIndex != -1) { |
| if (pDataSetsStream) { |
| pDataSetsStream->InitStreamFromFile(pFileWrite, std::move(pDataDict)); |
| } |
| } else { |
| auto pData = pPDFDocument->NewIndirect<CPDF_Stream>(); |
| pData->InitStreamFromFile(pFileWrite, std::move(pDataDict)); |
| int iLast = fxcrt::CollectionSize<int>(*pArray) - 2; |
| pArray->InsertNewAt<CPDF_String>(iLast, "datasets", false); |
| pArray->InsertNewAt<CPDF_Reference>(iLast + 1, pPDFDocument, |
| pData->GetObjNum()); |
| } |
| fileList->push_back(std::move(pFileWrite)); |
| } |
| } |
| // L"form" |
| { |
| RetainPtr<IFX_SeekableStream> pFileWrite = |
| pdfium::MakeRetain<CFX_MemoryStream>(); |
| if (pContext->SaveFormPackage(pFileWrite) && pFileWrite->GetSize() > 0) { |
| auto pDataDict = pPDFDocument->New<CPDF_Dictionary>(); |
| if (iFormIndex != -1) { |
| if (pFormStream) |
| pFormStream->InitStreamFromFile(pFileWrite, std::move(pDataDict)); |
| } else { |
| auto pData = pPDFDocument->NewIndirect<CPDF_Stream>(); |
| pData->InitStreamFromFile(pFileWrite, std::move(pDataDict)); |
| int iLast = fxcrt::CollectionSize<int>(*pArray) - 2; |
| pArray->InsertNewAt<CPDF_String>(iLast, "form", false); |
| pArray->InsertNewAt<CPDF_Reference>(iLast + 1, pPDFDocument, |
| pData->GetObjNum()); |
| } |
| fileList->push_back(std::move(pFileWrite)); |
| } |
| } |
| return true; |
| } |
| #endif // PDF_ENABLE_XFA |
| |
| bool DoDocSave(FPDF_DOCUMENT document, |
| FPDF_FILEWRITE* pFileWrite, |
| FPDF_DWORD flags, |
| absl::optional<int> version) { |
| CPDF_Document* pPDFDoc = CPDFDocumentFromFPDFDocument(document); |
| if (!pPDFDoc) |
| return false; |
| |
| #ifdef PDF_ENABLE_XFA |
| auto* pContext = static_cast<CPDFXFA_Context*>(pPDFDoc->GetExtension()); |
| if (pContext) { |
| std::vector<RetainPtr<IFX_SeekableStream>> fileList; |
| pContext->SendPreSaveToXFADoc(&fileList); |
| SaveXFADocumentData(pContext, &fileList); |
| } |
| #endif // PDF_ENABLE_XFA |
| |
| if (flags < FPDF_INCREMENTAL || flags > FPDF_REMOVE_SECURITY) |
| flags = 0; |
| |
| CPDF_Creator fileMaker( |
| pPDFDoc, pdfium::MakeRetain<CPDFSDK_FileWriteAdapter>(pFileWrite)); |
| if (version.has_value()) |
| fileMaker.SetFileVersion(version.value()); |
| if (flags == FPDF_REMOVE_SECURITY) { |
| flags = 0; |
| fileMaker.RemoveSecurity(); |
| } |
| |
| bool bRet = fileMaker.Create(static_cast<uint32_t>(flags)); |
| |
| #ifdef PDF_ENABLE_XFA |
| if (pContext) |
| pContext->SendPostSaveToXFADoc(); |
| #endif // PDF_ENABLE_XFA |
| |
| return bRet; |
| } |
| |
| } // namespace |
| |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SaveAsCopy(FPDF_DOCUMENT document, |
| FPDF_FILEWRITE* pFileWrite, |
| FPDF_DWORD flags) { |
| return DoDocSave(document, pFileWrite, flags, {}); |
| } |
| |
| FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV |
| FPDF_SaveWithVersion(FPDF_DOCUMENT document, |
| FPDF_FILEWRITE* pFileWrite, |
| FPDF_DWORD flags, |
| int fileVersion) { |
| return DoDocSave(document, pFileWrite, flags, fileVersion); |
| } |