package org.etk.core.rest.impl.header;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
public class HeaderParameterParser {
/**
* Parameter separator.
*/
private static final char SEPARATOR = ';';
/**
* Current position in the parsed string.
*/
private int pos = 0;
/**
* Token's start.
*/
private int i1 = 0;
/**
* Token's end.
*/
private int i2 = 0;
/**
* String to be parsed.
*/
private char[] chars = null;
/**
* Parsed string length.
*/
private int length = 0;
/**
* Parse header string for parameters.
*
* @param header source header string
* @return header parameter
* @throws ParseException if string can't be parsed or contains illegal
* characters
*/
public Map<String, String> parse(String header) throws ParseException {
init(header);
if (pos < 0)
return null;
pos++; // skip first ';'
Map<String, String> m = null;
while (hasChars()) {
String name = readToken(new char[] {'=', SEPARATOR});
String value = null;
if (hasChars() && chars[pos] == '=') {
pos++; // skip '='
if (chars[pos] == '"') // quoted string
value = readQuotedString();
else
value = readToken(new char[] {SEPARATOR});
}
if (hasChars() && chars[pos] == SEPARATOR)
pos++; // skip ';'
if (name != null && name.length() > 0) {
if (m == null)
m = new HashMap<String, String>();
m.put(name, value);
}
}
return m;
}
/**
* @param removeQuotes must leading and trailing quotes be skipped
* @return parsed token
*/
private String getToken(boolean removeQuotes) {
// leading whitespace
while ((i1 < i2) && Character.isWhitespace(chars[i1]))
i1++;
// tail whitespace
while ((i2 > i1) && Character.isWhitespace(chars[i2 - 1]))
i2--;
// remove quotes
if (removeQuotes && chars[i1] == '"' && chars[i2 - 1] == '"') {
i1++;
i2--;
}
String token = null;
if (i2 > i1)
token = new String(chars, i1, i2 - i1);
return token;
}
/**
* Check are there any character to be parsed.
*
* @return true if there are unparsed characters, false otherwise
*/
private boolean hasChars() {
return pos < length;
}
/**
* Initialize character array for parsing.
*
* @param source source string for parsing
*/
private void init(String source) {
// looking for start parameters position
// e.g. text/plain ; charsert=utf-8
pos = source.indexOf(SEPARATOR);
if (pos < 0)
// header string does not contains parameters
return;
chars = source.toCharArray();
length = chars.length;
i1 = 0;
i2 = 0;
}
/**
* Process quoted string, it minds remove escape characters for quotes.
* @see HeaderHelper#filterEscape(String)
*
* @return processed string
* @throws ParseException if string can't be parsed
*/
private String readQuotedString() throws ParseException {
i1 = pos;
i2 = pos;
// indicate was previous character '\'
boolean escape = false;
// indicate is final '"' already found
boolean qoute = false;
while (hasChars()) {
char c = chars[pos];
if (c == SEPARATOR && !qoute)
break;
if (c == '"' && !escape)
qoute = !qoute;
escape = !escape && c == '\\';
pos++;
i2++;
}
if (qoute)
throw new ParseException("String must be ended with qoute.", pos);
String token = getToken(true);
if (token != null)
token = HeaderHelper.filterEscape(getToken(true));
return token;
}
/**
* Read token from source string, token is not quoted string and does not
* contains any separators. See <a
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html">HTTP1.1
* specification</a>.
*
* @param terminators characters which indicate end of token
* @return token
* @throws ParseException if token contains illegal characters
*/
private String readToken(char[] terminators) throws ParseException {
i1 = pos;
i2 = pos;
while (hasChars()) {
char c = chars[pos];
if (checkChar(c, terminators))
break;
pos++;
i2++;
}
String token = getToken(false);
if (token != null) {
// check is it valid token
int err = -1;
if ((err = HeaderHelper.isToken(token)) != -1)
throw new ParseException("Token '" + token + "' contains not legal characters at " + err, err);
}
return token;
}
/**
* Check does char array <tt>chs</tt> contains char <tt>c</tt>.
*
* @param c char
* @param chs char array
* @return true if char array contains character <tt>c</tt>, false otherwise
*/
private boolean checkChar(char c, char[] chs) {
for (int i = 0; i < chs.length; i++) {
if (c == chs[i])
return true;
}
return false;
}
}