1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-17 08:42:25 +03:00

Fix challenge parsing to permit incorrectly ordered authentication headers.

Authentication headers with wrongly ordered parameters not following RFC 2617 were not recognized.

Closes: https://github.com/square/okhttp/issues/2780
This commit is contained in:
Lukas Aichbauer
2016-10-17 13:31:59 +02:00
committed by jwilson
parent 7de5351e9a
commit bb357db283
2 changed files with 120 additions and 31 deletions

View File

@@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.internal.Internal;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.http2.Header;
import okhttp3.internal.http2.Http2Codec;
import org.junit.Test;
@@ -339,4 +340,97 @@ public final class HeadersTest {
.build();
assertEquals("A: a\nB: bb\n", headers.toString());
}
/** See https://github.com/square/okhttp/issues/2780. */
@Test public void testDigestChallenges() {
// Strict RFC 2617 header.
Headers headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest realm=\"myrealm\", nonce=\"fjalskdflwejrlaskdfjlaskdjflaks"
+ "jdflkasdf\", qop=\"auth\", stale=\"FALSE\"")
.build();
List<Challenge> challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("Digest", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Not strict RFC 2617 header.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest qop=\"auth\", realm=\"myrealm\", nonce=\"fjalskdflwejrlask"
+ "dfjlaskdjflaksjdflkasdf\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("Digest", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Not strict RFC 2617 header #2.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest qop=\"auth\", nonce=\"fjalskdflwejrlaskdfjlaskdjflaksjdflk"
+ "asdf\", realm=\"myrealm\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("Digest", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Wrong header.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest qop=\"auth\", underrealm=\"myrealm\", nonce=\"fjalskdflwej"
+ "rlaskdfjlaskdjflaksjdflkasdf\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(0, challenges.size());
// Not strict RFC 2617 header with some spaces.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest qop=\"auth\", realm=\"myrealm\", nonce=\"fjalskdflwejrl"
+ "askdfjlaskdjflaksjdflkasdf\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("Digest", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Strict RFC 2617 header with some spaces.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest realm=\"myrealm\", nonce=\"fjalskdflwejrlaskdfjlaskdjfl"
+ "aksjdflkasdf\", qop=\"auth\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("Digest", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Not strict RFC 2617 camelcased.
headers = new Headers.Builder()
.add("WWW-Authenticate", "DiGeSt qop=\"auth\", rEaLm=\"myrealm\", nonce=\"fjalskdflwejrlask"
+ "dfjlaskdjflaksjdflkasdf\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("DiGeSt", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Strict RFC 2617 camelcased.
headers = new Headers.Builder()
.add("WWW-Authenticate", "DIgEsT rEaLm=\"myrealm\", nonce=\"fjalskdflwejrlaskdfjlaskdjflaks"
+ "jdflkasdf\", qop=\"auth\", stale=\"FALSE\"")
.build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(1, challenges.size());
assertEquals("DIgEsT", challenges.get(0).scheme());
assertEquals("myrealm", challenges.get(0).realm());
// Unquoted.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest realm=myrealm").build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(0, challenges.size());
// Scheme only.
headers = new Headers.Builder()
.add("WWW-Authenticate", "Digest").build();
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
assertEquals(0, challenges.size());
}
}

View File

@@ -20,6 +20,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Challenge;
import okhttp3.Cookie;
import okhttp3.CookieJar;
@@ -35,6 +37,11 @@ import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE;
/** Headers and utilities for internal use by OkHttp. */
public final class HttpHeaders {
private static final String TOKEN = "([^ \"=]*)";
private static final String QUOTED_STRING = "\"([^\"]*)\"";
private static final Pattern PARAMETER
= Pattern.compile(" +" + TOKEN + "=(:?" + QUOTED_STRING + "|" + TOKEN + ") *(:?,|$)");
private HttpHeaders() {
}
@@ -135,47 +142,35 @@ public final class HttpHeaders {
return result.build();
}
/** Parse RFC 2617 challenges. This API is only interested in the scheme name and realm. */
/**
* Parse RFC 2617 challenges, also wrong ordered ones.
* This API is only interested in the scheme name and realm.
*/
public static List<Challenge> parseChallenges(Headers responseHeaders, String challengeHeader) {
// auth-scheme = token
// auth-param = token "=" ( token | quoted-string )
// challenge = auth-scheme 1*SP 1#auth-param
// realm = "realm" "=" realm-value
// realm-value = quoted-string
List<Challenge> result = new ArrayList<>();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
if (!challengeHeader.equalsIgnoreCase(responseHeaders.name(i))) {
continue;
}
String value = responseHeaders.value(i);
int pos = 0;
while (pos < value.length()) {
int tokenStart = pos;
pos = skipUntil(value, pos, " ");
List<Challenge> challenges = new ArrayList<>();
List<String> authenticationHeaders = responseHeaders.values(challengeHeader);
for (String header : authenticationHeaders) {
int index = header.indexOf(' ');
if (index == -1) continue;
String scheme = value.substring(tokenStart, pos).trim();
pos = skipWhitespace(value, pos);
// TODO: This currently only handles schemes with a 'realm' parameter;
// It needs to be fixed to handle any scheme and any parameters
// http://code.google.com/p/android/issues/detail?id=11140
if (!value.regionMatches(true, pos, "realm=\"", 0, "realm=\"".length())) {
break; // Unexpected challenge parameter; give up!
Matcher matcher = PARAMETER.matcher(header);
for (int i = index; matcher.find(i); i = matcher.end()) {
if (header.regionMatches(true, matcher.start(1), "realm", 0, 5)) {
String scheme = header.substring(0, index);
String realm = matcher.group(3);
if (realm != null) {
challenges.add(new Challenge(scheme, realm));
break;
}
}
pos += "realm=\"".length();
int realmStart = pos;
pos = skipUntil(value, pos, "\"");
String realm = value.substring(realmStart, pos);
pos++; // Consume '"' close quote.
pos = skipUntil(value, pos, ",");
pos++; // Consume ',' comma.
pos = skipWhitespace(value, pos);
result.add(new Challenge(scheme, realm));
}
}
return result;
return challenges;
}
public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {