diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/html/HTMLFormSubmission.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/html/HTMLFormSubmission.cpp')
-rw-r--r-- | dom/html/HTMLFormSubmission.cpp | 980 |
1 files changed, 980 insertions, 0 deletions
diff --git a/dom/html/HTMLFormSubmission.cpp b/dom/html/HTMLFormSubmission.cpp new file mode 100644 index 000000000..d6269a7ca --- /dev/null +++ b/dom/html/HTMLFormSubmission.cpp @@ -0,0 +1,980 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsIForm.h" +#include "nsILinkHandler.h" +#include "nsIDocument.h" +#include "nsGkAtoms.h" +#include "nsIFormControl.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsError.h" +#include "nsGenericHTMLElement.h" +#include "nsAttrValueInlines.h" +#include "nsISaveAsCharset.h" +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsStringStream.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsLinebreakConverter.h" +#include "nsEscape.h" +#include "nsUnicharUtils.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMIMEInputStream.h" +#include "nsIMIMEService.h" +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIStringBundle.h" +#include "nsCExternalHandlerService.h" +#include "nsIFileStreams.h" +#include "nsContentUtils.h" + +#include "mozilla/dom/Directory.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/File.h" + +namespace mozilla { +namespace dom { + +namespace { + +void +SendJSWarning(nsIDocument* aDocument, + const char* aWarningName, + const char16_t** aWarningArgs, uint32_t aWarningArgsLen) +{ + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("HTML"), aDocument, + nsContentUtils::eFORMS_PROPERTIES, + aWarningName, + aWarningArgs, aWarningArgsLen); +} + +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 aCharset the charset of the form as a string + * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or + * NS_FORM_METHOD_POST). + */ + FSURLEncoded(const nsACString& aCharset, + int32_t aMethod, + nsIDocument* aDocument, + nsIContent* aOriginatingElement) + : EncodingFormSubmission(aCharset, aOriginatingElement), + mMethod(aMethod), + mDocument(aDocument), + mWarnedFileControl(false) + { + } + + virtual nsresult + AddNameValuePair(const nsAString& aName, const nsAString& aValue) override; + + virtual nsresult + AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override; + + virtual nsresult + AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override; + + virtual nsresult + GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) override; + + virtual bool SupportsIsindexSubmission() override + { + return true; + } + + virtual nsresult AddIsindex(const nsAString& aValue) 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<nsIDocument> 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_LITERAL_CSTRING("=") + convValue; + } else { + mQueryString += NS_LITERAL_CSTRING("&") + convName + + NS_LITERAL_CSTRING("=") + convValue; + } + + return NS_OK; +} + +nsresult +FSURLEncoded::AddIsindex(const nsAString& aValue) +{ + // Encode value + nsCString convValue; + nsresult rv = URLEncode(aValue, convValue); + NS_ENSURE_SUCCESS(rv, rv); + + // Append data to string + if (mQueryString.IsEmpty()) { + mQueryString.Assign(convValue); + } else { + mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue; + } + + return NS_OK; +} + +nsresult +FSURLEncoded::AddNameBlobOrNullPair(const nsAString& aName, + Blob* aBlob) +{ + if (!mWarnedFileControl) { + SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0); + 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 + nsXPIDLString brandName; + nsresult rv = + nsContentUtils::GetLocalizedString(nsContentUtils::eBRAND_PROPERTIES, + "brandShortName", brandName); + if (NS_FAILED(rv)) + return; + const char16_t *formatStrings[] = { brandName.get() }; + nsXPIDLString subjectStr; + rv = nsContentUtils::FormatLocalizedString( + nsContentUtils::eFORMS_PROPERTIES, + "DefaultFormSubject", + formatStrings, + subjectStr); + 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) +{ + nsresult rv = NS_OK; + + *aPostDataStream = nullptr; + + if (mMethod == NS_FORM_METHOD_POST) { + + bool isMailto = false; + aURI->SchemeIs("mailto", &isMailto); + if (isMailto) { + + nsAutoCString path; + rv = aURI->GetPath(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 += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody; + + rv = aURI->SetPath(path); + + } else { + + nsCOMPtr<nsIInputStream> dataStream; + // XXX We *really* need to either get the string to disown its data (and + // not destroy it), or make a string input stream that owns the CString + // that is passed to it. Right now this operation does a copy. + rv = NS_NewCStringInputStream(getter_AddRefs(dataStream), mQueryString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMIMEInputStream> mimeStream( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef SPECIFY_CHARSET_IN_CONTENT_TYPE + mimeStream->AddHeader("Content-Type", + PromiseFlatString( + "application/x-www-form-urlencoded; charset=" + + mCharset + ).get()); +#else + mimeStream->AddHeader("Content-Type", + "application/x-www-form-urlencoded"); +#endif + mimeStream->SetAddContentLength(true); + mimeStream->SetData(dataStream); + + *aPostDataStream = mimeStream; + NS_ADDREF(*aPostDataStream); + } + + } else { + // Get the full query string + bool schemeIsJavaScript; + rv = aURI->SchemeIs("javascript", &schemeIsJavaScript); + NS_ENSURE_SUCCESS(rv, rv); + if (schemeIsJavaScript) { + return NS_OK; + } + + nsCOMPtr<nsIURL> url = do_QueryInterface(aURI); + if (url) { + url->SetQuery(mQueryString); + } + else { + nsAutoCString path; + rv = aURI->GetPath(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); + + aURI->SetPath(path); + } + } + + return rv; +} + +// i18n helper routines +nsresult +FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) +{ + // convert to CRLF breaks + int32_t convertedBufLength = 0; + char16_t* convertedBuf = + nsLinebreakConverter::ConvertUnicharLineBreaks(aStr.BeginReading(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakNet, + aStr.Length(), + &convertedBufLength); + NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY); + + nsAutoString convertedString; + convertedString.Adopt(convertedBuf, convertedBufLength); + + nsAutoCString encodedBuf; + nsresult rv = EncodeVal(convertedString, encodedBuf, false); + 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(const nsACString& aCharset, + nsIContent* aOriginatingElement) + : EncodingFormSubmission(aCharset, aOriginatingElement) +{ + mPostDataStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + mTotalLength = 0; + + mBoundary.AssignLiteral("---------------------------"); + mBoundary.AppendInt(rand()); + mBoundary.AppendInt(rand()); + mBoundary.AppendInt(rand()); +} + +FSMultipartFormData::~FSMultipartFormData() +{ + NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data"); +} + +nsIInputStream* +FSMultipartFormData::GetSubmissionBody(uint64_t* aContentLength) +{ + // Finish data + mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + + NS_LITERAL_CSTRING("--" CRLF); + + // Add final data input stream + AddPostDataStream(); + + *aContentLength = mTotalLength; + return mPostDataStream; +} + +nsresult +FSMultipartFormData::AddNameValuePair(const nsAString& aName, + const nsAString& aValue) +{ + nsCString valueStr; + nsAutoCString encodedVal; + nsresult rv = EncodeVal(aValue, encodedVal, false); + NS_ENSURE_SUCCESS(rv, rv); + + valueStr.Adopt(nsLinebreakConverter:: + ConvertLineBreaks(encodedVal.get(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakNet)); + + nsAutoCString nameStr; + rv = EncodeVal(aName, nameStr, true); + NS_ENSURE_SUCCESS(rv, rv); + + // Make MIME block for name/value pair + + // XXX: name parameter should be encoded per RFC 2231 + // RFC 2388 specifies that RFC 2047 be used, but I think it's not + // consistent with MIME standard. + mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary + + NS_LITERAL_CSTRING(CRLF) + + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF) + + valueStr + NS_LITERAL_CSTRING(CRLF); + + return NS_OK; +} + +nsresult +FSMultipartFormData::AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) +{ + // Encode the control name + nsAutoCString nameStr; + nsresult rv = EncodeVal(aName, nameStr, true); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + + uint64_t size = 0; + nsAutoCString filename; + nsAutoCString contentType; + nsCOMPtr<nsIInputStream> fileStream; + + if (aBlob) { + nsAutoString filename16; + + RefPtr<File> file = aBlob->ToFile(); + if (file) { + nsAutoString relativePath; + file->GetRelativePath(relativePath); + if (Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr) && + !relativePath.IsEmpty()) { + filename16 = relativePath; + } + + if (filename16.IsEmpty()) { + RetrieveFileName(aBlob, filename16); + } + } + + rv = EncodeVal(filename16, filename, true); + NS_ENSURE_SUCCESS(rv, rv); + + // Get content type + nsAutoString contentType16; + aBlob->GetType(contentType16); + if (contentType16.IsEmpty()) { + contentType16.AssignLiteral("application/octet-stream"); + } + + contentType.Adopt(nsLinebreakConverter:: + ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakSpace)); + + // Get input stream + aBlob->GetInternalStream(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, 8192); + NS_ENSURE_SUCCESS(rv, rv); + + fileStream = bufferedStream; + } + } else { + contentType.AssignLiteral("application/octet-stream"); + } + + AddDataChunk(nameStr, filename, contentType, fileStream, size); + return NS_OK; +} + +nsresult +FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName, + Directory* aDirectory) +{ + if (!Directory::WebkitBlinkDirectoryPickerEnabled(nullptr, nullptr)) { + return NS_OK; + } + + // Encode the control name + nsAutoCString nameStr; + nsresult rv = EncodeVal(aName, nameStr, true); + 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, true); + NS_ENSURE_SUCCESS(rv, rv); + + AddDataChunk(nameStr, dirname, + NS_LITERAL_CSTRING("application/octet-stream"), + 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_LITERAL_CSTRING("--") + mBoundary + + NS_LITERAL_CSTRING(CRLF); + // XXX: name/filename parameter should be encoded per RFC 2231 + // RFC 2388 specifies that RFC 2047 be used, but I think it's not + // consistent with the MIME standard. + mPostDataChunk += + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"") + + aName + NS_LITERAL_CSTRING("\"; filename=\"") + + aFilename + NS_LITERAL_CSTRING("\"" CRLF) + + NS_LITERAL_CSTRING("Content-Type: ") + + aContentType + NS_LITERAL_CSTRING(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(); + + mPostDataStream->AppendStream(aInputStream); + mTotalLength += aInputStreamSize; + } + + // CRLF after file + mPostDataChunk.AppendLiteral(CRLF); +} + +nsresult +FSMultipartFormData::GetEncodedSubmission(nsIURI* aURI, + nsIInputStream** aPostDataStream) +{ + nsresult rv; + + // 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()); + mimeStream->SetAddContentLength(true); + uint64_t unused; + mimeStream->SetData(GetSubmissionBody(&unused)); + + 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) { + mPostDataStream->AppendStream(postDataChunkStream); + mTotalLength += mPostDataChunk.Length(); + } + + mPostDataChunk.Truncate(); + + return rv; +} + +// -------------------------------------------------------------------------- + +namespace { + +class FSTextPlain : public EncodingFormSubmission +{ +public: + FSTextPlain(const nsACString& aCharset, nsIContent* aOriginatingElement) + : EncodingFormSubmission(aCharset, aOriginatingElement) + { + } + + virtual nsresult + AddNameValuePair(const nsAString& aName, const nsAString& aValue) override; + + virtual nsresult + AddNameBlobOrNullPair(const nsAString& aName, Blob* aBlob) override; + + virtual nsresult + AddNameDirectoryPair(const nsAString& aName, Directory* aDirectory) override; + + virtual nsresult + GetEncodedSubmission(nsIURI* aURI, nsIInputStream** aPostDataStream) 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 + NS_LITERAL_STRING("=") + aValue + + NS_LITERAL_STRING(CRLF)); + + return NS_OK; +} + +nsresult +FSTextPlain::AddNameBlobOrNullPair(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) +{ + nsresult rv = NS_OK; + + // 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. + bool isMailto = false; + aURI->SchemeIs("mailto", &isMailto); + if (isMailto) { + nsAutoCString path; + rv = aURI->GetPath(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 += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody; + + rv = aURI->SetPath(path); + + } else { + // Create data stream. + // We do want to send the data through the charset encoder and we want to + // normalize linebreaks to use the "standard net" format (\r\n), but we + // don't want to perform any other encoding. This means that names and + // values which contains '=' or newlines are potentially ambigiously + // encoded, but that how text/plain is specced. + nsCString cbody; + EncodeVal(mBody, cbody, false); + cbody.Adopt(nsLinebreakConverter:: + ConvertLineBreaks(cbody.get(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakNet)); + nsCOMPtr<nsIInputStream> bodyStream; + rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), 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->SetAddContentLength(true); + mimeStream->SetData(bodyStream); + CallQueryInterface(mimeStream, aPostDataStream); + } + + return rv; +} + +} // anonymous namespace + +// -------------------------------------------------------------------------- + +EncodingFormSubmission::EncodingFormSubmission(const nsACString& aCharset, + nsIContent* aOriginatingElement) + : HTMLFormSubmission(aCharset, aOriginatingElement) + , mEncoder(aCharset) +{ + if (!(aCharset.EqualsLiteral("UTF-8") || aCharset.EqualsLiteral("gb18030"))) { + NS_ConvertUTF8toUTF16 charsetUtf16(aCharset); + const char16_t* charsetPtr = charsetUtf16.get(); + SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument() + : nullptr, + "CannotEncodeAllUnicode", + &charsetPtr, + 1); + } +} + +EncodingFormSubmission::~EncodingFormSubmission() +{ +} + +// i18n helper routines +nsresult +EncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut, + bool aHeaderEncode) +{ + if (!mEncoder.Encode(aStr, aOut)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (aHeaderEncode) { + aOut.Adopt(nsLinebreakConverter:: + ConvertLineBreaks(aOut.get(), + nsLinebreakConverter::eLinebreakAny, + nsLinebreakConverter::eLinebreakSpace)); + aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""), + NS_LITERAL_CSTRING("\\\"")); + } + + + return NS_OK; +} + +// -------------------------------------------------------------------------- + +namespace { + +void +GetSubmitCharset(nsGenericHTMLElement* aForm, + nsACString& oCharset) +{ + oCharset.AssignLiteral("UTF-8"); // default to utf-8 + + nsAutoString acceptCharsetValue; + aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset, + acceptCharsetValue); + + int32_t charsetLen = acceptCharsetValue.Length(); + if (charsetLen > 0) { + int32_t offset=0; + int32_t spPos=0; + // get charset from charsets one by one + do { + spPos = acceptCharsetValue.FindChar(char16_t(' '), offset); + int32_t cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset)); + if (cnt > 0) { + nsAutoString uCharset; + acceptCharsetValue.Mid(uCharset, offset, cnt); + + if (EncodingUtils::FindEncodingForLabelNoReplacement(uCharset, oCharset)) + return; + } + offset = spPos + 1; + } while (spPos != -1); + } + // if there are no accept-charset or all the charset are not supported + // Get the charset from document + nsIDocument* doc = aForm->GetComposedDoc(); + if (doc) { + oCharset = doc->GetDocumentCharacterSet(); + } +} + +void +GetEnumAttr(nsGenericHTMLElement* aContent, + nsIAtom* 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(nsGenericHTMLElement* aForm, + nsGenericHTMLElement* aOriginatingElement, + HTMLFormSubmission** aFormSubmission) +{ + // Get all the information necessary to encode the form data + NS_ASSERTION(aForm->GetComposedDoc(), + "Should have doc if we're building submission!"); + + // Get encoding type (default: urlencoded) + int32_t enctype = NS_FORM_ENCTYPE_URLENCODED; + if (aOriginatingElement && + aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) { + GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype); + } else { + GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype); + } + + // Get method (default: GET) + int32_t method = NS_FORM_METHOD_GET; + if (aOriginatingElement && + aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formmethod)) { + GetEnumAttr(aOriginatingElement, nsGkAtoms::formmethod, &method); + } else { + GetEnumAttr(aForm, nsGkAtoms::method, &method); + } + + // Get charset + nsAutoCString charset; + GetSubmitCharset(aForm, charset); + + // We now have a canonical charset name, so we only have to check it + // against canonical names. + + // use UTF-8 for UTF-16* (per WHATWG and existing practice of + // MS IE/Opera). + if (StringBeginsWith(charset, NS_LITERAL_CSTRING("UTF-16"))) { + charset.AssignLiteral("UTF-8"); + } + + // Choose encoder + if (method == NS_FORM_METHOD_POST && + enctype == NS_FORM_ENCTYPE_MULTIPART) { + *aFormSubmission = new FSMultipartFormData(charset, aOriginatingElement); + } else if (method == NS_FORM_METHOD_POST && + enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { + *aFormSubmission = new FSTextPlain(charset, aOriginatingElement); + } else { + nsIDocument* doc = aForm->OwnerDoc(); + if (enctype == NS_FORM_ENCTYPE_MULTIPART || + enctype == NS_FORM_ENCTYPE_TEXTPLAIN) { + nsAutoString enctypeStr; + if (aOriginatingElement && + aOriginatingElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::formenctype)) { + aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype, + enctypeStr); + } else { + aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr); + } + const char16_t* enctypeStrPtr = enctypeStr.get(); + SendJSWarning(doc, "ForgotPostWarning", + &enctypeStrPtr, 1); + } + *aFormSubmission = new FSURLEncoded(charset, method, doc, + aOriginatingElement); + } + + return NS_OK; +} + +} // dom namespace +} // mozilla namespace |