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