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