Eliminate BlobStorageData
[WebKit.git] / Source / WebCore / platform / network / FormData.cpp
1 /*
2  * Copyright (C) 2004, 2006, 2008, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Google Inc. All rights reserved.
4  * Copyright (C) 2012 Digia Plc. and/or its subsidiary(-ies)
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB. If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 #include "config.h"
23
24 #include "FormData.h"
25
26 #include "BlobRegistryImpl.h"
27 #include "BlobURL.h"
28 #include "Chrome.h"
29 #include "ChromeClient.h"
30 #include "Document.h"
31 #include "File.h"
32 #include "FileSystem.h"
33 #include "FormDataBuilder.h"
34 #include "FormDataList.h"
35 #include "KeyedCoding.h"
36 #include "MIMETypeRegistry.h"
37 #include "Page.h"
38 #include "TextEncoding.h"
39 #include <wtf/Decoder.h>
40 #include <wtf/Encoder.h>
41
42 namespace WebCore {
43
44 inline FormData::FormData()
45     : m_identifier(0)
46     , m_alwaysStream(false)
47     , m_containsPasswordData(false)
48 {
49 }
50
51 inline FormData::FormData(const FormData& data)
52     : RefCounted<FormData>()
53     , m_elements(data.m_elements)
54     , m_identifier(data.m_identifier)
55     , m_alwaysStream(false)
56     , m_containsPasswordData(data.m_containsPasswordData)
57 {
58     // We shouldn't be copying FormData that hasn't already removed its generated files
59     // but just in case, make sure the new FormData is ready to generate its own files.
60     for (FormDataElement& element : m_elements) {
61         if (element.m_type == FormDataElement::Type::EncodedFile) {
62             element.m_generatedFilename = String();
63             element.m_ownsGeneratedFile = false;
64         }
65     }
66 }
67
68 FormData::~FormData()
69 {
70     // This cleanup should've happened when the form submission finished.
71     // Just in case, let's assert, and do the cleanup anyway in release builds.
72     ASSERT(!hasOwnedGeneratedFiles());
73     removeGeneratedFilesIfNeeded();
74 }
75
76 PassRefPtr<FormData> FormData::create()
77 {
78     return adoptRef(new FormData);
79 }
80
81 PassRefPtr<FormData> FormData::create(const void* data, size_t size)
82 {
83     RefPtr<FormData> result = create();
84     result->appendData(data, size);
85     return result.release();
86 }
87
88 PassRefPtr<FormData> FormData::create(const CString& string)
89 {
90     RefPtr<FormData> result = create();
91     result->appendData(string.data(), string.length());
92     return result.release();
93 }
94
95 PassRefPtr<FormData> FormData::create(const Vector<char>& vector)
96 {
97     RefPtr<FormData> result = create();
98     result->appendData(vector.data(), vector.size());
99     return result.release();
100 }
101
102 PassRefPtr<FormData> FormData::create(const FormDataList& list, const TextEncoding& encoding, EncodingType encodingType)
103 {
104     RefPtr<FormData> result = create();
105     result->appendKeyValuePairItems(list, encoding, false, 0, encodingType);
106     return result.release();
107 }
108
109 PassRefPtr<FormData> FormData::createMultiPart(const FormDataList& list, const TextEncoding& encoding, Document* document)
110 {
111     RefPtr<FormData> result = create();
112     result->appendKeyValuePairItems(list, encoding, true, document);
113     return result.release();
114 }
115
116 PassRefPtr<FormData> FormData::copy() const
117 {
118     return adoptRef(new FormData(*this));
119 }
120
121 PassRefPtr<FormData> FormData::deepCopy() const
122 {
123     RefPtr<FormData> formData(create());
124
125     formData->m_alwaysStream = m_alwaysStream;
126
127     formData->m_elements.reserveInitialCapacity(m_elements.size());
128     for (const FormDataElement& element : m_elements) {
129         switch (element.m_type) {
130         case FormDataElement::Type::Data:
131             formData->m_elements.uncheckedAppend(FormDataElement(element.m_data));
132             break;
133         case FormDataElement::Type::EncodedFile:
134 #if ENABLE(BLOB)
135             formData->m_elements.uncheckedAppend(FormDataElement(element.m_filename, element.m_fileStart, element.m_fileLength, element.m_expectedFileModificationTime, element.m_shouldGenerateFile));
136 #else
137             formData->m_elements.uncheckedAppend(FormDataElement(element.m_filename, element.m_shouldGenerateFile));
138 #endif
139             break;
140 #if ENABLE(BLOB)
141         case FormDataElement::Type::EncodedBlob:
142             formData->m_elements.uncheckedAppend(FormDataElement(element.m_url));
143             break;
144 #endif
145         }
146     }
147     return formData.release();
148 }
149
150 void FormData::appendData(const void* data, size_t size)
151 {
152     memcpy(expandDataStore(size), data, size);
153 }
154
155 void FormData::appendFile(const String& filename, bool shouldGenerateFile)
156 {
157 #if ENABLE(BLOB)
158     m_elements.append(FormDataElement(filename, 0, BlobDataItem::toEndOfFile, invalidFileTime(), shouldGenerateFile));
159 #else
160     m_elements.append(FormDataElement(filename, shouldGenerateFile));
161 #endif
162 }
163
164 #if ENABLE(BLOB)
165 void FormData::appendFileRange(const String& filename, long long start, long long length, double expectedModificationTime, bool shouldGenerateFile)
166 {
167     m_elements.append(FormDataElement(filename, start, length, expectedModificationTime, shouldGenerateFile));
168 }
169
170 void FormData::appendBlob(const URL& blobURL)
171 {
172     m_elements.append(FormDataElement(blobURL));
173 }
174 #endif
175
176 void FormData::appendKeyValuePairItems(const FormDataList& list, const TextEncoding& encoding, bool isMultiPartForm, Document* document, EncodingType encodingType)
177 {
178     if (isMultiPartForm)
179         m_boundary = FormDataBuilder::generateUniqueBoundaryString();
180
181     Vector<char> encodedData;
182
183     const Vector<FormDataList::Item>& items = list.items();
184     size_t formDataListSize = items.size();
185     ASSERT(!(formDataListSize % 2));
186     for (size_t i = 0; i < formDataListSize; i += 2) {
187         const FormDataList::Item& key = items[i];
188         const FormDataList::Item& value = items[i + 1];
189         if (isMultiPartForm) {
190             Vector<char> header;
191             FormDataBuilder::beginMultiPartHeader(header, m_boundary.data(), key.data());
192
193             bool shouldGenerateFile = false;
194
195             // If the current type is blob, then we also need to include the filename
196             if (value.blob()) {
197                 String name;
198                 if (value.blob()->isFile()) {
199                     File* file = toFile(value.blob());
200                     name = file->name();
201                     // Let the application specify a filename if it's going to generate a replacement file for the upload.
202                     const String& path = file->path();
203                     if (!path.isEmpty()) {
204                         if (Page* page = document->page()) {
205                             String generatedFileName;
206                             shouldGenerateFile = page->chrome().client().shouldReplaceWithGeneratedFileForUpload(path, generatedFileName);
207                             if (shouldGenerateFile)
208                                 name = generatedFileName;
209                         }
210                     }
211
212                     // If a filename is passed in FormData.append(), use it instead of the file blob's name.
213                     if (!value.filename().isNull())
214                         name = value.filename();
215                 } else {
216                     // For non-file blob, use the filename if it is passed in FormData.append().
217                     if (!value.filename().isNull())
218                         name = value.filename();
219                     else
220                         name = "blob";
221                 }
222
223                 // We have to include the filename=".." part in the header, even if the filename is empty
224                 FormDataBuilder::addFilenameToMultiPartHeader(header, encoding, name);
225
226                 // Add the content type if available, or "application/octet-stream" otherwise (RFC 1867).
227                 String contentType = value.blob()->type();
228                 if (contentType.isEmpty())
229                     contentType = "application/octet-stream";
230                 ASSERT(Blob::isNormalizedContentType(contentType));
231                 FormDataBuilder::addContentTypeToMultiPartHeader(header, contentType.ascii());
232             }
233
234             FormDataBuilder::finishMultiPartHeader(header);
235
236             // Append body
237             appendData(header.data(), header.size());
238             if (value.blob()) {
239                 if (value.blob()->isFile()) {
240                     File* file = toFile(value.blob());
241                     // Do not add the file if the path is empty.
242                     if (!file->path().isEmpty())
243                         appendFile(file->path(), shouldGenerateFile);
244                 }
245 #if ENABLE(BLOB)
246                 else
247                     appendBlob(value.blob()->url());
248 #endif
249             } else
250                 appendData(value.data().data(), value.data().length());
251             appendData("\r\n", 2);
252         } else {
253             // Omit the name "isindex" if it's the first form data element.
254             // FIXME: Why is this a good rule? Is this obsolete now?
255             if (encodedData.isEmpty() && key.data() == "isindex")
256                 FormDataBuilder::encodeStringAsFormData(encodedData, value.data());
257             else
258                 FormDataBuilder::addKeyValuePairAsFormData(encodedData, key.data(), value.data(), encodingType);
259         }
260     }
261
262     if (isMultiPartForm)
263         FormDataBuilder::addBoundaryToMultiPartHeader(encodedData, m_boundary.data(), true);
264
265     appendData(encodedData.data(), encodedData.size());
266 }
267
268 char* FormData::expandDataStore(size_t size)
269 {
270     if (m_elements.isEmpty() || m_elements.last().m_type != FormDataElement::Type::Data)
271         m_elements.append(FormDataElement());
272     FormDataElement& e = m_elements.last();
273     size_t oldSize = e.m_data.size();
274     e.m_data.grow(oldSize + size);
275     return e.m_data.data() + oldSize;
276 }
277
278 void FormData::flatten(Vector<char>& data) const
279 {
280     // Concatenate all the byte arrays, but omit any files.
281     data.clear();
282     size_t n = m_elements.size();
283     for (size_t i = 0; i < n; ++i) {
284         const FormDataElement& e = m_elements[i];
285         if (e.m_type == FormDataElement::Type::Data)
286             data.append(e.m_data.data(), static_cast<size_t>(e.m_data.size()));
287     }
288 }
289
290 String FormData::flattenToString() const
291 {
292     Vector<char> bytes;
293     flatten(bytes);
294     return Latin1Encoding().decode(reinterpret_cast<const char*>(bytes.data()), bytes.size());
295 }
296
297 #if ENABLE(BLOB)
298 static void appendBlobResolved(FormData* formData, const URL& url)
299 {
300     if (!blobRegistry().isBlobRegistryImpl()) {
301         LOG_ERROR("Tried to resolve a blob without a usable registry");
302         return;
303     }
304     BlobData* blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(URL(ParsedURLString, url));
305     if (!blobData) {
306         LOG_ERROR("Could not get blob data from a registry");
307         return;
308     }
309
310     BlobDataItemList::const_iterator it = blobData->items().begin();
311     const BlobDataItemList::const_iterator itend = blobData->items().end();
312     for (; it != itend; ++it) {
313         const BlobDataItem& blobItem = *it;
314         if (blobItem.type == BlobDataItem::Data)
315             formData->appendData(blobItem.data->data() + static_cast<int>(blobItem.offset), static_cast<int>(blobItem.length));
316         else if (blobItem.type == BlobDataItem::File)
317             formData->appendFileRange(blobItem.path, blobItem.offset, blobItem.length, blobItem.expectedModificationTime);
318         else if (blobItem.type == BlobDataItem::Blob)
319             appendBlobResolved(formData, blobItem.url);
320         else
321             ASSERT_NOT_REACHED();
322     }
323 }
324
325 PassRefPtr<FormData> FormData::resolveBlobReferences()
326 {
327     // First check if any blobs needs to be resolved, or we can take the fast path.
328     bool hasBlob = false;
329     Vector<FormDataElement>::const_iterator it = elements().begin();
330     const Vector<FormDataElement>::const_iterator itend = elements().end();
331     for (; it != itend; ++it) {
332         if (it->m_type == FormDataElement::Type::EncodedBlob) {
333             hasBlob = true;
334             break;
335         }
336     }
337
338     if (!hasBlob)
339         return this;
340
341     // Create a copy to append the result into.
342     RefPtr<FormData> newFormData = FormData::create();
343     newFormData->setAlwaysStream(alwaysStream());
344     newFormData->setIdentifier(identifier());
345     it = elements().begin();
346     for (; it != itend; ++it) {
347         const FormDataElement& element = *it;
348         if (element.m_type == FormDataElement::Type::Data)
349             newFormData->appendData(element.m_data.data(), element.m_data.size());
350         else if (element.m_type == FormDataElement::Type::EncodedFile)
351             newFormData->appendFileRange(element.m_filename, element.m_fileStart, element.m_fileLength, element.m_expectedFileModificationTime, element.m_shouldGenerateFile);
352         else if (element.m_type == FormDataElement::Type::EncodedBlob)
353             appendBlobResolved(newFormData.get(), element.m_url);
354         else
355             ASSERT_NOT_REACHED();
356     }
357     return newFormData.release();
358 }
359 #endif
360
361 void FormData::generateFiles(Document* document)
362 {
363     Page* page = document->page();
364     if (!page)
365         return;
366
367     for (FormDataElement& element : m_elements) {
368         if (element.m_type == FormDataElement::Type::EncodedFile && element.m_shouldGenerateFile) {
369             ASSERT(!element.m_ownsGeneratedFile);
370             ASSERT(element.m_generatedFilename.isEmpty());
371             if (!element.m_generatedFilename.isEmpty())
372                 continue;
373             element.m_generatedFilename = page->chrome().client().generateReplacementFile(element.m_filename);
374             if (!element.m_generatedFilename.isEmpty())
375                 element.m_ownsGeneratedFile = true;
376         }
377     }
378 }
379
380 bool FormData::hasGeneratedFiles() const
381 {
382     for (const FormDataElement& element : m_elements) {
383         if (element.m_type == FormDataElement::Type::EncodedFile && !element.m_generatedFilename.isEmpty())
384             return true;
385     }
386     return false;
387 }
388
389 bool FormData::hasOwnedGeneratedFiles() const
390 {
391     for (const FormDataElement& element : m_elements) {
392         if (element.m_type == FormDataElement::Type::EncodedFile && element.m_ownsGeneratedFile) {
393             ASSERT(!element.m_generatedFilename.isEmpty());
394             return true;
395         }
396     }
397     return false;
398 }
399
400 void FormData::removeGeneratedFilesIfNeeded()
401 {
402     for (FormDataElement& element : m_elements) {
403         if (element.m_type == FormDataElement::Type::EncodedFile && element.m_ownsGeneratedFile) {
404             ASSERT(!element.m_generatedFilename.isEmpty());
405             ASSERT(element.m_shouldGenerateFile);
406             String directory = directoryName(element.m_generatedFilename);
407             deleteFile(element.m_generatedFilename);
408             deleteEmptyDirectory(directory);
409             element.m_generatedFilename = String();
410             element.m_ownsGeneratedFile = false;
411         }
412     }
413 }
414
415 static void encodeElement(Encoder& encoder, const FormDataElement& element)
416 {
417     encoder.encodeUInt32(static_cast<uint32_t>(element.m_type));
418
419     switch (element.m_type) {
420     case FormDataElement::Type::Data:
421         encoder.encodeBytes(reinterpret_cast<const uint8_t*>(element.m_data.data()), element.m_data.size());
422         return;
423
424     case FormDataElement::Type::EncodedFile:
425         encoder.encodeString(element.m_filename);
426         encoder.encodeString(element.m_generatedFilename);
427         encoder.encodeBool(element.m_shouldGenerateFile);
428 #if ENABLE(BLOB)
429         encoder.encodeInt64(element.m_fileStart);
430         encoder.encodeInt64(element.m_fileLength);
431         encoder.encodeDouble(element.m_expectedFileModificationTime);
432 #else
433         encoder.encodeInt64(0);
434         encoder.encodeInt64(0);
435         encoder.encodeDouble(invalidFileTime());
436 #endif
437         return;
438
439 #if ENABLE(BLOB)
440     case FormDataElement::Type::EncodedBlob:
441         encoder.encodeString(element.m_url.string());
442         return;
443 #endif
444     }
445
446     ASSERT_NOT_REACHED();
447 }
448
449 static void encodeElement(KeyedEncoder& encoder, const FormDataElement& element)
450 {
451     encoder.encodeEnum("type", element.m_type);
452
453     switch (element.m_type) {
454     case FormDataElement::Type::Data:
455         encoder.encodeBytes("data", reinterpret_cast<const uint8_t*>(element.m_data.data()), element.m_data.size());
456         return;
457     case FormDataElement::Type::EncodedFile:
458         encoder.encodeString("filename", element.m_filename);
459         encoder.encodeString("generatedFilename", element.m_generatedFilename);
460         encoder.encodeBool("shouldGenerateFile", element.m_shouldGenerateFile);
461 #if ENABLE(BLOB)
462         encoder.encodeInt64("fileStart", element.m_fileStart);
463         encoder.encodeInt64("fileLength", element.m_fileLength);
464         encoder.encodeDouble("expectedFileModificationTime", element.m_expectedFileModificationTime);
465 #endif
466         return;
467
468 #if ENABLE(BLOB)
469     case FormDataElement::Type::EncodedBlob:
470         encoder.encodeString("url", element.m_url.string());
471         return;
472 #endif
473     }
474
475     ASSERT_NOT_REACHED();
476 }
477
478 static bool decodeElement(Decoder& decoder, FormDataElement& element)
479 {
480     uint32_t type;
481     if (!decoder.decodeUInt32(type))
482         return false;
483
484     switch (static_cast<FormDataElement::Type>(type)) {
485     case FormDataElement::Type::Data: {
486         element.m_type = FormDataElement::Type::Data;
487         Vector<uint8_t> data;
488         if (!decoder.decodeBytes(data))
489             return false;
490         size_t size = data.size();
491         element.m_data.resize(size);
492         memcpy(element.m_data.data(), data.data(), size);
493         return true;
494     }
495
496     case FormDataElement::Type::EncodedFile: {
497         element.m_type = static_cast<FormDataElement::Type>(type);
498         String filenameOrURL;
499         if (!decoder.decodeString(filenameOrURL))
500             return false;
501         if (static_cast<FormDataElement::Type>(type) == FormDataElement::Type::EncodedFile) {
502             if (!decoder.decodeString(element.m_generatedFilename))
503                 return false;
504             if (!decoder.decodeBool(element.m_shouldGenerateFile))
505                 return false;
506         }
507         int64_t fileStart;
508         if (!decoder.decodeInt64(fileStart))
509             return false;
510         if (fileStart < 0)
511             return false;
512         int64_t fileLength;
513         if (!decoder.decodeInt64(fileLength))
514             return false;
515         if (fileLength != BlobDataItem::toEndOfFile && fileLength < fileStart)
516             return false;
517         double expectedFileModificationTime;
518         if (!decoder.decodeDouble(expectedFileModificationTime))
519             return false;
520
521         element.m_filename = filenameOrURL;
522 #if ENABLE(BLOB)
523         element.m_fileStart = fileStart;
524         element.m_fileLength = fileLength;
525         element.m_expectedFileModificationTime = expectedFileModificationTime;
526 #endif
527         return true;
528     }
529
530 #if ENABLE(BLOB)
531     case FormDataElement::Type::EncodedBlob:
532         element.m_type = FormDataElement::Type::EncodedBlob;
533         String blobURLString;
534         if (!decoder.decodeString(blobURLString))
535             return false;
536         element.m_url = URL(URL(), blobURLString);
537         return true;
538 #endif
539
540     }
541
542     return false;
543 }
544
545 static bool decodeElement(KeyedDecoder& decoder, FormDataElement& element)
546 {
547     if (!decoder.decodeEnum("type", element.m_type, [](FormDataElement::Type type) {
548         switch (type) {
549         case FormDataElement::Type::Data:
550         case FormDataElement::Type::EncodedFile:
551 #if ENABLE(BLOB)
552         case FormDataElement::Type::EncodedBlob:
553 #endif
554             return true;
555         }
556
557         return false;
558     }))
559         return false;
560
561     switch (element.m_type) {
562     case FormDataElement::Type::Data:
563         if (!decoder.decodeBytes("data", element.m_data))
564             return false;
565         break;
566
567     case FormDataElement::Type::EncodedFile: {
568         if (!decoder.decodeString("filename", element.m_filename))
569             return false;
570         if (!decoder.decodeString("generatedFilename", element.m_generatedFilename))
571             return false;
572         if (!decoder.decodeBool("shouldGenerateFile", element.m_shouldGenerateFile))
573             return false;
574
575 #if ENABLE(BLOB)
576         int64_t fileStart;
577         if (!decoder.decodeInt64("fileStart", fileStart))
578             return false;
579         if (fileStart < 0)
580             return false;
581
582         int64_t fileLength;
583         if (!decoder.decodeInt64("fileLength", fileLength))
584             return false;
585         if (fileLength != BlobDataItem::toEndOfFile && fileLength < fileStart)
586             return false;
587
588         double expectedFileModificationTime;
589         if (!decoder.decodeDouble("expectedFileModificationTime", expectedFileModificationTime))
590             return false;
591
592         element.m_fileStart = fileStart;
593         element.m_fileLength = fileLength;
594         element.m_expectedFileModificationTime = expectedFileModificationTime;
595 #endif
596         break;
597     }
598
599 #if ENABLE(BLOB)
600     case FormDataElement::Type::EncodedBlob: {
601         String blobURLString;
602         if (!decoder.decodeString("url", blobURLString))
603             return false;
604
605         element.m_url = URL(URL(), blobURLString);
606         break;
607     }
608 #endif
609     }
610
611     return true;
612 }
613
614 void FormData::encode(Encoder& encoder) const
615 {
616     encoder.encodeBool(m_alwaysStream);
617
618     encoder.encodeBytes(reinterpret_cast<const uint8_t*>(m_boundary.data()), m_boundary.size());
619
620     size_t size = m_elements.size();
621     encoder.encodeUInt64(size);
622     for (size_t i = 0; i < size; ++i)
623         encodeElement(encoder, m_elements[i]);
624
625     encoder.encodeBool(hasGeneratedFiles()); // For backward compatibility.
626
627     encoder.encodeInt64(m_identifier);
628 }
629
630 void FormData::encode(KeyedEncoder& encoder) const
631 {
632     encoder.encodeBool("alwaysStream", m_alwaysStream);
633     encoder.encodeBytes("boundary", reinterpret_cast<const uint8_t*>(m_boundary.data()), m_boundary.size());
634
635     encoder.encodeObjects("elements", m_elements.begin(), m_elements.end(), [](KeyedEncoder& encoder, const FormDataElement& element) {
636         encodeElement(encoder, element);
637     });
638
639     encoder.encodeInt64("identifier", m_identifier);
640 }
641
642 PassRefPtr<FormData> FormData::decode(Decoder& decoder)
643 {
644     RefPtr<FormData> data = FormData::create();
645
646     if (!decoder.decodeBool(data->m_alwaysStream))
647         return 0;
648
649     Vector<uint8_t> boundary;
650     if (!decoder.decodeBytes(boundary))
651         return 0;
652     size_t size = boundary.size();
653     data->m_boundary.resize(size);
654     memcpy(data->m_boundary.data(), boundary.data(), size);
655
656     uint64_t elementsSize;
657     if (!decoder.decodeUInt64(elementsSize))
658         return 0;
659     for (size_t i = 0; i < elementsSize; ++i) {
660         FormDataElement element;
661         if (!decodeElement(decoder, element))
662             return 0;
663         data->m_elements.append(element);
664     }
665
666     bool dummy;
667     if (!decoder.decodeBool(dummy))
668         return 0;
669
670     if (!decoder.decodeInt64(data->m_identifier))
671         return 0;
672
673     return data.release();
674 }
675
676 PassRefPtr<FormData> FormData::decode(KeyedDecoder& decoder)
677 {
678     RefPtr<FormData> data = FormData::create();
679
680     if (!decoder.decodeBool("alwaysStream", data->m_alwaysStream))
681         return nullptr;
682
683     if (!decoder.decodeBytes("boundary", data->m_boundary))
684         return nullptr;
685
686     if (!decoder.decodeObjects("elements", data->m_elements, [](KeyedDecoder& decoder, FormDataElement& element) {
687         return decodeElement(decoder, element);
688     }))
689         return nullptr;
690
691     if (!decoder.decodeInt64("identifier", data->m_identifier))
692         return nullptr;
693
694     return data.release();
695 }
696
697 } // namespace WebCore