Backed out changeset cd5ac132e763

This commit is contained in:
Jason Duell 2012-03-15 23:36:01 -07:00
commit 2f5f7cb248
2 changed files with 178 additions and 393 deletions

View File

@ -169,136 +169,6 @@ void RemoveQuotedStringEscapes(char *src)
*dst = 0;
}
// Support for continuations (RFC 2231, Section 3)
// only a sane number supported
#define MAX_CONTINUATIONS 999
// part of a continuation
class Continuation {
public:
Continuation(const char *aValue, PRUint32 aLength,
bool aNeedsPercentDecoding) {
value = aValue;
length = aLength;
needsPercentDecoding = aNeedsPercentDecoding;
}
Continuation() {
// empty constructor needed for nsTArray
value = 0L;
length = 0;
needsPercentDecoding = false;
}
~Continuation() {}
const char *value;
PRUint32 length;
bool needsPercentDecoding;
};
// combine segments into a single string, returning the allocated string
// (or NULL) while emptying the list
char *combineContinuations(nsTArray<Continuation>& aArray)
{
// Sanity check
if (aArray.Length() == 0)
return NULL;
// Get an upper bound for the length
PRUint32 length = 0;
for (PRUint32 i = 0; i < aArray.Length(); i++) {
length += aArray[i].length;
}
// Allocate
char *result = (char *) nsMemory::Alloc(length + 1);
// Concatenate
if (result) {
*result = '\0';
for (PRUint32 i = 0; i < aArray.Length(); i++) {
Continuation cont = aArray[i];
if (! cont.value) break;
char *c = result + strlen(result);
strncat(result, cont.value, cont.length);
if (cont.needsPercentDecoding) {
nsUnescape(c);
}
}
// return null if empty value
if (*result == '\0') {
nsMemory::Free(result);
result = NULL;
}
} else {
// Handle OOM
NS_WARNING("Out of memory\n");
}
return result;
}
// add a continuation, return false on error if segment already has been seen
bool addContinuation(nsTArray<Continuation>& aArray, PRUint32 aIndex,
const char *aValue, PRUint32 aLength,
bool aNeedsPercentDecoding)
{
if (aIndex < aArray.Length() && aArray[aIndex].value) {
NS_WARNING("duplicate RC2231 continuation segment #\n");
return false;
}
if (aIndex > MAX_CONTINUATIONS) {
NS_WARNING("RC2231 continuation segment # exceeds limit\n");
return false;
}
Continuation cont (aValue, aLength, aNeedsPercentDecoding);
if (aArray.Length() <= aIndex) {
aArray.SetLength(aIndex + 1);
}
aArray[aIndex] = cont;
return true;
}
// parse a segment number; return -1 on error
PRInt32 parseSegmentNumber(const char *aValue, PRInt32 aLen)
{
if (aLen < 1) {
NS_WARNING("segment number missing\n");
return -1;
}
if (aLen > 1 && aValue[0] == '0') {
NS_WARNING("leading '0' not allowed in segment number\n");
return -1;
}
PRInt32 segmentNumber = 0;
for (PRInt32 i = 0; i < aLen; i++) {
if (! (aValue[i] >= '0' && aValue[i] <= '9')) {
NS_WARNING("invalid characters in segment number\n");
return -1;
}
segmentNumber *= 10;
segmentNumber += aValue[i] - '0';
if (segmentNumber > MAX_CONTINUATIONS) {
NS_WARNING("Segment number exceeds sane size\n");
return -1;
}
}
return segmentNumber;
}
// moved almost verbatim from mimehdrs.cpp
// char *
// MimeHeaders_get_parameter (const char *header_value, const char *parm_name,
@ -335,10 +205,6 @@ nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
if (aCharset) *aCharset = nsnull;
if (aLang) *aLang = nsnull;
nsCAutoString charset;
bool acceptContinuations = (aDecoding != RFC_5987_DECODING);
const char *str = aHeaderValue;
// skip leading white space.
@ -389,52 +255,37 @@ nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
// title*1="There is no charset and lang info."
// RFC5987: only A and B
// collect results for the different algorithms (plain filename,
// RFC5987/2231-encoded filename, + continuations) separately and decide
// which to use at the end
char *caseAResult = NULL;
char *caseBResult = NULL;
char *caseCDResult = NULL;
// collect continuation segments
nsTArray<Continuation> segments;
// our copies of the charset parameter, kept separately as they might
// differ for the two formats
nsDependentCSubstring charsetB, charsetCD;
nsDependentCSubstring lang;
PRInt32 paramLen = strlen(aParamName);
while (*str) {
// find name/value
bool haveCaseAValue = false;
PRInt32 nextContinuation = 0; // next value in series, or -1 if error
const char *nameStart = str;
const char *nameEnd = NULL;
while (*str) {
const char *tokenStart = str;
const char *tokenEnd = 0;
const char *valueStart = str;
const char *valueEnd = NULL;
bool isQuotedString = false;
const char *valueEnd = 0;
bool seenEquals = false;
NS_ASSERTION(!nsCRT::IsAsciiSpace(*str), "should be after whitespace.");
// Skip forward to the end of this token.
for (; *str && !nsCRT::IsAsciiSpace(*str) && *str != '=' && *str != ';'; str++)
;
nameEnd = str;
PRInt32 nameLen = nameEnd - nameStart;
tokenEnd = str;
// Skip over whitespace, '=', and whitespace
while (nsCRT::IsAsciiSpace(*str)) ++str;
if (*str++ != '=') {
// don't accept parameters without "="
goto increment_str;
if (*str == '=') {
++str;
seenEquals = true;
}
while (nsCRT::IsAsciiSpace(*str)) ++str;
if (*str != '"') {
bool needUnquote = false;
if (*str != '"')
{
// The value is a token, not a quoted string.
valueStart = str;
for (valueEnd = str;
@ -442,12 +293,16 @@ nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
valueEnd++)
;
str = valueEnd;
} else {
isQuotedString = true;
}
else
{
// The value is a quoted string.
needUnquote = true;
++str;
valueStart = str;
for (valueEnd = str; *valueEnd; ++valueEnd) {
for (valueEnd = str; *valueEnd; ++valueEnd)
{
if (*valueEnd == '\\')
++valueEnd;
else if (*valueEnd == '"')
@ -462,14 +317,16 @@ nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
// See if this is the simplest case (case A above),
// a 'single' line value with no charset and lang.
// If so, copy it and return.
if (nameLen == paramLen &&
!nsCRT::strncasecmp(nameStart, aParamName, paramLen)) {
if (caseAResult) {
// we already have one caseA result, ignore subsequent ones
if (tokenEnd - tokenStart == paramLen &&
seenEquals &&
!nsCRT::strncasecmp(tokenStart, aParamName, paramLen))
{
if (*aResult)
{
// either seen earlier caseA value already--we prefer first--or caseA
// came after a continuation: either way, prefer other value
goto increment_str;
}
// if the parameter spans across multiple lines we have to strip out the
// line continuation -- jht 4/29/98
nsCAutoString tempStr(valueStart, valueEnd - valueStart);
@ -477,131 +334,144 @@ nsMIMEHeaderParamImpl::DoParameterInternal(const char *aHeaderValue,
char *res = ToNewCString(tempStr);
NS_ENSURE_TRUE(res, NS_ERROR_OUT_OF_MEMORY);
if (isQuotedString)
if (needUnquote)
RemoveQuotedStringEscapes(res);
caseAResult = res;
*aResult = res;
haveCaseAValue = true;
// keep going, we may find a RFC 2231/5987 encoded alternative
}
// case B, C, and D
else if (nameLen > paramLen &&
!nsCRT::strncasecmp(nameStart, aParamName, paramLen) &&
*(nameStart + paramLen) == '*') {
// 1st char past '*'
const char *cp = nameStart + paramLen + 1;
// if param name ends in "*" we need do to RFC5987 "ext-value" decoding
bool needExtDecoding = *(nameEnd - 1) == '*';
bool caseB = nameLen == paramLen + 1;
bool caseCStart = (*cp == '0') && needExtDecoding;
// parse the segment number
PRInt32 segmentNumber = -1;
if (!caseB) {
PRInt32 segLen = (nameEnd - cp) - (needExtDecoding ? 1 : 0);
segmentNumber = parseSegmentNumber(cp, segLen);
if (segmentNumber == -1) {
acceptContinuations = false;
goto increment_str;
}
}
else if (tokenEnd - tokenStart > paramLen &&
!nsCRT::strncasecmp(tokenStart, aParamName, paramLen) &&
seenEquals &&
*(tokenStart + paramLen) == '*')
{
const char *cp = tokenStart + paramLen + 1; // 1st char past '*'
bool needUnescape = *(tokenEnd - 1) == '*';
bool caseB = (tokenEnd - tokenStart) == paramLen + 1;
bool caseCorDStart = (*cp == '0') && needUnescape;
bool acceptContinuations = (aDecoding != RFC_5987_DECODING);
// CaseB and start of CaseC: requires charset and optional language
// in quotes (quotes required even if lang is blank)
if (caseB || (caseCStart && acceptContinuations)) {
if (caseB || (caseCorDStart && acceptContinuations))
{
if (caseCorDStart) {
if (nextContinuation++ != 0)
{
// error: already started a continuation. Skip future
// continuations and return whatever initial parts were in order.
nextContinuation = -1;
goto increment_str;
}
}
// look for single quotation mark(')
const char *sQuote1 = PL_strchr(valueStart, 0x27);
const char *sQuote2 = sQuote1 ? PL_strchr(sQuote1 + 1, 0x27) : nsnull;
const char *sQuote2 = (char *) (sQuote1 ? PL_strchr(sQuote1 + 1, 0x27) : nsnull);
// Two single quotation marks must be present even in
// absence of charset and lang.
if (!sQuote1 || !sQuote2) {
if (!sQuote1 || !sQuote2)
NS_WARNING("Mandatory two single quotes are missing in header parameter\n");
if (aCharset && sQuote1 > valueStart && sQuote1 < valueEnd)
{
*aCharset = (char *) nsMemory::Clone(valueStart, sQuote1 - valueStart + 1);
if (*aCharset)
*(*aCharset + (sQuote1 - valueStart)) = 0;
}
if (aLang && sQuote1 && sQuote2 && sQuote2 > sQuote1 + 1 &&
sQuote2 < valueEnd)
{
*aLang = (char *) nsMemory::Clone(sQuote1 + 1, sQuote2 - (sQuote1 + 1) + 1);
if (*aLang)
*(*aLang + (sQuote2 - (sQuote1 + 1))) = 0;
}
const char *charsetStart = NULL;
PRInt32 charsetLength = 0;
const char *langStart = NULL;
PRInt32 langLength = 0;
const char *rawValStart = NULL;
PRInt32 rawValLength = 0;
if (sQuote2 && sQuote1) {
// both delimiters present: charSet'lang'rawVal
rawValStart = sQuote2 + 1;
rawValLength = valueEnd - rawValStart;
langStart = sQuote1 + 1;
langLength = sQuote2 - langStart;
charsetStart = valueStart;
charsetLength = sQuote1 - charsetStart;
// Be generous and handle gracefully when required
// single quotes are absent.
if (sQuote1)
{
if(!sQuote2)
sQuote2 = sQuote1;
}
else if (sQuote1) {
// one delimiter; assume charset'rawVal
rawValStart = sQuote1 + 1;
rawValLength = valueEnd - rawValStart;
else
sQuote2 = valueStart - 1;
charsetStart = valueStart;
charsetLength = sQuote1 - valueStart;
}
else {
// no delimiter: just rawVal
rawValStart = valueStart;
rawValLength = valueEnd - valueStart;
}
if (langLength != 0) {
lang.Assign(langStart, langLength);
}
// keep the charset for later
if (caseB) {
charsetB.Assign(charsetStart, charsetLength);
} else {
// if caseCorD
charsetCD.Assign(charsetStart, charsetLength);
}
// non-empty value part
if (rawValLength > 0) {
if (!caseBResult && caseB) {
// allocate buffer for the raw value
char *tmpResult = (char *) nsMemory::Clone(rawValStart, rawValLength + 1);
if (!tmpResult) {
goto increment_str;
}
*(tmpResult + rawValLength) = 0;
nsUnescape(tmpResult);
caseBResult = tmpResult;
} else {
// caseC
bool added = addContinuation(segments, 0, rawValStart,
rawValLength, needExtDecoding);
if (!added) {
// continuation not added, stop processing them
acceptContinuations = false;
if (sQuote2 && sQuote2 + 1 < valueEnd)
{
if (*aResult)
{
// caseA value already read, or caseC/D value already read
// but we're now reading caseB: either way, drop old value
nsMemory::Free(*aResult);
haveCaseAValue = false;
}
*aResult = (char *) nsMemory::Alloc(valueEnd - (sQuote2 + 1) + 1);
if (*aResult)
{
memcpy(*aResult, sQuote2 + 1, valueEnd - (sQuote2 + 1));
*(*aResult + (valueEnd - (sQuote2 + 1))) = 0;
if (needUnescape)
{
nsUnescape(*aResult);
if (caseB)
return NS_OK; // caseB wins over everything else
}
}
}
} // end of if-block : title*0*= or title*=
// caseD: a line of multiline param with no need for unescaping : title*[0-9]=
// or 2nd or later lines of a caseC param : title*[1-9]*=
else if (acceptContinuations && segmentNumber != -1) {
PRUint32 valueLength = valueEnd - valueStart;
bool added = addContinuation(segments, segmentNumber, valueStart,
valueLength, needExtDecoding);
if (!added) {
// continuation not added, stop processing them
acceptContinuations = false;
else if (acceptContinuations && nsCRT::IsAsciiDigit(PRUnichar(*cp)))
{
PRInt32 nextSegment = atoi(cp);
// no leading zeros allowed except for ... position 0
bool broken = nextSegment > 0 && *cp == '0';
if (broken || nextSegment != nextContinuation++)
{
// error: gap in continuation or unneccessary leading 0.
// Skip future continuations and return whatever initial parts were
// in order.
nextContinuation = -1;
goto increment_str;
}
if (haveCaseAValue && *aResult)
{
// drop caseA value
nsMemory::Free(*aResult);
*aResult = 0;
haveCaseAValue = false;
}
PRInt32 len = 0;
if (*aResult) // 2nd or later lines of multiline parameter
{
len = strlen(*aResult);
char *ns = (char *) nsMemory::Realloc(*aResult, len + (valueEnd - valueStart) + 1);
if (!ns)
{
nsMemory::Free(*aResult);
}
*aResult = ns;
}
else
{
NS_ASSERTION(*cp == '0', "Not first value in continuation"); // must be; 1st line : title*0=
*aResult = (char *) nsMemory::Alloc(valueEnd - valueStart + 1);
}
if (*aResult)
{
// append a partial value
memcpy(*aResult + len, valueStart, valueEnd - valueStart);
*(*aResult + len + (valueEnd - valueStart)) = 0;
if (needUnescape)
nsUnescape(*aResult + len);
}
else
return NS_ERROR_OUT_OF_MEMORY;
} // end of if-block : title*[0-9]= or title*[1-9]*=
}
@ -613,50 +483,10 @@ increment_str:
while (nsCRT::IsAsciiSpace(*str)) ++str;
}
caseCDResult = combineContinuations(segments);
if (caseBResult) {
// prefer simple 5987 format over 2231 with continuations
*aResult = caseBResult;
caseBResult = NULL;
charset.Assign(charsetB);
}
else if (caseCDResult) {
// prefer 2231/5987 with or without continuations over plain format
*aResult = caseCDResult;
caseCDResult = NULL;
charset.Assign(charsetCD);
}
else if (caseAResult) {
*aResult = caseAResult;
caseAResult = NULL;
}
// free unused stuff
nsMemory::Free(caseAResult);
nsMemory::Free(caseBResult);
nsMemory::Free(caseCDResult);
// if we have a result
if (*aResult) {
// then return charset and lang as well
if (aLang && !lang.IsEmpty()) {
PRUint32 len = lang.Length();
*aLang = (char *) nsMemory::Clone(lang.BeginReading(), len + 1);
if (*aLang) {
*(*aLang + len) = 0;
}
}
if (aCharset && !charset.IsEmpty()) {
PRUint32 len = charset.Length();
*aCharset = (char *) nsMemory::Clone(charset.BeginReading(), len + 1);
if (*aCharset) {
*(*aCharset + len) = 0;
}
}
}
return *aResult ? NS_OK : NS_ERROR_INVALID_ARG;
if (*aResult)
return NS_OK;
else
return NS_ERROR_INVALID_ARG; // aParameter not found !!
}

View File

@ -96,16 +96,16 @@ var tests = [
"attachment", Cr.NS_ERROR_INVALID_ARG],
// continuations should prevail over non-extended (unless RFC 5987)
["attachment; filename=basic; filename*0*=UTF-8''multi;\r\n"
+ " filename*1=line;\r\n"
["attachment; filename=basic; filename*0*=UTF-8''multi\r\n"
+ " filename*1=line\r\n"
+ " filename*2*=%20extended",
"attachment", "multiline extended",
"attachment", "basic"],
// Gaps should result in returning only value until gap hit
// (invalid; error recovery)
["attachment; filename=basic; filename*0*=UTF-8''multi;\r\n"
+ " filename*1=line;\r\n"
["attachment; filename=basic; filename*0*=UTF-8''multi\r\n"
+ " filename*1=line\r\n"
+ " filename*3*=%20extended",
"attachment", "multiline",
"attachment", "basic"],
@ -113,60 +113,68 @@ var tests = [
// First series, only please, and don't slurp up higher elements (*2 in this
// case) from later series into earlier one (invalid; error recovery)
["attachment; filename=basic; filename*0*=UTF-8''multi\r\n"
+ " filename*1=line;\r\n"
+ " filename*0*=UTF-8''wrong;\r\n"
+ " filename*1=bad;\r\n"
+ " filename*1=line\r\n"
+ " filename*0*=UTF-8''wrong\r\n"
+ " filename*1=bad\r\n"
+ " filename*2=evil",
"attachment", "multiline",
"attachment", "basic"],
// RFC 2231 not clear on correct outcome: we prefer non-continued extended
// (invalid; error recovery)
["attachment; filename=basic; filename*0=UTF-8''multi\r\n;"
+ " filename*=UTF-8''extended;\r\n"
+ " filename*1=line;\r\n"
["attachment; filename=basic; filename*0=UTF-8''multi\r\n"
+ " filename*=UTF-8''extended\r\n"
+ " filename*1=line\r\n"
+ " filename*2*=%20extended",
"attachment", "extended"],
// sneaky: if unescaped, make sure we leave UTF-8'' in value
["attachment; filename*0=UTF-8''unescaped;\r\n"
["attachment; filename*0=UTF-8''unescaped\r\n"
+ " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
"attachment", "UTF-8''unescaped so includes UTF-8'' in value",
"attachment", Cr.NS_ERROR_INVALID_ARG],
// sneaky: if unescaped, make sure we leave UTF-8'' in value
["attachment; filename=basic; filename*0=UTF-8''unescaped;\r\n"
["attachment; filename=basic; filename*0=UTF-8''unescaped\r\n"
+ " filename*1*=%20so%20includes%20UTF-8''%20in%20value",
"attachment", "UTF-8''unescaped so includes UTF-8'' in value",
"attachment", "basic"],
// Prefer basic over invalid continuation
// (invalid; error recovery)
["attachment; filename=basic; filename*1=multi;\r\n"
+ " filename*2=line;\r\n"
["attachment; filename=basic; filename*1=multi\r\n"
+ " filename*2=line\r\n"
+ " filename*3*=%20extended",
"attachment", "basic"],
// support digits over 10
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
["attachment; filename=basic; filename*0*=UTF-8''0\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a\r\n"
+ " filename*11=b; filename*12=c;filename*13=d;filename*14=e;filename*15=f\r\n",
"attachment", "0123456789abcdef",
"attachment", "basic"],
// support digits over 10 (check ordering)
["attachment; filename=basic; filename*0*=UTF-8''0\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a\r\n"
+ " filename*11=b; filename*12=c;filename*13=d;filename*15=f;filename*14=e\r\n",
"attachment", "0123456789abcd" /* should see the 'f', see bug 588414 */,
"attachment", "basic"],
// support digits over 10 (detect gaps)
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
["attachment; filename=basic; filename*0*=UTF-8''0\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a\r\n"
+ " filename*11=b; filename*12=c;filename*14=e\r\n",
"attachment", "0123456789abc",
"attachment", "basic"],
// return nothing: invalid
// (invalid; error recovery)
["attachment; filename*1=multi;\r\n"
+ " filename*2=line;\r\n"
["attachment; filename*1=multi\r\n"
+ " filename*2=line\r\n"
+ " filename*3*=%20extended",
"attachment", Cr.NS_ERROR_INVALID_ARG],
@ -187,53 +195,6 @@ var tests = [
"filename=foo.html", "foo.html",
"filename=foo.html", "foo.html"],
// Bug 384571: RFC 2231 parameters not decoded when appearing in reversed order
// check ordering
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*1=1; filename*2=2;filename*3=3;filename*4=4;filename*5=5;\r\n"
+ " filename*6=6; filename*7=7;filename*8=8;filename*9=9;filename*10=a;\r\n"
+ " filename*11=b; filename*12=c;filename*13=d;filename*15=f;filename*14=e;\r\n",
"attachment", "0123456789abcdef",
"attachment", "basic"],
// check non-digits in sequence numbers
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*1a=1\r\n",
"attachment", "0",
"attachment", "basic"],
// check duplicate sequence numbers
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*0=bad; filename*1=1\;r\n",
"attachment", "0",
"attachment", "basic"],
// check overflow
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*11111111111111111111111111111111111111111111111111111111111=1",
"attachment", "0",
"attachment", "basic"],
// check underflow
["attachment; filename=basic; filename*0*=UTF-8''0;\r\n"
+ " filename*-1=1",
"attachment", "0",
"attachment", "basic"],
// check mixed token/quoted-string
["attachment; filename=basic; filename*0=\"0\";\r\n"
+ " filename*1=1;\r\n"
+ " filename*2*=%32",
"attachment", "012",
"attachment", "basic"],
// check empty sequence number
["attachment; filename=basic; filename**=UTF-8''0\r\n",
"attachment", "basic",
"attachment", "basic"],
// Bug 419157: ensure that a MIME parameter with no charset information
// fallbacks to Latin-1
@ -340,12 +301,6 @@ var tests = [
["attachment; filename=\"",
"attachment", ""],
// Bug 732369: Content-Disposition parser does not require presence of ";" between params
["attachment; extension=bla filename=foo",
"attachment", "foo"],
];
function do_tests(whichRFC)