FormData.cpp (13109B)
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 9 #include "MultipartBlobImpl.h" 10 #include "mozilla/Encoding.h" 11 #include "mozilla/dom/CustomElementTypes.h" 12 #include "mozilla/dom/Directory.h" 13 #include "mozilla/dom/File.h" 14 #include "mozilla/dom/HTMLFormElement.h" 15 #include "nsGenericHTMLElement.h" 16 #include "nsIInputStream.h" 17 #include "nsQueryObject.h" 18 19 using namespace mozilla; 20 using namespace mozilla::dom; 21 22 FormData::FormData(nsISupports* aOwner, NotNull<const Encoding*> aEncoding, 23 Element* aSubmitter) 24 : HTMLFormSubmission(nullptr, u""_ns, aEncoding), 25 mOwner(aOwner), 26 mSubmitter(aSubmitter) {} 27 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(); 34 } 35 36 namespace { 37 38 already_AddRefed<File> GetOrCreateFileCalledBlob(Blob& aBlob, 39 ErrorResult& aRv) { 40 // If this is file, we can just use it 41 RefPtr<File> file = aBlob.ToFile(); 42 if (file) { 43 return file.forget(); 44 } 45 46 // Forcing 'blob' as filename 47 file = aBlob.ToFile(u"blob"_ns, aRv); 48 if (NS_WARN_IF(aRv.Failed())) { 49 return nullptr; 50 } 51 52 return file.forget(); 53 } 54 55 already_AddRefed<File> GetBlobForFormDataStorage( 56 Blob& aBlob, const Optional<nsAString>& aFilename, ErrorResult& aRv) { 57 // Forcing a filename 58 if (aFilename.WasPassed()) { 59 RefPtr<File> file = aBlob.ToFile(aFilename.Value(), aRv); 60 if (NS_WARN_IF(aRv.Failed())) { 61 return nullptr; 62 } 63 64 return file.forget(); 65 } 66 67 return GetOrCreateFileCalledBlob(aBlob, aRv); 68 } 69 70 } // namespace 71 72 // ------------------------------------------------------------------------- 73 // nsISupports 74 75 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(FormData) 76 77 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FormData) 78 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) 79 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSubmitter) 80 81 for (uint32_t i = 0, len = tmp->mFormData.Length(); i < len; ++i) { 82 ImplCycleCollectionUnlink(tmp->mFormData[i].value); 83 } 84 85 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 86 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 87 88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FormData) 89 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) 90 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSubmitter) 91 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); 95 } 96 97 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 98 99 NS_IMPL_CYCLE_COLLECTING_ADDREF(FormData) 100 NS_IMPL_CYCLE_COLLECTING_RELEASE(FormData) 101 102 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FormData) 103 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 104 NS_INTERFACE_MAP_ENTRY(nsISupports) 105 NS_INTERFACE_MAP_END 106 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"); 113 return NS_OK; 114 } 115 116 void FormData::Append(const nsAString& aName, const nsAString& aValue, 117 ErrorResult& aRv) { 118 AddNameValuePair(aName, aValue); 119 } 120 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())) { 125 return; 126 } 127 128 AddNameBlobPair(aName, file); 129 } 130 131 void FormData::Append(const nsAString& aName, Directory* aDirectory) { 132 AddNameDirectoryPair(aName, aDirectory); 133 } 134 135 void FormData::Append(const FormData& aFormData) { 136 for (uint32_t i = 0; i < aFormData.mFormData.Length(); ++i) { 137 mFormData.AppendElement(aFormData.mFormData[i]); 138 } 139 } 140 141 void FormData::Delete(const nsAString& aName) { 142 mFormData.RemoveElementsBy([&aName](const auto& formDataItem) { 143 return aName.Equals(formDataItem.name); 144 }); 145 } 146 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; 152 return; 153 } 154 } 155 156 aOutValue.SetNull(); 157 } 158 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; 165 } 166 } 167 } 168 169 bool FormData::Has(const nsAString& aName) { 170 for (uint32_t i = 0; i < mFormData.Length(); ++i) { 171 if (aName.Equals(mFormData[i].name)) { 172 return true; 173 } 174 } 175 176 return false; 177 } 178 179 nsresult FormData::AddNameBlobPair(const nsAString& aName, Blob* aBlob) { 180 MOZ_ASSERT(aBlob); 181 182 nsAutoString usvName(aName); 183 if (!NormalizeUSVString(usvName)) { 184 return NS_ERROR_OUT_OF_MEMORY; 185 } 186 187 RefPtr<File> file; 188 ErrorResult rv; 189 file = GetOrCreateFileCalledBlob(*aBlob, rv); 190 if (NS_WARN_IF(rv.Failed())) { 191 return rv.StealNSResult(); 192 } 193 194 FormDataTuple* data = mFormData.AppendElement(); 195 SetNameFilePair(data, usvName, file); 196 return NS_OK; 197 } 198 199 nsresult FormData::AddNameDirectoryPair(const nsAString& aName, 200 Directory* aDirectory) { 201 MOZ_ASSERT(aDirectory); 202 203 nsAutoString usvName(aName); 204 if (!NormalizeUSVString(usvName)) { 205 return NS_ERROR_OUT_OF_MEMORY; 206 } 207 208 FormDataTuple* data = mFormData.AppendElement(); 209 SetNameDirectoryPair(data, usvName, aDirectory); 210 return NS_OK; 211 } 212 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); 224 } 225 226 lastFoundTuple = &mFormData[i]; 227 lastFoundIndex = i; 228 } 229 } 230 231 return lastFoundTuple; 232 } 233 234 void FormData::Set(const nsAString& aName, Blob& aBlob, 235 const Optional<nsAString>& aFilename, ErrorResult& aRv) { 236 FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName); 237 if (tuple) { 238 RefPtr<File> file = GetBlobForFormDataStorage(aBlob, aFilename, aRv); 239 if (NS_WARN_IF(aRv.Failed())) { 240 return; 241 } 242 243 SetNameFilePair(tuple, aName, file); 244 } else { 245 Append(aName, aBlob, aFilename, aRv); 246 } 247 } 248 249 void FormData::Set(const nsAString& aName, const nsAString& aValue, 250 ErrorResult& aRv) { 251 FormDataTuple* tuple = RemoveAllOthersAndGetFirstFormDataTuple(aName); 252 if (tuple) { 253 SetNameValuePair(tuple, aName, aValue); 254 } else { 255 Append(aName, aValue, aRv); 256 } 257 } 258 259 uint32_t FormData::GetIterableLength() const { return mFormData.Length(); } 260 261 const nsAString& FormData::GetKeyAtIndex(uint32_t aIndex) const { 262 MOZ_ASSERT(aIndex < mFormData.Length()); 263 return mFormData[aIndex].name; 264 } 265 266 const OwningBlobOrDirectoryOrUSVString& FormData::GetValueAtIndex( 267 uint32_t aIndex) const { 268 MOZ_ASSERT(aIndex < mFormData.Length()); 269 return mFormData[aIndex].value; 270 } 271 272 void FormData::SetNameValuePair(FormDataTuple* aData, const nsAString& aName, 273 const nsAString& aValue) { 274 MOZ_ASSERT(aData); 275 aData->name = aName; 276 aData->value.SetAsUSVString() = aValue; 277 } 278 279 void FormData::SetNameFilePair(FormDataTuple* aData, const nsAString& aName, 280 File* aFile) { 281 MOZ_ASSERT(aData); 282 MOZ_ASSERT(aFile); 283 284 aData->name = aName; 285 aData->value.SetAsBlob() = aFile; 286 } 287 288 void FormData::SetNameDirectoryPair(FormDataTuple* aData, 289 const nsAString& aName, 290 Directory* aDirectory) { 291 MOZ_ASSERT(aData); 292 MOZ_ASSERT(aDirectory); 293 294 aData->name = aName; 295 aData->value.SetAsDirectory() = aDirectory; 296 } 297 298 /* virtual */ 299 JSObject* FormData::WrapObject(JSContext* aCx, 300 JS::Handle<JSObject*> aGivenProto) { 301 return FormData_Binding::Wrap(aCx, this, aGivenProto); 302 } 303 304 // https://xhr.spec.whatwg.org/#dom-formdata 305 /* static */ 306 already_AddRefed<FormData> FormData::Constructor( 307 const GlobalObject& aGlobal, 308 const Optional<NonNull<HTMLFormElement> >& aFormElement, 309 nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) { 310 return Constructor(aGlobal.GetAsSupports(), 311 aFormElement.WasPassed() ? &aFormElement.Value() : nullptr, 312 aSubmitter, aRv); 313 } 314 315 /* static */ 316 already_AddRefed<FormData> FormData::Constructor( 317 nsISupports* aGlobal, HTMLFormElement* aFormElement, 318 nsGenericHTMLElement* aSubmitter, ErrorResult& aRv) { 319 RefPtr<FormData> formData; 320 // 1. If form is given, then: 321 if (aFormElement) { 322 // 1.1. If submitter is non-null, then: 323 if (aSubmitter) { 324 const nsIFormControl* fc = nsIFormControl::FromNode(aSubmitter); 325 326 // 1.1.1. If submitter is not a submit button, then throw a TypeError. 327 if (!fc || !fc->IsSubmitControl()) { 328 aRv.ThrowTypeError("The submitter is not a submit button."); 329 return nullptr; 330 } 331 332 // 1.1.2. If submitter's form owner is not this form element, then throw a 333 // "NotFoundError" DOMException. 334 if (fc->GetForm() != aFormElement) { 335 aRv.ThrowNotFoundError("The submitter is not owned by this form."); 336 return nullptr; 337 } 338 } 339 340 // 1.2. Let list be the result of constructing the entry list for form and 341 // submitter. 342 formData = new FormData(aGlobal, UTF_8_ENCODING, aSubmitter); 343 aRv = aFormElement->ConstructEntryList(formData); 344 if (NS_WARN_IF(aRv.Failed())) { 345 return nullptr; 346 } 347 348 // Step 9. Return a shallow clone of entry list. 349 // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-form-data-set 350 formData = formData->Clone(); 351 } else { 352 formData = new FormData(aGlobal); 353 } 354 355 return formData.forget(); 356 } 357 358 // contentTypeWithCharset can be set to the contentType or 359 // contentType+charset based on what the spec says. 360 // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract 361 nsresult FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength, 362 nsACString& aContentTypeWithCharset, 363 nsACString& aCharset) const { 364 FSMultipartFormData fs(nullptr, u""_ns, UTF_8_ENCODING, nullptr); 365 nsresult rv = CopySubmissionDataTo(&fs); 366 NS_ENSURE_SUCCESS(rv, rv); 367 368 fs.GetContentType(aContentTypeWithCharset); 369 aCharset.Truncate(); 370 *aContentLength = 0; 371 NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength)); 372 373 return NS_OK; 374 } 375 376 already_AddRefed<FormData> FormData::Clone() const { 377 RefPtr<FormData> formData = new FormData(*this); 378 return formData.forget(); 379 } 380 381 nsresult FormData::CopySubmissionDataTo( 382 HTMLFormSubmission* aFormSubmission) const { 383 MOZ_ASSERT(aFormSubmission, "Must have FormSubmission!"); 384 for (size_t i = 0; i < mFormData.Length(); ++i) { 385 if (mFormData[i].value.IsUSVString()) { 386 aFormSubmission->AddNameValuePair(mFormData[i].name, 387 mFormData[i].value.GetAsUSVString()); 388 } else if (mFormData[i].value.IsBlob()) { 389 aFormSubmission->AddNameBlobPair(mFormData[i].name, 390 mFormData[i].value.GetAsBlob()); 391 } else { 392 MOZ_ASSERT(mFormData[i].value.IsDirectory()); 393 aFormSubmission->AddNameDirectoryPair( 394 mFormData[i].name, mFormData[i].value.GetAsDirectory()); 395 } 396 } 397 398 return NS_OK; 399 } 400 401 CustomElementFormValue FormData::ConvertToCustomElementFormValue() { 402 nsTArray<mozilla::dom::FormDataTuple> formValue; 403 ForEach([&formValue](const nsString& aName, 404 const OwningBlobOrDirectoryOrUSVString& aValue) -> bool { 405 if (aValue.IsBlob()) { 406 IPCFormDataValue value(WrapNotNull(aValue.GetAsBlob()->Impl())); 407 formValue.AppendElement(mozilla::dom::FormDataTuple(aName, value)); 408 } else if (aValue.IsUSVString()) { 409 formValue.AppendElement( 410 mozilla::dom::FormDataTuple(aName, aValue.GetAsUSVString())); 411 } else { 412 MOZ_ASSERT_UNREACHABLE("Can't save FormData entry Directory value!"); 413 } 414 return true; 415 }); 416 return formValue; 417 }