tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsMIMEHeaderParamImpl.cpp (42661B)


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