--- /dev/null
+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()));
+ }
+}
--- /dev/null
+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());
+ }
+}
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));
}
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";
+ }
+ }
+ }
+ }
}
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);
}
}