Cookie.java
上传用户:demmber
上传日期:2007-12-22
资源大小:717k
文件大小:16k
源码类别:

Java编程

开发平台:

Java

  1. /*
  2.  * @(#)Cookie.java 0.3-3 06/05/2001
  3.  *
  4.  *  This file is part of the HTTPClient package
  5.  *  Copyright (C) 1996-2001 Ronald Tschal鋜
  6.  *
  7.  *  This library is free software; you can redistribute it and/or
  8.  *  modify it under the terms of the GNU Lesser General Public
  9.  *  License as published by the Free Software Foundation; either
  10.  *  version 2 of the License, or (at your option) any later version.
  11.  *
  12.  *  This library is distributed in the hope that it will be useful,
  13.  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  *  Lesser General Public License for more details.
  16.  *
  17.  *  You should have received a copy of the GNU Lesser General Public
  18.  *  License along with this library; if not, write to the Free
  19.  *  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  20.  *  MA 02111-1307, USA
  21.  *
  22.  *  For questions, suggestions, bug-reports, enhancement-requests etc.
  23.  *  I may be contacted at:
  24.  *
  25.  *  ronald@innovation.ch
  26.  *
  27.  *  The HTTPClient's home page is located at:
  28.  *
  29.  *  http://www.innovation.ch/java/HTTPClient/ 
  30.  *
  31.  */
  32. package HTTPClient;
  33. import java.io.Serializable;
  34. import java.net.ProtocolException;
  35. import java.util.Date;
  36. /**
  37.  * This class represents an http cookie as specified in <a
  38.  * href="http://home.netscape.com/newsref/std/cookie_spec.html">Netscape's
  39.  * cookie spec</a>; however, because not even Netscape follows their own spec,
  40.  * and because very few folks out there actually read specs but instead just
  41.  * look whether Netscape accepts their stuff, the Set-Cookie header field
  42.  * parser actually tries to follow what Netscape has implemented, instead of
  43.  * what the spec says. Additionally, the parser it will also recognize the
  44.  * Max-Age parameter from <a
  45.  * href="http://www.ietf.org/rfc/rfc2109.txt">rfc-2109</a>, as that uses the
  46.  * same header field (Set-Cookie).
  47.  *
  48.  * <P>Some notes about how Netscape (4.7) parses:
  49.  * <ul>
  50.  * <LI>Quoting: only quotes around the expires value are recognized as such;
  51.  *     quotes around any other value are treated as part of the value.
  52.  * <LI>White space: white space around names and values is ignored
  53.  * <LI>Default path: if no path parameter is given, the path defaults to the
  54.  *     path in the request-uri up to, but not including, the last '/'. Note
  55.  *     that this is entirely different from what the spec says.
  56.  * <LI>Commas and other delimiters: Netscape just parses until the next ';'.
  57.  *     This means will allow commas etc inside values.
  58.  * </ul>
  59.  *
  60.  * @version 0.3-3  06/05/2001
  61.  * @author Ronald Tschal鋜
  62.  * @since V0.3
  63.  */
  64. public class Cookie implements Serializable
  65. {
  66.     /** Make this compatible with V0.3-2 */
  67.     private static final long serialVersionUID = 8599975325569296615L;
  68.     protected String  name;
  69.     protected String  value;
  70.     protected Date    expires;
  71.     protected String  domain;
  72.     protected String  path;
  73.     protected boolean secure;
  74.     /**
  75.      * Create a cookie.
  76.      *
  77.      * @param name    the cookie name
  78.      * @param value   the cookie value
  79.      * @param domain  the host this cookie will be sent to
  80.      * @param path    the path prefix for which this cookie will be sent
  81.      * @param epxires the Date this cookie expires, null if at end of
  82.      *                session
  83.      * @param secure  if true this cookie will only be over secure connections
  84.      * @exception NullPointerException if <var>name</var>, <var>value</var>,
  85.      *                                 <var>domain</var>, or <var>path</var>
  86.      *                                 is null
  87.      * @since V0.3-1
  88.      */
  89.     public Cookie(String name, String value, String domain, String path,
  90.   Date expires, boolean secure)
  91.     {
  92. if (name == null)   throw new NullPointerException("missing name");
  93. if (value == null)  throw new NullPointerException("missing value");
  94. if (domain == null) throw new NullPointerException("missing domain");
  95. if (path == null)   throw new NullPointerException("missing path");
  96. this.name    = name;
  97. this.value   = value;
  98. this.domain  = domain.toLowerCase();
  99. this.path    = path;
  100. this.expires = expires;
  101. this.secure  = secure;
  102. if (this.domain.indexOf('.') == -1)  this.domain += ".local";
  103.     }
  104.     /**
  105.      * Use <code>parse()</code> to create cookies.
  106.      *
  107.      * @see #parse(java.lang.String, HTTPClient.RoRequest)
  108.      */
  109.     protected Cookie(RoRequest req)
  110.     {
  111. name    = null;
  112. value   = null;
  113. expires = null;
  114. domain  = req.getConnection().getHost();
  115. if (domain.indexOf('.') == -1)  domain += ".local";
  116. path    = Util.getPath(req.getRequestURI());
  117. /* This does not follow netscape's spec at all, but it's the way
  118.  * netscape seems to do it, and because people rely on that we
  119.  * therefore also have to do it...
  120.  */
  121. int slash = path.lastIndexOf('/');
  122. if (slash >= 0)
  123.     path = path.substring(0, slash);
  124. secure = false;
  125.     }
  126.     /**
  127.      * Parses the Set-Cookie header into an array of Cookies.
  128.      *
  129.      * @param set_cookie the Set-Cookie header received from the server
  130.      * @param req the request used
  131.      * @return an array of Cookies as parsed from the Set-Cookie header
  132.      * @exception ProtocolException if an error occurs during parsing
  133.      */
  134.     protected static Cookie[] parse(String set_cookie, RoRequest req)
  135. throws ProtocolException
  136.     {
  137.         int    beg = 0,
  138.                end = 0,
  139.        start = 0;
  140.         char[] buf = set_cookie.toCharArray();
  141.         int    len = buf.length;
  142.         Cookie cookie_arr[] = new Cookie[0], curr;
  143.         cookies: while (true)                    // get all cookies
  144.         {
  145.             beg = Util.skipSpace(buf, beg);
  146.             if (beg >= len)  break; // no more left
  147.     if (buf[beg] == ',') // empty header
  148.     {
  149. beg++;
  150. continue;
  151.     }
  152.     curr  = new Cookie(req);
  153.     start = beg;
  154.     // get cookie name and value first
  155.     end = set_cookie.indexOf('=', beg);
  156.     if (end == -1)
  157. throw new ProtocolException("Bad Set-Cookie header: " +
  158.     set_cookie + "nNo '=' found " +
  159.     "for token starting at " +
  160.     "position " + beg);
  161.     curr.name = set_cookie.substring(beg, end).trim(); 
  162.     beg = Util.skipSpace(buf, end+1);
  163.     int comma = set_cookie.indexOf(',', beg);
  164.     int semic = set_cookie.indexOf(';', beg);
  165.     if (comma == -1  &&  semic == -1)  end = len;
  166.     else if (comma == -1)  end = semic;
  167.     else if (semic == -1)  end = comma;
  168.     else
  169.     {
  170. if (comma > semic)
  171.     end = semic;
  172. else
  173. {
  174.     // try to handle broken servers which put commas
  175.     // into cookie values
  176.     int eq = set_cookie.indexOf('=', comma);
  177.     if (eq > 0  &&  eq < semic)
  178. end = set_cookie.lastIndexOf(',', eq);
  179.     else
  180. end = semic;
  181. }
  182.     }
  183.     curr.value = set_cookie.substring(beg, end).trim();
  184.     beg = end;
  185.     // now parse attributes
  186.     boolean legal = true;
  187.     parts: while (true) // parse all parts
  188.     {
  189. if (beg >= len  ||  buf[beg] == ',')  break;
  190. // skip empty fields
  191. if (buf[beg] == ';')
  192. {
  193.     beg = Util.skipSpace(buf, beg+1);
  194.     continue;
  195. }
  196. // first check for secure, as this is the only one w/o a '='
  197. if ((beg+6 <= len)  &&
  198.     set_cookie.regionMatches(true, beg, "secure", 0, 6))
  199. {
  200.     curr.secure = true;
  201.     beg += 6;
  202.     beg = Util.skipSpace(buf, beg);
  203.     if (beg < len  &&  buf[beg] == ';') // consume ";"
  204. beg = Util.skipSpace(buf, beg+1);
  205.     else if (beg < len  &&  buf[beg] != ',')
  206. throw new ProtocolException("Bad Set-Cookie header: " +
  207.     set_cookie + "nExpected " +
  208.     "';' or ',' at position " +
  209.     beg);
  210.     continue;
  211. }
  212. // alright, must now be of the form x=y
  213. end = set_cookie.indexOf('=', beg);
  214. if (end == -1)
  215.     throw new ProtocolException("Bad Set-Cookie header: " +
  216. set_cookie + "nNo '=' found " +
  217. "for token starting at " +
  218. "position " + beg);
  219. String name = set_cookie.substring(beg, end).trim();
  220. beg = Util.skipSpace(buf, end+1);
  221. if (name.equalsIgnoreCase("expires"))
  222. {
  223.     /* Netscape ignores quotes around the date, and some twits
  224.      * actually send that...
  225.      */
  226.     if (set_cookie.charAt(beg) == '"')
  227. beg = Util.skipSpace(buf, beg+1);
  228.     /* cut off the weekday if it is there. This is a little
  229.      * tricky because the comma is also used between cookies
  230.      * themselves. To make sure we don't inadvertantly
  231.      * mistake a date for a weekday we only skip letters.
  232.      */
  233.     int pos = beg;
  234.     while (pos < len  &&
  235.    (buf[pos] >= 'a'  &&  buf[pos] <= 'z'  ||
  236.     buf[pos] >= 'A'  &&  buf[pos] <= 'Z'))
  237. pos++;
  238.     pos = Util.skipSpace(buf, pos);
  239.     if (pos < len  &&  buf[pos] == ','  &&  pos > beg)
  240. beg = pos+1;
  241. }
  242. comma = set_cookie.indexOf(',', beg);
  243. semic = set_cookie.indexOf(';', beg);
  244. if (comma == -1  &&  semic == -1)  end = len;
  245. else if (comma == -1)  end = semic;
  246. else if (semic == -1)  end = comma;
  247. else end = Math.min(comma, semic);
  248. String value = set_cookie.substring(beg, end).trim();
  249. legal &= setAttribute(curr, name, value, set_cookie);
  250. beg = end;
  251. if (beg < len  &&  buf[beg] == ';') // consume ";"
  252.     beg = Util.skipSpace(buf, beg+1);
  253.     }
  254.     if (legal)
  255.     {
  256. cookie_arr = Util.resizeArray(cookie_arr, cookie_arr.length+1);
  257. cookie_arr[cookie_arr.length-1] = curr;
  258.     } else
  259. Log.write(Log.COOKI, "Cooki: Ignoring cookie: " + curr);
  260. }
  261. return cookie_arr;
  262.     }
  263.     /**
  264.      * Set the given attribute, if valid.
  265.      *
  266.      * @param cookie     the cookie on which to set the value
  267.      * @param name       the name of the attribute
  268.      * @param value      the value of the attribute
  269.      * @param set_cookie the complete Set-Cookie header
  270.      * @return true if the attribute is legal; false otherwise
  271.      */
  272.     private static boolean setAttribute(Cookie cookie, String name,
  273. String value, String set_cookie)
  274.     throws ProtocolException
  275.     {
  276. if (name.equalsIgnoreCase("expires"))
  277. {
  278.     if (value.charAt(value.length()-1) == '"')
  279. value = value.substring(0, value.length()-1).trim();
  280.     try
  281. // This is too strict...
  282. // { cookie.expires = Util.parseHttpDate(value); }
  283. { cookie.expires = new Date(value); }
  284.     catch (IllegalArgumentException iae)
  285.     {
  286. /* More broken servers to deal with... Ignore expires
  287.  * if it's invalid
  288. throw new ProtocolException("Bad Set-Cookie header: " +
  289.     set_cookie + "nInvalid date found at " +
  290.     "position " + beg);
  291. */
  292. Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
  293.      "n       Invalid date `" + value + "'");
  294.     }
  295. }
  296. else if (name.equals("max-age")) // from rfc-2109
  297. {
  298.     if (cookie.expires != null)  return true;
  299.     if (value.charAt(0) == '"'  &&  value.charAt(value.length()-1) == '"')
  300. value = value.substring(1, value.length()-1).trim();
  301.     int age;
  302.     try
  303. { age = Integer.parseInt(value); }
  304.     catch (NumberFormatException nfe)
  305.     {
  306. throw new ProtocolException("Bad Set-Cookie header: " +
  307.     set_cookie + "nMax-Age '" + value +
  308.     "' not a number");
  309.     }
  310.     cookie.expires = new Date(System.currentTimeMillis() + age*1000L);
  311. }
  312. else if (name.equalsIgnoreCase("domain"))
  313. {
  314.     // you get everything these days...
  315.     if (value.length() == 0)
  316.     {
  317. Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
  318.      "n       domain is empty - ignoring domain");
  319. return true;
  320.     }
  321.     // domains are case insensitive.
  322.     value = value.toLowerCase();
  323.     // add leading dot, if missing
  324.     if (value.length() != 0 && value.charAt(0) != '.'  &&
  325. !value.equals(cookie.domain))
  326. value = '.' + value;
  327.     // must be the same domain as in the url
  328.     if (!cookie.domain.endsWith(value))
  329.     {
  330. Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
  331.      "n       Current domain " + cookie.domain +
  332.      " does not match given parsed " + value);
  333. return false;
  334.     }
  335.     /* Netscape's original 2-/3-dot rule really doesn't work because
  336.      * many countries use a shallow hierarchy (similar to the special
  337.      * TLDs defined in the spec). While the rules in rfc-2965 aren't
  338.      * perfect either, they are better. OTOH, some sites use a domain
  339.      * so that the host name minus the domain name contains a dot (e.g.
  340.      * host x.x.yahoo.com and domain .yahoo.com). So, for the seven
  341.      * special TLDs we use the 2-dot rule, and for all others we use
  342.      * the rules in the state-man draft instead.
  343.      */
  344.     // domain must be either .local or must contain at least
  345.     // two dots
  346.     if (!value.equals(".local")  && value.indexOf('.', 1) == -1)
  347.     {
  348. Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
  349.      "n       Domain attribute " + value +
  350.      "isn't .local and doesn't have at " +
  351.      "least 2 dots");
  352. return false;
  353.     }
  354.     // If TLD not special then host minus domain may not
  355.     // contain any dots
  356.     String top = null;
  357.     if (value.length() > 3 )
  358. top = value.substring(value.length()-4);
  359.     if (top == null  ||  !(
  360. top.equalsIgnoreCase(".com")  ||
  361. top.equalsIgnoreCase(".edu")  ||
  362. top.equalsIgnoreCase(".net")  ||
  363. top.equalsIgnoreCase(".org")  ||
  364. top.equalsIgnoreCase(".gov")  ||
  365. top.equalsIgnoreCase(".mil")  ||
  366. top.equalsIgnoreCase(".int")))
  367.     {
  368. int dl = cookie.domain.length(), vl = value.length();
  369. if (dl > vl  &&
  370.     cookie.domain.substring(0, dl-vl).indexOf('.') != -1)
  371. {
  372.     Log.write(Log.COOKI, "Cooki: Bad Set-Cookie header: " + set_cookie +
  373.  "n       Domain attribute " + value +
  374.  "is more than one level below " +
  375.  "current domain " + cookie.domain);
  376.     return false;
  377. }
  378.     }
  379.     cookie.domain = value;
  380. }
  381. else if (name.equalsIgnoreCase("path"))
  382.     cookie.path = value;
  383. else
  384.   ; // unknown attribute - ignore
  385. return true;
  386.     }
  387.     /**
  388.      * Return the name of this cookie.
  389.      */
  390.     public String getName()
  391.     {
  392. return name;
  393.     }
  394.     /**
  395.      * Return the value of this cookie.
  396.      */
  397.     public String getValue()
  398.     {
  399. return value;
  400.     }
  401.     /**
  402.      * @return the expiry date of this cookie, or null if none set.
  403.      */
  404.     public Date expires()
  405.     {
  406. return expires;
  407.     }
  408.     /**
  409.      * @return true if the cookie should be discarded at the end of the
  410.      *         session; false otherwise
  411.      */
  412.     public boolean discard()
  413.     {
  414. return (expires == null);
  415.     }
  416.     /**
  417.      * Return the domain this cookie is valid in.
  418.      */
  419.     public String getDomain()
  420.     {
  421. return domain;
  422.     }
  423.     /**
  424.      * Return the path this cookie is associated with.
  425.      */
  426.     public String getPath()
  427.     {
  428. return path;
  429.     }
  430.     /**
  431.      * Return whether this cookie should only be sent over secure connections.
  432.      */
  433.     public boolean isSecure()
  434.     {
  435. return secure;
  436.     }
  437.     /**
  438.      * @return true if this cookie has expired
  439.      */
  440.     public boolean hasExpired()
  441.     {
  442. return (expires != null  &&  expires.getTime() <= System.currentTimeMillis());
  443.     }
  444.     /**
  445.      * @param  req  the request to be sent
  446.      * @return true if this cookie should be sent with the request
  447.      */
  448.     protected boolean sendWith(RoRequest req)
  449.     {
  450. HTTPConnection con = req.getConnection();
  451. String eff_host = con.getHost();
  452. if (eff_host.indexOf('.') == -1)  eff_host += ".local";
  453. return ((domain.charAt(0) == '.'  &&  eff_host.endsWith(domain)  ||
  454.  domain.charAt(0) != '.'  &&  eff_host.equals(domain))  &&
  455. Util.getPath(req.getRequestURI()).startsWith(path)  &&
  456. (!secure || con.getProtocol().equals("https") ||
  457.  con.getProtocol().equals("shttp")));
  458.     }
  459.     /**
  460.      * Hash up name, path and domain into new hash.
  461.      */
  462.     public int hashCode()
  463.     {
  464. return (name.hashCode() + path.hashCode() + domain.hashCode());
  465.     }
  466.     /**
  467.      * Two cookies match if the name, path and domain match.
  468.      */
  469.     public boolean equals(Object obj)
  470.     {
  471. if ((obj != null) && (obj instanceof Cookie))
  472. {
  473.     Cookie other = (Cookie) obj;
  474.     return  (this.name.equals(other.name)  &&
  475.      this.path.equals(other.path)  &&
  476.      this.domain.equals(other.domain));
  477. }
  478. return false;
  479.     }
  480.     /**
  481.      * @return a string suitable for sending in a Cookie header.
  482.      */
  483.     protected String toExternalForm()
  484.     {
  485. return name + "=" + value;
  486.     }
  487.     /**
  488.      * Create a string containing all the cookie fields. The format is that
  489.      * used in the Set-Cookie header.
  490.      */
  491.     public String toString()
  492.     {
  493. StringBuffer res = new StringBuffer(name.length() + value.length() + 30);
  494. res.append(name).append('=').append(value);
  495. if (expires != null)  res.append("; expires=").append(expires);
  496. if (path != null)     res.append("; path=").append(path);
  497. if (domain != null)   res.append("; domain=").append(domain);
  498. if (secure)           res.append("; secure");
  499. return res.toString();
  500.     }
  501. }