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