forked from mirrors/gecko-dev
		
	 ca81ae3abb
			
		
	
	
		ca81ae3abb
		
	
	
	
	
		
			
			This commit also changes the way escapes work in multipart/form-data names and filenames. Differential Revision: https://phabricator.services.mozilla.com/D108000
		
			
				
	
	
		
			888 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			888 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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 "nsCOMPtr.h"
 | |
| #include "nsIForm.h"
 | |
| #include "mozilla/dom/Document.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/AncestorIterator.h"
 | |
| #include "mozilla/dom/Directory.h"
 | |
| #include "mozilla/dom/File.h"
 | |
| #include "mozilla/StaticPrefs_dom.h"
 | |
| #include "mozilla/RandomNum.h"
 | |
| 
 | |
| 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, Element* aSubmitter)
 | |
|     : mActionURL(aActionURL),
 | |
|       mTarget(aTarget),
 | |
|       mEncoding(aEncoding),
 | |
|       mSubmitter(aSubmitter),
 | |
|       mInitiatedFromUserInput(UserActivation::IsHandlingUserInput()) {
 | |
|   MOZ_COUNT_CTOR(HTMLFormSubmission);
 | |
| }
 | |
| 
 | |
| EncodingFormSubmission::EncodingFormSubmission(
 | |
|     nsIURI* aActionURL, const nsAString& aTarget,
 | |
|     NotNull<const Encoding*> aEncoding, Element* aSubmitter)
 | |
|     : HTMLFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {
 | |
|   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;
 | |
|   const Encoding* ignored;
 | |
|   Tie(rv, ignored) = 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 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,
 | |
|                       &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(kNameSpaceID_None,
 | |
|                                           nsGkAtoms::formtarget, target)) &&
 | |
|       !aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::target, target)) {
 | |
|     aForm->GetBaseTarget(target);
 | |
|   }
 | |
| 
 | |
|   // Get encoding type (default: urlencoded)
 | |
|   int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
 | |
|   if (aSubmitter &&
 | |
|       aSubmitter->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
 | |
|     GetEnumAttr(aSubmitter, nsGkAtoms::formenctype, &enctype);
 | |
|   } else {
 | |
|     GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
 | |
|   }
 | |
| 
 | |
|   // Get method (default: GET)
 | |
|   int32_t method = NS_FORM_METHOD_GET;
 | |
|   if (aSubmitter &&
 | |
|       aSubmitter->HasAttr(kNameSpaceID_None, 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, or if it does not have an open attribute, do
 | |
|     // nothing.
 | |
|     if (!dialog || !dialog->Open()) {
 | |
|       return NS_ERROR_FAILURE;
 | |
|     }
 | |
| 
 | |
|     nsAutoString result;
 | |
|     if (aSubmitter) {
 | |
|       aSubmitter->ResultForDialogSubmit(result);
 | |
|     }
 | |
|     *aFormSubmission = new DialogFormSubmission(result, actionURL, target,
 | |
|                                                 aEncoding, aSubmitter, dialog);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(method != NS_FORM_METHOD_DIALOG);
 | |
| 
 | |
|   // 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(kNameSpaceID_None, nsGkAtoms::formenctype)) {
 | |
|         aSubmitter->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype,
 | |
|                             enctypeStr);
 | |
|       } else {
 | |
|         aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr);
 | |
|       }
 | |
| 
 | |
|       SendJSWarning(doc, "ForgotPostWarning", args);
 | |
|     }
 | |
|     *aFormSubmission =
 | |
|         new FSURLEncoded(actionURL, target, aEncoding, method, doc, aSubmitter);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom
 |