From efa9722bc37910a6224346bb20210205c96ecc47 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Wed, 14 Oct 2009 02:22:03 +0200 Subject: [PATCH] Added URL parameter {en,de}coding. --- src/dolda/jsvc/util/Misc.java | 51 +++++++++++++++++ src/dolda/jsvc/util/MixedBuffer.java | 46 ++++++++++++++++ src/dolda/jsvc/util/Params.java | 104 +++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 src/dolda/jsvc/util/MixedBuffer.java create mode 100644 src/dolda/jsvc/util/Params.java diff --git a/src/dolda/jsvc/util/Misc.java b/src/dolda/jsvc/util/Misc.java index e92b324..a86735a 100644 --- a/src/dolda/jsvc/util/Misc.java +++ b/src/dolda/jsvc/util/Misc.java @@ -5,6 +5,7 @@ import java.util.*; import java.io.*; public class Misc { + public static final java.nio.charset.Charset utf8 = java.nio.charset.Charset.forName("UTF-8"); private static Map stext = new HashMap(); static { @@ -52,4 +53,54 @@ public class Misc { ret = new ErrorHandler(ret); return(ret); } + + public static int hex2int(char digit) { + if((digit >= '0') && (digit <= '9')) + return(digit - '0'); + if((digit >= 'a') && (digit <= 'f')) + return(digit - 'a' + 10); + if((digit >= 'A') && (digit <= 'F')) + return(digit - 'A' + 10); + throw(new NumberFormatException("Invalid hex digit " + digit)); + } + + public static char int2hex(int nibble, boolean upper) { + if((nibble >= 0) && (nibble <= 9)) + return((char)('0' + nibble)); + if((nibble >= 10) && (nibble <= 15)) + return((char)((upper?'A':'a') + nibble - 10)); + throw(new NumberFormatException("Invalid hex nibble " + nibble)); + } + + public static String htmlq(String in) { + StringBuilder buf = new StringBuilder(); + for(int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + if(c == '&') + buf.append("&"); + else if(c == '<') + buf.append("<"); + else if(c == '>') + buf.append(">"); + else + buf.append(c); + } + return(buf.toString()); + } + + public static String urlq(String in) { + byte[] bytes = in.getBytes(utf8); + StringBuilder buf = new StringBuilder(); + for(int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + if((b < 32) || (b == ' ') || (b == '&') || (b == '?') || (b == '/') || (b == '=') || (b == '#') || (b == '%') || (b == '+') || (b >= 128)) { + buf.append('%'); + buf.append(int2hex((b & 0xf0) >> 4, true)); + buf.append(int2hex(b & 0x0f, true)); + } else { + buf.append((char)b); + } + } + return(buf.toString()); + } } diff --git a/src/dolda/jsvc/util/MixedBuffer.java b/src/dolda/jsvc/util/MixedBuffer.java new file mode 100644 index 0000000..f78b035 --- /dev/null +++ b/src/dolda/jsvc/util/MixedBuffer.java @@ -0,0 +1,46 @@ +package dolda.jsvc.util; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; + +public class MixedBuffer { + private ByteArrayOutputStream buf = new ByteArrayOutputStream(); + private Writer conv; + private Charset cs; + + public MixedBuffer(Charset cs) { + this.cs = cs; + conv = new OutputStreamWriter(buf, cs); + } + + public MixedBuffer() { + this(Misc.utf8); + } + + public void append(byte b) { + buf.write(b); + } + + public void append(char c) { + try { + conv.write(c); + conv.flush(); + } catch(IOException e) { + throw(new Error(e)); + } + } + + public String convert() throws java.nio.charset.CharacterCodingException { + CharsetDecoder dec = cs.newDecoder(); + ByteBuffer in = ByteBuffer.wrap(buf.toByteArray()); + CharBuffer out = dec.decode(in); + return(out.toString()); + } + + public int size() { + return(buf.size()); + } +} diff --git a/src/dolda/jsvc/util/Params.java b/src/dolda/jsvc/util/Params.java new file mode 100644 index 0000000..e2cff46 --- /dev/null +++ b/src/dolda/jsvc/util/Params.java @@ -0,0 +1,104 @@ +package dolda.jsvc.util; + +import dolda.jsvc.*; +import java.util.*; +import java.io.*; +import java.net.*; +import java.nio.charset.CharacterCodingException; + +public class Params { + public static class EncodingException extends RequestRestart { + public EncodingException(String msg) { + super(msg); + } + + public void respond(Request req) { + throw(Restarts.stdresponse(400, "Invalid parameter encoding", getMessage())); + } + } + + public static MultiMap urlparams(String q) { + try { + MultiMap ret = new WrappedMultiMap(new TreeMap>()); + String st = "key"; + String key = null; /* Java is stupid. */ + MixedBuffer buf = new MixedBuffer(); + int i = 0; + while(true) { + int c = (i >= q.length())?-1:(q.charAt(i++)); + if(st == "key") { + if(c == '%') { + if(q.length() - i < 2) + throw(new EncodingException("Invalid character escape")); + try { + buf.append((byte)((Misc.hex2int(q.charAt(i)) << 4) | Misc.hex2int(q.charAt(i + 1)))); + } catch(NumberFormatException e) { + throw(new EncodingException("Invalid character escape")); + } + i += 2; + } else if(c == '=') { + key = buf.convert(); + buf = new MixedBuffer(); + st = "val"; + } else if(c == '&') { + ret.add(buf.convert(), ""); + buf = new MixedBuffer(); + } else if(c == -1) { + if(buf.size() == 0) { + break; + } else { + ret.add(buf.convert(), ""); + buf = new MixedBuffer(); + } + } else { + buf.append((char)c); + } + } else if(st == "val") { + if(c == '%') { + if(q.length() - i < 2) + throw(new EncodingException("Invalid character escape")); + try { + buf.append((byte)((Misc.hex2int(q.charAt(i)) << 4) | Misc.hex2int(q.charAt(i + 1)))); + } catch(NumberFormatException e) { + throw(new EncodingException("Invalid character escape")); + } + i += 2; + } else if((c == '&') || (c == -1)) { + ret.add(key, buf.convert()); + buf = new MixedBuffer(); + st = "key"; + } else if(c == '+') { + buf.append(' '); + } else { + buf.append((char)c); + } + } + } + return(ret); + } catch(CharacterCodingException e) { + throw(new EncodingException("Escaped parameter text is not proper UTF-8")); + } + } + + public static MultiMap urlparams(URL url) { + return(urlparams(url.getQuery())); + } + + public static MultiMap urlparams(Request req) { + return(urlparams(req.url())); + } + + public static String encquery(Map pars) { + StringBuilder buf = new StringBuilder(); + boolean f = true; + for(Map.Entry par : pars.entrySet()) { + if(!f) + buf.append('&'); + buf.append(Misc.urlq(par.getKey())); + buf.append('='); + buf.append(Misc.urlq(par.getValue())); + f = false; + } + return(buf.toString()); + } +} -- 2.11.0