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/. */
8 #include "nsIInputStream.h"
9 #include "mozilla/dom/CustomElementTypes.h"
10 #include "mozilla/dom/File.h"
11 #include "mozilla/dom/Directory.h"
12 #include "mozilla/dom/HTMLFormElement.h"
13 #include "mozilla/Encoding.h"
14 #include "nsGenericHTMLElement.h"
15 #include "nsQueryObject.h"
17 #include "MultipartBlobImpl.h"
19 using namespace mozilla
;
20 using namespace mozilla::dom
;
22 FormData::FormData(nsISupports
* aOwner
, NotNull
<const Encoding
*> aEncoding
,
24 : HTMLFormSubmission(nullptr, u
""_ns
, aEncoding
),
26 mSubmitter(aSubmitter
) {}
28 FormData::FormData(const FormData
& aFormData
)
29 : HTMLFormSubmission(aFormData
.mActionURL
, aFormData
.mTarget
,
30 aFormData
.mEncoding
) {
31 mOwner
= aFormData
.mOwner
;
32 mSubmitter
= aFormData
.mSubmitter
;
33 mFormData
= aFormData
.mFormData
.Clone();
38 already_AddRefed
<File
> GetOrCreateFileCalledBlob(Blob
& aBlob
,
40 // If this is file, we can just use it
41 RefPtr
<File
> file
= aBlob
.ToFile();
46 // Forcing 'blob' as filename
47 file
= aBlob
.ToFile(u
"blob"_ns
, aRv
);
48 if (NS_WARN_IF(aRv
.Failed())) {
55 already_AddRefed
<File
> GetBlobForFormDataStorage(
56 Blob
& aBlob
, const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
58 if (aFilename
.WasPassed()) {
59 RefPtr
<File
> file
= aBlob
.ToFile(aFilename
.Value(), aRv
);
60 if (NS_WARN_IF(aRv
.Failed())) {
67 return GetOrCreateFileCalledBlob(aBlob
, aRv
);
72 // -------------------------------------------------------------------------
75 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FormData
)
77 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData
)
78 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
79 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubmitter
)
81 for (uint32_t i
= 0, len
= tmp
->mFormData
.Length(); i
< len
; ++i
) {
82 ImplCycleCollectionUnlink(tmp
->mFormData
[i
].value
);
85 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
86 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData
)
89 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
90 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubmitter
)
92 for (uint32_t i
= 0, len
= tmp
->mFormData
.Length(); i
< len
; ++i
) {
93 ImplCycleCollectionTraverse(cb
, tmp
->mFormData
[i
].value
,
94 "mFormData[i].GetAsBlob()", 0);
97 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
99 NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData
)
100 NS_IMPL_CYCLE_COLLECTING_RELEASE(FormData
)
102 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FormData
)
103 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
104 NS_INTERFACE_MAP_ENTRY(nsISupports
)
107 // -------------------------------------------------------------------------
108 // HTMLFormSubmission
109 nsresult
FormData::GetEncodedSubmission(nsIURI
* aURI
,
110 nsIInputStream
** aPostDataStream
,
111 nsCOMPtr
<nsIURI
>& aOutURI
) {
112 MOZ_ASSERT_UNREACHABLE("Shouldn't call FormData::GetEncodedSubmission");
116 void FormData::Append(const nsAString
& aName
, const nsAString
& aValue
,
118 AddNameValuePair(aName
, aValue
);
121 void FormData::Append(const nsAString
& aName
, Blob
& aBlob
,
122 const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
123 RefPtr
<File
> file
= GetBlobForFormDataStorage(aBlob
, aFilename
, aRv
);
124 if (NS_WARN_IF(aRv
.Failed())) {
128 AddNameBlobPair(aName
, file
);
131 void FormData::Append(const nsAString
& aName
, Directory
* aDirectory
) {
132 AddNameDirectoryPair(aName
, aDirectory
);
135 void FormData::Append(const FormData
& aFormData
) {
136 for (uint32_t i
= 0; i
< aFormData
.mFormData
.Length(); ++i
) {
137 mFormData
.AppendElement(aFormData
.mFormData
[i
]);
141 void FormData::Delete(const nsAString
& aName
) {
142 mFormData
.RemoveElementsBy([&aName
](const auto& formDataItem
) {
143 return aName
.Equals(formDataItem
.name
);
147 void FormData::Get(const nsAString
& aName
,
148 Nullable
<OwningBlobOrDirectoryOrUSVString
>& aOutValue
) {
149 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
150 if (aName
.Equals(mFormData
[i
].name
)) {
151 aOutValue
.SetValue() = mFormData
[i
].value
;
159 void FormData::GetAll(const nsAString
& aName
,
160 nsTArray
<OwningBlobOrDirectoryOrUSVString
>& aValues
) {
161 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
162 if (aName
.Equals(mFormData
[i
].name
)) {
163 OwningBlobOrDirectoryOrUSVString
* element
= aValues
.AppendElement();
164 *element
= mFormData
[i
].value
;
169 bool FormData::Has(const nsAString
& aName
) {
170 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
171 if (aName
.Equals(mFormData
[i
].name
)) {
179 nsresult
FormData::AddNameBlobPair(const nsAString
& aName
, Blob
* aBlob
) {
182 nsAutoString
usvName(aName
);
183 if (!NormalizeUSVString(usvName
)) {
184 return NS_ERROR_OUT_OF_MEMORY
;
189 file
= GetOrCreateFileCalledBlob(*aBlob
, rv
);
190 if (NS_WARN_IF(rv
.Failed())) {
191 return rv
.StealNSResult();
194 FormDataTuple
* data
= mFormData
.AppendElement();
195 SetNameFilePair(data
, usvName
, file
);
199 nsresult
FormData::AddNameDirectoryPair(const nsAString
& aName
,
200 Directory
* aDirectory
) {
201 MOZ_ASSERT(aDirectory
);
203 nsAutoString
usvName(aName
);
204 if (!NormalizeUSVString(usvName
)) {
205 return NS_ERROR_OUT_OF_MEMORY
;
208 FormDataTuple
* data
= mFormData
.AppendElement();
209 SetNameDirectoryPair(data
, usvName
, aDirectory
);
213 FormData::FormDataTuple
* FormData::RemoveAllOthersAndGetFirstFormDataTuple(
214 const nsAString
& aName
) {
215 FormDataTuple
* lastFoundTuple
= nullptr;
216 uint32_t lastFoundIndex
= mFormData
.Length();
217 // We have to use this slightly awkward for loop since uint32_t >= 0 is an
218 // error for being always true.
219 for (uint32_t i
= mFormData
.Length(); i
-- > 0;) {
220 if (aName
.Equals(mFormData
[i
].name
)) {
221 if (lastFoundTuple
) {
222 // The one we found earlier was not the first one, we can remove it.
223 mFormData
.RemoveElementAt(lastFoundIndex
);
226 lastFoundTuple
= &mFormData
[i
];
231 return lastFoundTuple
;
234 void FormData::Set(const nsAString
& aName
, Blob
& aBlob
,
235 const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
236 FormDataTuple
* tuple
= RemoveAllOthersAndGetFirstFormDataTuple(aName
);
238 RefPtr
<File
> file
= GetBlobForFormDataStorage(aBlob
, aFilename
, aRv
);
239 if (NS_WARN_IF(aRv
.Failed())) {
243 SetNameFilePair(tuple
, aName
, file
);
245 Append(aName
, aBlob
, aFilename
, aRv
);
249 void FormData::Set(const nsAString
& aName
, const nsAString
& aValue
,
251 FormDataTuple
* tuple
= RemoveAllOthersAndGetFirstFormDataTuple(aName
);
253 SetNameValuePair(tuple
, aName
, aValue
);
255 Append(aName
, aValue
, aRv
);
259 uint32_t FormData::GetIterableLength() const { return mFormData
.Length(); }
261 const nsAString
& FormData::GetKeyAtIndex(uint32_t aIndex
) const {
262 MOZ_ASSERT(aIndex
< mFormData
.Length());
263 return mFormData
[aIndex
].name
;
266 const OwningBlobOrDirectoryOrUSVString
& FormData::GetValueAtIndex(
267 uint32_t aIndex
) const {
268 MOZ_ASSERT(aIndex
< mFormData
.Length());
269 return mFormData
[aIndex
].value
;
272 void FormData::SetNameValuePair(FormDataTuple
* aData
, const nsAString
& aName
,
273 const nsAString
& aValue
) {
276 aData
->value
.SetAsUSVString() = aValue
;
279 void FormData::SetNameFilePair(FormDataTuple
* aData
, const nsAString
& aName
,
285 aData
->value
.SetAsBlob() = aFile
;
288 void FormData::SetNameDirectoryPair(FormDataTuple
* aData
,
289 const nsAString
& aName
,
290 Directory
* aDirectory
) {
292 MOZ_ASSERT(aDirectory
);
295 aData
->value
.SetAsDirectory() = aDirectory
;
299 JSObject
* FormData::WrapObject(JSContext
* aCx
,
300 JS::Handle
<JSObject
*> aGivenProto
) {
301 return FormData_Binding::Wrap(aCx
, this, aGivenProto
);
304 // https://xhr.spec.whatwg.org/#dom-formdata
306 already_AddRefed
<FormData
> FormData::Constructor(
307 const GlobalObject
& aGlobal
,
308 const Optional
<NonNull
<HTMLFormElement
> >& aFormElement
,
309 nsGenericHTMLElement
* aSubmitter
, ErrorResult
& aRv
) {
310 RefPtr
<FormData
> formData
;
311 // 1. If form is given, then:
312 if (aFormElement
.WasPassed()) {
313 // 1.1. If submitter is non-null, then:
315 const nsIFormControl
* fc
= nsIFormControl::FromNode(aSubmitter
);
317 // 1.1.1. If submitter is not a submit button, then throw a TypeError.
318 if (!fc
|| !fc
->IsSubmitControl()) {
319 aRv
.ThrowTypeError("The submitter is not a submit button.");
323 // 1.1.2. If submitter's form owner is not this form element, then throw a
324 // "NotFoundError" DOMException.
325 if (fc
->GetForm() != &aFormElement
.Value()) {
326 aRv
.ThrowNotFoundError("The submitter is not owned by this form.");
331 // 1.2. Let list be the result of constructing the entry list for form and
334 new FormData(aGlobal
.GetAsSupports(), UTF_8_ENCODING
, aSubmitter
);
335 aRv
= aFormElement
.Value().ConstructEntryList(formData
);
336 if (NS_WARN_IF(aRv
.Failed())) {
340 // Step 9. Return a shallow clone of entry list.
341 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set
342 formData
= formData
->Clone();
344 formData
= new FormData(aGlobal
.GetAsSupports());
347 return formData
.forget();
350 // contentTypeWithCharset can be set to the contentType or
351 // contentType+charset based on what the spec says.
352 // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
353 nsresult
FormData::GetSendInfo(nsIInputStream
** aBody
, uint64_t* aContentLength
,
354 nsACString
& aContentTypeWithCharset
,
355 nsACString
& aCharset
) const {
356 FSMultipartFormData
fs(nullptr, u
""_ns
, UTF_8_ENCODING
, nullptr);
357 nsresult rv
= CopySubmissionDataTo(&fs
);
358 NS_ENSURE_SUCCESS(rv
, rv
);
360 fs
.GetContentType(aContentTypeWithCharset
);
363 NS_ADDREF(*aBody
= fs
.GetSubmissionBody(aContentLength
));
368 already_AddRefed
<FormData
> FormData::Clone() {
369 RefPtr
<FormData
> formData
= new FormData(*this);
370 return formData
.forget();
373 nsresult
FormData::CopySubmissionDataTo(
374 HTMLFormSubmission
* aFormSubmission
) const {
375 MOZ_ASSERT(aFormSubmission
, "Must have FormSubmission!");
376 for (size_t i
= 0; i
< mFormData
.Length(); ++i
) {
377 if (mFormData
[i
].value
.IsUSVString()) {
378 aFormSubmission
->AddNameValuePair(mFormData
[i
].name
,
379 mFormData
[i
].value
.GetAsUSVString());
380 } else if (mFormData
[i
].value
.IsBlob()) {
381 aFormSubmission
->AddNameBlobPair(mFormData
[i
].name
,
382 mFormData
[i
].value
.GetAsBlob());
384 MOZ_ASSERT(mFormData
[i
].value
.IsDirectory());
385 aFormSubmission
->AddNameDirectoryPair(
386 mFormData
[i
].name
, mFormData
[i
].value
.GetAsDirectory());
393 CustomElementFormValue
FormData::ConvertToCustomElementFormValue() {
394 nsTArray
<mozilla::dom::FormDataTuple
> formValue
;
395 ForEach([&formValue
](const nsString
& aName
,
396 const OwningBlobOrDirectoryOrUSVString
& aValue
) -> bool {
397 if (aValue
.IsBlob()) {
398 FormDataValue
value(WrapNotNull(aValue
.GetAsBlob()->Impl()));
399 formValue
.AppendElement(mozilla::dom::FormDataTuple(aName
, value
));
400 } else if (aValue
.IsUSVString()) {
401 formValue
.AppendElement(
402 mozilla::dom::FormDataTuple(aName
, aValue
.GetAsUSVString()));
404 MOZ_ASSERT_UNREACHABLE("Can't save FormData entry Directory value!");