8061729: Update java/net tests to eliminate dependency on sun.net.www.MessageHeader and some other internal APIs
Reviewed-by: dfuchs
This commit is contained in:
parent
395bc141f2
commit
0f3d3ac32c
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2006, 2022, 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,14 +25,22 @@
|
||||
* @test
|
||||
* @bug 6498566
|
||||
* @summary URL.openConnection(Proxy.NO_PROXY) may connect through a proxy.
|
||||
* @modules java.base/sun.net.www
|
||||
* @library /test/lib
|
||||
* @run main/othervm ProxyFromCache
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
/* Creates a simple proxy and http server that just return 200 OK.
|
||||
@ -135,15 +143,12 @@ class SimpleServer extends Thread
|
||||
connectionCount++;
|
||||
InputStream is = sock.getInputStream();
|
||||
OutputStream os = sock.getOutputStream();
|
||||
|
||||
MessageHeader headers = new MessageHeader (is);
|
||||
HttpHeaderParser httpHeaderParser = new HttpHeaderParser(is);
|
||||
os.write(replyOK.getBytes("UTF-8"));
|
||||
|
||||
headers = new MessageHeader (is);
|
||||
httpHeaderParser = new HttpHeaderParser(is);
|
||||
// If we get here then we received a second request.
|
||||
connectionCount++;
|
||||
os.write(replyOK.getBytes("UTF-8"));
|
||||
|
||||
sock.close();
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2006, 2022, 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,13 +25,14 @@
|
||||
* @test
|
||||
* @bug 6469663
|
||||
* @summary HTTP Request-URI contains fragment when connecting through proxy
|
||||
* @modules java.base/sun.net.www
|
||||
* @library /test/lib
|
||||
* @run main/othervm RequestURI
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
|
||||
// Create a Server listening on port 5001 to act as the proxy. Requests
|
||||
// never need to be forwared from it. We are only interested in the
|
||||
@ -91,8 +92,8 @@ class RequestURIServer extends Thread
|
||||
InputStream is = sock.getInputStream();
|
||||
OutputStream os = sock.getOutputStream();
|
||||
|
||||
MessageHeader headers = new MessageHeader (is);
|
||||
String requestLine = headers.getValue(0);
|
||||
HttpHeaderParser headers = new HttpHeaderParser (is);
|
||||
String requestLine = headers.getRequestDetails();
|
||||
|
||||
int first = requestLine.indexOf(' ');
|
||||
int second = requestLine.lastIndexOf(' ');
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2004, 2022, 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
|
||||
@ -24,16 +24,23 @@
|
||||
/**
|
||||
* @test
|
||||
* @bug 6189206
|
||||
* @modules java.base/sun.net.www
|
||||
* @library /test/lib
|
||||
* @run main/othervm -Dhttp.keepAlive=false CloseOptionHeader
|
||||
* @summary HTTP client should set "Connection: close" header in request when keepalive is disabled
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
public class CloseOptionHeader implements Runnable {
|
||||
@ -49,10 +56,15 @@ public class CloseOptionHeader implements Runnable {
|
||||
|
||||
/* check the request to find close connection option header */
|
||||
InputStream is = s.getInputStream ();
|
||||
MessageHeader mh = new MessageHeader(is);
|
||||
String connHeader = mh.findValue("Connection");
|
||||
if (connHeader != null && connHeader.equalsIgnoreCase("close")) {
|
||||
hasCloseHeader = true;
|
||||
HttpHeaderParser mh = new HttpHeaderParser(is);
|
||||
List <String> connHeader = mh.getHeaderValue("Connection");
|
||||
if (connHeader != null) {
|
||||
for(String value : connHeader) {
|
||||
if (value.equalsIgnoreCase("close")) {
|
||||
hasCloseHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrintStream out = new PrintStream(
|
||||
|
498
test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java
Normal file
498
test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java
Normal file
@ -0,0 +1,498 @@
|
||||
|
||||
|
||||
/*
|
||||
* Copyright (c) 2022, 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 8061729
|
||||
* @library /test/lib
|
||||
* @summary Sanity check that HttpHeaderParser works same as MessageHeader
|
||||
* @modules java.base/sun.net.www java.base/sun.net.www.protocol.http:open
|
||||
* @run testng/othervm HttpHeaderParserTest
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.ISO_8859_1;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
public class HttpHeaderParserTest {
|
||||
@DataProvider(name = "responses")
|
||||
public Object[][] responses() {
|
||||
List<String> responses = new ArrayList<>();
|
||||
|
||||
String[] basic =
|
||||
{ "HTTP/1.1 200 OK\r\n\r\n",
|
||||
|
||||
"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" +
|
||||
"123456789",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 9\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Header: U\u00ffU\r\n" + // value with U+00FF - Extended Latin-1
|
||||
"Content-Length: 9\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 9\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" + // more than one SP after ':'
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length:\t10\r\n" +
|
||||
"Content-Type:\ttext/html; charset=UTF-8\r\n\r\n" + // HT separator
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length:\t\t10\r\n" +
|
||||
"Content-Type:\t\ttext/html; charset=UTF-8\r\n\r\n" + // more than one HT after ':'
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 407 Proxy Authorization Required\r\n" +
|
||||
"Proxy-Authenticate: Basic realm=\"a fake realm\"\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 401 Unauthorized\r\n" +
|
||||
"WWW-Authenticate: Digest realm=\"wally land\" domain=/ " +
|
||||
"nonce=\"2B7F3A2B\" qop=\"auth\"\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo:\r\n\r\n", // no value
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo:\r\n\r\n" + // no value, with response body
|
||||
"Some Response Body",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo:\r\n" + // no value, followed by another header
|
||||
"Content-Length: 10\r\n\r\n" +
|
||||
"Some Response Body",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo:\r\n" + // no value, followed by another header, with response body
|
||||
"Content-Length: 10\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo: chegar\r\n" +
|
||||
"X-Foo: dfuchs\r\n" + // same header appears multiple times
|
||||
"Content-Length: 0\r\n" +
|
||||
"X-Foo: michaelm\r\n" +
|
||||
"X-Foo: prappo\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-Foo:\r\n" + // no value, same header appears multiple times
|
||||
"X-Foo: dfuchs\r\n" +
|
||||
"Content-Length: 0\r\n" +
|
||||
"X-Foo: michaelm\r\n" +
|
||||
"X-Foo: prappo\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Accept-Ranges: bytes\r\n" +
|
||||
"Cache-control: max-age=0, no-cache=\"set-cookie\"\r\n" +
|
||||
"Content-Length: 132868\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n" +
|
||||
"Date: Sun, 05 Nov 2017 22:24:03 GMT\r\n" +
|
||||
"Server: Apache/2.4.6 (Red Hat Enterprise Linux) OpenSSL/1.0.1e-fips Communique/4.2.2\r\n" +
|
||||
"Set-Cookie: AWSELB=AF7927F5100F4202119876ED2436B5005EE;PATH=/;MAX-AGE=900\r\n" +
|
||||
"Vary: Host,Accept-Encoding,User-Agent\r\n" +
|
||||
"X-Mod-Pagespeed: 1.12.34.2-0\r\n" +
|
||||
"Connection: keep-alive\r\n\r\n"
|
||||
};
|
||||
Arrays.stream(basic).forEach(responses::add);
|
||||
// add some tests where some of the CRLF are replaced
|
||||
// by a single LF
|
||||
Arrays.stream(basic)
|
||||
.map(HttpHeaderParserTest::mixedCRLF)
|
||||
.forEach(responses::add);
|
||||
|
||||
String[] foldingTemplate =
|
||||
{ "HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 9\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r'
|
||||
" charset=UTF-8\r\n" + // one preceding SP
|
||||
"Connection: close\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEE",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 19\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r
|
||||
" charset=UTF-8\r\n" + // more than one preceding SP
|
||||
"Connection: keep-alive\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEEFFGG",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 999\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r
|
||||
"\tcharset=UTF-8\r\n" + // one preceding HT
|
||||
"Connection: close\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEE",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 54\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r
|
||||
"\t\t\tcharset=UTF-8\r\n" + // more than one preceding HT
|
||||
"Connection: keep-alive\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEEFFGG",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: -1\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r
|
||||
"\t \t \tcharset=UTF-8\r\n" + // mix of preceding HT and SP
|
||||
"Connection: keep-alive\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEEFFGGHH",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 65\r\n" +
|
||||
"Content-Type: text/html;$NEWLINE" + // folding field-value with '\n'|'\r
|
||||
" \t \t charset=UTF-8\r\n" + // mix of preceding SP and HT
|
||||
"Connection: keep-alive\r\n\r\n" +
|
||||
"XXYYZZAABBCCDDEEFFGGHHII",
|
||||
|
||||
"HTTP/1.1 401 Unauthorized\r\n" +
|
||||
"WWW-Authenticate: Digest realm=\"wally land\","
|
||||
+"$NEWLINE domain=/,"
|
||||
+"$NEWLINE nonce=\"2B7F3A2B\","
|
||||
+"$NEWLINE\tqop=\"auth\"\r\n\r\n",
|
||||
|
||||
};
|
||||
for (String newLineChar : new String[] { "\n", "\r", "\r\n" }) {
|
||||
for (String template : foldingTemplate)
|
||||
responses.add(template.replace("$NEWLINE", newLineChar));
|
||||
}
|
||||
// add some tests where some of the CRLF are replaced
|
||||
// by a single LF
|
||||
for (String newLineChar : new String[] { "\n", "\r", "\r\n" }) {
|
||||
for (String template : foldingTemplate)
|
||||
responses.add(mixedCRLF(template).replace("$NEWLINE", newLineChar));
|
||||
}
|
||||
|
||||
String[] bad = // much of this is to retain parity with legacy MessageHeaders
|
||||
{ "HTTP/1.1 200 OK\r\n" +
|
||||
"Connection:\r\n\r\n", // empty value, no body
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Connection:\r\n\r\n" + // empty value, with body
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
": no header\r\n\r\n", // no/empty header-name, no body, no following header
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
": no; header\r\n" + // no/empty header-name, no body, following header
|
||||
"Content-Length: 65\r\n\r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
": no header\r\n" + // no/empty header-name
|
||||
"Content-Length: 65\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-foo: bar\r\n" +
|
||||
" : no header\r\n" + // fold, not a blank header-name
|
||||
"Content-Length: 65\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-foo: bar\r\n" +
|
||||
" \t : no header\r\n" + // fold, not a blank header-name
|
||||
"Content-Length: 65\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
": no header\r\n\r\n" + // no/empty header-name, followed by header
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Conte\r" +
|
||||
"nt-Length: 9\r\n" + // fold/bad header name ??? without preceding space
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXXYYZZ",
|
||||
|
||||
"HTTP/1.0 404 Not Found\r\n" +
|
||||
"header-without-colon\r\n\r\n",
|
||||
|
||||
"HTTP/1.0 404 Not Found\r\n" +
|
||||
"header-without-colon\r\n\r\n" +
|
||||
"SOMEBODY",
|
||||
|
||||
};
|
||||
Arrays.stream(bad).forEach(responses::add);
|
||||
|
||||
return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
static final AtomicInteger index = new AtomicInteger();
|
||||
static final AtomicInteger limit = new AtomicInteger(1);
|
||||
static final AtomicBoolean useCRLF = new AtomicBoolean();
|
||||
// A small method to replace part of the CRLF present in a string
|
||||
// with simple LF. The method uses a deterministic algorithm based
|
||||
// on current values of static index/limit/useCRLF counters.
|
||||
// These counters are used to produce a stream of substitutes that
|
||||
// looks like this:
|
||||
// LF CRLF LF LF CRLF CRLF LF LF LF CRLF CRLF CRLF (then repeat from start)
|
||||
static final String mixedCRLF(String headers) {
|
||||
int next;
|
||||
int start = 0;
|
||||
int last = headers.lastIndexOf("\r\n");
|
||||
String prev = "";
|
||||
StringBuilder res = new StringBuilder();
|
||||
while ((next = headers.indexOf("\r\n", start)) > 0) {
|
||||
res.append(headers.substring(start, next));
|
||||
if ("\n".equals(prev) && next == last) {
|
||||
// for some reason the legacy MessageHeader parser will
|
||||
// not consume the final LF if the headers are terminated
|
||||
// by <LF><CRLF> instead of <CRLF><CRLF>. It consume
|
||||
// <LF><CR> but leaves the last <LF> in the stream.
|
||||
// Here we just make sure to avoid using <LF><CRLF>
|
||||
// as that would cause the legacy parser to consume
|
||||
// 1 byte less than the Http1HeadersParser - which
|
||||
// does consume the last <LF>, as it should.
|
||||
// if this is the last CRLF and the previous one
|
||||
// was replaced by LF then use LF.
|
||||
res.append(prev);
|
||||
} else {
|
||||
prev = useCRLF.get() ? "\r\n" : "\n";
|
||||
res.append(prev);
|
||||
}
|
||||
// skip CRLF
|
||||
start = next + 2;
|
||||
|
||||
// The idea is to substitute some of the CRLF with LF.
|
||||
// Rather than doing this randomly, always use the following
|
||||
// sequence:
|
||||
// LF CRLF LF LF CRLF CRLF LF LF LF CRLF CRLF CRLF
|
||||
index.incrementAndGet();
|
||||
if (index.get() == limit.get()) {
|
||||
index.set(0);
|
||||
if (useCRLF.get()) limit.incrementAndGet();
|
||||
if (limit.get() > 3) limit.set(1);
|
||||
useCRLF.set(!useCRLF.get());
|
||||
}
|
||||
}
|
||||
res.append(headers.substring(start));
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
|
||||
@Test(dataProvider = "responses")
|
||||
public void verifyHeaders(String respString) throws Exception {
|
||||
System.out.println("\ntesting:\n\t" + respString
|
||||
.replace("\r\n", "<CRLF>")
|
||||
.replace("\r", "<CR>")
|
||||
.replace("\n","<LF>")
|
||||
.replace("LF>", "LF>\n\t"));
|
||||
byte[] bytes = respString.getBytes(ISO_8859_1);
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
MessageHeader m = new MessageHeader(bais);
|
||||
Map<String,List<String>> messageHeaderMap = m.getHeaders();
|
||||
int availableBytes = bais.available();
|
||||
|
||||
HttpHeaderParser decoder = new HttpHeaderParser();
|
||||
ByteArrayInputStream headerStream = new ByteArrayInputStream(bytes);
|
||||
int initialBytes = headerStream.available();
|
||||
decoder.parse(headerStream);
|
||||
System.out.printf("HttpHeaderParser parsed %d bytes out of %d%n", initialBytes - headerStream.available(), bytes.length);
|
||||
Map<String,List<String>> decoderMap1 = decoder.getHeaderMap();
|
||||
|
||||
|
||||
// assert status-line
|
||||
String statusLine1 = messageHeaderMap.get(null).get(0);
|
||||
String statusLine2 = decoder.getRequestDetails();
|
||||
if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line
|
||||
assertEquals(statusLine2, statusLine1, "Status-line not equal");
|
||||
} else {
|
||||
assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1.");
|
||||
}
|
||||
|
||||
// remove the null'th entry with is the status-line
|
||||
Map<String,List<String>> map = new HashMap<>();
|
||||
for (Map.Entry<String,List<String>> e : messageHeaderMap.entrySet()) {
|
||||
if (e.getKey() != null) {
|
||||
map.put(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
messageHeaderMap = map;
|
||||
|
||||
assertHeadersEqual(messageHeaderMap, decoderMap1,
|
||||
"messageHeaderMap not equal to decoderMap1");
|
||||
|
||||
assertEquals(availableBytes, headerStream.available(),
|
||||
String.format("stream available (%d) not equal to remaining (%d)",
|
||||
availableBytes, headerStream.available()));
|
||||
}
|
||||
|
||||
@DataProvider(name = "errors")
|
||||
public Object[][] errors() {
|
||||
List<String> responses = new ArrayList<>();
|
||||
|
||||
// These responses are parsed, somewhat, by MessageHeaders but give
|
||||
// nonsensible results. They, correctly, fail with the Http1HeaderParser.
|
||||
String[] bad =
|
||||
{// "HTTP/1.1 402 Payment Required\r\n" +
|
||||
// "Content-Length: 65\r\n\r", // missing trailing LF //TODO: incomplete
|
||||
|
||||
"HTTP/1.1 402 Payment Required\r\n" +
|
||||
"Content-Length: 65\r\n\rT\r\n\r\nGGGGGG",
|
||||
|
||||
"HTTP/1.1 200OK\r\n\rT",
|
||||
|
||||
"HTTP/1.1 200OK\rT",
|
||||
|
||||
"HTTP/1.0 FOO\r\n",
|
||||
|
||||
"HTTP/1.1 BAR\r\n",
|
||||
|
||||
"HTTP/1.1 +99\r\n",
|
||||
|
||||
"HTTP/1.1 -22\r\n",
|
||||
|
||||
"HTTP/1.1 -20 \r\n",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-fo\u00ffo: foo\r\n" + // invalid char in name
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X-foo : bar\r\n" + // trim space after name
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
" X-foo: bar\r\n" + // trim space before name
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"X foo: bar\r\n" + // invalid space in name
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Content-Length: 5\r\n" +
|
||||
"Content Type: text/html; charset=UTF-8\r\n\r\n" + // invalid space in name
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
"Conte\r" +
|
||||
" nt-Length: 9\r\n" + // fold results in space in header name
|
||||
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
" : no header\r\n" + // all blank header-name (not fold)
|
||||
"Content-Length: 65\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
"HTTP/1.1 200 OK\r\n" +
|
||||
" \t : no header\r\n" + // all blank header-name (not fold)
|
||||
"Content-Length: 65\r\n\r\n" +
|
||||
"XXXXX",
|
||||
|
||||
};
|
||||
Arrays.stream(bad).forEach(responses::add);
|
||||
|
||||
return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "errors", expectedExceptions = IOException.class)
|
||||
public void errors(String respString) throws IOException {
|
||||
byte[] bytes = respString.getBytes(US_ASCII);
|
||||
HttpHeaderParser decoder = new HttpHeaderParser();
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||
decoder.parse(bais);
|
||||
}
|
||||
|
||||
void assertHeadersEqual(Map<String,List<String>> expected,
|
||||
Map<String,List<String>> actual,
|
||||
String msg) {
|
||||
|
||||
if (expected.equals(actual))
|
||||
return;
|
||||
|
||||
assertEquals(expected.size(), actual.size(),
|
||||
format("%s. Expected size %d, actual size %s. %nexpected= %s,%n actual=%s.",
|
||||
msg, expected.size(), actual.size(), mapToString(expected), mapToString(actual)));
|
||||
|
||||
for (Map.Entry<String,List<String>> e : expected.entrySet()) {
|
||||
String key = e.getKey();
|
||||
List<String> values = e.getValue();
|
||||
|
||||
boolean found = false;
|
||||
for (Map.Entry<String,List<String>> other: actual.entrySet()) {
|
||||
if (key.equalsIgnoreCase(other.getKey())) {
|
||||
found = true;
|
||||
List<String> otherValues = other.getValue();
|
||||
assertEquals(values.size(), otherValues.size(),
|
||||
format("%s. Expected list size %d, actual size %s",
|
||||
msg, values.size(), otherValues.size()));
|
||||
if (!(values.containsAll(otherValues) && otherValues.containsAll(values)))
|
||||
assertTrue(false, format("Lists are unequal [%s] [%s]", values, otherValues));
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(found, format("header name, %s, not found in %s", key, actual));
|
||||
}
|
||||
}
|
||||
|
||||
static String mapToString(Map<String,List<String>> map) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<String> sortedKeys = new ArrayList(map.keySet());
|
||||
Collections.sort(sortedKeys);
|
||||
for (String key : sortedKeys) {
|
||||
List<String> values = map.get(key);
|
||||
sb.append("\n\t" + key + " | " + values);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2007, 2022, 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
|
||||
@ -24,7 +24,6 @@
|
||||
/*
|
||||
* @test
|
||||
* @bug 6520665 6357133
|
||||
* @modules java.base/sun.net.www
|
||||
* @library /test/lib
|
||||
* @run main/othervm NTLMTest
|
||||
* @summary 6520665 & 6357133: NTLM authentication issues.
|
||||
@ -32,7 +31,8 @@
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
|
||||
public class NTLMTest
|
||||
@ -160,7 +160,7 @@ public class NTLMTest
|
||||
OutputStream os = s.getOutputStream();
|
||||
|
||||
for (int i=start; i<end; i++) {
|
||||
MessageHeader header = new MessageHeader (s.getInputStream());
|
||||
HttpHeaderParser httpHeaderParser = new HttpHeaderParser(s.getInputStream());
|
||||
//System.out.println("Input :" + header);
|
||||
//System.out.println("Output:" + resp[i]);
|
||||
os.write(resp[i].getBytes("ASCII"));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2013, 2022, 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
|
||||
@ -26,8 +26,7 @@
|
||||
* @library /test/lib
|
||||
* @summary Sanity check that NTLM will not be selected by the http protocol
|
||||
* handler when running on a profile that does not support NTLM
|
||||
* @modules java.base/sun.net.www
|
||||
* java.base/sun.net.www.protocol.http:open
|
||||
* @modules java.base/sun.net.www.protocol.http:open
|
||||
* @run main/othervm NoNTLM
|
||||
* @run main/othervm -Djava.net.preferIPv6Addresses=true NoNTLM
|
||||
*/
|
||||
@ -42,8 +41,9 @@ import java.net.Proxy;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URL;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
public class NoNTLM {
|
||||
|
||||
@ -163,7 +163,7 @@ public class NoNTLM {
|
||||
// client ---- GET ---> server
|
||||
// client <--- 401 ---- server
|
||||
try (Socket s = ss.accept()) {
|
||||
new MessageHeader().parseHeader(s.getInputStream());
|
||||
new HttpHeaderParser().parse(s.getInputStream());
|
||||
s.getOutputStream().write(reply.getBytes("US-ASCII"));
|
||||
}
|
||||
|
||||
@ -171,10 +171,10 @@ public class NoNTLM {
|
||||
// client <--- 200 ---- server
|
||||
String auth;
|
||||
try (Socket s = ss.accept()) {
|
||||
MessageHeader mh = new MessageHeader();
|
||||
mh.parseHeader(s.getInputStream());
|
||||
HttpHeaderParser mh = new HttpHeaderParser();
|
||||
mh.parse(s.getInputStream());
|
||||
s.getOutputStream().write(OKAY.getBytes("US-ASCII"));
|
||||
auth = mh.findValue("Authorization");
|
||||
auth = mh.getHeaderValue("Authorization").get(0);
|
||||
}
|
||||
|
||||
// check Authorization header
|
||||
@ -208,7 +208,7 @@ public class NoNTLM {
|
||||
// client ---- GET ---> server
|
||||
// client <--- 401 ---- client
|
||||
try (Socket s = ss.accept()) {
|
||||
new MessageHeader().parseHeader(s.getInputStream());
|
||||
new HttpHeaderParser().parse(s.getInputStream());
|
||||
s.getOutputStream().write(reply.getBytes("US-ASCII"));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2003, 2022, 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
|
||||
@ -26,13 +26,13 @@
|
||||
* @bug 4772077
|
||||
* @library /test/lib
|
||||
* @summary using defaultReadTimeout appear to retry request upon timeout
|
||||
* @modules java.base/sun.net.www
|
||||
*/
|
||||
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
import sun.net.www.*;
|
||||
|
||||
public class RetryUponTimeout implements Runnable {
|
||||
// run server
|
||||
@ -42,7 +42,7 @@ public class RetryUponTimeout implements Runnable {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
socket = server.accept();
|
||||
InputStream is = socket.getInputStream ();
|
||||
MessageHeader header = new MessageHeader (is);
|
||||
HttpHeaderParser header = new HttpHeaderParser (is);
|
||||
count++;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2001, 2022, 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,7 +25,6 @@
|
||||
* @test
|
||||
* @bug 4512200
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.net.www
|
||||
* @run main/othervm -Dhttp.agent=foo UserAgent
|
||||
* @run main/othervm -Dhttp.agent=foo -Djava.net.preferIPv6Addresses=true UserAgent
|
||||
* @summary HTTP header "User-Agent" format incorrect
|
||||
@ -34,8 +33,9 @@
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.net.*;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
import jdk.test.lib.net.URIBuilder;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
class Server extends Thread {
|
||||
Server (ServerSocket server) {
|
||||
@ -46,8 +46,8 @@ class Server extends Thread {
|
||||
String version = System.getProperty ("java.version");
|
||||
String expected = "foo Java/"+version;
|
||||
Socket s = server.accept ();
|
||||
MessageHeader header = new MessageHeader (s.getInputStream());
|
||||
String v = header.findValue ("User-Agent");
|
||||
HttpHeaderParser header = new HttpHeaderParser (s.getInputStream());
|
||||
String v = header.getHeaderValue ("User-Agent").get(0);
|
||||
if (!expected.equals (v)) {
|
||||
error ("Got unexpected User-Agent: " + v);
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2022, 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,7 +25,7 @@
|
||||
* @test
|
||||
* @bug 6226610 6973030
|
||||
* @summary HTTP tunnel connections send user headers to proxy
|
||||
* @modules java.base/sun.net.www
|
||||
* @library /test/lib
|
||||
* @run main/othervm B6226610
|
||||
*/
|
||||
|
||||
@ -37,7 +37,9 @@
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import sun.net.www.MessageHeader;
|
||||
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
|
||||
|
||||
public class B6226610 {
|
||||
static HeaderCheckerProxyTunnelServer proxy;
|
||||
@ -138,21 +140,21 @@ class HeaderCheckerProxyTunnelServer extends Thread
|
||||
private void processRequests() throws IOException
|
||||
{
|
||||
InputStream in = clientSocket.getInputStream();
|
||||
MessageHeader mheader = new MessageHeader(in);
|
||||
String statusLine = mheader.getValue(0);
|
||||
HttpHeaderParser mheader = new HttpHeaderParser(in);
|
||||
String statusLine = mheader.getRequestDetails();
|
||||
|
||||
if (statusLine.startsWith("CONNECT")) {
|
||||
// retrieve the host and port info from the status-line
|
||||
retrieveConnectInfo(statusLine);
|
||||
|
||||
if (mheader.findValue("X-TestHeader") != null) {
|
||||
if (mheader.getHeaderValue("X-TestHeader") != null) {
|
||||
System.out.println("Proxy should not receive user defined headers for tunneled requests");
|
||||
failed = true;
|
||||
}
|
||||
|
||||
// 6973030
|
||||
String value;
|
||||
if ((value = mheader.findValue("Proxy-Connection")) == null ||
|
||||
if ((value = mheader.getHeaderValue("Proxy-Connection").get(0)) == null ||
|
||||
!value.equals("keep-alive")) {
|
||||
System.out.println("Proxy-Connection:keep-alive not being sent");
|
||||
failed = true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2022, 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
|
||||
@ -45,7 +45,8 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import sun.net.www.MessageHeader;
|
||||
import jdk.test.lib.net.HttpHeaderParser;
|
||||
|
||||
|
||||
public class TunnelProxy {
|
||||
|
||||
@ -261,7 +262,7 @@ public class TunnelProxy {
|
||||
try {
|
||||
InputStream is = new BufferedInputStream (new NioInputStream (chan));
|
||||
String requestline = readLine (is);
|
||||
MessageHeader mhead = new MessageHeader (is);
|
||||
HttpHeaderParser mhead = new HttpHeaderParser (is);
|
||||
String[] req = requestline.split (" ");
|
||||
if (req.length < 2) {
|
||||
/* invalid request line */
|
||||
|
390
test/lib/jdk/test/lib/net/HttpHeaderParser.java
Normal file
390
test/lib/jdk/test/lib/net/HttpHeaderParser.java
Normal file
@ -0,0 +1,390 @@
|
||||
/*
|
||||
* Copyright (c) 2022, 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.
|
||||
*/
|
||||
|
||||
package jdk.test.lib.net;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class HttpHeaderParser {
|
||||
private static final char CR = '\r';
|
||||
private static final char LF = '\n';
|
||||
private static final char HT = '\t';
|
||||
private static final char SP = ' ';
|
||||
// ABNF primitives defined in RFC 7230
|
||||
private static boolean[] tchar = new boolean[256];
|
||||
private static boolean[] fieldvchar = new boolean[256];
|
||||
|
||||
static {
|
||||
char[] allowedTokenChars =
|
||||
("!#$%&'*+-.^_`|~0123456789" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
|
||||
for (char c : allowedTokenChars) {
|
||||
tchar[c] = true;
|
||||
}
|
||||
for (char c = 0x21; c <= 0xFF; c++) {
|
||||
fieldvchar[c] = true;
|
||||
}
|
||||
fieldvchar[0x7F] = false; // a little hole (DEL) in the range
|
||||
}
|
||||
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
|
||||
private Map <String, List<String>> headerMap = new LinkedHashMap<>();
|
||||
private List <String> keyList = new ArrayList<>();
|
||||
private String requestOrStatusLine;
|
||||
private int responseCode;
|
||||
private boolean eof;
|
||||
|
||||
|
||||
|
||||
enum State { INITIAL,
|
||||
STATUS_OR_REQUEST_LINE,
|
||||
STATUS_OR_REQUEST_LINE_FOUND_CR,
|
||||
STATUS_OR_REQUEST_LINE_FOUND_LF,
|
||||
STATUS_OR_REQUEST_LINE_END,
|
||||
STATUS_OR_REQUEST_LINE_END_CR,
|
||||
STATUS_OR_REQUEST_LINE_END_LF,
|
||||
HEADER,
|
||||
HEADER_FOUND_CR,
|
||||
HEADER_FOUND_LF,
|
||||
HEADER_FOUND_CR_LF,
|
||||
HEADER_FOUND_CR_LF_CR,
|
||||
FINISHED }
|
||||
|
||||
private HttpHeaderParser.State state = HttpHeaderParser.State.INITIAL;
|
||||
|
||||
public HttpHeaderParser() {
|
||||
}
|
||||
|
||||
|
||||
public HttpHeaderParser(InputStream is) throws IOException, ProtocolException {
|
||||
parse(is);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaderMap() {
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
public List<String> getHeaderValue(String key) {
|
||||
if(headerMap.containsKey(key.toLowerCase(Locale.ROOT))) {
|
||||
return headerMap.get(key.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public List<String> getValue(int id) {
|
||||
String key = keyList.get(id);
|
||||
return headerMap.get(key);
|
||||
}
|
||||
|
||||
public String getRequestDetails() {
|
||||
return requestOrStatusLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses HTTP/1.X status-line or request-line and headers from the given input stream.
|
||||
* @param input Containing the input stream of bytes representing request or response header data
|
||||
* @return true if the end of the headers block has been reached
|
||||
*/
|
||||
public boolean parse(InputStream input) throws IOException {
|
||||
requireNonNull(input, "null input");
|
||||
while (canContinueParsing()) {
|
||||
switch (state) {
|
||||
case INITIAL -> state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE;
|
||||
case STATUS_OR_REQUEST_LINE -> readResumeStatusLine(input);
|
||||
case STATUS_OR_REQUEST_LINE_FOUND_CR, STATUS_OR_REQUEST_LINE_FOUND_LF -> readStatusLineFeed(input);
|
||||
case STATUS_OR_REQUEST_LINE_END -> maybeStartHeaders(input);
|
||||
case STATUS_OR_REQUEST_LINE_END_CR, STATUS_OR_REQUEST_LINE_END_LF -> maybeEndHeaders(input);
|
||||
case HEADER -> readResumeHeader(input);
|
||||
case HEADER_FOUND_CR, HEADER_FOUND_LF -> resumeOrLF(input);
|
||||
case HEADER_FOUND_CR_LF -> resumeOrSecondCR(input);
|
||||
case HEADER_FOUND_CR_LF_CR -> resumeOrEndHeaders(input);
|
||||
default -> throw new InternalError("Unexpected state: " + state);
|
||||
}
|
||||
}
|
||||
return state == HttpHeaderParser.State.FINISHED;
|
||||
}
|
||||
|
||||
private boolean canContinueParsing() {
|
||||
// some states don't require any input to transition
|
||||
// to the next state.
|
||||
return switch (state) {
|
||||
case FINISHED -> false;
|
||||
case STATUS_OR_REQUEST_LINE_FOUND_LF, STATUS_OR_REQUEST_LINE_END_LF, HEADER_FOUND_LF -> true;
|
||||
default -> !eof;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a character (char) corresponding to the next byte in the
|
||||
* input, interpreted as an ISO-8859-1 encoded character.
|
||||
* <p>
|
||||
* The ISO-8859-1 encoding is a 8-bit character coding that
|
||||
* corresponds to the first 256 Unicode characters - from U+0000 to
|
||||
* U+00FF. UTF-16 is backward compatible with ISO-8859-1 - which
|
||||
* means each byte in the input should be interpreted as an unsigned
|
||||
* value from [0, 255] representing the character code.
|
||||
*
|
||||
* @param input a {@code InputStream} containing input stream of Bytes.
|
||||
* @return the next byte in the input, interpreted as an ISO-8859-1
|
||||
* encoded char
|
||||
* @throws IOException
|
||||
* if an I/O error occurs.
|
||||
*/
|
||||
private char get(InputStream input) throws IOException {
|
||||
int c = input.read();
|
||||
if(c < 0)
|
||||
eof = true;
|
||||
return (char)(c & 0xFF);
|
||||
}
|
||||
|
||||
private void readResumeStatusLine(InputStream input) throws IOException {
|
||||
char c;
|
||||
while ((c = get(input)) != CR && !eof) {
|
||||
if (c == LF) break;
|
||||
sb.append(c);
|
||||
}
|
||||
if (c == CR) {
|
||||
state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_FOUND_CR;
|
||||
} else if (c == LF) {
|
||||
state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_FOUND_LF;
|
||||
}
|
||||
}
|
||||
|
||||
private void readStatusLineFeed(InputStream input) throws IOException {
|
||||
char c = state == HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_FOUND_LF ? LF : get(input);
|
||||
if (c != LF) {
|
||||
throw protocolException("Bad trailing char, \"%s\", when parsing status line, \"%s\"",
|
||||
c, sb.toString());
|
||||
}
|
||||
requestOrStatusLine = sb.toString();
|
||||
sb = new StringBuilder();
|
||||
if (!requestOrStatusLine.startsWith("HTTP/1.")) {
|
||||
if(!requestOrStatusLine.startsWith("GET") && !requestOrStatusLine.startsWith("POST") &&
|
||||
!requestOrStatusLine.startsWith("PUT") && !requestOrStatusLine.startsWith("DELETE") &&
|
||||
!requestOrStatusLine.startsWith("OPTIONS") && !requestOrStatusLine.startsWith("HEAD") &&
|
||||
!requestOrStatusLine.startsWith("PATCH") && !requestOrStatusLine.startsWith("CONNECT")) {
|
||||
throw protocolException("Invalid request Or Status line: \"%s\"", requestOrStatusLine);
|
||||
} else { //This is request
|
||||
System.out.println("Request is :"+requestOrStatusLine);
|
||||
}
|
||||
} else { //This is response
|
||||
if (requestOrStatusLine.length() < 12) {
|
||||
throw protocolException("Invalid status line: \"%s\"", requestOrStatusLine);
|
||||
}
|
||||
try {
|
||||
responseCode = Integer.parseInt(requestOrStatusLine.substring(9, 12));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw protocolException("Invalid status line: \"%s\"", requestOrStatusLine);
|
||||
}
|
||||
// response code expected to be a 3-digit integer (RFC-2616, section 6.1.1)
|
||||
if (responseCode < 100) {
|
||||
throw protocolException("Invalid status line: \"%s\"", requestOrStatusLine);
|
||||
}
|
||||
}
|
||||
state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END;
|
||||
}
|
||||
|
||||
private void maybeStartHeaders(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END;
|
||||
assert sb.length() == 0;
|
||||
char c = get(input);
|
||||
if(!eof) {
|
||||
if (c == CR) {
|
||||
state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END_CR;
|
||||
} else if (c == LF) {
|
||||
state = HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END_LF;
|
||||
} else {
|
||||
sb.append(c);
|
||||
state = HttpHeaderParser.State.HEADER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeEndHeaders(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END_CR || state == HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END_LF;
|
||||
assert sb.length() == 0;
|
||||
char c = state == HttpHeaderParser.State.STATUS_OR_REQUEST_LINE_END_LF ? LF : get(input);
|
||||
if (c == LF) {
|
||||
state = HttpHeaderParser.State.FINISHED; // no headers
|
||||
} else {
|
||||
throw protocolException("Unexpected \"%s\", after status line CR", c);
|
||||
}
|
||||
}
|
||||
|
||||
private void readResumeHeader(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.HEADER;
|
||||
assert !eof;
|
||||
char c = get(input);
|
||||
while (!eof) {
|
||||
if (c == CR) {
|
||||
state = HttpHeaderParser.State.HEADER_FOUND_CR;
|
||||
break;
|
||||
} else if (c == LF) {
|
||||
state = HttpHeaderParser.State.HEADER_FOUND_LF;
|
||||
break;
|
||||
}
|
||||
if (c == HT)
|
||||
c = SP;
|
||||
sb.append(c);
|
||||
c = get(input);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHeaderFromString(String headerString) throws ProtocolException {
|
||||
assert sb.length() == 0;
|
||||
int idx = headerString.indexOf(':');
|
||||
if (idx == -1)
|
||||
return;
|
||||
String name = headerString.substring(0, idx);
|
||||
|
||||
// compatibility with HttpURLConnection;
|
||||
if (name.isEmpty()) return;
|
||||
|
||||
if (!isValidName(name)) {
|
||||
throw protocolException("Invalid header name \"%s\"", name);
|
||||
}
|
||||
String value = headerString.substring(idx + 1).trim();
|
||||
if (!isValidValue(value)) {
|
||||
throw protocolException("Invalid header value \"%s: %s\"", name, value);
|
||||
}
|
||||
|
||||
keyList.add(name);
|
||||
headerMap.computeIfAbsent(name.toLowerCase(Locale.US),
|
||||
k -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
private void resumeOrLF(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.HEADER_FOUND_CR || state == HttpHeaderParser.State.HEADER_FOUND_LF;
|
||||
char c = state == HttpHeaderParser.State.HEADER_FOUND_LF ? LF : get(input);
|
||||
if (!eof) {
|
||||
if (c == LF) {
|
||||
state = HttpHeaderParser.State.HEADER_FOUND_CR_LF;
|
||||
} else if (c == SP || c == HT) {
|
||||
sb.append(SP); // parity with MessageHeaders
|
||||
state = HttpHeaderParser.State.HEADER;
|
||||
} else {
|
||||
sb = new StringBuilder();
|
||||
sb.append(c);
|
||||
state = HttpHeaderParser.State.HEADER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeOrSecondCR(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.HEADER_FOUND_CR_LF;
|
||||
char c = get(input);
|
||||
if (!eof) {
|
||||
if (c == CR || c == LF) {
|
||||
if (sb.length() > 0) {
|
||||
// no continuation line - flush
|
||||
// previous header value.
|
||||
String headerString = sb.toString();
|
||||
sb = new StringBuilder();
|
||||
addHeaderFromString(headerString);
|
||||
}
|
||||
if (c == CR) {
|
||||
state = HttpHeaderParser.State.HEADER_FOUND_CR_LF_CR;
|
||||
} else {
|
||||
state = HttpHeaderParser.State.FINISHED;
|
||||
}
|
||||
} else if (c == SP || c == HT) {
|
||||
assert sb.length() != 0;
|
||||
sb.append(SP); // continuation line
|
||||
state = HttpHeaderParser.State.HEADER;
|
||||
} else {
|
||||
if (sb.length() > 0) {
|
||||
// no continuation line - flush
|
||||
// previous header value.
|
||||
String headerString = sb.toString();
|
||||
sb = new StringBuilder();
|
||||
addHeaderFromString(headerString);
|
||||
}
|
||||
sb.append(c);
|
||||
state = HttpHeaderParser.State.HEADER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeOrEndHeaders(InputStream input) throws IOException {
|
||||
assert state == HttpHeaderParser.State.HEADER_FOUND_CR_LF_CR;
|
||||
char c = get(input);
|
||||
if (!eof) {
|
||||
if (c == LF) {
|
||||
state = HttpHeaderParser.State.FINISHED;
|
||||
} else {
|
||||
throw protocolException("Unexpected \"%s\", after CR LF CR", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ProtocolException protocolException(String format, Object ... args) {
|
||||
return new ProtocolException(String.format(format, args));
|
||||
}
|
||||
|
||||
/*
|
||||
* Validates a RFC 7230 field-name.
|
||||
*/
|
||||
public boolean isValidName(String token) {
|
||||
for (int i = 0; i < token.length(); i++) {
|
||||
char c = token.charAt(i);
|
||||
if (c > 255 || !tchar[c]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !token.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* Validates a RFC 7230 field-value.
|
||||
*
|
||||
* "Obsolete line folding" rule
|
||||
*
|
||||
* obs-fold = CRLF 1*( SP / HTAB )
|
||||
*
|
||||
* is not permitted!
|
||||
*/
|
||||
public boolean isValidValue(String token) {
|
||||
for (int i = 0; i < token.length(); i++) {
|
||||
char c = token.charAt(i);
|
||||
if (c > 255) {
|
||||
return false;
|
||||
}
|
||||
if (c == ' ' || c == '\t') {
|
||||
continue;
|
||||
} else if (!fieldvchar[c]) {
|
||||
return false; // forbidden byte
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user