Skip to content

Commit b3ecb0a

Browse files
erikghonyansquarejesse
authored andcommitted
Add charset support for RFC 7617 challenges
1 parent abe5d62 commit b3ecb0a

File tree

7 files changed

+118
-14
lines changed

7 files changed

+118
-14
lines changed

okhttp-tests/src/test/java/okhttp3/HeadersTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import okhttp3.internal.Internal;
25+
import okhttp3.internal.Util;
2526
import okhttp3.internal.http.HttpHeaders;
2627
import okhttp3.internal.http2.Header;
2728
import okhttp3.internal.http2.Http2Codec;
@@ -434,4 +435,27 @@ public final class HeadersTest {
434435
challenges = HttpHeaders.parseChallenges(headers, "WWW-Authenticate");
435436
assertEquals(0, challenges.size());
436437
}
438+
439+
@Test public void basicChallenge() {
440+
Headers headers = new Headers.Builder()
441+
.add("WWW-Authenticate: Basic realm=\"protected area\"")
442+
.build();
443+
assertEquals(Arrays.asList(new Challenge("Basic", "protected area")),
444+
HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
445+
}
446+
447+
@Test public void basicChallengeWithCharset() {
448+
Headers headers = new Headers.Builder()
449+
.add("WWW-Authenticate: Basic realm=\"protected area\", charset=\"UTF-8\"")
450+
.build();
451+
assertEquals(Arrays.asList(new Challenge("Basic", "protected area").withCharset(Util.UTF_8)),
452+
HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
453+
}
454+
455+
@Test public void basicChallengeWithUnexpectedCharset() {
456+
Headers headers = new Headers.Builder()
457+
.add("WWW-Authenticate: Basic realm=\"protected area\", charset=\"US-ASCII\"")
458+
.build();
459+
assertEquals(Collections.emptyList(), HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
460+
}
437461
}

okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.net.HttpRetryException;
2525
import java.net.HttpURLConnection;
2626
import java.net.InetAddress;
27+
import java.net.PasswordAuthentication;
2728
import java.net.ProtocolException;
2829
import java.net.Proxy;
2930
import java.net.ProxySelector;
@@ -42,7 +43,6 @@
4243
import java.util.Arrays;
4344
import java.util.Collections;
4445
import java.util.EnumSet;
45-
import java.util.HashSet;
4646
import java.util.LinkedHashSet;
4747
import java.util.List;
4848
import java.util.Map;
@@ -58,7 +58,6 @@
5858
import javax.net.ssl.SSLException;
5959
import javax.net.ssl.SSLHandshakeException;
6060
import javax.net.ssl.SSLProtocolException;
61-
import javax.net.ssl.SSLSocket;
6261
import javax.net.ssl.SSLSocketFactory;
6362
import javax.net.ssl.TrustManager;
6463
import javax.net.ssl.TrustManagerFactory;
@@ -1930,6 +1929,37 @@ enum StreamingMode {
19301929
}
19311930
}
19321931

1932+
@Test public void authenticateWithCharset() throws Exception {
1933+
server.enqueue(new MockResponse().setResponseCode(401)
1934+
.addHeader("WWW-Authenticate: Basic realm=\"protected area\", charset=\"UTF-8\"")
1935+
.setBody("Please authenticate with UTF-8."));
1936+
server.enqueue(new MockResponse().setResponseCode(401)
1937+
.addHeader("WWW-Authenticate: Basic realm=\"protected area\"")
1938+
.setBody("Please authenticate with ISO-8859-1."));
1939+
server.enqueue(new MockResponse()
1940+
.setBody("Successful auth!"));
1941+
1942+
Authenticator.setDefault(new RecordingAuthenticator(
1943+
new PasswordAuthentication("username", "mötorhead".toCharArray())));
1944+
urlFactory.setClient(urlFactory.client().newBuilder()
1945+
.authenticator(new JavaNetAuthenticator())
1946+
.build());
1947+
connection = urlFactory.open(server.url("/").url());
1948+
assertEquals("Successful auth!", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
1949+
1950+
// No authorization header for the first request...
1951+
RecordedRequest request1 = server.takeRequest();
1952+
assertNull(request1.getHeader("Authorization"));
1953+
1954+
// UTF-8 encoding for the first credential.
1955+
RecordedRequest request2 = server.takeRequest();
1956+
assertEquals("Basic dXNlcm5hbWU6bcO2dG9yaGVhZA==", request2.getHeader("Authorization"));
1957+
1958+
// ISO-8859-1 encoding for the second credential.
1959+
RecordedRequest request3 = server.takeRequest();
1960+
assertEquals("Basic dXNlcm5hbWU6bfZ0b3JoZWFk", request3.getHeader("Authorization"));
1961+
}
1962+
19331963
/** https://code.google.com/p/android/issues/detail?id=74026 */
19341964
@Test public void authenticateWithGetAndTransparentGzip() throws Exception {
19351965
MockResponse pleaseAuthenticate = new MockResponse().setResponseCode(401)

okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ public final class JavaNetAuthenticator implements Authenticator {
5454
}
5555

5656
if (auth != null) {
57-
String credential = Credentials.basic(auth.getUserName(), new String(auth.getPassword()));
57+
String credential = Credentials.basic(
58+
auth.getUserName(), new String(auth.getPassword()), challenge.charset());
5859
return request.newBuilder()
5960
.header(proxyAuthorization ? "Proxy-Authorization" : "Authorization", credential)
6061
.build();

okhttp/src/main/java/okhttp3/Challenge.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,33 @@
1515
*/
1616
package okhttp3;
1717

18+
import java.nio.charset.Charset;
1819
import javax.annotation.Nullable;
1920

20-
/** An RFC 2617 challenge. */
21+
import static okhttp3.internal.Util.ISO_8859_1;
22+
23+
/** An RFC 7617 challenge. */
2124
public final class Challenge {
2225
private final String scheme;
2326
private final String realm;
27+
private final Charset charset;
2428

2529
public Challenge(String scheme, String realm) {
30+
this(scheme, realm, ISO_8859_1);
31+
}
32+
33+
private Challenge(String scheme, String realm, Charset charset) {
2634
if (scheme == null) throw new NullPointerException("scheme == null");
2735
if (realm == null) throw new NullPointerException("realm == null");
36+
if (charset == null) throw new NullPointerException("charset == null");
2837
this.scheme = scheme;
2938
this.realm = realm;
39+
this.charset = charset;
40+
}
41+
42+
/** Returns a copy of this charset that expects a credential encoded with {@code charset}. */
43+
public Challenge withCharset(Charset charset) {
44+
return new Challenge(scheme, realm, charset);
3045
}
3146

3247
/** Returns the authentication scheme, like {@code Basic}. */
@@ -39,20 +54,29 @@ public String realm() {
3954
return realm;
4055
}
4156

57+
/** Returns the charset that should be used to encode the credential. */
58+
public Charset charset() {
59+
return charset;
60+
}
61+
4262
@Override public boolean equals(@Nullable Object other) {
4363
return other instanceof Challenge
4464
&& ((Challenge) other).scheme.equals(scheme)
45-
&& ((Challenge) other).realm.equals(realm);
65+
&& ((Challenge) other).realm.equals(realm)
66+
&& ((Challenge) other).charset.equals(charset);
4667
}
4768

4869
@Override public int hashCode() {
4970
int result = 29;
5071
result = 31 * result + realm.hashCode();
5172
result = 31 * result + scheme.hashCode();
73+
result = 31 * result + charset.hashCode();
5274
return result;
5375
}
5476

5577
@Override public String toString() {
56-
return scheme + " realm=\"" + realm + "\"";
78+
return scheme
79+
+ " realm=\"" + realm + "\""
80+
+ " charset=\"" + charset + "\"";
5781
}
5882
}

okhttp/src/main/java/okhttp3/Credentials.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
import java.nio.charset.Charset;
1919
import okio.ByteString;
2020

21+
import static okhttp3.internal.Util.ISO_8859_1;
22+
2123
/** Factory for HTTP authorization credentials. */
2224
public final class Credentials {
2325
private Credentials() {
2426
}
2527

2628
/** Returns an auth credential for the Basic scheme. */
2729
public static String basic(String userName, String password) {
28-
return basic(userName, password, Charset.forName("ISO-8859-1"));
30+
return basic(userName, password, ISO_8859_1);
2931
}
3032

3133
public static String basic(String userName, String password, Charset charset) {

okhttp/src/main/java/okhttp3/internal/Util.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public final class Util {
5858
private static final ByteString UTF_32_LE_BOM = ByteString.decodeHex("ffff0000");
5959

6060
public static final Charset UTF_8 = Charset.forName("UTF-8");
61+
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
6162
private static final Charset UTF_16_BE = Charset.forName("UTF-16BE");
6263
private static final Charset UTF_16_LE = Charset.forName("UTF-16LE");
6364
private static final Charset UTF_32_BE = Charset.forName("UTF-32BE");

okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import okhttp3.HttpUrl;
3030
import okhttp3.Request;
3131
import okhttp3.Response;
32+
import okhttp3.internal.Util;
3233

3334
import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
3435
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
@@ -143,7 +144,7 @@ public static Headers varyHeaders(Headers requestHeaders, Headers responseHeader
143144
}
144145

145146
/**
146-
* Parse RFC 2617 challenges, also wrong ordered ones.
147+
* Parse RFC 7617 challenges, also wrong ordered ones.
147148
* This API is only interested in the scheme name and realm.
148149
*/
149150
public static List<Challenge> parseChallenges(Headers responseHeaders, String challengeHeader) {
@@ -158,17 +159,38 @@ public static List<Challenge> parseChallenges(Headers responseHeaders, String ch
158159
int index = header.indexOf(' ');
159160
if (index == -1) continue;
160161

162+
String scheme = header.substring(0, index);
163+
String realm = null;
164+
String charset = null;
165+
161166
Matcher matcher = PARAMETER.matcher(header);
162167
for (int i = index; matcher.find(i); i = matcher.end()) {
163168
if (header.regionMatches(true, matcher.start(1), "realm", 0, 5)) {
164-
String scheme = header.substring(0, index);
165-
String realm = matcher.group(3);
166-
if (realm != null) {
167-
challenges.add(new Challenge(scheme, realm));
168-
break;
169-
}
169+
realm = matcher.group(3);
170+
} else if (header.regionMatches(true, matcher.start(1), "charset", 0, 7)) {
171+
charset = matcher.group(3);
172+
}
173+
174+
if (realm != null && charset != null) {
175+
break;
170176
}
171177
}
178+
179+
// "realm" is required.
180+
if (realm == null) continue;
181+
182+
Challenge challenge = new Challenge(scheme, realm);
183+
184+
// If a charset is provided, RFC 7617 says it must be "UTF-8".
185+
if (charset != null) {
186+
if (charset.equalsIgnoreCase("UTF-8")) {
187+
challenge = challenge.withCharset(Util.UTF_8);
188+
} else {
189+
continue;
190+
}
191+
}
192+
193+
challenges.add(challenge);
172194
}
173195
return challenges;
174196
}

0 commit comments

Comments
 (0)