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"
11 #include "nsComponentManagerUtils.h"
12 #include "nsGkAtoms.h"
13 #include "nsIFormControl.h"
15 #include "nsGenericHTMLElement.h"
16 #include "nsAttrValueInlines.h"
17 #include "nsDirectoryServiceDefs.h"
18 #include "nsStringStream.h"
20 #include "nsIURIMutator.h"
22 #include "nsNetUtil.h"
23 #include "nsLinebreakConverter.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"
41 namespace mozilla::dom
{
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
) {
57 RefPtr
<File
> file
= aBlob
->ToFile();
59 file
->GetName(aFilename
);
63 void RetrieveDirectoryName(Directory
* aDirectory
, nsAString
& aDirname
) {
64 MOZ_ASSERT(aDirectory
);
67 aDirectory
->GetName(aDirname
, rv
);
68 if (NS_WARN_IF(rv
.Failed())) {
69 rv
.SuppressException();
74 // --------------------------------------------------------------------------
76 class FSURLEncoded
: public EncodingFormSubmission
{
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
),
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
;
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
);
117 * The method of the submit (either NS_FORM_METHOD_GET or
118 * NS_FORM_METHOD_POST).
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
) {
136 nsresult rv
= URLEncode(aValue
, convValue
);
137 NS_ENSURE_SUCCESS(rv
, rv
);
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
;
148 mQueryString
+= "&"_ns
+ convName
+ "="_ns
+ convValue
;
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()) {
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")) {
204 paramSep
= nextParamSep
;
207 // If there is no subject, append a preformed subject to the mailto line
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",
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
) {
241 *aPostDataStream
= nullptr;
243 if (mMethod
== NS_FORM_METHOD_POST
) {
244 if (aURI
->SchemeIs("mailto")) {
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
);
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
);
279 // Get the full query string
280 if (aURI
->SchemeIs("javascript")) {
284 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aURI
);
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
)
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
);
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
);
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
;
338 } // anonymous namespace
340 // --------------------------------------------------------------------------
342 FSMultipartFormData::FSMultipartFormData(nsIURI
* aActionURL
,
343 const nsAString
& aTarget
,
344 NotNull
<const Encoding
*> aEncoding
,
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
;
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
) {
368 mPostDataChunk
+= "--"_ns
+ mBoundary
+ nsLiteralCString("--" CRLF
);
370 // Add final data input stream
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
);
397 nsresult
FSMultipartFormData::AddNameBlobPair(const nsAString
& aName
,
401 // Encode the control name
402 nsAutoCString nameStr
;
403 nsresult rv
= EncodeVal(aName
, nameStr
, EncodeType::eNameEncode
);
404 NS_ENSURE_SUCCESS(rv
, rv
);
409 nsAutoCString filename
;
410 nsAutoCString contentType
;
411 nsCOMPtr
<nsIInputStream
> fileStream
;
412 nsAutoString filename16
;
414 RefPtr
<File
> file
= aBlob
->ToFile();
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
);
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
);
447 aBlob
->CreateInputStream(getter_AddRefs(fileStream
), error
);
448 if (NS_WARN_IF(error
.Failed())) {
449 return error
.StealNSResult();
453 size
= aBlob
->GetSize(error
);
454 if (error
.Failed()) {
455 error
.SuppressException();
456 fileStream
= nullptr;
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
);
473 nsresult
FSMultipartFormData::AddNameDirectoryPair(const nsAString
& aName
,
474 Directory
* aDirectory
) {
475 if (!StaticPrefs::dom_webkitBlink_dirPicker_enabled()) {
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
;
489 aDirectory
->GetPath(path
, error
);
490 if (NS_WARN_IF(error
.Failed())) {
491 error
.SuppressException();
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);
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.
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
529 mPostData
->AppendStream(aInputStream
);
530 mTotalLength
+= aInputStreamSize
;
534 mPostDataChunk
.AppendLiteral(CRLF
);
537 nsresult
FSMultipartFormData::GetEncodedSubmission(
538 nsIURI
* aURI
, nsIInputStream
** aPostDataStream
, nsCOMPtr
<nsIURI
>& aOutURI
) {
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());
552 mimeStream
->SetData(GetSubmissionBody(&bodySize
));
554 mimeStream
.forget(aPostDataStream
);
559 nsresult
FSMultipartFormData::AddPostDataStream() {
562 nsCOMPtr
<nsIInputStream
> postDataChunkStream
;
563 rv
= NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream
),
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();
576 // --------------------------------------------------------------------------
580 class FSTextPlain
: public EncodingFormSubmission
{
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
;
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
));
613 nsresult
FSTextPlain::AddNameBlobPair(const nsAString
& aName
, Blob
* aBlob
) {
614 nsAutoString filename
;
615 RetrieveFileName(aBlob
, filename
);
616 AddNameValuePair(aName
, filename
);
620 nsresult
FSTextPlain::AddNameDirectoryPair(const nsAString
& aName
,
621 Directory
* aDirectory
) {
622 nsAutoString dirname
;
623 RetrieveDirectoryName(aDirectory
, dirname
);
624 AddNameValuePair(aName
, dirname
);
628 nsresult
FSTextPlain::GetEncodedSubmission(nsIURI
* aURI
,
629 nsIInputStream
** aPostDataStream
,
630 nsCOMPtr
<nsIURI
>& aOutURI
) {
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
639 if (aURI
->SchemeIs("mailto")) {
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
,
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
);
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.
664 EncodeVal(mBody
, cbody
, EncodeType::eValueEncode
);
666 nsCOMPtr
<nsIInputStream
> bodyStream
;
667 rv
= NS_NewCStringInputStream(getter_AddRefs(bodyStream
), std::move(cbody
));
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
);
685 } // anonymous namespace
687 // --------------------------------------------------------------------------
689 HTMLFormSubmission::HTMLFormSubmission(
690 nsIURI
* aActionURL
, const nsAString
& aTarget
,
691 mozilla::NotNull
<const mozilla::Encoding
*> aEncoding
)
692 : mActionURL(aActionURL
),
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()) {
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
,
718 EncodeType aEncodeType
) {
720 std::tie(rv
, std::ignore
) = mEncoding
->Encode(aStr
, aOut
);
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.
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");
756 // --------------------------------------------------------------------------
760 void GetEnumAttr(nsGenericHTMLElement
* aContent
, nsAtom
* atom
,
762 const nsAttrValue
* value
= aContent
->GetParsedAttr(atom
);
763 if (value
&& value
->Type() == nsAttrValue::eEnum
) {
764 *aValue
= value
->GetEnumValue();
768 } // anonymous namespace
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!");
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
);
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.
794 return NS_ERROR_FAILURE
;
799 aSubmitter
->ResultForDialogSubmit(result
);
801 *aFormSubmission
= new DialogFormSubmission(result
, aEncoding
, dialog
);
805 MOZ_ASSERT(method
!= NS_FORM_METHOD_DIALOG
);
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();
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 */,
824 NS_ENSURE_SUCCESS(rv
, rv
);
825 if (!permitsFormAction
) {
826 return NS_ERROR_CSP_FORM_ACTION_VIOLATION
;
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.
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
);
849 GetEnumAttr(aForm
, nsGkAtoms::enctype
, &enctype
);
853 if (method
== NS_FORM_METHOD_POST
&& enctype
== NS_FORM_ENCTYPE_MULTIPART
) {
855 new FSMultipartFormData(actionURL
, target
, aEncoding
, aSubmitter
);
856 } else if (method
== NS_FORM_METHOD_POST
&&
857 enctype
== NS_FORM_ENCTYPE_TEXTPLAIN
) {
859 new FSTextPlain(actionURL
, target
, aEncoding
, aSubmitter
);
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
);
869 aForm
->GetAttr(nsGkAtoms::enctype
, enctypeStr
);
872 SendJSWarning(doc
, "ForgotPostWarning", args
);
875 new FSURLEncoded(actionURL
, target
, aEncoding
, method
, doc
, aSubmitter
);
881 } // namespace mozilla::dom