8199849: Add support for UTF-8 encoded credentials in HTTP Basic Authentication
Reviewed-by: chegar, dfuchs
This commit is contained in:
parent
f2e17b7658
commit
e3b6b7f842
@ -29,11 +29,17 @@ import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import sun.net.www.HeaderParser;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
|
||||
/**
|
||||
* BasicAuthentication: Encapsulate an http server authentication using
|
||||
@ -49,37 +55,18 @@ class BasicAuthentication extends AuthenticationInfo {
|
||||
|
||||
/** The authentication string for this host, port, and realm. This is
|
||||
a simple BASE64 encoding of "login:password". */
|
||||
String auth;
|
||||
final String auth;
|
||||
|
||||
/**
|
||||
* Create a BasicAuthentication
|
||||
*/
|
||||
public BasicAuthentication(boolean isProxy, String host, int port,
|
||||
String realm, PasswordAuthentication pw,
|
||||
String authenticatorKey) {
|
||||
boolean isUTF8, String authenticatorKey) {
|
||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||
AuthScheme.BASIC, host, port, realm,
|
||||
Objects.requireNonNull(authenticatorKey));
|
||||
String plain = pw.getUserName() + ":";
|
||||
byte[] nameBytes = null;
|
||||
try {
|
||||
nameBytes = plain.getBytes("ISO-8859-1");
|
||||
} catch (java.io.UnsupportedEncodingException uee) {
|
||||
assert false;
|
||||
}
|
||||
|
||||
// get password bytes
|
||||
char[] passwd = pw.getPassword();
|
||||
byte[] passwdBytes = new byte[passwd.length];
|
||||
for (int i=0; i<passwd.length; i++)
|
||||
passwdBytes[i] = (byte)passwd[i];
|
||||
|
||||
// concatenate user name and password bytes and encode them
|
||||
byte[] concat = new byte[nameBytes.length + passwdBytes.length];
|
||||
System.arraycopy(nameBytes, 0, concat, 0, nameBytes.length);
|
||||
System.arraycopy(passwdBytes, 0, concat, nameBytes.length,
|
||||
passwdBytes.length);
|
||||
this.auth = "Basic " + Base64.getEncoder().encodeToString(concat);
|
||||
this.auth = authValueFrom(pw, isUTF8);
|
||||
this.pw = pw;
|
||||
}
|
||||
|
||||
@ -99,34 +86,30 @@ class BasicAuthentication extends AuthenticationInfo {
|
||||
* Create a BasicAuthentication
|
||||
*/
|
||||
public BasicAuthentication(boolean isProxy, URL url, String realm,
|
||||
PasswordAuthentication pw,
|
||||
PasswordAuthentication pw, boolean isUTF8,
|
||||
String authenticatorKey) {
|
||||
super(isProxy ? PROXY_AUTHENTICATION : SERVER_AUTHENTICATION,
|
||||
AuthScheme.BASIC, url, realm,
|
||||
Objects.requireNonNull(authenticatorKey));
|
||||
String plain = pw.getUserName() + ":";
|
||||
byte[] nameBytes = null;
|
||||
try {
|
||||
nameBytes = plain.getBytes("ISO-8859-1");
|
||||
} catch (java.io.UnsupportedEncodingException uee) {
|
||||
assert false;
|
||||
}
|
||||
|
||||
// get password bytes
|
||||
char[] passwd = pw.getPassword();
|
||||
byte[] passwdBytes = new byte[passwd.length];
|
||||
for (int i=0; i<passwd.length; i++)
|
||||
passwdBytes[i] = (byte)passwd[i];
|
||||
|
||||
// concatenate user name and password bytes and encode them
|
||||
byte[] concat = new byte[nameBytes.length + passwdBytes.length];
|
||||
System.arraycopy(nameBytes, 0, concat, 0, nameBytes.length);
|
||||
System.arraycopy(passwdBytes, 0, concat, nameBytes.length,
|
||||
passwdBytes.length);
|
||||
this.auth = "Basic " + Base64.getEncoder().encodeToString(concat);
|
||||
this.auth = authValueFrom(pw, isUTF8);
|
||||
this.pw = pw;
|
||||
}
|
||||
|
||||
private static String authValueFrom(PasswordAuthentication pw, boolean isUTF8) {
|
||||
String plain = pw.getUserName() + ":";
|
||||
char[] password = pw.getPassword();
|
||||
CharBuffer cbuf = CharBuffer.allocate(plain.length() + password.length);
|
||||
cbuf.put(plain).put(password).flip();
|
||||
Charset charset = isUTF8 ? UTF_8 : ISO_8859_1;
|
||||
ByteBuffer buf = charset.encode(cbuf);
|
||||
ByteBuffer enc = Base64.getEncoder().encode(buf);
|
||||
String ret = "Basic " + new String(enc.array(), enc.position(), enc.remaining(), ISO_8859_1);
|
||||
Arrays.fill(buf.array(), (byte) 0);
|
||||
Arrays.fill(enc.array(), (byte) 0);
|
||||
Arrays.fill(cbuf.array(), (char) 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BasicAuthentication
|
||||
*/
|
||||
|
@ -2265,6 +2265,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
if (host != null && authhdr.isPresent()) {
|
||||
HeaderParser p = authhdr.headerParser();
|
||||
String realm = p.findValue("realm");
|
||||
String charset = p.findValue("charset");
|
||||
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||
String scheme = authhdr.scheme();
|
||||
AuthScheme authScheme = UNKNOWN;
|
||||
if ("basic".equalsIgnoreCase(scheme)) {
|
||||
@ -2310,7 +2312,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
realm, scheme, url, RequestorType.PROXY);
|
||||
if (a != null) {
|
||||
ret = new BasicAuthentication(true, host, port, realm, a,
|
||||
getAuthenticatorKey());
|
||||
isUTF8, getAuthenticatorKey());
|
||||
}
|
||||
break;
|
||||
case DIGEST:
|
||||
@ -2428,6 +2430,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
HeaderParser p = authhdr.headerParser();
|
||||
String realm = p.findValue("realm");
|
||||
String scheme = authhdr.scheme();
|
||||
String charset = p.findValue("charset");
|
||||
boolean isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||
AuthScheme authScheme = UNKNOWN;
|
||||
if ("basic".equalsIgnoreCase(scheme)) {
|
||||
authScheme = BASIC;
|
||||
@ -2479,7 +2483,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
|
||||
realm, scheme, url, RequestorType.SERVER);
|
||||
if (a != null) {
|
||||
ret = new BasicAuthentication(false, url, realm, a,
|
||||
getAuthenticatorKey());
|
||||
isUTF8, getAuthenticatorKey());
|
||||
}
|
||||
break;
|
||||
case DIGEST:
|
||||
|
@ -32,6 +32,7 @@ import java.net.URI;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Base64;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -43,6 +44,7 @@ import jdk.internal.net.http.common.Utils;
|
||||
import static java.net.Authenticator.RequestorType.PROXY;
|
||||
import static java.net.Authenticator.RequestorType.SERVER;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Implementation of Http Basic authentication.
|
||||
@ -155,8 +157,8 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (proxyURI != null) {
|
||||
CacheEntry ca = cache.get(proxyURI, true);
|
||||
if (ca != null) {
|
||||
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca);
|
||||
addBasicCredentials(r, true, ca.value);
|
||||
exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||
addBasicCredentials(r, true, ca.value, ca.isUTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -165,8 +167,8 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (exchange.serverauth == null) {
|
||||
CacheEntry ca = cache.get(r.uri(), false);
|
||||
if (ca != null) {
|
||||
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca);
|
||||
addBasicCredentials(r, false, ca.value);
|
||||
exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8);
|
||||
addBasicCredentials(r, false, ca.value, ca.isUTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -174,11 +176,13 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
// TODO: refactor into per auth scheme class
|
||||
private static void addBasicCredentials(HttpRequestImpl r,
|
||||
boolean proxy,
|
||||
PasswordAuthentication pw) {
|
||||
PasswordAuthentication pw,
|
||||
boolean isUTF8) {
|
||||
String hdrname = proxy ? "Proxy-Authorization" : "Authorization";
|
||||
StringBuilder sb = new StringBuilder(128);
|
||||
sb.append(pw.getUserName()).append(':').append(pw.getPassword());
|
||||
String s = encoder.encodeToString(sb.toString().getBytes(ISO_8859_1));
|
||||
var charset = isUTF8 ? UTF_8 : ISO_8859_1;
|
||||
String s = encoder.encodeToString(sb.toString().getBytes(charset));
|
||||
String value = "Basic " + s;
|
||||
if (proxy) {
|
||||
if (r.isConnect()) {
|
||||
@ -203,35 +207,36 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
int retries;
|
||||
PasswordAuthentication credentials; // used in request
|
||||
CacheEntry cacheEntry; // if used
|
||||
final boolean isUTF8; //
|
||||
|
||||
AuthInfo(boolean fromcache,
|
||||
String scheme,
|
||||
PasswordAuthentication credentials) {
|
||||
PasswordAuthentication credentials, boolean isUTF8) {
|
||||
this.fromcache = fromcache;
|
||||
this.scheme = scheme;
|
||||
this.credentials = credentials;
|
||||
this.retries = 1;
|
||||
this.isUTF8 = isUTF8;
|
||||
}
|
||||
|
||||
AuthInfo(boolean fromcache,
|
||||
String scheme,
|
||||
PasswordAuthentication credentials,
|
||||
CacheEntry ca) {
|
||||
this(fromcache, scheme, credentials);
|
||||
CacheEntry ca, boolean isUTF8) {
|
||||
this(fromcache, scheme, credentials, isUTF8);
|
||||
assert credentials == null || (ca != null && ca.value == null);
|
||||
cacheEntry = ca;
|
||||
}
|
||||
|
||||
AuthInfo retryWithCredentials(PasswordAuthentication pw) {
|
||||
AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) {
|
||||
// If the info was already in the cache we need to create a new
|
||||
// instance with fromCache==false so that it's put back in the
|
||||
// cache if authentication succeeds
|
||||
AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw) : this;
|
||||
AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw, isUTF8) : this;
|
||||
res.credentials = Objects.requireNonNull(pw);
|
||||
res.retries = retries;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -245,13 +250,13 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
// check if any authentication succeeded for first time
|
||||
if (exchange.serverauth != null && !exchange.serverauth.fromcache) {
|
||||
AuthInfo au = exchange.serverauth;
|
||||
cache.store(au.scheme, req.uri(), false, au.credentials);
|
||||
cache.store(au.scheme, req.uri(), false, au.credentials, au.isUTF8);
|
||||
}
|
||||
if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) {
|
||||
AuthInfo au = exchange.proxyauth;
|
||||
URI proxyURI = getProxyURI(req);
|
||||
if (proxyURI != null) {
|
||||
cache.store(au.scheme, proxyURI, true, au.credentials);
|
||||
cache.store(au.scheme, proxyURI, true, au.credentials, au.isUTF8);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -264,11 +269,14 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
throw new IOException(authname + " header missing for response code " + status);
|
||||
}
|
||||
String authval = null;
|
||||
boolean isUTF8 = false;
|
||||
for (String aval : authvals) {
|
||||
HeaderParser parser = new HeaderParser(aval);
|
||||
String scheme = parser.findKey(0);
|
||||
if (scheme.equalsIgnoreCase("Basic")) {
|
||||
authval = aval;
|
||||
var charset = parser.findValue("charset");
|
||||
isUTF8 = (charset != null && charset.equalsIgnoreCase("UTF-8"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -302,14 +310,14 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
throw new IOException("No credentials provided");
|
||||
}
|
||||
// No authentication in request. Get credentials from user
|
||||
au = new AuthInfo(false, "Basic", pw);
|
||||
au = new AuthInfo(false, "Basic", pw, isUTF8);
|
||||
if (proxy) {
|
||||
exchange.proxyauth = au;
|
||||
} else {
|
||||
exchange.serverauth = au;
|
||||
}
|
||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||
addBasicCredentials(req, proxy, pw);
|
||||
addBasicCredentials(req, proxy, pw, isUTF8);
|
||||
return req;
|
||||
} else if (au.retries > retry_limit) {
|
||||
throw new IOException("too many authentication attempts. Limit: " +
|
||||
@ -328,14 +336,14 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
if (pw == null) {
|
||||
throw new IOException("No credentials provided");
|
||||
}
|
||||
au = au.retryWithCredentials(pw);
|
||||
au = au.retryWithCredentials(pw, isUTF8);
|
||||
if (proxy) {
|
||||
exchange.proxyauth = au;
|
||||
} else {
|
||||
exchange.serverauth = au;
|
||||
}
|
||||
req = HttpRequestImpl.newInstanceForAuthentication(req);
|
||||
addBasicCredentials(req, proxy, au.credentials);
|
||||
addBasicCredentials(req, proxy, au.credentials, isUTF8);
|
||||
au.retries++;
|
||||
return req;
|
||||
}
|
||||
@ -387,9 +395,9 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
synchronized void store(String authscheme,
|
||||
URI domain,
|
||||
boolean proxy,
|
||||
PasswordAuthentication value) {
|
||||
PasswordAuthentication value, boolean isUTF8) {
|
||||
remove(authscheme, domain, proxy);
|
||||
entries.add(new CacheEntry(authscheme, domain, proxy, value));
|
||||
entries.add(new CacheEntry(authscheme, domain, proxy, value, isUTF8));
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,15 +425,17 @@ class AuthenticationFilter implements HeaderFilter {
|
||||
final String scheme;
|
||||
final boolean proxy;
|
||||
final PasswordAuthentication value;
|
||||
final boolean isUTF8;
|
||||
|
||||
CacheEntry(String authscheme,
|
||||
URI uri,
|
||||
boolean proxy,
|
||||
PasswordAuthentication value) {
|
||||
PasswordAuthentication value, boolean isUTF8) {
|
||||
this.scheme = authscheme;
|
||||
this.root = normalize(uri, true).toString(); // remove extraneous components
|
||||
this.proxy = proxy;
|
||||
this.value = value;
|
||||
this.isUTF8 = isUTF8;
|
||||
}
|
||||
|
||||
public PasswordAuthentication value() {
|
||||
|
@ -25,7 +25,11 @@
|
||||
|
||||
package com.sun.net.httpserver;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* BasicAuthenticator provides an implementation of HTTP Basic
|
||||
@ -35,15 +39,44 @@ import java.util.Base64;
|
||||
*/
|
||||
public abstract class BasicAuthenticator extends Authenticator {
|
||||
|
||||
protected String realm;
|
||||
protected final String realm;
|
||||
protected final Charset charset;
|
||||
private final boolean isUTF8;
|
||||
|
||||
/**
|
||||
* Creates a BasicAuthenticator for the given HTTP realm
|
||||
* Creates a BasicAuthenticator for the given HTTP realm.
|
||||
* The Basic authentication credentials (username and password) are decoded
|
||||
* using the platform's {@link Charset#defaultCharset() default character set}.
|
||||
*
|
||||
* @param realm The HTTP Basic authentication realm
|
||||
* @throws NullPointerException if the realm is an empty string
|
||||
* @throws NullPointerException if realm is {@code null}
|
||||
* @throws IllegalArgumentException if realm is an empty string
|
||||
*/
|
||||
public BasicAuthenticator (String realm) {
|
||||
this(realm, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BasicAuthenticator for the given HTTP realm and using the
|
||||
* given {@link Charset} to decode the Basic authentication credentials
|
||||
* (username and password).
|
||||
*
|
||||
* @apiNote {@code UTF-8} is the recommended charset because its usage is
|
||||
* communicated to the client, and therefore more likely to be used also
|
||||
* by the client.
|
||||
*
|
||||
* @param realm The HTTP Basic authentication realm
|
||||
* @param charset The Charset to decode incoming credentials from the client
|
||||
* @throws NullPointerException if realm or charset are {@code null}
|
||||
* @throws IllegalArgumentException if realm is an empty string
|
||||
*/
|
||||
public BasicAuthenticator (String realm, Charset charset) {
|
||||
Objects.requireNonNull(charset);
|
||||
if (realm.isEmpty()) // implicit NPE check
|
||||
throw new IllegalArgumentException("realm must not be empty");
|
||||
this.realm = realm;
|
||||
this.charset = charset;
|
||||
this.isUTF8 = charset.equals(UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,7 +96,9 @@ public abstract class BasicAuthenticator extends Authenticator {
|
||||
String auth = rmap.getFirst ("Authorization");
|
||||
if (auth == null) {
|
||||
Headers map = t.getResponseHeaders();
|
||||
map.set ("WWW-Authenticate", "Basic realm=" + "\""+realm+"\"");
|
||||
var authString = "Basic realm=" + "\"" + realm + "\"" +
|
||||
(isUTF8 ? " charset=\"UTF-8\"" : "");
|
||||
map.set ("WWW-Authenticate", authString);
|
||||
return new Authenticator.Retry (401);
|
||||
}
|
||||
int sp = auth.indexOf (' ');
|
||||
@ -71,7 +106,7 @@ public abstract class BasicAuthenticator extends Authenticator {
|
||||
return new Authenticator.Failure (401);
|
||||
}
|
||||
byte[] b = Base64.getDecoder().decode(auth.substring(sp+1));
|
||||
String userpass = new String (b);
|
||||
String userpass = new String (b, charset);
|
||||
int colon = userpass.indexOf (':');
|
||||
String uname = userpass.substring (0, colon);
|
||||
String pass = userpass.substring (colon+1);
|
||||
|
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8199849
|
||||
* @library /test/lib
|
||||
* @run main/othervm/timeout=6000 BasicAuthenticatorCharset
|
||||
* @summary Check for correct use of character sets with BasicAuthenticator() authentication
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.*;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.*;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
|
||||
/**
|
||||
* Test authentication
|
||||
*/
|
||||
|
||||
public class BasicAuthenticatorCharset {
|
||||
|
||||
public static volatile int failCount = 0;
|
||||
|
||||
static final String TEST_USER = "test";
|
||||
static final String UNICODE_PW = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||
|
||||
static Handler testHandler;
|
||||
static HttpServer testHttpServer;
|
||||
static java.net.Authenticator clientAuth;
|
||||
static HttpClient client;
|
||||
|
||||
static class Handler implements HttpHandler {
|
||||
public void handle(HttpExchange t) throws IOException {
|
||||
InputStream is = t.getRequestBody();
|
||||
while (is.read() != -1) ;
|
||||
is.close();
|
||||
t.sendResponseHeaders(200, -1);
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
static class ClientAuthenticator extends java.net.Authenticator {
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(TEST_USER, UNICODE_PW.toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
static void setAuthenticationPW(String path, String realm, String testPW, Charset charset) {
|
||||
HttpContext ctx = testHttpServer.createContext(path, testHandler);
|
||||
BasicAuthenticator auth;
|
||||
if (charset != null) {
|
||||
auth = new BasicAuthenticator(realm, charset) {
|
||||
public boolean checkCredentials(String username, String pw) {
|
||||
return username.equals(TEST_USER) && pw.equals(testPW);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
auth = new BasicAuthenticator(realm) {
|
||||
public boolean checkCredentials(String username, String pw) {
|
||||
return username.equals(TEST_USER) && pw.equals(testPW);
|
||||
}
|
||||
};
|
||||
}
|
||||
ctx.setAuthenticator(auth);
|
||||
}
|
||||
|
||||
static void connectAndAuth(String path, int expectedStatus) throws Exception {
|
||||
// path is prepended with /old or /new for old and new http client
|
||||
URL oldurl = URIBuilder.newBuilder()
|
||||
.scheme("http")
|
||||
.loopback()
|
||||
.port(testHttpServer.getAddress().getPort())
|
||||
.path("/old" + path)
|
||||
.toURL();
|
||||
|
||||
URI newuri = URIBuilder.newBuilder()
|
||||
.scheme("http")
|
||||
.loopback()
|
||||
.port(testHttpServer.getAddress().getPort())
|
||||
.path("/new" + path)
|
||||
.build();
|
||||
|
||||
// check old client
|
||||
|
||||
HttpURLConnection testConnection = (HttpURLConnection) oldurl.openConnection(Proxy.NO_PROXY);
|
||||
|
||||
// Check for successful authentication
|
||||
int status = testConnection.getResponseCode();
|
||||
if (status != 401) {
|
||||
InputStream is = testConnection.getInputStream();
|
||||
while (is.read() != -1) ;
|
||||
is.close();
|
||||
}
|
||||
if (status != expectedStatus) {
|
||||
System.err.println("Error (old): " + path);
|
||||
failCount++;
|
||||
}
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(newuri)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
status = -1;
|
||||
try {
|
||||
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
|
||||
status = response.statusCode();
|
||||
} catch (IOException e) {
|
||||
System.out.println("NEW: " + e);
|
||||
status = 401; // limitation in new API.
|
||||
}
|
||||
if (status != expectedStatus) {
|
||||
System.err.println("Error (new): " + path);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
clientAuth = new ClientAuthenticator();
|
||||
client = HttpClient.newBuilder()
|
||||
.authenticator(clientAuth)
|
||||
.build();
|
||||
|
||||
String defaultCharset = System.getProperty("file.encoding");
|
||||
boolean isUTF8 = defaultCharset.equalsIgnoreCase("UTF-8");
|
||||
testHandler = new Handler();
|
||||
InetSocketAddress addr = new InetSocketAddress(0);
|
||||
testHttpServer = HttpServer.create(addr, 0);
|
||||
|
||||
// Set the passing credentials OLD client
|
||||
setAuthenticationPW("/old/test1/", "passingCharset@test.realm", UNICODE_PW, UTF_8);
|
||||
setAuthenticationPW("/old/test2/", "failingCharset@test.realm", UNICODE_PW, ISO_8859_1);
|
||||
setAuthenticationPW("/old/test3/", "defaultCharset@test.realm", UNICODE_PW, null);
|
||||
|
||||
// Set the passing credentials NEW client
|
||||
setAuthenticationPW("/new/test1/", "passingCharset@test.realm", UNICODE_PW, UTF_8);
|
||||
setAuthenticationPW("/new/test2/", "failingCharset@test.realm", UNICODE_PW, ISO_8859_1);
|
||||
setAuthenticationPW("/new/test3/", "defaultCharset@test.realm", UNICODE_PW, null);
|
||||
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
testHttpServer.setExecutor(executor);
|
||||
testHttpServer.start();
|
||||
java.net.Authenticator.setDefault(clientAuth);
|
||||
|
||||
connectAndAuth("/test1/passingCharset.html", 200);
|
||||
connectAndAuth("/test2/failingCharset.html", 401);
|
||||
if (isUTF8) {
|
||||
connectAndAuth("/test3/defaultCharset.html", 200);
|
||||
}
|
||||
|
||||
testHttpServer.stop(2);
|
||||
executor.shutdown();
|
||||
|
||||
// should fail once with UNICODE_PW and unsupporting character set
|
||||
if (failCount > 0)
|
||||
throw new RuntimeException("Fail count : " + failCount);
|
||||
}
|
||||
}
|
229
test/jdk/com/sun/net/httpserver/bugs/8199849/ParamTest.java
Normal file
229
test/jdk/com/sun/net/httpserver/bugs/8199849/ParamTest.java
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8199849
|
||||
* @summary
|
||||
* @library /test/lib
|
||||
* @run main/othervm ParamTest
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true ParamTest
|
||||
*/
|
||||
|
||||
public class ParamTest {
|
||||
|
||||
static final String[] variants = {
|
||||
" charset=utf-8",
|
||||
" charset=UtF-8",
|
||||
" charset=\"utF-8\"",
|
||||
" charset=\"UtF-8\""
|
||||
};
|
||||
|
||||
static final int LOOPS = variants.length;
|
||||
|
||||
volatile static boolean error = false;
|
||||
|
||||
static class BasicServer extends Thread {
|
||||
|
||||
final ServerSocket server;
|
||||
|
||||
Socket s;
|
||||
InputStream is;
|
||||
OutputStream os;
|
||||
|
||||
static final String realm = "wallyworld";
|
||||
|
||||
String reply1 = "HTTP/1.1 401 Unauthorized\r\n"+
|
||||
"WWW-Authenticate: Basic realm=\""+realm+"\"\r\n";
|
||||
|
||||
String reply2 = "HTTP/1.1 200 OK\r\n"+
|
||||
"Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" +
|
||||
"Server: Apache/1.3.14 (Unix)\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"Content-Type: text/html; charset=iso-8859-1\r\n" +
|
||||
"Content-Length: 10\r\n\r\n";
|
||||
|
||||
BasicServer(ServerSocket s) {
|
||||
server = s;
|
||||
}
|
||||
|
||||
String readHeaders(Socket sock) throws IOException {
|
||||
InputStream is = sock.getInputStream();
|
||||
String s = "";
|
||||
byte[] buf = new byte[1024];
|
||||
while (!s.endsWith("\r\n\r\n")) {
|
||||
int c = is.read(buf);
|
||||
if (c == -1)
|
||||
return s;
|
||||
String f = new String(buf, 0, c, StandardCharsets.ISO_8859_1);
|
||||
s = s + f;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void check(String s, int iteration) {
|
||||
if (s.indexOf(encodedAuthString) == -1) {
|
||||
System.err.printf("On iteration %d, wrong auth string received %s\n", iteration, s);
|
||||
error = true;
|
||||
} else {
|
||||
System.err.println("check: correct auth string received");
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
for (int j = 0; j < 2; j++)
|
||||
for (int i = 0; i < LOOPS; i++) {
|
||||
System.out.println("Server 1: accept");
|
||||
s = server.accept();
|
||||
readHeaders(s);
|
||||
System.out.println("accepted");
|
||||
os = s.getOutputStream();
|
||||
String str = reply1 + variants[i] + "\r\n\r\n";
|
||||
os.write(str.getBytes());
|
||||
|
||||
System.out.println("Server 2: accept");
|
||||
Socket s1 = server.accept();
|
||||
String request = readHeaders(s1);
|
||||
check(request, i);
|
||||
System.out.println("accepted");
|
||||
os = s1.getOutputStream();
|
||||
os.write((reply2 + "HelloWorld").getBytes());
|
||||
os.flush();
|
||||
s.close();
|
||||
s1.close();
|
||||
finished();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println(e);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void finished() {
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final String password = "Selam D\u00fcnya.";
|
||||
|
||||
// "user : <password above>" encoded in UTF-8 and converted to Base 64
|
||||
|
||||
static final String encodedAuthString = "dXNlcjpTZWxhbSBEw7xueWEu";
|
||||
|
||||
static class MyAuthenticator extends Authenticator {
|
||||
MyAuthenticator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
System.out.println("Auth called");
|
||||
return (new PasswordAuthentication ("user", password.toCharArray()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void read(InputStream is) throws IOException {
|
||||
int c;
|
||||
System.out.println("reading");
|
||||
while ((c=is.read()) != -1) {
|
||||
System.out.write(c);
|
||||
}
|
||||
System.out.println("");
|
||||
System.out.println("finished reading");
|
||||
}
|
||||
|
||||
public static void main(String args[]) throws Exception {
|
||||
MyAuthenticator auth = new MyAuthenticator();
|
||||
Authenticator.setDefault(auth);
|
||||
InetAddress loopback = InetAddress.getLoopbackAddress();
|
||||
ServerSocket ss = new ServerSocket();
|
||||
ss.bind(new InetSocketAddress(loopback, 0));
|
||||
int port = ss.getLocalPort();
|
||||
BasicServer server = new BasicServer(ss);
|
||||
synchronized (server) {
|
||||
server.start();
|
||||
System.out.println("client 1");
|
||||
String base = URIBuilder.newBuilder()
|
||||
.scheme("http")
|
||||
.loopback()
|
||||
.port(port)
|
||||
.path("/")
|
||||
.build()
|
||||
.toString();
|
||||
URL url = new URL(base + "d1/d2/d3/foo.html");
|
||||
|
||||
for (int i = 0; i < LOOPS; i++) {
|
||||
URLConnection urlc = url.openConnection(Proxy.NO_PROXY);
|
||||
InputStream is = urlc.getInputStream();
|
||||
read(is);
|
||||
System.out.println("Client: waiting for notify");
|
||||
server.wait();
|
||||
System.out.println("Client: continue");
|
||||
// check if authenticator was called once (ok) or twice (not)
|
||||
if (error) {
|
||||
System.err.println("Error old client iteration " + i);
|
||||
}
|
||||
}
|
||||
|
||||
URI uri = url.toURI();
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.authenticator(auth)
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest
|
||||
.newBuilder(uri)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
for (int i = 0; i < LOOPS; i++) {
|
||||
HttpResponse<Void> response = client.send(request, HttpResponse.BodyHandlers.discarding());
|
||||
int status = response.statusCode();
|
||||
if (status != 200) {
|
||||
System.err.printf("Error new client (%d) iteration ",
|
||||
status, i);
|
||||
error = true;
|
||||
} else
|
||||
System.err.println("New client ok iteration " + i);
|
||||
System.out.println("New Client: waiting for notify");
|
||||
server.wait();
|
||||
System.out.println("New Client: continue");
|
||||
}
|
||||
|
||||
if (error) {
|
||||
throw new RuntimeException("Test failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8199849
|
||||
* @library /test/lib
|
||||
* @summary Checks that unicode bytes are being handled correctly
|
||||
* @run main/othervm -Dfile.encoding=UTF_8 TestHttpUnicode
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.*;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.*;
|
||||
|
||||
public class TestHttpUnicode {
|
||||
|
||||
private static final String TEST_USER = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||
private static final String TEST_PW = "Selam D\u00fcnya. Ho\u015f\u00e7akal D\u00fcnya";
|
||||
|
||||
static class ClientAuthenticator extends java.net.Authenticator {
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(TEST_USER, TEST_PW.toCharArray());
|
||||
}
|
||||
}
|
||||
|
||||
static class Handler implements HttpHandler {
|
||||
public void handle(HttpExchange t) throws IOException {
|
||||
InputStream is = t.getRequestBody();
|
||||
while (is.read() != -1) ;
|
||||
is.close();
|
||||
|
||||
HttpPrincipal p = t.getPrincipal();
|
||||
if (p.getUsername().equals(TEST_USER)) {
|
||||
t.sendResponseHeaders(200, -1);
|
||||
}
|
||||
t.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
HttpServer testHttpServer = null;
|
||||
try {
|
||||
InetSocketAddress loopbackAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
|
||||
testHttpServer = HttpServer.create(loopbackAddress, 0);
|
||||
HttpContext context = testHttpServer.createContext("/test", new Handler());
|
||||
System.setProperty("http.maxRedirects", "3");
|
||||
|
||||
BasicAuthenticator serverAuthenticator = new BasicAuthenticator("authCharacterSet@test.realm") {
|
||||
public boolean checkCredentials(String username, String pw) {
|
||||
return username.equals(TEST_USER) && pw.equals(TEST_PW);
|
||||
}
|
||||
};
|
||||
context.setAuthenticator(serverAuthenticator);
|
||||
java.net.Authenticator.setDefault(new ClientAuthenticator());
|
||||
|
||||
testHttpServer.start();
|
||||
URL url = URIBuilder.newBuilder()
|
||||
.scheme("http")
|
||||
.loopback()
|
||||
.port(testHttpServer.getAddress().getPort())
|
||||
.path("/test/authCharacterSet.html")
|
||||
.toURL();
|
||||
HttpURLConnection testConnection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
|
||||
|
||||
// Authenication CHECK
|
||||
if (testConnection.getResponseCode() == 401) {
|
||||
throw new RuntimeException("Test Authentication failed with HTTP Status 401.");
|
||||
}
|
||||
|
||||
InputStream is = testConnection.getInputStream();
|
||||
while (is.read() != -1) ;
|
||||
} finally {
|
||||
testHttpServer.stop(2);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user