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}