001: /*
002: * @(#)Cookie2.java 0.3-2 18/06/1999
003: *
004: * This file is part of the HTTPClient package
005: * Copyright (C) 1996-1999 Ronald Tschalär
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free
019: * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
020: * MA 02111-1307, USA
021: *
022: * For questions, suggestions, bug-reports, enhancement-requests etc.
023: * I may be contacted at:
024: *
025: * ronald@innovation.ch
026: *
027: */
028:
029: package HTTPClient;
030:
031: import java.io.File;
032: import java.net.ProtocolException;
033: import java.util.Date;
034: import java.util.Vector;
035: import java.util.Hashtable;
036: import java.util.Enumeration;
037: import java.util.StringTokenizer;
038:
039: /**
040: * This class represents an http cookie as specified in the
041: * <A HREF="ftp://ds.internic.net/internet-drafts/draft-ietf-http-state-man-mec-10.txt">
042: * HTTP State Management Mechanism spec</A> (also known as a version 1 cookie).
043: *
044: * @version 0.3-2 18/06/1999
045: * @author Ronald Tschalär
046: * @since V0.3
047: */
048:
049: public class Cookie2 extends Cookie {
050: protected int version;
051: protected boolean discard;
052: protected String comment;
053: protected URI comment_url;
054: protected int[] port_list;
055: protected String port_list_str;
056:
057: protected boolean path_set;
058: protected boolean port_set;
059: protected boolean domain_set;
060:
061: /**
062: * Create a cookie.
063: *
064: * @param name the cookie name
065: * @param value the cookie value
066: * @param domain the host this cookie will be sent to
067: * @param port_list an array of allowed server ports for this cookie,
068: * or null if the the cookie may be sent to any port
069: * @param path the path prefix for which this cookie will be sent
070: * @param epxires the Date this cookie expires, or null if never
071: * @param discard if true then the cookie will be discarded at the
072: * end of the session regardless of expiry
073: * @param secure if true this cookie will only be over secure connections
074: * @param comment the comment associated with this cookie, or null if none
075: * @param comment_url the comment URL associated with this cookie, or null
076: * if none
077: * @exception NullPointerException if <var>name</var>, <var>value</var>,
078: * <var>domain</var>, or <var>path</var>
079: * is null
080: */
081: public Cookie2(String name, String value, String domain,
082: int[] port_list, String path, Date expires,
083: boolean discard, boolean secure, String comment,
084: URI comment_url) {
085: super (name, value, domain, path, expires, secure);
086:
087: this .discard = discard;
088: this .port_list = port_list;
089: this .comment = comment;
090: this .comment_url = comment_url;
091:
092: path_set = true;
093: domain_set = true;
094:
095: if (port_list != null && port_list.length > 0) {
096: StringBuffer tmp = new StringBuffer();
097: tmp.append(port_list[0]);
098: for (int idx = 1; idx < port_list.length; idx++) {
099: tmp.append(',');
100: tmp.append(port_list[idx]);
101: }
102:
103: port_list_str = tmp.toString();
104: port_set = true;
105: }
106:
107: version = 1;
108: }
109:
110: /**
111: * Use <code>parse()</code> to create cookies.
112: *
113: * @see #parse(java.lang.String, HTTPClient.RoRequest)
114: */
115: protected Cookie2(RoRequest req) {
116: super (req);
117:
118: int slash = path.lastIndexOf('/');
119: if (slash != -1)
120: path = path.substring(0, slash + 1);
121: if (domain.indexOf('.') == -1)
122: domain += ".local";
123:
124: version = -1;
125: discard = false;
126: comment = null;
127: comment_url = null;
128: port_list = null;
129: port_list_str = null;
130:
131: path_set = false;
132: port_set = false;
133: domain_set = false;
134: }
135:
136: /**
137: * Parses the Set-Cookie2 header into an array of Cookies.
138: *
139: * @param set_cookie the Set-Cookie header received from the server
140: * @param req the request used
141: * @return an array of Cookies as parsed from the Set-Cookie header
142: * @exception ProtocolException if an error occurs during parsing
143: */
144: protected static Cookie[] parse(String set_cookie, RoRequest req)
145: throws ProtocolException {
146: Vector cookies;
147: try {
148: cookies = Util.parseHeader(set_cookie);
149: } catch (ParseException pe) {
150: throw new ProtocolException(pe.getMessage());
151: }
152:
153: Cookie cookie_arr[] = new Cookie[cookies.size()];
154: int cidx = 0;
155: for (int idx = 0; idx < cookie_arr.length; idx++) {
156: HttpHeaderElement c_elem = (HttpHeaderElement) cookies
157: .elementAt(idx);
158:
159: // set NAME and VALUE
160:
161: if (c_elem.getValue() == null)
162: throw new ProtocolException("Bad Set-Cookie2 header: "
163: + set_cookie + "\nMissing value "
164: + "for cookie '" + c_elem.getName() + "'");
165: Cookie2 curr = new Cookie2(req);
166: curr.name = c_elem.getName();
167: curr.value = c_elem.getValue();
168:
169: // set all params
170:
171: NVPair[] params = c_elem.getParams();
172: boolean discard_set = false, secure_set = false;
173: for (int idx2 = 0; idx2 < params.length; idx2++) {
174: String name = params[idx2].getName().toLowerCase();
175:
176: // check for required value parts
177: if ((name.equals("version") || name.equals("max-age")
178: || name.equals("domain") || name.equals("path")
179: || name.equals("comment") || name
180: .equals("commenturl"))
181: && params[idx2].getValue() == null) {
182: throw new ProtocolException(
183: "Bad Set-Cookie2 header: " + set_cookie
184: + "\nMissing value " + "for "
185: + params[idx2].getName()
186: + " attribute in cookie '"
187: + c_elem.getName() + "'");
188: }
189:
190: if (name.equals("version")) // Version
191: {
192: if (curr.version != -1)
193: continue;
194: try {
195: curr.version = Integer.parseInt(params[idx2]
196: .getValue());
197: } catch (NumberFormatException nfe) {
198: throw new ProtocolException(
199: "Bad Set-Cookie2 header: " + set_cookie
200: + "\nVersion '"
201: + params[idx2].getValue()
202: + "' not a number");
203: }
204: } else if (name.equals("path")) // Path
205: {
206: if (curr.path_set)
207: continue;
208: curr.path = params[idx2].getValue();
209: curr.path_set = true;
210: } else if (name.equals("domain")) // Domain
211: {
212: if (curr.domain_set)
213: continue;
214: String d = params[idx2].getValue().toLowerCase();
215:
216: // add leading dot if not present and if domain is
217: // not the full host name
218: if (d.charAt(0) != '.' && !d.equals(curr.domain))
219: curr.domain = "." + d;
220: else
221: curr.domain = d;
222: curr.domain_set = true;
223: } else if (name.equals("max-age")) // Max-Age
224: {
225: if (curr.expires != null)
226: continue;
227: int age;
228: try {
229: age = Integer.parseInt(params[idx2].getValue());
230: } catch (NumberFormatException nfe) {
231: throw new ProtocolException(
232: "Bad Set-Cookie2 header: " + set_cookie
233: + "\nMax-Age '"
234: + params[idx2].getValue()
235: + "' not a number");
236: }
237: curr.expires = new Date(System.currentTimeMillis()
238: + age * 1000L);
239: } else if (name.equals("port")) // Port
240: {
241: if (curr.port_set)
242: continue;
243:
244: if (params[idx2].getValue() == null) {
245: curr.port_list = new int[1];
246: curr.port_list[0] = req.getConnection()
247: .getPort();
248: curr.port_set = true;
249: continue;
250: }
251:
252: curr.port_list_str = params[idx2].getValue();
253: StringTokenizer tok = new StringTokenizer(
254: params[idx2].getValue(), ",");
255: curr.port_list = new int[tok.countTokens()];
256: for (int idx3 = 0; idx3 < curr.port_list.length; idx3++) {
257: String port = tok.nextToken().trim();
258: try {
259: curr.port_list[idx3] = Integer
260: .parseInt(port);
261: } catch (NumberFormatException nfe) {
262: throw new ProtocolException(
263: "Bad Set-Cookie2 header: "
264: + set_cookie + "\nPort '"
265: + port + "' not a number");
266: }
267: }
268: curr.port_set = true;
269: } else if (name.equals("discard")) // Domain
270: {
271: if (discard_set)
272: continue;
273: curr.discard = true;
274: discard_set = true;
275: } else if (name.equals("secure")) // Secure
276: {
277: if (secure_set)
278: continue;
279: curr.secure = true;
280: secure_set = true;
281: } else if (name.equals("comment")) // Comment
282: {
283: if (curr.comment != null)
284: continue;
285: curr.comment = params[idx2].getValue();
286: } else if (name.equals("commenturl")) // CommentURL
287: {
288: if (curr.comment_url != null)
289: continue;
290: try {
291: curr.comment_url = new URI(params[idx2]
292: .getValue());
293: } catch (ParseException pe) {
294: throw new ProtocolException(
295: "Bad Set-Cookie2 header: " + set_cookie
296: + "\nCommentURL '"
297: + params[idx2].getValue()
298: + "' not a valid URL");
299: }
300: }
301: // ignore unknown element
302: }
303:
304: // check version
305:
306: if (curr.version == -1)
307: throw new ProtocolException("Bad Set-Cookie2 header: "
308: + set_cookie + "\nMissing Version "
309: + "attribute");
310: if (curr.version != 1)
311: continue; // ignore unknown version
312:
313: // setup defaults
314:
315: if (curr.expires == null)
316: curr.discard = true;
317:
318: // check validity
319:
320: // path attribute must be a prefix of the request-URI
321: if (!Util.getPath(req.getRequestURI())
322: .startsWith(curr.path))
323: continue;
324:
325: // if host name is simple (i.e w/o a domain) then append .local
326: String eff_host = req.getConnection().getHost();
327: if (eff_host.indexOf('.') == -1)
328: eff_host += ".local";
329:
330: // domain must be either .local or must contain at least two dots
331: if (!curr.domain.equals(".local")
332: && curr.domain.indexOf('.', 1) == -1)
333: continue;
334:
335: // domain must domain match host
336: if (!eff_host.endsWith(curr.domain))
337: continue;
338:
339: // host minus domain may not contain any dots
340: if (eff_host.substring(0,
341: eff_host.length() - curr.domain.length()).indexOf(
342: '.') != -1)
343: continue;
344:
345: // if a port list is given it must include the current port
346: if (curr.port_set) {
347: int idx2 = 0;
348: for (idx2 = 0; idx2 < curr.port_list.length; idx2++)
349: if (curr.port_list[idx2] == req.getConnection()
350: .getPort())
351: break;
352: if (idx2 == curr.port_list.length)
353: continue;
354: }
355:
356: // looks ok
357:
358: cookie_arr[cidx++] = curr;
359: }
360:
361: if (cidx < cookie_arr.length)
362: cookie_arr = Util.resizeArray(cookie_arr, cidx);
363:
364: return cookie_arr;
365: }
366:
367: /**
368: * @return the version as an int
369: */
370: public int getVersion() {
371: return version;
372: }
373:
374: /**
375: * @return the comment string, or null if none was set
376: */
377: public String getComment() {
378: return comment;
379: }
380:
381: /**
382: * @return the comment url
383: */
384: public URI getCommentURL() {
385: return comment_url;
386: }
387:
388: /**
389: * @return the array of ports
390: */
391: public int[] getPorts() {
392: return port_list;
393: }
394:
395: /**
396: * @return true if the cookie should be discarded at the end of the
397: * session; false otherwise
398: */
399: public boolean discard() {
400: return discard;
401: }
402:
403: /**
404: * @param req the request to be sent
405: * @return true if this cookie should be sent with the request
406: */
407: protected boolean sendWith(RoRequest req) {
408: HTTPConnection con = req.getConnection();
409:
410: boolean port_match = !port_set;
411: if (port_set)
412: for (int idx = 0; idx < port_list.length; idx++)
413: if (port_list[idx] == con.getPort()) {
414: port_match = true;
415: break;
416: }
417:
418: String eff_host = con.getHost();
419: if (eff_host.indexOf('.') == -1)
420: eff_host += ".local";
421:
422: return ((domain.charAt(0) == '.' && eff_host.endsWith(domain) || domain
423: .charAt(0) != '.'
424: && eff_host.equals(domain))
425: && port_match
426: && Util.getPath(req.getRequestURI()).startsWith(path) && (!secure
427: || con.getProtocol().equals("https") || con
428: .getProtocol().equals("shttp")));
429: }
430:
431: protected String toExternalForm() {
432: StringBuffer cookie = new StringBuffer();
433:
434: if (version == 1) {
435: /*
436: cookie.append("$Version=");
437: cookie.append(version);
438: cookie.append("; ");
439: */
440:
441: cookie.append(name);
442: cookie.append("=");
443: cookie.append(value);
444:
445: if (path_set) {
446: cookie.append("; ");
447: cookie.append("$Path=");
448: cookie.append(path);
449: }
450:
451: if (domain_set) {
452: cookie.append("; ");
453: cookie.append("$Domain=");
454: cookie.append(domain);
455: }
456:
457: if (port_set) {
458: cookie.append("; ");
459: cookie.append("$Port");
460: if (port_list_str != null) {
461: cookie.append("=\"");
462: cookie.append(port_list_str);
463: cookie.append('\"');
464: }
465: }
466: } else
467: throw new Error("Internal Error: unknown version "
468: + version);
469:
470: return cookie.toString();
471: }
472:
473: /**
474: * Create a string containing all the cookie fields. The format is that
475: * used in the Set-Cookie header.
476: */
477: public String toString() {
478: String string = name + "=" + value;
479:
480: if (version == 1) {
481: string += "; Version=" + version;
482: string += "; Path=" + path;
483: string += "; Domain=" + domain;
484: if (port_set) {
485: string += "; Port=\"" + port_list[0];
486: for (int idx = 1; idx < port_list.length; idx++)
487: string += "," + port_list[idx];
488: string += "\"";
489: }
490: if (expires != null)
491: string += "; Max-Age="
492: + ((expires.getTime() - new Date().getTime()) / 1000L);
493: if (discard)
494: string += "; Discard";
495: if (secure)
496: string += "; Secure";
497: if (comment != null)
498: string += "; Comment=\"" + comment + "\"";
499: if (comment_url != null)
500: string += "; CommentURL=\"" + comment_url + "\"";
501: } else
502: throw new Error("Internal Error: unknown version "
503: + version);
504:
505: return string;
506: }
507: }
|