From 141e5e3c8719f1fc12e30a1f06d902580f9a34a2 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Mon, 26 Oct 2009 08:09:15 +0100 Subject: [PATCH] Added library functions for setting and parsing cookies. --- src/dolda/jsvc/util/ClientError.java | 20 ++++++++ src/dolda/jsvc/util/Cookie.java | 98 ++++++++++++++++++++++++++++++++++++ src/dolda/jsvc/util/Http.java | 93 ++++++++++++++++++++++++++++++++++ src/dolda/jsvc/util/Params.java | 8 +-- 4 files changed, 213 insertions(+), 6 deletions(-) create mode 100644 src/dolda/jsvc/util/ClientError.java create mode 100644 src/dolda/jsvc/util/Cookie.java diff --git a/src/dolda/jsvc/util/ClientError.java b/src/dolda/jsvc/util/ClientError.java new file mode 100644 index 0000000..e4713af --- /dev/null +++ b/src/dolda/jsvc/util/ClientError.java @@ -0,0 +1,20 @@ +package dolda.jsvc.util; + +import dolda.jsvc.*; + +public class ClientError extends RequestRestart { + private final String title; + + public ClientError(String title, String msg) { + super(msg); + this.title = title; + } + + public ClientError(String msg) { + this("Invalid request", msg); + } + + public void respond(Request req) { + throw(Restarts.stdresponse(400, title, getMessage())); + } +} diff --git a/src/dolda/jsvc/util/Cookie.java b/src/dolda/jsvc/util/Cookie.java new file mode 100644 index 0000000..8783427 --- /dev/null +++ b/src/dolda/jsvc/util/Cookie.java @@ -0,0 +1,98 @@ +package dolda.jsvc.util; + +import dolda.jsvc.*; +import java.util.*; +import java.text.*; +import java.io.*; + +public class Cookie { + public final static DateFormat datefmt; + static { + datefmt = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.ENGLISH); + datefmt.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); + } + public final String name; + public String value; + public Date expires; + public String domain, path; + public boolean secure; + + public Cookie(String name, String value, Date expires, String domain, String path, boolean secure) { + if(!Http.istoken(name)) + throw(new RuntimeException("Invalid cookie name: `" + name + "'")); + this.name = name; + this.value = value; + this.expires = expires; + this.domain = domain; + this.path = path; + this.secure = secure; + } + + public Cookie(String name) { + this(name, null, null, null, null, false); + } + + public Cookie(String name, String value) { + this(name, value, null, null, null, false); + } + + public String format() { + StringBuilder buf = new StringBuilder(); + buf.append(Http.tokenquote(name)); + buf.append('='); + buf.append(Http.tokenquote(value)); + if(domain != null) + buf.append("; Domain=" + Http.tokenquote(domain)); + if(path != null) + buf.append("; Path=" + Http.tokenquote(path)); + if(expires != null) + buf.append("; Expires=" + Http.tokenquote(datefmt.format(expires))); + if(secure) + buf.append("; Secure"); + return(buf.toString()); + } + + public void addto(Request req) { + req.outheaders().add("Set-Cookie", format()); + } + + public static MultiMap parse(Request req) { + MultiMap ret = new WrappedMultiMap(new TreeMap>()); + for(String in : req.inheaders().values("Cookie")) { + try { + StringReader r = new StringReader(in); + Cookie c = null; + while(true) { + String k = Http.tokenunquote(r); + String v = Http.tokenunquote(r); + if(k == null) + break; + if(k.equals("$Version")) { + if(Integer.parseInt(v) != 1) + throw(new Http.EncodingException("Unknown cookie format version")); + } else if(k.equals("$Path")) { + if(c != null) + c.path = v; + } else if(k.equals("$Domain")) { + if(c != null) + c.domain = v; + } else { + c = new Cookie(k, v); + ret.add(k, c); + } + } + } catch(IOException e) { + throw(new Error(e)); + } + } + return(ret); + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("Cookie("); + buf.append(format()); + buf.append(")"); + return(buf.toString()); + } +} diff --git a/src/dolda/jsvc/util/Http.java b/src/dolda/jsvc/util/Http.java index 000e224..b83e6d2 100644 --- a/src/dolda/jsvc/util/Http.java +++ b/src/dolda/jsvc/util/Http.java @@ -2,14 +2,22 @@ package dolda.jsvc.util; import java.util.*; import java.text.*; +import java.io.*; public class Http { public final static DateFormat datefmt; + public final static String tspecials = "()<>@,;:\\\"/[]?={} "; static { datefmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); datefmt.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); } + public static class EncodingException extends ClientError { + public EncodingException(String msg) { + super("Invalid header encoding", msg); + } + } + public static String fmtdate(Date d) { return(datefmt.format(d)); } @@ -17,4 +25,89 @@ public class Http { public static Date parsedate(String str) throws ParseException { return(datefmt.parse(str)); } + + public static boolean istoken(String str) { + for(int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if(c < 32) + return(false); + if(c >= 127) + return(false); + if(tspecials.indexOf(c) >= 0) + return(false); + } + return(true); + } + + public static String tokenquote(String str) { + if(istoken(str)) + return(str); + StringBuilder buf = new StringBuilder(); + buf.append("\""); + for(int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if(((c < 32) && (c != 9)) || (c >= 127)) + throw(new RuntimeException("Invalid character in HTTP quoted-string: `" + c + "'")); + if((c == '"') || (c == '\\')) { + buf.append('\\'); + buf.append(c); + } else { + buf.append(c); + } + } + buf.append("\""); + return(buf.toString()); + } + + public static String tokenunquote(Reader in) throws IOException { + StringBuilder buf = new StringBuilder(); + String st = "eatws"; + int c = in.read(); + while(true) { + if(st == "eatws") { + if(Character.isWhitespace((char)c)) + c = in.read(); + else + st = "token"; + } else if(st == "token") { + if((c < 0) || Character.isWhitespace((char)c) || (tspecials.indexOf((char)c) >= 0)) { + if(buf.length() == 0) + return(null); + return(buf.toString()); + } else if((c < 32) || (c >= 127)) { + throw(new EncodingException("Invalid characters in header")); + } else if(c == '"') { + st = "quoted"; + c = in.read(); + } else { + buf.append((char)c); + c = in.read(); + } + } else if(st == "quoted") { + if(c < 0) { + throw(new EncodingException("Unterminated quoted-string")); + } else if((c < 32) && !Character.isWhitespace((char)c)) { + throw(new EncodingException("Invalid characters in header")); + } else if(c == '"') { + return(buf.toString()); + } else if(c == '\\') { + st = "q1"; + c = in.read(); + } else { + buf.append((char)c); + c = in.read(); + } + } else if(st == "q1") { + if(c < 0) { + throw(new EncodingException("Unterminated quoted-string")); + } else if(c > 127) { + throw(new EncodingException("Invalid characters in header")); + } else { + buf.append((char)c); + c = in.read(); + st = "quoted"; + } + } + } + } } diff --git a/src/dolda/jsvc/util/Params.java b/src/dolda/jsvc/util/Params.java index aac3639..7a4f27a 100644 --- a/src/dolda/jsvc/util/Params.java +++ b/src/dolda/jsvc/util/Params.java @@ -7,13 +7,9 @@ import java.net.*; import java.nio.charset.CharacterCodingException; public class Params { - public static class EncodingException extends RequestRestart { + public static class EncodingException extends ClientError { public EncodingException(String msg) { - super(msg); - } - - public void respond(Request req) { - throw(Restarts.stdresponse(400, "Invalid parameter encoding", getMessage())); + super("Invalid parameter encoding", msg); } } -- 2.11.0