Coverage Report

Created: 2018-09-25 14:53

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