1 /*
2  * Copyright (c) 2012, 2016, 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  * @test
25  * @bug 7095980 8007315
26  * @modules jdk.httpserver
27  * @summary Ensure HttpURLConnection (and supporting APIs) don't expose
28  *          HttpOnly cookies
29  */
30 
31 import java.io.IOException;
32 import java.net.CookieHandler;
33 import java.net.CookieManager;
34 import java.net.CookiePolicy;
35 import java.net.InetAddress;
36 import java.net.InetSocketAddress;
37 import java.net.URI;
38 import java.net.HttpURLConnection;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Set;
44 import com.sun.net.httpserver.Headers;
45 import com.sun.net.httpserver.HttpExchange;
46 import com.sun.net.httpserver.HttpHandler;
47 import com.sun.net.httpserver.HttpServer;
48 
49 /*
50  * 1) start the HTTP server
51  * 2) populate cookie store with HttpOnly cookies
52  * 3) make HTTP request that should contain HttpOnly cookies
53  * 4) check HttpOnly cookies received by server
54  * 5) server reply with Set-Cookie containing HttpOnly cookie
55  * 6) check HttpOnly cookies are not accessible from Http client
56  * 7) check that non-null (empty string) values are returned for
57       scenario where all values are stripped from original key values
58  */
59 
60 public class HttpOnly {
61 
62     static final String URI_PATH = "/xxyyzz/";
63     static final int SESSION_ID = 12345;
64 
test(String[] args)65      void test(String[] args) throws Exception {
66         HttpServer server = startHttpServer();
67         CookieHandler previousHandler = CookieHandler.getDefault();
68         try {
69             InetSocketAddress address = server.getAddress();
70             URI uri = new URI("http://" + InetAddress.getLocalHost().getHostAddress()
71                               + ":" + address.getPort() + URI_PATH);
72             populateCookieStore(uri);
73             doClient(uri);
74         } finally {
75             CookieHandler.setDefault(previousHandler);
76             server.stop(0);
77         }
78     }
79 
populateCookieStore(URI uri)80     void populateCookieStore(URI uri)
81             throws IOException {
82 
83         CookieManager cm = new CookieManager(null, CookiePolicy.ACCEPT_ALL);
84         CookieHandler.setDefault(cm);
85         Map<String,List<String>> header = new HashMap<>();
86         List<String> values = new ArrayList<>();
87         values.add("JSESSIONID=" + SESSION_ID + "; version=1; Path="
88                    + URI_PATH +"; HttpOnly");
89         values.add("CUSTOMER=WILE_E_COYOTE; version=1; Path=" + URI_PATH);
90         header.put("Set-Cookie", values);
91         cm.put(uri, header);
92     }
93 
doClient(URI uri)94     void doClient(URI uri) throws Exception {
95         HttpURLConnection uc = (HttpURLConnection) uri.toURL().openConnection();
96         int resp = uc.getResponseCode();
97         check(resp == 200,
98               "Unexpected response code. Expected 200, got " + resp);
99 
100         // TEST 1: check getRequestProperty doesn't return the HttpOnly cookie
101         // In fact, that it doesn't return any automatically set cookies.
102         String cookie = uc.getRequestProperty("Cookie");
103         check(cookie == null,
104               "Cookie header returned from getRequestProperty, value " + cookie);
105 
106         // TEST 2: check getRequestProperties doesn't return the HttpOnly cookie.
107         // In fact, that it doesn't return any automatically set cookies.
108         Map<String,List<String>> reqHeaders = uc.getRequestProperties();
109         Set<Map.Entry<String,List<String>>> entries = reqHeaders.entrySet();
110         for (Map.Entry<String,List<String>> entry : entries) {
111             String header = entry.getKey();
112             check(!"Cookie".equalsIgnoreCase(header),
113                   "Cookie header returned from getRequestProperties, value " +
114                          entry.getValue());
115         }
116 
117         // TEST 3: check getHeaderField doesn't return Set-Cookie with HttpOnly
118         String setCookie = uc.getHeaderField("Set-Cookie");
119         if (setCookie != null) {
120             debug("Set-Cookie:" + setCookie);
121             check(!setCookie.toLowerCase().contains("httponly"),
122                   "getHeaderField returned Set-Cookie header with HttpOnly, " +
123                   "value = " + setCookie);
124         }
125 
126         // TEST 3.5: check getHeaderField doesn't return Set-Cookie2 with HttpOnly
127         String setCookie2 = uc.getHeaderField("Set-Cookie2");
128         if (setCookie2 != null) {
129             debug("Set-Cookie2:" + setCookie2);
130             check(!setCookie2.toLowerCase().contains("httponly"),
131                   "getHeaderField returned Set-Cookie2 header with HttpOnly, " +
132                   "value = " + setCookie2);
133         }
134 
135         // TEST 4: check getHeaderFields doesn't return Set-Cookie
136         //         or Set-Cookie2 headers with HttpOnly
137         Map<String,List<String>> respHeaders = uc.getHeaderFields();
138         Set<Map.Entry<String,List<String>>> respEntries = respHeaders.entrySet();
139         for (Map.Entry<String,List<String>> entry : respEntries) {
140             String header = entry.getKey();
141             if ("Set-Cookie".equalsIgnoreCase(header)) {
142                 List<String> setCookieValues = entry.getValue();
143                 debug("Set-Cookie:" + setCookieValues);
144                 for (String value : setCookieValues)
145                     check(!value.toLowerCase().contains("httponly"),
146                           "getHeaderFields returned Set-Cookie header with HttpOnly, "
147                           + "value = " + value);
148             }
149             if ("Set-Cookie2".equalsIgnoreCase(header)) {
150                 List<String> setCookieValues = entry.getValue();
151                 debug("Set-Cookie2:" + setCookieValues);
152                 for (String value : setCookieValues)
153                     check(!value.toLowerCase().contains("httponly"),
154                           "getHeaderFields returned Set-Cookie2 header with HttpOnly, "
155                           + "value = " + value);
156             }
157         }
158 
159         // Now add some user set cookies into the mix.
160         uc = (HttpURLConnection) uri.toURL().openConnection();
161         uc.addRequestProperty("Cookie", "CUSTOMER_ID=CHEGAR;");
162         resp = uc.getResponseCode();
163         check(resp == 200,
164               "Unexpected response code. Expected 200, got " + resp);
165 
166         // TEST 5: check getRequestProperty doesn't return the HttpOnly cookie
167         cookie = uc.getRequestProperty("Cookie");
168         check(!cookie.toLowerCase().contains("httponly"),
169               "HttpOnly cookie returned from getRequestProperty, value " + cookie);
170 
171         // TEST 6: check getRequestProperties doesn't return the HttpOnly cookie.
172         reqHeaders = uc.getRequestProperties();
173         entries = reqHeaders.entrySet();
174         for (Map.Entry<String,List<String>> entry : entries) {
175             String header = entry.getKey();
176             if ("Cookie".equalsIgnoreCase(header)) {
177                 for (String val : entry.getValue())
178                     check(!val.toLowerCase().contains("httponly"),
179                           "HttpOnly cookie returned from getRequestProperties," +
180                           " value " + val);
181             }
182         }
183 
184         // TEST 7 : check that header keys containing empty key values don't return null
185         int i = 1;
186         String key = "";
187         String value = "";
188 
189         while (true) {
190             key = uc.getHeaderFieldKey(i);
191             value = uc.getHeaderField(i++);
192             if (key == null && value == null)
193                 break;
194 
195             if (key != null)
196                 check(value != null,
197                     "Encountered a null value for key value : " + key);
198         }
199 
200         // TEST 7.5 similar test but use getHeaderFields
201         respHeaders = uc.getHeaderFields();
202         respEntries = respHeaders.entrySet();
203         for (Map.Entry<String,List<String>> entry : respEntries) {
204             String header = entry.getKey();
205             if (header != null) {
206                 List<String> listValues = entry.getValue();
207                 for (String value1 : listValues)
208                     check(value1 != null,
209                         "getHeaderFields returned null values for header:, "
210                         + header);
211             }
212         }
213     }
214 
215     // HTTP Server
startHttpServer()216     HttpServer startHttpServer() throws IOException {
217         HttpServer httpServer = HttpServer.create(new InetSocketAddress(0), 0);
218         httpServer.createContext(URI_PATH, new SimpleHandler());
219         httpServer.start();
220         return httpServer;
221     }
222 
223     class SimpleHandler implements HttpHandler {
224         @Override
handle(HttpExchange t)225         public void handle(HttpExchange t) throws IOException {
226             Headers reqHeaders = t.getRequestHeaders();
227 
228             // some small sanity check
229             List<String> cookies = reqHeaders.get("Cookie");
230             for (String cookie : cookies) {
231                 if (!cookie.contains("JSESSIONID")
232                     || !cookie.contains("WILE_E_COYOTE"))
233                     t.sendResponseHeaders(400, -1);
234             }
235 
236             // return some cookies so we can check getHeaderField(s)
237             Headers respHeaders = t.getResponseHeaders();
238             List<String> values = new ArrayList<>();
239             values.add("ID=JOEBLOGGS; version=1; Path=" + URI_PATH);
240             values.add("NEW_JSESSIONID=" + (SESSION_ID+1) + "; version=1; Path="
241                        + URI_PATH +"; HttpOnly");
242             values.add("NEW_CUSTOMER=WILE_E_COYOTE2; version=1; Path=" + URI_PATH);
243             respHeaders.put("Set-Cookie", values);
244             values = new ArrayList<>();
245             values.add("COOKIE2_CUSTOMER=WILE_E_COYOTE2; version=1; Path="
246                        + URI_PATH);
247             respHeaders.put("Set-Cookie2", values);
248             values.add("COOKIE2_JSESSIONID=" + (SESSION_ID+100)
249                        + "; version=1; Path=" + URI_PATH +"; HttpOnly");
250             respHeaders.put("Set-Cookie2", values);
251 
252             t.sendResponseHeaders(200, -1);
253             t.close();
254         }
255     }
256 
257     volatile int passed = 0, failed = 0;
258     boolean debug = false;
pass()259     void pass() {passed++;}
fail()260     void fail() {failed++;}
fail(String msg)261     void fail(String msg) {System.err.println(msg); fail();}
unexpected(Throwable t)262     void unexpected(Throwable t) {failed++; t.printStackTrace();}
debug(String message)263     void debug(String message) { if (debug) System.out.println(message); }
check(boolean cond, String failMessage)264     void check(boolean cond, String failMessage) {if (cond) pass(); else fail(failMessage);}
main(String[] args)265     public static void main(String[] args) throws Throwable {
266         Class<?> k = new Object(){}.getClass().getEnclosingClass();
267         try {k.getMethod("instanceMain",String[].class)
268                 .invoke( k.newInstance(), (Object) args);}
269         catch (Throwable e) {throw e.getCause();}}
instanceMain(String[] args)270     public void instanceMain(String[] args) throws Throwable {
271         try {test(args);} catch (Throwable t) {unexpected(t);}
272         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
273         if (failed > 0) throw new AssertionError("Some tests failed");}
274 }
275 
276