Bug 669675 - Use Tokenizer in ParseRealm r=necko-reviewers,dragana

We also import the testcases from http://test.greenbytes.de/tech/tc/httpauth/
as unit tests.

This patch adds a network.auth.use_new_parse_realm pref in case this change
causes any regressions.

Depends on D112605

Differential Revision: https://phabricator.services.mozilla.com/D112594
This commit is contained in:
Valentin Gosu 2021-05-26 09:27:21 +00:00
parent 69c6a23516
commit 76cc18727f
4 changed files with 390 additions and 34 deletions

View File

@ -8847,6 +8847,12 @@
value: true
mirror: always
# Whether to use new implementation of ParseReasm
- name: network.auth.use_new_parse_realm
type: RelaxedAtomicBool
value: true
mirror: always
# See the full list of values in nsICookieService.idl.
- name: network.cookie.cookieBehavior
type: RelaxedAtomicInt32

View File

@ -1107,8 +1107,7 @@ void nsHttpChannelAuthProvider::GetIdentityFromURI(uint32_t authFlags,
}
}
void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
nsACString& realm) {
static void OldParseRealm(const nsACString& aChallenge, nsACString& realm) {
//
// From RFC2617 section 1.2, the realm value is defined as such:
//
@ -1138,9 +1137,10 @@ void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
if (*end == '\\') {
// escaped character, store that one instead if not zero
if (!*++end) break;
} else if (*end == '\"')
} else if (*end == '\"') {
// end of string
break;
}
realm.Append(*end);
++end;
@ -1148,10 +1148,107 @@ void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
} else {
// realm given without quotes
end = strchr(p, ' ');
if (end)
if (end) {
realm.Assign(p, end - p);
else
} else {
realm.Assign(p);
}
}
}
}
void nsHttpChannelAuthProvider::ParseRealm(const nsACString& aChallenge,
nsACString& realm) {
//
// From RFC2617 section 1.2, the realm value is defined as such:
//
// realm = "realm" "=" realm-value
// realm-value = quoted-string
//
// but, we'll accept anything after the the "=" up to the first space, or
// end-of-line, if the string is not quoted.
//
if (!StaticPrefs::network_auth_use_new_parse_realm()) {
OldParseRealm(aChallenge, realm);
return;
}
Tokenizer t(aChallenge);
// The challenge begins with the authType.
// If we can't find that something has probably gone wrong.
t.SkipWhites();
nsDependentCSubstring authType;
if (!t.ReadWord(authType)) {
return;
}
// Will return true if the tokenizer advanced the cursor - false otherwise.
auto readParam = [&](nsDependentCSubstring& key, nsAutoCString& value) {
key.Rebind(EmptyCString(), 0);
value.Truncate();
t.SkipWhites();
if (!t.ReadWord(key)) {
return false;
}
t.SkipWhites();
if (!t.CheckChar('=')) {
return true;
}
t.SkipWhites();
Tokenizer::Token token1;
t.Record();
if (!t.Next(token1)) {
return true;
}
nsDependentCSubstring sub;
bool hasQuote = false;
if (token1.Equals(Tokenizer::Token::Char('"'))) {
hasQuote = true;
} else {
t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
value.Append(sub);
}
t.Record();
Tokenizer::Token token2;
while (t.Next(token2)) {
if (hasQuote && token2.Equals(Tokenizer::Token::Char('"')) &&
!token1.Equals(Tokenizer::Token::Char('\\'))) {
break;
}
if (!hasQuote && (token2.Type() == Tokenizer::TokenType::TOKEN_WS ||
token2.Type() == Tokenizer::TokenType::TOKEN_EOL)) {
break;
}
t.Claim(sub, Tokenizer::ClaimInclusion::INCLUDE_LAST);
if (!sub.Equals(R"(\)")) {
value.Append(sub);
}
t.Record();
token1 = token2;
}
return true;
};
while (!t.CheckEOF()) {
nsDependentCSubstring key;
nsAutoCString value;
// If we couldn't read anything, and the input isn't followed by a ,
// then we exit.
if (!readParam(key, value) && !t.Check(Tokenizer::Token::Char(','))) {
break;
}
// When we find the first instance of realm we exit.
// Theoretically there should be only one instance and we should fail
// if there are more, but we're trying to preserve existing behaviour.
if (key.Equals("realm"_ns, nsCaseInsensitiveCStringComparator)) {
realm = value;
break;
}
}
}

