package org.dynjs.runtime.builtins; import org.dynjs.exception.ThrowException; import org.dynjs.runtime.ExecutionContext; public class URLCodec { public static String URI_RESERVED_SET = ";/?:@&=+$,"; public static String URI_ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static String DECIMAL_DIGIT = "0123456789"; public static String URI_MARK = "-_.!~*'()"; public static String URI_UNESCAPED_SET = URI_ALPHA + DECIMAL_DIGIT + URI_MARK; public static String encode(ExecutionContext context, String str, String unescapedSet) { int len = str.length(); StringBuilder r = new StringBuilder(); int k = 0; while (true) { if (k == len) { return r.toString(); } char c = str.charAt(k); if (unescapedSet.contains("" + c)) { r.append(c); } else { if ((!(c < 0xDC00)) && (!(c > 0xDFFF))) { throw new ThrowException(context, context.createUriError("invalid escape")); } long v = 0; if (c < 0xD800 || c > 0xDBFF) { v = c; } else { ++k; if (k == len) { throw new ThrowException(context, context.createUriError("invalid escape")); } char kChar = str.charAt(k); if (kChar < 0xDC00 || kChar > 0xDFFF) { throw new ThrowException(context, context.createUriError("invalid escape")); } v = ((c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000); } if (v < 0x80) { r.append(String.format("%%%02X", v)); } else if (v < 0x800) { int o1 = (int) ((v >> 6) | 0xC0); int o2 = (int) ((v & 0x3F) | 0x80); r.append(String.format("%%%02X%%%02X", o1, o2)); } else if (v <= 0xFFFF) { int o1 = (int) (((v >> 12) & 0x1F) | 0xE0); int o2 = (int) (((v >> 6) & 0x3F) | 0x80); int o3 = (int) ((v & 0x3F) | 0x80); r.append(String.format("%%%02X%%%02X%%%02X", o1, o2, o3)); } else if (v <= 0x10FFFF) { int o1 = (int) (((v >> 18) & 0x07) | 0xF0); int o2 = (int) (((v >> 12) & 0x3F) | 0x80); int o3 = (int) (((v >> 6) & 0x3F) | 0x80); int o4 = (int) ((v & 0x3F) | 0x80); r.append(String.format("%%%02X%%%02X%%%02X%%%02X", o1, o2, o3, o4)); } } ++k; } } public static String decode(ExecutionContext context, String str, String reservedSet) { int len = str.length(); StringBuilder r = new StringBuilder(); int k = 0; while (true) { String s = null; if (k == len) { return r.toString(); } char c = str.charAt(k); if (c != '%') { s = "" + c; } else { int start = k; if ((k + 2) >= len) { throw new ThrowException(context, context.createUriError("invalid escape (not enough chars follow %)")); } if (!isHexDigit(str.charAt(k + 1)) || !isHexDigit(str.charAt(k + 2))) { throw new ThrowException(context, context.createUriError("invalid escape (non-hex follow %)")); } int b = Integer.parseInt(str.substring(k + 1, k + 3), 16); k = k + 2; if ((b & 0x80) == 0) { String charStr = new String(new char[] { (char) b }); if (!reservedSet.contains(charStr)) { s = charStr; } else { s = str.substring(start, k + 1); } } else { int n = 1; for (int nPos = 1; nPos < 8; ++nPos) { if (((b << nPos) & 0x80) == 0) { n = nPos; break; } } if (n == 1 || n > 4) { throw new ThrowException(context, context.createUriError("invalid escape (too many hex sequences)")); } int[] octets = new int[n]; octets[0] = b; if ((k + (3 * (n - 1))) >= len) { throw new ThrowException(context, context.createUriError("invalid escape (too many hex sequences)")); } for (int j = 1; j < n; ++j) { ++k; if (str.charAt(k) != '%') { throw new ThrowException(context, context.createUriError("invalid escape (multiple hex sequences expected)")); } if (!isHexDigit(str.charAt(k + 1)) || !isHexDigit(str.charAt(k + 2))) { throw new ThrowException(context, context.createUriError("invalid escape (following sequences do not contain hex sequences)")); } b = Integer.parseInt(str.substring(k + 1, k + 3), 16); if ((b & 0x80) == 0) { throw new ThrowException(context, context.createUriError("invalid escape (first significant bit must be 1)")); } if ((b & 0x40) != 0) { throw new ThrowException(context, context.createUriError("invalid escape (second significant bit must be 0)")); } k = k + 2; octets[j] = b; } int v = 0; switch (octets.length) { case 1: v = octets[0] & 0x7F; break; case 2: v = octets[0] & 0x3F; break; case 3: v = octets[0] & 0x1F; break; case 4: v = octets[0] & 0x0F; break; } for (int i = 1; i < octets.length; ++i) { v = v << 6; v = v | (octets[i] & 0x3F); } if (!Character.isValidCodePoint(v)) { throw new ThrowException(context, context.createUriError("invalid code-point: " + v)); } if (v < 0x10000) { String charStr = new String(new char[] { (char) v }); if (!reservedSet.contains(charStr)) { s = charStr; } else { s = str.substring(start, k + 1); } } else { char l = (char) (((v - 0x10000) & 0x3FF) + 0xDC00); char h = (char) ((((v - 0x10000) >> 10) & 0x3FF) + 0xD800); s = new String(new char[] { h, l }); } } } r.append(s); ++k; } } protected static boolean isHexDigit(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } }