Bug 1560374 - Set testharness and reftest web-platform-tests to Tier-1; r=jmaher...
[gecko.git] / dom / html / HTMLFormSubmission.cpp
blob508bdb38d6c91f3e08309839dd921b388b569ea4
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"
9 #include "nsCOMPtr.h"
10 #include "nsIForm.h"
11 #include "mozilla/dom/Document.h"
12 #include "nsGkAtoms.h"
13 #include "nsIFormControl.h"
14 #include "nsError.h"
15 #include "nsGenericHTMLElement.h"
16 #include "nsAttrValueInlines.h"
17 #include "nsIFile.h"
18 #include "nsDirectoryServiceDefs.h"
19 #include "nsStringStream.h"
20 #include "nsIURI.h"
21 #include "nsIURIMutator.h"
22 #include "nsIURL.h"
23 #include "nsNetUtil.h"
24 #include "nsLinebreakConverter.h"
25 #include "nsEscape.h"
26 #include "nsUnicharUtils.h"
27 #include "nsIMultiplexInputStream.h"
28 #include "nsIMIMEInputStream.h"
29 #include "nsIMIMEService.h"
30 #include "nsIConsoleService.h"
31 #include "nsIScriptError.h"
32 #include "nsIStringBundle.h"
33 #include "nsCExternalHandlerService.h"
34 #include "nsIFileStreams.h"
35 #include "nsContentUtils.h"
37 #include "mozilla/dom/Directory.h"
38 #include "mozilla/dom/File.h"
39 #include "mozilla/StaticPrefs.h"
41 namespace mozilla {
42 namespace dom {
44 namespace {
46 void SendJSWarning(Document* aDocument, const char* aWarningName,
47 const nsTArray<nsString>& aWarningArgs) {
48 nsContentUtils::ReportToConsole(
49 nsIScriptError::warningFlag, NS_LITERAL_CSTRING("HTML"), aDocument,
50 nsContentUtils::eFORMS_PROPERTIES, aWarningName, aWarningArgs);
53 void RetrieveFileName(Blob* aBlob, nsAString& aFilename) {
54 if (!aBlob) {
55 return;
58 RefPtr<File> file = aBlob->ToFile();
59 if (file) {
60 file->GetName(aFilename);
64 void RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname) {
65 MOZ_ASSERT(aDirectory);
67 ErrorResult rv;
68 aDirectory->GetName(aDirname, rv);
69 if (NS_WARN_IF(rv.Failed())) {
70 rv.SuppressException();
71 aDirname.Truncate();
75 // --------------------------------------------------------------------------
77 class FSURLEncoded : public EncodingFormSubmission {
78 public:
79 /**
80 * @param aEncoding the character encoding of the form
81 * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
82 * NS_FORM_METHOD_POST).
84 FSURLEncoded(nsIURI* aActionURL, const nsAString& aTarget,
85 NotNull<const Encoding*> aEncoding, int32_t aMethod,
86 Document* aDocument, Element* aOriginatingElement)
87 : EncodingFormSubmission(aActionURL, aTarget, aEncoding,
88 aOriginatingElement),
89 mMethod(aMethod),
90 mDocument(aDocument),
91 mWarnedFileControl(false) {}
93 virtual nsresult AddNameValuePair(const nsAString& aName,
94 const nsAString& aValue) override;
96 virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
97 Blob* aBlob) override;
99 virtual nsresult AddNameDirectoryPair(const nsAString& aName,
100 Directory* aDirectory) override;
102 virtual nsresult GetEncodedSubmission(nsIURI* aURI,
103 nsIInputStream** aPostDataStream,
104 nsCOMPtr<nsIURI>& aOutURI) override;
106 protected:
108 * URL encode a Unicode string by encoding it to bytes, converting linebreaks
109 * properly, and then escaping many bytes as %xx.
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
115 nsresult URLEncode(const nsAString& aStr, nsACString& aEncoded);
117 private:
119 * The method of the submit (either NS_FORM_METHOD_GET or
120 * NS_FORM_METHOD_POST).
122 int32_t mMethod;
124 /** The query string so far (the part after the ?) */
125 nsCString mQueryString;
127 /** The document whose URI to use when reporting errors */
128 nsCOMPtr<Document> mDocument;
130 /** Whether or not we have warned about a file control not being submitted */
131 bool mWarnedFileControl;
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);
141 // Encode name
142 nsAutoCString convName;
143 rv = URLEncode(aName, convName);
144 NS_ENSURE_SUCCESS(rv, rv);
146 // Append data to string
147 if (mQueryString.IsEmpty()) {
148 mQueryString += convName + NS_LITERAL_CSTRING("=") + convValue;
149 } else {
150 mQueryString += NS_LITERAL_CSTRING("&") + convName +
151 NS_LITERAL_CSTRING("=") + convValue;
154 return NS_OK;
157 nsresult FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName,
158 Blob* aBlob) {
159 if (!mWarnedFileControl) {
160 SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nsTArray<nsString>());
161 mWarnedFileControl = true;
164 nsAutoString filename;
165 RetrieveFileName(aBlob, filename);
166 return AddNameValuePair(aName, filename);
169 nsresult FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
170 Directory* aDirectory) {
171 // No warning about because Directory objects are never sent via form.
173 nsAutoString dirname;
174 RetrieveDirectoryName(aDirectory, dirname);
175 return AddNameValuePair(aName, dirname);
178 void HandleMailtoSubject(nsCString& aPath) {
179 // Walk through the string and see if we have a subject already.
180 bool hasSubject = false;
181 bool hasParams = false;
182 int32_t paramSep = aPath.FindChar('?');
183 while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
184 hasParams = true;
186 // Get the end of the name at the = op. If it is *after* the next &,
187 // assume that someone made a parameter without an = in it
188 int32_t nameEnd = aPath.FindChar('=', paramSep + 1);
189 int32_t nextParamSep = aPath.FindChar('&', paramSep + 1);
190 if (nextParamSep == kNotFound) {
191 nextParamSep = aPath.Length();
194 // If the = op is after the &, this parameter is a name without value.
195 // If there is no = op, same thing.
196 if (nameEnd == kNotFound || nextParamSep < nameEnd) {
197 nameEnd = nextParamSep;
200 if (nameEnd != kNotFound) {
201 if (Substring(aPath, paramSep + 1, nameEnd - (paramSep + 1))
202 .LowerCaseEqualsLiteral("subject")) {
203 hasSubject = true;
204 break;
208 paramSep = nextParamSep;
211 // If there is no subject, append a preformed subject to the mailto line
212 if (!hasSubject) {
213 if (hasParams) {
214 aPath.Append('&');
215 } else {
216 aPath.Append('?');
219 // Get the default subject
220 nsAutoString brandName;
221 nsresult rv = nsContentUtils::GetLocalizedString(
222 nsContentUtils::eBRAND_PROPERTIES, "brandShortName", brandName);
223 if (NS_FAILED(rv)) return;
224 nsAutoString subjectStr;
225 rv = nsContentUtils::FormatLocalizedString(
226 subjectStr, nsContentUtils::eFORMS_PROPERTIES, "DefaultFormSubject",
227 brandName);
228 if (NS_FAILED(rv)) return;
229 aPath.AppendLiteral("subject=");
230 nsCString subjectStrEscaped;
231 rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
232 subjectStrEscaped, mozilla::fallible);
233 if (NS_FAILED(rv)) return;
235 aPath.Append(subjectStrEscaped);
239 nsresult FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
240 nsIInputStream** aPostDataStream,
241 nsCOMPtr<nsIURI>& aOutURI) {
242 nsresult rv = NS_OK;
243 aOutURI = aURI;
245 *aPostDataStream = nullptr;
247 if (mMethod == NS_FORM_METHOD_POST) {
248 bool isMailto = false;
249 aURI->SchemeIs("mailto", &isMailto);
250 if (isMailto) {
251 nsAutoCString path;
252 rv = aURI->GetPathQueryRef(path);
253 NS_ENSURE_SUCCESS(rv, rv);
255 HandleMailtoSubject(path);
257 // Append the body to and force-plain-text args to the mailto line
258 nsAutoCString escapedBody;
259 if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) {
260 return NS_ERROR_OUT_OF_MEMORY;
263 path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
265 return NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
266 } else {
267 nsCOMPtr<nsIInputStream> dataStream;
268 rv = NS_NewCStringInputStream(getter_AddRefs(dataStream),
269 std::move(mQueryString));
270 NS_ENSURE_SUCCESS(rv, rv);
271 mQueryString.Truncate();
273 nsCOMPtr<nsIMIMEInputStream> mimeStream(
274 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
275 NS_ENSURE_SUCCESS(rv, rv);
277 mimeStream->AddHeader("Content-Type",
278 "application/x-www-form-urlencoded");
279 mimeStream->SetData(dataStream);
281 mimeStream.forget(aPostDataStream);
284 } else {
285 // Get the full query string
286 bool schemeIsJavaScript;
287 rv = aURI->SchemeIs("javascript", &schemeIsJavaScript);
288 NS_ENSURE_SUCCESS(rv, rv);
289 if (schemeIsJavaScript) {
290 return NS_OK;
293 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
294 if (url) {
295 rv = NS_MutateURI(aURI).SetQuery(mQueryString).Finalize(aOutURI);
296 } else {
297 nsAutoCString path;
298 rv = aURI->GetPathQueryRef(path);
299 NS_ENSURE_SUCCESS(rv, rv);
300 // Bug 42616: Trim off named anchor and save it to add later
301 int32_t namedAnchorPos = path.FindChar('#');
302 nsAutoCString namedAnchor;
303 if (kNotFound != namedAnchorPos) {
304 path.Right(namedAnchor, (path.Length() - namedAnchorPos));
305 path.Truncate(namedAnchorPos);
308 // Chop off old query string (bug 25330, 57333)
309 // Only do this for GET not POST (bug 41585)
310 int32_t queryStart = path.FindChar('?');
311 if (kNotFound != queryStart) {
312 path.Truncate(queryStart);
315 path.Append('?');
316 // Bug 42616: Add named anchor to end after query string
317 path.Append(mQueryString + namedAnchor);
319 rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
323 return rv;
326 // i18n helper routines
327 nsresult FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) {
328 // convert to CRLF breaks
329 int32_t convertedBufLength = 0;
330 char16_t* convertedBuf = nsLinebreakConverter::ConvertUnicharLineBreaks(
331 aStr.BeginReading(), nsLinebreakConverter::eLinebreakAny,
332 nsLinebreakConverter::eLinebreakNet, aStr.Length(), &convertedBufLength);
333 NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
335 nsAutoString convertedString;
336 convertedString.Adopt(convertedBuf, convertedBufLength);
338 nsAutoCString encodedBuf;
339 nsresult rv = EncodeVal(convertedString, encodedBuf, false);
340 NS_ENSURE_SUCCESS(rv, rv);
342 if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
343 return NS_ERROR_OUT_OF_MEMORY;
346 return NS_OK;
349 } // anonymous namespace
351 // --------------------------------------------------------------------------
353 FSMultipartFormData::FSMultipartFormData(nsIURI* aActionURL,
354 const nsAString& aTarget,
355 NotNull<const Encoding*> aEncoding,
356 Element* aOriginatingElement)
357 : EncodingFormSubmission(aActionURL, aTarget, aEncoding,
358 aOriginatingElement) {
359 mPostData = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
361 nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mPostData);
362 MOZ_ASSERT(SameCOMIdentity(mPostData, inputStream));
363 mPostDataStream = inputStream;
365 mTotalLength = 0;
367 mBoundary.AssignLiteral("---------------------------");
368 mBoundary.AppendInt(rand());
369 mBoundary.AppendInt(rand());
370 mBoundary.AppendInt(rand());
373 FSMultipartFormData::~FSMultipartFormData() {
374 NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
377 nsIInputStream* FSMultipartFormData::GetSubmissionBody(
378 uint64_t* aContentLength) {
379 // Finish data
380 mPostDataChunk +=
381 NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING("--" CRLF);
383 // Add final data input stream
384 AddPostDataStream();
386 *aContentLength = mTotalLength;
387 return mPostDataStream;
390 nsresult FSMultipartFormData::AddNameValuePair(const nsAString& aName,
391 const nsAString& aValue) {
392 nsCString valueStr;
393 nsAutoCString encodedVal;
394 nsresult rv = EncodeVal(aValue, encodedVal, false);
395 NS_ENSURE_SUCCESS(rv, rv);
397 valueStr.Adopt(nsLinebreakConverter::ConvertLineBreaks(
398 encodedVal.get(), nsLinebreakConverter::eLinebreakAny,
399 nsLinebreakConverter::eLinebreakNet));
401 nsAutoCString nameStr;
402 rv = EncodeVal(aName, nameStr, true);
403 NS_ENSURE_SUCCESS(rv, rv);
405 // Make MIME block for name/value pair
407 // XXX: name parameter should be encoded per RFC 2231
408 // RFC 2388 specifies that RFC 2047 be used, but I think it's not
409 // consistent with MIME standard.
410 mPostDataChunk +=
411 NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF) +
412 NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + nameStr +
413 NS_LITERAL_CSTRING("\"" CRLF CRLF) + valueStr + NS_LITERAL_CSTRING(CRLF);
415 return NS_OK;
418 nsresult FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName,
419 Blob* aBlob) {
420 // Encode the control name
421 nsAutoCString nameStr;
422 nsresult rv = EncodeVal(aName, nameStr, true);
423 NS_ENSURE_SUCCESS(rv, rv);
425 ErrorResult error;
427 uint64_t size = 0;
428 nsAutoCString filename;
429 nsAutoCString contentType;
430 nsCOMPtr<nsIInputStream> fileStream;
432 if (aBlob) {
433 nsAutoString filename16;
435 RefPtr<File> file = aBlob->ToFile();
436 if (file) {
437 nsAutoString relativePath;
438 file->GetRelativePath(relativePath);
439 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
440 !relativePath.IsEmpty()) {
441 filename16 = relativePath;
444 if (filename16.IsEmpty()) {
445 RetrieveFileName(aBlob, filename16);
449 rv = EncodeVal(filename16, filename, true);
450 NS_ENSURE_SUCCESS(rv, rv);
452 // Get content type
453 nsAutoString contentType16;
454 aBlob->GetType(contentType16);
455 if (contentType16.IsEmpty()) {
456 contentType16.AssignLiteral("application/octet-stream");
459 contentType.Adopt(nsLinebreakConverter::ConvertLineBreaks(
460 NS_ConvertUTF16toUTF8(contentType16).get(),
461 nsLinebreakConverter::eLinebreakAny,
462 nsLinebreakConverter::eLinebreakSpace));
464 // Get input stream
465 aBlob->CreateInputStream(getter_AddRefs(fileStream), error);
466 if (NS_WARN_IF(error.Failed())) {
467 return error.StealNSResult();
470 // Get size
471 size = aBlob->GetSize(error);
472 if (error.Failed()) {
473 error.SuppressException();
474 fileStream = nullptr;
477 if (fileStream) {
478 // Create buffered stream (for efficiency)
479 nsCOMPtr<nsIInputStream> bufferedStream;
480 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
481 fileStream.forget(), 8192);
482 NS_ENSURE_SUCCESS(rv, rv);
484 fileStream = bufferedStream;
486 } else {
487 contentType.AssignLiteral("application/octet-stream");
490 AddDataChunk(nameStr, filename, contentType, fileStream, size);
491 return NS_OK;
494 nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
495 Directory* aDirectory) {
496 if (!StaticPrefs::dom_webkitBlink_dirPicker_enabled()) {
497 return NS_OK;
500 // Encode the control name
501 nsAutoCString nameStr;
502 nsresult rv = EncodeVal(aName, nameStr, true);
503 NS_ENSURE_SUCCESS(rv, rv);
505 nsAutoCString dirname;
506 nsAutoString dirname16;
508 ErrorResult error;
509 nsAutoString path;
510 aDirectory->GetPath(path, error);
511 if (NS_WARN_IF(error.Failed())) {
512 error.SuppressException();
513 } else {
514 dirname16 = path;
517 if (dirname16.IsEmpty()) {
518 RetrieveDirectoryName(aDirectory, dirname16);
521 rv = EncodeVal(dirname16, dirname, true);
522 NS_ENSURE_SUCCESS(rv, rv);
524 AddDataChunk(nameStr, dirname, NS_LITERAL_CSTRING("application/octet-stream"),
525 nullptr, 0);
526 return NS_OK;
529 void FSMultipartFormData::AddDataChunk(const nsACString& aName,
530 const nsACString& aFilename,
531 const nsACString& aContentType,
532 nsIInputStream* aInputStream,
533 uint64_t aInputStreamSize) {
535 // Make MIME block for name/value pair
537 // more appropriate than always using binary?
538 mPostDataChunk +=
539 NS_LITERAL_CSTRING("--") + mBoundary + NS_LITERAL_CSTRING(CRLF);
540 // XXX: name/filename parameter should be encoded per RFC 2231
541 // RFC 2388 specifies that RFC 2047 be used, but I think it's not
542 // consistent with the MIME standard.
543 mPostDataChunk +=
544 NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + aName +
545 NS_LITERAL_CSTRING("\"; filename=\"") + aFilename +
546 NS_LITERAL_CSTRING("\"" CRLF) + NS_LITERAL_CSTRING("Content-Type: ") +
547 aContentType + NS_LITERAL_CSTRING(CRLF CRLF);
549 // We should not try to append an invalid stream. That will happen for example
550 // if we try to update a file that actually do not exist.
551 if (aInputStream) {
552 // We need to dump the data up to this point into the POST data stream
553 // here, since we're about to add the file input stream
554 AddPostDataStream();
556 mPostData->AppendStream(aInputStream);
557 mTotalLength += aInputStreamSize;
560 // CRLF after file
561 mPostDataChunk.AppendLiteral(CRLF);
564 nsresult FSMultipartFormData::GetEncodedSubmission(
565 nsIURI* aURI, nsIInputStream** aPostDataStream, nsCOMPtr<nsIURI>& aOutURI) {
566 nsresult rv;
567 aOutURI = aURI;
569 // Make header
570 nsCOMPtr<nsIMIMEInputStream> mimeStream =
571 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
572 NS_ENSURE_SUCCESS(rv, rv);
574 nsAutoCString contentType;
575 GetContentType(contentType);
576 mimeStream->AddHeader("Content-Type", contentType.get());
578 uint64_t bodySize;
579 mimeStream->SetData(GetSubmissionBody(&bodySize));
581 mimeStream.forget(aPostDataStream);
583 return NS_OK;
586 nsresult FSMultipartFormData::AddPostDataStream() {
587 nsresult rv = NS_OK;
589 nsCOMPtr<nsIInputStream> postDataChunkStream;
590 rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
591 mPostDataChunk);
592 NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
593 if (postDataChunkStream) {
594 mPostData->AppendStream(postDataChunkStream);
595 mTotalLength += mPostDataChunk.Length();
598 mPostDataChunk.Truncate();
600 return rv;
603 // --------------------------------------------------------------------------
605 namespace {
607 class FSTextPlain : public EncodingFormSubmission {
608 public:
609 FSTextPlain(nsIURI* aActionURL, const nsAString& aTarget,
610 NotNull<const Encoding*> aEncoding, Element* aOriginatingElement)
611 : EncodingFormSubmission(aActionURL, aTarget, aEncoding,
612 aOriginatingElement) {}
614 virtual nsresult AddNameValuePair(const nsAString& aName,
615 const nsAString& aValue) override;
617 virtual nsresult AddNameBlobOrNullPair(const nsAString& aName,
618 Blob* aBlob) override;
620 virtual nsresult AddNameDirectoryPair(const nsAString& aName,
621 Directory* aDirectory) override;
623 virtual nsresult GetEncodedSubmission(nsIURI* aURI,
624 nsIInputStream** aPostDataStream,
625 nsCOMPtr<nsIURI>& aOutURI) override;
627 private:
628 nsString mBody;
631 nsresult FSTextPlain::AddNameValuePair(const nsAString& aName,
632 const nsAString& aValue) {
633 // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose
634 // text/plain doesn't care about that. Parsers aren't built for escaped
635 // values so we'll have to live with it.
636 mBody.Append(aName + NS_LITERAL_STRING("=") + aValue +
637 NS_LITERAL_STRING(CRLF));
639 return NS_OK;
642 nsresult FSTextPlain::AddNameBlobOrNullPair(const nsAString& aName,
643 Blob* aBlob) {
644 nsAutoString filename;
645 RetrieveFileName(aBlob, filename);
646 AddNameValuePair(aName, filename);
647 return NS_OK;
650 nsresult FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
651 Directory* aDirectory) {
652 nsAutoString dirname;
653 RetrieveDirectoryName(aDirectory, dirname);
654 AddNameValuePair(aName, dirname);
655 return NS_OK;
658 nsresult FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
659 nsIInputStream** aPostDataStream,
660 nsCOMPtr<nsIURI>& aOutURI) {
661 nsresult rv = NS_OK;
662 aOutURI = aURI;
664 *aPostDataStream = nullptr;
666 // XXX HACK We are using the standard URL mechanism to give the body to the
667 // mailer instead of passing the post data stream to it, since that sounds
668 // hard.
669 bool isMailto = false;
670 aURI->SchemeIs("mailto", &isMailto);
671 if (isMailto) {
672 nsAutoCString path;
673 rv = aURI->GetPathQueryRef(path);
674 NS_ENSURE_SUCCESS(rv, rv);
676 HandleMailtoSubject(path);
678 // Append the body to and force-plain-text args to the mailto line
679 nsAutoCString escapedBody;
680 if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody,
681 url_XAlphas))) {
682 return NS_ERROR_OUT_OF_MEMORY;
685 path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
687 rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
688 } else {
689 // Create data stream.
690 // We do want to send the data through the charset encoder and we want to
691 // normalize linebreaks to use the "standard net" format (\r\n), but we
692 // don't want to perform any other encoding. This means that names and
693 // values which contains '=' or newlines are potentially ambigiously
694 // encoded, but that how text/plain is specced.
695 nsCString cbody;
696 EncodeVal(mBody, cbody, false);
697 cbody.Adopt(nsLinebreakConverter::ConvertLineBreaks(
698 cbody.get(), nsLinebreakConverter::eLinebreakAny,
699 nsLinebreakConverter::eLinebreakNet));
700 nsCOMPtr<nsIInputStream> bodyStream;
701 rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), std::move(cbody));
702 if (!bodyStream) {
703 return NS_ERROR_OUT_OF_MEMORY;
706 // Create mime stream with headers and such
707 nsCOMPtr<nsIMIMEInputStream> mimeStream =
708 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
709 NS_ENSURE_SUCCESS(rv, rv);
711 mimeStream->AddHeader("Content-Type", "text/plain");
712 mimeStream->SetData(bodyStream);
713 mimeStream.forget(aPostDataStream);
716 return rv;
719 } // anonymous namespace
721 // --------------------------------------------------------------------------
723 EncodingFormSubmission::EncodingFormSubmission(
724 nsIURI* aActionURL, const nsAString& aTarget,
725 NotNull<const Encoding*> aEncoding, Element* aOriginatingElement)
726 : HTMLFormSubmission(aActionURL, aTarget, aEncoding, aOriginatingElement) {
727 if (!aEncoding->CanEncodeEverything()) {
728 nsAutoCString name;
729 aEncoding->Name(name);
730 AutoTArray<nsString, 1> args;
731 CopyUTF8toUTF16(name, *args.AppendElement());
732 SendJSWarning(
733 aOriginatingElement ? aOriginatingElement->GetOwnerDocument() : nullptr,
734 "CannotEncodeAllUnicode", args);
738 EncodingFormSubmission::~EncodingFormSubmission() {}
740 // i18n helper routines
741 nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr,
742 nsCString& aOut,
743 bool aHeaderEncode) {
744 nsresult rv;
745 const Encoding* ignored;
746 Tie(rv, ignored) = mEncoding->Encode(aStr, aOut);
747 if (NS_FAILED(rv)) {
748 return rv;
751 if (aHeaderEncode) {
752 aOut.Adopt(nsLinebreakConverter::ConvertLineBreaks(
753 aOut.get(), nsLinebreakConverter::eLinebreakAny,
754 nsLinebreakConverter::eLinebreakSpace));
755 aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""), NS_LITERAL_CSTRING("\\\""));
758 return NS_OK;
761 // --------------------------------------------------------------------------
763 namespace {
765 NotNull<const Encoding*> GetSubmitEncoding(nsGenericHTMLElement* aForm) {
766 nsAutoString acceptCharsetValue;
767 aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset,
768 acceptCharsetValue);
770 int32_t charsetLen = acceptCharsetValue.Length();
771 if (charsetLen > 0) {
772 int32_t offset = 0;
773 int32_t spPos = 0;
774 // get charset from charsets one by one
775 do {
776 spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
777 int32_t cnt = ((-1 == spPos) ? (charsetLen - offset) : (spPos - offset));
778 if (cnt > 0) {
779 nsAutoString uCharset;
780 acceptCharsetValue.Mid(uCharset, offset, cnt);
782 auto encoding = Encoding::ForLabelNoReplacement(uCharset);
783 if (encoding) {
784 return WrapNotNull(encoding);
787 offset = spPos + 1;
788 } while (spPos != -1);
790 // if there are no accept-charset or all the charset are not supported
791 // Get the charset from document
792 Document* doc = aForm->GetComposedDoc();
793 if (doc) {
794 return doc->GetDocumentCharacterSet();
796 return UTF_8_ENCODING;
799 void GetEnumAttr(nsGenericHTMLElement* aContent, nsAtom* atom,
800 int32_t* aValue) {
801 const nsAttrValue* value = aContent->GetParsedAttr(atom);
802 if (value && value->Type() == nsAttrValue::eEnum) {
803 *aValue = value->GetEnumValue();
807 } // anonymous namespace
809 /* static */
810 nsresult HTMLFormSubmission::GetFromForm(
811 HTMLFormElement* aForm, nsGenericHTMLElement* aOriginatingElement,
812 HTMLFormSubmission** aFormSubmission) {
813 // Get all the information necessary to encode the form data
814 NS_ASSERTION(aForm->GetComposedDoc(),
815 "Should have doc if we're building submission!");
817 nsresult rv;
819 // Get action
820 nsCOMPtr<nsIURI> actionURL;
821 rv = aForm->GetActionURL(getter_AddRefs(actionURL), aOriginatingElement);
822 NS_ENSURE_SUCCESS(rv, rv);
824 // Check if CSP allows this form-action
825 nsCOMPtr<nsIContentSecurityPolicy> csp = aForm->GetCsp();
826 if (csp) {
827 bool permitsFormAction = true;
829 // form-action is only enforced if explicitly defined in the
830 // policy - do *not* consult default-src, see:
831 // http://www.w3.org/TR/CSP2/#directive-default-src
832 rv = csp->Permits(aForm, nullptr /* nsICSPEventListener */, actionURL,
833 nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE, true,
834 &permitsFormAction);
835 NS_ENSURE_SUCCESS(rv, rv);
836 if (!permitsFormAction) {
837 return NS_ERROR_CSP_FORM_ACTION_VIOLATION;
841 // Get target
842 // The target is the originating element formtarget attribute if the element
843 // is a submit control and has such an attribute.
844 // Otherwise, the target is the form owner's target attribute,
845 // if it has such an attribute.
846 // Finally, if one of the child nodes of the head element is a base element
847 // with a target attribute, then the value of the target attribute of the
848 // first such base element; or, if there is no such element, the empty string.
849 nsAutoString target;
850 if (!(aOriginatingElement &&
851 aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formtarget,
852 target)) &&
853 !aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
854 aForm->GetBaseTarget(target);
857 // Get encoding type (default: urlencoded)
858 int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
859 if (aOriginatingElement &&
860 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
861 GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype);
862 } else {
863 GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
866 // Get method (default: GET)
867 int32_t method = NS_FORM_METHOD_GET;
868 if (aOriginatingElement &&
869 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formmethod)) {
870 GetEnumAttr(aOriginatingElement, nsGkAtoms::formmethod, &method);
871 } else {
872 GetEnumAttr(aForm, nsGkAtoms::method, &method);
875 // Get encoding
876 auto encoding = GetSubmitEncoding(aForm)->OutputEncoding();
878 // Choose encoder
879 if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_MULTIPART) {
880 *aFormSubmission = new FSMultipartFormData(actionURL, target, encoding,
881 aOriginatingElement);
882 } else if (method == NS_FORM_METHOD_POST &&
883 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
884 *aFormSubmission =
885 new FSTextPlain(actionURL, target, encoding, aOriginatingElement);
886 } else {
887 Document* doc = aForm->OwnerDoc();
888 if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
889 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
890 AutoTArray<nsString, 1> args;
891 nsString& enctypeStr = *args.AppendElement();
892 if (aOriginatingElement &&
893 aOriginatingElement->HasAttr(kNameSpaceID_None,
894 nsGkAtoms::formenctype)) {
895 aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype,
896 enctypeStr);
897 } else {
898 aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr);
901 SendJSWarning(doc, "ForgotPostWarning", args);
903 *aFormSubmission = new FSURLEncoded(actionURL, target, encoding, method,
904 doc, aOriginatingElement);
907 return NS_OK;
910 } // namespace dom
911 } // namespace mozilla