/*
* @(#)Cookie2.java 0.3-3 06/05/2001
*
* This file is part of the HTTPClient package
* Copyright (C) 1996-2001 Ronald Tschal�r
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307, USA
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* ronald@innovation.ch
*
* The HTTPClient's home page is located at:
*
* http://www.innovation.ch/java/HTTPClient/
*
*/
package HTTPClient;
import java.io.UnsupportedEncodingException;
import java.net.ProtocolException;
import java.util.Date;
import java.util.Vector;
import java.util.StringTokenizer;
/**
* This class represents an http cookie as specified in the <A
* HREF="http://www.ietf.org/rfc/rfc2965.txt">HTTP State Management Mechanism spec</A>
* (also known as a version 1 cookie).
*
* @version 0.3-3 06/05/2001
* @author Ronald Tschal�r
* @since V0.3
*/
public class Cookie2 extends Cookie
{
/** Make this compatible with V0.3-2 */
private static final long serialVersionUID = 2208203902820875917L;
protected int version;
protected boolean discard;
protected String comment;
protected URI comment_url;
protected int[] port_list;
protected String port_list_str;
protected boolean path_set;
protected boolean port_set;
protected boolean domain_set;
/**
* Create a cookie.
*
* @param name the cookie name
* @param value the cookie value
* @param domain the host this cookie will be sent to
* @param port_list an array of allowed server ports for this cookie,
* or null if the the cookie may be sent to any port
* @param path the path prefix for which this cookie will be sent
* @param epxires the Date this cookie expires, or null if never
* @param discard if true then the cookie will be discarded at the
* end of the session regardless of expiry
* @param secure if true this cookie will only be over secure connections
* @param comment the comment associated with this cookie, or null if none
* @param comment_url the comment URL associated with this cookie, or null
* if none
* @exception NullPointerException if <var>name</var>, <var>value</var>,
* <var>domain</var>, or <var>path</var>
* is null
*/
public Cookie2(String name, String value, String domain, int[] port_list,
String path, Date expires, boolean discard, boolean secure,
String comment, URI comment_url)
{
super(name, value, domain, path, expires, secure);
this.discard = discard;
this.port_list = port_list;
this.comment = comment;
this.comment_url = comment_url;
path_set = true;
domain_set = true;
if (port_list != null && port_list.length > 0)
{
StringBuffer tmp = new StringBuffer();
tmp.append(port_list[0]);
for (int idx=1; idx<port_list.length; idx++)
{
tmp.append(',');
tmp.append(port_list[idx]);
}
port_list_str = tmp.toString();
port_set = true;
}
version = 1;
}
/**
* Use <code>parse()</code> to create cookies.
*
* @see #parse(java.lang.String, HTTPClient.RoRequest)
*/
protected Cookie2(RoRequest req)
{
super(req);
path = Util.getPath(req.getRequestURI());
int slash = path.lastIndexOf('/');
if (slash != -1) path = path.substring(0, slash+1);
if (domain.indexOf('.') == -1) domain += ".local";
version = -1;
discard = false;
comment = null;
comment_url = null;
port_list = null;
port_list_str = null;
path_set = false;
port_set = false;
domain_set = false;
}
/**
* Parses the Set-Cookie2 header into an array of Cookies.
*
* @param set_cookie the Set-Cookie2 header received from the server
* @param req the request used
* @return an array of Cookies as parsed from the Set-Cookie2 header
* @exception ProtocolException if an error occurs during parsing
*/
protected static Cookie[] parse(String set_cookie, RoRequest req)
throws ProtocolException
{
Vector cookies;
try
{ cookies = Util.parseHeader(set_cookie); }
catch (ParseException pe)
{ throw new ProtocolException(pe.getMessage()); }
Cookie cookie_arr[] = new Cookie[cookies.size()];
int cidx=0;
for (int idx=0; idx<cookie_arr.length; idx++)
{
HttpHeaderElement c_elem =
(HttpHeaderElement) cookies.elementAt(idx);
// set NAME and VALUE
if (c_elem.getValue() == null)
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nMissing value " +
"for cookie '" + c_elem.getName() +
"'");
Cookie2 curr = new Cookie2(req);
curr.name = c_elem.getName();
curr.value = c_elem.getValue();
// set all params
NVPair[] params = c_elem.getParams();
boolean discard_set = false, secure_set = false;
for (int idx2=0; idx2<params.length; idx2++)
{
String name = params[idx2].getName().toLowerCase();
// check for required value parts
if ((name.equals("version") || name.equals("max-age") ||
name.equals("domain") || name.equals("path") ||
name.equals("comment") || name.equals("commenturl")) &&
params[idx2].getValue() == null)
{
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nMissing value "+
"for " + params[idx2].getName()+
" attribute in cookie '" +
c_elem.getName() + "'");
}
if (name.equals("version")) // Version
{
if (curr.version != -1) continue;
try
{
curr.version =
Integer.parseInt(params[idx2].getValue());
}
catch (NumberFormatException nfe)
{
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nVersion '" +
params[idx2].getValue() +
"' not a number");
}
}
else if (name.equals("path")) // Path
{
if (curr.path_set) continue;
curr.path = params[idx2].getValue();
curr.path_set = true;
}
else if (name.equals("domain")) // Domain
{
if (curr.domain_set) continue;
String d = params[idx2].getValue().toLowerCase();
// add leading dot if not present and if domain is
// not the full host name
if (d.charAt(0) != '.' && !d.equals(curr.domain))
curr.domain = "." + d;
else
curr.domain = d;
curr.domain_set = true;
}
else if (name.equals("max-age")) // Max-Age
{
if (curr.expires != null) continue;
int age;
try
{ age = Integer.parseInt(params[idx2].getValue()); }
catch (NumberFormatException nfe)
{
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nMax-Age '" +
params[idx2].getValue() +
"' not a number");
}
curr.expires =
new Date(System.currentTimeMillis() + age*1000L);
}
else if (name.equals("port")) // Port
{
if (curr.port_set) continue;
if (params[idx2].getValue() == null)
{
curr.port_list = new int[1];
curr.port_list[0] = req.getConnection().getPort();
curr.port_set = true;
continue;
}
curr.port_list_str = params[idx2].getValue();
StringTokenizer tok =
new StringTokenizer(params[idx2].getValue(), ",");
curr.port_list = new int[tok.countTokens()];
for (int idx3=0; idx3<curr.port_list.length; idx3++)
{
String port = tok.nextToken().trim();
try
{ curr.port_list[idx3] = Integer.parseInt(port); }
catch (NumberFormatException nfe)
{
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nPort '" +
port + "' not a number");
}
}
curr.port_set = true;
}
else if (name.equals("discard")) // Domain
{
if (discard_set) continue;
curr.discard = true;
discard_set = true;
}
else if (name.equals("secure")) // Secure
{
if (secure_set) continue;
curr.secure = true;
secure_set = true;
}
else if (name.equals("comment")) // Comment
{
if (curr.comment != null) continue;
try
{
curr.comment =
new String(params[idx2].getValue().getBytes("8859_1"), "UTF8");
}
catch (UnsupportedEncodingException usee)
{ throw new Error(usee.toString()); /* shouldn't happen */ }
}
else if (name.equals("commenturl")) // CommentURL
{
if (curr.comment_url != null) continue;
try
{ curr.comment_url = new URI(params[idx2].getValue()); }
catch (ParseException pe)
{
throw new ProtocolException("Bad Set-Cookie2 header: " +
set_cookie + "\nCommentURL '" +
params[idx2].getValue() +
"' not a valid URL");
}
}
// ignore unknown element
}
// check version
if (curr.version == -1) continue;
// setup defaults
if (curr.expires == null) curr.discard = true;
// check validity
// path attribute must be a prefix of the request-URI
if (!Util.getPath(req.getRequestURI()).startsWith(curr.path))
{
Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
set_cookie + "\n path `" +
curr.path + "' is not a prefix of the " +
"request uri `" + req.getRequestURI() +
"'");
continue;
}
// if host name is simple (i.e w/o a domain) then append .local
String eff_host = req.getConnection().getHost();
if (eff_host.indexOf('.') == -1) eff_host += ".local";
// domain must be either .local or must contain at least two dots
if (!curr.domain.equals(".local") &&
curr.domain.indexOf('.', 1) == -1)
{
Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
set_cookie + "\n domain `" +
curr.domain + "' is not `.local' and " +
"doesn't contain two `.'s");
continue;
}
// domain must domain match host
if (!eff_host.endsWith(curr.domain))
{
Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
set_cookie + "\n domain `" +
curr.domain + "' does not match current" +
"host `" + eff_host + "'");
continue;
}
// host minus domain may not contain any dots
if (eff_host.substring(0, eff_host.length()-curr.domain.length()).
indexOf('.') != -1)
{
Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
set_cookie + "\n domain `" +
curr.domain + "' is more than one `.'" +
"away from host `" + eff_host + "'");
continue;
}
// if a port list is given it must include the current port
if (curr.port_set)
{
int idx2=0;
for (idx2=0; idx2<curr.port_list.length; idx2++)
if (curr.port_list[idx2] == req.getConnection().getPort())
break;
if (idx2 == curr.port_list.length)
{
Log.write(Log.COOKI, "Cook2: Bad Set-Cookie2 header: " +
set_cookie + "\n port list " +
"does include current port " +
req.getConnection().getPort());
continue;
}
}
// looks ok
cookie_arr[cidx++] = curr;
}
if (cidx < cookie_arr.length)
cookie_arr = Util.resizeArray(cookie_arr, cidx);
return cookie_arr;
}
/**
* @return the version as an int
*/
public int getVersion()
{
return version;
}
/**
* @return the comment string, or null if none was set
*/
public String getComment()
{
return comment;
}
/**
* @return the comment url
*/
public URI getCommentURL()
{
return comment_url;
}
/**
* @return the array of ports
*/
public int[] getPorts()
{
return port_list;
}
/**
* @return true if the cookie should be discarded at the end of the
* session; false otherwise
*/
public boolean discard()
{
return discard;
}
/**
* @param req the request to be sent
* @return true if this cookie should be sent with the request
*/
protected boolean sendWith(RoRequest req)
{
HTTPConnection con = req.getConnection();
boolean port_match = !port_set;
if (port_set)
for (int idx=0; idx<port_list.length; idx++)
if (port_list[idx] == con.getPort())
{
port_match = true;
break;
}
String eff_host = con.getHost();
if (eff_host.indexOf('.') == -1) eff_host += ".local";
return ((domain.charAt(0) == '.' && eff_host.endsWith(domain) ||
domain.charAt(0) != '.' && eff_host.equals(domain)) &&
port_match &&
Util.getPath(req.getRequestURI()).startsWith(path) &&
(!secure || con.getProtocol().equals("https") ||
con.getProtocol().equals("shttp")));
}
protected String toExternalForm()
{
StringBuffer cookie = new StringBuffer();
if (version == 1)
{
/*
cookie.append("$Version=");
cookie.append(version);
cookie.append("; ");
*/
cookie.append(name);
cookie.append("=");
cookie.append(value);
if (path_set)
{
cookie.append("; ");
cookie.append("$Path=");
cookie.append(path);
}
if (domain_set)
{
cookie.append("; ");
cookie.append("$Domain=");
cookie.append(domain);
}
if (port_set)
{
cookie.append("; ");
cookie.append("$Port");
if (port_list_str != null)
{
cookie.append("=\"");
cookie.append(port_list_str);
cookie.append('\"');
}
}
}
else
throw new Error("Internal Error: unknown version " + version);
return cookie.toString();
}
/**
* Create a string containing all the cookie fields. The format is that
* used in the Set-Cookie header.
*/
public String toString()
{
StringBuffer res = new StringBuffer(name.length() + value.length() + 50);
res.append(name).append('=').append(value);
if (version == 1)
{
res.append("; Version=").append(version);
res.append("; Path=").append(path);
res.append("; Domain=").append(domain);
if (port_set)
{
res.append("; Port=\"").append(port_list[0]);
for (int idx=1; idx<port_list.length; idx++)
res.append(',').append(port_list[idx]);
res.append('\"');
}
if (expires != null)
res.append("; Max-Age=").append(
((expires.getTime() - System.currentTimeMillis()) / 1000L));
if (discard) res.append("; Discard");
if (secure) res.append("; Secure");
if (comment != null) res.append("; Comment=\"").append(comment).append('\"');
if (comment_url != null)
res.append("; CommentURL=\"").append(comment_url).append('\"');
}
else
throw new Error("Internal Error: unknown version " + version);
return res.toString();
}
}