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