001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.tomcat.util.http;
019:
020: import java.io.Serializable;
021: import java.text.FieldPosition;
022: import java.util.Date;
023:
024: import org.apache.tomcat.util.buf.DateTool;
025: import org.apache.tomcat.util.buf.MessageBytes;
026:
027: /**
028: * Server-side cookie representation.
029: * Allows recycling and uses MessageBytes as low-level
030: * representation ( and thus the byte-> char conversion can be delayed
031: * until we know the charset ).
032: *
033: * Tomcat.core uses this recyclable object to represent cookies,
034: * and the facade will convert it to the external representation.
035: */
036: public class ServerCookie implements Serializable {
037:
038: private static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
039: .getLog(ServerCookie.class);
040:
041: private MessageBytes name = MessageBytes.newInstance();
042: private MessageBytes value = MessageBytes.newInstance();
043:
044: private MessageBytes comment = MessageBytes.newInstance(); // ;Comment=VALUE
045: private MessageBytes domain = MessageBytes.newInstance(); // ;Domain=VALUE
046:
047: private int maxAge = -1; // ;Max-Age=VALUE
048: // ;Discard ... implied by maxAge < 0
049: // RFC2109: maxAge=0 will end a session
050: private MessageBytes path = MessageBytes.newInstance(); // ;Path=VALUE
051: private boolean secure; // ;Secure
052: private int version = 0; // ;Version=1
053:
054: //XXX CommentURL, Port -> use notes ?
055:
056: public ServerCookie() {
057:
058: }
059:
060: public void recycle() {
061: path.recycle();
062: name.recycle();
063: value.recycle();
064: comment.recycle();
065: maxAge = -1;
066: path.recycle();
067: domain.recycle();
068: version = 0;
069: secure = false;
070: }
071:
072: public MessageBytes getComment() {
073: return comment;
074: }
075:
076: public MessageBytes getDomain() {
077: return domain;
078: }
079:
080: public void setMaxAge(int expiry) {
081: maxAge = expiry;
082: }
083:
084: public int getMaxAge() {
085: return maxAge;
086: }
087:
088: public MessageBytes getPath() {
089: return path;
090: }
091:
092: public void setSecure(boolean flag) {
093: secure = flag;
094: }
095:
096: public boolean getSecure() {
097: return secure;
098: }
099:
100: public MessageBytes getName() {
101: return name;
102: }
103:
104: public MessageBytes getValue() {
105: return value;
106: }
107:
108: public int getVersion() {
109: return version;
110: }
111:
112: public void setVersion(int v) {
113: version = v;
114: }
115:
116: // -------------------- utils --------------------
117:
118: public String toString() {
119: return "Cookie " + getName() + "=" + getValue() + " ; "
120: + getVersion() + " " + getPath() + " " + getDomain();
121: }
122:
123: // Note -- disabled for now to allow full Netscape compatibility
124: // from RFC 2068, token special case characters
125: //
126: // private static final String tspecials = "()<>@,;:\\\"/[]?={} \t";
127: private static final String tspecials = ",; ";
128: private static final String tspecials2 = ",; \"";
129:
130: /*
131: * Tests a string and returns true if the string counts as a
132: * reserved token in the Java language.
133: *
134: * @param value the <code>String</code> to be tested
135: *
136: * @return <code>true</code> if the <code>String</code> is a reserved
137: * token; <code>false</code> if it is not
138: */
139: public static boolean isToken(String value) {
140: if (value == null)
141: return true;
142: int len = value.length();
143:
144: for (int i = 0; i < len; i++) {
145: char c = value.charAt(i);
146:
147: if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1)
148: return false;
149: }
150: return true;
151: }
152:
153: public static boolean isToken2(String value) {
154: if (value == null)
155: return true;
156: int len = value.length();
157:
158: for (int i = 0; i < len; i++) {
159: char c = value.charAt(i);
160:
161: if (c < 0x20 || c >= 0x7f || tspecials2.indexOf(c) != -1)
162: return false;
163: }
164: return true;
165: }
166:
167: public static boolean checkName(String name) {
168: if (!isToken(name)
169: || name.equalsIgnoreCase("Comment") // rfc2019
170: || name.equalsIgnoreCase("Discard") // 2019++
171: || name.equalsIgnoreCase("Domain")
172: || name.equalsIgnoreCase("Expires") // (old cookies)
173: || name.equalsIgnoreCase("Max-Age") // rfc2019
174: || name.equalsIgnoreCase("Path")
175: || name.equalsIgnoreCase("Secure")
176: || name.equalsIgnoreCase("Version")) {
177: return false;
178: }
179: return true;
180: }
181:
182: // -------------------- Cookie parsing tools
183:
184: /** Return the header name to set the cookie, based on cookie
185: * version
186: */
187: public String getCookieHeaderName() {
188: return getCookieHeaderName(version);
189: }
190:
191: /** Return the header name to set the cookie, based on cookie
192: * version
193: */
194: public static String getCookieHeaderName(int version) {
195: if (dbg > 0)
196: log((version == 1) ? "Set-Cookie2" : "Set-Cookie");
197: if (version == 1) {
198: // RFC2109
199: return "Set-Cookie";
200: // XXX RFC2965 is not standard yet, and Set-Cookie2
201: // is not supported by Netscape 4, 6, IE 3, 5 .
202: // It is supported by Lynx, and there is hope
203: // return "Set-Cookie2";
204: } else {
205: // Old Netscape
206: return "Set-Cookie";
207: }
208: }
209:
210: private static final String ancientDate = DateTool
211: .formatOldCookie(new Date(10000));
212:
213: public static void appendCookieValue(StringBuffer buf, int version,
214: String name, String value, String path, String domain,
215: String comment, int maxAge, boolean isSecure) {
216: // this part is the same for all cookies
217: buf.append(name);
218: buf.append("=");
219: maybeQuote2(version, buf, value);
220:
221: // XXX Netscape cookie: "; "
222: // add version 1 specific information
223: if (version == 1) {
224: // Version=1 ... required
225: buf.append("; Version=1");
226:
227: // Comment=comment
228: if (comment != null) {
229: buf.append("; Comment=");
230: maybeQuote(version, buf, comment);
231: }
232: }
233:
234: // add domain information, if present
235:
236: if (domain != null) {
237: buf.append("; Domain=");
238: maybeQuote(version, buf, domain);
239: }
240:
241: // Max-Age=secs/Discard ... or use old "Expires" format
242: if (maxAge >= 0) {
243: if (version == 0) {
244: // XXX XXX XXX We need to send both, for
245: // interoperatibility (long word )
246: buf.append("; Expires=");
247: // Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires netscape format )
248: // To expire we need to set the time back in future
249: // ( pfrieden@dChain.com )
250: if (maxAge == 0)
251: buf.append(ancientDate);
252: else
253: DateTool.formatOldCookie(new Date(System
254: .currentTimeMillis()
255: + maxAge * 1000L), buf,
256: new FieldPosition(0));
257:
258: } else {
259: buf.append("; Max-Age=");
260: buf.append(maxAge);
261: }
262: }
263:
264: // Path=path
265: if (path != null) {
266: buf.append("; Path=");
267: maybeQuote(version, buf, path);
268: }
269:
270: // Secure
271: if (isSecure) {
272: buf.append("; Secure");
273: }
274:
275: }
276:
277: public static void maybeQuote(int version, StringBuffer buf,
278: String value) {
279: // special case - a \n or \r shouldn't happen in any case
280: if (isToken(value)) {
281: buf.append(value);
282: } else {
283: buf.append('"');
284: buf.append(escapeDoubleQuotes(value));
285: buf.append('"');
286: }
287: }
288:
289: public static void maybeQuote2(int version, StringBuffer buf,
290: String value) {
291: // special case - a \n or \r shouldn't happen in any case
292: if (isToken2(value)) {
293: buf.append(value);
294: } else {
295: buf.append('"');
296: buf.append(escapeDoubleQuotes(value));
297: buf.append('"');
298: }
299: }
300:
301: // log
302: static final int dbg = 1;
303:
304: public static void log(String s) {
305: if (log.isDebugEnabled())
306: log.debug("ServerCookie: " + s);
307: }
308:
309: /**
310: * Escapes any double quotes in the given string.
311: *
312: * @param s the input string
313: *
314: * @return The (possibly) escaped string
315: */
316: private static String escapeDoubleQuotes(String s) {
317:
318: if (s == null || s.length() == 0 || s.indexOf('"') == -1) {
319: return s;
320: }
321:
322: StringBuffer b = new StringBuffer();
323: char p = s.charAt(0);
324: for (int i = 0; i < s.length(); i++) {
325: char c = s.charAt(i);
326: if (c == '"' && p != '\\')
327: b.append('\\').append('"');
328: else
329: b.append(c);
330: p = c;
331: }
332:
333: return b.toString();
334: }
335:
336: }
|