/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.core.impl.header;
import org.everrest.core.util.StringUtils;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Character.isWhitespace;
import static org.everrest.core.util.StringUtils.charAtIs;
import static org.everrest.core.util.StringUtils.charAtIsNot;
import static org.everrest.core.util.StringUtils.scan;
public class HeaderParameterParser {
private static final char PARAMS_SEPARATOR = ';';
private static final String NAME_TERMINATORS = "=;";
private static final String VALUE_TERMINATORS = ";";
/** Current position in the parsed string. */
private int pos;
/** Parsed String */
private String source;
/** Length of parsed string */
private int sourceLength;
/**
* Parses header string to map of parameters.
*
* @param header
* header 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);
Map<String, String> parameters = new HashMap<>();
pos++; // skip first ';'
while (hasRemainingChars()) {
String name = readToken(NAME_TERMINATORS);
String value = null;
if (charAtIs(source, pos, '=')) {
pos++; // skip '='
if (charAtIs(source, pos, '"')) {
value = readQuotedString();
} else if (hasRemainingChars()) {
value = readToken(VALUE_TERMINATORS);
}
}
if (charAtIs(source, pos, PARAMS_SEPARATOR)) {
pos++; // skip ';'
}
if (!isNullOrEmpty(name)) {
parameters.put(name, value);
}
}
return parameters;
}
private boolean hasRemainingChars() {
return pos < sourceLength;
}
private void init(String header) {
pos = scan(header, PARAMS_SEPARATOR);
if (charAtIsNot(header, pos, PARAMS_SEPARATOR)) {
return;
}
source = header;
sourceLength = header.length();
}
private String readQuotedString() throws ParseException {
int startOfToken = pos;
int endOfToken = pos;
// indicate was previous character '\'
boolean escape = false;
// indicate is final '"' already found
boolean inQuotes = false;
while (hasRemainingChars()) {
if (!inQuotes && charAtIs(source, pos, PARAMS_SEPARATOR)) {
break;
}
if (!escape && charAtIs(source, pos, '"')) {
inQuotes = !inQuotes;
}
escape = !escape && charAtIs(source, pos, '\\');
pos++;
endOfToken++;
}
if (inQuotes) {
throw new ParseException("String must be ended with quote.", pos);
}
String token = readToken(startOfToken, endOfToken);
if (token != null) {
token = HeaderHelper.removeQuoteEscapes(token);
}
return token;
}
/**
* Reads token from header 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(String terminators) throws ParseException {
int startOfToken = pos;
int endOfToken = pos;
while (hasRemainingChars()) {
char c = source.charAt(pos);
if (StringUtils.contains(terminators, c)) {
break;
}
pos++;
endOfToken++;
}
String token = readToken(startOfToken, endOfToken);
if (token != null) {
int err;
if ((err = HeaderHelper.isToken(token)) != -1) {
throw new ParseException(String.format("Token '%s' contains not legal characters at %d", token, err), err);
}
}
return token;
}
private String readToken(int startOfToken, int endOfToken) {
while ((startOfToken < endOfToken) && isWhitespace(source.charAt(startOfToken))) {
startOfToken++;
}
while ((endOfToken > startOfToken) && isWhitespace(source.charAt(endOfToken - 1))) {
endOfToken--;
}
if (charAtIs(source, startOfToken, '"') && charAtIs(source, endOfToken - 1, '"')) {
startOfToken++;
endOfToken--;
}
String token = null;
if (endOfToken > startOfToken) {
token = source.substring(startOfToken, endOfToken);
}
return token;
}
}