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