View File

@ -218,7 +218,9 @@ Requestor.prototype = {
prompt2: null,
};
function RealmTestRequestor() {}
function RealmTestRequestor(expected) {
this.expectedRealm = expected;
}
RealmTestRequestor.prototype = {
QueryInterface: ChromeUtils.generateQI([
@ -235,7 +237,7 @@ RealmTestRequestor.prototype = {
},
promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
Assert.equal(authInfo.realm, '"foo_bar');
Assert.equal(authInfo.realm, this.expectedRealm);
return false;
},
@ -419,7 +421,7 @@ add_task(async function test_ntlm() {
add_task(async function test_basicrealm() {
var chan = makeChan(URL + "/auth/realm", URL);
chan.notificationCallbacks = new RealmTestRequestor();
chan.notificationCallbacks = new RealmTestRequestor('"foo_bar');
listener.expectedCode = 401; // Unauthorized
await openAndListen(chan);
});
@ -906,3 +908,280 @@ add_task(async function test_large_domain() {
listener.expectedCode = 401; // Unauthorized
await openAndListen(chan);
});
async function add_parse_realm_testcase(testcase) {
httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", testcase.input, false);
let body = "failed";
response.bodyOutputStream.write(body, body.length);
});
let chan = makeChan(URL + "/parse_realm", URL);
chan.notificationCallbacks = new RealmTestRequestor(testcase.realm);
listener.expectedCode = 401;
await openAndListen(chan);
}
add_task(async function simplebasic() {
await add_parse_realm_testcase({
input: `Basic realm="foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasiclf() {
await add_parse_realm_testcase({
input: `Basic\r\n realm="foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicucase() {
await add_parse_realm_testcase({
input: `BASIC REALM="foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasictok() {
await add_parse_realm_testcase({
input: `Basic realm=foo`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasictokbs() {
await add_parse_realm_testcase({
input: `Basic realm=\\f\\o\\o`,
scheme: `Basic`,
realm: `\\foo`,
});
});
add_task(async function simplebasicsq() {
await add_parse_realm_testcase({
input: `Basic realm='foo'`,
scheme: `Basic`,
realm: `'foo'`,
});
});
add_task(async function simplebasicpct() {
await add_parse_realm_testcase({
input: `Basic realm="foo%20bar"`,
scheme: `Basic`,
realm: `foo%20bar`,
});
});
add_task(async function simplebasiccomma() {
await add_parse_realm_testcase({
input: `Basic , realm="foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasiccomma2() {
await add_parse_realm_testcase({
input: `Basic, realm="foo"`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async function simplebasicnorealm() {
await add_parse_realm_testcase({
input: `Basic`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async function simplebasic2realms() {
await add_parse_realm_testcase({
input: `Basic realm="foo", realm="bar"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicwsrealm() {
await add_parse_realm_testcase({
input: `Basic realm = "foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicrealmsqc() {
await add_parse_realm_testcase({
input: `Basic realm="\\f\\o\\o"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicrealmsqc2() {
await add_parse_realm_testcase({
input: `Basic realm="\\"foo\\""`,
scheme: `Basic`,
realm: `"foo"`,
});
});
add_task(async function simplebasicnewparam1() {
await add_parse_realm_testcase({
input: `Basic realm="foo", bar="xyz",, a=b,,,c=d`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicnewparam2() {
await add_parse_realm_testcase({
input: `Basic bar="xyz", realm="foo"`,
scheme: `Basic`,
realm: `foo`,
});
});
add_task(async function simplebasicrealmiso88591() {
await add_parse_realm_testcase({
input: `Basic realm="foo-ä"`,
scheme: `Basic`,
realm: `foo-ä`,
});
});
add_task(async function simplebasicrealmutf8() {
await add_parse_realm_testcase({
input: `Basic realm="foo-ä"`,
scheme: `Basic`,
realm: `foo-ä`,
});
});
add_task(async function simplebasicrealmrfc2047() {
await add_parse_realm_testcase({
input: `Basic realm="=?ISO-8859-1?Q?foo-=E4?="`,
scheme: `Basic`,
realm: `=?ISO-8859-1?Q?foo-=E4?=`,
});
});
add_task(async function multibasicunknown() {
await add_parse_realm_testcase({
input: `Basic realm="basic", Newauth realm="newauth"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function multibasicunknownnoparam() {
await add_parse_realm_testcase({
input: `Basic realm="basic", Newauth`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function multibasicunknown2() {
await add_parse_realm_testcase({
input: `Newauth realm="newauth", Basic realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function multibasicunknown2np() {
await add_parse_realm_testcase({
input: `Newauth, Basic realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function multibasicunknown2mf() {
httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
response.setHeader("WWW-Authenticate", `Newauth realm="newauth"`, false);
response.setHeader("WWW-Authenticate", `Basic realm="basic"`, false);
let body = "failed";
response.bodyOutputStream.write(body, body.length);
});
let chan = makeChan(URL + "/parse_realm", URL);
chan.notificationCallbacks = new RealmTestRequestor("basic");
listener.expectedCode = 401;
await openAndListen(chan);
});
add_task(async function multibasicempty() {
await add_parse_realm_testcase({
input: `,Basic realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function multibasicqs() {
await add_parse_realm_testcase({
input: `Newauth realm="apps", type=1, title="Login to \"apps\"", Basic realm="simple"`,
scheme: `Basic`,
realm: `simple`,
});
});
add_task(async function multidisgscheme() {
await add_parse_realm_testcase({
input: `Newauth realm="Newauth Realm", basic=foo, Basic realm="Basic Realm"`,
scheme: `Basic`,
realm: `Basic Realm`,
});
});
add_task(async function unknown() {
await add_parse_realm_testcase({
input: `Newauth param="value"`,
scheme: `Basic`,
realm: ``,
});
});
add_task(async function parametersnotrequired() {
await add_parse_realm_testcase({ input: `A, B`, scheme: `Basic`, realm: `` });
});
add_task(async function disguisedrealm() {
await add_parse_realm_testcase({
input: `Basic foo="realm=nottherealm", realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function disguisedrealm2() {
await add_parse_realm_testcase({
input: `Basic nottherealm="nottherealm", realm="basic"`,
scheme: `Basic`,
realm: `basic`,
});
});
add_task(async function missingquote() {
await add_parse_realm_testcase({
input: `Basic realm="basic`,
scheme: `Basic`,
realm: `basic`,
});
});

View File

@ -201,32 +201,6 @@ function run_test() {
info.domain = "";
info.username = "";
info.password = "";
// 5: FTP
var uri2 = NetUtil.newURI("ftp://" + host);
var ftpchan = NetUtil.newChannel({
uri: uri2,
loadUsingSystemPrincipal: true,
});
prompt1 = new Prompt1();
prompt1.rv = expectedRV;
prompt1.scheme = "ftp";
wrapper = adapter.createAdapter(prompt1);
var rv = wrapper.promptAuth(ftpchan, 0, info);
Assert.equal(rv, prompt1.rv);
Assert.equal(prompt1.called, CALLED_PROMPTUP);
if (rv) {
Assert.equal(info.domain, "");
Assert.equal(info.username, prompt1.user);
Assert.equal(info.password, prompt1.pw);
}
info.domain = "";
info.username = "";
info.password = "";
}
do_tests(true);
do_tests(false);