package io.undertow.server.handlers.form;
import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.UndertowOptions;
import io.undertow.connector.PooledByteBuffer;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;
import io.undertow.util.SameThreadExecutor;
import io.undertow.util.URLUtils;
import org.xnio.ChannelListener;
import org.xnio.IoUtils;
import org.xnio.channels.StreamSourceChannel;
import java.io.IOException;
import java.nio.ByteBuffer;
public class FormEncodedDataDefinition implements FormParserFactory.ParserDefinition<FormEncodedDataDefinition> {
public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
private String defaultEncoding = "ISO-8859-1";
private boolean forceCreation = false;
public FormEncodedDataDefinition() {
}
@Override
public FormDataParser create(final HttpServerExchange exchange) {
String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
if (forceCreation || (mimeType != null && mimeType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED))) {
String charset = defaultEncoding;
String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
if (contentType != null) {
String cs = Headers.extractQuotedValueFromHeader(contentType, "charset");
if (cs != null) {
charset = cs;
}
}
UndertowLogger.REQUEST_LOGGER.tracef("Created form encoded parser for %s", exchange);
return new FormEncodedDataParser(charset, exchange);
}
return null;
}
public String getDefaultEncoding() {
return defaultEncoding;
}
public boolean isForceCreation() {
return forceCreation;
}
public FormEncodedDataDefinition setForceCreation(boolean forceCreation) {
this.forceCreation = forceCreation;
return this;
}
public FormEncodedDataDefinition setDefaultEncoding(final String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
return this;
}
private static final class FormEncodedDataParser implements ChannelListener<StreamSourceChannel>, FormDataParser {
private final HttpServerExchange exchange;
private final FormData data;
private final StringBuilder builder = new StringBuilder();
private String name = null;
private String charset;
private HttpHandler handler;
private int state = 0;
private FormEncodedDataParser(final String charset, final HttpServerExchange exchange) {
this.exchange = exchange;
this.charset = charset;
this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000));
}
@Override
public void handleEvent(final StreamSourceChannel channel) {
try {
doParse(channel);
if (state == 4) {
exchange.dispatch(SameThreadExecutor.INSTANCE, handler);
}
} catch (IOException e) {
IoUtils.safeClose(channel);
UndertowLogger.REQUEST_IO_LOGGER.ioExceptionReadingFromChannel(e);
exchange.endExchange();
}
}
private void doParse(final StreamSourceChannel channel) throws IOException {
int c = 0;
final PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate();
try {
final ByteBuffer buffer = pooled.getBuffer();
do {
buffer.clear();
c = channel.read(buffer);
if (c > 0) {
buffer.flip();
while (buffer.hasRemaining()) {
byte n = buffer.get();
switch (state) {
case 0: {
if (n == '=') {
name = builder.toString();
builder.setLength(0);
state = 2;
} else if (n == '&') {
data.add(builder.toString(), "");
builder.setLength(0);
state = 0;
} else if (n == '%' || n == '+') {
state = 1;
builder.append((char) n);
} else {
builder.append((char) n);
}
break;
}
case 1: {
if (n == '=') {
name = URLUtils.decode(builder.toString(), charset, true, new StringBuilder());
builder.setLength(0);
state = 2;
} else if (n == '&') {
data.add(URLUtils.decode(builder.toString(), charset, true, new StringBuilder()), "");
builder.setLength(0);
state = 0;
} else {
builder.append((char) n);
}
break;
}
case 2: {
if (n == '&') {
data.add(name, builder.toString());
builder.setLength(0);
state = 0;
} else if (n == '%' || n == '+') {
state = 3;
builder.append((char) n);
} else {
builder.append((char) n);
}
break;
}
case 3: {
if (n == '&') {
data.add(name, URLUtils.decode(builder.toString(), charset, true, new StringBuilder()));
builder.setLength(0);
state = 0;
} else {
builder.append((char) n);
}
break;
}
}
}
}
} while (c > 0);
if (c == -1) {
if (state == 2) {
data.add(name, builder.toString());
} else if (state == 3) {
data.add(name, URLUtils.decode(builder.toString(), charset, true, new StringBuilder()));
} else if(builder.length() > 0) {
if(state == 1) {
data.add(URLUtils.decode(builder.toString(), charset, true, new StringBuilder()), "");
} else {
data.add(builder.toString(), "");
}
}
state = 4;
exchange.putAttachment(FORM_DATA, data);
}
} finally {
pooled.close();
}
}
@Override
public void parse(HttpHandler handler) throws Exception {
if (exchange.getAttachment(FORM_DATA) != null) {
handler.handleRequest(exchange);
return;
}
this.handler = handler;
StreamSourceChannel channel = exchange.getRequestChannel();
if (channel == null) {
throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided());
} else {
doParse(channel);
if (state != 4) {
channel.getReadSetter().set(this);
channel.resumeReads();
} else {
exchange.dispatch(SameThreadExecutor.INSTANCE, handler);
}
}
}
@Override
public FormData parseBlocking() throws IOException {
final FormData existing = exchange.getAttachment(FORM_DATA);
if (existing != null) {
return existing;
}
StreamSourceChannel channel = exchange.getRequestChannel();
if (channel == null) {
throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided());
} else {
while (state != 4) {
doParse(channel);
if (state != 4) {
channel.awaitReadable();
}
}
}
return data;
}
@Override
public void close() throws IOException {
}
@Override
public void setCharacterEncoding(final String encoding) {
this.charset = encoding;
}
}
}