nsMIMEHeaderParamImpl.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346
  1. /* -*- Mode: C++; tab-width: 4; 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 <string.h>
  6. #include "prmem.h"
  7. #include "prprf.h"
  8. #include "plstr.h"
  9. #include "plbase64.h"
  10. #include "nsCRT.h"
  11. #include "nsMemory.h"
  12. #include "nsTArray.h"
  13. #include "nsCOMPtr.h"
  14. #include "nsEscape.h"
  15. #include "nsIUTF8ConverterService.h"
  16. #include "nsUConvCID.h"
  17. #include "nsIServiceManager.h"
  18. #include "nsMIMEHeaderParamImpl.h"
  19. #include "nsReadableUtils.h"
  20. #include "nsNativeCharsetUtils.h"
  21. #include "nsError.h"
  22. #include "nsIUnicodeDecoder.h"
  23. #include "mozilla/dom/EncodingUtils.h"
  24. using mozilla::dom::EncodingUtils;
  25. // static functions declared below are moved from mailnews/mime/src/comi18n.cpp
  26. static char *DecodeQ(const char *, uint32_t);
  27. static bool Is7bitNonAsciiString(const char *, uint32_t);
  28. static void CopyRawHeader(const char *, uint32_t, const char *, nsACString &);
  29. static nsresult DecodeRFC2047Str(const char *, const char *, bool, nsACString&);
  30. static nsresult internalDecodeParameter(const nsACString&, const char*,
  31. const char*, bool, bool, nsACString&);
  32. // XXX The chance of UTF-7 being used in the message header is really
  33. // low, but in theory it's possible.
  34. #define IS_7BIT_NON_ASCII_CHARSET(cset) \
  35. (!nsCRT::strncasecmp((cset), "ISO-2022", 8) || \
  36. !nsCRT::strncasecmp((cset), "HZ-GB", 5) || \
  37. !nsCRT::strncasecmp((cset), "UTF-7", 5))
  38. NS_IMPL_ISUPPORTS(nsMIMEHeaderParamImpl, nsIMIMEHeaderParam)
  39. NS_IMETHODIMP
  40. nsMIMEHeaderParamImpl::GetParameter(const nsACString& aHeaderVal,
  41. const char *aParamName,
  42. const nsACString& aFallbackCharset,
  43. bool aTryLocaleCharset,
  44. char **aLang, nsAString& aResult)
  45. {
  46. return DoGetParameter(aHeaderVal, aParamName, MIME_FIELD_ENCODING,
  47. aFallbackCharset, aTryLocaleCharset, aLang, aResult);
  48. }
  49. NS_IMETHODIMP
  50. nsMIMEHeaderParamImpl::GetParameterHTTP(const nsACString& aHeaderVal,
  51. const char *aParamName,
  52. const nsACString& aFallbackCharset,
  53. bool aTryLocaleCharset,
  54. char **aLang, nsAString& aResult)
  55. {
  56. return DoGetParameter(aHeaderVal, aParamName, HTTP_FIELD_ENCODING,
  57. aFallbackCharset, aTryLocaleCharset, aLang, aResult);
  58. }
  59. // XXX : aTryLocaleCharset is not yet effective.
  60. nsresult
  61. nsMIMEHeaderParamImpl::DoGetParameter(const nsACString& aHeaderVal,
  62. const char *aParamName,
  63. ParamDecoding aDecoding,
  64. const nsACString& aFallbackCharset,
  65. bool aTryLocaleCharset,
  66. char **aLang, nsAString& aResult)
  67. {
  68. aResult.Truncate();
  69. nsresult rv;
  70. // get parameter (decode RFC 2231/5987 when applicable, as specified by
  71. // aDecoding (5987 being a subset of 2231) and return charset.)
  72. nsXPIDLCString med;
  73. nsXPIDLCString charset;
  74. rv = DoParameterInternal(PromiseFlatCString(aHeaderVal).get(), aParamName,
  75. aDecoding, getter_Copies(charset), aLang,
  76. getter_Copies(med));
  77. if (NS_FAILED(rv))
  78. return rv;
  79. // convert to UTF-8 after charset conversion and RFC 2047 decoding
  80. // if necessary.
  81. nsAutoCString str1;
  82. rv = internalDecodeParameter(med, charset.get(), nullptr, false,
  83. // was aDecoding == MIME_FIELD_ENCODING
  84. // see bug 875615
  85. true,
  86. str1);
  87. NS_ENSURE_SUCCESS(rv, rv);
  88. if (!aFallbackCharset.IsEmpty())
  89. {
  90. nsAutoCString charset;
  91. EncodingUtils::FindEncodingForLabel(aFallbackCharset, charset);
  92. nsAutoCString str2;
  93. nsCOMPtr<nsIUTF8ConverterService>
  94. cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
  95. if (cvtUTF8 &&
  96. NS_SUCCEEDED(cvtUTF8->ConvertStringToUTF8(str1,
  97. PromiseFlatCString(aFallbackCharset).get(), false,
  98. !charset.EqualsLiteral("UTF-8"),
  99. 1, str2))) {
  100. CopyUTF8toUTF16(str2, aResult);
  101. return NS_OK;
  102. }
  103. }
  104. if (IsUTF8(str1)) {
  105. CopyUTF8toUTF16(str1, aResult);
  106. return NS_OK;
  107. }
  108. if (aTryLocaleCharset && !NS_IsNativeUTF8())
  109. return NS_CopyNativeToUnicode(str1, aResult);
  110. CopyASCIItoUTF16(str1, aResult);
  111. return NS_OK;
  112. }
  113. // remove backslash-encoded sequences from quoted-strings
  114. // modifies string in place, potentially shortening it
  115. void RemoveQuotedStringEscapes(char *src)
  116. {
  117. char *dst = src;
  118. for (char *c = src; *c; ++c)
  119. {
  120. if (c[0] == '\\' && c[1])
  121. {
  122. // skip backslash if not at end
  123. ++c;
  124. }
  125. *dst++ = *c;
  126. }
  127. *dst = 0;
  128. }
  129. // true is character is a hex digit
  130. bool IsHexDigit(char aChar)
  131. {
  132. char c = aChar;
  133. return (c >= 'a' && c <= 'f') ||
  134. (c >= 'A' && c <= 'F') ||
  135. (c >= '0' && c <= '9');
  136. }
  137. // validate that a C String containing %-escapes is syntactically valid
  138. bool IsValidPercentEscaped(const char *aValue, int32_t len)
  139. {
  140. for (int32_t i = 0; i < len; i++) {
  141. if (aValue[i] == '%') {
  142. if (!IsHexDigit(aValue[i + 1]) || !IsHexDigit(aValue[i + 2])) {
  143. return false;
  144. }
  145. }
  146. }
  147. return true;
  148. }
  149. // Support for continuations (RFC 2231, Section 3)
  150. // only a sane number supported
  151. #define MAX_CONTINUATIONS 999
  152. // part of a continuation
  153. class Continuation {
  154. public:
  155. Continuation(const char *aValue, uint32_t aLength,
  156. bool aNeedsPercentDecoding, bool aWasQuotedString) {
  157. value = aValue;
  158. length = aLength;
  159. needsPercentDecoding = aNeedsPercentDecoding;
  160. wasQuotedString = aWasQuotedString;
  161. }
  162. Continuation() {
  163. // empty constructor needed for nsTArray
  164. value = 0L;
  165. length = 0;
  166. needsPercentDecoding = false;
  167. wasQuotedString = false;
  168. }
  169. ~Continuation() = default;
  170. const char *value;
  171. uint32_t length;
  172. bool needsPercentDecoding;
  173. bool wasQuotedString;
  174. };
  175. // combine segments into a single string, returning the allocated string
  176. // (or nullptr) while emptying the list
  177. char *combineContinuations(nsTArray<Continuation>& aArray)
  178. {
  179. // Sanity check
  180. if (aArray.Length() == 0)
  181. return nullptr;
  182. // Get an upper bound for the length
  183. uint32_t length = 0;
  184. for (uint32_t i = 0; i < aArray.Length(); i++) {
  185. length += aArray[i].length;
  186. }
  187. // Allocate
  188. char *result = (char *) moz_xmalloc(length + 1);
  189. // Concatenate
  190. if (result) {
  191. *result = '\0';
  192. for (uint32_t i = 0; i < aArray.Length(); i++) {
  193. Continuation cont = aArray[i];
  194. if (! cont.value) break;
  195. char *c = result + strlen(result);
  196. strncat(result, cont.value, cont.length);
  197. if (cont.needsPercentDecoding) {
  198. nsUnescape(c);
  199. }
  200. if (cont.wasQuotedString) {
  201. RemoveQuotedStringEscapes(c);
  202. }
  203. }
  204. // return null if empty value
  205. if (*result == '\0') {
  206. free(result);
  207. result = nullptr;
  208. }
  209. } else {
  210. // Handle OOM
  211. NS_WARNING("Out of memory\n");
  212. }
  213. return result;
  214. }
  215. // add a continuation, return false on error if segment already has been seen
  216. bool addContinuation(nsTArray<Continuation>& aArray, uint32_t aIndex,
  217. const char *aValue, uint32_t aLength,
  218. bool aNeedsPercentDecoding, bool aWasQuotedString)
  219. {
  220. if (aIndex < aArray.Length() && aArray[aIndex].value) {
  221. NS_WARNING("duplicate RC2231 continuation segment #\n");
  222. return false;
  223. }
  224. if (aIndex > MAX_CONTINUATIONS) {
  225. NS_WARNING("RC2231 continuation segment # exceeds limit\n");
  226. return false;
  227. }
  228. if (aNeedsPercentDecoding && aWasQuotedString) {
  229. NS_WARNING("RC2231 continuation segment can't use percent encoding and quoted string form at the same time\n");
  230. return false;
  231. }
  232. Continuation cont(aValue, aLength, aNeedsPercentDecoding, aWasQuotedString);
  233. if (aArray.Length() <= aIndex) {
  234. aArray.SetLength(aIndex + 1);
  235. }
  236. aArray[aIndex] = cont;
  237. return true;
  238. }
  239. // parse a segment number; return -1 on error
  240. int32_t parseSegmentNumber(const char *aValue, int32_t aLen)
  241. {
  242. if (aLen < 1) {
  243. NS_WARNING("segment number missing\n");
  244. return -1;
  245. }
  246. if (aLen > 1 && aValue[0] == '0') {
  247. NS_WARNING("leading '0' not allowed in segment number\n");
  248. return -1;
  249. }
  250. int32_t segmentNumber = 0;
  251. for (int32_t i = 0; i < aLen; i++) {
  252. if (! (aValue[i] >= '0' && aValue[i] <= '9')) {
  253. NS_WARNING("invalid characters in segment number\n");
  254. return -1;
  255. }
  256. segmentNumber *= 10;
  257. segmentNumber += aValue[i] - '0';
  258. if (segmentNumber > MAX_CONTINUATIONS) {
  259. NS_WARNING("Segment number exceeds sane size\n");
  260. return -1;
  261. }
  262. }
  263. return segmentNumber;
  264. }
  265. // validate a given octet sequence for compliance with the specified
  266. // encoding
  267. bool IsValidOctetSequenceForCharset(nsACString& aCharset, const char *aOctets)
  268. {
  269. nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(do_GetService
  270. (NS_UTF8CONVERTERSERVICE_CONTRACTID));
  271. if (!cvtUTF8) {
  272. NS_WARNING("Can't get UTF8ConverterService\n");
  273. return false;
  274. }
  275. nsAutoCString tmpRaw;
  276. tmpRaw.Assign(aOctets);
  277. nsAutoCString tmpDecoded;
  278. nsresult rv = cvtUTF8->ConvertStringToUTF8(tmpRaw,
  279. PromiseFlatCString(aCharset).get(),
  280. false, false, 1, tmpDecoded);
  281. if (rv != NS_OK) {
  282. // we can't decode; charset may be unsupported, or the octet sequence
  283. // is broken (illegal or incomplete octet sequence contained)
  284. NS_WARNING("RFC2231/5987 parameter value does not decode according to specified charset\n");
  285. return false;
  286. }
  287. return true;
  288. }
  289. // moved almost verbatim from mimehdrs.cpp
  290. // char *
  291. // MimeHeaders_get_parameter (const char *header_value, const char *parm_name,
  292. // char **charset, char **language)
  293. //
  294. // The format of these header lines is
  295. // <token> [ ';' <token> '=' <token-or-quoted-string> ]*
  296. NS_IMETHODIMP
  297. nsMIMEHeaderParamImpl::GetParameterInternal(const char *aHeaderValue,
  298. const char *aParamName,
  299. char **aCharset,
  300. char **aLang,
  301. char **aResult)
  302. {
  303. return DoParameterInternal(aHeaderValue, aParamName, MIME_FIELD_ENCODING,
  304. aCharset, aLang, aResult);
  305. }
  306. nsresult
  307. nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
  308. const char *aParamName,
  309. ParamDecoding aDecoding,
  310. char **aCharset,
  311. char **aLang,
  312. char **aResult)
  313. {
  314. if (!aHeaderValue || !*aHeaderValue || !aResult)
  315. return NS_ERROR_INVALID_ARG;
  316. *aResult = nullptr;
  317. if (aCharset) *aCharset = nullptr;
  318. if (aLang) *aLang = nullptr;
  319. nsAutoCString charset;
  320. // change to (aDecoding != HTTP_FIELD_ENCODING) when we want to disable
  321. // them for HTTP header fields later on, see bug 776324
  322. bool acceptContinuations = true;
  323. const char *str = aHeaderValue;
  324. // skip leading white space.
  325. for (; *str && nsCRT::IsAsciiSpace(*str); ++str)
  326. ;
  327. const char *start = str;
  328. // aParamName is empty. return the first (possibly) _unnamed_ 'parameter'
  329. // For instance, return 'inline' in the following case:
  330. // Content-Disposition: inline; filename=.....
  331. if (!aParamName || !*aParamName)
  332. {
  333. for (; *str && *str != ';' && !nsCRT::IsAsciiSpace(*str); ++str)
  334. ;
  335. if (str == start)
  336. return NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY;
  337. *aResult = (char *) nsMemory::Clone(start, (str - start) + 1);
  338. NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
  339. (*aResult)[str - start] = '\0'; // null-terminate
  340. return NS_OK;
  341. }
  342. /* Skip forward to first ';' */
  343. for (; *str && *str != ';' && *str != ','; ++str)
  344. ;
  345. if (*str)
  346. str++;
  347. /* Skip over following whitespace */
  348. for (; *str && nsCRT::IsAsciiSpace(*str); ++str)
  349. ;
  350. // Some broken http servers just specify parameters
  351. // like 'filename' without specifying disposition
  352. // method. Rewind to the first non-white-space
  353. // character.
  354. if (!*str)
  355. str = start;
  356. // RFC2231 - The legitimate parm format can be:
  357. // A. title=ThisIsTitle
  358. // B. title*=us-ascii'en-us'This%20is%20wierd.
  359. // C. title*0*=us-ascii'en'This%20is%20wierd.%20We
  360. // title*1*=have%20to%20support%20this.
  361. // title*2="Else..."
  362. // D. title*0="Hey, what you think you are doing?"
  363. // title*1="There is no charset and lang info."
  364. // RFC5987: only A and B
  365. // collect results for the different algorithms (plain filename,
  366. // RFC5987/2231-encoded filename, + continuations) separately and decide
  367. // which to use at the end
  368. char *caseAResult = nullptr;
  369. char *caseBResult = nullptr;
  370. char *caseCDResult = nullptr;
  371. // collect continuation segments
  372. nsTArray<Continuation> segments;
  373. // our copies of the charset parameter, kept separately as they might
  374. // differ for the two formats
  375. nsDependentCSubstring charsetB, charsetCD;
  376. nsDependentCSubstring lang;
  377. int32_t paramLen = strlen(aParamName);
  378. while (*str) {
  379. // find name/value
  380. const char *nameStart = str;
  381. const char *nameEnd = nullptr;
  382. const char *valueStart = str;
  383. const char *valueEnd = nullptr;
  384. bool isQuotedString = false;
  385. NS_ASSERTION(!nsCRT::IsAsciiSpace(*str), "should be after whitespace.");
  386. // Skip forward to the end of this token.
  387. for (; *str && !nsCRT::IsAsciiSpace(*str) && *str != '=' && *str != ';'; str++)
  388. ;
  389. nameEnd = str;
  390. int32_t nameLen = nameEnd - nameStart;
  391. // Skip over whitespace, '=', and whitespace
  392. while (nsCRT::IsAsciiSpace(*str)) ++str;
  393. if (!*str) {
  394. break;
  395. }
  396. if (*str++ != '=') {
  397. // don't accept parameters without "="
  398. goto increment_str;
  399. }
  400. while (nsCRT::IsAsciiSpace(*str)) ++str;
  401. if (*str != '"') {
  402. // The value is a token, not a quoted string.
  403. valueStart = str;
  404. for (valueEnd = str;
  405. *valueEnd && !nsCRT::IsAsciiSpace (*valueEnd) && *valueEnd != ';';
  406. valueEnd++)
  407. ;
  408. str = valueEnd;
  409. } else {
  410. isQuotedString = true;
  411. ++str;
  412. valueStart = str;
  413. for (valueEnd = str; *valueEnd; ++valueEnd) {
  414. if (*valueEnd == '\\' && *(valueEnd + 1))
  415. ++valueEnd;
  416. else if (*valueEnd == '"')
  417. break;
  418. }
  419. str = valueEnd;
  420. // *valueEnd != null means that *valueEnd is quote character.
  421. if (*valueEnd)
  422. str++;
  423. }
  424. // See if this is the simplest case (case A above),
  425. // a 'single' line value with no charset and lang.
  426. // If so, copy it and return.
  427. if (nameLen == paramLen &&
  428. !nsCRT::strncasecmp(nameStart, aParamName, paramLen)) {
  429. if (caseAResult) {
  430. // we already have one caseA result, ignore subsequent ones
  431. goto increment_str;
  432. }
  433. // if the parameter spans across multiple lines we have to strip out the
  434. // line continuation -- jht 4/29/98
  435. nsAutoCString tempStr(valueStart, valueEnd - valueStart);
  436. tempStr.StripChars("\r\n");
  437. char *res = ToNewCString(tempStr);
  438. NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY);
  439. if (isQuotedString)
  440. RemoveQuotedStringEscapes(res);
  441. caseAResult = res;
  442. // keep going, we may find a RFC 2231/5987 encoded alternative
  443. }
  444. // case B, C, and D
  445. else if (nameLen > paramLen &&
  446. !nsCRT::strncasecmp(nameStart, aParamName, paramLen) &&
  447. *(nameStart + paramLen) == '*') {
  448. // 1st char past '*'
  449. const char *cp = nameStart + paramLen + 1;
  450. // if param name ends in "*" we need do to RFC5987 "ext-value" decoding
  451. bool needExtDecoding = *(nameEnd - 1) == '*';
  452. bool caseB = nameLen == paramLen + 1;
  453. bool caseCStart = (*cp == '0') && needExtDecoding;
  454. // parse the segment number
  455. int32_t segmentNumber = -1;
  456. if (!caseB) {
  457. int32_t segLen = (nameEnd - cp) - (needExtDecoding ? 1 : 0);
  458. segmentNumber = parseSegmentNumber(cp, segLen);
  459. if (segmentNumber == -1) {
  460. acceptContinuations = false;
  461. goto increment_str;
  462. }
  463. }
  464. // CaseB and start of CaseC: requires charset and optional language
  465. // in quotes (quotes required even if lang is blank)
  466. if (caseB || (caseCStart && acceptContinuations)) {
  467. // look for single quotation mark(')
  468. const char *sQuote1 = PL_strchr(valueStart, 0x27);
  469. const char *sQuote2 = sQuote1 ? PL_strchr(sQuote1 + 1, 0x27) : nullptr;
  470. // Two single quotation marks must be present even in
  471. // absence of charset and lang.
  472. if (!sQuote1 || !sQuote2) {
  473. NS_WARNING("Mandatory two single quotes are missing in header parameter\n");
  474. }
  475. const char *charsetStart = nullptr;
  476. int32_t charsetLength = 0;
  477. const char *langStart = nullptr;
  478. int32_t langLength = 0;
  479. const char *rawValStart = nullptr;
  480. int32_t rawValLength = 0;
  481. if (sQuote2 && sQuote1) {
  482. // both delimiters present: charSet'lang'rawVal
  483. rawValStart = sQuote2 + 1;
  484. rawValLength = valueEnd - rawValStart;
  485. langStart = sQuote1 + 1;
  486. langLength = sQuote2 - langStart;
  487. charsetStart = valueStart;
  488. charsetLength = sQuote1 - charsetStart;
  489. }
  490. else if (sQuote1) {
  491. // one delimiter; assume charset'rawVal
  492. rawValStart = sQuote1 + 1;
  493. rawValLength = valueEnd - rawValStart;
  494. charsetStart = valueStart;
  495. charsetLength = sQuote1 - valueStart;
  496. }
  497. else {
  498. // no delimiter: just rawVal
  499. rawValStart = valueStart;
  500. rawValLength = valueEnd - valueStart;
  501. }
  502. if (langLength != 0) {
  503. lang.Assign(langStart, langLength);
  504. }
  505. // keep the charset for later
  506. if (caseB) {
  507. charsetB.Assign(charsetStart, charsetLength);
  508. } else {
  509. // if caseCorD
  510. charsetCD.Assign(charsetStart, charsetLength);
  511. }
  512. // non-empty value part
  513. if (rawValLength > 0) {
  514. if (!caseBResult && caseB) {
  515. if (!IsValidPercentEscaped(rawValStart, rawValLength)) {
  516. goto increment_str;
  517. }
  518. // allocate buffer for the raw value
  519. char *tmpResult = (char *) nsMemory::Clone(rawValStart, rawValLength + 1);
  520. if (!tmpResult) {
  521. goto increment_str;
  522. }
  523. *(tmpResult + rawValLength) = 0;
  524. nsUnescape(tmpResult);
  525. caseBResult = tmpResult;
  526. } else {
  527. // caseC
  528. bool added = addContinuation(segments, 0, rawValStart,
  529. rawValLength, needExtDecoding,
  530. isQuotedString);
  531. if (!added) {
  532. // continuation not added, stop processing them
  533. acceptContinuations = false;
  534. }
  535. }
  536. }
  537. } // end of if-block : title*0*= or title*=
  538. // caseD: a line of multiline param with no need for unescaping : title*[0-9]=
  539. // or 2nd or later lines of a caseC param : title*[1-9]*=
  540. else if (acceptContinuations && segmentNumber != -1) {
  541. uint32_t valueLength = valueEnd - valueStart;
  542. bool added = addContinuation(segments, segmentNumber, valueStart,
  543. valueLength, needExtDecoding,
  544. isQuotedString);
  545. if (!added) {
  546. // continuation not added, stop processing them
  547. acceptContinuations = false;
  548. }
  549. } // end of if-block : title*[0-9]= or title*[1-9]*=
  550. }
  551. // str now points after the end of the value.
  552. // skip over whitespace, ';', whitespace.
  553. increment_str:
  554. while (nsCRT::IsAsciiSpace(*str)) ++str;
  555. if (*str == ';') {
  556. ++str;
  557. } else {
  558. // stop processing the header field; either we are done or the
  559. // separator was missing
  560. break;
  561. }
  562. while (nsCRT::IsAsciiSpace(*str)) ++str;
  563. }
  564. caseCDResult = combineContinuations(segments);
  565. if (caseBResult && !charsetB.IsEmpty()) {
  566. // check that the 2231/5987 result decodes properly given the
  567. // specified character set
  568. if (!IsValidOctetSequenceForCharset(charsetB, caseBResult))
  569. caseBResult = nullptr;
  570. }
  571. if (caseCDResult && !charsetCD.IsEmpty()) {
  572. // check that the 2231/5987 result decodes properly given the
  573. // specified character set
  574. if (!IsValidOctetSequenceForCharset(charsetCD, caseCDResult))
  575. caseCDResult = nullptr;
  576. }
  577. if (caseBResult) {
  578. // prefer simple 5987 format over 2231 with continuations
  579. *aResult = caseBResult;
  580. caseBResult = nullptr;
  581. charset.Assign(charsetB);
  582. }
  583. else if (caseCDResult) {
  584. // prefer 2231/5987 with or without continuations over plain format
  585. *aResult = caseCDResult;
  586. caseCDResult = nullptr;
  587. charset.Assign(charsetCD);
  588. }
  589. else if (caseAResult) {
  590. *aResult = caseAResult;
  591. caseAResult = nullptr;
  592. }
  593. // free unused stuff
  594. free(caseAResult);
  595. free(caseBResult);
  596. free(caseCDResult);
  597. // if we have a result
  598. if (*aResult) {
  599. // then return charset and lang as well
  600. if (aLang && !lang.IsEmpty()) {
  601. uint32_t len = lang.Length();
  602. *aLang = (char *) nsMemory::Clone(lang.BeginReading(), len + 1);
  603. if (*aLang) {
  604. *(*aLang + len) = 0;
  605. }
  606. }
  607. if (aCharset && !charset.IsEmpty()) {
  608. uint32_t len = charset.Length();
  609. *aCharset = (char *) nsMemory::Clone(charset.BeginReading(), len + 1);
  610. if (*aCharset) {
  611. *(*aCharset + len) = 0;
  612. }
  613. }
  614. }
  615. return *aResult ? NS_OK : NS_ERROR_INVALID_ARG;
  616. }
  617. nsresult
  618. internalDecodeRFC2047Header(const char* aHeaderVal, const char* aDefaultCharset,
  619. bool aOverrideCharset, bool aEatContinuations,
  620. nsACString& aResult)
  621. {
  622. aResult.Truncate();
  623. if (!aHeaderVal)
  624. return NS_ERROR_INVALID_ARG;
  625. if (!*aHeaderVal)
  626. return NS_OK;
  627. // If aHeaderVal is RFC 2047 encoded or is not a UTF-8 string but
  628. // aDefaultCharset is specified, decodes RFC 2047 encoding and converts
  629. // to UTF-8. Otherwise, just strips away CRLF.
  630. if (PL_strstr(aHeaderVal, "=?") ||
  631. (aDefaultCharset && (!IsUTF8(nsDependentCString(aHeaderVal)) ||
  632. Is7bitNonAsciiString(aHeaderVal, strlen(aHeaderVal))))) {
  633. DecodeRFC2047Str(aHeaderVal, aDefaultCharset, aOverrideCharset, aResult);
  634. } else if (aEatContinuations &&
  635. (PL_strchr(aHeaderVal, '\n') || PL_strchr(aHeaderVal, '\r'))) {
  636. aResult = aHeaderVal;
  637. } else {
  638. aEatContinuations = false;
  639. aResult = aHeaderVal;
  640. }
  641. if (aEatContinuations) {
  642. nsAutoCString temp(aResult);
  643. temp.ReplaceSubstring("\n\t", " ");
  644. temp.ReplaceSubstring("\r\t", " ");
  645. temp.StripChars("\r\n");
  646. aResult = temp;
  647. }
  648. return NS_OK;
  649. }
  650. NS_IMETHODIMP
  651. nsMIMEHeaderParamImpl::DecodeRFC2047Header(const char* aHeaderVal,
  652. const char* aDefaultCharset,
  653. bool aOverrideCharset,
  654. bool aEatContinuations,
  655. nsACString& aResult)
  656. {
  657. return internalDecodeRFC2047Header(aHeaderVal, aDefaultCharset,
  658. aOverrideCharset, aEatContinuations,
  659. aResult);
  660. }
  661. // true if the character is allowed in a RFC 5987 value
  662. // see RFC 5987, Section 3.2.1, "attr-char"
  663. bool IsRFC5987AttrChar(char aChar)
  664. {
  665. char c = aChar;
  666. return (c >= 'a' && c <= 'z') ||
  667. (c >= 'A' && c <= 'Z') ||
  668. (c >= '0' && c <= '9') ||
  669. (c == '!' || c == '#' || c == '$' || c == '&' ||
  670. c == '+' || c == '-' || c == '.' || c == '^' ||
  671. c == '_' || c == '`' || c == '|' || c == '~');
  672. }
  673. // percent-decode a value
  674. // returns false on failure
  675. bool PercentDecode(nsACString& aValue)
  676. {
  677. char *c = (char *) moz_xmalloc(aValue.Length() + 1);
  678. if (!c) {
  679. return false;
  680. }
  681. strcpy(c, PromiseFlatCString(aValue).get());
  682. nsUnescape(c);
  683. aValue.Assign(c);
  684. free(c);
  685. return true;
  686. }
  687. // Decode a parameter value using the encoding defined in RFC 5987
  688. //
  689. // charset "'" [ language ] "'" value-chars
  690. NS_IMETHODIMP
  691. nsMIMEHeaderParamImpl::DecodeRFC5987Param(const nsACString& aParamVal,
  692. nsACString& aLang,
  693. nsAString& aResult)
  694. {
  695. nsAutoCString charset;
  696. nsAutoCString language;
  697. nsAutoCString value;
  698. uint32_t delimiters = 0;
  699. const nsCString& encoded = PromiseFlatCString(aParamVal);
  700. const char *c = encoded.get();
  701. while (*c) {
  702. char tc = *c++;
  703. if (tc == '\'') {
  704. // single quote
  705. delimiters++;
  706. } else if (((unsigned char)tc) >= 128) {
  707. // fail early, not ASCII
  708. NS_WARNING("non-US-ASCII character in RFC5987-encoded param");
  709. return NS_ERROR_INVALID_ARG;
  710. } else {
  711. if (delimiters == 0) {
  712. // valid characters are checked later implicitly
  713. charset.Append(tc);
  714. } else if (delimiters == 1) {
  715. // no value checking for now
  716. language.Append(tc);
  717. } else if (delimiters == 2) {
  718. if (IsRFC5987AttrChar(tc)) {
  719. value.Append(tc);
  720. } else if (tc == '%') {
  721. if (!IsHexDigit(c[0]) || !IsHexDigit(c[1])) {
  722. // we expect two more characters
  723. NS_WARNING("broken %-escape in RFC5987-encoded param");
  724. return NS_ERROR_INVALID_ARG;
  725. }
  726. value.Append(tc);
  727. // we consume two more
  728. value.Append(*c++);
  729. value.Append(*c++);
  730. } else {
  731. // character not allowed here
  732. NS_WARNING("invalid character in RFC5987-encoded param");
  733. return NS_ERROR_INVALID_ARG;
  734. }
  735. }
  736. }
  737. }
  738. if (delimiters != 2) {
  739. NS_WARNING("missing delimiters in RFC5987-encoded param");
  740. return NS_ERROR_INVALID_ARG;
  741. }
  742. // abort early for unsupported encodings
  743. if (!charset.LowerCaseEqualsLiteral("utf-8")) {
  744. NS_WARNING("unsupported charset in RFC5987-encoded param");
  745. return NS_ERROR_INVALID_ARG;
  746. }
  747. // percent-decode
  748. if (!PercentDecode(value)) {
  749. return NS_ERROR_OUT_OF_MEMORY;
  750. }
  751. // return the encoding
  752. aLang.Assign(language);
  753. // finally convert octet sequence to UTF-8 and be done
  754. nsresult rv = NS_OK;
  755. nsCOMPtr<nsIUTF8ConverterService> cvtUTF8 =
  756. do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv);
  757. NS_ENSURE_SUCCESS(rv, rv);
  758. nsAutoCString utf8;
  759. rv = cvtUTF8->ConvertStringToUTF8(value, charset.get(), true, false, 1, utf8);
  760. NS_ENSURE_SUCCESS(rv, rv);
  761. CopyUTF8toUTF16(utf8, aResult);
  762. return NS_OK;
  763. }
  764. nsresult
  765. internalDecodeParameter(const nsACString& aParamValue, const char* aCharset,
  766. const char* aDefaultCharset, bool aOverrideCharset,
  767. bool aDecode2047, nsACString& aResult)
  768. {
  769. aResult.Truncate();
  770. // If aCharset is given, aParamValue was obtained from RFC2231/5987
  771. // encoding and we're pretty sure that it's in aCharset.
  772. if (aCharset && *aCharset)
  773. {
  774. nsCOMPtr<nsIUTF8ConverterService> cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
  775. if (cvtUTF8)
  776. return cvtUTF8->ConvertStringToUTF8(aParamValue, aCharset,
  777. true, true, 1, aResult);
  778. }
  779. const nsAFlatCString& param = PromiseFlatCString(aParamValue);
  780. nsAutoCString unQuoted;
  781. nsACString::const_iterator s, e;
  782. param.BeginReading(s);
  783. param.EndReading(e);
  784. // strip '\' when used to quote CR, LF, '"' and '\'
  785. for ( ; s != e; ++s) {
  786. if ((*s == '\\')) {
  787. if (++s == e) {
  788. --s; // '\' is at the end. move back and append '\'.
  789. }
  790. else if (*s != nsCRT::CR && *s != nsCRT::LF && *s != '"' && *s != '\\') {
  791. --s; // '\' is not foll. by CR,LF,'"','\'. move back and append '\'
  792. }
  793. // else : skip '\' and append the quoted character.
  794. }
  795. unQuoted.Append(*s);
  796. }
  797. aResult = unQuoted;
  798. nsresult rv = NS_OK;
  799. if (aDecode2047) {
  800. nsAutoCString decoded;
  801. // Try RFC 2047 encoding, instead.
  802. rv = internalDecodeRFC2047Header(unQuoted.get(), aDefaultCharset,
  803. aOverrideCharset, true, decoded);
  804. if (NS_SUCCEEDED(rv) && !decoded.IsEmpty())
  805. aResult = decoded;
  806. }
  807. return rv;
  808. }
  809. NS_IMETHODIMP
  810. nsMIMEHeaderParamImpl::DecodeParameter(const nsACString& aParamValue,
  811. const char* aCharset,
  812. const char* aDefaultCharset,
  813. bool aOverrideCharset,
  814. nsACString& aResult)
  815. {
  816. return internalDecodeParameter(aParamValue, aCharset, aDefaultCharset,
  817. aOverrideCharset, true, aResult);
  818. }
  819. #define ISHEXCHAR(c) \
  820. ((0x30 <= uint8_t(c) && uint8_t(c) <= 0x39) || \
  821. (0x41 <= uint8_t(c) && uint8_t(c) <= 0x46) || \
  822. (0x61 <= uint8_t(c) && uint8_t(c) <= 0x66))
  823. // Decode Q encoding (RFC 2047).
  824. // static
  825. char *DecodeQ(const char *in, uint32_t length)
  826. {
  827. char *out, *dest = 0;
  828. out = dest = (char *)PR_Calloc(length + 1, sizeof(char));
  829. if (dest == nullptr)
  830. return nullptr;
  831. while (length > 0) {
  832. unsigned c = 0;
  833. switch (*in) {
  834. case '=':
  835. // check if |in| in the form of '=hh' where h is [0-9a-fA-F].
  836. if (length < 3 || !ISHEXCHAR(in[1]) || !ISHEXCHAR(in[2]))
  837. goto badsyntax;
  838. PR_sscanf(in + 1, "%2X", &c);
  839. *out++ = (char) c;
  840. in += 3;
  841. length -= 3;
  842. break;
  843. case '_':
  844. *out++ = ' ';
  845. in++;
  846. length--;
  847. break;
  848. default:
  849. if (*in & 0x80) goto badsyntax;
  850. *out++ = *in++;
  851. length--;
  852. }
  853. }
  854. *out++ = '\0';
  855. for (out = dest; *out ; ++out) {
  856. if (*out == '\t')
  857. *out = ' ';
  858. }
  859. return dest;
  860. badsyntax:
  861. PR_Free(dest);
  862. return nullptr;
  863. }
  864. // check if input is HZ (a 7bit encoding for simplified Chinese : RFC 1842))
  865. // or has ESC which may be an indication that it's in one of many ISO
  866. // 2022 7bit encodings (e.g. ISO-2022-JP(-2)/CN : see RFC 1468, 1922, 1554).
  867. // static
  868. bool Is7bitNonAsciiString(const char *input, uint32_t len)
  869. {
  870. int32_t c;
  871. enum { hz_initial, // No HZ seen yet
  872. hz_escaped, // Inside an HZ ~{ escape sequence
  873. hz_seen, // Have seen at least one complete HZ sequence
  874. hz_notpresent // Have seen something that is not legal HZ
  875. } hz_state;
  876. hz_state = hz_initial;
  877. while (len) {
  878. c = uint8_t(*input++);
  879. len--;
  880. if (c & 0x80) return false;
  881. if (c == 0x1B) return true;
  882. if (c == '~') {
  883. switch (hz_state) {
  884. case hz_initial:
  885. case hz_seen:
  886. if (*input == '{') {
  887. hz_state = hz_escaped;
  888. } else if (*input == '~') {
  889. // ~~ is the HZ encoding of ~. Skip over second ~ as well
  890. hz_state = hz_seen;
  891. input++;
  892. len--;
  893. } else {
  894. hz_state = hz_notpresent;
  895. }
  896. break;
  897. case hz_escaped:
  898. if (*input == '}') hz_state = hz_seen;
  899. break;
  900. default:
  901. break;
  902. }
  903. }
  904. }
  905. return hz_state == hz_seen;
  906. }
  907. #define REPLACEMENT_CHAR "\357\277\275" // EF BF BD (UTF-8 encoding of U+FFFD)
  908. // copy 'raw' sequences of octets in aInput to aOutput.
  909. // If aDefaultCharset is specified, the input is assumed to be in the
  910. // charset and converted to UTF-8. Otherwise, a blind copy is made.
  911. // If aDefaultCharset is specified, but the conversion to UTF-8
  912. // is not successful, each octet is replaced by Unicode replacement
  913. // chars. *aOutput is advanced by the number of output octets.
  914. // static
  915. void CopyRawHeader(const char *aInput, uint32_t aLen,
  916. const char *aDefaultCharset, nsACString &aOutput)
  917. {
  918. int32_t c;
  919. // If aDefaultCharset is not specified, make a blind copy.
  920. if (!aDefaultCharset || !*aDefaultCharset) {
  921. aOutput.Append(aInput, aLen);
  922. return;
  923. }
  924. // Copy as long as it's US-ASCII. An ESC may indicate ISO 2022
  925. // A ~ may indicate it is HZ
  926. while (aLen && (c = uint8_t(*aInput++)) != 0x1B && c != '~' && !(c & 0x80)) {
  927. aOutput.Append(char(c));
  928. aLen--;
  929. }
  930. if (!aLen) {
  931. return;
  932. }
  933. aInput--;
  934. // skip ASCIIness/UTF8ness test if aInput is supected to be a 7bit non-ascii
  935. // string and aDefaultCharset is a 7bit non-ascii charset.
  936. bool skipCheck = (c == 0x1B || c == '~') &&
  937. IS_7BIT_NON_ASCII_CHARSET(aDefaultCharset);
  938. // If not UTF-8, treat as default charset
  939. nsCOMPtr<nsIUTF8ConverterService>
  940. cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));
  941. nsAutoCString utf8Text;
  942. if (cvtUTF8 &&
  943. NS_SUCCEEDED(
  944. cvtUTF8->ConvertStringToUTF8(Substring(aInput, aInput + aLen),
  945. aDefaultCharset, skipCheck, true, 1,
  946. utf8Text))) {
  947. aOutput.Append(utf8Text);
  948. } else { // replace each octet with Unicode replacement char in UTF-8.
  949. for (uint32_t i = 0; i < aLen; i++) {
  950. c = uint8_t(*aInput++);
  951. if (c & 0x80)
  952. aOutput.Append(REPLACEMENT_CHAR);
  953. else
  954. aOutput.Append(char(c));
  955. }
  956. }
  957. }
  958. nsresult DecodeQOrBase64Str(const char *aEncoded, size_t aLen, char aQOrBase64,
  959. const char *aCharset, nsACString &aResult)
  960. {
  961. char *decodedText;
  962. NS_ASSERTION(aQOrBase64 == 'Q' || aQOrBase64 == 'B', "Should be 'Q' or 'B'");
  963. if(aQOrBase64 == 'Q')
  964. decodedText = DecodeQ(aEncoded, aLen);
  965. else if (aQOrBase64 == 'B') {
  966. decodedText = PL_Base64Decode(aEncoded, aLen, nullptr);
  967. } else {
  968. return NS_ERROR_INVALID_ARG;
  969. }
  970. if (!decodedText) {
  971. return NS_ERROR_INVALID_ARG;
  972. }
  973. nsresult rv;
  974. nsCOMPtr<nsIUTF8ConverterService>
  975. cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv));
  976. nsAutoCString utf8Text;
  977. if (NS_SUCCEEDED(rv)) {
  978. // skip ASCIIness/UTF8ness test if aCharset is 7bit non-ascii charset.
  979. rv = cvtUTF8->ConvertStringToUTF8(nsDependentCString(decodedText),
  980. aCharset,
  981. IS_7BIT_NON_ASCII_CHARSET(aCharset),
  982. true, 1, utf8Text);
  983. }
  984. PR_Free(decodedText);
  985. if (NS_FAILED(rv)) {
  986. return rv;
  987. }
  988. aResult.Append(utf8Text);
  989. return NS_OK;
  990. }
  991. static const char especials[] = R"(()<>@,;:\"/[]?.=)";
  992. // |decode_mime_part2_str| taken from comi18n.c
  993. // Decode RFC2047-encoded words in the input and convert the result to UTF-8.
  994. // If aOverrideCharset is true, charset in RFC2047-encoded words is
  995. // ignored and aDefaultCharset is assumed, instead. aDefaultCharset
  996. // is also used to convert raw octets (without RFC 2047 encoding) to UTF-8.
  997. //static
  998. nsresult DecodeRFC2047Str(const char *aHeader, const char *aDefaultCharset,
  999. bool aOverrideCharset, nsACString &aResult)
  1000. {
  1001. const char *p, *q = nullptr, *r;
  1002. const char *begin; // tracking pointer for where we are in the input buffer
  1003. int32_t isLastEncodedWord = 0;
  1004. const char *charsetStart, *charsetEnd;
  1005. nsAutoCString prevCharset, curCharset;
  1006. nsAutoCString encodedText;
  1007. char prevEncoding = '\0', curEncoding;
  1008. nsresult rv;
  1009. begin = aHeader;
  1010. // To avoid buffer realloc, if possible, set capacity in advance. No
  1011. // matter what, more than 3x expansion can never happen for all charsets
  1012. // supported by Mozilla. SCSU/BCSU with the sliding window set to a
  1013. // non-BMP block may be exceptions, but Mozilla does not support them.
  1014. // Neither any known mail/news program use them. Even if there's, we're
  1015. // safe because we don't use a raw *char any more.
  1016. aResult.SetCapacity(3 * strlen(aHeader));
  1017. while ((p = PL_strstr(begin, "=?")) != 0) {
  1018. if (isLastEncodedWord) {
  1019. // See if it's all whitespace.
  1020. for (q = begin; q < p; ++q) {
  1021. if (!PL_strchr(" \t\r\n", *q)) break;
  1022. }
  1023. }
  1024. if (!isLastEncodedWord || q < p) {
  1025. if (!encodedText.IsEmpty()) {
  1026. rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
  1027. prevEncoding, prevCharset.get(), aResult);
  1028. if (NS_FAILED(rv)) {
  1029. aResult.Append(encodedText);
  1030. }
  1031. encodedText.Truncate();
  1032. prevCharset.Truncate();
  1033. prevEncoding = '\0';
  1034. }
  1035. // copy the part before the encoded-word
  1036. CopyRawHeader(begin, p - begin, aDefaultCharset, aResult);
  1037. begin = p;
  1038. }
  1039. p += 2;
  1040. // Get charset info
  1041. charsetStart = p;
  1042. charsetEnd = 0;
  1043. for (q = p; *q != '?'; q++) {
  1044. if (*q <= ' ' || PL_strchr(especials, *q)) {
  1045. goto badsyntax;
  1046. }
  1047. // RFC 2231 section 5
  1048. if (!charsetEnd && *q == '*') {
  1049. charsetEnd = q;
  1050. }
  1051. }
  1052. if (!charsetEnd) {
  1053. charsetEnd = q;
  1054. }
  1055. q++;
  1056. curEncoding = nsCRT::ToUpper(*q);
  1057. if (curEncoding != 'Q' && curEncoding != 'B')
  1058. goto badsyntax;
  1059. if (q[1] != '?')
  1060. goto badsyntax;
  1061. // loop-wise, keep going until we hit "?=". the inner check handles the
  1062. // nul terminator should the string terminate before we hit the right
  1063. // marker. (And the r[1] will never reach beyond the end of the string
  1064. // because *r != '?' is true if r is the nul character.)
  1065. for (r = q + 2; *r != '?' || r[1] != '='; r++) {
  1066. if (*r < ' ') goto badsyntax;
  1067. }
  1068. if (r == q + 2) {
  1069. // it's empty, skip
  1070. begin = r + 2;
  1071. isLastEncodedWord = 1;
  1072. continue;
  1073. }
  1074. curCharset.Assign(charsetStart, charsetEnd - charsetStart);
  1075. // Override charset if requested. Never override labeled UTF-8.
  1076. // Use default charset instead of UNKNOWN-8BIT
  1077. if ((aOverrideCharset && 0 != nsCRT::strcasecmp(curCharset.get(), "UTF-8"))
  1078. || (aDefaultCharset && 0 == nsCRT::strcasecmp(curCharset.get(), "UNKNOWN-8BIT"))
  1079. ) {
  1080. curCharset = aDefaultCharset;
  1081. }
  1082. const char *R;
  1083. R = r;
  1084. if (curEncoding == 'B') {
  1085. // bug 227290. ignore an extraneous '=' at the end.
  1086. // (# of characters in B-encoded part has to be a multiple of 4)
  1087. int32_t n = r - (q + 2);
  1088. R -= (n % 4 == 1 && !PL_strncmp(r - 3, "===", 3)) ? 1 : 0;
  1089. }
  1090. // Bug 493544. Don't decode the encoded text until it ends
  1091. if (R[-1] != '='
  1092. && (prevCharset.IsEmpty()
  1093. || (curCharset == prevCharset && curEncoding == prevEncoding))
  1094. ) {
  1095. encodedText.Append(q + 2, R - (q + 2));
  1096. prevCharset = curCharset;
  1097. prevEncoding = curEncoding;
  1098. begin = r + 2;
  1099. isLastEncodedWord = 1;
  1100. continue;
  1101. }
  1102. bool bDecoded; // If the current line has been decoded.
  1103. bDecoded = false;
  1104. if (!encodedText.IsEmpty()) {
  1105. if (curCharset == prevCharset && curEncoding == prevEncoding) {
  1106. encodedText.Append(q + 2, R - (q + 2));
  1107. bDecoded = true;
  1108. }
  1109. rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
  1110. prevEncoding, prevCharset.get(), aResult);
  1111. if (NS_FAILED(rv)) {
  1112. aResult.Append(encodedText);
  1113. }
  1114. encodedText.Truncate();
  1115. prevCharset.Truncate();
  1116. prevEncoding = '\0';
  1117. }
  1118. if (!bDecoded) {
  1119. rv = DecodeQOrBase64Str(q + 2, R - (q + 2), curEncoding,
  1120. curCharset.get(), aResult);
  1121. if (NS_FAILED(rv)) {
  1122. aResult.Append(encodedText);
  1123. }
  1124. }
  1125. begin = r + 2;
  1126. isLastEncodedWord = 1;
  1127. continue;
  1128. badsyntax:
  1129. if (!encodedText.IsEmpty()) {
  1130. rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
  1131. prevEncoding, prevCharset.get(), aResult);
  1132. if (NS_FAILED(rv)) {
  1133. aResult.Append(encodedText);
  1134. }
  1135. encodedText.Truncate();
  1136. prevCharset.Truncate();
  1137. }
  1138. // copy the part before the encoded-word
  1139. aResult.Append(begin, p - begin);
  1140. begin = p;
  1141. isLastEncodedWord = 0;
  1142. }
  1143. if (!encodedText.IsEmpty()) {
  1144. rv = DecodeQOrBase64Str(encodedText.get(), encodedText.Length(),
  1145. prevEncoding, prevCharset.get(), aResult);
  1146. if (NS_FAILED(rv)) {
  1147. aResult.Append(encodedText);
  1148. }
  1149. }
  1150. // put the tail back
  1151. CopyRawHeader(begin, strlen(begin), aDefaultCharset, aResult);
  1152. nsAutoCString tempStr(aResult);
  1153. tempStr.ReplaceChar('\t', ' ');
  1154. aResult = tempStr;
  1155. return NS_OK;
  1156. }