1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 8185898 27 * @modules java.base/sun.net.www 28 * @library /test/lib 29 * @run main/othervm B8185898 30 * @summary setRequestProperty(key, null) results in HTTP header without colon in request 31 */ 32 33 import java.io.*; 34 import java.net.*; 35 import java.util.Arrays; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.concurrent.ExecutorService; 39 import java.util.concurrent.Executors; 40 import java.util.stream.Collectors; 41 42 import jdk.test.lib.net.URIBuilder; 43 import sun.net.www.MessageHeader; 44 import com.sun.net.httpserver.HttpContext; 45 import com.sun.net.httpserver.HttpExchange; 46 import com.sun.net.httpserver.HttpHandler; 47 import com.sun.net.httpserver.HttpServer; 48 49 import static java.nio.charset.StandardCharsets.ISO_8859_1; 50 import static java.nio.charset.StandardCharsets.UTF_8; 51 52 /* 53 * Test checks that MessageHeader with key != null and value == null is set correctly 54 * and printed according to HTTP standard in the format <key>: <value> 55 * */ 56 public class B8185898 { 57 58 static HttpServer server; 59 static final String RESPONSE_BODY = "Test response body"; 60 static final String H1 = "X-header1"; 61 static final String H2 = "X-header2"; 62 static final String VALUE = "This test value should appear"; 63 static int port; 64 static URL url; 65 static volatile Map<String, List<String>> headers; 66 67 static class Handler implements HttpHandler { 68 handle(HttpExchange t)69 public void handle(HttpExchange t) throws IOException { 70 InputStream is = t.getRequestBody(); 71 InetSocketAddress rem = t.getRemoteAddress(); 72 headers = t.getRequestHeaders(); // Get request headers on the server side 73 is.readAllBytes(); 74 is.close(); 75 76 OutputStream os = t.getResponseBody(); 77 t.sendResponseHeaders(200, RESPONSE_BODY.length()); 78 os.write(RESPONSE_BODY.getBytes(UTF_8)); 79 t.close(); 80 } 81 } 82 main(String[] args)83 public static void main(String[] args) throws Exception { 84 ExecutorService exec = Executors.newCachedThreadPool(); 85 InetAddress loopback = InetAddress.getLoopbackAddress(); 86 87 try { 88 InetSocketAddress addr = new InetSocketAddress(loopback, 0); 89 server = HttpServer.create(addr, 100); 90 HttpHandler handler = new Handler(); 91 HttpContext context = server.createContext("/", handler); 92 server.setExecutor(exec); 93 server.start(); 94 95 port = server.getAddress().getPort(); 96 System.out.println("Server on port: " + port); 97 url = URIBuilder.newBuilder() 98 .scheme("http") 99 .loopback() 100 .port(port) 101 .path("/foo") 102 .toURLUnchecked(); 103 System.out.println("URL: " + url); 104 testMessageHeader(); 105 testMessageHeaderMethods(); 106 testURLConnectionMethods(); 107 } finally { 108 server.stop(0); 109 System.out.println("After server shutdown"); 110 exec.shutdown(); 111 } 112 } 113 114 // Test message header with malformed message header and fake request line testMessageHeader()115 static void testMessageHeader() { 116 final String badHeader = "This is not a request line for HTTP/1.1"; 117 final String fakeRequestLine = "This /is/a/fake/status/line HTTP/2.0"; 118 final String expectedHeaders = fakeRequestLine + "\r\n" 119 + H1 + ": " + VALUE + "\r\n" 120 + H2 + ": " + VALUE + "\r\n" 121 + badHeader + ":\r\n\r\n"; 122 123 MessageHeader header = new MessageHeader(); 124 header.add(H1, VALUE); 125 header.add(H2, VALUE); 126 header.add(badHeader, null); 127 header.prepend(fakeRequestLine, null); 128 ByteArrayOutputStream out = new ByteArrayOutputStream(); 129 header.print(new PrintStream(out)); 130 131 if (!out.toString().equals(expectedHeaders)) { 132 throw new AssertionError("FAILED: expected: " 133 + expectedHeaders + "\nReceived: " + out.toString()); 134 } else { 135 System.out.println("PASSED: ::print returned correct " 136 + "status line and headers:\n" + out.toString()); 137 } 138 } 139 140 // Test MessageHeader::print, ::toString, implicitly testing that 141 // MessageHeader::mergeHeader formats headers correctly for responses testMessageHeaderMethods()142 static void testMessageHeaderMethods() throws IOException { 143 // {{inputString1, expectedToString1, expectedPrint1}, {...}} 144 String[][] strings = { 145 {"HTTP/1.1 200 OK\r\n" 146 + "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" 147 + "Connection: keep-alive\r\n" 148 + "Host: 127.0.0.1:12345\r\n" 149 + "User-agent: Java/12\r\n\r\nfoooo", 150 "pairs: {null: HTTP/1.1 200 OK}" 151 + "{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}" 152 + "{Connection: keep-alive}" 153 + "{Host: 127.0.0.1:12345}" 154 + "{User-agent: Java/12}", 155 "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" 156 + "Connection: keep-alive\r\n" 157 + "Host: 127.0.0.1:12345\r\n" 158 + "User-agent: Java/12\r\n\r\n"}, 159 {"HTTP/1.1 200 OK\r\n" 160 + "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" 161 + "Connection: keep-alive\r\n" 162 + "Host: 127.0.0.1:12345\r\n" 163 + "User-agent: Java/12\r\n" 164 + "X-Header:\r\n\r\n", 165 "pairs: {null: HTTP/1.1 200 OK}" 166 + "{Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2}" 167 + "{Connection: keep-alive}" 168 + "{Host: 127.0.0.1:12345}" 169 + "{User-agent: Java/12}" 170 + "{X-Header: }", 171 "Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n" 172 + "Connection: keep-alive\r\n" 173 + "Host: 127.0.0.1:12345\r\n" 174 + "User-agent: Java/12\r\n" 175 + "X-Header: \r\n\r\n"}, 176 }; 177 178 System.out.println("Test custom message headers"); 179 for (String[] s : strings) { 180 // Test MessageHeader::toString 181 MessageHeader header = new MessageHeader( 182 new ByteArrayInputStream(s[0].getBytes(ISO_8859_1))); 183 if (!header.toString().endsWith(s[1])) { 184 throw new AssertionError("FAILED: expected: " 185 + s[1] + "\nReceived: " + header); 186 } else { 187 System.out.println("PASSED: ::toString returned correct " 188 + "status line and headers:\n" + header); 189 } 190 191 // Test MessageHeader::print 192 ByteArrayOutputStream out = new ByteArrayOutputStream(); 193 header.print(new PrintStream(out)); 194 if (!out.toString().equals(s[2])) { 195 throw new AssertionError("FAILED: expected: " 196 + s[2] + "\nReceived: " + out.toString()); 197 } else { 198 System.out.println("PASSED: ::print returned correct " 199 + "status line and headers:\n" + out.toString()); 200 } 201 } 202 } 203 204 // Test methods URLConnection::getRequestProperties, 205 // ::getHeaderField, ::getHeaderFieldKey testURLConnectionMethods()206 static void testURLConnectionMethods() throws IOException { 207 HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); 208 urlConn.setRequestProperty(H1, ""); 209 urlConn.setRequestProperty(H1, VALUE); 210 urlConn.setRequestProperty(H2, null); // Expected to contain ':' between key and value 211 Map<String, List<String>> props = urlConn.getRequestProperties(); 212 Map<String, List<String>> expectedMap = Map.of( 213 H1, List.of(VALUE), 214 H2, Arrays.asList((String) null)); 215 216 // Test request properties 217 System.out.println("Client request properties"); 218 StringBuilder sb = new StringBuilder(); 219 props.forEach((k, v) -> sb.append(k + ": " 220 + v.stream().collect(Collectors.joining()) + "\n")); 221 System.out.println(sb); 222 223 if (!props.equals(expectedMap)) { 224 throw new AssertionError("Unexpected properties returned: " 225 + props); 226 } else { 227 System.out.println("Properties returned as expected"); 228 } 229 230 // Test header fields 231 String headerField = urlConn.getHeaderField(0); 232 if (!headerField.contains("200 OK")) { 233 throw new AssertionError("Expected headerField[0]: status line. " 234 + "Received: " + headerField); 235 } else { 236 System.out.println("PASSED: headerField[0] contains status line: " 237 + headerField); 238 } 239 240 String headerFieldKey = urlConn.getHeaderFieldKey(0); 241 if (headerFieldKey != null) { 242 throw new AssertionError("Expected headerFieldKey[0]: null. " 243 + "Received: " + headerFieldKey); 244 } else { 245 System.out.println("PASSED: headerFieldKey[0] is null"); 246 } 247 248 // Check that test request headers are included with correct format 249 try ( 250 BufferedReader in = new BufferedReader( 251 new InputStreamReader(urlConn.getInputStream())) 252 ) { 253 if (!headers.keySet().contains(H1)) { 254 throw new AssertionError("Expected key not found: " 255 + H1 + ": " + VALUE); 256 } else if (!headers.get(H1).equals(List.of(VALUE))) { 257 throw new AssertionError("Unexpected key-value pair: " 258 + H1 + ": " + headers.get(H1)); 259 } else { 260 System.out.println("PASSED: " + H1 + " included in request headers"); 261 } 262 263 if (!headers.keySet().contains(H2)) { 264 throw new AssertionError("Expected key not found: " 265 + H2 + ": "); 266 // Check that empty list is returned 267 } else if (!headers.get(H2).equals(List.of(""))) { 268 throw new AssertionError("Unexpected key-value pair: " 269 + H2 + ": " + headers.get(H2)); 270 } else { 271 System.out.println("PASSED: " + H2 + " included in request headers"); 272 } 273 274 String inputLine; 275 while ((inputLine = in.readLine()) != null) { 276 System.out.println(inputLine); 277 } 278 } 279 } 280 } 281