tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLFormSubmission.cpp (28708B)


      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/. */
      6 
      7 #include "HTMLFormSubmission.h"
      8 
      9 #include <tuple>
     10 
     11 #include "HTMLFormElement.h"
     12 #include "HTMLFormSubmissionConstants.h"
     13 #include "mozilla/RandomNum.h"
     14 #include "mozilla/StaticPrefs_dom.h"
     15 #include "mozilla/dom/AncestorIterator.h"
     16 #include "mozilla/dom/Directory.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/File.h"
     19 #include "mozilla/dom/FormData.h"
     20 #include "mozilla/dom/PolicyContainer.h"
     21 #include "nsAttrValueInlines.h"
     22 #include "nsCExternalHandlerService.h"
     23 #include "nsCOMPtr.h"
     24 #include "nsComponentManagerUtils.h"
     25 #include "nsContentUtils.h"
     26 #include "nsDirectoryServiceDefs.h"
     27 #include "nsError.h"
     28 #include "nsEscape.h"
     29 #include "nsGenericHTMLElement.h"
     30 #include "nsGkAtoms.h"
     31 #include "nsIFormControl.h"
     32 #include "nsIMIMEInputStream.h"
     33 #include "nsIMultiplexInputStream.h"
     34 #include "nsIScriptError.h"
     35 #include "nsIURI.h"
     36 #include "nsIURIMutator.h"
     37 #include "nsIURL.h"
     38 #include "nsLinebreakConverter.h"
     39 #include "nsNetUtil.h"
     40 #include "nsStringStream.h"
     41 #include "nsUnicharUtils.h"
     42 
     43 namespace mozilla::dom {
     44 
     45 namespace {
     46 
     47 void SendJSWarning(Document* aDocument, const char* aWarningName,
     48                   const nsTArray<nsString>& aWarningArgs) {
     49  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "HTML"_ns,
     50                                  aDocument, nsContentUtils::eFORMS_PROPERTIES,
     51                                  aWarningName, aWarningArgs);
     52 }
     53 
     54 void RetrieveFileName(Blob* aBlob, nsAString& aFilename) {
     55  if (!aBlob) {
     56    return;
     57  }
     58 
     59  RefPtr<File> file = aBlob->ToFile();
     60  if (file) {
     61    file->GetName(aFilename);
     62  }
     63 }
     64 
     65 void RetrieveDirectoryName(Directory* aDirectory, nsAString& aDirname) {
     66  MOZ_ASSERT(aDirectory);
     67 
     68  ErrorResult rv;
     69  aDirectory->GetName(aDirname, rv);
     70  if (NS_WARN_IF(rv.Failed())) {
     71    rv.SuppressException();
     72    aDirname.Truncate();
     73  }
     74 }
     75 
     76 // --------------------------------------------------------------------------
     77 
     78 class FSURLEncoded : public EncodingFormSubmission {
     79 public:
     80  /**
     81   * @param aEncoding the character encoding of the form
     82   * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
     83   *        NS_FORM_METHOD_POST).
     84   */
     85  FSURLEncoded(nsIURI* aActionURL, const nsAString& aTarget,
     86               NotNull<const Encoding*> aEncoding, int32_t aMethod,
     87               Document* aDocument, Element* aSubmitter)
     88      : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter),
     89        mMethod(aMethod),
     90        mDocument(aDocument),
     91        mWarnedFileControl(false) {}
     92 
     93  virtual nsresult AddNameValuePair(const nsAString& aName,
     94                                    const nsAString& aValue) override;
     95 
     96  virtual nsresult AddNameBlobPair(const nsAString& aName,
     97                                   Blob* aBlob) override;
     98 
     99  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
    100                                        Directory* aDirectory) override;
    101 
    102  virtual nsresult GetEncodedSubmission(nsIURI* aURI,
    103                                        nsIInputStream** aPostDataStream,
    104                                        nsCOMPtr<nsIURI>& aOutURI) override;
    105 
    106 protected:
    107  /**
    108   * URL encode a Unicode string by encoding it to bytes, converting linebreaks
    109   * properly, and then escaping many bytes as %xx.
    110   *
    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
    114   */
    115  nsresult URLEncode(const nsAString& aStr, nsACString& aEncoded);
    116 
    117 private:
    118  /**
    119   * The method of the submit (either NS_FORM_METHOD_GET or
    120   * NS_FORM_METHOD_POST).
    121   */
    122  int32_t mMethod;
    123 
    124  /** The query string so far (the part after the ?) */
    125  nsCString mQueryString;
    126 
    127  /** The document whose URI to use when reporting errors */
    128  nsCOMPtr<Document> mDocument;
    129 
    130  /** Whether or not we have warned about a file control not being submitted */
    131  bool mWarnedFileControl;
    132 };
    133 
    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);
    140 
    141  // Encode name
    142  nsAutoCString convName;
    143  rv = URLEncode(aName, convName);
    144  NS_ENSURE_SUCCESS(rv, rv);
    145 
    146  // Append data to string
    147  if (mQueryString.IsEmpty()) {
    148    mQueryString += convName + "="_ns + convValue;
    149  } else {
    150    mQueryString += "&"_ns + convName + "="_ns + convValue;
    151  }
    152 
    153  return NS_OK;
    154 }
    155 
    156 nsresult FSURLEncoded::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
    157  if (!mWarnedFileControl) {
    158    SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nsTArray<nsString>());
    159    mWarnedFileControl = true;
    160  }
    161 
    162  nsAutoString filename;
    163  RetrieveFileName(aBlob, filename);
    164  return AddNameValuePair(aName, filename);
    165 }
    166 
    167 nsresult FSURLEncoded::AddNameDirectoryPair(const nsAString& aName,
    168                                            Directory* aDirectory) {
    169  // No warning about because Directory objects are never sent via form.
    170 
    171  nsAutoString dirname;
    172  RetrieveDirectoryName(aDirectory, dirname);
    173  return AddNameValuePair(aName, dirname);
    174 }
    175 
    176 void HandleMailtoSubject(nsCString& aPath) {
    177  // Walk through the string and see if we have a subject already.
    178  bool hasSubject = false;
    179  bool hasParams = false;
    180  int32_t paramSep = aPath.FindChar('?');
    181  while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
    182    hasParams = true;
    183 
    184    // Get the end of the name at the = op.  If it is *after* the next &,
    185    // assume that someone made a parameter without an = in it
    186    int32_t nameEnd = aPath.FindChar('=', paramSep + 1);
    187    int32_t nextParamSep = aPath.FindChar('&', paramSep + 1);
    188    if (nextParamSep == kNotFound) {
    189      nextParamSep = aPath.Length();
    190    }
    191 
    192    // If the = op is after the &, this parameter is a name without value.
    193    // If there is no = op, same thing.
    194    if (nameEnd == kNotFound || nextParamSep < nameEnd) {
    195      nameEnd = nextParamSep;
    196    }
    197 
    198    if (nameEnd != kNotFound) {
    199      if (Substring(aPath, paramSep + 1, nameEnd - (paramSep + 1))
    200              .LowerCaseEqualsLiteral("subject")) {
    201        hasSubject = true;
    202        break;
    203      }
    204    }
    205 
    206    paramSep = nextParamSep;
    207  }
    208 
    209  // If there is no subject, append a preformed subject to the mailto line
    210  if (!hasSubject) {
    211    if (hasParams) {
    212      aPath.Append('&');
    213    } else {
    214      aPath.Append('?');
    215    }
    216 
    217    // Get the default subject
    218    nsAutoString brandName;
    219    nsresult rv = nsContentUtils::GetLocalizedString(
    220        nsContentUtils::eBRAND_PROPERTIES, "brandShortName", brandName);
    221    if (NS_FAILED(rv)) return;
    222    nsAutoString subjectStr;
    223    rv = nsContentUtils::FormatLocalizedString(
    224        subjectStr, nsContentUtils::eFORMS_PROPERTIES, "DefaultFormSubject",
    225        brandName);
    226    if (NS_FAILED(rv)) return;
    227    aPath.AppendLiteral("subject=");
    228    nsCString subjectStrEscaped;
    229    rv = NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
    230                      subjectStrEscaped, mozilla::fallible);
    231    if (NS_FAILED(rv)) return;
    232 
    233    aPath.Append(subjectStrEscaped);
    234  }
    235 }
    236 
    237 nsresult FSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
    238                                            nsIInputStream** aPostDataStream,
    239                                            nsCOMPtr<nsIURI>& aOutURI) {
    240  nsresult rv = NS_OK;
    241  aOutURI = aURI;
    242 
    243  *aPostDataStream = nullptr;
    244 
    245  if (mMethod == NS_FORM_METHOD_POST) {
    246    if (aURI->SchemeIs("mailto")) {
    247      nsAutoCString path;
    248      rv = aURI->GetPathQueryRef(path);
    249      NS_ENSURE_SUCCESS(rv, rv);
    250 
    251      HandleMailtoSubject(path);
    252 
    253      // Append the body to and force-plain-text args to the mailto line
    254      nsAutoCString escapedBody;
    255      if (NS_WARN_IF(!NS_Escape(mQueryString, escapedBody, url_XAlphas))) {
    256        return NS_ERROR_OUT_OF_MEMORY;
    257      }
    258 
    259      path += "&force-plain-text=Y&body="_ns + escapedBody;
    260 
    261      return NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
    262    } else {
    263      nsCOMPtr<nsIInputStream> dataStream;
    264      rv = NS_NewCStringInputStream(getter_AddRefs(dataStream),
    265                                    std::move(mQueryString));
    266      NS_ENSURE_SUCCESS(rv, rv);
    267      mQueryString.Truncate();
    268 
    269      nsCOMPtr<nsIMIMEInputStream> mimeStream(
    270          do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
    271      NS_ENSURE_SUCCESS(rv, rv);
    272 
    273      mimeStream->AddHeader("Content-Type",
    274                            "application/x-www-form-urlencoded");
    275      mimeStream->SetData(dataStream);
    276 
    277      mimeStream.forget(aPostDataStream);
    278    }
    279 
    280  } else {
    281    // Get the full query string
    282    if (aURI->SchemeIs("javascript")) {
    283      return NS_OK;
    284    }
    285 
    286    nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
    287    if (url) {
    288      // Make sure that we end up with a query component in the URL.  If
    289      // mQueryString is empty, nsIURI::SetQuery() will remove the query
    290      // component, which is not what we want.
    291      rv = NS_MutateURI(aURI)
    292               .SetQuery(mQueryString.IsEmpty() ? "?"_ns : mQueryString)
    293               .Finalize(aOutURI);
    294    } else {
    295      nsAutoCString path;
    296      rv = aURI->GetPathQueryRef(path);
    297      NS_ENSURE_SUCCESS(rv, rv);
    298      // Bug 42616: Trim off named anchor and save it to add later
    299      int32_t namedAnchorPos = path.FindChar('#');
    300      nsAutoCString namedAnchor;
    301      if (kNotFound != namedAnchorPos) {
    302        path.Right(namedAnchor, (path.Length() - namedAnchorPos));
    303        path.Truncate(namedAnchorPos);
    304      }
    305 
    306      // Chop off old query string (bug 25330, 57333)
    307      // Only do this for GET not POST (bug 41585)
    308      int32_t queryStart = path.FindChar('?');
    309      if (kNotFound != queryStart) {
    310        path.Truncate(queryStart);
    311      }
    312 
    313      path.Append('?');
    314      // Bug 42616: Add named anchor to end after query string
    315      path.Append(mQueryString + namedAnchor);
    316 
    317      rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
    318    }
    319  }
    320 
    321  return rv;
    322 }
    323 
    324 // i18n helper routines
    325 nsresult FSURLEncoded::URLEncode(const nsAString& aStr, nsACString& aEncoded) {
    326  nsAutoCString encodedBuf;
    327  // We encode with eValueEncode because the urlencoded format needs the newline
    328  // normalizations but percent-escapes characters that eNameEncode doesn't,
    329  // so calling NS_Escape would still be needed.
    330  nsresult rv = EncodeVal(aStr, encodedBuf, EncodeType::eValueEncode);
    331  NS_ENSURE_SUCCESS(rv, rv);
    332 
    333  if (NS_WARN_IF(!NS_Escape(encodedBuf, aEncoded, url_XPAlphas))) {
    334    return NS_ERROR_OUT_OF_MEMORY;
    335  }
    336 
    337  return NS_OK;
    338 }
    339 
    340 }  // anonymous namespace
    341 
    342 // --------------------------------------------------------------------------
    343 
    344 FSMultipartFormData::FSMultipartFormData(nsIURI* aActionURL,
    345                                         const nsAString& aTarget,
    346                                         NotNull<const Encoding*> aEncoding,
    347                                         Element* aSubmitter)
    348    : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {
    349  mPostData = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
    350 
    351  nsCOMPtr<nsIInputStream> inputStream = do_QueryInterface(mPostData);
    352  MOZ_ASSERT(SameCOMIdentity(mPostData, inputStream));
    353  mPostDataStream = inputStream;
    354 
    355  mTotalLength = 0;
    356 
    357  mBoundary.AssignLiteral("----geckoformboundary");
    358  mBoundary.AppendInt(mozilla::RandomUint64OrDie(), 16);
    359  mBoundary.AppendInt(mozilla::RandomUint64OrDie(), 16);
    360 }
    361 
    362 FSMultipartFormData::~FSMultipartFormData() {
    363  NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
    364 }
    365 
    366 nsIInputStream* FSMultipartFormData::GetSubmissionBody(
    367    uint64_t* aContentLength) {
    368  // Finish data
    369  mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString("--" CRLF);
    370 
    371  // Add final data input stream
    372  AddPostDataStream();
    373 
    374  *aContentLength = mTotalLength;
    375  return mPostDataStream;
    376 }
    377 
    378 nsresult FSMultipartFormData::AddNameValuePair(const nsAString& aName,
    379                                               const nsAString& aValue) {
    380  nsAutoCString encodedVal;
    381  nsresult rv = EncodeVal(aValue, encodedVal, EncodeType::eValueEncode);
    382  NS_ENSURE_SUCCESS(rv, rv);
    383 
    384  nsAutoCString nameStr;
    385  rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
    386  NS_ENSURE_SUCCESS(rv, rv);
    387 
    388  // Make MIME block for name/value pair
    389 
    390  mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF) +
    391                    "Content-Disposition: form-data; name=\""_ns + nameStr +
    392                    nsLiteralCString("\"" CRLF CRLF) + encodedVal +
    393                    nsLiteralCString(CRLF);
    394 
    395  return NS_OK;
    396 }
    397 
    398 nsresult FSMultipartFormData::AddNameBlobPair(const nsAString& aName,
    399                                              Blob* aBlob) {
    400  MOZ_ASSERT(aBlob);
    401 
    402  // Encode the control name
    403  nsAutoCString nameStr;
    404  nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
    405  NS_ENSURE_SUCCESS(rv, rv);
    406 
    407  ErrorResult error;
    408 
    409  uint64_t size = 0;
    410  nsAutoCString filename;
    411  nsAutoCString contentType;
    412  nsCOMPtr<nsIInputStream> fileStream;
    413  nsAutoString filename16;
    414 
    415  RefPtr<File> file = aBlob->ToFile();
    416  if (file) {
    417    nsAutoString relativePath;
    418    file->GetRelativePath(relativePath);
    419    if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
    420        !relativePath.IsEmpty()) {
    421      filename16 = relativePath;
    422    }
    423 
    424    if (filename16.IsEmpty()) {
    425      RetrieveFileName(aBlob, filename16);
    426    }
    427  }
    428 
    429  rv = EncodeVal(filename16, filename, EncodeType::eFilenameEncode);
    430  NS_ENSURE_SUCCESS(rv, rv);
    431 
    432  // Get content type
    433  nsAutoString contentType16;
    434  aBlob->GetType(contentType16);
    435  if (contentType16.IsEmpty()) {
    436    contentType16.AssignLiteral("application/octet-stream");
    437  }
    438 
    439  NS_ConvertUTF16toUTF8 contentType8(contentType16);
    440  int32_t convertedBufLength = 0;
    441  char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
    442      contentType8.get(), nsLinebreakConverter::eLinebreakAny,
    443      nsLinebreakConverter::eLinebreakSpace, contentType8.Length(),
    444      &convertedBufLength);
    445  contentType.Adopt(convertedBuf, convertedBufLength);
    446 
    447  // Get input stream
    448  aBlob->CreateInputStream(getter_AddRefs(fileStream), error);
    449  if (NS_WARN_IF(error.Failed())) {
    450    return error.StealNSResult();
    451  }
    452 
    453  // Get size
    454  size = aBlob->GetSize(error);
    455  if (error.Failed()) {
    456    error.SuppressException();
    457    fileStream = nullptr;
    458  }
    459 
    460  if (fileStream) {
    461    // Create buffered stream (for efficiency)
    462    nsCOMPtr<nsIInputStream> bufferedStream;
    463    rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
    464                                   fileStream.forget(), 8192);
    465    NS_ENSURE_SUCCESS(rv, rv);
    466 
    467    fileStream = bufferedStream;
    468  }
    469 
    470  AddDataChunk(nameStr, filename, contentType, fileStream, size);
    471  return NS_OK;
    472 }
    473 
    474 nsresult FSMultipartFormData::AddNameDirectoryPair(const nsAString& aName,
    475                                                   Directory* aDirectory) {
    476  if (!StaticPrefs::dom_webkitBlink_dirPicker_enabled()) {
    477    return NS_OK;
    478  }
    479 
    480  // Encode the control name
    481  nsAutoCString nameStr;
    482  nsresult rv = EncodeVal(aName, nameStr, EncodeType::eNameEncode);
    483  NS_ENSURE_SUCCESS(rv, rv);
    484 
    485  nsAutoCString dirname;
    486  nsAutoString dirname16;
    487 
    488  ErrorResult error;
    489  nsAutoString path;
    490  aDirectory->GetPath(path, error);
    491  if (NS_WARN_IF(error.Failed())) {
    492    error.SuppressException();
    493  } else {
    494    dirname16 = path;
    495  }
    496 
    497  if (dirname16.IsEmpty()) {
    498    RetrieveDirectoryName(aDirectory, dirname16);
    499  }
    500 
    501  rv = EncodeVal(dirname16, dirname, EncodeType::eFilenameEncode);
    502  NS_ENSURE_SUCCESS(rv, rv);
    503 
    504  AddDataChunk(nameStr, dirname, "application/octet-stream"_ns, nullptr, 0);
    505  return NS_OK;
    506 }
    507 
    508 void FSMultipartFormData::AddDataChunk(const nsACString& aName,
    509                                       const nsACString& aFilename,
    510                                       const nsACString& aContentType,
    511                                       nsIInputStream* aInputStream,
    512                                       uint64_t aInputStreamSize) {
    513  //
    514  // Make MIME block for name/value pair
    515  //
    516  // more appropriate than always using binary?
    517  mPostDataChunk += "--"_ns + mBoundary + nsLiteralCString(CRLF);
    518  mPostDataChunk += "Content-Disposition: form-data; name=\""_ns + aName +
    519                    "\"; filename=\""_ns + aFilename +
    520                    nsLiteralCString("\"" CRLF) + "Content-Type: "_ns +
    521                    aContentType + nsLiteralCString(CRLF CRLF);
    522 
    523  // We should not try to append an invalid stream. That will happen for example
    524  // if we try to update a file that actually do not exist.
    525  if (aInputStream) {
    526    // We need to dump the data up to this point into the POST data stream
    527    // here, since we're about to add the file input stream
    528    AddPostDataStream();
    529 
    530    mPostData->AppendStream(aInputStream);
    531    mTotalLength += aInputStreamSize;
    532  }
    533 
    534  // CRLF after file
    535  mPostDataChunk.AppendLiteral(CRLF);
    536 }
    537 
    538 nsresult FSMultipartFormData::GetEncodedSubmission(
    539    nsIURI* aURI, nsIInputStream** aPostDataStream, nsCOMPtr<nsIURI>& aOutURI) {
    540  nsresult rv;
    541  aOutURI = aURI;
    542 
    543  // Make header
    544  nsCOMPtr<nsIMIMEInputStream> mimeStream =
    545      do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
    546  NS_ENSURE_SUCCESS(rv, rv);
    547 
    548  nsAutoCString contentType;
    549  GetContentType(contentType);
    550  mimeStream->AddHeader("Content-Type", contentType.get());
    551 
    552  uint64_t bodySize;
    553  mimeStream->SetData(GetSubmissionBody(&bodySize));
    554 
    555  mimeStream.forget(aPostDataStream);
    556 
    557  return NS_OK;
    558 }
    559 
    560 nsresult FSMultipartFormData::AddPostDataStream() {
    561  nsresult rv = NS_OK;
    562 
    563  nsCOMPtr<nsIInputStream> postDataChunkStream;
    564  rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
    565                                mPostDataChunk);
    566  NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
    567  if (postDataChunkStream) {
    568    mPostData->AppendStream(postDataChunkStream);
    569    mTotalLength += mPostDataChunk.Length();
    570  }
    571 
    572  mPostDataChunk.Truncate();
    573 
    574  return rv;
    575 }
    576 
    577 // --------------------------------------------------------------------------
    578 
    579 namespace {
    580 
    581 class FSTextPlain : public EncodingFormSubmission {
    582 public:
    583  FSTextPlain(nsIURI* aActionURL, const nsAString& aTarget,
    584              NotNull<const Encoding*> aEncoding, Element* aSubmitter)
    585      : EncodingFormSubmission(aActionURL, aTarget, aEncoding, aSubmitter) {}
    586 
    587  virtual nsresult AddNameValuePair(const nsAString& aName,
    588                                    const nsAString& aValue) override;
    589 
    590  virtual nsresult AddNameBlobPair(const nsAString& aName,
    591                                   Blob* aBlob) override;
    592 
    593  virtual nsresult AddNameDirectoryPair(const nsAString& aName,
    594                                        Directory* aDirectory) override;
    595 
    596  virtual nsresult GetEncodedSubmission(nsIURI* aURI,
    597                                        nsIInputStream** aPostDataStream,
    598                                        nsCOMPtr<nsIURI>& aOutURI) override;
    599 
    600 private:
    601  nsString mBody;
    602 };
    603 
    604 nsresult FSTextPlain::AddNameValuePair(const nsAString& aName,
    605                                       const nsAString& aValue) {
    606  // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose
    607  // text/plain doesn't care about that.  Parsers aren't built for escaped
    608  // values so we'll have to live with it.
    609  mBody.Append(aName + u"="_ns + aValue + NS_LITERAL_STRING_FROM_CSTRING(CRLF));
    610 
    611  return NS_OK;
    612 }
    613 
    614 nsresult FSTextPlain::AddNameBlobPair(const nsAString& aName, Blob* aBlob) {
    615  nsAutoString filename;
    616  RetrieveFileName(aBlob, filename);
    617  AddNameValuePair(aName, filename);
    618  return NS_OK;
    619 }
    620 
    621 nsresult FSTextPlain::AddNameDirectoryPair(const nsAString& aName,
    622                                           Directory* aDirectory) {
    623  nsAutoString dirname;
    624  RetrieveDirectoryName(aDirectory, dirname);
    625  AddNameValuePair(aName, dirname);
    626  return NS_OK;
    627 }
    628 
    629 nsresult FSTextPlain::GetEncodedSubmission(nsIURI* aURI,
    630                                           nsIInputStream** aPostDataStream,
    631                                           nsCOMPtr<nsIURI>& aOutURI) {
    632  nsresult rv = NS_OK;
    633  aOutURI = aURI;
    634 
    635  *aPostDataStream = nullptr;
    636 
    637  // XXX HACK We are using the standard URL mechanism to give the body to the
    638  // mailer instead of passing the post data stream to it, since that sounds
    639  // hard.
    640  if (aURI->SchemeIs("mailto")) {
    641    nsAutoCString path;
    642    rv = aURI->GetPathQueryRef(path);
    643    NS_ENSURE_SUCCESS(rv, rv);
    644 
    645    HandleMailtoSubject(path);
    646 
    647    // Append the body to and force-plain-text args to the mailto line
    648    nsAutoCString escapedBody;
    649    if (NS_WARN_IF(!NS_Escape(NS_ConvertUTF16toUTF8(mBody), escapedBody,
    650                              url_XAlphas))) {
    651      return NS_ERROR_OUT_OF_MEMORY;
    652    }
    653 
    654    path += "&force-plain-text=Y&body="_ns + escapedBody;
    655 
    656    rv = NS_MutateURI(aURI).SetPathQueryRef(path).Finalize(aOutURI);
    657  } else {
    658    // Create data stream.
    659    // We use eValueEncode to send the data through the charset encoder and to
    660    // normalize linebreaks to use the "standard net" format (\r\n), but not
    661    // perform any other escaping. This means that names and values which
    662    // contain '=' or newlines are potentially ambiguously encoded, but that is
    663    // how text/plain is specced.
    664    nsCString cbody;
    665    EncodeVal(mBody, cbody, EncodeType::eValueEncode);
    666 
    667    nsCOMPtr<nsIInputStream> bodyStream;
    668    rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), std::move(cbody));
    669    if (!bodyStream) {
    670      return NS_ERROR_OUT_OF_MEMORY;
    671    }
    672 
    673    // Create mime stream with headers and such
    674    nsCOMPtr<nsIMIMEInputStream> mimeStream =
    675        do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
    676    NS_ENSURE_SUCCESS(rv, rv);
    677 
    678    mimeStream->AddHeader("Content-Type", "text/plain");
    679    mimeStream->SetData(bodyStream);
    680    mimeStream.forget(aPostDataStream);
    681  }
    682 
    683  return rv;
    684 }
    685 
    686 }  // anonymous namespace
    687 
    688 // --------------------------------------------------------------------------
    689 
    690 HTMLFormSubmission::HTMLFormSubmission(
    691    nsIURI* aActionURL, const nsAString& aTarget,
    692    mozilla::NotNull<const mozilla::Encoding*> aEncoding)
    693    : mActionURL(aActionURL),
    694      mTarget(aTarget),
    695      mEncoding(aEncoding),
    696      mInitiatedFromUserInput(UserActivation::IsHandlingUserInput()) {
    697  MOZ_COUNT_CTOR(HTMLFormSubmission);
    698 }
    699 
    700 EncodingFormSubmission::EncodingFormSubmission(
    701    nsIURI* aActionURL, const nsAString& aTarget,
    702    NotNull<const Encoding*> aEncoding, Element* aSubmitter)
    703    : HTMLFormSubmission(aActionURL, aTarget, aEncoding) {
    704  if (!aEncoding->CanEncodeEverything()) {
    705    nsAutoCString name;
    706    aEncoding->Name(name);
    707    AutoTArray<nsString, 1> args;
    708    CopyUTF8toUTF16(name, *args.AppendElement());
    709    SendJSWarning(aSubmitter ? aSubmitter->GetOwnerDocument() : nullptr,
    710                  "CannotEncodeAllUnicode", args);
    711  }
    712 }
    713 
    714 EncodingFormSubmission::~EncodingFormSubmission() = default;
    715 
    716 // i18n helper routines
    717 nsresult EncodingFormSubmission::EncodeVal(const nsAString& aStr,
    718                                           nsCString& aOut,
    719                                           EncodeType aEncodeType) {
    720  nsresult rv;
    721  std::tie(rv, std::ignore) = mEncoding->Encode(aStr, aOut);
    722  if (NS_FAILED(rv)) {
    723    return rv;
    724  }
    725 
    726  if (aEncodeType != EncodeType::eFilenameEncode) {
    727    // Normalize newlines
    728    int32_t convertedBufLength = 0;
    729    char* convertedBuf = nsLinebreakConverter::ConvertLineBreaks(
    730        aOut.get(), nsLinebreakConverter::eLinebreakAny,
    731        nsLinebreakConverter::eLinebreakNet, (int32_t)aOut.Length(),
    732        &convertedBufLength);
    733    aOut.Adopt(convertedBuf, convertedBufLength);
    734  }
    735 
    736  if (aEncodeType != EncodeType::eValueEncode) {
    737    // Percent-escape LF, CR and double quotes.
    738    int32_t offset = 0;
    739    while ((offset = aOut.FindCharInSet("\n\r\"", offset)) != kNotFound) {
    740      if (aOut[offset] == '\n') {
    741        aOut.ReplaceLiteral(offset, 1, "%0A");
    742      } else if (aOut[offset] == '\r') {
    743        aOut.ReplaceLiteral(offset, 1, "%0D");
    744      } else if (aOut[offset] == '"') {
    745        aOut.ReplaceLiteral(offset, 1, "%22");
    746      } else {
    747        MOZ_ASSERT(false);
    748        offset++;
    749        continue;
    750      }
    751    }
    752  }
    753 
    754  return NS_OK;
    755 }
    756 
    757 // --------------------------------------------------------------------------
    758 
    759 namespace {
    760 
    761 void GetEnumAttr(nsGenericHTMLElement* aContent, nsAtom* atom,
    762                 int32_t* aValue) {
    763  const nsAttrValue* value = aContent->GetParsedAttr(atom);
    764  if (value && value->Type() == nsAttrValue::eEnum) {
    765    *aValue = value->GetEnumValue();
    766  }
    767 }
    768 
    769 }  // anonymous namespace
    770 
    771 /* static */
    772 nsresult HTMLFormSubmission::GetFromForm(HTMLFormElement* aForm,
    773                                         nsGenericHTMLElement* aSubmitter,
    774                                         NotNull<const Encoding*>& aEncoding,
    775                                         FormData* aFormData,
    776                                         HTMLFormSubmission** aFormSubmission) {
    777  // Get all the information necessary to encode the form data
    778  NS_ASSERTION(aForm->GetComposedDoc(),
    779               "Should have doc if we're building submission!");
    780 
    781  nsresult rv;
    782 
    783  // Get method (default: GET)
    784  int32_t method = NS_FORM_METHOD_GET;
    785  if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formmethod)) {
    786    GetEnumAttr(aSubmitter, nsGkAtoms::formmethod, &method);
    787  } else {
    788    GetEnumAttr(aForm, nsGkAtoms::method, &method);
    789  }
    790 
    791  if (method == NS_FORM_METHOD_DIALOG) {
    792    HTMLDialogElement* dialog = aForm->FirstAncestorOfType<HTMLDialogElement>();
    793 
    794    // If there isn't one, do nothing.
    795    if (!dialog) {
    796      return NS_ERROR_FAILURE;
    797    }
    798 
    799    nsAutoString result;
    800    if (aSubmitter) {
    801      aSubmitter->ResultForDialogSubmit(result);
    802    }
    803    *aFormSubmission = new DialogFormSubmission(result, aEncoding, dialog);
    804    return NS_OK;
    805  }
    806 
    807  MOZ_ASSERT(method != NS_FORM_METHOD_DIALOG);
    808 
    809  // Get action
    810  nsCOMPtr<nsIURI> actionURL;
    811  rv = aForm->GetActionURL(getter_AddRefs(actionURL), aSubmitter);
    812  NS_ENSURE_SUCCESS(rv, rv);
    813 
    814  // Check if CSP allows this form-action
    815  nsCOMPtr<nsIContentSecurityPolicy> csp =
    816      PolicyContainer::GetCSP(aForm->GetPolicyContainer());
    817  if (csp) {
    818    bool permitsFormAction = true;
    819 
    820    // form-action is only enforced if explicitly defined in the
    821    // policy - do *not* consult default-src, see:
    822    // http://www.w3.org/TR/CSP2/#directive-default-src
    823    rv = csp->Permits(aForm, nullptr /* nsICSPEventListener */, actionURL,
    824                      nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
    825                      true /* aSpecific */, true /* aSendViolationReports */,
    826                      &permitsFormAction);
    827    NS_ENSURE_SUCCESS(rv, rv);
    828    if (!permitsFormAction) {
    829      return NS_ERROR_CSP_FORM_ACTION_VIOLATION;
    830    }
    831  }
    832 
    833  // Get target
    834  nsAutoString target;
    835  aForm->GetSubmissionTarget(aSubmitter, target);
    836 
    837  // Get encoding type (default: urlencoded)
    838  int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
    839  if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) {
    840    GetEnumAttr(aSubmitter, nsGkAtoms::formenctype, &enctype);
    841  } else {
    842    GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
    843  }
    844 
    845  // Choose encoder
    846  if (method == NS_FORM_METHOD_POST && enctype == NS_FORM_ENCTYPE_MULTIPART) {
    847    *aFormSubmission =
    848        new FSMultipartFormData(actionURL, target, aEncoding, aSubmitter);
    849  } else if (method == NS_FORM_METHOD_POST &&
    850             enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
    851    *aFormSubmission =
    852        new FSTextPlain(actionURL, target, aEncoding, aSubmitter);
    853  } else {
    854    Document* doc = aForm->OwnerDoc();
    855    if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
    856        enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
    857      AutoTArray<nsString, 1> args;
    858      nsString& enctypeStr = *args.AppendElement();
    859      if (aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formenctype)) {
    860        aSubmitter->GetAttr(nsGkAtoms::formenctype, enctypeStr);
    861      } else {
    862        aForm->GetAttr(nsGkAtoms::enctype, enctypeStr);
    863      }
    864 
    865      SendJSWarning(doc, "ForgotPostWarning", args);
    866    }
    867    *aFormSubmission =
    868        new FSURLEncoded(actionURL, target, aEncoding, method, doc, aSubmitter);
    869  }
    870 
    871  // We store the FormData here to be able to set it on the load state when we
    872  // submit the submission. It's used for the #navigate algorithm in the HTML
    873  // spec and is only ever needed when the method is POST.
    874  if (method == NS_FORM_METHOD_POST) {
    875    (*aFormSubmission)->mFormData = aFormData;
    876  }
    877 
    878  return NS_OK;
    879 }
    880 
    881 }  // namespace mozilla::dom