HTMLFormSubmission.cpp 29 KB

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