1
18
19 package io.undertow.server.handlers.form;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.UndertowMessages;
23 import io.undertow.UndertowOptions;
24 import io.undertow.connector.PooledByteBuffer;
25 import io.undertow.server.HttpHandler;
26 import io.undertow.server.HttpServerExchange;
27 import io.undertow.util.UrlDecodeException;
28 import io.undertow.util.Headers;
29 import io.undertow.util.SameThreadExecutor;
30 import io.undertow.util.URLUtils;
31 import org.xnio.ChannelListener;
32 import org.xnio.IoUtils;
33 import org.xnio.channels.StreamSourceChannel;
34
35 import java.io.IOException;
36 import java.nio.ByteBuffer;
37
38
45 public class FormEncodedDataDefinition implements FormParserFactory.ParserDefinition<FormEncodedDataDefinition> {
46
47 public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
48 private static boolean parseExceptionLogAsDebug = false;
49 private String defaultEncoding = "ISO-8859-1";
50 private boolean forceCreation = false;
51
52 public FormEncodedDataDefinition() {
53 }
54
55 @Override
56 public FormDataParser create(final HttpServerExchange exchange) {
57 String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
58 if (forceCreation || (mimeType != null && mimeType.startsWith(APPLICATION_X_WWW_FORM_URLENCODED))) {
59
60 String charset = defaultEncoding;
61 String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
62 if (contentType != null) {
63 String cs = Headers.extractQuotedValueFromHeader(contentType, "charset");
64 if (cs != null) {
65 charset = cs;
66 }
67 }
68 UndertowLogger.REQUEST_LOGGER.tracef("Created form encoded parser for %s", exchange);
69 return new FormEncodedDataParser(charset, exchange);
70 }
71 return null;
72 }
73
74 public String getDefaultEncoding() {
75 return defaultEncoding;
76 }
77
78 public boolean isForceCreation() {
79 return forceCreation;
80 }
81
82 public FormEncodedDataDefinition setForceCreation(boolean forceCreation) {
83 this.forceCreation = forceCreation;
84 return this;
85 }
86
87 public FormEncodedDataDefinition setDefaultEncoding(final String defaultEncoding) {
88 this.defaultEncoding = defaultEncoding;
89 return this;
90 }
91
92 private static final class FormEncodedDataParser implements ChannelListener<StreamSourceChannel>, FormDataParser {
93
94 private final HttpServerExchange exchange;
95 private final FormData data;
96 private final StringBuilder builder = new StringBuilder();
97 private String name = null;
98 private String charset;
99 private HttpHandler handler;
100
101
102
103
104
105
106 private int state = 0;
107
108 private FormEncodedDataParser(final String charset, final HttpServerExchange exchange) {
109 this.exchange = exchange;
110 this.charset = charset;
111 this.data = new FormData(exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, 1000));
112 }
113
114 @Override
115 public void handleEvent(final StreamSourceChannel channel) {
116 try {
117 doParse(channel);
118 if (state == 4) {
119 exchange.dispatch(SameThreadExecutor.INSTANCE, handler);
120 }
121 } catch (IOException e) {
122 IoUtils.safeClose(channel);
123 UndertowLogger.REQUEST_IO_LOGGER.ioExceptionReadingFromChannel(e);
124 exchange.endExchange();
125
126 }
127 }
128
129 private void doParse(final StreamSourceChannel channel) throws IOException {
130 int c = 0;
131 final PooledByteBuffer pooled = exchange.getConnection().getByteBufferPool().allocate();
132 try {
133 final ByteBuffer buffer = pooled.getBuffer();
134 do {
135 buffer.clear();
136 c = channel.read(buffer);
137 if (c > 0) {
138 buffer.flip();
139 while (buffer.hasRemaining()) {
140 byte n = buffer.get();
141 switch (state) {
142 case 0: {
143 if (n == '=') {
144 name = builder.toString();
145 builder.setLength(0);
146 state = 2;
147 } else if (n == '&') {
148 addPair(builder.toString(), "");
149 builder.setLength(0);
150 state = 0;
151 } else if (n == '%' || n == '+') {
152 state = 1;
153 builder.append((char) n);
154 } else {
155 builder.append((char) n);
156 }
157 break;
158 }
159 case 1: {
160 if (n == '=') {
161 name = decodeParameterName(builder.toString(), charset, true, new StringBuilder());
162 builder.setLength(0);
163 state = 2;
164 } else if (n == '&') {
165 addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), "");
166 builder.setLength(0);
167 state = 0;
168 } else {
169 builder.append((char) n);
170 }
171 break;
172 }
173 case 2: {
174 if (n == '&') {
175 addPair(name, builder.toString());
176 builder.setLength(0);
177 state = 0;
178 } else if (n == '%' || n == '+') {
179 state = 3;
180 builder.append((char) n);
181 } else {
182 builder.append((char) n);
183 }
184 break;
185 }
186 case 3: {
187 if (n == '&') {
188 addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder()));
189 builder.setLength(0);
190 state = 0;
191 } else {
192 builder.append((char) n);
193 }
194 break;
195 }
196 }
197 }
198 }
199 } while (c > 0);
200 if (c == -1) {
201 if (state == 2) {
202 addPair(name, builder.toString());
203 } else if (state == 3) {
204 addPair(name, decodeParameterValue(name, builder.toString(), charset, true, new StringBuilder()));
205 } else if(builder.length() > 0) {
206 if(state == 1) {
207 addPair(decodeParameterName(builder.toString(), charset, true, new StringBuilder()), "");
208 } else {
209 addPair(builder.toString(), "");
210 }
211 }
212 state = 4;
213 exchange.putAttachment(FORM_DATA, data);
214 }
215 } finally {
216 pooled.close();
217 }
218 }
219
220 private void addPair(String name, String value) {
221
222 if(name != null && value != null) {
223 data.add(name, value);
224 }
225 }
226
227 private String decodeParameterValue(String name, String value, String charset, boolean decodeSlash, StringBuilder stringBuilder) {
228 String decodedValue = null;
229
230 try {
231 decodedValue = URLUtils.decode(value, charset, decodeSlash, stringBuilder);
232 } catch (UrlDecodeException e) {
233 if (!parseExceptionLogAsDebug) {
234 UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e));
235 parseExceptionLogAsDebug = true;
236 } else {
237 UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterValue(name, value, e));
238 }
239 }
240
241 return decodedValue;
242 }
243
244 private String decodeParameterName(String name, String charset, boolean decodeSlash, StringBuilder stringBuilder) {
245 String decodedName = null;
246
247 try {
248 decodedName = URLUtils.decode(name, charset, decodeSlash, stringBuilder);
249 } catch (UrlDecodeException e) {
250 if (!parseExceptionLogAsDebug) {
251 UndertowLogger.REQUEST_LOGGER.errorf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e));
252 parseExceptionLogAsDebug = true;
253 } else {
254 UndertowLogger.REQUEST_LOGGER.debugf(UndertowMessages.MESSAGES.failedToDecodeParameterName(name, e));
255 }
256 }
257
258 return decodedName;
259 }
260
261 @Override
262 public void parse(HttpHandler handler) throws Exception {
263 if (exchange.getAttachment(FORM_DATA) != null) {
264 handler.handleRequest(exchange);
265 return;
266 }
267 this.handler = handler;
268 StreamSourceChannel channel = exchange.getRequestChannel();
269 if (channel == null) {
270 throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided());
271 } else {
272 doParse(channel);
273 if (state != 4) {
274 channel.getReadSetter().set(this);
275 channel.resumeReads();
276 } else {
277 exchange.dispatch(SameThreadExecutor.INSTANCE, handler);
278 }
279 }
280 }
281
282 @Override
283 public FormData parseBlocking() throws IOException {
284 final FormData existing = exchange.getAttachment(FORM_DATA);
285 if (existing != null) {
286 return existing;
287 }
288
289 StreamSourceChannel channel = exchange.getRequestChannel();
290 if (channel == null) {
291 throw new IOException(UndertowMessages.MESSAGES.requestChannelAlreadyProvided());
292 } else {
293 while (state != 4) {
294 doParse(channel);
295 if (state != 4) {
296 channel.awaitReadable();
297 }
298 }
299 }
300 return data;
301 }
302
303 @Override
304 public void close() throws IOException {
305
306 }
307
308 @Override
309 public void setCharacterEncoding(final String encoding) {
310 this.charset = encoding;
311 }
312 }
313
314 }
315