summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLFormSubmission.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLFormSubmission.cpp')
-rw-r--r--dom/html/HTMLFormSubmission.cpp881
1 files changed, 881 insertions, 0 deletions
diff --git a/dom/html/HTMLFormSubmission.cpp b/dom/html/HTMLFormSubmission.cpp
new file mode 100644
index 0000000000..fa25794274
--- /dev/null
+++ b/dom/html/HTMLFormSubmission.cpp
@@ -0,0 +1,881 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HTMLFormSubmission.h"
+#include "HTMLFormElement.h"
+#include "HTMLFormSubmissionConstants.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGkAtoms.h"
+#include "nsIFormControl.h"
+#include "nsError.h"
+#include "nsGenericHTMLElement.h"
+#include "nsAttrValueInlines.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsStringStream.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsLinebreakConverter.h"
+#include "nsEscape.h"
+#include "nsUnicharUtils.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIScriptError.h"
+#include "nsCExternalHandlerService.h"
+#include "nsContentUtils.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/RandomNum.h"
+
+#include <tuple>
+
+namespace mozilla::dom {
+
+namespace {
+
+void SendJSWarning(Document* aDocument, const char* aWarningName,
+ const nsTArray<nsString>& aWarningArgs) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "HTML"_ns,
+ aDocument, nsContentUtils::eFORMS_PROPERTIES,
+ aWarningName, aWarningArgs);
+}
+
+void RetrieveFileName(Blob* aBlob, nsAString& aFilename) {
+ if (!aBlob) {
+ return;
+ }
+
+ RefPtr<File> file = aBlob->ToFile();
+ if (file) {
+ file->GetName(aFilename);
+ }
+}
+
+void RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname) {
+ MOZ_ASSERT(aDirectory);
+
+ ErrorResult rv;
+ aDirectory->GetName(aDirname, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ aDirname.Truncate();
+ }
+}
+
+// --------------------------------------------------------------------------
+
+class FSURLEncoded : public EncodingFormSubmission {
+ public:
+ /**
+ * @param aEncoding the character encoding of the form
+ * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
+ * NS_FORM_METHOD_POST).
+ */
+ FSURLEncoded(nsIURI* aActionURL, const nsAString& aTarget,
+ NotNull<const Encoding*> aEncoding, int32_t aMethod,
+ Document* aDocument, Element* aSubmitter)
+ : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter),
+ mMethod(aMethod),
+ mDocument(aDocument),
+ mWarnedFileControl(false) {}
+
+ virtual nsresult AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) override;
+
+ virtual nsresult AddNameBlobPair(const nsAString& aName,
+ Blob* aBlob) override;
+
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) override;
+
+ virtual nsresult GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) override;
+
+ protected:
+ /**
+ * URL encode a Unicode string by encoding it to bytes, converting linebreaks
+ * properly, and then escaping many bytes as %xx.
+ *
+ * @param aStr the string to encode
+ * @param aEncoded the encoded string [OUT]
+ * @throws NS_ERROR_OUT_OF_MEMORY if we run out of memory
+ */
+ nsresult URLEncode(const nsAString& aStr, nsACString& aEncoded);
+
+ private:
+ /**
+ * The method of the submit (either NS_FORM_METHOD_GET or
+ * NS_FORM_METHOD_POST).
+ */
+ int32_t mMethod;
+
+ /** The query string so far (the part after the ?) */
+ nsCString mQueryString;
+
+ /** The document whose URI to use when reporting errors */
+ nsCOMPtr<Document> mDocument;
+
+ /** Whether or not we have warned about a file control not being submitted */
+ bool mWarnedFileControl;
+};
+
+nsresult FSURLEncoded::AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) {
+ // Encode value
+ nsCString convValue;
+ nsresult rv = URLEncode(aValue, convValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Encode name
+ nsAutoCString convName;
+ rv = URLEncode(aName, convName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Append data to string
+ if (mQueryString.IsEmpty()) {
+ mQueryString += convName + "="_ns + convValue;
+ } else {
+ mQueryString += "&"_ns + convName + "="_ns + convValue;
+ }
+
+ return NS_OK;
+}
+
+nsresult FSURLEncoded::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
+ if (!mWarnedFileControl) {
+ SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nsTArray<nsString>());
+ mWarnedFileControl = true;
+ }
+
+ nsAutoString filename;
+ RetrieveFileName(aBlob, filename);
+ return AddNameValuePair(aName, filename);
+}
+
+nsresult FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) {
+ // No warning about because Directory objects are never sent via form.
+
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ return AddNameValuePair(aName, dirname);
+}
+
+void HandleMailtoSubject(nsCString& aPath) {
+ // Walk through the string and see if we have a subject already.
+ bool hasSubject = false;
+ bool hasParams = false;
+ int32_t paramSep = aPath.FindChar('?');
+ while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
+ hasParams = true;
+
+ // Get the end of the name at the = op. If it is *after* the next &,
+ // assume that someone made a parameter without an = in it
+ int32_t nameEnd = aPath.FindChar('=', paramSep + 1);
+ int32_t nextParamSep = aPath.FindChar('&', paramSep + 1);
+ if (nextParamSep == kNotFound) {
+ nextParamSep = aPath.Length();
+ }
+
+ // If the = op is after the &, this parameter is a name without value.
+ // If there is no = op, same thing.
+ if (nameEnd == kNotFound || nextParamSep < nameEnd) {
+ nameEnd = nextParamSep;
+ }
+
+ if (nameEnd != kNotFound) {
+ if (Substring(aPath, paramSep + 1, nameEnd - (paramSep + 1))
+ .LowerCaseEqualsLiteral("subject")) {
+ hasSubject = true;
+ break;
+ }
+ }
+
+ paramSep = nextParamSep;
+ }
+
+ // If there is no subject, append a preformed subject to the mailto line
+ if (!hasSubject) {
+ if (hasParams) {
+ aPath.Append('&');
+ } else {
+ aPath.Append('?');
+ }
+
+ // Get the default subject
+ nsAutoString brandName;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eBRAND_PROPERTIES, "brandShortName", brandName);
+ if (NS_FAILED(rv)) return;
+ nsAutoString subjectStr;
+ rv = nsContentUtils::FormatLocalizedString(
+ subjectStr, nsContentUtils::eFORMS_PROPERTIES, "DefaultFormSubject",
+ brandName);
+ if (NS_FAILED(rv)) return;
+ aPath.AppendLiteral("subject=");
+ nsCString subjectStrEscaped;
+ rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
+ subjectStrEscaped, mozilla::fallible);
+ if (NS_FAILED(rv)) return;
+
+ aPath.Append(subjectStrEscaped);
+ }
+}
+
+nsresult FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) {
+ nsresult rv = NS_OK;
+ aOutURI = aURI;
+
+ *aPostDataStream = nullptr;
+
+ if (mMethod == NS_FORM_METHOD_POST) {
+ if (aURI->SchemeIs("mailto")) {
+ nsAutoCString path;
+ rv = aURI->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HandleMailtoSubject(path);
+
+ // Append the body to and force-plain-text args to the mailto line
+ nsAutoCString escapedBody;
+ if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ path += "&force-plain-text=Y&body="_ns + escapedBody;
+
+ return NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
+ } else {
+ nsCOMPtr<nsIInputStream> dataStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(dataStream),
+ std::move(mQueryString));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mQueryString.Truncate();
+
+ nsCOMPtr<nsIMIMEInputStream> mimeStream(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeStream->AddHeader("Content-Type",
+ "application/x-www-form-urlencoded");
+ mimeStream->SetData(dataStream);
+
+ mimeStream.forget(aPostDataStream);
+ }
+
+ } else {
+ // Get the full query string
+ if (aURI->SchemeIs("javascript")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
+ if (url) {
+ // Make sure that we end up with a query component in the URL. If
+ // mQueryString is empty, nsIURI::SetQuery() will remove the query
+ // component, which is not what we want.
+ rv = NS_MutateURI(aURI)
+ .SetQuery(mQueryString.IsEmpty() ? "?"_ns : mQueryString)
+ .Finalize(aOutURI);
+ } else {
+ nsAutoCString path;
+ rv = aURI->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Bug 42616: Trim off named anchor and save it to add later
+ int32_t namedAnchorPos = path.FindChar('#');
+ nsAutoCString namedAnchor;
+ if (kNotFound != namedAnchorPos) {
+ path.Right(namedAnchor, (path.Length() - namedAnchorPos));
+ path.Truncate(namedAnchorPos);
+ }
+
+ // Chop off old query string (bug 25330, 57333)
+ // Only do this for GET not POST (bug 41585)
+ int32_t queryStart = path.FindChar('?');
+ if (kNotFound != queryStart) {
+ path.Truncate(queryStart);
+ }
+
+ path.Append('?');
+ // Bug 42616: Add named anchor to end after query string
+ path.Append(mQueryString + namedAnchor);
+
+ rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
+ }
+ }
+
+ return rv;
+}
+
+// i18n helper routines
+nsresult FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) {
+ nsAutoCString encodedBuf;
+ // We encode with eValueEncode because the urlencoded format needs the newline
+ // normalizations but percent-escapes characters that eNameEncode doesn't,
+ // so calling NS_Escape would still be needed.
+ nsresult rv = EncodeVal(aStr, encodedBuf, EncodeType::eValueEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+// --------------------------------------------------------------------------
+
+FSMultipartFormData::FSMultipartFormData(nsIURI* aActionURL,
+ const nsAString& aTarget,
+ NotNull<const Encoding*> aEncoding,
+ Element* aSubmitter)
+ : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {
+ mPostData = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
+
+ nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mPostData);
+ MOZ_ASSERT(SameCOMIdentity(mPostData, inputStream));
+ mPostDataStream = inputStream;
+
+ mTotalLength = 0;
+
+ mBoundary.AssignLiteral("---------------------------");
+ mBoundary.AppendInt(static_cast<uint32_t>(mozilla::RandomUint64OrDie()));
+ mBoundary.AppendInt(static_cast<uint32_t>(mozilla::RandomUint64OrDie()));
+ mBoundary.AppendInt(static_cast<uint32_t>(mozilla::RandomUint64OrDie()));
+}
+
+FSMultipartFormData::~FSMultipartFormData() {
+ NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
+}
+
+nsIInputStream* FSMultipartFormData::GetSubmissionBody(
+ uint64_t* aContentLength) {
+ // Finish data
+ mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString("--" CRLF);
+
+ // Add final data input stream
+ AddPostDataStream();
+
+ *aContentLength = mTotalLength;
+ return mPostDataStream;
+}
+
+nsresult FSMultipartFormData::AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) {
+ nsAutoCString encodedVal;
+ nsresult rv = EncodeVal(aValue, encodedVal, EncodeType::eValueEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nameStr;
+ rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make MIME block for name/value pair
+
+ mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF) +
+ "Content-Disposition: form-data; name=\""_ns + nameStr +
+ nsLiteralCString("\"" CRLF CRLF) + encodedVal +
+ nsLiteralCString(CRLF);
+
+ return NS_OK;
+}
+
+nsresult FSMultipartFormData::AddNameBlobPair(const nsAString& aName,
+ Blob* aBlob) {
+ MOZ_ASSERT(aBlob);
+
+ // Encode the control name
+ nsAutoCString nameStr;
+ nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ErrorResult error;
+
+ uint64_t size = 0;
+ nsAutoCString filename;
+ nsAutoCString contentType;
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsAutoString filename16;
+
+ RefPtr<File> file = aBlob->ToFile();
+ if (file) {
+ nsAutoString relativePath;
+ file->GetRelativePath(relativePath);
+ if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
+ !relativePath.IsEmpty()) {
+ filename16 = relativePath;
+ }
+
+ if (filename16.IsEmpty()) {
+ RetrieveFileName(aBlob, filename16);
+ }
+ }
+
+ rv = EncodeVal(filename16, filename, EncodeType::eFilenameEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get content type
+ nsAutoString contentType16;
+ aBlob->GetType(contentType16);
+ if (contentType16.IsEmpty()) {
+ contentType16.AssignLiteral("application/octet-stream");
+ }
+
+ NS_ConvertUTF16toUTF8 contentType8(contentType16);
+ int32_t convertedBufLength = 0;
+ char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
+ contentType8.get(), nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakSpace, contentType8.Length(),
+ &convertedBufLength);
+ contentType.Adopt(convertedBuf, convertedBufLength);
+
+ // Get input stream
+ aBlob->CreateInputStream(getter_AddRefs(fileStream), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ // Get size
+ size = aBlob->GetSize(error);
+ if (error.Failed()) {
+ error.SuppressException();
+ fileStream = nullptr;
+ }
+
+ if (fileStream) {
+ // Create buffered stream (for efficiency)
+ nsCOMPtr<nsIInputStream> bufferedStream;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
+ fileStream.forget(), 8192);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fileStream = bufferedStream;
+ }
+
+ AddDataChunk(nameStr, filename, contentType, fileStream, size);
+ return NS_OK;
+}
+
+nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) {
+ if (!StaticPrefs::dom_webkitBlink_dirPicker_enabled()) {
+ return NS_OK;
+ }
+
+ // Encode the control name
+ nsAutoCString nameStr;
+ nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString dirname;
+ nsAutoString dirname16;
+
+ ErrorResult error;
+ nsAutoString path;
+ aDirectory->GetPath(path, error);
+ if (NS_WARN_IF(error.Failed())) {
+ error.SuppressException();
+ } else {
+ dirname16 = path;
+ }
+
+ if (dirname16.IsEmpty()) {
+ RetrieveDirectoryName(aDirectory, dirname16);
+ }
+
+ rv = EncodeVal(dirname16, dirname, EncodeType::eFilenameEncode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDataChunk(nameStr, dirname, "application/octet-stream"_ns, nullptr, 0);
+ return NS_OK;
+}
+
+void FSMultipartFormData::AddDataChunk(const nsACString& aName,
+ const nsACString& aFilename,
+ const nsACString& aContentType,
+ nsIInputStream* aInputStream,
+ uint64_t aInputStreamSize) {
+ //
+ // Make MIME block for name/value pair
+ //
+ // more appropriate than always using binary?
+ mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF);
+ mPostDataChunk += "Content-Disposition: form-data; name=\""_ns + aName +
+ "\"; filename=\""_ns + aFilename +
+ nsLiteralCString("\"" CRLF) + "Content-Type: "_ns +
+ aContentType + nsLiteralCString(CRLF CRLF);
+
+ // We should not try to append an invalid stream. That will happen for example
+ // if we try to update a file that actually do not exist.
+ if (aInputStream) {
+ // We need to dump the data up to this point into the POST data stream
+ // here, since we're about to add the file input stream
+ AddPostDataStream();
+
+ mPostData->AppendStream(aInputStream);
+ mTotalLength += aInputStreamSize;
+ }
+
+ // CRLF after file
+ mPostDataChunk.AppendLiteral(CRLF);
+}
+
+nsresult FSMultipartFormData::GetEncodedSubmission(
+ nsIURI* aURI, nsIInputStream** aPostDataStream, nsCOMPtr<nsIURI>& aOutURI) {
+ nsresult rv;
+ aOutURI = aURI;
+
+ // Make header
+ nsCOMPtr<nsIMIMEInputStream> mimeStream =
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentType;
+ GetContentType(contentType);
+ mimeStream->AddHeader("Content-Type", contentType.get());
+
+ uint64_t bodySize;
+ mimeStream->SetData(GetSubmissionBody(&bodySize));
+
+ mimeStream.forget(aPostDataStream);
+
+ return NS_OK;
+}
+
+nsresult FSMultipartFormData::AddPostDataStream() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIInputStream> postDataChunkStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
+ mPostDataChunk);
+ NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
+ if (postDataChunkStream) {
+ mPostData->AppendStream(postDataChunkStream);
+ mTotalLength += mPostDataChunk.Length();
+ }
+
+ mPostDataChunk.Truncate();
+
+ return rv;
+}
+
+// --------------------------------------------------------------------------
+
+namespace {
+
+class FSTextPlain : public EncodingFormSubmission {
+ public:
+ FSTextPlain(nsIURI* aActionURL, const nsAString& aTarget,
+ NotNull<const Encoding*> aEncoding, Element* aSubmitter)
+ : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {}
+
+ virtual nsresult AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) override;
+
+ virtual nsresult AddNameBlobPair(const nsAString& aName,
+ Blob* aBlob) override;
+
+ virtual nsresult AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) override;
+
+ virtual nsresult GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) override;
+
+ private:
+ nsString mBody;
+};
+
+nsresult FSTextPlain::AddNameValuePair(const nsAString& aName,
+ const nsAString& aValue) {
+ // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose
+ // text/plain doesn't care about that. Parsers aren't built for escaped
+ // values so we'll have to live with it.
+ mBody.Append(aName + u"="_ns + aValue + NS_LITERAL_STRING_FROM_CSTRING(CRLF));
+
+ return NS_OK;
+}
+
+nsresult FSTextPlain::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
+ nsAutoString filename;
+ RetrieveFileName(aBlob, filename);
+ AddNameValuePair(aName, filename);
+ return NS_OK;
+}
+
+nsresult FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
+ Directory* aDirectory) {
+ nsAutoString dirname;
+ RetrieveDirectoryName(aDirectory, dirname);
+ AddNameValuePair(aName, dirname);
+ return NS_OK;
+}
+
+nsresult FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
+ nsIInputStream** aPostDataStream,
+ nsCOMPtr<nsIURI>& aOutURI) {
+ nsresult rv = NS_OK;
+ aOutURI = aURI;
+
+ *aPostDataStream = nullptr;
+
+ // XXX HACK We are using the standard URL mechanism to give the body to the
+ // mailer instead of passing the post data stream to it, since that sounds
+ // hard.
+ if (aURI->SchemeIs("mailto")) {
+ nsAutoCString path;
+ rv = aURI->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HandleMailtoSubject(path);
+
+ // Append the body to and force-plain-text args to the mailto line
+ nsAutoCString escapedBody;
+ if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody,
+ url_XAlphas))) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ path += "&force-plain-text=Y&body="_ns + escapedBody;
+
+ rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
+ } else {
+ // Create data stream.
+ // We use eValueEncode to send the data through the charset encoder and to
+ // normalize linebreaks to use the "standard net" format (\r\n), but not
+ // perform any other escaping. This means that names and values which
+ // contain '=' or newlines are potentially ambiguously encoded, but that is
+ // how text/plain is specced.
+ nsCString cbody;
+ EncodeVal(mBody, cbody, EncodeType::eValueEncode);
+
+ nsCOMPtr<nsIInputStream> bodyStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), std::move(cbody));
+ if (!bodyStream) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Create mime stream with headers and such
+ nsCOMPtr<nsIMIMEInputStream> mimeStream =
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mimeStream->AddHeader("Content-Type", "text/plain");
+ mimeStream->SetData(bodyStream);
+ mimeStream.forget(aPostDataStream);
+ }
+
+ return rv;
+}
+
+} // anonymous namespace
+
+// --------------------------------------------------------------------------
+
+HTMLFormSubmission::HTMLFormSubmission(
+ nsIURI* aActionURL, const nsAString& aTarget,
+ mozilla::NotNull<const mozilla::Encoding*> aEncoding)
+ : mActionURL(aActionURL),
+ mTarget(aTarget),
+ mEncoding(aEncoding),
+ mInitiatedFromUserInput(UserActivation::IsHandlingUserInput()) {
+ MOZ_COUNT_CTOR(HTMLFormSubmission);
+}
+
+EncodingFormSubmission::EncodingFormSubmission(
+ nsIURI* aActionURL, const nsAString& aTarget,
+ NotNull<const Encoding*> aEncoding, Element* aSubmitter)
+ : HTMLFormSubmission(aActionURL, aTarget, aEncoding) {
+ if (!aEncoding->CanEncodeEverything()) {
+ nsAutoCString name;
+ aEncoding->Name(name);
+ AutoTArray<nsString, 1> args;
+ CopyUTF8toUTF16(name, *args.AppendElement());
+ SendJSWarning(aSubmitter ? aSubmitter->GetOwnerDocument() : nullptr,
+ "CannotEncodeAllUnicode", args);
+ }
+}
+
+EncodingFormSubmission::~EncodingFormSubmission() = default;
+
+// i18n helper routines
+nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr,
+ nsCString& aOut,
+ EncodeType aEncodeType) {
+ nsresult rv;
+ std::tie(rv, std::ignore) = mEncoding->Encode(aStr, aOut);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (aEncodeType != EncodeType::eFilenameEncode) {
+ // Normalize newlines
+ int32_t convertedBufLength = 0;
+ char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
+ aOut.get(), nsLinebreakConverter::eLinebreakAny,
+ nsLinebreakConverter::eLinebreakNet, (int32_t)aOut.Length(),
+ &convertedBufLength);
+ aOut.Adopt(convertedBuf, convertedBufLength);
+ }
+
+ if (aEncodeType != EncodeType::eValueEncode) {
+ // Percent-escape LF, CR and double quotes.
+ int32_t offset = 0;
+ while ((offset = aOut.FindCharInSet("\n\r\"", offset)) != kNotFound) {
+ if (aOut[offset] == '\n') {
+ aOut.ReplaceLiteral(offset, 1, "%0A");
+ } else if (aOut[offset] == '\r') {
+ aOut.ReplaceLiteral(offset, 1, "%0D");
+ } else if (aOut[offset] == '"') {
+ aOut.ReplaceLiteral(offset, 1, "%22");
+ } else {
+ MOZ_ASSERT(false);
+ offset++;
+ continue;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// --------------------------------------------------------------------------
+
+namespace {
+
+void GetEnumAttr(nsGenericHTMLElement* aContent, nsAtom* atom,
+ int32_t* aValue) {
+ const nsAttrValue* value = aContent->GetParsedAttr(atom);
+ if (value && value->Type() == nsAttrValue::eEnum) {
+ *aValue = value->GetEnumValue();
+ }
+}
+
+} // anonymous namespace
+
+/* static */
+nsresult HTMLFormSubmission::GetFromForm(HTMLFormElement* aForm,
+ nsGenericHTMLElement* aSubmitter,
+ NotNull<const Encoding*>& aEncoding,
+ HTMLFormSubmission** aFormSubmission) {
+ // Get all the information necessary to encode the form data
+ NS_ASSERTION(aForm->GetComposedDoc(),
+ "Should have doc if we're building submission!");
+
+ nsresult rv;
+
+ // Get method (default: GET)
+ int32_t method = NS_FORM_METHOD_GET;
+ if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formmethod)) {
+ GetEnumAttr(aSubmitter, nsGkAtoms::formmethod, &method);
+ } else {
+ GetEnumAttr(aForm, nsGkAtoms::method, &method);
+ }
+
+ if (method == NS_FORM_METHOD_DIALOG) {
+ HTMLDialogElement* dialog = aForm->FirstAncestorOfType<HTMLDialogElement>();
+
+ // If there isn't one, do nothing.
+ if (!dialog) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString result;
+ if (aSubmitter) {
+ aSubmitter->ResultForDialogSubmit(result);
+ }
+ *aFormSubmission = new DialogFormSubmission(result, aEncoding, dialog);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(method != NS_FORM_METHOD_DIALOG);
+
+ // Get action
+ nsCOMPtr<nsIURI> actionURL;
+ rv = aForm->GetActionURL(getter_AddRefs(actionURL), aSubmitter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if CSP allows this form-action
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aForm->GetCsp();
+ if (csp) {
+ bool permitsFormAction = true;
+
+ // form-action is only enforced if explicitly defined in the
+ // policy - do *not* consult default-src, see:
+ // http://www.w3.org/TR/CSP2/#directive-default-src
+ rv = csp->Permits(aForm, nullptr /* nsICSPEventListener */, actionURL,
+ nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
+ true /* aSpecific */, true /* aSendViolationReports */,
+ &permitsFormAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!permitsFormAction) {
+ return NS_ERROR_CSP_FORM_ACTION_VIOLATION;
+ }
+ }
+
+ // Get target
+ // The target is the submitter element formtarget attribute if the element
+ // is a submit control and has such an attribute.
+ // Otherwise, the target is the form owner's target attribute,
+ // if it has such an attribute.
+ // Finally, if one of the child nodes of the head element is a base element
+ // with a target attribute, then the value of the target attribute of the
+ // first such base element; or, if there is no such element, the empty string.
+ nsAutoString target;
+ if (!(aSubmitter && aSubmitter->GetAttr(nsGkAtoms::formtarget, target)) &&
+ !aForm->GetAttr(nsGkAtoms::target, target)) {
+ aForm->GetBaseTarget(target);
+ }
+
+ // Get encoding type (default: urlencoded)
+ int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
+ if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) {
+ GetEnumAttr(aSubmitter, nsGkAtoms::formenctype, &enctype);
+ } else {
+ GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
+ }
+
+ // Choose encoder
+ if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_MULTIPART) {
+ *aFormSubmission =
+ new FSMultipartFormData(actionURL, target, aEncoding, aSubmitter);
+ } else if (method == NS_FORM_METHOD_POST &&
+ enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
+ *aFormSubmission =
+ new FSTextPlain(actionURL, target, aEncoding, aSubmitter);
+ } else {
+ Document* doc = aForm->OwnerDoc();
+ if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
+ enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
+ AutoTArray<nsString, 1> args;
+ nsString& enctypeStr = *args.AppendElement();
+ if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) {
+ aSubmitter->GetAttr(nsGkAtoms::formenctype, enctypeStr);
+ } else {
+ aForm->GetAttr(nsGkAtoms::enctype, enctypeStr);
+ }
+
+ SendJSWarning(doc, "ForgotPostWarning", args);
+ }
+ *aFormSubmission =
+ new FSURLEncoded(actionURL, target, aEncoding, method, doc, aSubmitter);
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom