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:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user