Skip to content

Commit

Permalink
8328286: Enhance HTTP client
Browse files Browse the repository at this point in the history
Reviewed-by: andrew, mbalao
Backport-of: cf8dc79f392c8ec3414d8b36803f026852c4e386
  • Loading branch information
alexeybakhtin authored and gnu-andrew committed Oct 4, 2024
1 parent 260fe06 commit 81c99d8
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 4 deletions.
8 changes: 8 additions & 0 deletions jdk/src/share/classes/java/net/doc-files/net-properties.html
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ <H2>Misc HTTP properties</H2>
property is defined, then its value will be used a the domain
name.</P>
</OL>
<LI><P><B>{@systemProperty jdk.http.maxHeaderSize}</B> (default: 393216 or 384kB)<BR>
This is the maximum header field section size that a client is prepared to accept.
This is computed as the sum of the size of the uncompressed header name, plus
the size of the uncompressed header value, plus an overhead of 32 bytes for
each field section line. If a peer sends a field section that exceeds this
size a {@link java.net.ProtocolException ProtocolException} will be raised.
This applies to all versions of the HTTP protocol. A value of zero or a negative
value means no limit. If left unspecified, the default value is 393216 bytes.
</UL>
<P>All these properties are checked only once at startup.</P>
<a name="AddressCache"></a>
Expand Down
55 changes: 55 additions & 0 deletions jdk/src/share/classes/sun/net/www/MessageHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
package sun.net.www;

import java.io.*;
import java.lang.reflect.Array;
import java.net.ProtocolException;
import java.util.Collections;
import java.util.*;

