com.fireflysource.net.websocket.common.utils.QuoteUtil Maven / Gradle / Ivy
package com.fireflysource.net.websocket.common.utils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Provide some consistent Http header value and Extension configuration parameter quoting support.
*
* - ABNF defined extension parameter parsing requirements of RFC-6455 (WebSocket) ABNF, is slightly different than the ABNF parsing defined in RFC-2616
* (HTTP/1.1).
* - Future HTTPbis ABNF changes for parsing will impact QuotedStringTokenizer
*
* It was decided to keep this implementation separate for the above reasons.
*/
public class QuoteUtil {
private static class DeQuotingStringIterator implements Iterator {
private enum State {
START,
TOKEN,
QUOTE_SINGLE,
QUOTE_DOUBLE
}
private final String input;
private final String delims;
private StringBuilder token;
private boolean hasToken = false;
private int i = 0;
public DeQuotingStringIterator(String input, String delims) {
this.input = input;
this.delims = delims;
int len = input.length();
token = new StringBuilder(len > 1024 ? 512 : len / 2);
}
private void appendToken(char c) {
if (hasToken) {
token.append(c);
} else {
if (Character.isWhitespace(c)) {
return; // skip whitespace at start of token.
} else {
token.append(c);
hasToken = true;
}
}
}
@Override
public boolean hasNext() {
// already found a token
if (hasToken) {
return true;
}
State state = State.START;
boolean escape = false;
int inputLen = input.length();
while (i < inputLen) {
char c = input.charAt(i++);
switch (state) {
case START: {
if (c == '\'') {
state = State.QUOTE_SINGLE;
appendToken(c);
} else if (c == '\"') {
state = State.QUOTE_DOUBLE;
appendToken(c);
} else {
appendToken(c);
state = State.TOKEN;
}
break;
}
case TOKEN: {
if (delims.indexOf(c) >= 0) {
// System.out.printf("hasNext/t: %b [%s]%n",hasToken,token);
return hasToken;
} else if (c == '\'') {
state = State.QUOTE_SINGLE;
} else if (c == '\"') {
state = State.QUOTE_DOUBLE;
}
appendToken(c);
break;
}
case QUOTE_SINGLE: {
if (escape) {
escape = false;
appendToken(c);
} else if (c == '\'') {
appendToken(c);
state = State.TOKEN;
} else if (c == '\\') {
escape = true;
} else {
appendToken(c);
}
break;
}
case QUOTE_DOUBLE: {
if (escape) {
escape = false;
appendToken(c);
} else if (c == '\"') {
appendToken(c);
state = State.TOKEN;
} else if (c == '\\') {
escape = true;
} else {
appendToken(c);
}
break;
}
}
// System.out.printf("%s <%s> : [%s]%n",state,c,token);
}
// System.out.printf("hasNext/e: %b [%s]%n",hasToken,token);
return hasToken;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String ret = token.toString();
token.setLength(0);
hasToken = false;
return QuoteUtil.dequote(ret.trim());
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported with this iterator");
}
}
/**
* ABNF from RFC 2616, RFC 822, and RFC 6455 specified characters requiring quoting.
*/
public static final String ABNF_REQUIRED_QUOTING = "\"'\\\n\r\t\f\b%+ ;=";
private static final char UNICODE_TAG = 0xFFFF;
private static final char[] escapes = new char[32];
static {
Arrays.fill(escapes, UNICODE_TAG);
// non-unicode
escapes['\b'] = 'b';
escapes['\t'] = 't';
escapes['\n'] = 'n';
escapes['\f'] = 'f';
escapes['\r'] = 'r';
}
private static int dehex(byte b) {
if ((b >= '0') && (b <= '9')) {
return (byte) (b - '0');
}
if ((b >= 'a') && (b <= 'f')) {
return (byte) ((b - 'a') + 10);
}
if ((b >= 'A') && (b <= 'F')) {
return (byte) ((b - 'A') + 10);
}
throw new IllegalArgumentException("!hex:" + Integer.toHexString(0xff & b));
}
/**
* Remove quotes from a string, only if the input string start with and end with the same quote character.
*
* @param str the string to remove surrounding quotes from
* @return the de-quoted string
*/
public static String dequote(String str) {
char start = str.charAt(0);
if ((start == '\'') || (start == '\"')) {
// possibly quoted
char end = str.charAt(str.length() - 1);
if (start == end) {
// dequote
return str.substring(1, str.length() - 1);
}
}
return str;
}
public static void escape(StringBuilder buf, String str) {
for (char c : str.toCharArray()) {
if (c >= 32) {
// non special character
if ((c == '"') || (c == '\\')) {
buf.append('\\');
}
buf.append(c);
} else {
// special characters, requiring escaping
char escaped = escapes[c];
// is this a unicode escape?
if (escaped == UNICODE_TAG) {
buf.append("\\u00");
if (c < 0x10) {
buf.append('0');
}
buf.append(Integer.toString(c, 16)); // hex
} else {
// normal escape
buf.append('\\').append(escaped);
}
}
}
}
/**
* Simple quote of a string, escaping where needed.
*
* @param buf the StringBuilder to append to
* @param str the string to quote
*/
public static void quote(StringBuilder buf, String str) {
buf.append('"');
escape(buf, str);
buf.append('"');
}
/**
* Append into buf the provided string, adding quotes if needed.
*
* Quoting is determined if any of the characters in the delim
are found in the input str
.
*
* @param buf the buffer to append to
* @param str the string to possibly quote
* @param delim the delimiter characters that will trigger automatic quoting
*/
public static void quoteIfNeeded(StringBuilder buf, String str, String delim) {
if (str == null) {
return;
}
// check for delimiters in input string
int len = str.length();
if (len == 0) {
return;
}
int ch;
for (int i = 0; i < len; i++) {
ch = str.codePointAt(i);
if (delim.indexOf(ch) >= 0) {
// found a delimiter codepoint. we need to quote it.
quote(buf, str);
return;
}
}
// no special delimiters used, no quote needed.
buf.append(str);
}
/**
* Create an iterator of the input string, breaking apart the string at the provided delimiters, removing quotes and triming the parts of the string as
* needed.
*
* @param str the input string to split apart
* @param delims the delimiter characters to split the string on
* @return the iterator of the parts of the string, trimmed, with quotes around the string part removed, and unescaped
*/
public static Iterator splitAt(String str, String delims) {
return new DeQuotingStringIterator(str.trim(), delims);
}
public static String unescape(String str) {
if (str == null) {
// nothing there
return null;
}
int len = str.length();
if (len <= 1) {
// impossible to be escaped
return str;
}
StringBuilder ret = new StringBuilder(len - 2);
boolean escaped = false;
char c;
for (int i = 0; i < len; i++) {
c = str.charAt(i);
if (escaped) {
escaped = false;
switch (c) {
case 'n':
ret.append('\n');
break;
case 'r':
ret.append('\r');
break;
case 't':
ret.append('\t');
break;
case 'f':
ret.append('\f');
break;
case 'b':
ret.append('\b');
break;
case '\\':
ret.append('\\');
break;
case '/':
ret.append('/');
break;
case '"':
ret.append('"');
break;
case 'u':
ret.append((char) ((dehex((byte) str.charAt(i++)) << 24) + (dehex((byte) str.charAt(i++)) << 16) + (dehex((byte) str.charAt(i++)) << 8) + (dehex((byte) str
.charAt(i++)))));
break;
default:
ret.append(c);
}
} else if (c == '\\') {
escaped = true;
} else {
ret.append(c);
}
}
return ret.toString();
}
public static String join(Object[] objs, String delim) {
if (objs == null) {
return "";
}
StringBuilder ret = new StringBuilder();
int len = objs.length;
for (int i = 0; i < len; i++) {
if (i > 0) {
ret.append(delim);
}
if (objs[i] instanceof String) {
ret.append('"').append(objs[i]).append('"');
} else {
ret.append(objs[i]);
}
}
return ret.toString();
}
public static String join(Collection> objs, String delim) {
if (objs == null) {
return "";
}
StringBuilder ret = new StringBuilder();
boolean needDelim = false;
for (Object obj : objs) {
if (needDelim) {
ret.append(delim);
}
if (obj instanceof String) {
ret.append('"').append(obj).append('"');
} else {
ret.append(obj);
}
needDelim = true;
}
return ret.toString();
}
}