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