1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "FormData.h"
8 #include "nsIInputStream.h"
9 #include "mozilla/dom/File.h"
10 #include "mozilla/dom/Directory.h"
11 #include "mozilla/dom/HTMLFormElement.h"
12 #include "mozilla/Encoding.h"
13 
14 #include "MultipartBlobImpl.h"
15 
16 using namespace mozilla;
17 using namespace mozilla::dom;
18 
FormData(nsISupports * aOwner,NotNull<const Encoding * > aEncoding,Element * aSubmitter)19 FormData::FormData(nsISupports* aOwner, NotNull<const Encoding*> aEncoding,
20                    Element* aSubmitter)
21     : HTMLFormSubmission(nullptr, u""_ns, aEncoding),
22       mOwner(aOwner),
23       mSubmitter(aSubmitter) {}
24 
FormData(const FormData & aFormData)25 FormData::FormData(const FormData& aFormData)
26     : HTMLFormSubmission(aFormData.mActionURL, aFormData.mTarget,
27                          aFormData.mEncoding) {
28   mOwner = aFormData.mOwner;
29   mSubmitter = aFormData.mSubmitter;
30   mFormData = aFormData.mFormData.Clone();
31 }
32 
33 namespace {
34 
GetOrCreateFileCalledBlob(Blob & aBlob,ErrorResult & aRv)35 already_AddRefed<File> GetOrCreateFileCalledBlob(Blob& aBlob,
36                                                  ErrorResult& aRv) {
37   // If this is file, we can just use it
38   RefPtr<File> file = aBlob.ToFile();
39   if (file) {
40     return file.forget();
41   }
42 
43   // Forcing 'blob' as filename
44   file = aBlob.ToFile(u"blob"_ns, aRv);
45   if (NS_WARN_IF(aRv.Failed())) {
46     return nullptr;
47   }
48 
49   return file.forget();
50 }
51 
GetBlobForFormDataStorage(Blob & aBlob,const Optional<nsAString> & aFilename,ErrorResult & aRv)52 already_AddRefed<File> GetBlobForFormDataStorage(
53     Blob& aBlob, const Optional<nsAString>& aFilename, ErrorResult& aRv) {
54   // Forcing a filename
55   if (aFilename.WasPassed()) {
56     RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv);
57     if (NS_WARN_IF(aRv.Failed())) {
58       return nullptr;
59     }
60 
61     return file.forget();
62   }
63 
64   return GetOrCreateFileCalledBlob(aBlob, aRv);
65 }
66 
67 }  // namespace
68 
69 // -------------------------------------------------------------------------
70 // nsISupports
71 
72 NS_IMPL_CYCLE_COLLECTION_CLASS(FormData)
73 
74 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData)
75   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
76   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubmitter)
77 
78   for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) {
79     ImplCycleCollectionUnlink(tmp->mFormData[i].value);
80   }
81 
82   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
83 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
84 
85 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData)
86   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
87   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubmitter)
88 
89   for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) {
90     ImplCycleCollectionTraverse(cb, tmp->mFormData[i].value,
91                                 "mFormData[i].GetAsBlob()", 0);
92   }
93 
94 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
95 
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FormData)96 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FormData)
97 
98 NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData)
99 NS_IMPL_CYCLE_COLLECTING_RELEASE(FormData)
100 
101 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FormData)
102   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
103   NS_INTERFACE_MAP_ENTRY(nsISupports)
104 NS_INTERFACE_MAP_END
105 
106 // -------------------------------------------------------------------------
107 // HTMLFormSubmission
108 nsresult FormData::GetEncodedSubmission(nsIURI* aURI,
109                                         nsIInputStream** aPostDataStream,
110                                         nsCOMPtr<nsIURI>& aOutURI) {
111   MOZ_ASSERT_UNREACHABLE("Shouldn't call FormData::GetEncodedSubmission");
112   return NS_OK;
113 }
114 
Append(const nsAString & aName,const nsAString & aValue,ErrorResult & aRv)115 void FormData::Append(const nsAString& aName, const nsAString& aValue,
116                       ErrorResult& aRv) {
117   AddNameValuePair(aName, aValue);
118 }
119 
Append(const nsAString & aName,Blob & aBlob,const Optional<nsAString> & aFilename,ErrorResult & aRv)120 void FormData::Append(const nsAString& aName, Blob& aBlob,
121                       const Optional<nsAString>& aFilename, ErrorResult& aRv) {
122   RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
123   if (NS_WARN_IF(aRv.Failed())) {
124     return;
125   }
126 
127   AddNameBlobPair(aName, file);
128 }
129 
Append(const nsAString & aName,Directory * aDirectory)130 void FormData::Append(const nsAString& aName, Directory* aDirectory) {
131   AddNameDirectoryPair(aName, aDirectory);
132 }
133 
Append(const FormData & aFormData)134 void FormData::Append(const FormData& aFormData) {
135   for (uint32_t i = 0; i < aFormData.mFormData.Length(); ++i) {
136     mFormData.AppendElement(aFormData.mFormData[i]);
137   }
138 }
139 
Delete(const nsAString & aName)140 void FormData::Delete(const nsAString& aName) {
141   mFormData.RemoveElementsBy([&aName](const auto& formDataItem) {
142     return aName.Equals(formDataItem.name);
143   });
144 }
145 
Get(const nsAString & aName,Nullable<OwningBlobOrDirectoryOrUSVString> & aOutValue)146 void FormData::Get(const nsAString& aName,
147                    Nullable<OwningBlobOrDirectoryOrUSVString>& aOutValue) {
148   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
149     if (aName.Equals(mFormData[i].name)) {
150       aOutValue.SetValue() = mFormData[i].value;
151       return;
152     }
153   }
154 
155   aOutValue.SetNull();
156 }
157 
GetAll(const nsAString & aName,nsTArray<OwningBlobOrDirectoryOrUSVString> & aValues)158 void FormData::GetAll(const nsAString& aName,
159                       nsTArray<OwningBlobOrDirectoryOrUSVString>& aValues) {
160   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
161     if (aName.Equals(mFormData[i].name)) {
162       OwningBlobOrDirectoryOrUSVString* element = aValues.AppendElement();
163       *element = mFormData[i].value;
164     }
165   }
166 }
167 
Has(const nsAString & aName)168 bool FormData::Has(const nsAString& aName) {
169   for (uint32_t i = 0; i < mFormData.Length(); ++i) {
170     if (aName.Equals(mFormData[i].name)) {
171       return true;
172     }
173   }
174 
175   return false;
176 }
177 
AddNameBlobPair(const nsAString & aName,Blob * aBlob)178 nsresult FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
179   MOZ_ASSERT(aBlob);
180 
181   nsAutoString usvName(aName);
182   if (!NormalizeUSVString(usvName)) {
183     return NS_ERROR_OUT_OF_MEMORY;
184   }
185 
186   RefPtr<File> file;
187   ErrorResult rv;
188   file = GetOrCreateFileCalledBlob(*aBlob, rv);
189   if (NS_WARN_IF(rv.Failed())) {
190     return rv.StealNSResult();
191   }
192 
193   FormDataTuple* data = mFormData.AppendElement();
194   SetNameFilePair(data, usvName, file);
195   return NS_OK;
196 }
197 
AddNameDirectoryPair(const nsAString & aName,Directory * aDirectory)198 nsresult FormData::AddNameDirectoryPair(const nsAString& aName,
199                                         Directory* aDirectory) {
200   MOZ_ASSERT(aDirectory);
201 
202   nsAutoString usvName(aName);
203   if (!NormalizeUSVString(usvName)) {
204     return NS_ERROR_OUT_OF_MEMORY;
205   }
206 
207   FormDataTuple* data = mFormData.AppendElement();
208   SetNameDirectoryPair(data, usvName, aDirectory);
209   return NS_OK;
210 }
211 
RemoveAllOthersAndGetFirstFormDataTuple(const nsAString & aName)212 FormData::FormDataTuple* FormData::RemoveAllOthersAndGetFirstFormDataTuple(
213     const nsAString& aName) {
214   FormDataTuple* lastFoundTuple = nullptr;
215   uint32_t lastFoundIndex = mFormData.Length();
216   // We have to use this slightly awkward for loop since uint32_t >= 0 is an
217   // error for being always true.
218   for (uint32_t i = mFormData.Length(); i-- > 0;) {
219     if (aName.Equals(mFormData[i].name)) {
220       if (lastFoundTuple) {
221         // The one we found earlier was not the first one, we can remove it.
222         mFormData.RemoveElementAt(lastFoundIndex);
223       }
224 
225       lastFoundTuple = &mFormData[i];
226       lastFoundIndex = i;
227     }
228   }
229 
230   return lastFoundTuple;
231 }
232 
Set(const nsAString & aName,Blob & aBlob,const Optional<nsAString> & aFilename,ErrorResult & aRv)233 void FormData::Set(const nsAString& aName, Blob& aBlob,
234                    const Optional<nsAString>& aFilename, ErrorResult& aRv) {
235   FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
236   if (tuple) {
237     RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv);
238     if (NS_WARN_IF(aRv.Failed())) {
239       return;
240     }
241 
242     SetNameFilePair(tuple, aName, file);
243   } else {
244     Append(aName, aBlob, aFilename, aRv);
245   }
246 }
247 
Set(const nsAString & aName,const nsAString & aValue,ErrorResult & aRv)248 void FormData::Set(const nsAString& aName, const nsAString& aValue,
249                    ErrorResult& aRv) {
250   FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName);
251   if (tuple) {
252     SetNameValuePair(tuple, aName, aValue);
253   } else {
254     Append(aName, aValue, aRv);
255   }
256 }
257 
GetIterableLength() const258 uint32_t FormData::GetIterableLength() const { return mFormData.Length(); }
259 
GetKeyAtIndex(uint32_t aIndex) const260 const nsAString& FormData::GetKeyAtIndex(uint32_t aIndex) const {
261   MOZ_ASSERT(aIndex < mFormData.Length());
262   return mFormData[aIndex].name;
263 }
264 
GetValueAtIndex(uint32_t aIndex) const265 const OwningBlobOrDirectoryOrUSVString& FormData::GetValueAtIndex(
266     uint32_t aIndex) const {
267   MOZ_ASSERT(aIndex < mFormData.Length());
268   return mFormData[aIndex].value;
269 }
270 
SetNameValuePair(FormDataTuple * aData,const nsAString & aName,const nsAString & aValue)271 void FormData::SetNameValuePair(FormDataTuple* aData, const nsAString& aName,
272                                 const nsAString& aValue) {
273   MOZ_ASSERT(aData);
274   aData->name = aName;
275   aData->value.SetAsUSVString() = aValue;
276 }
277 
SetNameFilePair(FormDataTuple * aData,const nsAString & aName,File * aFile)278 void FormData::SetNameFilePair(FormDataTuple* aData, const nsAString& aName,
279                                File* aFile) {
280   MOZ_ASSERT(aData);
281   MOZ_ASSERT(aFile);
282 
283   aData->name = aName;
284   aData->value.SetAsBlob() = aFile;
285 }
286 
SetNameDirectoryPair(FormDataTuple * aData,const nsAString & aName,Directory * aDirectory)287 void FormData::SetNameDirectoryPair(FormDataTuple* aData,
288                                     const nsAString& aName,
289                                     Directory* aDirectory) {
290   MOZ_ASSERT(aData);
291   MOZ_ASSERT(aDirectory);
292 
293   aData->name = aName;
294   aData->value.SetAsDirectory() = aDirectory;
295 }
296 
297 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)298 JSObject* FormData::WrapObject(JSContext* aCx,
299                                JS::Handle<JSObject*> aGivenProto) {
300   return FormData_Binding::Wrap(aCx, this, aGivenProto);
301 }
302 
303 /* static */
Constructor(const GlobalObject & aGlobal,const Optional<NonNull<HTMLFormElement>> & aFormElement,ErrorResult & aRv)304 already_AddRefed<FormData> FormData::Constructor(
305     const GlobalObject& aGlobal,
306     const Optional<NonNull<HTMLFormElement> >& aFormElement, ErrorResult& aRv) {
307   RefPtr<FormData> formData = new FormData(aGlobal.GetAsSupports());
308   if (aFormElement.WasPassed()) {
309     aRv = aFormElement.Value().ConstructEntryList(formData);
310     if (NS_WARN_IF(aRv.Failed())) {
311       return nullptr;
312     }
313 
314     // Step 9. Return a shallow clone of entry list.
315     // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set
316     formData = formData->Clone();
317   }
318 
319   return formData.forget();
320 }
321 
322 // contentTypeWithCharset can be set to the contentType or
323 // contentType+charset based on what the spec says.
324 // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
GetSendInfo(nsIInputStream ** aBody,uint64_t * aContentLength,nsACString & aContentTypeWithCharset,nsACString & aCharset) const325 nsresult FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
326                                nsACString& aContentTypeWithCharset,
327                                nsACString& aCharset) const {
328   FSMultipartFormData fs(nullptr, u""_ns, UTF_8_ENCODING, nullptr);
329   nsresult rv = CopySubmissionDataTo(&fs);
330   NS_ENSURE_SUCCESS(rv, rv);
331 
332   fs.GetContentType(aContentTypeWithCharset);
333   aCharset.Truncate();
334   *aContentLength = 0;
335   NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
336 
337   return NS_OK;
338 }
339 
Clone()340 already_AddRefed<FormData> FormData::Clone() {
341   RefPtr<FormData> formData = new FormData(*this);
342   return formData.forget();
343 }
344 
CopySubmissionDataTo(HTMLFormSubmission * aFormSubmission) const345 nsresult FormData::CopySubmissionDataTo(
346     HTMLFormSubmission* aFormSubmission) const {
347   MOZ_ASSERT(aFormSubmission, "Must have FormSubmission!");
348   for (size_t i = 0; i < mFormData.Length(); ++i) {
349     if (mFormData[i].value.IsUSVString()) {
350       aFormSubmission->AddNameValuePair(mFormData[i].name,
351                                         mFormData[i].value.GetAsUSVString());
352     } else if (mFormData[i].value.IsBlob()) {
353       aFormSubmission->AddNameBlobPair(mFormData[i].name,
354                                        mFormData[i].value.GetAsBlob());
355     } else {
356       MOZ_ASSERT(mFormData[i].value.IsDirectory());
357       aFormSubmission->AddNameDirectoryPair(
358           mFormData[i].name, mFormData[i].value.GetAsDirectory());
359     }
360   }
361 
362   return NS_OK;
363 }
364