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