1 // Copyright 2017 The PDFium Authors
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 <limits.h>
8
9 #include <array>
10 #include <memory>
11 #include <utility>
12
13 #include "constants/stream_dict_common.h"
14 #include "core/fdrm/fx_crypt.h"
15 #include "core/fpdfapi/parser/cpdf_array.h"
16 #include "core/fpdfapi/parser/cpdf_dictionary.h"
17 #include "core/fpdfapi/parser/cpdf_document.h"
18 #include "core/fpdfapi/parser/cpdf_name.h"
19 #include "core/fpdfapi/parser/cpdf_number.h"
20 #include "core/fpdfapi/parser/cpdf_reference.h"
21 #include "core/fpdfapi/parser/cpdf_stream.h"
22 #include "core/fpdfapi/parser/cpdf_string.h"
23 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
24 #include "core/fpdfdoc/cpdf_filespec.h"
25 #include "core/fpdfdoc/cpdf_nametree.h"
26 #include "core/fxcodec/data_and_bytes_consumed.h"
27 #include "core/fxcrt/cfx_datetime.h"
28 #include "core/fxcrt/data_vector.h"
29 #include "core/fxcrt/fx_extension.h"
30 #include "core/fxcrt/numerics/safe_conversions.h"
31 #include "fpdfsdk/cpdfsdk_helpers.h"
32
33 namespace {
34
35 constexpr char kChecksumKey[] = "CheckSum";
36
37 } // namespace
38
39 FPDF_EXPORT int FPDF_CALLCONV
FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document)40 FPDFDoc_GetAttachmentCount(FPDF_DOCUMENT document) {
41 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
42 if (!pDoc)
43 return 0;
44
45 auto name_tree = CPDF_NameTree::Create(pDoc, "EmbeddedFiles");
46 return name_tree ? pdfium::checked_cast<int>(name_tree->GetCount()) : 0;
47 }
48
49 FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
FPDFDoc_AddAttachment(FPDF_DOCUMENT document,FPDF_WIDESTRING name)50 FPDFDoc_AddAttachment(FPDF_DOCUMENT document, FPDF_WIDESTRING name) {
51 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
52 if (!pDoc)
53 return nullptr;
54
55 // SAFETY: required from caller.
56 WideString wsName = UNSAFE_BUFFERS(WideStringFromFPDFWideString(name));
57 if (wsName.IsEmpty())
58 return nullptr;
59
60 auto name_tree =
61 CPDF_NameTree::CreateWithRootNameArray(pDoc, "EmbeddedFiles");
62 if (!name_tree)
63 return nullptr;
64
65 // Set up the basic entries in the filespec dictionary.
66 auto pFile = pDoc->NewIndirect<CPDF_Dictionary>();
67 pFile->SetNewFor<CPDF_Name>("Type", "Filespec");
68 pFile->SetNewFor<CPDF_String>("UF", wsName.AsStringView());
69 pFile->SetNewFor<CPDF_String>(pdfium::stream::kF, wsName.AsStringView());
70
71 // Add the new attachment name and filespec into the document's EmbeddedFiles.
72 if (!name_tree->AddValueAndName(pFile->MakeReference(pDoc), wsName))
73 return nullptr;
74
75 // Unretained reference in public API. NOLINTNEXTLINE
76 return FPDFAttachmentFromCPDFObject(pFile);
77 }
78
79 FPDF_EXPORT FPDF_ATTACHMENT FPDF_CALLCONV
FPDFDoc_GetAttachment(FPDF_DOCUMENT document,int index)80 FPDFDoc_GetAttachment(FPDF_DOCUMENT document, int index) {
81 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
82 if (!pDoc || index < 0)
83 return nullptr;
84
85 auto name_tree = CPDF_NameTree::Create(pDoc, "EmbeddedFiles");
86 if (!name_tree || static_cast<size_t>(index) >= name_tree->GetCount())
87 return nullptr;
88
89 WideString csName;
90
91 // Unretained reference in public API. NOLINTNEXTLINE
92 return FPDFAttachmentFromCPDFObject(
93 name_tree->LookupValueAndName(index, &csName));
94 }
95
96 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document,int index)97 FPDFDoc_DeleteAttachment(FPDF_DOCUMENT document, int index) {
98 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
99 if (!pDoc || index < 0)
100 return false;
101
102 auto name_tree = CPDF_NameTree::Create(pDoc, "EmbeddedFiles");
103 if (!name_tree || static_cast<size_t>(index) >= name_tree->GetCount())
104 return false;
105
106 return name_tree->DeleteValueAndName(index);
107 }
108
109 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFAttachment_GetName(FPDF_ATTACHMENT attachment,FPDF_WCHAR * buffer,unsigned long buflen)110 FPDFAttachment_GetName(FPDF_ATTACHMENT attachment,
111 FPDF_WCHAR* buffer,
112 unsigned long buflen) {
113 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment);
114 if (!pFile)
115 return 0;
116
117 CPDF_FileSpec spec(pdfium::WrapRetain(pFile));
118 // SAFETY: required from caller.
119 return Utf16EncodeMaybeCopyAndReturnLength(
120 spec.GetFileName(), UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen)));
121 }
122
123 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment,FPDF_BYTESTRING key)124 FPDFAttachment_HasKey(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) {
125 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment);
126 if (!pFile)
127 return 0;
128
129 CPDF_FileSpec spec(pdfium::WrapRetain(pFile));
130 RetainPtr<const CPDF_Dictionary> pParamsDict = spec.GetParamsDict();
131 return pParamsDict ? pParamsDict->KeyExist(key) : 0;
132 }
133
134 FPDF_EXPORT FPDF_OBJECT_TYPE FPDF_CALLCONV
FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment,FPDF_BYTESTRING key)135 FPDFAttachment_GetValueType(FPDF_ATTACHMENT attachment, FPDF_BYTESTRING key) {
136 if (!FPDFAttachment_HasKey(attachment, key))
137 return FPDF_OBJECT_UNKNOWN;
138
139 CPDF_FileSpec spec(
140 pdfium::WrapRetain(CPDFObjectFromFPDFAttachment(attachment)));
141 RetainPtr<const CPDF_Object> pObj = spec.GetParamsDict()->GetObjectFor(key);
142 return pObj ? pObj->GetType() : FPDF_OBJECT_UNKNOWN;
143 }
144
145 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment,FPDF_BYTESTRING key,FPDF_WIDESTRING value)146 FPDFAttachment_SetStringValue(FPDF_ATTACHMENT attachment,
147 FPDF_BYTESTRING key,
148 FPDF_WIDESTRING value) {
149 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment);
150 if (!pFile)
151 return false;
152
153 CPDF_FileSpec spec(pdfium::WrapRetain(pFile));
154 RetainPtr<CPDF_Dictionary> pParamsDict = spec.GetMutableParamsDict();
155 if (!pParamsDict)
156 return false;
157
158 // SAFETY: required from caller.
159 ByteString bsValue = UNSAFE_BUFFERS(ByteStringFromFPDFWideString(value));
160 ByteString bsKey = key;
161 if (bsKey == kChecksumKey) {
162 pParamsDict->SetNewFor<CPDF_String>(bsKey,
163 HexDecode(bsValue.unsigned_span()).data,
164 CPDF_String::DataType::kIsHex);
165 } else {
166 pParamsDict->SetNewFor<CPDF_String>(bsKey, bsValue);
167 }
168 return true;
169 }
170
171 FPDF_EXPORT unsigned long FPDF_CALLCONV
FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment,FPDF_BYTESTRING key,FPDF_WCHAR * buffer,unsigned long buflen)172 FPDFAttachment_GetStringValue(FPDF_ATTACHMENT attachment,
173 FPDF_BYTESTRING key,
174 FPDF_WCHAR* buffer,
175 unsigned long buflen) {
176 CPDF_Object* file = CPDFObjectFromFPDFAttachment(attachment);
177 if (!file) {
178 return 0;
179 }
180
181 CPDF_FileSpec spec(pdfium::WrapRetain(file));
182 RetainPtr<const CPDF_Dictionary> params = spec.GetParamsDict();
183 if (!params) {
184 return 0;
185 }
186
187 // SAFETY: required from caller.
188 auto buffer_span = UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen));
189
190 ByteString key_str = key;
191 RetainPtr<const CPDF_Object> object = params->GetObjectFor(key_str);
192 if (!object || (!object->IsString() && !object->IsName())) {
193 // Per API description, return an empty string in these cases.
194 return Utf16EncodeMaybeCopyAndReturnLength(WideString(), buffer_span);
195 }
196
197 if (key_str == kChecksumKey) {
198 RetainPtr<const CPDF_String> string_object = ToString(object);
199 if (string_object && string_object->IsHex()) {
200 ByteString encoded =
201 PDF_HexEncodeString(string_object->GetString().AsStringView());
202 return Utf16EncodeMaybeCopyAndReturnLength(
203 PDF_DecodeText(encoded.unsigned_span()), buffer_span);
204 }
205 }
206
207 return Utf16EncodeMaybeCopyAndReturnLength(object->GetUnicodeText(),
208 buffer_span);
209 }
210
211 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment,FPDF_DOCUMENT document,const void * contents,unsigned long len)212 FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment,
213 FPDF_DOCUMENT document,
214 const void* contents,
215 unsigned long len) {
216 // An empty content must have a zero length.
217 if (!contents && len != 0) {
218 return false;
219 }
220
221 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment);
222 CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
223 if (!pFile || !pFile->IsDictionary() || !pDoc || len > INT_MAX) {
224 return false;
225 }
226
227 // Create a dictionary for the new embedded file stream.
228 auto pFileStreamDict = pdfium::MakeRetain<CPDF_Dictionary>();
229 auto pParamsDict = pFileStreamDict->SetNewFor<CPDF_Dictionary>("Params");
230
231 // Set the size of the new file in the dictionary.
232 pFileStreamDict->SetNewFor<CPDF_Number>(pdfium::stream::kDL,
233 static_cast<int>(len));
234 pParamsDict->SetNewFor<CPDF_Number>("Size", static_cast<int>(len));
235
236 // Set the creation date of the new attachment in the dictionary.
237 CFX_DateTime dateTime = CFX_DateTime::Now();
238 pParamsDict->SetNewFor<CPDF_String>(
239 "CreationDate",
240 ByteString::Format("D:%d%02d%02d%02d%02d%02d", dateTime.GetYear(),
241 dateTime.GetMonth(), dateTime.GetDay(),
242 dateTime.GetHour(), dateTime.GetMinute(),
243 dateTime.GetSecond()));
244
245 // SAFETY: required from caller.
246 pdfium::span<const uint8_t> contents_span = UNSAFE_BUFFERS(
247 pdfium::make_span(static_cast<const uint8_t*>(contents), len));
248
249 std::array<uint8_t, 16> digest;
250 CRYPT_MD5Generate(contents_span, digest);
251
252 // Set the checksum of the new attachment in the dictionary.
253 pParamsDict->SetNewFor<CPDF_String>(kChecksumKey, digest,
254 CPDF_String::DataType::kIsHex);
255
256 // Create the file stream and have the filespec dictionary link to it.
257 auto pFileStream = pDoc->NewIndirect<CPDF_Stream>(
258 DataVector<uint8_t>(contents_span.begin(), contents_span.end()),
259 std::move(pFileStreamDict));
260
261 auto pEFDict = pFile->AsMutableDictionary()->SetNewFor<CPDF_Dictionary>("EF");
262 pEFDict->SetNewFor<CPDF_Reference>("F", pDoc, pFileStream->GetObjNum());
263 return true;
264 }
265
266 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment,void * buffer,unsigned long buflen,unsigned long * out_buflen)267 FPDFAttachment_GetFile(FPDF_ATTACHMENT attachment,
268 void* buffer,
269 unsigned long buflen,
270 unsigned long* out_buflen) {
271 if (!out_buflen)
272 return false;
273
274 CPDF_Object* pFile = CPDFObjectFromFPDFAttachment(attachment);
275 if (!pFile)
276 return false;
277
278 CPDF_FileSpec spec(pdfium::WrapRetain(pFile));
279 RetainPtr<const CPDF_Stream> pFileStream = spec.GetFileStream();
280 if (!pFileStream)
281 return false;
282
283 // SAFETY: required from caller.
284 *out_buflen = DecodeStreamMaybeCopyAndReturnLength(
285 std::move(pFileStream),
286 UNSAFE_BUFFERS(pdfium::make_span(static_cast<uint8_t*>(buffer),
287 static_cast<size_t>(buflen))));
288 return true;
289 }
290