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