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