001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.util;
020:
021: import java.io.IOException;
022: import java.io.UnsupportedEncodingException;
023: import java.util.Date;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026:
027: import org.restlet.data.Cookie;
028: import org.restlet.data.CookieSetting;
029: import org.restlet.data.Parameter;
030: import org.restlet.util.DateUtils;
031:
032: import com.noelios.restlet.http.HttpUtils;
033:
034: /**
035: * Cookie header reader.
036: *
037: * @author Jerome Louvel (contact@noelios.com)
038: */
039: public class CookieReader extends HeaderReader {
040: private static final String NAME_VERSION = "$Version";
041:
042: private static final String NAME_PATH = "$Path";
043:
044: private static final String NAME_DOMAIN = "$Domain";
045:
046: private static final String NAME_SET_COMMENT = "comment";
047:
048: private static final String NAME_SET_COMMENT_URL = "commentURL";
049:
050: private static final String NAME_SET_DISCARD = "discard";
051:
052: private static final String NAME_SET_DOMAIN = "domain";
053:
054: private static final String NAME_SET_EXPIRES = "expires";
055:
056: private static final String NAME_SET_MAX_AGE = "max-age";
057:
058: private static final String NAME_SET_PATH = "path";
059:
060: private static final String NAME_SET_PORT = "port";
061:
062: private static final String NAME_SET_SECURE = "secure";
063:
064: private static final String NAME_SET_VERSION = "version";
065:
066: /** The logger to use. */
067: private Logger logger;
068:
069: /** The cached pair. Used by the readPair() method. */
070: private Parameter cachedPair;
071:
072: /** The global cookie specification version. */
073: private int globalVersion;
074:
075: /**
076: * Constructor.
077: *
078: * @param logger
079: * The logger to use.
080: * @param header
081: * The header to read.
082: */
083: public CookieReader(Logger logger, String header) {
084: super (header);
085: this .logger = logger;
086: this .cachedPair = null;
087: this .globalVersion = -1;
088: }
089:
090: /**
091: * Reads the next cookie available or null.
092: *
093: * @return The next cookie available or null.
094: */
095: public Cookie readCookie() throws IOException {
096: Cookie result = null;
097: Parameter pair = readPair();
098:
099: if (this .globalVersion == -1) {
100: // Cookies version not yet detected
101: if (pair.getName().equalsIgnoreCase(NAME_VERSION)) {
102: if (pair.getValue() != null) {
103: this .globalVersion = Integer.parseInt(pair
104: .getValue());
105: } else {
106: throw new IOException(
107: "Empty cookies version attribute detected. Please check your cookie header");
108: }
109: } else {
110: // Set the default version for old Netscape cookies
111: this .globalVersion = 0;
112: }
113: }
114:
115: while ((pair != null) && (pair.getName().charAt(0) == '$')) {
116: // Unexpected special attribute
117: // Silently ignore it as it may have been introduced by new
118: // specifications
119: pair = readPair();
120: }
121:
122: if (pair != null) {
123: // Set the cookie name and value
124: result = new Cookie(this .globalVersion, pair.getName(),
125: pair.getValue());
126: pair = readPair();
127: }
128:
129: while ((pair != null) && (pair.getName().charAt(0) == '$')) {
130: if (pair.getName().equalsIgnoreCase(NAME_PATH)) {
131: result.setPath(pair.getValue());
132: } else if (pair.getName().equalsIgnoreCase(NAME_DOMAIN)) {
133: result.setDomain(pair.getValue());
134: } else {
135: // Unexpected special attribute
136: // Silently ignore it as it may have been introduced by new
137: // specifications
138: }
139:
140: pair = readPair();
141: }
142:
143: if (pair != null) {
144: // We started to read the next cookie
145: // So let's put it back into the stream
146: this .cachedPair = pair;
147: }
148:
149: return result;
150: }
151:
152: /**
153: * Reads the next cookie setting available or null.
154: *
155: * @return The next cookie setting available or null.
156: */
157: public CookieSetting readCookieSetting() throws IOException {
158: CookieSetting result = null;
159: Parameter pair = readPair();
160:
161: while ((pair != null) && (pair.getName().charAt(0) == '$')) {
162: // Unexpected special attribute
163: // Silently ignore it as it may have been introduced by new
164: // specifications
165: pair = readPair();
166: }
167:
168: if (pair != null) {
169: // Set the cookie name and value
170: result = new CookieSetting(pair.getName(), pair.getValue());
171: pair = readPair();
172: }
173:
174: while (pair != null) {
175: if (pair.getName().equalsIgnoreCase(NAME_SET_PATH)) {
176: result.setPath(pair.getValue());
177: } else if (pair.getName().equalsIgnoreCase(NAME_SET_DOMAIN)) {
178: result.setDomain(pair.getValue());
179: } else if (pair.getName()
180: .equalsIgnoreCase(NAME_SET_COMMENT)) {
181: result.setComment(pair.getValue());
182: } else if (pair.getName().equalsIgnoreCase(
183: NAME_SET_COMMENT_URL)) {
184: // No yet supported
185: } else if (pair.getName()
186: .equalsIgnoreCase(NAME_SET_DISCARD)) {
187: result.setMaxAge(-1);
188: } else if (pair.getName()
189: .equalsIgnoreCase(NAME_SET_EXPIRES)) {
190: Date current = new Date(System.currentTimeMillis());
191: Date expires = DateUtils.parse(pair.getValue(),
192: DateUtils.FORMAT_RFC_1036);
193:
194: if (expires == null) {
195: expires = DateUtils.parse(pair.getValue(),
196: DateUtils.FORMAT_RFC_1123);
197: }
198:
199: if (expires == null) {
200: expires = DateUtils.parse(pair.getValue(),
201: DateUtils.FORMAT_ASC_TIME);
202: }
203:
204: if (expires != null) {
205: if (DateUtils.after(current, expires)) {
206: result
207: .setMaxAge((int) ((expires.getTime() - current
208: .getTime()) / 1000));
209: } else {
210: result.setMaxAge(0);
211: }
212: } else {
213: // Ignore the expires header
214: this .logger.log(Level.WARNING,
215: "Ignoring cookie setting expiration date. Unable to parse the date: "
216: + pair.getValue());
217: }
218: } else if (pair.getName()
219: .equalsIgnoreCase(NAME_SET_MAX_AGE)) {
220: result.setMaxAge(Integer.valueOf(pair.getValue()));
221: } else if (pair.getName().equalsIgnoreCase(NAME_SET_PORT)) {
222: // No yet supported
223: } else if (pair.getName().equalsIgnoreCase(NAME_SET_SECURE)) {
224: if ((pair.getValue() == null)
225: || (pair.getValue().length() == 0)) {
226: result.setSecure(true);
227: }
228: } else if (pair.getName()
229: .equalsIgnoreCase(NAME_SET_VERSION)) {
230: result.setVersion(Integer.valueOf(pair.getValue()));
231: } else {
232: // Unexpected special attribute
233: // Silently ignore it as it may have been introduced by new
234: // specifications
235: }
236:
237: pair = readPair();
238: }
239:
240: return result;
241: }
242:
243: /**
244: * Reads the next pair as a parameter.
245: *
246: * @return The next pair as a parameter.
247: * @throws IOException
248: */
249: private Parameter readPair() throws IOException {
250: Parameter result = null;
251:
252: if (cachedPair != null) {
253: result = cachedPair;
254: cachedPair = null;
255: } else {
256: try {
257: boolean readingName = true;
258: boolean readingValue = false;
259: StringBuilder nameBuffer = new StringBuilder();
260: StringBuilder valueBuffer = new StringBuilder();
261:
262: int nextChar = 0;
263: while ((result == null) && (nextChar != -1)) {
264: nextChar = read();
265:
266: if (readingName) {
267: if ((HttpUtils.isSpace(nextChar))
268: && (nameBuffer.length() == 0)) {
269: // Skip spaces
270: } else if ((nextChar == -1)
271: || (nextChar == ';')
272: || (nextChar == ',')) {
273: if (nameBuffer.length() > 0) {
274: // End of pair with no value
275: result = HttpUtils.createParameter(
276: nameBuffer, null);
277: } else if (nextChar == -1) {
278: // Do nothing return null preference
279: } else {
280: throw new IOException(
281: "Empty cookie name detected. Please check your cookies");
282: }
283: } else if (nextChar == '=') {
284: readingName = false;
285: readingValue = true;
286: } else if (HttpUtils.isTokenChar(nextChar)
287: || (this .globalVersion < 1)) {
288: nameBuffer.append((char) nextChar);
289: } else {
290: throw new IOException(
291: "Separator and control characters are not allowed within a token. Please check your cookie header");
292: }
293: } else if (readingValue) {
294: if ((HttpUtils.isSpace(nextChar))
295: && (valueBuffer.length() == 0)) {
296: // Skip spaces
297: } else if ((nextChar == -1)
298: || (nextChar == ';')) {
299: // End of pair
300: result = HttpUtils.createParameter(
301: nameBuffer, valueBuffer);
302: } else if ((nextChar == '"')
303: && (valueBuffer.length() == 0)) {
304: valueBuffer.append(readQuotedString());
305: } else if (HttpUtils.isTokenChar(nextChar)
306: || (this .globalVersion < 1)) {
307: valueBuffer.append((char) nextChar);
308: } else {
309: throw new IOException(
310: "Separator and control characters are not allowed within a token. Please check your cookie header");
311: }
312: }
313: }
314: } catch (UnsupportedEncodingException uee) {
315: throw new IOException(
316: "Unsupported encoding. Please contact the administrator");
317: }
318: }
319:
320: return result;
321: }
322:
323: }
|