001package ca.bc.webarts.tools; 002 003import java.net.*; 004import javax.net.ssl.HttpsURLConnection; 005import java.io.*; 006import java.util.*; 007import java.text.*; 008 009/** 010 * MyCookieHandler is a simple utilty for handling cookies when working 011 * with java.net.URL and java.net.URLConnection 012 * objects.<br> 013 * 014 * It provides all the details of the calls to set and get the cookies... 015 *<pre> 016 * MyCookieHandler cm = new MyCookieHandler(); 017 * URL url = new URL("http://www.hccp.org/test/cookieTest.jsp"); 018 * 019 * . . . 020 * 021 * // getting cookies: 022 * URLConnection conn = url.openConnection(); 023 * conn.connect(); 024 * 025 * // setting cookies 026 * cm.storeCookies(conn); 027 * cm.setCookies(url.openConnection()); 028 * 029 *</pre> 030 * <u><b>MyCookieHandler abstracts all the following java api code...</b></u> 031 * <h1>HOW-TO: Handling cookies using the java.net.* API</h1> 032 * <h3>Author: <i>Ian Brown <a href="mailto:spam@hccp.org">spam@hccp.org</a></i></h3> 033 * <p> 034 * This is a brief overview on how to retrieve cookies from HTTP responses and how to return cookies in HTTP requests to the appropriate server using the <b>java.net.*</b> APIs. 035 * <br> 036 * <b><ul> 037 * <li><a href="#what_are_cookies">What are cookies?</a></li> 038 * <li><a href="#retrieving_cookies">Retrieving cookies from a response.</a></li> 039 * <li><a href="#setting_values">Setting a cookie value in a request.</a></li> 040 * <li><a href="#setting_multiple">Setting <i>multiple</i> cookie values in a request.</a></li> 041 * <li><a href="#sample_code">Sample code.</a></li> 042 * </ul> 043 * </b> 044 * <p> 045 * <a name="what_are_cookies"/><h2>What are cookies?</h2>Cookies are small strings of data of the form <i>name</i>=<i>value</i>. These are delivered to the client via the header variables in an HTTP response. Upon recieving a cookie from a web server, the client application should store that cookie, returning it to the server in subsequent requests. For greater detail see the Netscape specification: <a href="http://web.archive.org/web/20101114022703/http://wp.netscape.com/newsref/std/cookie_spec.html">http://wp.netscape.com/newsref/std/cookie_spec.html</a></p> 046 * 047 * <p> 048 * <a name="retrieving_cookies"/><h2>Retrieving cookies from a response:</h2> 049 * <ol> 050 * <li><b>Open a <i>java.net.URLConnection</i> to the server:</b><br> 051 * <pre> 052 * URL myUrl = new URL("http://www.hccp.org/cookieTest.jsp"); 053 * URLConnection urlConn = myUrl.openConnection(); 054 * urlConn.connect(); 055 * </pre> 056 * </li> 057 * <li><b>Loop through response headers looking for cookies:</b><br> 058 * <p>Since a server may set multiple cookies in a single request, we will need to loop through the response headers, looking for all headers named "Set-Cookie".</p> 059 * <pre> 060 * String headerName=null; 061 * for (int i=1; (headerName = uc.getHeaderFieldKey(i))!=null; i++) { 062 * if (headerName.equals("Set-Cookie")) { 063 * String cookie = urlConn.getHeaderField(i); 064 * ... 065 * 066 * </pre> 067 * </li> 068 * <li><b>Extract cookie name and value from cookie string:</b><br> 069 * <p>The string returned by the <b>getHeaderField(int index)</b> method is a series of <i>name=value</i> separated by semi-colons (;). The first name/value pairing is actual data string we are interested in (<i>i.e. "sessionId=0949eeee22222rtg" or "userId=igbrown"</i>), the subsequent name/value pairings are meta-information that we would use to manage the storage of the cookie (<i>when it expires, etc.</i>).</p> 070 * <pre> 071 * cookie = cookie.substring(0, cookie.indexOf(";")); 072 * String cookieName = cookie.substring(0, cookie.indexOf("=")); 073 * String cookieValue = cookie.substring(cookie.indexOf("=") + 1, cookie.length()); 074 * 075 * </pre> 076 * This is basically it. We now have the cookie name (<i>cookieName</i>) and the cookie value (<i>cookieValue</i>). 077 * </li> 078 * 079 * </ol> 080 * </p> 081 * <p> 082 * <a name="setting_values"/><h2>Setting a cookie value in a request:</h2> 083 * <ol> 084 * <li><b>Values must be set prior to calling the connect method:</b><br> 085 * <pre> 086 * URL myUrl = new URL("http://www.hccp.org/cookieTest.jsp"); 087 * URLConnection urlConn = myUrl.openConnection(); 088 * </pre> 089 * </li> 090 * <li><b>Create a cookie string:</b><br> 091 * <pre> 092 * String myCookie = "userId=igbrown"; 093 * </pre> 094 * </li> 095 * <li><b>Add the cookie to a request:</b><br> 096 * <p>Using the <i>setRequestProperty(String name, String value);</i> method, we will add a property named "Cookie", passing the cookie string created in the previous step as the property value. 097 * <pre> 098 * urlConn.setRequestProperty("Cookie", myCookie); 099 * </pre> 100 * </p> 101 * </li> 102 * <li><b>Send the cookie to the server:</b><br> 103 * <p>To send the cookie, simply call connect() on the URLConnection for which we have added the cookie property: 104 * <pre>urlConn.connect()</pre> 105 * </p> 106 * </li> 107 * </ol> 108 * </p> 109 * <p> 110 * <a name="setting_multiple"/><h2>Setting a <i>multiple</i> cookie values in a request:</h2> 111 * <ol> 112 * <li>Perform the same steps as the above item (<i>Setting a a cookie value in a request</i>), replacing the single valued cookie string with something like the following: 113 * <pre> 114 * String myCookies = "userId=igbrown; sessionId=SID77689211949; isAuthenticated=true"; 115 * </pre> 116 * This string contains three cookies (<i>userId</i>, <i>sessionId</i>, and <i>isAuthenticated</i>). Separate cookie name/value pairs with <i>"; "</i> (semicolon and whitespace).<br><br> 117 * Note that you cannot set multiple request properties using the same name, so trying to call the setRequestProperty("Cookie" , someCookieValue) method will just overwrite any previously set value. 118 * </li> 119 * </ol> 120 * </p> 121 * <p> 122 * <a name="sample_code"/><h2>Sample Code:</h2> 123 * 124 * <table cellpadding=3 bgcolor="000000> 125 * <tr"> 126 * <td bgcolor="eeeeee"> 127 * <table width="100%"><tr><td><tt><b>MyCookieHandler.java</b></tt></td><td align="right"><tt>Download this example at <a href="http://www.hccp.org/cvs/org/hccp/net/MyCookieHandler.java">http://www.hccp.org/cvs/org/hccp/net/MyCookieHandler.java</a></tt></td></tr></table> 128 * </td> 129 * </tr> 130 * <tr> 131 * <td bgcolor="eeeeee"> 132 * code goes here... 133 * </td> 134 * </tr> 135 * </table> 136 * </p> 137 * 138 * @author Ian Brown 139 **/ 140 141public class MyCookieHandler 142{ 143 144 private Map<String, Map> store; 145 146 private static final String SET_COOKIE = "Set-Cookie"; 147 private static final String COOKIE_VALUE_DELIMITER = ";"; 148 private static final String PATH = "path"; 149 private static final String EXPIRES = "expires"; 150 private static final String DATE_FORMAT = "EEE, dd-MMM-yyyy hh:mm:ss z"; 151 private static final String SET_COOKIE_SEPARATOR="; "; 152 private static final String COOKIE = "Cookie"; 153 154 private static final char NAME_VALUE_SEPARATOR = '='; 155 private static final char DOT = '.'; 156 157 private DateFormat dateFormat; 158 159 160 /** Defaulkt Constructor. **/ 161 public MyCookieHandler() 162 { 163 store = new HashMap<String, Map>(); 164 dateFormat = new SimpleDateFormat(DATE_FORMAT); 165 } 166 167/* 168 public void storeCookies(HttpsURLConnection conn) throws IOException 169 { 170 storeCookies((java.net.URLConnection) conn); 171 } 172 173 174 public void storeCookies(HttpURLConnection conn) throws IOException 175 { 176 storeCookies((java.net.URLConnection) conn); 177 } 178*/ 179 180 /** 181 * Retrieves and stores cookies returned by the host on the other side 182 * of the the open java.net.URLConnection. 183 * 184 * The connection MUST have been opened using the connect() 185 * method or a IOException will be thrown. 186 * 187 * @param conn a java.net.URLConnection - must be open, or IOException will be thrown 188 * @throws java.io.IOException Thrown if conn is not open. 189 */ 190 public void storeCookies(java.net.URLConnection conn) throws IOException 191 { 192 193 // let's determine the domain from where these cookies are being sent 194 String domain = getDomainFromHost(conn.getURL().getHost()); 195 196 Map domainStore; // this is where we will store cookies for this domain 197 198 // now let's check the store to see if we have an entry for this domain 199 if (store.containsKey(domain)) 200 { 201 // we do, so lets retrieve it from the store 202 domainStore = (Map)store.get(domain); 203 } 204 else 205 { 206 // we don't, so let's create it and put it in the store 207 domainStore = new HashMap<String, Map<String, String> >(); 208 store.put(domain, domainStore); 209 } 210 // OK, now we are ready to get the cookies out of the URLConnection 211 212 String headerName=null; 213 for (int i=1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) 214 { 215 if (headerName.equalsIgnoreCase(SET_COOKIE)) 216 { 217 Map<String, String> cookie = new HashMap<String, String>(); 218 StringTokenizer st = new StringTokenizer(conn.getHeaderField(i), COOKIE_VALUE_DELIMITER); 219 220 // the specification dictates that the first name/value pair 221 // in the string is the cookie name and value, so let's handle 222 // them as a special case: 223 if (st.hasMoreTokens()) 224 { 225 String token = st.nextToken(); 226 String name = token.substring(0, token.indexOf(NAME_VALUE_SEPARATOR)); 227 String value = token.substring(token.indexOf(NAME_VALUE_SEPARATOR) + 1, token.length()); 228 cookie.put(name, value); 229 domainStore.put(name, cookie); 230 } 231 232 while (st.hasMoreTokens()) 233 { 234 String token = st.nextToken(); 235 cookie.put(token.substring(0, token.indexOf(NAME_VALUE_SEPARATOR)).toLowerCase(), 236 token.substring(token.indexOf(NAME_VALUE_SEPARATOR) + 1, token.length())); 237 } 238 } 239 } 240 } 241 242 243 /** 244 * Prior to opening a URLConnection, calling this method will set all 245 * unexpired cookies that match the path or subpaths for thi underlying URL 246 * 247 * The connection MUST NOT have been opened 248 * method or an IOException will be thrown. 249 * 250 * @param conn a java.net.URLConnection - must NOT be open, or IOException will be thrown 251 * @throws java.io.IOException Thrown if conn has already been opened. 252 */ 253 public void setCookies(URLConnection conn) throws IOException 254 { 255 256 // let's determine the domain and path to retrieve the appropriate cookies 257 URL url = conn.getURL(); 258 String domain = getDomainFromHost(url.getHost()); 259 String path = url.getPath(); 260 261 Map domainStore = (Map)store.get(domain); 262 if (domainStore == null) return; 263 StringBuffer cookieStringBuffer = new StringBuffer(); 264 265 Iterator cookieNames = domainStore.keySet().iterator(); 266 while(cookieNames.hasNext()) 267 { 268 String cookieName = (String)cookieNames.next(); 269 Map cookie = (Map)domainStore.get(cookieName); 270 // check cookie to ensure path matches and cookie is not expired 271 // if all is cool, add cookie to header string 272 if (comparePaths((String)cookie.get(PATH), path) && isNotExpired((String)cookie.get(EXPIRES))) 273 { 274 cookieStringBuffer.append(cookieName); 275 cookieStringBuffer.append("="); 276 cookieStringBuffer.append((String)cookie.get(cookieName)); 277 if (cookieNames.hasNext()) cookieStringBuffer.append(SET_COOKIE_SEPARATOR); 278 } 279 } 280 try 281 { 282 conn.setRequestProperty(COOKIE, cookieStringBuffer.toString()); 283 } 284 catch (java.lang.IllegalStateException ise) 285 { 286 IOException ioe = new IOException("Illegal State! Cookies cannot be set on a URLConnection that is already connected. " 287 + "Only call setCookies(java.net.URLConnection) AFTER calling java.net.URLConnection.connect()."); 288 throw ioe; 289 } 290 } 291 292 293 private String getDomainFromHost(String host) 294 { 295 if (host.indexOf(DOT) != host.lastIndexOf(DOT)) 296 { 297 return host.substring(host.indexOf(DOT) + 1); 298 } 299 else 300 { 301 return host; 302 } 303 } 304 305 306 private boolean isNotExpired(String cookieExpires) 307 { 308 if (cookieExpires == null) return true; 309 Date now = new Date(); 310 try 311 { 312 return (now.compareTo(dateFormat.parse(cookieExpires))) <= 0; 313 } 314 catch (java.text.ParseException pe) 315 { 316 pe.printStackTrace(); 317 return false; 318 } 319 } 320 321 322 private boolean comparePaths(String cookiePath, String targetPath) 323 { 324 if (cookiePath == null) 325 { 326 return true; 327 } 328 else if (cookiePath.equals("/")) 329 { 330 return true; 331 } 332 else if (targetPath.regionMatches(0, cookiePath, 0, cookiePath.length())) 333 { 334 return true; 335 } 336 else { 337 return false; 338 } 339 } 340 341 342 /** 343 * Returns a string representation of stored cookies organized by domain. 344 */ 345 346 public String toString() 347 { 348 return store.toString(); 349 } 350 351 352 public static void main(String[] args) 353 { 354 MyCookieHandler cm = new MyCookieHandler(); 355 try { 356 URL url = new URL("http://www.hccp.org/test/cookieTest.jsp"); 357 URLConnection conn = url.openConnection(); 358 conn.connect(); 359 cm.storeCookies(conn); 360 System.out.println(cm); 361 cm.setCookies(url.openConnection()); 362 } 363 catch (IOException ioe) 364 { 365 ioe.printStackTrace(); 366 } 367 } 368}