Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsSecurityHeaderParser.h"
#include "mozilla/Logging.h"
// The character classes in this file are informed by [RFC2616], Section 2.2.
// signed char is a signed data type one byte (8 bits) wide, so its value can
// never be greater than 127. The following implicitly makes use of this.
// A token is one or more CHAR except CTLs or separators.
// A CHAR is any US-ASCII character (octets 0 - 127).
// A CTL is any US-ASCII control character (octets 0 - 31) and DEL (127).
// A separator is one of ()<>@,;:\"/[]?={} as well as space and
// horizontal-tab (32 and 9, respectively).
// So, this returns true if chr is any octet 33-126 except ()<>@,;:\"/[]?={}
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;
}
// A quoted-string consists of a quote (") followed by any amount of
// qdtext or quoted-pair, followed by a quote.
// qdtext is any TEXT except a quote.
// TEXT is any 8-bit octet except CTLs, but including LWS.
// quoted-pair is a backslash (\) followed by a CHAR.
// So, it turns out, \ can't really be a qdtext symbol for our purposes.
// This returns true if chr is any octet 9,10,13,32-126 except <"> or "\"
bool IsQuotedTextSymbol(signed char chr) {
return ((chr >= 32 && chr != '"' && chr != '\\' && chr != 127) ||
chr == 0x9 || chr == 0xa || chr == 0xd);
}
// The octet following the "\" in a quoted pair can be anything 0-127.
bool IsQuotedPairSymbol(signed char chr) { return (chr >= 0); }
static mozilla::LazyLogModule sSHParserLog("nsSecurityHeaderParser");
#define SHPARSERLOG(args) MOZ_LOG(sSHParserLog, mozilla::LogLevel::Debug, args)
nsSecurityHeaderParser::nsSecurityHeaderParser(const nsCString& aHeader)
: mCursor(aHeader.get()), mDirective(nullptr), mError(false) {}
nsSecurityHeaderParser::~nsSecurityHeaderParser() {
nsSecurityHeaderDirective* directive;
while ((directive = mDirectives.popFirst())) {
delete directive;
}
}
mozilla::LinkedList<nsSecurityHeaderDirective>*
nsSecurityHeaderParser::GetDirectives() {
return &mDirectives;
}
nsresult nsSecurityHeaderParser::Parse() {
MOZ_ASSERT(mDirectives.isEmpty());
SHPARSERLOG(("trying to parse '%s'", mCursor));
Header();
// if we didn't consume the entire input, we were unable to parse it => error
if (mError || *mCursor) {
return NS_ERROR_FAILURE;
} else {
return NS_OK;
}
}
bool nsSecurityHeaderParser::Accept(char aChr) {
if (*mCursor == aChr) {
Advance();
return true;
}
return false;
}
bool nsSecurityHeaderParser::Accept(bool (*aClassifier)(signed char)) {
if (aClassifier(*mCursor)) {
Advance();
return true;
}
return false;
}
void nsSecurityHeaderParser::Expect(char aChr) {
if (*mCursor != aChr) {
mError = true;
} else {
Advance();
}
}
void nsSecurityHeaderParser::Advance() {
// Technically, 0 is valid in quoted-pair, but we were handed a
// null-terminated const char *, so this doesn't handle that.
if (*mCursor) {
mOutput.Append(*mCursor);
mCursor++;
} else {
mError = true;
}
}
void nsSecurityHeaderParser::Header() {
Directive();
while (Accept(';')) {
Directive();
}
}
void nsSecurityHeaderParser::Directive() {
mDirective = new nsSecurityHeaderDirective();
LWSMultiple();
DirectiveName();
LWSMultiple();
if (Accept('=')) {
LWSMultiple();
DirectiveValue();
LWSMultiple();
}
mDirectives.insertBack(mDirective);
if (mDirective->mValue.isSome()) {
SHPARSERLOG(("read directive name '%s', value '%s'",
mDirective->mName.Data(), mDirective->mValue->Data()));
} else {
SHPARSERLOG(
("read valueless directive name '%s'", mDirective->mName.Data()));
}
}
void nsSecurityHeaderParser::DirectiveName() {
mOutput.Truncate(0);
Token();
mDirective->mName.Assign(mOutput);
}
void nsSecurityHeaderParser::DirectiveValue() {
mOutput.Truncate(0);
mDirective->mValue.emplace();
if (Accept(IsTokenSymbol)) {
Token();
mDirective->mValue->Assign(mOutput);
} else if (Accept('"')) {
// Accept advances the cursor if successful, which appends a character to
// mOutput. The " is not part of what we want to capture, so truncate
// mOutput again.
mOutput.Truncate(0);
QuotedString();
mDirective->mValue->Assign(mOutput);
Expect('"');
}
}
void nsSecurityHeaderParser::Token() { while (Accept(IsTokenSymbol)); }
void nsSecurityHeaderParser::QuotedString() {
while (true) {
if (Accept(IsQuotedTextSymbol)) {
QuotedText();
} else if (Accept('\\')) {
QuotedPair();
} else {
break;
}
}
}
void nsSecurityHeaderParser::QuotedText() {
while (Accept(IsQuotedTextSymbol));
}
void nsSecurityHeaderParser::QuotedPair() { Accept(IsQuotedPairSymbol); }
void nsSecurityHeaderParser::LWSMultiple() {
while (true) {
if (Accept('\r')) {
LWSCRLF();
} else if (Accept(' ') || Accept('\t')) {
LWS();
} else {
break;
}
}
}
void nsSecurityHeaderParser::LWSCRLF() {
Expect('\n');
if (!(Accept(' ') || Accept('\t'))) {
mError = true;
}
LWS();
}
void nsSecurityHeaderParser::LWS() {
// Note that becaue of how we're called, we don't have to check for
// the mandatory presense of at least one of SP or HT.
while (Accept(' ') || Accept('\t'));
}