HTMLFormSubmission.cpp (28708B)
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 "HTMLFormSubmission.h" 8 9 #include <tuple> 10 11 #include "HTMLFormElement.h" 12 #include "HTMLFormSubmissionConstants.h" 13 #include "mozilla/RandomNum.h" 14 #include "mozilla/StaticPrefs_dom.h" 15 #include "mozilla/dom/AncestorIterator.h" 16 #include "mozilla/dom/Directory.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/File.h" 19 #include "mozilla/dom/FormData.h" 20 #include "mozilla/dom/PolicyContainer.h" 21 #include "nsAttrValueInlines.h" 22 #include "nsCExternalHandlerService.h" 23 #include "nsCOMPtr.h" 24 #include "nsComponentManagerUtils.h" 25 #include "nsContentUtils.h" 26 #include "nsDirectoryServiceDefs.h" 27 #include "nsError.h" 28 #include "nsEscape.h" 29 #include "nsGenericHTMLElement.h" 30 #include "nsGkAtoms.h" 31 #include "nsIFormControl.h" 32 #include "nsIMIMEInputStream.h" 33 #include "nsIMultiplexInputStream.h" 34 #include "nsIScriptError.h" 35 #include "nsIURI.h" 36 #include "nsIURIMutator.h" 37 #include "nsIURL.h" 38 #include "nsLinebreakConverter.h" 39 #include "nsNetUtil.h" 40 #include "nsStringStream.h" 41 #include "nsUnicharUtils.h" 42 43 namespace mozilla::dom { 44 45 namespace { 46 47 void SendJSWarning(Document* aDocument, const char* aWarningName, 48 const nsTArray<nsString>& aWarningArgs) { 49 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "HTML"_ns, 50 aDocument, nsContentUtils::eFORMS_PROPERTIES, 51 aWarningName, aWarningArgs); 52 } 53 54 void RetrieveFileName(Blob* aBlob, nsAString& aFilename) { 55 if (!aBlob) { 56 return; 57 } 58 59 RefPtr<File> file = aBlob->ToFile(); 60 if (file) { 61 file->GetName(aFilename); 62 } 63 } 64 65 void RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname) { 66 MOZ_ASSERT(aDirectory); 67 68 ErrorResult rv; 69 aDirectory->GetName(aDirname, rv); 70 if (NS_WARN_IF(rv.Failed())) { 71 rv.SuppressException(); 72 aDirname.Truncate(); 73 } 74 } 75 76 // -------------------------------------------------------------------------- 77 78 class FSURLEncoded : public EncodingFormSubmission { 79 public: 80 /** 81 * @param aEncoding the character encoding of the form 82 * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or 83 * NS_FORM_METHOD_POST). 84 */ 85 FSURLEncoded(nsIURI* aActionURL, const nsAString& aTarget, 86 NotNull<const Encoding*> aEncoding, int32_t aMethod, 87 Document* aDocument, Element* aSubmitter) 88 : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter), 89 mMethod(aMethod), 90 mDocument(aDocument), 91 mWarnedFileControl(false) {} 92 93 virtual nsresult AddNameValuePair(const nsAString& aName, 94 const nsAString& aValue) override; 95 96 virtual nsresult AddNameBlobPair(const nsAString& aName, 97 Blob* aBlob) override; 98 99 virtual nsresult AddNameDirectoryPair(const nsAString& aName, 100 Directory* aDirectory) override; 101 102 virtual nsresult GetEncodedSubmission(nsIURI* aURI, 103 nsIInputStream** aPostDataStream, 104 nsCOMPtr<nsIURI>& aOutURI) override; 105 106 protected: 107 /** 108 * URL encode a Unicode string by encoding it to bytes, converting linebreaks 109 * properly, and then escaping many bytes as %xx. 110 * 111 * @param aStr the string to encode 112 * @param aEncoded the encoded string [OUT] 113 * @throws NS_ERROR_OUT_OF_MEMORY if we run out of memory 114 */ 115 nsresult URLEncode(const nsAString& aStr, nsACString& aEncoded); 116 117 private: 118 /** 119 * The method of the submit (either NS_FORM_METHOD_GET or 120 * NS_FORM_METHOD_POST). 121 */ 122 int32_t mMethod; 123 124 /** The query string so far (the part after the ?) */ 125 nsCString mQueryString; 126 127 /** The document whose URI to use when reporting errors */ 128 nsCOMPtr<Document> mDocument; 129 130 /** Whether or not we have warned about a file control not being submitted */ 131 bool mWarnedFileControl; 132 }; 133 134 nsresult FSURLEncoded::AddNameValuePair(const nsAString& aName, 135 const nsAString& aValue) { 136 // Encode value 137 nsCString convValue; 138 nsresult rv = URLEncode(aValue, convValue); 139 NS_ENSURE_SUCCESS(rv, rv); 140 141 // Encode name 142 nsAutoCString convName; 143 rv = URLEncode(aName, convName); 144 NS_ENSURE_SUCCESS(rv, rv); 145 146 // Append data to string 147 if (mQueryString.IsEmpty()) { 148 mQueryString += convName + "="_ns + convValue; 149 } else { 150 mQueryString += "&"_ns + convName + "="_ns + convValue; 151 } 152 153 return NS_OK; 154 } 155 156 nsresult FSURLEncoded::AddNameBlobPair(const nsAString& aName, Blob* aBlob) { 157 if (!mWarnedFileControl) { 158 SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nsTArray<nsString>()); 159 mWarnedFileControl = true; 160 } 161 162 nsAutoString filename; 163 RetrieveFileName(aBlob, filename); 164 return AddNameValuePair(aName, filename); 165 } 166 167 nsresult FSURLEncoded::AddNameDirectoryPair(const nsAString& aName, 168 Directory* aDirectory) { 169 // No warning about because Directory objects are never sent via form. 170 171 nsAutoString dirname; 172 RetrieveDirectoryName(aDirectory, dirname); 173 return AddNameValuePair(aName, dirname); 174 } 175 176 void HandleMailtoSubject(nsCString& aPath) { 177 // Walk through the string and see if we have a subject already. 178 bool hasSubject = false; 179 bool hasParams = false; 180 int32_t paramSep = aPath.FindChar('?'); 181 while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) { 182 hasParams = true; 183 184 // Get the end of the name at the = op. If it is *after* the next &, 185 // assume that someone made a parameter without an = in it 186 int32_t nameEnd = aPath.FindChar('=', paramSep + 1); 187 int32_t nextParamSep = aPath.FindChar('&', paramSep + 1); 188 if (nextParamSep == kNotFound) { 189 nextParamSep = aPath.Length(); 190 } 191 192 // If the = op is after the &, this parameter is a name without value. 193 // If there is no = op, same thing. 194 if (nameEnd == kNotFound || nextParamSep < nameEnd) { 195 nameEnd = nextParamSep; 196 } 197 198 if (nameEnd != kNotFound) { 199 if (Substring(aPath, paramSep + 1, nameEnd - (paramSep + 1)) 200 .LowerCaseEqualsLiteral("subject")) { 201 hasSubject = true; 202 break; 203 } 204 } 205 206 paramSep = nextParamSep; 207 } 208 209 // If there is no subject, append a preformed subject to the mailto line 210 if (!hasSubject) { 211 if (hasParams) { 212 aPath.Append('&'); 213 } else { 214 aPath.Append('?'); 215 } 216 217 // Get the default subject 218 nsAutoString brandName; 219 nsresult rv = nsContentUtils::GetLocalizedString( 220 nsContentUtils::eBRAND_PROPERTIES, "brandShortName", brandName); 221 if (NS_FAILED(rv)) return; 222 nsAutoString subjectStr; 223 rv = nsContentUtils::FormatLocalizedString( 224 subjectStr, nsContentUtils::eFORMS_PROPERTIES, "DefaultFormSubject", 225 brandName); 226 if (NS_FAILED(rv)) return; 227 aPath.AppendLiteral("subject="); 228 nsCString subjectStrEscaped; 229 rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query, 230 subjectStrEscaped, mozilla::fallible); 231 if (NS_FAILED(rv)) return; 232 233 aPath.Append(subjectStrEscaped); 234 } 235 } 236 237 nsresult FSURLEncoded::GetEncodedSubmission(nsIURI* aURI, 238 nsIInputStream** aPostDataStream, 239 nsCOMPtr<nsIURI>& aOutURI) { 240 nsresult rv = NS_OK; 241 aOutURI = aURI; 242 243 *aPostDataStream = nullptr; 244 245 if (mMethod == NS_FORM_METHOD_POST) { 246 if (aURI->SchemeIs("mailto")) { 247 nsAutoCString path; 248 rv = aURI->GetPathQueryRef(path); 249 NS_ENSURE_SUCCESS(rv, rv); 250 251 HandleMailtoSubject(path); 252 253 // Append the body to and force-plain-text args to the mailto line 254 nsAutoCString escapedBody; 255 if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) { 256 return NS_ERROR_OUT_OF_MEMORY; 257 } 258 259 path += "&force-plain-text=Y&body="_ns + escapedBody; 260 261 return NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI); 262 } else { 263 nsCOMPtr<nsIInputStream> dataStream; 264 rv = NS_NewCStringInputStream(getter_AddRefs(dataStream), 265 std::move(mQueryString)); 266 NS_ENSURE_SUCCESS(rv, rv); 267 mQueryString.Truncate(); 268 269 nsCOMPtr<nsIMIMEInputStream> mimeStream( 270 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); 271 NS_ENSURE_SUCCESS(rv, rv); 272 273 mimeStream->AddHeader("Content-Type", 274 "application/x-www-form-urlencoded"); 275 mimeStream->SetData(dataStream); 276 277 mimeStream.forget(aPostDataStream); 278 } 279 280 } else { 281 // Get the full query string 282 if (aURI->SchemeIs("javascript")) { 283 return NS_OK; 284 } 285 286 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); 287 if (url) { 288 // Make sure that we end up with a query component in the URL. If 289 // mQueryString is empty, nsIURI::SetQuery() will remove the query 290 // component, which is not what we want. 291 rv = NS_MutateURI(aURI) 292 .SetQuery(mQueryString.IsEmpty() ? "?"_ns : mQueryString) 293 .Finalize(aOutURI); 294 } else { 295 nsAutoCString path; 296 rv = aURI->GetPathQueryRef(path); 297 NS_ENSURE_SUCCESS(rv, rv); 298 // Bug 42616: Trim off named anchor and save it to add later 299 int32_t namedAnchorPos = path.FindChar('#'); 300 nsAutoCString namedAnchor; 301 if (kNotFound != namedAnchorPos) { 302 path.Right(namedAnchor, (path.Length() - namedAnchorPos)); 303 path.Truncate(namedAnchorPos); 304 } 305 306 // Chop off old query string (bug 25330, 57333) 307 // Only do this for GET not POST (bug 41585) 308 int32_t queryStart = path.FindChar('?'); 309 if (kNotFound != queryStart) { 310 path.Truncate(queryStart); 311 } 312 313 path.Append('?'); 314 // Bug 42616: Add named anchor to end after query string 315 path.Append(mQueryString + namedAnchor); 316 317 rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI); 318 } 319 } 320 321 return rv; 322 } 323 324 // i18n helper routines 325 nsresult FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) { 326 nsAutoCString encodedBuf; 327 // We encode with eValueEncode because the urlencoded format needs the newline 328 // normalizations but percent-escapes characters that eNameEncode doesn't, 329 // so calling NS_Escape would still be needed. 330 nsresult rv = EncodeVal(aStr, encodedBuf, EncodeType::eValueEncode); 331 NS_ENSURE_SUCCESS(rv, rv); 332 333 if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) { 334 return NS_ERROR_OUT_OF_MEMORY; 335 } 336 337 return NS_OK; 338 } 339 340 } // anonymous namespace 341 342 // -------------------------------------------------------------------------- 343 344 FSMultipartFormData::FSMultipartFormData(nsIURI* aActionURL, 345 const nsAString& aTarget, 346 NotNull<const Encoding*> aEncoding, 347 Element* aSubmitter) 348 : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) { 349 mPostData = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); 350 351 nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mPostData); 352 MOZ_ASSERT(SameCOMIdentity(mPostData, inputStream)); 353 mPostDataStream = inputStream; 354 355 mTotalLength = 0; 356 357 mBoundary.AssignLiteral("----geckoformboundary"); 358 mBoundary.AppendInt(mozilla::RandomUint64OrDie(), 16); 359 mBoundary.AppendInt(mozilla::RandomUint64OrDie(), 16); 360 } 361 362 FSMultipartFormData::~FSMultipartFormData() { 363 NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data"); 364 } 365 366 nsIInputStream* FSMultipartFormData::GetSubmissionBody( 367 uint64_t* aContentLength) { 368 // Finish data 369 mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString("--" CRLF); 370 371 // Add final data input stream 372 AddPostDataStream(); 373 374 *aContentLength = mTotalLength; 375 return mPostDataStream; 376 } 377 378 nsresult FSMultipartFormData::AddNameValuePair(const nsAString& aName, 379 const nsAString& aValue) { 380 nsAutoCString encodedVal; 381 nsresult rv = EncodeVal(aValue, encodedVal, EncodeType::eValueEncode); 382 NS_ENSURE_SUCCESS(rv, rv); 383 384 nsAutoCString nameStr; 385 rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode); 386 NS_ENSURE_SUCCESS(rv, rv); 387 388 // Make MIME block for name/value pair 389 390 mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF) + 391 "Content-Disposition: form-data; name=\""_ns + nameStr + 392 nsLiteralCString("\"" CRLF CRLF) + encodedVal + 393 nsLiteralCString(CRLF); 394 395 return NS_OK; 396 } 397 398 nsresult FSMultipartFormData::AddNameBlobPair(const nsAString& aName, 399 Blob* aBlob) { 400 MOZ_ASSERT(aBlob); 401 402 // Encode the control name 403 nsAutoCString nameStr; 404 nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode); 405 NS_ENSURE_SUCCESS(rv, rv); 406 407 ErrorResult error; 408 409 uint64_t size = 0; 410 nsAutoCString filename; 411 nsAutoCString contentType; 412 nsCOMPtr<nsIInputStream> fileStream; 413 nsAutoString filename16; 414 415 RefPtr<File> file = aBlob->ToFile(); 416 if (file) { 417 nsAutoString relativePath; 418 file->GetRelativePath(relativePath); 419 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() && 420 !relativePath.IsEmpty()) { 421 filename16 = relativePath; 422 } 423 424 if (filename16.IsEmpty()) { 425 RetrieveFileName(aBlob, filename16); 426 } 427 } 428 429 rv = EncodeVal(filename16, filename, EncodeType::eFilenameEncode); 430 NS_ENSURE_SUCCESS(rv, rv); 431 432 // Get content type 433 nsAutoString contentType16; 434 aBlob->GetType(contentType16); 435 if (contentType16.IsEmpty()) { 436 contentType16.AssignLiteral("application/octet-stream"); 437 } 438 439 NS_ConvertUTF16toUTF8 contentType8(contentType16); 440 int32_t convertedBufLength = 0; 441 char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks( 442 contentType8.get(), nsLinebreakConverter::eLinebreakAny, 443 nsLinebreakConverter::eLinebreakSpace, contentType8.Length(), 444 &convertedBufLength); 445 contentType.Adopt(convertedBuf, convertedBufLength); 446 447 // Get input stream 448 aBlob->CreateInputStream(getter_AddRefs(fileStream), error); 449 if (NS_WARN_IF(error.Failed())) { 450 return error.StealNSResult(); 451 } 452 453 // Get size 454 size = aBlob->GetSize(error); 455 if (error.Failed()) { 456 error.SuppressException(); 457 fileStream = nullptr; 458 } 459 460 if (fileStream) { 461 // Create buffered stream (for efficiency) 462 nsCOMPtr<nsIInputStream> bufferedStream; 463 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), 464 fileStream.forget(), 8192); 465 NS_ENSURE_SUCCESS(rv, rv); 466 467 fileStream = bufferedStream; 468 } 469 470 AddDataChunk(nameStr, filename, contentType, fileStream, size); 471 return NS_OK; 472 } 473 474 nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName, 475 Directory* aDirectory) { 476 if (!StaticPrefs::dom_webkitBlink_dirPicker_enabled()) { 477 return NS_OK; 478 } 479 480 // Encode the control name 481 nsAutoCString nameStr; 482 nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode); 483 NS_ENSURE_SUCCESS(rv, rv); 484 485 nsAutoCString dirname; 486 nsAutoString dirname16; 487 488 ErrorResult error; 489 nsAutoString path; 490 aDirectory->GetPath(path, error); 491 if (NS_WARN_IF(error.Failed())) { 492 error.SuppressException(); 493 } else { 494 dirname16 = path; 495 } 496 497 if (dirname16.IsEmpty()) { 498 RetrieveDirectoryName(aDirectory, dirname16); 499 } 500 501 rv = EncodeVal(dirname16, dirname, EncodeType::eFilenameEncode); 502 NS_ENSURE_SUCCESS(rv, rv); 503 504 AddDataChunk(nameStr, dirname, "application/octet-stream"_ns, nullptr, 0); 505 return NS_OK; 506 } 507 508 void FSMultipartFormData::AddDataChunk(const nsACString& aName, 509 const nsACString& aFilename, 510 const nsACString& aContentType, 511 nsIInputStream* aInputStream, 512 uint64_t aInputStreamSize) { 513 // 514 // Make MIME block for name/value pair 515 // 516 // more appropriate than always using binary? 517 mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF); 518 mPostDataChunk += "Content-Disposition: form-data; name=\""_ns + aName + 519 "\"; filename=\""_ns + aFilename + 520 nsLiteralCString("\"" CRLF) + "Content-Type: "_ns + 521 aContentType + nsLiteralCString(CRLF CRLF); 522 523 // We should not try to append an invalid stream. That will happen for example 524 // if we try to update a file that actually do not exist. 525 if (aInputStream) { 526 // We need to dump the data up to this point into the POST data stream 527 // here, since we're about to add the file input stream 528 AddPostDataStream(); 529 530 mPostData->AppendStream(aInputStream); 531 mTotalLength += aInputStreamSize; 532 } 533 534 // CRLF after file 535 mPostDataChunk.AppendLiteral(CRLF); 536 } 537 538 nsresult FSMultipartFormData::GetEncodedSubmission( 539 nsIURI* aURI, nsIInputStream** aPostDataStream, nsCOMPtr<nsIURI>& aOutURI) { 540 nsresult rv; 541 aOutURI = aURI; 542 543 // Make header 544 nsCOMPtr<nsIMIMEInputStream> mimeStream = 545 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv); 546 NS_ENSURE_SUCCESS(rv, rv); 547 548 nsAutoCString contentType; 549 GetContentType(contentType); 550 mimeStream->AddHeader("Content-Type", contentType.get()); 551 552 uint64_t bodySize; 553 mimeStream->SetData(GetSubmissionBody(&bodySize)); 554 555 mimeStream.forget(aPostDataStream); 556 557 return NS_OK; 558 } 559 560 nsresult FSMultipartFormData::AddPostDataStream() { 561 nsresult rv = NS_OK; 562 563 nsCOMPtr<nsIInputStream> postDataChunkStream; 564 rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream), 565 mPostDataChunk); 566 NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!"); 567 if (postDataChunkStream) { 568 mPostData->AppendStream(postDataChunkStream); 569 mTotalLength += mPostDataChunk.Length(); 570 } 571 572 mPostDataChunk.Truncate(); 573 574 return rv; 575 } 576 577 // -------------------------------------------------------------------------- 578 579 namespace { 580 581 class FSTextPlain : public EncodingFormSubmission { 582 public: 583 FSTextPlain(nsIURI* aActionURL, const nsAString& aTarget, 584 NotNull<const Encoding*> aEncoding, Element* aSubmitter) 585 : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {} 586 587 virtual nsresult AddNameValuePair(const nsAString& aName, 588 const nsAString& aValue) override; 589 590 virtual nsresult AddNameBlobPair(const nsAString& aName, 591 Blob* aBlob) override; 592 593 virtual nsresult AddNameDirectoryPair(const nsAString& aName, 594 Directory* aDirectory) override; 595 596 virtual nsresult GetEncodedSubmission(nsIURI* aURI, 597 nsIInputStream** aPostDataStream, 598 nsCOMPtr<nsIURI>& aOutURI) override; 599 600 private: 601 nsString mBody; 602 }; 603 604 nsresult FSTextPlain::AddNameValuePair(const nsAString& aName, 605 const nsAString& aValue) { 606 // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose 607 // text/plain doesn't care about that. Parsers aren't built for escaped 608 // values so we'll have to live with it. 609 mBody.Append(aName + u"="_ns + aValue + NS_LITERAL_STRING_FROM_CSTRING(CRLF)); 610 611 return NS_OK; 612 } 613 614 nsresult FSTextPlain::AddNameBlobPair(const nsAString& aName, Blob* aBlob) { 615 nsAutoString filename; 616 RetrieveFileName(aBlob, filename); 617 AddNameValuePair(aName, filename); 618 return NS_OK; 619 } 620 621 nsresult FSTextPlain::AddNameDirectoryPair(const nsAString& aName, 622 Directory* aDirectory) { 623 nsAutoString dirname; 624 RetrieveDirectoryName(aDirectory, dirname); 625 AddNameValuePair(aName, dirname); 626 return NS_OK; 627 } 628 629 nsresult FSTextPlain::GetEncodedSubmission(nsIURI* aURI, 630 nsIInputStream** aPostDataStream, 631 nsCOMPtr<nsIURI>& aOutURI) { 632 nsresult rv = NS_OK; 633 aOutURI = aURI; 634 635 *aPostDataStream = nullptr; 636 637 // XXX HACK We are using the standard URL mechanism to give the body to the 638 // mailer instead of passing the post data stream to it, since that sounds 639 // hard. 640 if (aURI->SchemeIs("mailto")) { 641 nsAutoCString path; 642 rv = aURI->GetPathQueryRef(path); 643 NS_ENSURE_SUCCESS(rv, rv); 644 645 HandleMailtoSubject(path); 646 647 // Append the body to and force-plain-text args to the mailto line 648 nsAutoCString escapedBody; 649 if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody, 650 url_XAlphas))) { 651 return NS_ERROR_OUT_OF_MEMORY; 652 } 653 654 path += "&force-plain-text=Y&body="_ns + escapedBody; 655 656 rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI); 657 } else { 658 // Create data stream. 659 // We use eValueEncode to send the data through the charset encoder and to 660 // normalize linebreaks to use the "standard net" format (\r\n), but not 661 // perform any other escaping. This means that names and values which 662 // contain '=' or newlines are potentially ambiguously encoded, but that is 663 // how text/plain is specced. 664 nsCString cbody; 665 EncodeVal(mBody, cbody, EncodeType::eValueEncode); 666 667 nsCOMPtr<nsIInputStream> bodyStream; 668 rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), std::move(cbody)); 669 if (!bodyStream) { 670 return NS_ERROR_OUT_OF_MEMORY; 671 } 672 673 // Create mime stream with headers and such 674 nsCOMPtr<nsIMIMEInputStream> mimeStream = 675 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv); 676 NS_ENSURE_SUCCESS(rv, rv); 677 678 mimeStream->AddHeader("Content-Type", "text/plain"); 679 mimeStream->SetData(bodyStream); 680 mimeStream.forget(aPostDataStream); 681 } 682 683 return rv; 684 } 685 686 } // anonymous namespace 687 688 // -------------------------------------------------------------------------- 689 690 HTMLFormSubmission::HTMLFormSubmission( 691 nsIURI* aActionURL, const nsAString& aTarget, 692 mozilla::NotNull<const mozilla::Encoding*> aEncoding) 693 : mActionURL(aActionURL), 694 mTarget(aTarget), 695 mEncoding(aEncoding), 696 mInitiatedFromUserInput(UserActivation::IsHandlingUserInput()) { 697 MOZ_COUNT_CTOR(HTMLFormSubmission); 698 } 699 700 EncodingFormSubmission::EncodingFormSubmission( 701 nsIURI* aActionURL, const nsAString& aTarget, 702 NotNull<const Encoding*> aEncoding, Element* aSubmitter) 703 : HTMLFormSubmission(aActionURL, aTarget, aEncoding) { 704 if (!aEncoding->CanEncodeEverything()) { 705 nsAutoCString name; 706 aEncoding->Name(name); 707 AutoTArray<nsString, 1> args; 708 CopyUTF8toUTF16(name, *args.AppendElement()); 709 SendJSWarning(aSubmitter ? aSubmitter->GetOwnerDocument() : nullptr, 710 "CannotEncodeAllUnicode", args); 711 } 712 } 713 714 EncodingFormSubmission::~EncodingFormSubmission() = default; 715 716 // i18n helper routines 717 nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr, 718 nsCString& aOut, 719 EncodeType aEncodeType) { 720 nsresult rv; 721 std::tie(rv, std::ignore) = mEncoding->Encode(aStr, aOut); 722 if (NS_FAILED(rv)) { 723 return rv; 724 } 725 726 if (aEncodeType != EncodeType::eFilenameEncode) { 727 // Normalize newlines 728 int32_t convertedBufLength = 0; 729 char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks( 730 aOut.get(), nsLinebreakConverter::eLinebreakAny, 731 nsLinebreakConverter::eLinebreakNet, (int32_t)aOut.Length(), 732 &convertedBufLength); 733 aOut.Adopt(convertedBuf, convertedBufLength); 734 } 735 736 if (aEncodeType != EncodeType::eValueEncode) { 737 // Percent-escape LF, CR and double quotes. 738 int32_t offset = 0; 739 while ((offset = aOut.FindCharInSet("\n\r\"", offset)) != kNotFound) { 740 if (aOut[offset] == '\n') { 741 aOut.ReplaceLiteral(offset, 1, "%0A"); 742 } else if (aOut[offset] == '\r') { 743 aOut.ReplaceLiteral(offset, 1, "%0D"); 744 } else if (aOut[offset] == '"') { 745 aOut.ReplaceLiteral(offset, 1, "%22"); 746 } else { 747 MOZ_ASSERT(false); 748 offset++; 749 continue; 750 } 751 } 752 } 753 754 return NS_OK; 755 } 756 757 // -------------------------------------------------------------------------- 758 759 namespace { 760 761 void GetEnumAttr(nsGenericHTMLElement* aContent, nsAtom* atom, 762 int32_t* aValue) { 763 const nsAttrValue* value = aContent->GetParsedAttr(atom); 764 if (value && value->Type() == nsAttrValue::eEnum) { 765 *aValue = value->GetEnumValue(); 766 } 767 } 768 769 } // anonymous namespace 770 771 /* static */ 772 nsresult HTMLFormSubmission::GetFromForm(HTMLFormElement* aForm, 773 nsGenericHTMLElement* aSubmitter, 774 NotNull<const Encoding*>& aEncoding, 775 FormData* aFormData, 776 HTMLFormSubmission** aFormSubmission) { 777 // Get all the information necessary to encode the form data 778 NS_ASSERTION(aForm->GetComposedDoc(), 779 "Should have doc if we're building submission!"); 780 781 nsresult rv; 782 783 // Get method (default: GET) 784 int32_t method = NS_FORM_METHOD_GET; 785 if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formmethod)) { 786 GetEnumAttr(aSubmitter, nsGkAtoms::formmethod, &method); 787 } else { 788 GetEnumAttr(aForm, nsGkAtoms::method, &method); 789 } 790 791 if (method == NS_FORM_METHOD_DIALOG) { 792 HTMLDialogElement* dialog = aForm->FirstAncestorOfType<HTMLDialogElement>(); 793 794 // If there isn't one, do nothing. 795 if (!dialog) { 796 return NS_ERROR_FAILURE; 797 } 798 799 nsAutoString result; 800 if (aSubmitter) { 801 aSubmitter->ResultForDialogSubmit(result); 802 } 803 *aFormSubmission = new DialogFormSubmission(result, aEncoding, dialog); 804 return NS_OK; 805 } 806 807 MOZ_ASSERT(method != NS_FORM_METHOD_DIALOG); 808 809 // Get action 810 nsCOMPtr<nsIURI> actionURL; 811 rv = aForm->GetActionURL(getter_AddRefs(actionURL), aSubmitter); 812 NS_ENSURE_SUCCESS(rv, rv); 813 814 // Check if CSP allows this form-action 815 nsCOMPtr<nsIContentSecurityPolicy> csp = 816 PolicyContainer::GetCSP(aForm->GetPolicyContainer()); 817 if (csp) { 818 bool permitsFormAction = true; 819 820 // form-action is only enforced if explicitly defined in the 821 // policy - do *not* consult default-src, see: 822 // http://www.w3.org/TR/CSP2/#directive-default-src 823 rv = csp->Permits(aForm, nullptr /* nsICSPEventListener */, actionURL, 824 nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE, 825 true /* aSpecific */, true /* aSendViolationReports */, 826 &permitsFormAction); 827 NS_ENSURE_SUCCESS(rv, rv); 828 if (!permitsFormAction) { 829 return NS_ERROR_CSP_FORM_ACTION_VIOLATION; 830 } 831 } 832 833 // Get target 834 nsAutoString target; 835 aForm->GetSubmissionTarget(aSubmitter, target); 836 837 // Get encoding type (default: urlencoded) 838 int32_t enctype = NS_FORM_ENCTYPE_URLENCODED; 839 if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) { 840 GetEnumAttr(aSubmitter, nsGkAtoms::formenctype, &enctype); 841 } else { 842 GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype); 843 } 844 845 // Choose encoder 846 if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_MULTIPART) { 847 *aFormSubmission = 848 new FSMultipartFormData(actionURL, target, aEncoding, aSubmitter); 849 } else if (method == NS_FORM_METHOD_POST && 850 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { 851 *aFormSubmission = 852 new FSTextPlain(actionURL, target, aEncoding, aSubmitter); 853 } else { 854 Document* doc = aForm->OwnerDoc(); 855 if (enctype == NS_FORM_ENCTYPE_MULTIPART || 856 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { 857 AutoTArray<nsString, 1> args; 858 nsString& enctypeStr = *args.AppendElement(); 859 if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) { 860 aSubmitter->GetAttr(nsGkAtoms::formenctype, enctypeStr); 861 } else { 862 aForm->GetAttr(nsGkAtoms::enctype, enctypeStr); 863 } 864 865 SendJSWarning(doc, "ForgotPostWarning", args); 866 } 867 *aFormSubmission = 868 new FSURLEncoded(actionURL, target, aEncoding, method, doc, aSubmitter); 869 } 870 871 // We store the FormData here to be able to set it on the load state when we 872 // submit the submission. It's used for the #navigate algorithm in the HTML 873 // spec and is only ever needed when the method is POST. 874 if (method == NS_FORM_METHOD_POST) { 875 (*aFormSubmission)->mFormData = aFormData; 876 } 877 878 return NS_OK; 879 } 880 881 } // namespace mozilla::dom