package com.firefly.codec.http2.model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Implements a quoted comma separated list of values in accordance with
* RFC7230. OWS is removed and quoted characters ignored for parsing.
*
* @see "https://tools.ietf.org/html/rfc7230#section-3.2.6"
* @see "https://tools.ietf.org/html/rfc7230#section-7"
*/
public class QuotedCSV implements Iterable<String> {
private enum State {
VALUE, PARAM_NAME, PARAM_VALUE
};
protected final List<String> _values = new ArrayList<>();
protected final boolean _keepQuotes;
public QuotedCSV(String... values) {
this(true, values);
}
public QuotedCSV(boolean keepQuotes, String... values) {
_keepQuotes = keepQuotes;
for (String v : values)
addValue(v);
}
/**
* Add and parse a value string(s)
*
* @param value
* A value that may contain one or more Quoted CSV items.
*/
public void addValue(String value) {
StringBuffer buffer = new StringBuffer();
int l = value.length();
State state = State.VALUE;
boolean quoted = false;
boolean sloshed = false;
int nws_length = 0;
int last_length = 0;
int value_length = -1;
int param_name = -1;
int param_value = -1;
for (int i = 0; i <= l; i++) {
char c = i == l ? 0 : value.charAt(i);
// Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
if (quoted && c != 0) {
if (sloshed)
sloshed = false;
else {
switch (c) {
case '\\':
sloshed = true;
if (!_keepQuotes)
continue;
break;
case '"':
quoted = false;
if (!_keepQuotes)
continue;
break;
}
}
buffer.append(c);
nws_length = buffer.length();
continue;
}
// Handle common cases
switch (c) {
case ' ':
case '\t':
if (buffer.length() > last_length) // not leading OWS
buffer.append(c);
continue;
case '"':
quoted = true;
if (_keepQuotes) {
if (state == State.PARAM_VALUE && param_value < 0)
param_value = nws_length;
buffer.append(c);
} else if (state == State.PARAM_VALUE && param_value < 0)
param_value = nws_length;
nws_length = buffer.length();
continue;
case ';':
buffer.setLength(nws_length); // trim following OWS
if (state == State.VALUE) {
parsedValue(buffer);
value_length = buffer.length();
} else
parsedParam(buffer, value_length, param_name, param_value);
nws_length = buffer.length();
param_name = param_value = -1;
buffer.append(c);
last_length = ++nws_length;
state = State.PARAM_NAME;
continue;
case ',':
case 0:
if (nws_length > 0) {
buffer.setLength(nws_length); // trim following OWS
switch (state) {
case VALUE:
parsedValue(buffer);
value_length = buffer.length();
break;
case PARAM_NAME:
case PARAM_VALUE:
parsedParam(buffer, value_length, param_name, param_value);
break;
}
_values.add(buffer.toString());
}
buffer.setLength(0);
last_length = 0;
nws_length = 0;
value_length = param_name = param_value = -1;
state = State.VALUE;
continue;
case '=':
switch (state) {
case VALUE:
// It wasn't really a value, it was a param name
value_length = param_name = 0;
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length = ++nws_length;
state = State.PARAM_VALUE;
continue;
case PARAM_NAME:
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length = ++nws_length;
state = State.PARAM_VALUE;
continue;
case PARAM_VALUE:
if (param_value < 0)
param_value = nws_length;
buffer.append(c);
nws_length = buffer.length();
continue;
}
continue;
default: {
switch (state) {
case VALUE: {
buffer.append(c);
nws_length = buffer.length();
continue;
}
case PARAM_NAME: {
if (param_name < 0)
param_name = nws_length;
buffer.append(c);
nws_length = buffer.length();
continue;
}
case PARAM_VALUE: {
if (param_value < 0)
param_value = nws_length;
buffer.append(c);
nws_length = buffer.length();
continue;
}
}
}
}
}
}
/**
* Called when a value has been parsed
*
* @param buffer
* Containing the trimmed value, which may be mutated
*/
protected void parsedValue(StringBuffer buffer) {
}
/**
* Called when a parameter has been parsed
*
* @param buffer
* Containing the trimmed value and all parameters, which may be
* mutated
* @param valueLength
* The length of the value
* @param paramName
* The index of the start of the parameter just parsed
* @param paramValue
* The index of the start of the parameter value just parsed, or
* -1
*/
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) {
}
public int size() {
return _values.size();
}
public boolean isEmpty() {
return _values.isEmpty();
}
public List<String> getValues() {
return _values;
}
@Override
public Iterator<String> iterator() {
return _values.iterator();
}
public static String unquote(String s) {
// handle trivial cases
int l = s.length();
if (s == null || l == 0)
return s;
// Look for any quotes
int i = 0;
for (; i < l; i++) {
char c = s.charAt(i);
if (c == '"')
break;
}
if (i == l)
return s;
boolean quoted = true;
boolean sloshed = false;
StringBuffer buffer = new StringBuffer();
buffer.append(s, 0, i);
i++;
for (; i < l; i++) {
char c = s.charAt(i);
if (quoted) {
if (sloshed) {
buffer.append(c);
sloshed = false;
} else if (c == '"')
quoted = false;
else if (c == '\\')
sloshed = true;
else
buffer.append(c);
} else if (c == '"')
quoted = true;
else
buffer.append(c);
}
return buffer.toString();
}
}