Added library functions for setting and parsing cookies.
authorFredrik Tolf <fredrik@dolda2000.com>
Mon, 26 Oct 2009 07:09:15 +0000 (08:09 +0100)
committerFredrik Tolf <fredrik@dolda2000.com>
Mon, 26 Oct 2009 07:09:15 +0000 (08:09 +0100)
src/dolda/jsvc/util/ClientError.java [new file with mode: 0644]
src/dolda/jsvc/util/Cookie.java [new file with mode: 0644]
src/dolda/jsvc/util/Http.java
src/dolda/jsvc/util/Params.java

diff --git a/src/dolda/jsvc/util/ClientError.java b/src/dolda/jsvc/util/ClientError.java
new file mode 100644 (file)
index 0000000..e4713af
--- /dev/null
@@ -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 (file)
index 0000000..8783427
--- /dev/null
@@ -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<String, Cookie> parse(Request req) {
+       MultiMap<String, Cookie> ret = new WrappedMultiMap<String, Cookie>(new TreeMap<String, Collection<Cookie>>());
+       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());
+    }
+}
index 000e224..b83e6d2 100644 (file)
@@ -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";
+               }
+           }
+       }
+    }
 }
index aac3639..7a4f27a 100644 (file)
@@ -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);
        }
     }