8229867: Re-examine synchronization usages in http and https protocol handlers

Reviewed-by: chegar, alanb, michaelm
This commit is contained in:
Daniel Fuchs 2020-10-13 14:22:11 +00:00
parent 6fe209b564
commit 65393a093c
18 changed files with 964 additions and 562 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2020, 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
@ -294,7 +294,7 @@ class MessageHeader {
* @param line the line to check.
* @return true if the line might be a request line.
*/
private boolean isRequestline(String line) {
private static boolean isRequestline(String line) {
String k = line.trim();
int i = k.lastIndexOf(' ');
if (i <= 0) return false;
@ -311,12 +311,23 @@ class MessageHeader {
return (k.substring(i+1, len-3).equalsIgnoreCase("HTTP/"));
}
/** Prints the key-value pairs represented by this
header. Also prints the RFC required blank line
at the end. Omits pairs with a null key. Omits
colon if key-value pair is the requestline. */
public void print(PrintStream p) {
// no synchronization: use cloned arrays instead.
String[] k; String[] v; int n;
synchronized (this) { n = nkeys; k = keys.clone(); v = values.clone(); }
print(n, k, v, p);
}
/** Prints the key-value pairs represented by this
header. Also prints the RFC required blank line
at the end. Omits pairs with a null key. Omits
colon if key-value pair is the requestline. */
public synchronized void print(PrintStream p) {
private static void print(int nkeys, String[] keys, String[] values, PrintStream p) {
for (int i = 0; i < nkeys; i++)
if (keys[i] != null) {
StringBuilder sb = new StringBuilder(keys[i]);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2020, 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
@ -25,9 +25,8 @@
package sun.net.www;
import java.net.URL;
import java.util.*;
import java.io.*;
import java.util.concurrent.locks.ReentrantLock;
import sun.net.ProgressSource;
import sun.net.www.http.ChunkedInputStream;
@ -44,6 +43,7 @@ public class MeteredStream extends FilterInputStream {
protected long markedCount = 0;
protected int markLimit = -1;
protected ProgressSource pi;
private final ReentrantLock readLock = new ReentrantLock();
public MeteredStream(InputStream is, ProgressSource pi, long expected)
{
@ -57,7 +57,9 @@ public class MeteredStream extends FilterInputStream {
}
}
private final void justRead(long n) throws IOException {
private final void justRead(long n) throws IOException {
assert isLockHeldByCurrentThread();
if (n == -1) {
/*
@ -99,6 +101,7 @@ public class MeteredStream extends FilterInputStream {
* Returns true if the mark is valid, false otherwise
*/
private boolean isMarked() {
assert isLockHeldByCurrentThread();
if (markLimit < 0) {
return false;
@ -113,94 +116,130 @@ public class MeteredStream extends FilterInputStream {
return true;
}
public synchronized int read() throws java.io.IOException {
if (closed) {
return -1;
public int read() throws java.io.IOException {
lock();
try {
if (closed) return -1;
int c = in.read();
if (c != -1) {
justRead(1);
} else {
justRead(c);
}
return c;
} finally {
unlock();
}
int c = in.read();
if (c != -1) {
justRead(1);
} else {
justRead(c);
}
return c;
}
public synchronized int read(byte b[], int off, int len)
public int read(byte b[], int off, int len)
throws java.io.IOException {
if (closed) {
return -1;
lock();
try {
if (closed) return -1;
int n = in.read(b, off, len);
justRead(n);
return n;
} finally {
unlock();
}
int n = in.read(b, off, len);
justRead(n);
return n;
}
public synchronized long skip(long n) throws IOException {
public long skip(long n) throws IOException {
lock();
try {
// REMIND: what does skip do on EOF????
if (closed) return 0;
// REMIND: what does skip do on EOF????
if (closed) {
return 0;
if (in instanceof ChunkedInputStream) {
n = in.skip(n);
} else {
// just skip min(n, num_bytes_left)
long min = (n > expected - count) ? expected - count : n;
n = in.skip(min);
}
justRead(n);
return n;
} finally {
unlock();
}
if (in instanceof ChunkedInputStream) {
n = in.skip(n);
}
else {
// just skip min(n, num_bytes_left)
long min = (n > expected - count) ? expected - count: n;
n = in.skip(min);
}
justRead(n);
return n;
}
public void close() throws IOException {
if (closed) {
return;
}
if (pi != null)
pi.finishTracking();
lock();
try {
if (closed) return;
if (pi != null)
pi.finishTracking();
closed = true;
in.close();
closed = true;
in.close();
} finally {
unlock();
}
}
public synchronized int available() throws IOException {
return closed ? 0: in.available();
public int available() throws IOException {
lock();
try {
return closed ? 0 : in.available();
} finally {
unlock();
}
}
public synchronized void mark(int readLimit) {
if (closed) {
return;
}
super.mark(readLimit);
public void mark(int readLimit) {
lock();
try {
if (closed) return;
super.mark(readLimit);
/*
* mark the count to restore upon reset
*/
markedCount = count;
markLimit = readLimit;
/*
* mark the count to restore upon reset
*/
markedCount = count;
markLimit = readLimit;
} finally {
unlock();
}
}
public synchronized void reset() throws IOException {
if (closed) {
return;
}
public void reset() throws IOException {
lock();
try {
if (closed) return;
if (!isMarked()) {
throw new IOException("Resetting to an invalid mark");
}
if (!isMarked()) {
throw new IOException ("Resetting to an invalid mark");
count = markedCount;
super.reset();
} finally {
unlock();
}
count = markedCount;
super.reset();
}
public boolean markSupported() {
if (closed) {
return false;
lock();
try {
if (closed) return false;
return super.markSupported();
} finally {
unlock();
}
return super.markSupported();
}
public final void lock() {
readLock.lock();
}
public final void unlock() {
readLock.unlock();
}
public final boolean isLockHeldByCurrentThread() {
return readLock.isHeldByCurrentThread();
}
@SuppressWarnings("deprecation")

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2020, 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
@ -25,9 +25,7 @@
package sun.net.www.http;
import java.io.*;
import java.util.*;
import sun.net.*;
import java.util.concurrent.locks.ReentrantLock;
import sun.net.www.*;
import sun.nio.cs.US_ASCII;
@ -41,8 +39,7 @@ import sun.nio.cs.US_ASCII;
* can be hurried to the end of the stream if the bytes are available on
* the underlying stream.
*/
public
class ChunkedInputStream extends InputStream implements Hurryable {
public class ChunkedInputStream extends InputStream implements Hurryable {
/**
* The underlying stream
@ -126,6 +123,8 @@ class ChunkedInputStream extends InputStream implements Hurryable {
*/
private boolean closed;
private final ReentrantLock readLock = new ReentrantLock();
/*
* Maximum chunk header size of 2KB + 2 bytes for CRLF
*/
@ -648,14 +647,19 @@ class ChunkedInputStream extends InputStream implements Hurryable {
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public synchronized int read() throws IOException {
ensureOpen();
if (chunkPos >= chunkCount) {
if (readAhead(true) <= 0) {
return -1;
public int read() throws IOException {
readLock.lock();
try {
ensureOpen();
if (chunkPos >= chunkCount) {
if (readAhead(true) <= 0) {
return -1;
}
}
return chunkData[chunkPos++] & 0xff;
} finally {
readLock.unlock();
}
return chunkData[chunkPos++] & 0xff;
}
@ -670,42 +674,47 @@ class ChunkedInputStream extends InputStream implements Hurryable {
* the stream has been reached.
* @exception IOException if an I/O error occurs.
*/
public synchronized int read(byte b[], int off, int len)
public int read(byte b[], int off, int len)
throws IOException
{
ensureOpen();
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int avail = chunkCount - chunkPos;
if (avail <= 0) {
/*
* Optimization: if we're in the middle of the chunk read
* directly from the underlying stream into the caller's
* buffer
*/
if (state == STATE_READING_CHUNK) {
return fastRead( b, off, len );
readLock.lock();
try {
ensureOpen();
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
/*
* We're not in the middle of a chunk so we must read ahead
* until there is some chunk data available.
*/
avail = readAhead(true);
if (avail < 0) {
return -1; /* EOF */
}
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(chunkData, chunkPos, b, off, cnt);
chunkPos += cnt;
int avail = chunkCount - chunkPos;
if (avail <= 0) {
/*
* Optimization: if we're in the middle of the chunk read
* directly from the underlying stream into the caller's
* buffer
*/
if (state == STATE_READING_CHUNK) {
return fastRead(b, off, len);
}
return cnt;
/*
* We're not in the middle of a chunk so we must read ahead
* until there is some chunk data available.
*/
avail = readAhead(true);
if (avail < 0) {
return -1; /* EOF */
}
}
int cnt = (avail < len) ? avail : len;
System.arraycopy(chunkData, chunkPos, b, off, cnt);
chunkPos += cnt;
return cnt;
} finally {
readLock.unlock();
}
}
/**
@ -717,20 +726,25 @@ class ChunkedInputStream extends InputStream implements Hurryable {
* @exception IOException if an I/O error occurs.
* @see java.io.FilterInputStream#in
*/
public synchronized int available() throws IOException {
ensureOpen();
public int available() throws IOException {
readLock.lock();
try {
ensureOpen();
int avail = chunkCount - chunkPos;
if(avail > 0) {
return avail;
}
int avail = chunkCount - chunkPos;
if (avail > 0) {
return avail;
}
avail = readAhead(false);
avail = readAhead(false);
if (avail < 0) {
return 0;
} else {
return avail;
if (avail < 0) {
return 0;
} else {
return avail;
}
} finally {
readLock.unlock();
}
}
@ -745,12 +759,18 @@ class ChunkedInputStream extends InputStream implements Hurryable {
*
* @exception IOException if an I/O error occurs.
*/
public synchronized void close() throws IOException {
if (closed) {
return;
public void close() throws IOException {
if (closed) return;
readLock.lock();
try {
if (closed) {
return;
}
closeUnderlying();
closed = true;
} finally {
readLock.unlock();
}
closeUnderlying();
closed = true;
}
/**
@ -762,22 +782,27 @@ class ChunkedInputStream extends InputStream implements Hurryable {
* without blocking then this stream can't be hurried and should be
* closed.
*/
public synchronized boolean hurry() {
if (in == null || error) {
return false;
}
public boolean hurry() {
readLock.lock();
try {
readAhead(false);
} catch (Exception e) {
return false;
}
if (in == null || error) {
return false;
}
if (error) {
return false;
}
try {
readAhead(false);
} catch (Exception e) {
return false;
}
return (state == STATE_DONE);
if (error) {
return false;
}
return (state == STATE_DONE);
} finally {
readLock.unlock();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2004, 2020, 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
@ -25,6 +25,8 @@
package sun.net.www.http;
import java.io.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import sun.nio.cs.US_ASCII;
@ -32,7 +34,7 @@ import sun.nio.cs.US_ASCII;
* OutputStream that sends the output to the underlying stream using chunked
* encoding as specified in RFC 2068.
*/
public class ChunkedOutputStream extends PrintStream {
public class ChunkedOutputStream extends OutputStream {
/* Default chunk size (including chunk header) if not specified */
static final int DEFAULT_CHUNK_SIZE = 4096;
@ -63,6 +65,8 @@ public class ChunkedOutputStream extends PrintStream {
/* header for a complete Chunk */
private byte[] completeHeader;
private final Lock writeLock = new ReentrantLock();
/* return the size of the header for a particular chunk size */
private static int getHeaderSize(int size) {
return (Integer.toHexString(size)).length() + CRLF_SIZE;
@ -85,7 +89,6 @@ public class ChunkedOutputStream extends PrintStream {
}
public ChunkedOutputStream(PrintStream o, int size) {
super(o);
out = o;
if (size <= 0) {
@ -144,7 +147,7 @@ public class ChunkedOutputStream extends PrintStream {
reset();
} else if (flushAll){
/* complete the last chunk and flush it to underlying stream */
if (size > 0){
if (size > 0) {
/* adjust a header start index in case the header of the last
* chunk is shorter then preferedHeaderSize */
@ -168,18 +171,18 @@ public class ChunkedOutputStream extends PrintStream {
out.flush();
reset();
}
}
}
@Override
public boolean checkError() {
return out.checkError();
var out = this.out;
return out == null || out.checkError();
}
/* Check that the output stream is still open */
private void ensureOpen() {
private void ensureOpen() throws IOException {
if (out == null)
setError();
throw new IOException("closed");
}
/*
@ -194,77 +197,92 @@ public class ChunkedOutputStream extends PrintStream {
* The size of the data is of course smaller than preferredChunkSize.
*/
@Override
public synchronized void write(byte b[], int off, int len) {
ensureOpen();
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
public void write(byte b[], int off, int len) throws IOException {
writeLock.lock();
try {
ensureOpen();
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
/* if b[] contains enough data then one loop cycle creates one complete
* data chunk with a header, body and a footer, and then flushes the
* chunk to the underlying stream. Otherwise, the last loop cycle
* creates incomplete data chunk with empty header and with no footer
* and stores this incomplete chunk in an internal buffer buf[]
*/
int bytesToWrite = len;
int inputIndex = off; /* the index of the byte[] currently being written */
/* if b[] contains enough data then one loop cycle creates one complete
* data chunk with a header, body and a footer, and then flushes the
* chunk to the underlying stream. Otherwise, the last loop cycle
* creates incomplete data chunk with empty header and with no footer
* and stores this incomplete chunk in an internal buffer buf[]
*/
int bytesToWrite = len;
int inputIndex = off; /* the index of the byte[] currently being written */
do {
/* enough data to complete a chunk */
if (bytesToWrite >= spaceInCurrentChunk) {
do {
/* enough data to complete a chunk */
if (bytesToWrite >= spaceInCurrentChunk) {
/* header */
for (int i=0; i<completeHeader.length; i++)
buf[i] = completeHeader[i];
/* header */
for (int i = 0; i < completeHeader.length; i++)
buf[i] = completeHeader[i];
/* data */
System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk);
inputIndex += spaceInCurrentChunk;
bytesToWrite -= spaceInCurrentChunk;
count += spaceInCurrentChunk;
/* data */
System.arraycopy(b, inputIndex, buf, count, spaceInCurrentChunk);
inputIndex += spaceInCurrentChunk;
bytesToWrite -= spaceInCurrentChunk;
count += spaceInCurrentChunk;
/* footer */
buf[count++] = FOOTER[0];
buf[count++] = FOOTER[1];
spaceInCurrentChunk = 0; //chunk is complete
/* footer */
buf[count++] = FOOTER[0];
buf[count++] = FOOTER[1];
spaceInCurrentChunk = 0; //chunk is complete
flush(false);
if (checkError()){
break;
flush(false);
if (checkError()) {
break;
}
}
}
/* not enough data to build a chunk */
else {
/* header */
/* do not write header if not enough bytes to build a chunk yet */
/* not enough data to build a chunk */
else {
/* header */
/* do not write header if not enough bytes to build a chunk yet */
/* data */
System.arraycopy(b, inputIndex, buf, count, bytesToWrite);
count += bytesToWrite;
size += bytesToWrite;
spaceInCurrentChunk -= bytesToWrite;
bytesToWrite = 0;
/* data */
System.arraycopy(b, inputIndex, buf, count, bytesToWrite);
count += bytesToWrite;
size += bytesToWrite;
spaceInCurrentChunk -= bytesToWrite;
bytesToWrite = 0;
/* footer */
/* do not write header if not enough bytes to build a chunk yet */
}
} while (bytesToWrite > 0);
/* footer */
/* do not write header if not enough bytes to build a chunk yet */
}
} while (bytesToWrite > 0);
} finally {
writeLock.unlock();
}
}
@Override
public synchronized void write(int _b) {
byte b[] = {(byte)_b};
write(b, 0, 1);
public void write(int _b) throws IOException {
writeLock.lock();
try {
byte b[] = {(byte) _b};
write(b, 0, 1);
} finally {
writeLock.unlock();
}
}
public synchronized void reset() {
count = preferedHeaderSize;
size = 0;
spaceInCurrentChunk = preferredChunkDataSize;
public void reset() {
writeLock.lock();
try {
count = preferedHeaderSize;
size = 0;
spaceInCurrentChunk = preferredChunkDataSize;
} finally {
writeLock.unlock();
}
}
public int size() {
@ -272,26 +290,36 @@ public class ChunkedOutputStream extends PrintStream {
}
@Override
public synchronized void close() {
ensureOpen();
public void close() {
writeLock.lock();
try {
if (out == null) return;
/* if we have buffer a chunked send it */
if (size > 0) {
/* if we have buffer a chunked send it */
if (size > 0) {
flush(true);
}
/* send a zero length chunk */
flush(true);
/* don't close the underlying stream */
out = null;
} finally {
writeLock.unlock();
}
/* send a zero length chunk */
flush(true);
/* don't close the underlying stream */
out = null;
}
@Override
public synchronized void flush() {
ensureOpen();
if (size > 0) {
flush(true);
public void flush() throws IOException {
writeLock.lock();
try {
ensureOpen();
if (size > 0) {
flush(true);
}
} finally {
writeLock.unlock();
}
}
}

View File

@ -54,6 +54,8 @@ import sun.util.logging.PlatformLogger;
* @author jccollet
*/
public class HttpCapture {
// HttpCapture does blocking I/O operations while holding monitors.
// This is not a concern because it is rarely used.
private File file;
private boolean incoming = true;
private BufferedWriter out;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2020, 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
@ -30,6 +30,8 @@ import java.net.*;
import java.util.Locale;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
import sun.net.NetworkClient;
import sun.net.ProgressSource;
import sun.net.www.MessageHeader;
@ -47,6 +49,8 @@ import sun.security.action.GetPropertyAction;
* @author Dave Brown
*/
public class HttpClient extends NetworkClient {
private final ReentrantLock clientLock = new ReentrantLock();
// whether this httpclient comes from the cache
protected boolean cachedHttpClient = false;
@ -316,22 +320,28 @@ public class HttpClient extends NetworkClient {
boolean compatible = Objects.equals(ret.proxy, p)
&& Objects.equals(ret.getAuthenticatorKey(), ak);
if (compatible) {
synchronized (ret) {
ret.lock();
try {
ret.cachedHttpClient = true;
assert ret.inCache;
ret.inCache = false;
if (httpuc != null && ret.needsTunneling())
httpuc.setTunnelState(TUNNELING);
logFinest("KeepAlive stream retrieved from the cache, " + ret);
} finally {
ret.unlock();
}
} else {
// We cannot return this connection to the cache as it's
// KeepAliveTimeout will get reset. We simply close the connection.
// This should be fine as it is very rare that a connection
// to the same host will not use the same proxy.
synchronized(ret) {
ret.lock();
try {
ret.inCache = false;
ret.closeServer();
} finally {
ret.unlock();
}
ret = null;
}
@ -409,10 +419,11 @@ public class HttpClient extends NetworkClient {
}
}
protected synchronized boolean available() {
protected boolean available() {
boolean available = true;
int old = -1;
lock();
try {
try {
old = serverSocket.getSoTimeout();
@ -436,21 +447,33 @@ public class HttpClient extends NetworkClient {
logFinest("HttpClient.available(): " +
"SocketException: not available");
available = false;
} finally {
unlock();
}
return available;
}
protected synchronized void putInKeepAliveCache() {
if (inCache) {
assert false : "Duplicate put to keep alive cache";
return;
protected void putInKeepAliveCache() {
lock();
try {
if (inCache) {
assert false : "Duplicate put to keep alive cache";
return;
}
inCache = true;
kac.put(url, null, this);
} finally {
unlock();
}
inCache = true;
kac.put(url, null, this);
}
protected synchronized boolean isInKeepAliveCache() {
return inCache;
protected boolean isInKeepAliveCache() {
lock();
try {
return inCache;
} finally {
unlock();
}
}
/*
@ -497,8 +520,13 @@ public class HttpClient extends NetworkClient {
/*
* Returns true if this httpclient is from cache
*/
public synchronized boolean isCachedConnection() {
return cachedHttpClient;
public boolean isCachedConnection() {
lock();
try {
return cachedHttpClient;
} finally {
unlock();
}
}
/*
@ -516,9 +544,10 @@ public class HttpClient extends NetworkClient {
/*
* call openServer in a privileged block
*/
private synchronized void privilegedOpenServer(final InetSocketAddress server)
private void privilegedOpenServer(final InetSocketAddress server)
throws IOException
{
assert clientLock.isHeldByCurrentThread();
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<>() {
@ -544,48 +573,53 @@ public class HttpClient extends NetworkClient {
/*
*/
protected synchronized void openServer() throws IOException {
protected void openServer() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkConnect(host, port);
}
lock();
try {
if (security != null) {
security.checkConnect(host, port);
}
if (keepingAlive) { // already opened
return;
}
if (url.getProtocol().equals("http") ||
url.getProtocol().equals("https") ) {
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
openServer(host, port);
usingProxy = false;
if (keepingAlive) { // already opened
return;
}
} else {
/* we're opening some other kind of url, most likely an
* ftp url.
*/
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
if (url.getProtocol().equals("http") ||
url.getProtocol().equals("https")) {
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
openServer(host, port);
usingProxy = false;
return;
}
} else {
// make direct connection
super.openServer(host, port);
usingProxy = false;
return;
/* we're opening some other kind of url, most likely an
* ftp url.
*/
if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
sun.net.www.URLConnection.setProxiedHost(host);
privilegedOpenServer((InetSocketAddress) proxy.address());
usingProxy = true;
return;
} else {
// make direct connection
super.openServer(host, port);
usingProxy = false;
return;
}
}
} finally {
unlock();
}
}
@ -1010,8 +1044,13 @@ public class HttpClient extends NetworkClient {
return ret;
}
public synchronized InputStream getInputStream() {
return serverInput;
public InputStream getInputStream() {
lock();
try {
return serverInput;
} finally {
unlock();
}
}
public OutputStream getOutputStream() {
@ -1084,4 +1123,12 @@ public class HttpClient extends NetworkClient {
return ((InetSocketAddress)proxy.address()).getPort();
return -1;
}
public final void lock() {
clientLock.lock();
}
public final void unlock() {
clientLock.unlock();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2020, 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
@ -36,6 +36,8 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import jdk.internal.misc.InnocuousThread;
import sun.security.action.GetIntegerAction;
@ -74,6 +76,8 @@ public class KeepAliveCache
static final int LIFETIME = 5000;
// This class is never serialized (see writeObject/readObject).
private final ReentrantLock cacheLock = new ReentrantLock();
private Thread keepAliveTimer = null;
/**
@ -86,76 +90,92 @@ public class KeepAliveCache
* @param url The URL contains info about the host and port
* @param http The HttpClient to be cached
*/
public synchronized void put(final URL url, Object obj, HttpClient http) {
boolean startThread = (keepAliveTimer == null);
if (!startThread) {
if (!keepAliveTimer.isAlive()) {
startThread = true;
}
}
if (startThread) {
clear();
/* Unfortunately, we can't always believe the keep-alive timeout we got
* back from the server. If I'm connected through a Netscape proxy
* to a server that sent me a keep-alive
* time of 15 sec, the proxy unilaterally terminates my connection
* The robustness to get around this is in HttpClient.parseHTTP()
*/
final KeepAliveCache cache = this;
AccessController.doPrivileged(new PrivilegedAction<>() {
public Void run() {
keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
keepAliveTimer.setDaemon(true);
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
keepAliveTimer.start();
return null;
public void put(final URL url, Object obj, HttpClient http) {
cacheLock.lock();
try {
boolean startThread = (keepAliveTimer == null);
if (!startThread) {
if (!keepAliveTimer.isAlive()) {
startThread = true;
}
});
}
}
if (startThread) {
clear();
/* Unfortunately, we can't always believe the keep-alive timeout we got
* back from the server. If I'm connected through a Netscape proxy
* to a server that sent me a keep-alive
* time of 15 sec, the proxy unilaterally terminates my connection
* The robustness to get around this is in HttpClient.parseHTTP()
*/
final KeepAliveCache cache = this;
AccessController.doPrivileged(new PrivilegedAction<>() {
public Void run() {
keepAliveTimer = InnocuousThread.newSystemThread("Keep-Alive-Timer", cache);
keepAliveTimer.setDaemon(true);
keepAliveTimer.setPriority(Thread.MAX_PRIORITY - 2);
keepAliveTimer.start();
return null;
}
});
}
KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);
KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);
if (v == null) {
int keepAliveTimeout = http.getKeepAliveTimeout();
v = new ClientVector(keepAliveTimeout > 0 ?
keepAliveTimeout * 1000 : LIFETIME);
v.put(http);
super.put(key, v);
} else {
v.put(http);
if (v == null) {
int keepAliveTimeout = http.getKeepAliveTimeout();
v = new ClientVector(keepAliveTimeout > 0 ?
keepAliveTimeout * 1000 : LIFETIME);
v.put(http);
super.put(key, v);
} else {
v.put(http);
}
} finally {
cacheLock.unlock();
}
}
/* remove an obsolete HttpClient from its VectorCache */
public synchronized void remove(HttpClient h, Object obj) {
KeepAliveKey key = new KeepAliveKey(h.url, obj);
ClientVector v = super.get(key);
if (v != null) {
v.remove(h);
if (v.isEmpty()) {
removeVector(key);
public void remove(HttpClient h, Object obj) {
cacheLock.lock();
try {
KeepAliveKey key = new KeepAliveKey(h.url, obj);
ClientVector v = super.get(key);
if (v != null) {
v.remove(h);
if (v.isEmpty()) {
removeVector(key);
}
}
} finally {
cacheLock.unlock();
}
}
/* called by a clientVector thread when all its connections have timed out
* and that vector of connections should be removed.
*/
synchronized void removeVector(KeepAliveKey k) {
private void removeVector(KeepAliveKey k) {
assert cacheLock.isHeldByCurrentThread();
super.remove(k);
}
/**
* Check to see if this URL has a cached HttpClient
*/
public synchronized HttpClient get(URL url, Object obj) {
KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);
if (v == null) { // nothing in cache yet
return null;
public HttpClient get(URL url, Object obj) {
cacheLock.lock();
try {
KeepAliveKey key = new KeepAliveKey(url, obj);
ClientVector v = super.get(key);
if (v == null) { // nothing in cache yet
return null;
}
return v.get();
} finally {
cacheLock.unlock();
}
return v.get();
}
/* Sleeps for an alloted timeout, then checks for timed out connections.
@ -170,13 +190,15 @@ public class KeepAliveCache
} catch (InterruptedException e) {}
// Remove all outdated HttpClients.
synchronized (this) {
cacheLock.lock();
try {
long currentTime = System.currentTimeMillis();
List<KeepAliveKey> keysToRemove = new ArrayList<>();
for (KeepAliveKey key : keySet()) {
ClientVector v = get(key);
synchronized (v) {
v.lock();
try {
KeepAliveEntry e = v.peek();
while (e != null) {
if ((currentTime - e.idleStartTime) > v.nap) {
@ -191,12 +213,16 @@ public class KeepAliveCache
if (v.isEmpty()) {
keysToRemove.add(key);
}
} finally {
v.unlock();
}
}
for (KeepAliveKey key : keysToRemove) {
removeVector(key);
}
} finally {
cacheLock.unlock();
}
} while (!isEmpty());
}
@ -223,6 +249,7 @@ public class KeepAliveCache
class ClientVector extends ArrayDeque<KeepAliveEntry> {
@java.io.Serial
private static final long serialVersionUID = -8680532108106489459L;
private final ReentrantLock lock = new ReentrantLock();
// sleep time in milliseconds, before cache clear
int nap;
@ -231,42 +258,65 @@ class ClientVector extends ArrayDeque<KeepAliveEntry> {
this.nap = nap;
}
synchronized HttpClient get() {
if (isEmpty()) {
return null;
}
// Loop until we find a connection that has not timed out
HttpClient hc = null;
long currentTime = System.currentTimeMillis();
do {
KeepAliveEntry e = pop();
if ((currentTime - e.idleStartTime) > nap) {
e.hc.closeServer();
} else {
hc = e.hc;
HttpClient get() {
lock();
try {
if (isEmpty()) {
return null;
}
} while ((hc == null) && (!isEmpty()));
return hc;
// Loop until we find a connection that has not timed out
HttpClient hc = null;
long currentTime = System.currentTimeMillis();
do {
KeepAliveEntry e = pop();
if ((currentTime - e.idleStartTime) > nap) {
e.hc.closeServer();
} else {
hc = e.hc;
}
} while ((hc == null) && (!isEmpty()));
return hc;
} finally {
unlock();
}
}
/* return a still valid, unused HttpClient */
synchronized void put(HttpClient h) {
if (size() >= KeepAliveCache.getMaxConnections()) {
h.closeServer(); // otherwise the connection remains in limbo
} else {
push(new KeepAliveEntry(h, System.currentTimeMillis()));
void put(HttpClient h) {
lock();
try {
if (size() >= KeepAliveCache.getMaxConnections()) {
h.closeServer(); // otherwise the connection remains in limbo
} else {
push(new KeepAliveEntry(h, System.currentTimeMillis()));
}
} finally {
unlock();
}
}
/* remove an HttpClient */
synchronized boolean remove(HttpClient h) {
for (KeepAliveEntry curr : this) {
if (curr.hc == h) {
return super.remove(curr);
boolean remove(HttpClient h) {
lock();
try {
for (KeepAliveEntry curr : this) {
if (curr.hc == h) {
return super.remove(curr);
}
}
return false;
} finally {
unlock();
}
return false;
}
final void lock() {
lock.lock();
}
final void unlock() {
lock.unlock();
}
/*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2020, 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
@ -47,7 +47,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
boolean hurried;
// has this KeepAliveStream been put on the queue for asynchronous cleanup.
protected boolean queuedForCleanup = false;
// This flag is read from within KeepAliveCleanerEntry outside of any lock.
protected volatile boolean queuedForCleanup = false;
private static final KeepAliveStreamCleaner queue = new KeepAliveStreamCleaner();
private static Thread cleanerThread; // null
@ -64,15 +65,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
* Attempt to cache this connection
*/
public void close() throws IOException {
// If the inputstream is closed already, just return.
if (closed) {
return;
}
// If this stream has already been queued for cleanup.
if (queuedForCleanup) {
return;
}
// If the inputstream is queued for cleanup, just return.
if (queuedForCleanup) return;
// Skip past the data that's left in the Inputstream because
// some sort of error may have occurred.
@ -81,34 +75,45 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
// to hang around for nothing. So if we can't skip without blocking
// we just close the socket and, therefore, terminate the keepAlive
// NOTE: Don't close super class
// For consistency, access to `expected` and `count` should be
// protected by readLock
lock();
try {
if (expected > count) {
long nskip = expected - count;
if (nskip <= available()) {
do {} while ((nskip = (expected - count)) > 0L
&& skip(Math.min(nskip, available())) > 0L);
} else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
//put this KeepAliveStream on the queue so that the data remaining
//on the socket can be cleanup asyncronously.
queueForCleanup(new KeepAliveCleanerEntry(this, hc));
} else {
hc.closeServer();
// If the inputstream is closed already, or if this stream
// has already been queued for cleanup, just return.
if (closed || queuedForCleanup) return;
try {
if (expected > count) {
long nskip = expected - count;
if (nskip <= available()) {
do {
} while ((nskip = (expected - count)) > 0L
&& skip(Math.min(nskip, available())) > 0L);
} else if (expected <= KeepAliveStreamCleaner.MAX_DATA_REMAINING && !hurried) {
//put this KeepAliveStream on the queue so that the data remaining
//on the socket can be cleanup asyncronously.
queueForCleanup(new KeepAliveCleanerEntry(this, hc));
} else {
hc.closeServer();
}
}
if (!closed && !hurried && !queuedForCleanup) {
hc.finished();
}
} finally {
if (pi != null)
pi.finishTracking();
if (!queuedForCleanup) {
// nulling out the underlying inputstream as well as
// httpClient to let gc collect the memories faster
in = null;
hc = null;
closed = true;
}
}
if (!closed && !hurried && !queuedForCleanup) {
hc.finished();
}
} finally {
if (pi != null)
pi.finishTracking();
if (!queuedForCleanup) {
// nulling out the underlying inputstream as well as
// httpClient to let gc collect the memories faster
in = null;
hc = null;
closed = true;
}
unlock();
}
}
@ -124,7 +129,8 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
throw new IOException("mark/reset not supported");
}
public synchronized boolean hurry() {
public boolean hurry() {
lock();
try {
/* CASE 0: we're actually already done */
if (closed || count >= expected) {
@ -147,11 +153,14 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
} catch (IOException e) {
// e.printStackTrace();
return false;
} finally {
unlock();
}
}
private static void queueForCleanup(KeepAliveCleanerEntry kace) {
synchronized(queue) {
queue.lock();
try {
if(!kace.getQueuedForCleanup()) {
if (!queue.offer(kace)) {
kace.getHttpClient().closeServer();
@ -159,7 +168,7 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
}
kace.setQueuedForCleanup();
queue.notifyAll();
queue.signalAll();
}
boolean startCleanupThread = (cleanerThread == null);
@ -181,14 +190,20 @@ class KeepAliveStream extends MeteredStream implements Hurryable {
}
});
}
} // queue
} finally {
queue.unlock();
}
}
// Only called from KeepAliveStreamCleaner
protected long remainingToRead() {
assert isLockHeldByCurrentThread();
return expected - count;
}
// Only called from KeepAliveStreamCleaner
protected void setClosed() {
assert isLockHeldByCurrentThread();
in = null;
hc = null;
closed = true;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2020, 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
@ -30,6 +30,8 @@ import java.util.LinkedList;
import sun.net.NetProperties;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* This class is used to cleanup any remaining data that may be on a KeepAliveStream
@ -78,6 +80,20 @@ class KeepAliveStreamCleaner
}
private final ReentrantLock queueLock = new ReentrantLock();
private final Condition waiter = queueLock.newCondition();
final void signalAll() {
waiter.signalAll();
}
final void lock() {
queueLock.lock();
}
final void unlock() {
queueLock.unlock();
}
@Override
public boolean offer(KeepAliveCleanerEntry e) {
@ -94,11 +110,12 @@ class KeepAliveStreamCleaner
do {
try {
synchronized(this) {
lock();
try {
long before = System.currentTimeMillis();
long timeout = TIMEOUT;
while ((kace = poll()) == null) {
this.wait(timeout);
waiter.wait(timeout);
long after = System.currentTimeMillis();
long elapsed = after - before;
@ -110,6 +127,8 @@ class KeepAliveStreamCleaner
before = after;
timeout -= elapsed;
}
} finally {
unlock();
}
if(kace == null)
@ -118,7 +137,8 @@ class KeepAliveStreamCleaner
KeepAliveStream kas = kace.getKeepAliveStream();
if (kas != null) {
synchronized(kas) {
kas.lock();
try {
HttpClient hc = kace.getHttpClient();
try {
if (hc != null && !hc.isInKeepAliveCache()) {
@ -147,6 +167,8 @@ class KeepAliveStreamCleaner
} finally {
kas.setClosed();
}
} finally {
kas.unlock();
}
}
} catch (InterruptedException ie) { }

View File

@ -32,8 +32,10 @@ import java.util.HashMap;
/**
* @author Michael McMahon
*/
public class AuthCacheImpl implements AuthCache {
// No blocking IO is performed within the synchronized code blocks
// in this class, so there is no need to convert this class to using
// java.util.concurrent.locks
HashMap<String,LinkedList<AuthCacheValue>> hashtable;
public AuthCacheImpl () {
@ -46,7 +48,6 @@ public class AuthCacheImpl implements AuthCache {
// put a value in map according to primary key + secondary key which
// is the path field of AuthenticationInfo
public synchronized void put (String pkey, AuthCacheValue value) {
LinkedList<AuthCacheValue> list = hashtable.get (pkey);
String skey = value.getPath();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2020, 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
@ -31,6 +31,8 @@ import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import sun.net.www.HeaderParser;
@ -125,8 +127,9 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
* at the same time, then all but the first will block until
* the first completes its authentication.
*/
private static HashMap<String,Thread> requests = new HashMap<>();
private static final HashMap<String,Thread> requests = new HashMap<>();
private static final ReentrantLock requestLock = new ReentrantLock();
private static final Condition requestFinished = requestLock.newCondition();
/*
* check if AuthenticationInfo is available in the cache.
* If not, check if a request for this destination is in progress
@ -142,8 +145,9 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
// and we can revert to concurrent requests
return cached;
}
synchronized (requests) {
// check again after synchronizing, and if available
requestLock.lock();
try {
// check again after locking, and if available
// just return the cached value.
cached = cache.apply(key);
if (cached != null) return cached;
@ -164,10 +168,10 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
// Otherwise, an other thread is currently performing authentication:
// wait until it finishes.
while (requests.containsKey(key)) {
try {
requests.wait ();
} catch (InterruptedException e) {}
requestFinished.awaitUninterruptibly();
}
} finally {
requestLock.unlock();
}
/* entry may be in cache now. */
return cache.apply(key);
@ -177,13 +181,16 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
* so that other threads can continue.
*/
private static void requestCompleted (String key) {
synchronized (requests) {
requestLock.lock();
try {
Thread thread = requests.get(key);
if (thread != null && thread == Thread.currentThread()) {
boolean waspresent = requests.remove(key) != null;
assert waspresent;
}
requests.notifyAll();
requestFinished.signalAll();
} finally {
requestLock.unlock();
}
}
@ -414,9 +421,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
if (!serializeAuth) {
return;
}
synchronized (requests) {
requestCompleted(key);
}
requestCompleted(key);
}
/**
@ -500,6 +505,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
String s1, s2; /* used for serialization of pw */
@java.io.Serial
// should be safe to keep synchronized here
private synchronized void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException
{
@ -512,6 +518,7 @@ public abstract class AuthenticationInfo extends AuthCacheValue implements Clone
}
@java.io.Serial
// should be safe to keep synchronized here
private synchronized void writeObject(java.io.ObjectOutputStream s)
throws IOException
{

View File

@ -141,6 +141,9 @@ class BasicAuthentication extends AuthenticationInfo {
*/
@Override
public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
// no need to synchronize here:
// already locked by s.n.w.p.h.HttpURLConnection
assert conn.isLockHeldByCurrentThread();
conn.setAuthenticationProperty(getHeaderName(), getHeaderValue(null,null));
return true;
}

View File

@ -78,6 +78,9 @@ class DigestAuthentication extends AuthenticationInfo {
// One instance of these may be shared among several DigestAuthentication
// instances as a result of a single authorization (for multiple domains)
// There don't appear to be any blocking IO calls performed from
// within the synchronized code blocks in the Parameters class, so there don't
// seem to be any need to migrate it to using java.util.concurrent.locks
static class Parameters implements java.io.Serializable {
private static final long serialVersionUID = -3584543755194526252L;
@ -298,6 +301,10 @@ class DigestAuthentication extends AuthenticationInfo {
*/
@Override
public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
// no need to synchronize here:
// already locked by s.n.w.p.h.HttpURLConnection
assert conn.isLockHeldByCurrentThread();
params.setNonce (p.findValue("nonce"));
params.setOpaque (p.findValue("opaque"));
params.setQop (p.findValue("qop"));

View File

@ -81,6 +81,8 @@ import java.net.MalformedURLException;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.locks.ReentrantLock;
import static sun.net.www.protocol.http.AuthScheme.BASIC;
import static sun.net.www.protocol.http.AuthScheme.DIGEST;
import static sun.net.www.protocol.http.AuthScheme.NTLM;
@ -97,7 +99,7 @@ import sun.security.action.GetPropertyAction;
public class HttpURLConnection extends java.net.HttpURLConnection {
static String HTTP_CONNECT = "CONNECT";
static final String HTTP_CONNECT = "CONNECT";
static final String version;
public static final String userAgent;
@ -353,7 +355,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* - getOutputStream()
* - getInputStream())
* - connect()
* Access synchronized on this.
* Access is protected by connectionLock.
*/
private boolean connecting = false;
@ -432,6 +434,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
private static final PlatformLogger logger =
PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
/* Lock */
private final ReentrantLock connectionLock = new ReentrantLock();
private final void lock() {
connectionLock.lock();
}
private final void unlock() {
connectionLock.unlock();
}
public final boolean isLockHeldByCurrentThread() {
return connectionLock.isHeldByCurrentThread();
}
/*
* privileged request password authentication
*
@ -514,13 +532,18 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
}
@Override
public synchronized void setAuthenticator(Authenticator auth) {
if (connecting || connected) {
throw new IllegalStateException(
"Authenticator must be set before connecting");
public void setAuthenticator(Authenticator auth) {
lock();
try {
if (connecting || connected) {
throw new IllegalStateException(
"Authenticator must be set before connecting");
}
authenticator = Objects.requireNonNull(auth);
authenticatorKey = AuthenticatorKeys.getKey(authenticator);
} finally {
unlock();
}
authenticator = Objects.requireNonNull(auth);
authenticatorKey = AuthenticatorKeys.getKey(authenticator);
}
public String getAuthenticatorKey() {
@ -563,18 +586,25 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
}
}
public synchronized void setRequestMethod(String method)
public void setRequestMethod(String method)
throws ProtocolException {
if (connecting) {
throw new IllegalStateException("connect in progress");
lock();
try {
if (connecting) {
throw new IllegalStateException("connect in progress");
}
super.setRequestMethod(method);
} finally {
unlock();
}
super.setRequestMethod(method);
}
/* adds the standard key/val pairs to reqests if necessary & write to
* given PrintStream
*/
private void writeRequests() throws IOException {
assert isLockHeldByCurrentThread();
/* print all message headers in the MessageHeader
* onto the wire - all the ones we've set and any
* others that have been set
@ -683,6 +713,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
}
} else if (poster != null) {
/* add Content-Length & POST/PUT data */
// safe to synchronize on poster: this is
// a simple subclass of ByteArrayOutputStream
synchronized (poster) {
/* close it, so no more data can be added */
poster.close();
@ -1010,8 +1042,11 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
// overridden in HTTPS subclass
public void connect() throws IOException {
synchronized (this) {
lock();
try {
connecting = true;
} finally {
unlock();
}
plainConnect();
}
@ -1057,11 +1092,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
return host + ":" + Integer.toString(port);
}
protected void plainConnect() throws IOException {
synchronized (this) {
protected void plainConnect() throws IOException {
lock();
try {
if (connected) {
return;
}
} finally {
unlock();
}
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
@ -1330,28 +1368,34 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
*/
@Override
public synchronized OutputStream getOutputStream() throws IOException {
connecting = true;
SocketPermission p = URLtoSocketPermission(this.url);
public OutputStream getOutputStream() throws IOException {
lock();
try {
connecting = true;
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
try {
return AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public OutputStream run() throws IOException {
return getOutputStream0();
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
if (p != null) {
try {
return AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public OutputStream run() throws IOException {
return getOutputStream0();
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
} else {
return getOutputStream0();
}
} else {
return getOutputStream0();
} finally {
unlock();
}
}
private synchronized OutputStream getOutputStream0() throws IOException {
private OutputStream getOutputStream0() throws IOException {
assert isLockHeldByCurrentThread();
try {
if (!doOutput) {
throw new ProtocolException("cannot write to a URLConnection"
@ -1441,16 +1485,19 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
// we only want to capture the user defined Cookies once, as
// they cannot be changed by user code after we are connected,
// only internally.
synchronized (this) {
if (setUserCookies) {
int k = requests.getKey("Cookie");
if (k != -1)
userCookies = requests.getValue(k);
k = requests.getKey("Cookie2");
if (k != -1)
userCookies2 = requests.getValue(k);
setUserCookies = false;
}
// we should only reach here when called from
// writeRequest, which in turn is only called by
// getInputStream0
assert isLockHeldByCurrentThread();
if (setUserCookies) {
int k = requests.getKey("Cookie");
if (k != -1)
userCookies = requests.getValue(k);
k = requests.getKey("Cookie2");
if (k != -1)
userCookies2 = requests.getValue(k);
setUserCookies = false;
}
// remove old Cookie header before setting new one.
@ -1508,30 +1555,36 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
}
@Override
public synchronized InputStream getInputStream() throws IOException {
connecting = true;
SocketPermission p = URLtoSocketPermission(this.url);
public InputStream getInputStream() throws IOException {
lock();
try {
connecting = true;
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
try {
return AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public InputStream run() throws IOException {
return getInputStream0();
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
if (p != null) {
try {
return AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<>() {
public InputStream run() throws IOException {
return getInputStream0();
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
} else {
return getInputStream0();
}
} else {
return getInputStream0();
} finally {
unlock();
}
}
@SuppressWarnings("empty-statement")
private synchronized InputStream getInputStream0() throws IOException {
private InputStream getInputStream0() throws IOException {
assert isLockHeldByCurrentThread();
if (!doInput) {
throw new ProtocolException("Cannot read from URLConnection"
+ " if doInput=false (call setDoInput(true))");
@ -2010,6 +2063,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
*/
private AuthenticationInfo
resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) throws IOException {
// Only called from getInputStream0 and doTunneling0
assert isLockHeldByCurrentThread();
if ((proxyAuthentication != null )&&
proxyAuthentication.getAuthScheme() != NTLM) {
String raw = auth.raw();
@ -2060,7 +2117,16 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
/**
* establish a tunnel through proxy server
*/
public synchronized void doTunneling() throws IOException {
public void doTunneling() throws IOException {
lock();
try {
doTunneling0();
} finally{
unlock();
}
}
private void doTunneling0() throws IOException {
int retryTunnel = 0;
String statusLine = "";
int respCode = 0;
@ -2068,6 +2134,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
String proxyHost = null;
int proxyPort = -1;
assert isLockHeldByCurrentThread();
// save current requests so that they can be restored after tunnel is setup.
MessageHeader savedRequests = requests;
requests = new MessageHeader();
@ -2273,7 +2341,10 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* the connection.
*/
@SuppressWarnings("fallthrough")
private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) {
private AuthenticationInfo getHttpProxyAuthentication(AuthenticationHeader authhdr) {
assert isLockHeldByCurrentThread();
/* get authorization from authenticator */
AuthenticationInfo ret = null;
String raw = authhdr.raw();
@ -2434,11 +2505,15 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
/**
* Gets the authentication for an HTTP server, and applies it to
* the connection.
* @param authHdr the AuthenticationHeader which tells what auth scheme is
* @param authhdr the AuthenticationHeader which tells what auth scheme is
* preferred.
*/
@SuppressWarnings("fallthrough")
private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
private AuthenticationInfo getServerAuthentication(AuthenticationHeader authhdr) {
// Only called from getInputStream0
assert isLockHeldByCurrentThread();
/* get authorization from authenticator */
AuthenticationInfo ret = null;
String raw = authhdr.raw();
@ -2716,6 +2791,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
private boolean followRedirect0(String loc, int stat, URL locUrl)
throws IOException
{
assert isLockHeldByCurrentThread();
disconnectInternal();
if (streaming()) {
throw new HttpRetryException (RETRY_MSG3, stat, loc);
@ -3195,17 +3272,22 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* @param value the value to be set
*/
@Override
public synchronized void setRequestProperty(String key, String value) {
if (connected || connecting)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException ("key is null");
public void setRequestProperty(String key, String value) {
lock();
try {
if (connected || connecting)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException("key is null");
if (isExternalMessageHeaderAllowed(key, value)) {
requests.set(key, value);
if (!key.equalsIgnoreCase("Content-Type")) {
userHeaders.set(key, value);
if (isExternalMessageHeaderAllowed(key, value)) {
requests.set(key, value);
if (!key.equalsIgnoreCase("Content-Type")) {
userHeaders.set(key, value);
}
}
} finally {
unlock();
}
}
@ -3221,21 +3303,26 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* @param key the keyword by which the request is known
* (e.g., "<code>accept</code>").
* @param value the value associated with it.
* @see #getRequestProperties(java.lang.String)
* @see #getRequestProperty(java.lang.String)
* @since 1.4
*/
@Override
public synchronized void addRequestProperty(String key, String value) {
if (connected || connecting)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException ("key is null");
public void addRequestProperty(String key, String value) {
lock();
try {
if (connected || connecting)
throw new IllegalStateException("Already connected");
if (key == null)
throw new NullPointerException("key is null");
if (isExternalMessageHeaderAllowed(key, value)) {
requests.add(key, value);
if (!key.equalsIgnoreCase("Content-Type")) {
if (isExternalMessageHeaderAllowed(key, value)) {
requests.add(key, value);
if (!key.equalsIgnoreCase("Content-Type")) {
userHeaders.add(key, value);
}
}
} finally {
unlock();
}
}
@ -3244,31 +3331,41 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
// the connected test.
//
public void setAuthenticationProperty(String key, String value) {
// Only called by the implementation of AuthenticationInfo::setHeaders(...)
// in AuthenticationInfo subclasses, which is only called from
// methods from HttpURLConnection protected by the connectionLock.
assert isLockHeldByCurrentThread();
checkMessageHeader(key, value);
requests.set(key, value);
}
@Override
public synchronized String getRequestProperty (String key) {
if (key == null) {
return null;
}
// don't return headers containing security sensitive information
for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
public String getRequestProperty (String key) {
lock();
try {
if (key == null) {
return null;
}
}
if (!setUserCookies) {
if (key.equalsIgnoreCase("Cookie")) {
return userCookies;
// don't return headers containing security sensitive information
for (int i = 0; i < EXCLUDE_HEADERS.length; i++) {
if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
return null;
}
}
if (key.equalsIgnoreCase("Cookie2")) {
return userCookies2;
if (!setUserCookies) {
if (key.equalsIgnoreCase("Cookie")) {
return userCookies;
}
if (key.equalsIgnoreCase("Cookie2")) {
return userCookies2;
}
}
return requests.findValue(key);
} finally {
unlock();
}
return requests.findValue(key);
}
/**
@ -3284,29 +3381,34 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* @since 1.4
*/
@Override
public synchronized Map<String, List<String>> getRequestProperties() {
if (connected)
throw new IllegalStateException("Already connected");
public Map<String, List<String>> getRequestProperties() {
lock();
try {
if (connected)
throw new IllegalStateException("Already connected");
// exclude headers containing security-sensitive info
if (setUserCookies) {
return requests.getHeaders(EXCLUDE_HEADERS);
}
/*
* The cookies in the requests message headers may have
* been modified. Use the saved user cookies instead.
*/
Map<String, List<String>> userCookiesMap = null;
if (userCookies != null || userCookies2 != null) {
userCookiesMap = new HashMap<>();
if (userCookies != null) {
userCookiesMap.put("Cookie", Arrays.asList(userCookies));
// exclude headers containing security-sensitive info
if (setUserCookies) {
return requests.getHeaders(EXCLUDE_HEADERS);
}
if (userCookies2 != null) {
userCookiesMap.put("Cookie2", Arrays.asList(userCookies2));
/*
* The cookies in the requests message headers may have
* been modified. Use the saved user cookies instead.
*/
Map<String, List<String>> userCookiesMap = null;
if (userCookies != null || userCookies2 != null) {
userCookiesMap = new HashMap<>();
if (userCookies != null) {
userCookiesMap.put("Cookie", Arrays.asList(userCookies));
}
if (userCookies2 != null) {
userCookiesMap.put("Cookie2", Arrays.asList(userCookies2));
}
}
return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
} finally {
unlock();
}
return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
}
@Override
@ -3350,7 +3452,7 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* value to be used in milliseconds
* @throws IllegalArgumentException if the timeout parameter is negative
*
* @see java.net.URLConnectiongetReadTimeout()
* @see java.net.URLConnection#getReadTimeout()
* @see java.io.InputStream#read()
* @since 1.5
*/
@ -3469,6 +3571,9 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* @see java.io.FilterInputStream#in
* @see java.io.FilterInputStream#reset()
*/
// safe to use synchronized here: super method is synchronized too
// and involves no blocking operation; only mark & reset are
// synchronized in the super class hierarchy.
@Override
public synchronized void mark(int readlimit) {
super.mark(readlimit);
@ -3499,6 +3604,9 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
* @see java.io.FilterInputStream#in
* @see java.io.FilterInputStream#mark(int)
*/
// safe to use synchronized here: super method is synchronized too
// and involves no blocking operation; only mark & reset are
// synchronized in the super class hierarchy.
@Override
public synchronized void reset() throws IOException {
super.reset();
@ -3679,8 +3787,14 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
if (error) {
throw errorExcp;
}
if (((PrintStream)out).checkError()) {
throw new IOException("Error writing request body to server");
if (out instanceof PrintStream) {
if (((PrintStream) out).checkError()) {
throw new IOException("Error writing request body to server");
}
} else if (out instanceof ChunkedOutputStream) {
if (((ChunkedOutputStream) out).checkError()) {
throw new IOException("Error writing request body to server");
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2020, 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
@ -30,6 +30,8 @@ import java.io.IOException;
import java.net.Authenticator.RequestorType;
import java.util.Base64;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock;
import sun.net.www.HeaderParser;
import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
@ -57,6 +59,8 @@ class NegotiateAuthentication extends AuthenticationInfo {
// the cache can be used only once, so after the first use, it's cleaned.
static HashMap <String, Boolean> supported = null;
static ThreadLocal <HashMap <String, Negotiator>> cache = null;
private static final ReentrantLock negotiateLock = new ReentrantLock();
/* Whether cache is enabled for Negotiate/Kerberos */
private static final boolean cacheSPNEGO;
static {
@ -101,40 +105,50 @@ class NegotiateAuthentication extends AuthenticationInfo {
*
* @return true if supported
*/
synchronized public static boolean isSupported(HttpCallerInfo hci) {
if (supported == null) {
supported = new HashMap<>();
}
String hostname = hci.host;
hostname = hostname.toLowerCase();
if (supported.containsKey(hostname)) {
return supported.get(hostname);
}
Negotiator neg = Negotiator.getNegotiator(hci);
if (neg != null) {
supported.put(hostname, true);
// the only place cache.put is called. here we can make sure
// the object is valid and the oneToken inside is not null
if (cache == null) {
cache = new ThreadLocal<>() {
@Override
protected HashMap<String, Negotiator> initialValue() {
return new HashMap<>();
}
};
public static boolean isSupported(HttpCallerInfo hci) {
negotiateLock.lock();
try {
if (supported == null) {
supported = new HashMap<>();
}
cache.get().put(hostname, neg);
return true;
} else {
supported.put(hostname, false);
return false;
String hostname = hci.host;
hostname = hostname.toLowerCase();
if (supported.containsKey(hostname)) {
return supported.get(hostname);
}
Negotiator neg = Negotiator.getNegotiator(hci);
if (neg != null) {
supported.put(hostname, true);
// the only place cache.put is called. here we can make sure
// the object is valid and the oneToken inside is not null
if (cache == null) {
cache = new ThreadLocal<>() {
@Override
protected HashMap<String, Negotiator> initialValue() {
return new HashMap<>();
}
};
}
cache.get().put(hostname, neg);
return true;
} else {
supported.put(hostname, false);
return false;
}
} finally {
negotiateLock.unlock();
}
}
private static synchronized HashMap<String, Negotiator> getCache() {
if (cache == null) return null;
return cache.get();
private static HashMap<String, Negotiator> getCache() {
negotiateLock.lock();
try {
if (cache == null) return null;
return cache.get();
} finally {
negotiateLock.unlock();
}
}
@Override
@ -172,7 +186,10 @@ class NegotiateAuthentication extends AuthenticationInfo {
* @return true if all goes well, false if no headers were set.
*/
@Override
public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
// no need to synchronize here:
// already locked by s.n.w.p.h.HttpURLConnection
assert conn.isLockHeldByCurrentThread();
try {
String response;

View File

@ -341,8 +341,10 @@ final class HttpsClient extends HttpClient
boolean compatible = ((ret.proxy != null && ret.proxy.equals(p)) ||
(ret.proxy == null && p == Proxy.NO_PROXY))
&& Objects.equals(ret.getAuthenticatorKey(), ak);
if (compatible) {
synchronized (ret) {
ret.lock();
try {
ret.cachedHttpClient = true;
assert ret.inCache;
ret.inCache = false;
@ -351,18 +353,23 @@ final class HttpsClient extends HttpClient
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
logger.finest("KeepAlive stream retrieved from the cache, " + ret);
}
} finally {
ret.unlock();
}
} else {
// We cannot return this connection to the cache as it's
// KeepAliveTimeout will get reset. We simply close the connection.
// This should be fine as it is very rare that a connection
// to the same host will not use the same proxy.
synchronized(ret) {
ret.lock();
try {
if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
logger.finest("Not returning this connection to cache: " + ret);
}
ret.inCache = false;
ret.closeServer();
} finally {
ret.unlock();
}
ret = null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2020, 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
@ -225,7 +225,10 @@ public class NTLMAuthentication extends AuthenticationInfo {
* @return true if all goes well, false if no headers were set.
*/
@Override
public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
// no need to synchronize here:
// already locked by s.n.w.p.h.HttpURLConnection
assert conn.isLockHeldByCurrentThread();
try {
String response;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2020, 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
@ -244,7 +244,11 @@ public class NTLMAuthentication extends AuthenticationInfo {
* @return true if all goes well, false if no headers were set.
*/
@Override
public synchronized boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
public boolean setHeaders(HttpURLConnection conn, HeaderParser p, String raw) {
// no need to synchronize here:
// already locked by s.n.w.p.h.HttpURLConnection
assert conn.isLockHeldByCurrentThread();
try {
NTLMAuthSequence seq = (NTLMAuthSequence)conn.authObj();