Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / html / nsFormSubmission.cpp
blob24336b7fcf48c65f8dc55f3d2811a8d676eb7422
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
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/. */
7 #include "nsFormSubmission.h"
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 "nsIDOMHTMLFormElement.h"
16 #include "nsError.h"
17 #include "nsGenericHTMLElement.h"
18 #include "nsAttrValueInlines.h"
19 #include "nsISaveAsCharset.h"
20 #include "nsIFile.h"
21 #include "nsIDOMFile.h"
22 #include "nsDirectoryServiceDefs.h"
23 #include "nsStringStream.h"
24 #include "nsIURI.h"
25 #include "nsIURL.h"
26 #include "nsNetUtil.h"
27 #include "nsLinebreakConverter.h"
28 #include "nsEscape.h"
29 #include "nsUnicharUtils.h"
30 #include "nsIMultiplexInputStream.h"
31 #include "nsIMIMEInputStream.h"
32 #include "nsIMIMEService.h"
33 #include "nsIConsoleService.h"
34 #include "nsIScriptError.h"
35 #include "nsIStringBundle.h"
36 #include "nsCExternalHandlerService.h"
37 #include "nsIFileStreams.h"
38 #include "nsContentUtils.h"
40 #include "mozilla/dom/EncodingUtils.h"
42 using namespace mozilla;
43 using mozilla::dom::EncodingUtils;
45 static void
46 SendJSWarning(nsIDocument* aDocument,
47 const char* aWarningName,
48 const char16_t** aWarningArgs, uint32_t aWarningArgsLen)
50 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
51 NS_LITERAL_CSTRING("HTML"), aDocument,
52 nsContentUtils::eFORMS_PROPERTIES,
53 aWarningName,
54 aWarningArgs, aWarningArgsLen);
57 // --------------------------------------------------------------------------
59 class nsFSURLEncoded : public nsEncodingFormSubmission
61 public:
62 /**
63 * @param aCharset the charset of the form as a string
64 * @param aMethod the method of the submit (either NS_FORM_METHOD_GET or
65 * NS_FORM_METHOD_POST).
67 nsFSURLEncoded(const nsACString& aCharset,
68 int32_t aMethod,
69 nsIDocument* aDocument,
70 nsIContent* aOriginatingElement)
71 : nsEncodingFormSubmission(aCharset, aOriginatingElement),
72 mMethod(aMethod),
73 mDocument(aDocument),
74 mWarnedFileControl(false)
78 virtual nsresult AddNameValuePair(const nsAString& aName,
79 const nsAString& aValue);
80 virtual nsresult AddNameFilePair(const nsAString& aName,
81 nsIDOMBlob* aBlob,
82 const nsString& aFilename);
83 virtual nsresult GetEncodedSubmission(nsIURI* aURI,
84 nsIInputStream** aPostDataStream);
86 virtual bool SupportsIsindexSubmission()
88 return true;
91 virtual nsresult AddIsindex(const nsAString& aValue);
93 protected:
95 /**
96 * URL encode a Unicode string by encoding it to bytes, converting linebreaks
97 * properly, and then escaping many bytes as %xx.
99 * @param aStr the string to encode
100 * @param aEncoded the encoded string [OUT]
101 * @throws NS_ERROR_OUT_OF_MEMORY if we run out of memory
103 nsresult URLEncode(const nsAString& aStr, nsCString& aEncoded);
105 private:
107 * The method of the submit (either NS_FORM_METHOD_GET or
108 * NS_FORM_METHOD_POST).
110 int32_t mMethod;
112 /** The query string so far (the part after the ?) */
113 nsCString mQueryString;
115 /** The document whose URI to use when reporting errors */
116 nsCOMPtr<nsIDocument> mDocument;
118 /** Whether or not we have warned about a file control not being submitted */
119 bool mWarnedFileControl;
122 nsresult
123 nsFSURLEncoded::AddNameValuePair(const nsAString& aName,
124 const nsAString& aValue)
126 // Encode value
127 nsCString convValue;
128 nsresult rv = URLEncode(aValue, convValue);
129 NS_ENSURE_SUCCESS(rv, rv);
131 // Encode name
132 nsAutoCString convName;
133 rv = URLEncode(aName, convName);
134 NS_ENSURE_SUCCESS(rv, rv);
137 // Append data to string
138 if (mQueryString.IsEmpty()) {
139 mQueryString += convName + NS_LITERAL_CSTRING("=") + convValue;
140 } else {
141 mQueryString += NS_LITERAL_CSTRING("&") + convName
142 + NS_LITERAL_CSTRING("=") + convValue;
145 return NS_OK;
148 nsresult
149 nsFSURLEncoded::AddIsindex(const nsAString& aValue)
151 // Encode value
152 nsCString convValue;
153 nsresult rv = URLEncode(aValue, convValue);
154 NS_ENSURE_SUCCESS(rv, rv);
156 // Append data to string
157 if (mQueryString.IsEmpty()) {
158 mQueryString.Assign(convValue);
159 } else {
160 mQueryString += NS_LITERAL_CSTRING("&isindex=") + convValue;
163 return NS_OK;
166 nsresult
167 nsFSURLEncoded::AddNameFilePair(const nsAString& aName,
168 nsIDOMBlob* aBlob,
169 const nsString& aFilename)
171 if (!mWarnedFileControl) {
172 SendJSWarning(mDocument, "ForgotFileEnctypeWarning", nullptr, 0);
173 mWarnedFileControl = true;
176 nsAutoString filename;
177 nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
178 if (file) {
179 file->GetName(filename);
182 return AddNameValuePair(aName, filename);
185 static void
186 HandleMailtoSubject(nsCString& aPath) {
188 // Walk through the string and see if we have a subject already.
189 bool hasSubject = false;
190 bool hasParams = false;
191 int32_t paramSep = aPath.FindChar('?');
192 while (paramSep != kNotFound && paramSep < (int32_t)aPath.Length()) {
193 hasParams = true;
195 // Get the end of the name at the = op. If it is *after* the next &,
196 // assume that someone made a parameter without an = in it
197 int32_t nameEnd = aPath.FindChar('=', paramSep+1);
198 int32_t nextParamSep = aPath.FindChar('&', paramSep+1);
199 if (nextParamSep == kNotFound) {
200 nextParamSep = aPath.Length();
203 // If the = op is after the &, this parameter is a name without value.
204 // If there is no = op, same thing.
205 if (nameEnd == kNotFound || nextParamSep < nameEnd) {
206 nameEnd = nextParamSep;
209 if (nameEnd != kNotFound) {
210 if (Substring(aPath, paramSep+1, nameEnd-(paramSep+1)).
211 LowerCaseEqualsLiteral("subject")) {
212 hasSubject = true;
213 break;
217 paramSep = nextParamSep;
220 // If there is no subject, append a preformed subject to the mailto line
221 if (!hasSubject) {
222 if (hasParams) {
223 aPath.Append('&');
224 } else {
225 aPath.Append('?');
228 // Get the default subject
229 nsXPIDLString brandName;
230 nsresult rv =
231 nsContentUtils::GetLocalizedString(nsContentUtils::eBRAND_PROPERTIES,
232 "brandShortName", brandName);
233 if (NS_FAILED(rv))
234 return;
235 const char16_t *formatStrings[] = { brandName.get() };
236 nsXPIDLString subjectStr;
237 rv = nsContentUtils::FormatLocalizedString(
238 nsContentUtils::eFORMS_PROPERTIES,
239 "DefaultFormSubject",
240 formatStrings,
241 subjectStr);
242 if (NS_FAILED(rv))
243 return;
244 aPath.AppendLiteral("subject=");
245 nsCString subjectStrEscaped;
246 aPath.Append(NS_EscapeURL(NS_ConvertUTF16toUTF8(subjectStr), esc_Query,
247 subjectStrEscaped));
251 nsresult
252 nsFSURLEncoded::GetEncodedSubmission(nsIURI* aURI,
253 nsIInputStream** aPostDataStream)
255 nsresult rv = NS_OK;
257 *aPostDataStream = nullptr;
259 if (mMethod == NS_FORM_METHOD_POST) {
261 bool isMailto = false;
262 aURI->SchemeIs("mailto", &isMailto);
263 if (isMailto) {
265 nsAutoCString path;
266 rv = aURI->GetPath(path);
267 NS_ENSURE_SUCCESS(rv, rv);
269 HandleMailtoSubject(path);
271 // Append the body to and force-plain-text args to the mailto line
272 nsCString escapedBody;
273 escapedBody.Adopt(nsEscape(mQueryString.get(), url_XAlphas));
275 path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
277 rv = aURI->SetPath(path);
279 } else {
281 nsCOMPtr<nsIInputStream> dataStream;
282 // XXX We *really* need to either get the string to disown its data (and
283 // not destroy it), or make a string input stream that owns the CString
284 // that is passed to it. Right now this operation does a copy.
285 rv = NS_NewCStringInputStream(getter_AddRefs(dataStream), mQueryString);
286 NS_ENSURE_SUCCESS(rv, rv);
288 nsCOMPtr<nsIMIMEInputStream> mimeStream(
289 do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
290 NS_ENSURE_SUCCESS(rv, rv);
292 #ifdef SPECIFY_CHARSET_IN_CONTENT_TYPE
293 mimeStream->AddHeader("Content-Type",
294 PromiseFlatString(
295 "application/x-www-form-urlencoded; charset="
296 + mCharset
297 ).get());
298 #else
299 mimeStream->AddHeader("Content-Type",
300 "application/x-www-form-urlencoded");
301 #endif
302 mimeStream->SetAddContentLength(true);
303 mimeStream->SetData(dataStream);
305 *aPostDataStream = mimeStream;
306 NS_ADDREF(*aPostDataStream);
309 } else {
310 // Get the full query string
311 bool schemeIsJavaScript;
312 rv = aURI->SchemeIs("javascript", &schemeIsJavaScript);
313 NS_ENSURE_SUCCESS(rv, rv);
314 if (schemeIsJavaScript) {
315 return NS_OK;
318 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
319 if (url) {
320 url->SetQuery(mQueryString);
322 else {
323 nsAutoCString path;
324 rv = aURI->GetPath(path);
325 NS_ENSURE_SUCCESS(rv, rv);
326 // Bug 42616: Trim off named anchor and save it to add later
327 int32_t namedAnchorPos = path.FindChar('#');
328 nsAutoCString namedAnchor;
329 if (kNotFound != namedAnchorPos) {
330 path.Right(namedAnchor, (path.Length() - namedAnchorPos));
331 path.Truncate(namedAnchorPos);
334 // Chop off old query string (bug 25330, 57333)
335 // Only do this for GET not POST (bug 41585)
336 int32_t queryStart = path.FindChar('?');
337 if (kNotFound != queryStart) {
338 path.Truncate(queryStart);
341 path.Append('?');
342 // Bug 42616: Add named anchor to end after query string
343 path.Append(mQueryString + namedAnchor);
345 aURI->SetPath(path);
349 return rv;
352 // i18n helper routines
353 nsresult
354 nsFSURLEncoded::URLEncode(const nsAString& aStr, nsCString& aEncoded)
356 // convert to CRLF breaks
357 char16_t* convertedBuf =
358 nsLinebreakConverter::ConvertUnicharLineBreaks(PromiseFlatString(aStr).get(),
359 nsLinebreakConverter::eLinebreakAny,
360 nsLinebreakConverter::eLinebreakNet);
361 NS_ENSURE_TRUE(convertedBuf, NS_ERROR_OUT_OF_MEMORY);
363 nsAutoCString encodedBuf;
364 nsresult rv = EncodeVal(nsDependentString(convertedBuf), encodedBuf, false);
365 nsMemory::Free(convertedBuf);
366 NS_ENSURE_SUCCESS(rv, rv);
368 char* escapedBuf = nsEscape(encodedBuf.get(), url_XPAlphas);
369 NS_ENSURE_TRUE(escapedBuf, NS_ERROR_OUT_OF_MEMORY);
370 aEncoded.Adopt(escapedBuf);
372 return NS_OK;
375 // --------------------------------------------------------------------------
377 nsFSMultipartFormData::nsFSMultipartFormData(const nsACString& aCharset,
378 nsIContent* aOriginatingElement)
379 : nsEncodingFormSubmission(aCharset, aOriginatingElement)
381 mPostDataStream =
382 do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1");
383 mTotalLength = 0;
385 mBoundary.AssignLiteral("---------------------------");
386 mBoundary.AppendInt(rand());
387 mBoundary.AppendInt(rand());
388 mBoundary.AppendInt(rand());
391 nsFSMultipartFormData::~nsFSMultipartFormData()
393 NS_ASSERTION(mPostDataChunk.IsEmpty(), "Left unsubmitted data");
396 nsIInputStream*
397 nsFSMultipartFormData::GetSubmissionBody(uint64_t* aContentLength)
399 // Finish data
400 mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
401 + NS_LITERAL_CSTRING("--" CRLF);
403 // Add final data input stream
404 AddPostDataStream();
406 *aContentLength = mTotalLength;
407 return mPostDataStream;
410 nsresult
411 nsFSMultipartFormData::AddNameValuePair(const nsAString& aName,
412 const nsAString& aValue)
414 nsCString valueStr;
415 nsAutoCString encodedVal;
416 nsresult rv = EncodeVal(aValue, encodedVal, false);
417 NS_ENSURE_SUCCESS(rv, rv);
419 valueStr.Adopt(nsLinebreakConverter::
420 ConvertLineBreaks(encodedVal.get(),
421 nsLinebreakConverter::eLinebreakAny,
422 nsLinebreakConverter::eLinebreakNet));
424 nsAutoCString nameStr;
425 rv = EncodeVal(aName, nameStr, true);
426 NS_ENSURE_SUCCESS(rv, rv);
428 // Make MIME block for name/value pair
430 // XXX: name parameter should be encoded per RFC 2231
431 // RFC 2388 specifies that RFC 2047 be used, but I think it's not
432 // consistent with MIME standard.
433 mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
434 + NS_LITERAL_CSTRING(CRLF)
435 + NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
436 + nameStr + NS_LITERAL_CSTRING("\"" CRLF CRLF)
437 + valueStr + NS_LITERAL_CSTRING(CRLF);
439 return NS_OK;
442 nsresult
443 nsFSMultipartFormData::AddNameFilePair(const nsAString& aName,
444 nsIDOMBlob* aBlob,
445 const nsString& aFilename)
447 // Encode the control name
448 nsAutoCString nameStr;
449 nsresult rv = EncodeVal(aName, nameStr, true);
450 NS_ENSURE_SUCCESS(rv, rv);
452 nsCString filename, contentType;
453 nsCOMPtr<nsIInputStream> fileStream;
454 if (aBlob) {
455 // We prefer the explicitly passed filename
456 if (!aFilename.IsVoid()) {
457 rv = EncodeVal(aFilename, filename, true);
458 NS_ENSURE_SUCCESS(rv, rv);
459 } else {
460 // Get and encode the filename
461 nsAutoString filename16;
462 nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
463 if (file) {
464 rv = file->GetName(filename16);
465 NS_ENSURE_SUCCESS(rv, rv);
468 if (filename16.IsEmpty()) {
469 filename16.AssignLiteral("blob");
470 } else {
471 nsAutoString filepath16;
472 rv = file->GetPath(filepath16);
473 NS_ENSURE_SUCCESS(rv, rv);
474 if (!filepath16.IsEmpty()) {
475 // File.path includes trailing "/"
476 filename16 = filepath16 + filename16;
480 rv = EncodeVal(filename16, filename, true);
481 NS_ENSURE_SUCCESS(rv, rv);
484 // Get content type
485 nsAutoString contentType16;
486 rv = aBlob->GetType(contentType16);
487 if (NS_FAILED(rv) || contentType16.IsEmpty()) {
488 contentType16.AssignLiteral("application/octet-stream");
490 contentType.Adopt(nsLinebreakConverter::
491 ConvertLineBreaks(NS_ConvertUTF16toUTF8(contentType16).get(),
492 nsLinebreakConverter::eLinebreakAny,
493 nsLinebreakConverter::eLinebreakSpace));
495 // Get input stream
496 rv = aBlob->GetInternalStream(getter_AddRefs(fileStream));
497 NS_ENSURE_SUCCESS(rv, rv);
498 if (fileStream) {
499 // Create buffered stream (for efficiency)
500 nsCOMPtr<nsIInputStream> bufferedStream;
501 rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream),
502 fileStream, 8192);
503 NS_ENSURE_SUCCESS(rv, rv);
505 fileStream = bufferedStream;
508 else {
509 contentType.AssignLiteral("application/octet-stream");
513 // Make MIME block for name/value pair
515 // more appropriate than always using binary?
516 mPostDataChunk += NS_LITERAL_CSTRING("--") + mBoundary
517 + NS_LITERAL_CSTRING(CRLF);
518 // XXX: name/filename parameter should be encoded per RFC 2231
519 // RFC 2388 specifies that RFC 2047 be used, but I think it's not
520 // consistent with the MIME standard.
521 mPostDataChunk +=
522 NS_LITERAL_CSTRING("Content-Disposition: form-data; name=\"")
523 + nameStr + NS_LITERAL_CSTRING("\"; filename=\"")
524 + filename + NS_LITERAL_CSTRING("\"" CRLF)
525 + NS_LITERAL_CSTRING("Content-Type: ")
526 + contentType + NS_LITERAL_CSTRING(CRLF CRLF);
528 // We should not try to append an invalid stream. That will happen for example
529 // if we try to update a file that actually do not exist.
530 uint64_t size;
531 if (fileStream && NS_SUCCEEDED(aBlob->GetSize(&size))) {
532 // We need to dump the data up to this point into the POST data stream here,
533 // since we're about to add the file input stream
534 AddPostDataStream();
536 mPostDataStream->AppendStream(fileStream);
537 mTotalLength += size;
540 // CRLF after file
541 mPostDataChunk.AppendLiteral(CRLF);
543 return NS_OK;
546 nsresult
547 nsFSMultipartFormData::GetEncodedSubmission(nsIURI* aURI,
548 nsIInputStream** aPostDataStream)
550 nsresult rv;
552 // Make header
553 nsCOMPtr<nsIMIMEInputStream> mimeStream
554 = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
555 NS_ENSURE_SUCCESS(rv, rv);
557 nsAutoCString contentType;
558 GetContentType(contentType);
559 mimeStream->AddHeader("Content-Type", contentType.get());
560 mimeStream->SetAddContentLength(true);
561 uint64_t unused;
562 mimeStream->SetData(GetSubmissionBody(&unused));
564 mimeStream.forget(aPostDataStream);
566 return NS_OK;
569 nsresult
570 nsFSMultipartFormData::AddPostDataStream()
572 nsresult rv = NS_OK;
574 nsCOMPtr<nsIInputStream> postDataChunkStream;
575 rv = NS_NewCStringInputStream(getter_AddRefs(postDataChunkStream),
576 mPostDataChunk);
577 NS_ASSERTION(postDataChunkStream, "Could not open a stream for POST!");
578 if (postDataChunkStream) {
579 mPostDataStream->AppendStream(postDataChunkStream);
580 mTotalLength += mPostDataChunk.Length();
583 mPostDataChunk.Truncate();
585 return rv;
588 // --------------------------------------------------------------------------
590 class nsFSTextPlain : public nsEncodingFormSubmission
592 public:
593 nsFSTextPlain(const nsACString& aCharset, nsIContent* aOriginatingElement)
594 : nsEncodingFormSubmission(aCharset, aOriginatingElement)
598 virtual nsresult AddNameValuePair(const nsAString& aName,
599 const nsAString& aValue);
600 virtual nsresult AddNameFilePair(const nsAString& aName,
601 nsIDOMBlob* aBlob,
602 const nsString& aFilename);
603 virtual nsresult GetEncodedSubmission(nsIURI* aURI,
604 nsIInputStream** aPostDataStream);
606 private:
607 nsString mBody;
610 nsresult
611 nsFSTextPlain::AddNameValuePair(const nsAString& aName,
612 const nsAString& aValue)
614 // XXX This won't work well with a name like "a=b" or "a\nb" but I suppose
615 // text/plain doesn't care about that. Parsers aren't built for escaped
616 // values so we'll have to live with it.
617 mBody.Append(aName + NS_LITERAL_STRING("=") + aValue +
618 NS_LITERAL_STRING(CRLF));
620 return NS_OK;
623 nsresult
624 nsFSTextPlain::AddNameFilePair(const nsAString& aName,
625 nsIDOMBlob* aBlob,
626 const nsString& aFilename)
628 nsAutoString filename;
629 nsCOMPtr<nsIDOMFile> file = do_QueryInterface(aBlob);
630 if (file) {
631 file->GetName(filename);
634 AddNameValuePair(aName, filename);
635 return NS_OK;
638 nsresult
639 nsFSTextPlain::GetEncodedSubmission(nsIURI* aURI,
640 nsIInputStream** aPostDataStream)
642 nsresult rv = NS_OK;
644 // XXX HACK We are using the standard URL mechanism to give the body to the
645 // mailer instead of passing the post data stream to it, since that sounds
646 // hard.
647 bool isMailto = false;
648 aURI->SchemeIs("mailto", &isMailto);
649 if (isMailto) {
650 nsAutoCString path;
651 rv = aURI->GetPath(path);
652 NS_ENSURE_SUCCESS(rv, rv);
654 HandleMailtoSubject(path);
656 // Append the body to and force-plain-text args to the mailto line
657 char* escapedBuf = nsEscape(NS_ConvertUTF16toUTF8(mBody).get(),
658 url_XAlphas);
659 NS_ENSURE_TRUE(escapedBuf, NS_ERROR_OUT_OF_MEMORY);
660 nsCString escapedBody;
661 escapedBody.Adopt(escapedBuf);
663 path += NS_LITERAL_CSTRING("&force-plain-text=Y&body=") + escapedBody;
665 rv = aURI->SetPath(path);
667 } else {
668 // Create data stream.
669 // We do want to send the data through the charset encoder and we want to
670 // normalize linebreaks to use the "standard net" format (\r\n), but we
671 // don't want to perform any other encoding. This means that names and
672 // values which contains '=' or newlines are potentially ambigiously
673 // encoded, but that how text/plain is specced.
674 nsCString cbody;
675 EncodeVal(mBody, cbody, false);
676 cbody.Adopt(nsLinebreakConverter::
677 ConvertLineBreaks(cbody.get(),
678 nsLinebreakConverter::eLinebreakAny,
679 nsLinebreakConverter::eLinebreakNet));
680 nsCOMPtr<nsIInputStream> bodyStream;
681 rv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), cbody);
682 if (!bodyStream) {
683 return NS_ERROR_OUT_OF_MEMORY;
686 // Create mime stream with headers and such
687 nsCOMPtr<nsIMIMEInputStream> mimeStream
688 = do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv);
689 NS_ENSURE_SUCCESS(rv, rv);
691 mimeStream->AddHeader("Content-Type", "text/plain");
692 mimeStream->SetAddContentLength(true);
693 mimeStream->SetData(bodyStream);
694 CallQueryInterface(mimeStream, aPostDataStream);
697 return rv;
700 // --------------------------------------------------------------------------
702 nsEncodingFormSubmission::nsEncodingFormSubmission(const nsACString& aCharset,
703 nsIContent* aOriginatingElement)
704 : nsFormSubmission(aCharset, aOriginatingElement)
706 nsAutoCString charset(aCharset);
707 // canonical name is passed so that we just have to check against
708 // *our* canonical names listed in charsetaliases.properties
709 if (charset.EqualsLiteral("ISO-8859-1")) {
710 charset.AssignLiteral("windows-1252");
713 if (!(charset.EqualsLiteral("UTF-8") || charset.EqualsLiteral("gb18030"))) {
714 NS_ConvertUTF8toUTF16 charsetUtf16(charset);
715 const char16_t* charsetPtr = charsetUtf16.get();
716 SendJSWarning(aOriginatingElement ? aOriginatingElement->GetOwnerDocument()
717 : nullptr,
718 "CannotEncodeAllUnicode",
719 &charsetPtr,
723 mEncoder = do_CreateInstance(NS_SAVEASCHARSET_CONTRACTID);
724 if (mEncoder) {
725 nsresult rv =
726 mEncoder->Init(charset.get(),
727 (nsISaveAsCharset::attr_EntityAfterCharsetConv +
728 nsISaveAsCharset::attr_FallbackDecimalNCR),
730 if (NS_FAILED(rv)) {
731 mEncoder = nullptr;
736 nsEncodingFormSubmission::~nsEncodingFormSubmission()
740 // i18n helper routines
741 nsresult
742 nsEncodingFormSubmission::EncodeVal(const nsAString& aStr, nsCString& aOut,
743 bool aHeaderEncode)
745 if (mEncoder && !aStr.IsEmpty()) {
746 aOut.Truncate();
747 nsresult rv = mEncoder->Convert(PromiseFlatString(aStr).get(),
748 getter_Copies(aOut));
749 NS_ENSURE_SUCCESS(rv, rv);
751 else {
752 // fall back to UTF-8
753 CopyUTF16toUTF8(aStr, aOut);
756 if (aHeaderEncode) {
757 aOut.Adopt(nsLinebreakConverter::
758 ConvertLineBreaks(aOut.get(),
759 nsLinebreakConverter::eLinebreakAny,
760 nsLinebreakConverter::eLinebreakSpace));
761 aOut.ReplaceSubstring(NS_LITERAL_CSTRING("\""),
762 NS_LITERAL_CSTRING("\\\""));
766 return NS_OK;
769 // --------------------------------------------------------------------------
771 static void
772 GetSubmitCharset(nsGenericHTMLElement* aForm,
773 nsACString& oCharset)
775 oCharset.AssignLiteral("UTF-8"); // default to utf-8
777 nsAutoString acceptCharsetValue;
778 aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::acceptcharset,
779 acceptCharsetValue);
781 int32_t charsetLen = acceptCharsetValue.Length();
782 if (charsetLen > 0) {
783 int32_t offset=0;
784 int32_t spPos=0;
785 // get charset from charsets one by one
786 do {
787 spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
788 int32_t cnt = ((-1==spPos)?(charsetLen-offset):(spPos-offset));
789 if (cnt > 0) {
790 nsAutoString uCharset;
791 acceptCharsetValue.Mid(uCharset, offset, cnt);
793 if (EncodingUtils::FindEncodingForLabel(uCharset, oCharset))
794 return;
796 offset = spPos + 1;
797 } while (spPos != -1);
799 // if there are no accept-charset or all the charset are not supported
800 // Get the charset from document
801 nsIDocument* doc = aForm->GetComposedDoc();
802 if (doc) {
803 oCharset = doc->GetDocumentCharacterSet();
807 static void
808 GetEnumAttr(nsGenericHTMLElement* aContent,
809 nsIAtom* atom, int32_t* aValue)
811 const nsAttrValue* value = aContent->GetParsedAttr(atom);
812 if (value && value->Type() == nsAttrValue::eEnum) {
813 *aValue = value->GetEnumValue();
817 nsresult
818 GetSubmissionFromForm(nsGenericHTMLElement* aForm,
819 nsGenericHTMLElement* aOriginatingElement,
820 nsFormSubmission** aFormSubmission)
822 // Get all the information necessary to encode the form data
823 NS_ASSERTION(aForm->GetComposedDoc(),
824 "Should have doc if we're building submission!");
826 // Get encoding type (default: urlencoded)
827 int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
828 if (aOriginatingElement &&
829 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
830 GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype);
831 } else {
832 GetEnumAttr(aForm, nsGkAtoms::enctype, &enctype);
835 // Get method (default: GET)
836 int32_t method = NS_FORM_METHOD_GET;
837 if (aOriginatingElement &&
838 aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formmethod)) {
839 GetEnumAttr(aOriginatingElement, nsGkAtoms::formmethod, &method);
840 } else {
841 GetEnumAttr(aForm, nsGkAtoms::method, &method);
844 // Get charset
845 nsAutoCString charset;
846 GetSubmitCharset(aForm, charset);
848 // We now have a canonical charset name, so we only have to check it
849 // against canonical names.
851 // use UTF-8 for UTF-16* (per WHATWG and existing practice of
852 // MS IE/Opera).
853 if (StringBeginsWith(charset, NS_LITERAL_CSTRING("UTF-16"))) {
854 charset.AssignLiteral("UTF-8");
857 // Choose encoder
858 if (method == NS_FORM_METHOD_POST &&
859 enctype == NS_FORM_ENCTYPE_MULTIPART) {
860 *aFormSubmission = new nsFSMultipartFormData(charset, aOriginatingElement);
861 } else if (method == NS_FORM_METHOD_POST &&
862 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
863 *aFormSubmission = new nsFSTextPlain(charset, aOriginatingElement);
864 } else {
865 nsIDocument* doc = aForm->OwnerDoc();
866 if (enctype == NS_FORM_ENCTYPE_MULTIPART ||
867 enctype == NS_FORM_ENCTYPE_TEXTPLAIN) {
868 nsAutoString enctypeStr;
869 if (aOriginatingElement &&
870 aOriginatingElement->HasAttr(kNameSpaceID_None,
871 nsGkAtoms::formenctype)) {
872 aOriginatingElement->GetAttr(kNameSpaceID_None, nsGkAtoms::formenctype,
873 enctypeStr);
874 } else {
875 aForm->GetAttr(kNameSpaceID_None, nsGkAtoms::enctype, enctypeStr);
877 const char16_t* enctypeStrPtr = enctypeStr.get();
878 SendJSWarning(doc, "ForgotPostWarning",
879 &enctypeStrPtr, 1);
881 *aFormSubmission = new nsFSURLEncoded(charset, method, doc,
882 aOriginatingElement);
884 NS_ENSURE_TRUE(*aFormSubmission, NS_ERROR_OUT_OF_MEMORY);
886 return NS_OK;