io.undertow.server.handlers.form.FormEncodedDataDefinition Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.UrlDecodeException;
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;
/**
* Parser definition for form encoded data. This handler takes effect for any request that has a mime type
* of application/x-www-form-urlencoded. The handler attaches a {@link FormDataParser} to the chain
* that can parse the underlying form data asynchronously.
*
* @author Stuart Douglas
*/
public class FormEncodedDataDefinition implements FormParserFactory.ParserDefinition {
public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
private static boolean parseExceptionLogAsDebug = false;
private String defaultEncoding = "ISO-8859-1";
private boolean forceCreation = false; //if the parser should be created even if the correct headers are missing
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, 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;
//0= parsing name
//1=parsing name, decode required
//2=parsing value
//3=parsing value, decode required
//4=finished
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 == '&') {
addPair(builder.toString(), "");
builder.setLength(0);
state = 0;
} else if (n == '%' || n == '+' || n < 0) {
state = 1;
builder.append((char) (n & 0xFF));
} else {
builder.append((char) n);
}
break;
}
case 1: {
if (n == '=') {
name = decodeParameterName(builder.toString(), charset, true, new StringBuilder());
builder.setLength(0);
state = 2;
} else if (n == '&') {
addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), "");
builder.setLength(0);
state = 0;
} else {
builder.append((char) (n & 0xFF));
}
break;
}
case 2: {
if (n == '&') {
addPair(name, builder.toString());
builder.setLength(0);
state = 0;
} else if (n == '%' || n == '+' || n < 0) {
state = 3;
builder.append((char) (n & 0xFF));
} else {
builder.append((char) n);
}
break;
}
case 3: {
if (n == '&') {
addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder()));
builder.setLength(0);
state = 0;
} else {
builder.append((char) (n & 0xFF));
}
break;
}
}
}
}
} while (c > 0);
if (c == -1) {
if (state == 2) {
addPair(name, builder.toString());
} else if (state == 3) {
addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder()));
} else if(builder.length() > 0) {
if(state == 1) {
addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), "");
} else {
addPair(builder.toString(), "");
}
}
state = 4;
exchange.putAttachment(FORM_DATA, data);
}
} finally {
pooled.close();
}
}
private void addPair(String name, String value) {
//if there was exception during decoding ignore the parameter [UNDERTOW-1554]
if(name != null && value != null) {
data.add(name, value);
}
}
private String decodeParameterValue(String name, String value, String charset, boolean decodeSlash, StringBuilder stringBuilder) {
String decodedValue = null;
try {
decodedValue = URLUtils.decode(value, charset, decodeSlash, stringBuilder);
} catch (UrlDecodeException e) {
if (!parseExceptionLogAsDebug) {
UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e));
parseExceptionLogAsDebug = true;
} else {
UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e));
}
}
return decodedValue;
}
private String decodeParameterName(String name, String charset, boolean decodeSlash, StringBuilder stringBuilder) {
String decodedName = null;
try {
decodedName = URLUtils.decode(name, charset, decodeSlash, stringBuilder);
} catch (UrlDecodeException e) {
if (!parseExceptionLogAsDebug) {
UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e));
parseExceptionLogAsDebug = true;
} else {
UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e));
}
}
return decodedName;
}
@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;
}
}
}