| /* |
| * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /** |
| * @test |
| * @bug 8185898 |
| * @library /lib/testlibrary |
| * @run main/othervm B8185898 |
| * @summary setRequestProperty(key, null) results in HTTP header without colon in request |
| */ |
| |
| import java.io.*; |
| import java.net.*; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.HashMap; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.stream.Collectors; |
| import java.util.Collections; |
| |
| import jdk.testlibrary.net.URIBuilder; |
| import sun.net.www.MessageHeader; |
| import com.sun.net.httpserver.HttpContext; |
| import com.sun.net.httpserver.HttpExchange; |
| import com.sun.net.httpserver.HttpHandler; |
| import com.sun.net.httpserver.HttpServer; |
| |
| import static java.nio.charset.StandardCharsets.ISO_8859_1; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| /* |
| * Test checks that MessageHeader with key != null and value == null is set correctly |
| * and printed according to HTTP standard in the format <key>: <value> |
| * */ |
| public class B8185898 { |
| |
| static HttpServer server; |
| static final String RESPONSE_BODY = "Test response body"; |
| static final String H1 = "X-header1"; |
| static final String H2 = "X-header2"; |
| static final String VALUE = "This test value should appear"; |
| static final List<String> oneList = Arrays.asList(VALUE); |
| static final List<String> zeroList = Arrays.asList(""); |
| static int port; |
| static URL url; |
| static volatile Map<String, List<String>> headers; |
| |
| static class Handler implements HttpHandler { |
| |
| public void handle(HttpExchange t) throws IOException { |
| InputStream is = t.getRequestBody(); |
| InetSocketAddress rem = t.getRemoteAddress(); |
| headers = t.getRequestHeaders(); // Get request headers on the server side |
| while(is.read() != -1){} |
| is.close(); |
| |
| OutputStream os = t.getResponseBody(); |
| t.sendResponseHeaders(200, RESPONSE_BODY.length()); |
| os.write(RESPONSE_BODY.getBytes(UTF_8)); |
| t.close(); |
| } |
| } |
| |
| public static void main(String[] args) throws Exception { |
| ExecutorService exec = Executors.newCachedThreadPool(); |
| InetAddress loopback = InetAddress.getLoopbackAddress(); |
| |
| try { |
| InetSocketAddress addr = new InetSocketAddress(loopback, 0); |
| server = HttpServer.create(addr, 100); |
| HttpHandler handler = new Handler(); |
| HttpContext context = server.createContext("/", handler); |
| server.setExecutor(exec); |
| server.start(); |
| |
| port = server.getAddress().getPort(); |
| System.out.println("Server on port: " + port); |
| url = URIBuilder.newBuilder() |
| .scheme("http") |
| .loopback() |
| .port(port) |
| .path("/foo") |
| .toURLUnchecked(); |
| System.out.println("URL: " + url); |
| testMessageHeader(); |
| testMessageHeaderMethods(); |
| testURLConnectionMethods(); |
| } finally { |
| server.stop(0); |
| System.out.println("After server shutdown"); |
| exec.shutdown(); |
| } |
| } |
| |
| // Test message header with malformed message header and fake request line |
| static void testMessageHeader() { |
| final String badHeader = "This is not a request line for HTTP/1.1"; |
| final String fakeRequestLine = "This /is/a/fake/status/line HTTP/2.0"; |
| final String expectedHeaders = fakeRequestLine + "\r\n" |
| + H1 + ": " + VALUE + "\r\n" |
| + H2 + ": " + VALUE + "\r\n" |
| + badHeader + ":\r\n\r\n"; |
| |
| MessageHeader header = new MessageHeader(); |
| header.add(H1, VALUE); |
| header.add(H2, VALUE); |
| header.add(badHeader, null); |
| header.prepend(fakeRequestLine, null); |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| header.print(new PrintStream(out)); |
| |
| if (!out.toString().equals(expectedHeaders)) { |
| throw new AssertionError("FAILED: expected: " |
| + expectedHeaders + "\nReceived: " + out.toString()); |
| } else { |
| System.out.println("PASSED: ::print returned correct " |
| + "status line and headers:\n" + out.toString()); |
| } |
| } |
| |
| // Test MessageHeader::print, ::toString, implicitly testing that |
| // MessageHeader::mergeHeader formats headers correctly for responses |
| static void testMessageHeaderMethods() throws IOException { |
| // {{inputString1, expectedToString1, expectedPrint1}, {...}} |
| String[][] strings = { |
| {"HTTP/1.1 200 OK\r\n" |
| + "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" |
| + "Connection: keep-alive\r\n" |
| + "Host: 127.0.0.1:12345\r\n" |
| + "User-agent: Java/12\r\n\r\nfoooo", |
| "pairs: {null: HTTP/1.1 200 OK}" |
| + "{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}" |
| + "{Connection: keep-alive}" |
| + "{Host: 127.0.0.1:12345}" |
| + "{User-agent: Java/12}", |
| "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" |
| + "Connection: keep-alive\r\n" |
| + "Host: 127.0.0.1:12345\r\n" |
| + "User-agent: Java/12\r\n\r\n"}, |
| {"HTTP/1.1 200 OK\r\n" |
| + "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" |
| + "Connection: keep-alive\r\n" |
| + "Host: 127.0.0.1:12345\r\n" |
| + "User-agent: Java/12\r\n" |
| + "X-Header:\r\n\r\n", |
| "pairs: {null: HTTP/1.1 200 OK}" |
| + "{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}" |
| + "{Connection: keep-alive}" |
| + "{Host: 127.0.0.1:12345}" |
| + "{User-agent: Java/12}" |
| + "{X-Header: }", |
| "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" |
| + "Connection: keep-alive\r\n" |
| + "Host: 127.0.0.1:12345\r\n" |
| + "User-agent: Java/12\r\n" |
| + "X-Header: \r\n\r\n"}, |
| }; |
| |
| System.out.println("Test custom message headers"); |
| for (String[] s : strings) { |
| // Test MessageHeader::toString |
| MessageHeader header = new MessageHeader( |
| new ByteArrayInputStream(s[0].getBytes(ISO_8859_1))); |
| if (!header.toString().endsWith(s[1])) { |
| throw new AssertionError("FAILED: expected: " |
| + s[1] + "\nReceived: " + header); |
| } else { |
| System.out.println("PASSED: ::toString returned correct " |
| + "status line and headers:\n" + header); |
| } |
| |
| // Test MessageHeader::print |
| ByteArrayOutputStream out = new ByteArrayOutputStream(); |
| header.print(new PrintStream(out)); |
| if (!out.toString().equals(s[2])) { |
| throw new AssertionError("FAILED: expected: " |
| + s[2] + "\nReceived: " + out.toString()); |
| } else { |
| System.out.println("PASSED: ::print returned correct " |
| + "status line and headers:\n" + out.toString()); |
| } |
| } |
| } |
| |
| // Test methods URLConnection::getRequestProperties, |
| // ::getHeaderField, ::getHeaderFieldKey |
| static void testURLConnectionMethods() throws IOException { |
| HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); |
| urlConn.setRequestProperty(H1, ""); |
| urlConn.setRequestProperty(H1, VALUE); |
| urlConn.setRequestProperty(H2, null); // Expected to contain ':' between key and value |
| Map<String, List<String>> props = urlConn.getRequestProperties(); |
| Map<String, List<String>> expectedMap = new HashMap<String, List<String>>(); |
| expectedMap.put(H1, oneList); |
| expectedMap.put(H2, Arrays.asList((String)null)); |
| |
| // Test request properties |
| System.out.println("Client request properties"); |
| StringBuilder sb = new StringBuilder(); |
| props.forEach((k, v) -> sb.append(k + ": " |
| + v.stream().collect(Collectors.joining()) + "\n")); |
| System.out.println(sb); |
| |
| if (!props.equals(expectedMap)) { |
| throw new AssertionError("Unexpected properties returned: " |
| + props); |
| } else { |
| System.out.println("Properties returned as expected"); |
| } |
| |
| // Test header fields |
| String headerField = urlConn.getHeaderField(0); |
| if (!headerField.contains("200 OK")) { |
| throw new AssertionError("Expected headerField[0]: status line. " |
| + "Received: " + headerField); |
| } else { |
| System.out.println("PASSED: headerField[0] contains status line: " |
| + headerField); |
| } |
| |
| String headerFieldKey = urlConn.getHeaderFieldKey(0); |
| if (headerFieldKey != null) { |
| throw new AssertionError("Expected headerFieldKey[0]: null. " |
| + "Received: " + headerFieldKey); |
| } else { |
| System.out.println("PASSED: headerFieldKey[0] is null"); |
| } |
| |
| // Check that test request headers are included with correct format |
| try ( |
| BufferedReader in = new BufferedReader( |
| new InputStreamReader(urlConn.getInputStream())) |
| ) { |
| if (!headers.keySet().contains(H1)) { |
| throw new AssertionError("Expected key not found: " |
| + H1 + ": " + VALUE); |
| } else if (!headers.get(H1).equals(oneList)) { |
| throw new AssertionError("Unexpected key-value pair: " |
| + H1 + ": " + headers.get(H1)); |
| } else { |
| System.out.println("PASSED: " + H1 + " included in request headers"); |
| } |
| |
| if (!headers.keySet().contains(H2)) { |
| throw new AssertionError("Expected key not found: " |
| + H2 + ": "); |
| // Check that empty list is returned |
| } else if (!headers.get(H2).equals(zeroList)) { |
| throw new AssertionError("Unexpected key-value pair: " |
| + H2 + ": " + headers.get(H2)); |
| } else { |
| System.out.println("PASSED: " + H2 + " included in request headers"); |
| } |
| |
| String inputLine; |
| while ((inputLine = in.readLine()) != null) { |
| System.out.println(inputLine); |
| } |
| } |
| } |
| } |