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