Expand All @@ -46,11 +48,32 @@ class MessageHeader {
private String values[];
private int nkeys;

// max number of bytes for headers, <=0 means unlimited;
// this corresponds to the length of the names, plus the length
// of the values, plus an overhead of 32 bytes per name: value
// pair.
// Note: we use the same definition as HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE
// see RFC 9113, section 6.5.2.
// https://www.rfc-editor.org/rfc/rfc9113.html#SETTINGS_MAX_HEADER_LIST_SIZE
private final int maxHeaderSize;

// Aggregate size of the field lines (name + value + 32) x N
// that have been parsed and accepted so far.
// This is defined as a long to force promotion to long
// and avoid overflows; see checkNewSize;
private long size;

public MessageHeader () {
this(0);
}

public MessageHeader (int maxHeaderSize) {
this.maxHeaderSize = maxHeaderSize;
grow();
}

public MessageHeader (InputStream is) throws java.io.IOException {
maxHeaderSize = 0;
parseHeader(is);
}

Expand Down Expand Up @@ -466,10 +489,28 @@ public static String canonicalID(String id) {
public void parseHeader(InputStream is) throws java.io.IOException {
synchronized (this) {
nkeys = 0;
size = 0;
}
mergeHeader(is);
}

private void checkMaxHeaderSize(int sz) throws ProtocolException {
if (maxHeaderSize > 0) checkNewSize(size, sz, 0);
}

private long checkNewSize(long size, int name, int value) throws ProtocolException {
// See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2.
long newSize = size + name + value + 32;
if (maxHeaderSize > 0 && newSize > maxHeaderSize) {
Arrays.fill(keys, 0, nkeys, null);
Arrays.fill(values,0, nkeys, null);
nkeys = 0;
throw new ProtocolException(String.format("Header size too big: %s > %s",
newSize, maxHeaderSize));
}
return newSize;
}

/** Parse and merge a MIME header from an input stream. */
@SuppressWarnings("fallthrough")
public void mergeHeader(InputStream is) throws java.io.IOException {
Expand All @@ -483,7 +524,15 @@ public void mergeHeader(InputStream is) throws java.io.IOException {
int c;
boolean inKey = firstc > ' ';
s[len++] = (char) firstc;
checkMaxHeaderSize(len);
parseloop:{
// We start parsing for a new name value pair here.
// The max header size includes an overhead of 32 bytes per
// name value pair.
// See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2.
long maxRemaining = maxHeaderSize > 0
? maxHeaderSize - size - 32
: Long.MAX_VALUE;
while ((c = is.read()) >= 0) {
switch (c) {
case ':':
Expand Down Expand Up @@ -517,6 +566,9 @@ public void mergeHeader(InputStream is) throws java.io.IOException {
s = ns;
}
s[len++] = (char) c;
if (maxHeaderSize > 0 && len > maxRemaining) {
checkMaxHeaderSize(len);
}
}
firstc = -1;
}
Expand All @@ -538,6 +590,9 @@ public void mergeHeader(InputStream is) throws java.io.IOException {
v = new String();
else
v = String.copyValueOf(s, keyend, len - keyend);
int klen = k == null ? 0 : k.length();

size = checkNewSize(size, klen, v.length());
add(k, v);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection {
*/
private static int bufSize4ES = 0;

private static final int maxHeaderSize;

/*
* Restrict setting of request headers through the public api
* consistent with JavaScript XMLHttpRequest2 with a few
Expand Down Expand Up @@ -284,6 +286,19 @@ private static Set<String> schemesListToSet(String list) {
} else {
restrictedHeaderSet = null;
}

int defMaxHeaderSize = 384 * 1024;
String maxHeaderSizeStr = getNetProperty("jdk.http.maxHeaderSize");
int maxHeaderSizeVal = defMaxHeaderSize;
if (maxHeaderSizeStr != null) {
try {
maxHeaderSizeVal = Integer.parseInt(maxHeaderSizeStr);
} catch (NumberFormatException n) {
maxHeaderSizeVal = defMaxHeaderSize;
}
}
if (maxHeaderSizeVal < 0) maxHeaderSizeVal = 0;
maxHeaderSize = maxHeaderSizeVal;
}

static final String httpVersion = "HTTP/1.1";
Expand Down Expand Up @@ -707,7 +722,7 @@ private void writeRequests() throws IOException {
}
ps = (PrintStream) http.getOutputStream();
connected=true;
responses = new MessageHeader();
responses = new MessageHeader(maxHeaderSize);
setRequests=false;
writeRequests();
}
Expand Down Expand Up @@ -862,7 +877,7 @@ protected HttpURLConnection(URL u, Proxy p, Handler handler)
throws IOException {
super(checkURL(u));
requests = new MessageHeader();
responses = new MessageHeader();
responses = new MessageHeader(maxHeaderSize);
userHeaders = new MessageHeader();
this.handler = handler;
instProxy = p;
Expand Down Expand Up @@ -2675,7 +2690,7 @@ private boolean followRedirect0(String loc, int stat, URL locUrl)
}

// clear out old response headers!!!!
responses = new MessageHeader();
responses = new MessageHeader(maxHeaderSize);
if (stat == HTTP_USE_PROXY) {
/* This means we must re-request the resource through the
* proxy denoted in the "Location:" field of the response.
Expand Down Expand Up @@ -2864,7 +2879,7 @@ private void reset() throws IOException {
} catch (IOException e) { }
}
responseCode = -1;
responses = new MessageHeader();
responses = new MessageHeader(maxHeaderSize);
connected = false;
}

Expand Down
17 changes: 17 additions & 0 deletions jdk/src/share/lib/net.properties
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,20 @@ jdk.http.auth.tunneling.disabledSchemes=Basic
#jdk.http.ntlm.transparentAuth=trustedHosts
#
jdk.http.ntlm.transparentAuth=disabled

#
# Maximum HTTP field section size that a client is prepared to accept
#
# jdk.http.maxHeaderSize=393216
#
# This is the maximum header field section size that a client is prepared to accept.
# This is computed as the sum of the size of the uncompressed header name, plus
# the size of the uncompressed header value, plus an overhead of 32 bytes for
# each field section line. If a peer sends a field section that exceeds this
# size a {@link java.net.ProtocolException ProtocolException} will be raised.
# This applies to all versions of the HTTP protocol. A value of zero or a negative
# value means no limit. If left unspecified, the default value is 393216 bytes
# or 384kB.
#
# Note: This property is currently used by the JDK Reference implementation. It
# is not guaranteed to be examined and used by other implementations.

0 comments on commit 81c99d8

Please sign in to comment.