1 // Copyright 2017 PDFium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "public/fpdf_attachment.h" 6 7 #include <memory> 8 #include <utility> 9 10 #include "core/fdrm/crypto/fx_crypt.h" 11 #include "core/fpdfapi/parser/cpdf_array.h" 12 #include "core/fpdfapi/parser/cpdf_document.h" 13 #include "core/fpdfapi/parser/cpdf_name.h" 14 #include "core/fpdfapi/parser/cpdf_number.h" 15 #include "core/fpdfapi/parser/cpdf_reference.h" 16 #include "core/fpdfapi/parser/cpdf_string.h" 17 #include "core/fpdfapi/parser/fpdf_parser_decode.h" 18 #include "core/fpdfdoc/cpdf_filespec.h" 19 #include "core/fpdfdoc/cpdf_nametree.h" 20 #include "core/fxcrt/cfx_datetime.h" 21 #include "core/fxcrt/fx_extension.h" 22 #include "fpdfsdk/fsdk_define.h" 23 24 namespace { 25 26 constexpr char kChecksumKey[] = "CheckSum"; 27 28 ByteString CFXByteStringHexDecode(const ByteString& bsHex) { 29 uint8_t* result = nullptr; 30 uint32_t size = 0; 31 HexDecode(bsHex.raw_str(), bsHex.GetLength(), &result, &size); 32 ByteString bsDecoded(result, size); 33 FX_Free(result); 34 return bsDecoded; 35 } 36 37 ByteString GenerateMD5Base16(const void* contents, const unsigned long len) { 38 uint8_t digest[16]; 39 CRYPT_MD5Generate(reinterpret_cast<const uint8_t*>(contents), len, digest); 40 char buf[32]; 41 for (int i = 0; i < 16; ++i) 42 FXSYS_IntToTwoHexChars(digest[i], &buf[i * 2]); 43 44 return ByteString(buf, 32); 45 } 46 47 } // namespace 48 49 FPDF_EXPORT int FPDF_CALLCONV 50 FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document) { 51 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); 52 if (!pDoc) 53 return 0; 54 55 return CPDF_NameTree(pDoc, "EmbeddedFiles").GetCount(); 56 } 57 58 FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV 59 FPDFDoc_AddAttachment(FPDF_DOCUMENT document, FPDF_WIDESTRING name) { 60 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); 61 WideString wsName = 62 WideString::FromUTF16LE(name, WideString::WStringLength(name)); 63 if (!pDoc || wsName.IsEmpty()) 64 return nullptr; 65 66 CPDF_Dictionary* pRoot = pDoc->GetRoot(); 67 if (!pRoot) 68 return nullptr; 69 70 // Retrieve the document's Names dictionary; create it if missing. 71 CPDF_Dictionary* pNames = pRoot->GetDictFor("Names"); 72 if (!pNames) { 73 pNames = pDoc->NewIndirect<CPDF_Dictionary>(); 74 pRoot->SetNewFor<CPDF_Reference>("Names", pDoc, pNames->GetObjNum()); 75 } 76 77 // Create the EmbeddedFiles dictionary if missing. 78 if (!pNames->GetDictFor("EmbeddedFiles")) { 79 CPDF_Dictionary* pFiles = pDoc->NewIndirect<CPDF_Dictionary>(); 80 pFiles->SetNewFor<CPDF_Array>("Names"); 81 pNames->SetNewFor<CPDF_Reference>("EmbeddedFiles", pDoc, 82 pFiles->GetObjNum()); 83 } 84 85 // Set up the basic entries in the filespec dictionary. 86 CPDF_Dictionary* pFile = pDoc->NewIndirect<CPDF_Dictionary>(); 87 pFile->SetNewFor<CPDF_Name>("Type", "Filespec"); 88 pFile->SetNewFor<CPDF_String>("UF", wsName); 89 pFile->SetNewFor<CPDF_String>("F", wsName); 90 91 // Add the new attachment name and filespec into the document's EmbeddedFiles. 92 CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); 93 if (!nameTree.AddValueAndName( 94 pdfium::MakeUnique<CPDF_Reference>(pDoc, pFile->GetObjNum()), 95 wsName)) { 96 return nullptr; 97 } 98 99 return pFile; 100 } 101 102 FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV 103 FPDFDoc_GetAttachment(FPDF_DOCUMENT document, int index) { 104 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); 105 if (!pDoc || index < 0) 106 return nullptr; 107 108 CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); 109 if (static_cast<size_t>(index) >= nameTree.GetCount()) 110 return nullptr; 111 112 WideString csName; 113 return nameTree.LookupValueAndName(index, &csName); 114 } 115 116 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV 117 FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, int index) { 118 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); 119 if (!pDoc || index < 0) 120 return false; 121 122 CPDF_NameTree nameTree(pDoc, "EmbeddedFiles"); 123 if (static_cast<size_t>(index) >= nameTree.GetCount()) 124 return false; 125 126 return nameTree.DeleteValueAndName(index); 127 } 128 129 FPDF_EXPORT unsigned long FPDF_CALLCONV 130 FPDFAttachment_GetName(FPDF_ATTACHMENT attachment, 131 void* buffer, 132 unsigned long buflen) { 133 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 134 if (!pFile) 135 return 0; 136 137 return Utf16EncodeMaybeCopyAndReturnLength(CPDF_FileSpec(pFile).GetFileName(), 138 buffer, buflen); 139 } 140 141 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV 142 FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { 143 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 144 if (!pFile) 145 return 0; 146 147 CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); 148 return pParamsDict ? pParamsDict->KeyExist(key) : 0; 149 } 150 151 FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV 152 FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) { 153 if (!FPDFAttachment_HasKey(attachment, key)) 154 return FPDF_OBJECT_UNKNOWN; 155 156 CPDF_FileSpec spec(CPDFObjectFromFPDFAttachment(attachment)); 157 CPDF_Object* pObj = spec.GetParamsDict()->GetObjectFor(key); 158 return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN; 159 } 160 161 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV 162 FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment, 163 FPDF_BYTESTRING key, 164 FPDF_WIDESTRING value) { 165 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 166 if (!pFile) 167 return false; 168 169 CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); 170 if (!pParamsDict) 171 return false; 172 173 ByteString bsKey = key; 174 ByteString bsValue = CFXByteStringFromFPDFWideString(value); 175 bool bEncodedAsHex = bsKey == kChecksumKey; 176 if (bEncodedAsHex) 177 bsValue = CFXByteStringHexDecode(bsValue); 178 179 pParamsDict->SetNewFor<CPDF_String>(bsKey, bsValue, bEncodedAsHex); 180 return true; 181 } 182 183 FPDF_EXPORT unsigned long FPDF_CALLCONV 184 FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment, 185 FPDF_BYTESTRING key, 186 void* buffer, 187 unsigned long buflen) { 188 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 189 if (!pFile) 190 return 0; 191 192 CPDF_Dictionary* pParamsDict = CPDF_FileSpec(pFile).GetParamsDict(); 193 if (!pParamsDict) 194 return 0; 195 196 ByteString bsKey = key; 197 WideString value = pParamsDict->GetUnicodeTextFor(bsKey); 198 if (bsKey == kChecksumKey && !value.IsEmpty()) { 199 CPDF_String* stringValue = pParamsDict->GetObjectFor(bsKey)->AsString(); 200 if (stringValue->IsHex()) { 201 ByteString encoded = PDF_EncodeString(stringValue->GetString(), true); 202 value = CPDF_String(nullptr, encoded, false).GetUnicodeText(); 203 } 204 } 205 206 return Utf16EncodeMaybeCopyAndReturnLength(value, buffer, buflen); 207 } 208 209 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV 210 FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, 211 FPDF_DOCUMENT document, 212 const void* contents, 213 const unsigned long len) { 214 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 215 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document); 216 if (!pFile || !pFile->IsDictionary() || !pDoc || len > INT_MAX) 217 return false; 218 219 // An empty content must have a zero length. 220 if (!contents && len != 0) 221 return false; 222 223 // Create a dictionary for the new embedded file stream. 224 auto pFileStreamDict = pdfium::MakeUnique<CPDF_Dictionary>(); 225 CPDF_Dictionary* pParamsDict = 226 pFileStreamDict->SetNewFor<CPDF_Dictionary>("Params"); 227 228 // Set the size of the new file in the dictionary. 229 pFileStreamDict->SetNewFor<CPDF_Number>("DL", static_cast<int>(len)); 230 pParamsDict->SetNewFor<CPDF_Number>("Size", static_cast<int>(len)); 231 232 // Set the creation date of the new attachment in the dictionary. 233 CFX_DateTime dateTime = CFX_DateTime::Now(); 234 pParamsDict->SetNewFor<CPDF_String>( 235 "CreationDate", 236 ByteString::Format("D:%d%02d%02d%02d%02d%02d", dateTime.GetYear(), 237 dateTime.GetMonth(), dateTime.GetDay(), 238 dateTime.GetHour(), dateTime.GetMinute(), 239 dateTime.GetSecond()), 240 false); 241 242 // Set the checksum of the new attachment in the dictionary. 243 pParamsDict->SetNewFor<CPDF_String>( 244 kChecksumKey, CFXByteStringHexDecode(GenerateMD5Base16(contents, len)), 245 true); 246 247 // Create the file stream and have the filespec dictionary link to it. 248 std::unique_ptr<uint8_t, FxFreeDeleter> stream(FX_Alloc(uint8_t, len)); 249 memcpy(stream.get(), contents, len); 250 CPDF_Stream* pFileStream = pDoc->NewIndirect<CPDF_Stream>( 251 std::move(stream), len, std::move(pFileStreamDict)); 252 CPDF_Dictionary* pEFDict = 253 pFile->AsDictionary()->SetNewFor<CPDF_Dictionary>("EF"); 254 pEFDict->SetNewFor<CPDF_Reference>("F", pDoc, pFileStream->GetObjNum()); 255 return true; 256 } 257 258 FPDF_EXPORT unsigned long FPDF_CALLCONV 259 FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment, 260 void* buffer, 261 unsigned long buflen) { 262 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment); 263 if (!pFile) 264 return 0; 265 266 CPDF_Stream* pFileStream = CPDF_FileSpec(pFile).GetFileStream(); 267 if (!pFileStream) 268 return 0; 269 270 return DecodeStreamMaybeCopyAndReturnLength(pFileStream, buffer, buflen); 271 } 272