package org.glassfish.grizzly.http.util;
import java.io.CharConversionException;
import java.io.UnsupportedEncodingException;
import org.glassfish.grizzly.Buffer;
public class URLDecoder {
public static void decode(final DataChunk dataChunk)
throws CharConversionException {
decode(dataChunk, true);
}
public static void decode(final DataChunk dataChunk,
final boolean allowEncodedSlash) throws CharConversionException {
decodeAscii(dataChunk, dataChunk, allowEncodedSlash);
}
public static void decode(final DataChunk srcDataChunk,
final DataChunk dstDataChunk, final boolean allowEncodedSlash)
throws CharConversionException {
decodeAscii(srcDataChunk, dstDataChunk, allowEncodedSlash);
}
public static void decode(final DataChunk srcDataChunk,
final DataChunk dstDataChunk, final boolean allowEncodedSlash,
final String enc)
throws CharConversionException {
switch (srcDataChunk.getType()) {
case Bytes:
{
final ByteChunk srcByteChunk = srcDataChunk.getByteChunk();
final ByteChunk dstByteChunk = dstDataChunk.getByteChunk();
if (dstByteChunk != srcByteChunk) {
dstByteChunk.allocate(srcByteChunk.getLength(), -1);
}
decode(srcByteChunk, dstByteChunk, allowEncodedSlash);
return;
}
case Buffer:
{
final BufferChunk srcBufferChunk = srcDataChunk.getBufferChunk();
if (dstDataChunk != srcDataChunk) {
final ByteChunk dstByteChunk = dstDataChunk.getByteChunk();
dstByteChunk.allocate(srcBufferChunk.getLength(), -1);
decode(srcBufferChunk, dstByteChunk, allowEncodedSlash);
} else {
decode(srcBufferChunk, srcBufferChunk, allowEncodedSlash);
}
return;
}
case String:
{
dstDataChunk.setString(
decode(srcDataChunk.toString(), allowEncodedSlash, enc));
return;
}
case Chars:
{
final CharChunk srcCharChunk = srcDataChunk.getCharChunk();
final CharChunk dstCharChunk = dstDataChunk.getCharChunk();
if (dstDataChunk != srcDataChunk) {
dstCharChunk.ensureCapacity(srcCharChunk.getLength());
decode(srcCharChunk, dstCharChunk, allowEncodedSlash, enc);
} else {
decode(srcCharChunk, srcCharChunk, allowEncodedSlash, enc);
}
dstDataChunk.setChars(dstCharChunk.getChars(),
dstCharChunk.getStart(), dstCharChunk.getEnd());
return;
}
default: throw new NullPointerException();
}
}
public static void decodeAscii(final DataChunk srcDataChunk,
final DataChunk dstDataChunk, final boolean allowEncodedSlash)
throws CharConversionException {
switch (srcDataChunk.getType()) {
case Bytes:
{
final ByteChunk srcByteChunk = srcDataChunk.getByteChunk();
final ByteChunk dstByteChunk = dstDataChunk.getByteChunk();
if (dstByteChunk != srcByteChunk) {
dstByteChunk.allocate(srcByteChunk.getLength(), -1);
}
decode(srcByteChunk, dstByteChunk, allowEncodedSlash);
return;
}
case Buffer:
{
final BufferChunk srcBufferChunk = srcDataChunk.getBufferChunk();
if (dstDataChunk != srcDataChunk) {
final ByteChunk dstByteChunk = dstDataChunk.getByteChunk();
dstByteChunk.allocate(srcBufferChunk.getLength(), -1);
decode(srcBufferChunk, dstByteChunk, allowEncodedSlash);
} else {
decode(srcBufferChunk, srcBufferChunk, allowEncodedSlash);
}
return;
}
case String:
{
dstDataChunk.setString(
decodeAscii(srcDataChunk.toString(), allowEncodedSlash));
return;
}
case Chars:
{
final CharChunk srcCharChunk = srcDataChunk.getCharChunk();
final CharChunk dstCharChunk = dstDataChunk.getCharChunk();
if (dstDataChunk != srcDataChunk) {
dstCharChunk.ensureCapacity(srcCharChunk.getLength());
decodeAscii(srcCharChunk, dstCharChunk, allowEncodedSlash);
} else {
decodeAscii(srcCharChunk, srcCharChunk, allowEncodedSlash);
}
dstDataChunk.setChars(dstCharChunk.getChars(),
dstCharChunk.getStart(), dstCharChunk.getEnd());
return;
}
default: throw new NullPointerException();
}
}
public static void decode(final ByteChunk byteChunk,
final boolean allowEncodedSlash) throws CharConversionException {
decode(byteChunk, byteChunk, allowEncodedSlash);
}
public static void decode(final ByteChunk srcByteChunk,
final ByteChunk dstByteChunk,
final boolean allowEncodedSlash) throws CharConversionException {
final byte[] srcBuffer = srcByteChunk.getBuffer();
final int srcStart = srcByteChunk.getStart();
final int srcEnd = srcByteChunk.getEnd();
final byte[] dstBuffer = dstByteChunk.getBuffer();
int idx = dstByteChunk.getStart();
for (int j = srcStart; j < srcEnd; j++, idx++) {
final byte b = srcBuffer[j];
if (b == '+') {
dstBuffer[idx] = (byte) ' ';
} else if (b != '%') {
dstBuffer[idx] = b;
} else {
if (j + 2 >= srcEnd) {
throw new IllegalStateException("Unexpected termination");
}
byte b1 = srcBuffer[j + 1];
byte b2 = srcBuffer[j + 2];
if (!HexUtils.isHexDigit(b1) || !HexUtils.isHexDigit(b2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ (char) b1 + "" + (char) b2);
}
j += 2;
final int res = x2c(b1, b2);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dstBuffer[idx] = (byte) res;
}
}
dstByteChunk.setEnd(idx);
}
public static void decode(final BufferChunk srcBufferChunk,
final ByteChunk dstByteChunk,
final boolean allowEncodedSlash) throws CharConversionException {
final Buffer srcBuffer = srcBufferChunk.getBuffer();
final int srcStart = srcBufferChunk.getStart();
final int srcEnd = srcBufferChunk.getEnd();
final byte[] dstBuffer = dstByteChunk.getBuffer();
int idx = dstByteChunk.getStart();
for (int j = srcStart; j < srcEnd; j++, idx++) {
final byte b = srcBuffer.get(j);
if (b == '+') {
dstBuffer[idx] = (byte) ' ';
} else if (b != '%') {
dstBuffer[idx] = b;
} else {
if (j + 2 >= srcEnd) {
throw new IllegalStateException("Unexpected termination");
}
byte b1 = srcBuffer.get(j + 1);
byte b2 = srcBuffer.get(j + 2);
if (!HexUtils.isHexDigit(b1) || !HexUtils.isHexDigit(b2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ (char) b1 + "" + (char) b2);
}
j += 2;
final int res = x2c(b1, b2);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dstBuffer[idx] = (byte) res;
}
}
dstByteChunk.setEnd(idx);
}
public static void decode(final ByteChunk srcByteChunk,
final BufferChunk dstBufferChunk,
final boolean allowEncodedSlash) throws CharConversionException {
final byte[] srcBuffer = srcByteChunk.getBuffer();
final int srcStart = srcByteChunk.getStart();
final int srcEnd = srcByteChunk.getEnd();
final Buffer dstBuffer = dstBufferChunk.getBuffer();
int idx = dstBufferChunk.getStart();
for (int j = srcStart; j < srcEnd; j++, idx++) {
final byte b = srcBuffer[j];
if (b == '+') {
dstBuffer.put(idx , (byte) ' ');
} else if (b != '%') {
dstBuffer.put(idx, b);
} else {
if (j + 2 >= srcEnd) {
throw new IllegalStateException("Unexpected termination");
}
byte b1 = srcBuffer[j + 1];
byte b2 = srcBuffer[j + 2];
if (!HexUtils.isHexDigit(b1) || !HexUtils.isHexDigit(b2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ (char) b1 + "" + (char) b2);
}
j += 2;
final int res = x2c(b1, b2);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dstBuffer.put(idx, (byte) res);
}
}
dstBufferChunk.setEnd(idx);
}
public static void decode(final BufferChunk bufferChunk,
final boolean allowEncodedSlash) throws CharConversionException {
decode(bufferChunk, bufferChunk, allowEncodedSlash);
}
public static void decode(final BufferChunk srcBufferChunk,
final BufferChunk dstBufferChunk,
final boolean allowEncodedSlash) throws CharConversionException {
final Buffer srcBuffer = srcBufferChunk.getBuffer();
final int srcStart = srcBufferChunk.getStart();
final int srcEnd = srcBufferChunk.getEnd();
final Buffer dstBuffer = dstBufferChunk.getBuffer();
int idx = dstBufferChunk.getStart();
for (int j = srcStart; j < srcEnd; j++, idx++) {
final byte b = srcBuffer.get(j);
if (b == '+') {
dstBuffer.put(idx , (byte) ' ');
} else if (b != '%') {
dstBuffer.put(idx, b);
} else {
if (j + 2 >= srcEnd) {
throw new IllegalStateException("Unexpected termination");
}
byte b1 = srcBuffer.get(j + 1);
byte b2 = srcBuffer.get(j + 2);
if (!HexUtils.isHexDigit(b1) || !HexUtils.isHexDigit(b2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ (char) b1 + "" + (char) b2);
}
j += 2;
final int res = x2c(b1, b2);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dstBuffer.put(idx, (byte) res);
}
}
dstBufferChunk.setEnd(idx);
}
public static void decode(final CharChunk charChunk,
final boolean allowEncodedSlash) throws CharConversionException {
decodeAscii(charChunk, charChunk, allowEncodedSlash);
}
public static void decode(final CharChunk srcCharChunk,
final CharChunk dstCharChunk,
final boolean allowEncodedSlash) throws CharConversionException {
decodeAscii(srcCharChunk, dstCharChunk, allowEncodedSlash);
}
public static void decode(final CharChunk srcCharChunk,
final CharChunk dstCharChunk,
final boolean allowEncodedSlash,
String enc) throws CharConversionException {
byte[] bytes = null;
final char[] srcBuffer = srcCharChunk.getBuffer();
final int srcStart = srcCharChunk.getStart();
final int srcEnd = srcCharChunk.getEnd();
final int srcLen = srcEnd - srcStart;
final char[] dstBuffer = dstCharChunk.getBuffer();
int idx = dstCharChunk.getStart();
int j = srcStart;
while(j < srcEnd) {
char c = srcBuffer[j];
if (c == '+') {
dstBuffer[idx++] = ' ';
j++;
} else if (c != '%') {
dstBuffer[idx++] = c;
j++;
} else {
try {
if (bytes == null)
bytes = new byte[(srcLen-j)/3];
int pos = 0;
while (((j + 2) < srcLen)
&& (c == '%')) {
final char c1 = srcBuffer[j + 1];
final char c2 = srcBuffer[j + 2];
if (!HexUtils.isHexDigit(c1) || !HexUtils.isHexDigit(c2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ c1 + "" + c2);
}
int v = x2c(c1, c2);
if (v < 0) {
throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - negative value");
}
bytes[pos++] = (byte) v;
j += 3;
if (j < srcLen) {
c = srcBuffer[j];
}
}
if ((j < srcLen) && (c == '%')) {
throw new IllegalArgumentException(
"URLDecoder: Incomplete trailing escape (%) pattern");
}
final String decodedChunk = new String(bytes, 0, pos, enc);
if (!allowEncodedSlash && (decodedChunk.indexOf('/') != -1)) {
throw new CharConversionException("Encoded slashes are not allowed");
}
final int chunkLen = decodedChunk.length();
decodedChunk.getChars(0, chunkLen, dstBuffer, idx);
idx += chunkLen;
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - "
+ e.getMessage());
} catch (UnsupportedEncodingException ignored) {
}
}
}
dstCharChunk.setEnd(idx);
}
public static void decodeAscii(final CharChunk srcCharChunk,
final CharChunk dstCharChunk,
final boolean allowEncodedSlash) throws CharConversionException {
final char[] srcBuffer = srcCharChunk.getBuffer();
final int srcStart = srcCharChunk.getStart();
final int srcEnd = srcCharChunk.getEnd();
final char[] dstBuffer = dstCharChunk.getBuffer();
int idx = dstCharChunk.getStart();
for (int j = srcStart; j < srcEnd; j++, idx++) {
final char c = srcBuffer[j];
if (c == '+') {
dstBuffer[idx] = ' ';
} else if (c != '%') {
dstBuffer[idx] = c;
} else {
if (j + 2 >= srcEnd) {
throw new IllegalStateException("Unexpected termination");
}
final char c1 = srcBuffer[j + 1];
final char c2 = srcBuffer[j + 2];
if (!HexUtils.isHexDigit(c1) || !HexUtils.isHexDigit(c2)) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - %"
+ c1 + "" + c2);
}
j += 2;
final int res = x2c(c1, c2);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dstBuffer[idx] = (char) res;
}
}
dstCharChunk.setEnd(idx);
}
public static String decode(final String str) throws CharConversionException {
return decodeAscii(str, true);
}
public static String decode(final String str, final boolean allowEncodedSlash)
throws CharConversionException {
return decodeAscii(str, allowEncodedSlash);
}
public static String decode(String s, final boolean allowEncodedSlash,
String enc) throws CharConversionException {
boolean needToChange = false;
int numChars = s.length();
StringBuilder sb = new StringBuilder(numChars > 500 ? numChars / 2 : numChars);
int i = 0;
char c;
byte[] bytes = null;
while (i < numChars) {
c = s.charAt(i);
switch (c) {
case '+':
sb.append(' ');
i++;
needToChange = true;
break;
case '%':
try {
if (bytes == null)
bytes = new byte[(numChars-i)/3];
int pos = 0;
while (((i + 2) < numChars)
&& (c == '%')) {
int v = Integer.parseInt(s.substring(i + 1, i + 3), 16);
if (v < 0) {
throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - negative value");
}
bytes[pos++] = (byte) v;
i += 3;
if (i < numChars) {
c = s.charAt(i);
}
}
if ((i < numChars) && (c == '%')) {
throw new IllegalArgumentException(
"URLDecoder: Incomplete trailing escape (%) pattern");
}
final String decodedChunk = new String(bytes, 0, pos, enc);
if (!allowEncodedSlash && (decodedChunk.indexOf('/') != -1)) {
throw new CharConversionException("Encoded slashes are not allowed");
}
sb.append(decodedChunk);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"URLDecoder: Illegal hex characters in escape (%) pattern - "
+ e.getMessage());
} catch (UnsupportedEncodingException ignored) {
}
needToChange = true;
break;
default:
sb.append(c);
i++;
break;
}
}
return (needToChange? sb.toString() : s);
}
public static String decodeAscii(final String str, final boolean allowEncodedSlash)
throws CharConversionException {
if (str == null) {
return null;
}
int mPos = 0;
int strPos = 0;
int strLen = str.length();
StringBuilder dec = null;
while (strPos < strLen) {
final char metaChar = str.charAt(strPos);
final boolean isPlus = (metaChar == '+');
final boolean isNorm = !(isPlus || (metaChar == '%'));
if (isNorm) {
strPos++;
} else {
if (dec == null) {
dec = new StringBuilder(strLen);
}
if (mPos < strPos) {
dec.append(str, mPos, strPos);
}
if (isPlus) {
dec.append(' ');
strPos++;
} else {
final char res = (char) Integer.parseInt(str.substring(strPos + 1, strPos + 3), 16);
if (!allowEncodedSlash && (res == '/')) {
throw new CharConversionException("Encoded slashes are not allowed");
}
dec.append(res);
strPos += 3;
}
mPos = strPos;
}
}
if (dec != null) {
if (mPos < strPos) {
dec.append(str, mPos, strPos);
}
return dec.toString();
}
return str;
}
private static int x2c(byte b1, byte b2) {
return (HexUtils.hexDigit2Dec(b1) << 4) + HexUtils.hexDigit2Dec(b2);
}
private static int x2c(int c1, int c2) {
return (HexUtils.hexDigit2Dec(c1) << 4) + HexUtils.hexDigit2Dec(c2);
}
}