Bug 1413999 - Part1: Parse Serve-Timing header r=dragana

1. Add a helper function Tokenize() which is able to handle quoted-string and quoted-pair.
2. Modify ParsedHeaderValueListList
 - Use Tokenize() to split string by ','.
3. Modify ParsedHeaderValueList
 - Use Tokenize() to split string by ';'.
 - Function ParseNameAndValue() is for getting name and value from the string containing '='.
4. Handle backslash escapes for quoted string.
5. Reuse ParsedHeaderValueListList to parse server-timing header.

--HG--
extra : rebase_source : 7802a1ae9a6410c4ea992b1197018c5a3f994fa0
This commit is contained in:
Kershaw Chang 2018-01-03 02:04:00 +02:00
Родитель 511d112e2d
Коммит 39f697a80b
2 изменённых файлов: 229 добавлений и 112 удалений

Просмотреть файл

@ -19,6 +19,7 @@
#include "nsICacheEntry.h"
#include "nsIRequest.h"
#include <errno.h>
#include <functional>
namespace mozilla {
namespace net {
@ -603,127 +604,235 @@ void EnsureBuffer(UniquePtr<uint8_t[]> &buf, uint32_t newSize,
{
localEnsureBuffer<uint8_t> (buf, newSize, preserve, objSize);
}
///
static bool
IsTokenSymbol(signed char chr) {
if (chr < 33 || chr == 127 ||
chr == '(' || chr == ')' || chr == '<' || chr == '>' ||
chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
chr == '"' || chr == '/' || chr == '[' || chr == ']' ||
chr == '?' || chr == '=' || chr == '{' || chr == '}' || chr == '\\') {
return false;
}
return true;
}
ParsedHeaderPair::ParsedHeaderPair(const char *name, int32_t nameLen,
const char *val, int32_t valLen,
bool isQuotedValue)
: mName(nsDependentCSubstring(nullptr, 0u))
, mValue(nsDependentCSubstring(nullptr, 0u))
, mIsQuotedValue(isQuotedValue)
{
if (nameLen > 0) {
mName.Rebind(name, name + nameLen);
}
if (valLen > 0) {
if (mIsQuotedValue) {
RemoveQuotedStringEscapes(val, valLen);
mValue.Rebind(mUnquotedValue.BeginWriting(), mUnquotedValue.Length());
} else {
mValue.Rebind(val, val + valLen);
}
}
}
void
ParsedHeaderValueList::Tokenize(char *input, uint32_t inputLen, char **token,
uint32_t *tokenLen, bool *foundEquals, char **next)
ParsedHeaderPair::RemoveQuotedStringEscapes(const char *val, int32_t valLen)
{
if (foundEquals) {
*foundEquals = false;
}
if (next) {
*next = nullptr;
}
if (inputLen < 1 || !input || !token) {
return;
}
bool foundFirst = false;
bool inQuote = false;
bool foundToken = false;
*token = input;
*tokenLen = inputLen;
for (uint32_t index = 0; !foundToken && index < inputLen; ++index) {
// strip leading cruft
if (!foundFirst &&
(input[index] == ' ' || input[index] == '"' || input[index] == '\t')) {
(*token)++;
} else {
foundFirst = true;
mUnquotedValue.Truncate();
const char *c = val;
for (int32_t i = 0; i < valLen; ++i) {
if (c[i] == '\\' && c[i + 1]) {
++i;
}
mUnquotedValue.Append(c[i]);
}
}
static
void Tokenize(const char *input, uint32_t inputLen, const char token,
const std::function<void(const char *, uint32_t)>& consumer)
{
auto trimWhitespace =
[] (const char *in, uint32_t inLen, const char **out, uint32_t *outLen) {
*out = in;
*outLen = inLen;
if (inLen == 0) {
return;
}
// Trim leading space
while (nsCRT::IsAsciiSpace(**out)) {
(*out)++;
--(*outLen);
}
// Trim tailing space
for (const char *i = *out + *outLen - 1; i >= *out; --i) {
if (!nsCRT::IsAsciiSpace(*i)) {
break;
}
--(*outLen);
}
};
const char *first = input;
bool inQuote = false;
const char *result = nullptr;
uint32_t resultLen = 0;
for (uint32_t index = 0; index < inputLen; ++index) {
if (inQuote && input[index] == '\\' && input[index + 1]) {
index++;
continue;
}
if (input[index] == '"') {
inQuote = !inQuote;
continue;
}
if (inQuote) {
continue;
}
if (input[index] == '=' || input[index] == ';') {
*tokenLen = (input + index) - *token;
if (next && ((index + 1) < inputLen)) {
*next = input + index + 1;
}
foundToken = true;
if (foundEquals && input[index] == '=') {
*foundEquals = true;
}
break;
if (input[index] == token) {
trimWhitespace(first, (input + index) - first,
&result, &resultLen);
consumer(result, resultLen);
first = input + index + 1;
}
}
if (!foundToken) {
*tokenLen = (input + inputLen) - *token;
}
// strip trailing cruft
for (char *index = *token + *tokenLen - 1; index >= *token; --index) {
if (*index != ' ' && *index != '\t' && *index != '"') {
break;
}
--(*tokenLen);
if (*index == '"') {
break;
}
}
trimWhitespace(first, (input + inputLen) - first,
&result, &resultLen);
consumer(result, resultLen);
}
ParsedHeaderValueList::ParsedHeaderValueList(char *t, uint32_t len)
ParsedHeaderValueList::ParsedHeaderValueList(const char *t,
uint32_t len,
bool allowInvalidValue)
{
char *name = nullptr;
uint32_t nameLen = 0;
char *value = nullptr;
uint32_t valueLen = 0;
char *next = nullptr;
bool foundEquals;
while (t) {
Tokenize(t, len, &name, &nameLen, &foundEquals, &next);
if (next) {
len -= next - t;
}
t = next;
if (foundEquals && t) {
Tokenize(t, len, &value, &valueLen, nullptr, &next);
if (next) {
len -= next - t;
}
t = next;
}
mValues.AppendElement(ParsedHeaderPair(name, nameLen, value, valueLen));
value = name = nullptr;
valueLen = nameLen = 0;
next = nullptr;
if (!len) {
return;
}
ParsedHeaderValueList *self = this;
auto consumer = [=] (const char *output, uint32_t outputLength) {
self->ParseNameAndValue(output, allowInvalidValue);
};
Tokenize(t, len, ';', consumer);
}
ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader)
void
ParsedHeaderValueList::ParseNameAndValue(const char *input, bool allowInvalidValue)
{
const char *nameStart = input;
const char *nameEnd = nullptr;
const char *valueStart = input;
const char *valueEnd = nullptr;
bool isQuotedString = false;
bool invalidValue = false;
for (; *input && *input != ';' && *input != ',' &&
!nsCRT::IsAsciiSpace(*input) && *input != '='; input++)
;
nameEnd = input;
if (!(nameEnd - nameStart)) {
return;
}
// Check whether param name is a valid token.
for (const char *c = nameStart; c < nameEnd; c++) {
if (!IsTokenSymbol(*c)) {
nameEnd = c;
break;
}
}
if (!(nameEnd - nameStart)) {
return;
}
while (nsCRT::IsAsciiSpace(*input)) {
++input;
}
if (!*input || *input++ != '=') {
mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
nullptr, 0, false));
return;
}
while (nsCRT::IsAsciiSpace(*input)) {
++input;
}
if (*input != '"') {
// The value is a token, not a quoted string.
valueStart = input;
for (valueEnd = input;
*valueEnd && !nsCRT::IsAsciiSpace (*valueEnd) &&
*valueEnd != ';' && *valueEnd != ',';
valueEnd++)
;
input = valueEnd;
if (!allowInvalidValue) {
for (const char *c = valueStart; c < valueEnd; c++) {
if (!IsTokenSymbol(*c)) {
valueEnd = c;
break;
}
}
}
} else {
bool foundQuotedEnd = false;
isQuotedString = true;
++input;
valueStart = input;
for (valueEnd = input; *valueEnd; ++valueEnd) {
if (*valueEnd == '\\' && *(valueEnd + 1)) {
++valueEnd;
}
else if (*valueEnd == '"') {
foundQuotedEnd = true;
break;
}
}
if (!foundQuotedEnd) {
invalidValue = true;
}
input = valueEnd;
// *valueEnd != null means that *valueEnd is quote character.
if (*valueEnd) {
input++;
}
}
if (invalidValue) {
valueEnd = valueStart;
}
mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
valueStart, valueEnd - valueStart,
isQuotedString));
}
ParsedHeaderValueListList::ParsedHeaderValueListList(const nsCString &fullHeader,
bool allowInvalidValue)
: mFull(fullHeader)
{
char *t = mFull.BeginWriting();
uint32_t len = mFull.Length();
char *last = t;
bool inQuote = false;
for (uint32_t index = 0; index < len; ++index) {
if (t[index] == '"') {
inQuote = !inQuote;
continue;
}
if (inQuote) {
continue;
}
if (t[index] == ',') {
mValues.AppendElement(ParsedHeaderValueList(last, (t + index) - last));
last = t + index + 1;
}
}
if (!inQuote) {
mValues.AppendElement(ParsedHeaderValueList(last, (t + len) - last));
}
auto &values = mValues;
auto consumer =
[&values, allowInvalidValue] (const char *output, uint32_t outputLength) {
values.AppendElement(ParsedHeaderValueList(output,
outputLength,
allowInvalidValue));
};
Tokenize(mFull.BeginReading(), mFull.Length(), ',', consumer);
}
} // namespace net

Просмотреть файл

@ -266,42 +266,50 @@ class ParsedHeaderPair
{
public:
ParsedHeaderPair(const char *name, int32_t nameLen,
const char *val, int32_t valLen)
{
if (nameLen > 0) {
mName.Rebind(name, name + nameLen);
}
if (valLen > 0) {
mValue.Rebind(val, val + valLen);
}
}
const char *val, int32_t valLen, bool isQuotedValue);
ParsedHeaderPair(ParsedHeaderPair const &copy)
: mName(copy.mName)
, mValue(copy.mValue)
, mUnquotedValue(copy.mUnquotedValue)
, mIsQuotedValue(copy.mIsQuotedValue)
{
if (mIsQuotedValue) {
mValue.Rebind(mUnquotedValue.BeginReading(), mUnquotedValue.Length());
}
}
nsDependentCSubstring mName;
nsDependentCSubstring mValue;
private:
nsCString mUnquotedValue;
bool mIsQuotedValue;
void RemoveQuotedStringEscapes(const char *val, int32_t valLen);
};
class ParsedHeaderValueList
{
public:
ParsedHeaderValueList(char *t, uint32_t len);
ParsedHeaderValueList(const char *t, uint32_t len, bool allowInvalidValue);
nsTArray<ParsedHeaderPair> mValues;
private:
void ParsePair(char *t, uint32_t len);
void Tokenize(char *input, uint32_t inputLen, char **token,
uint32_t *tokenLen, bool *foundEquals, char **next);
void ParseNameAndValue(const char *input, bool allowInvalidValue);
};
class ParsedHeaderValueListList
{
public:
explicit ParsedHeaderValueListList(const nsCString &txt);
// RFC 7231 section 3.2.6 defines the syntax of the header field values.
// |allowInvalidValue| indicates whether the rule will be used to check
// the input text.
// Note that ParsedHeaderValueListList is currently used to parse
// Alt-Svc and Server-Timing header. |allowInvalidValue| is set to true
// when parsing Alt-Svc for historical reasons.
explicit ParsedHeaderValueListList(const nsCString &txt,
bool allowInvalidValue = true);
nsTArray<ParsedHeaderValueList> mValues;
private: