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/File.h"
10 #include "mozilla/dom/Directory.h"
11 #include "mozilla/dom/HTMLFormElement.h"
12 #include "mozilla/Encoding.h"
14 #include "MultipartBlobImpl.h"
16 using namespace mozilla
;
17 using namespace mozilla::dom
;
19 FormData::FormData(nsISupports
* aOwner
, NotNull
<const Encoding
*> aEncoding
,
21 : HTMLFormSubmission(nullptr, u
""_ns
, aEncoding
, aSubmitter
),
24 FormData::FormData(const FormData
& aFormData
)
25 : HTMLFormSubmission(aFormData
.mActionURL
, aFormData
.mTarget
,
26 aFormData
.mEncoding
, aFormData
.mSubmitter
) {
27 mOwner
= aFormData
.mOwner
;
28 mFormData
= aFormData
.mFormData
.Clone();
33 already_AddRefed
<File
> GetOrCreateFileCalledBlob(Blob
& aBlob
,
35 // If this is file, we can just use it
36 RefPtr
<File
> file
= aBlob
.ToFile();
41 // Forcing 'blob' as filename
42 file
= aBlob
.ToFile(u
"blob"_ns
, aRv
);
43 if (NS_WARN_IF(aRv
.Failed())) {
50 already_AddRefed
<File
> GetBlobForFormDataStorage(
51 Blob
& aBlob
, const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
53 if (aFilename
.WasPassed()) {
54 RefPtr
<File
> file
= aBlob
.ToFile(aFilename
.Value(), aRv
);
55 if (NS_WARN_IF(aRv
.Failed())) {
62 return GetOrCreateFileCalledBlob(aBlob
, aRv
);
67 // -------------------------------------------------------------------------
70 NS_IMPL_CYCLE_COLLECTION_CLASS(FormData
)
72 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData
)
73 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner
)
75 for (uint32_t i
= 0, len
= tmp
->mFormData
.Length(); i
< len
; ++i
) {
76 ImplCycleCollectionUnlink(tmp
->mFormData
[i
].value
);
79 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
82 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData
)
83 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner
)
85 for (uint32_t i
= 0, len
= tmp
->mFormData
.Length(); i
< len
; ++i
) {
86 ImplCycleCollectionTraverse(cb
, tmp
->mFormData
[i
].value
,
87 "mFormData[i].GetAsBlob()", 0);
90 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
92 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(FormData
)
94 NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData
)
95 NS_IMPL_CYCLE_COLLECTING_RELEASE(FormData
)
97 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FormData
)
98 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
99 NS_INTERFACE_MAP_ENTRY(nsISupports
)
102 // -------------------------------------------------------------------------
103 // HTMLFormSubmission
104 nsresult
FormData::GetEncodedSubmission(nsIURI
* aURI
,
105 nsIInputStream
** aPostDataStream
,
106 nsCOMPtr
<nsIURI
>& aOutURI
) {
107 MOZ_ASSERT_UNREACHABLE("Shouldn't call FormData::GetEncodedSubmission");
111 void FormData::Append(const nsAString
& aName
, const nsAString
& aValue
,
113 AddNameValuePair(aName
, aValue
);
116 void FormData::Append(const nsAString
& aName
, Blob
& aBlob
,
117 const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
118 RefPtr
<File
> file
= GetBlobForFormDataStorage(aBlob
, aFilename
, aRv
);
119 if (NS_WARN_IF(aRv
.Failed())) {
123 AddNameBlobPair(aName
, file
);
126 void FormData::Append(const nsAString
& aName
, Directory
* aDirectory
) {
127 AddNameDirectoryPair(aName
, aDirectory
);
130 void FormData::Delete(const nsAString
& aName
) {
131 mFormData
.RemoveElementsBy([&aName
](const auto& formDataItem
) {
132 return aName
.Equals(formDataItem
.name
);
136 void FormData::Get(const nsAString
& aName
,
137 Nullable
<OwningBlobOrDirectoryOrUSVString
>& aOutValue
) {
138 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
139 if (aName
.Equals(mFormData
[i
].name
)) {
140 aOutValue
.SetValue() = mFormData
[i
].value
;
148 void FormData::GetAll(const nsAString
& aName
,
149 nsTArray
<OwningBlobOrDirectoryOrUSVString
>& aValues
) {
150 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
151 if (aName
.Equals(mFormData
[i
].name
)) {
152 OwningBlobOrDirectoryOrUSVString
* element
= aValues
.AppendElement();
153 *element
= mFormData
[i
].value
;
158 bool FormData::Has(const nsAString
& aName
) {
159 for (uint32_t i
= 0; i
< mFormData
.Length(); ++i
) {
160 if (aName
.Equals(mFormData
[i
].name
)) {
168 nsresult
FormData::AddNameBlobPair(const nsAString
& aName
, Blob
* aBlob
) {
173 file
= GetOrCreateFileCalledBlob(*aBlob
, rv
);
174 if (NS_WARN_IF(rv
.Failed())) {
175 return rv
.StealNSResult();
178 FormDataTuple
* data
= mFormData
.AppendElement();
179 SetNameFilePair(data
, aName
, file
);
183 nsresult
FormData::AddNameDirectoryPair(const nsAString
& aName
,
184 Directory
* aDirectory
) {
185 MOZ_ASSERT(aDirectory
);
187 FormDataTuple
* data
= mFormData
.AppendElement();
188 SetNameDirectoryPair(data
, aName
, aDirectory
);
192 FormData::FormDataTuple
* FormData::RemoveAllOthersAndGetFirstFormDataTuple(
193 const nsAString
& aName
) {
194 FormDataTuple
* lastFoundTuple
= nullptr;
195 uint32_t lastFoundIndex
= mFormData
.Length();
196 // We have to use this slightly awkward for loop since uint32_t >= 0 is an
197 // error for being always true.
198 for (uint32_t i
= mFormData
.Length(); i
-- > 0;) {
199 if (aName
.Equals(mFormData
[i
].name
)) {
200 if (lastFoundTuple
) {
201 // The one we found earlier was not the first one, we can remove it.
202 mFormData
.RemoveElementAt(lastFoundIndex
);
205 lastFoundTuple
= &mFormData
[i
];
210 return lastFoundTuple
;
213 void FormData::Set(const nsAString
& aName
, Blob
& aBlob
,
214 const Optional
<nsAString
>& aFilename
, ErrorResult
& aRv
) {
215 FormDataTuple
* tuple
= RemoveAllOthersAndGetFirstFormDataTuple(aName
);
217 RefPtr
<File
> file
= GetBlobForFormDataStorage(aBlob
, aFilename
, aRv
);
218 if (NS_WARN_IF(aRv
.Failed())) {
222 SetNameFilePair(tuple
, aName
, file
);
224 Append(aName
, aBlob
, aFilename
, aRv
);
228 void FormData::Set(const nsAString
& aName
, const nsAString
& aValue
,
230 FormDataTuple
* tuple
= RemoveAllOthersAndGetFirstFormDataTuple(aName
);
232 SetNameValuePair(tuple
, aName
, aValue
);
234 Append(aName
, aValue
, aRv
);
238 uint32_t FormData::GetIterableLength() const { return mFormData
.Length(); }
240 const nsAString
& FormData::GetKeyAtIndex(uint32_t aIndex
) const {
241 MOZ_ASSERT(aIndex
< mFormData
.Length());
242 return mFormData
[aIndex
].name
;
245 const OwningBlobOrDirectoryOrUSVString
& FormData::GetValueAtIndex(
246 uint32_t aIndex
) const {
247 MOZ_ASSERT(aIndex
< mFormData
.Length());
248 return mFormData
[aIndex
].value
;
251 void FormData::SetNameValuePair(FormDataTuple
* aData
, const nsAString
& aName
,
252 const nsAString
& aValue
) {
255 aData
->value
.SetAsUSVString() = aValue
;
258 void FormData::SetNameFilePair(FormDataTuple
* aData
, const nsAString
& aName
,
264 aData
->value
.SetAsBlob() = aFile
;
267 void FormData::SetNameDirectoryPair(FormDataTuple
* aData
,
268 const nsAString
& aName
,
269 Directory
* aDirectory
) {
271 MOZ_ASSERT(aDirectory
);
274 aData
->value
.SetAsDirectory() = aDirectory
;
278 JSObject
* FormData::WrapObject(JSContext
* aCx
,
279 JS::Handle
<JSObject
*> aGivenProto
) {
280 return FormData_Binding::Wrap(aCx
, this, aGivenProto
);
284 already_AddRefed
<FormData
> FormData::Constructor(
285 const GlobalObject
& aGlobal
,
286 const Optional
<NonNull
<HTMLFormElement
> >& aFormElement
, ErrorResult
& aRv
) {
287 RefPtr
<FormData
> formData
= new FormData(aGlobal
.GetAsSupports());
288 if (aFormElement
.WasPassed()) {
289 aRv
= aFormElement
.Value().ConstructEntryList(formData
);
290 if (NS_WARN_IF(aRv
.Failed())) {
294 // Step 9. Return a shallow clone of entry list.
295 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set
296 formData
= formData
->Clone();
299 return formData
.forget();
302 // contentTypeWithCharset can be set to the contentType or
303 // contentType+charset based on what the spec says.
304 // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
305 nsresult
FormData::GetSendInfo(nsIInputStream
** aBody
, uint64_t* aContentLength
,
306 nsACString
& aContentTypeWithCharset
,
307 nsACString
& aCharset
) const {
308 FSMultipartFormData
fs(nullptr, u
""_ns
, UTF_8_ENCODING
, nullptr);
309 nsresult rv
= CopySubmissionDataTo(&fs
);
310 NS_ENSURE_SUCCESS(rv
, rv
);
312 fs
.GetContentType(aContentTypeWithCharset
);
315 NS_ADDREF(*aBody
= fs
.GetSubmissionBody(aContentLength
));
320 already_AddRefed
<FormData
> FormData::Clone() {
321 RefPtr
<FormData
> formData
= new FormData(*this);
322 return formData
.forget();
325 nsresult
FormData::CopySubmissionDataTo(
326 HTMLFormSubmission
* aFormSubmission
) const {
327 MOZ_ASSERT(aFormSubmission
, "Must have FormSubmission!");
328 for (size_t i
= 0; i
< mFormData
.Length(); ++i
) {
329 if (mFormData
[i
].value
.IsUSVString()) {
330 aFormSubmission
->AddNameValuePair(mFormData
[i
].name
,
331 mFormData
[i
].value
.GetAsUSVString());
332 } else if (mFormData
[i
].value
.IsBlob()) {
333 aFormSubmission
->AddNameBlobPair(mFormData
[i
].name
,
334 mFormData
[i
].value
.GetAsBlob());
336 MOZ_ASSERT(mFormData
[i
].value
.IsDirectory());
337 aFormSubmission
->AddNameDirectoryPair(
338 mFormData
[i
].name
, mFormData
[i
].value
.GetAsDirectory());