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