1
18
19 package io.undertow.server;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.UndertowMessages;
23 import io.undertow.UndertowOptions;
24 import io.undertow.server.handlers.Cookie;
25 import io.undertow.util.DateUtils;
26 import io.undertow.util.HeaderMap;
27 import io.undertow.util.HeaderValues;
28 import io.undertow.util.Headers;
29 import io.undertow.util.HttpString;
30 import io.undertow.util.LegacyCookieSupport;
31 import io.undertow.util.ParameterLimitException;
32 import io.undertow.util.StatusCodes;
33 import io.undertow.util.URLUtils;
34 import io.undertow.connector.PooledByteBuffer;
35 import org.xnio.channels.StreamSourceChannel;
36 import org.xnio.conduits.ConduitStreamSinkChannel;
37
38 import java.io.IOException;
39 import java.util.Date;
40 import java.util.Map;
41 import java.util.concurrent.Executor;
42 import java.util.concurrent.RejectedExecutionException;
43
44
52 public class Connectors {
53
54 private static final boolean[] ALLOWED_TOKEN_CHARACTERS = new boolean[256];
55
56 static {
57 for(int i = 0; i < ALLOWED_TOKEN_CHARACTERS.length; ++i) {
58 if((i >='0' && i <= '9') ||
59 (i >='a' && i <= 'z') ||
60 (i >='A' && i <= 'Z')) {
61 ALLOWED_TOKEN_CHARACTERS[i] = true;
62 } else {
63 switch (i) {
64 case '!':
65 case '#':
66 case '$':
67 case '%':
68 case '&':
69 case '\'':
70 case '*':
71 case '+':
72 case '-':
73 case '.':
74 case '^':
75 case '_':
76 case '`':
77 case '|':
78 case '~': {
79 ALLOWED_TOKEN_CHARACTERS[i] = true;
80 break;
81 }
82 default:
83 ALLOWED_TOKEN_CHARACTERS[i] = false;
84 }
85 }
86 }
87 }
88
94 public static void flattenCookies(final HttpServerExchange exchange) {
95 Map<String, Cookie> cookies = exchange.getResponseCookiesInternal();
96 boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION);
97 if (cookies != null) {
98 for (Map.Entry<String, Cookie> entry : cookies.entrySet()) {
99 exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(entry.getValue(), enableRfc6265Validation));
100 }
101 }
102 }
103
104
111 public static void addCookie(final HttpServerExchange exchange, Cookie cookie) {
112 boolean enableRfc6265Validation = exchange.getConnection().getUndertowOptions().get(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, UndertowOptions.DEFAULT_ENABLE_RFC6265_COOKIE_VALIDATION);
113 exchange.getResponseHeaders().add(Headers.SET_COOKIE, getCookieString(cookie, enableRfc6265Validation));
114 }
115
116
122 public static void ungetRequestBytes(final HttpServerExchange exchange, PooledByteBuffer... buffers) {
123 PooledByteBuffer[] existing = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA);
124 PooledByteBuffer[] newArray;
125 if (existing == null) {
126 newArray = new PooledByteBuffer[buffers.length];
127 System.arraycopy(buffers, 0, newArray, 0, buffers.length);
128 } else {
129 newArray = new PooledByteBuffer[existing.length + buffers.length];
130 System.arraycopy(existing, 0, newArray, 0, existing.length);
131 System.arraycopy(buffers, 0, newArray, existing.length, buffers.length);
132 }
133 exchange.putAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA, newArray);
134 exchange.addExchangeCompleteListener(BufferedRequestDataCleanupListener.INSTANCE);
135 }
136
137 private enum BufferedRequestDataCleanupListener implements ExchangeCompletionListener {
138 INSTANCE;
139
140 @Override
141 public void exchangeEvent(HttpServerExchange exchange, NextListener nextListener) {
142 PooledByteBuffer[] bufs = exchange.getAttachment(HttpServerExchange.BUFFERED_REQUEST_DATA);
143 if (bufs != null) {
144 for (PooledByteBuffer i : bufs) {
145 if(i != null) {
146 i.close();
147 }
148 }
149 }
150 nextListener.proceed();
151 }
152 }
153
154 public static void terminateRequest(final HttpServerExchange exchange) {
155 exchange.terminateRequest();
156 }
157
158 public static void terminateResponse(final HttpServerExchange exchange) {
159 exchange.terminateResponse();
160 }
161
162 public static void resetRequestChannel(final HttpServerExchange exchange) {
163 exchange.resetRequestChannel();
164 }
165
166 private static String getCookieString(final Cookie cookie, boolean enableRfc6265Validation) {
167 if(enableRfc6265Validation) {
168 return addRfc6265ResponseCookieToExchange(cookie);
169 } else {
170 switch (LegacyCookieSupport.adjustedCookieVersion(cookie)) {
171 case 0:
172 return addVersion0ResponseCookieToExchange(cookie);
173 case 1:
174 default:
175 return addVersion1ResponseCookieToExchange(cookie);
176 }
177 }
178 }
179
180 public static void setRequestStartTime(HttpServerExchange exchange) {
181 exchange.setRequestStartTime(System.nanoTime());
182 }
183
184 public static void setRequestStartTime(HttpServerExchange existing, HttpServerExchange newExchange) {
185 newExchange.setRequestStartTime(existing.getRequestStartTime());
186 }
187
188 private static String addRfc6265ResponseCookieToExchange(final Cookie cookie) {
189 final StringBuilder header = new StringBuilder(cookie.getName());
190 header.append("=");
191 if(cookie.getValue() != null) {
192 header.append(cookie.getValue());
193 }
194 if (cookie.getPath() != null) {
195 header.append("; Path=");
196 header.append(cookie.getPath());
197 }
198 if (cookie.getDomain() != null) {
199 header.append("; Domain=");
200 header.append(cookie.getDomain());
201 }
202 if (cookie.isDiscard()) {
203 header.append("; Discard");
204 }
205 if (cookie.isSecure()) {
206 header.append("; Secure");
207 }
208 if (cookie.isHttpOnly()) {
209 header.append("; HttpOnly");
210 }
211 if (cookie.getMaxAge() != null) {
212 if (cookie.getMaxAge() >= 0) {
213 header.append("; Max-Age=");
214 header.append(cookie.getMaxAge());
215 }
216
217
218
219
220 if (cookie.getExpires() == null) {
221 if (cookie.getMaxAge() == 0) {
222 Date expires = new Date();
223 expires.setTime(0);
224 header.append("; Expires=");
225 header.append(DateUtils.toOldCookieDateString(expires));
226 } else if (cookie.getMaxAge() > 0) {
227 Date expires = new Date();
228 expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
229 header.append("; Expires=");
230 header.append(DateUtils.toOldCookieDateString(expires));
231 }
232 }
233 }
234 if (cookie.getExpires() != null) {
235 header.append("; Expires=");
236 header.append(DateUtils.toDateString(cookie.getExpires()));
237 }
238 if (cookie.getComment() != null && !cookie.getComment().isEmpty()) {
239 header.append("; Comment=");
240 header.append(cookie.getComment());
241 }
242 if (cookie.isSameSite()) {
243 if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
244 header.append("; SameSite=");
245 header.append(cookie.getSameSiteMode());
246 }
247 }
248 return header.toString();
249 }
250
251 private static String addVersion0ResponseCookieToExchange(final Cookie cookie) {
252 final StringBuilder header = new StringBuilder(cookie.getName());
253 header.append("=");
254 if(cookie.getValue() != null) {
255 LegacyCookieSupport.maybeQuote(header, cookie.getValue());
256 }
257
258 if (cookie.getPath() != null) {
259 header.append("; path=");
260 LegacyCookieSupport.maybeQuote(header, cookie.getPath());
261 }
262 if (cookie.getDomain() != null) {
263 header.append("; domain=");
264 LegacyCookieSupport.maybeQuote(header, cookie.getDomain());
265 }
266 if (cookie.isSecure()) {
267 header.append("; secure");
268 }
269 if (cookie.isHttpOnly()) {
270 header.append("; HttpOnly");
271 }
272 if (cookie.getExpires() != null) {
273 header.append("; Expires=");
274 header.append(DateUtils.toOldCookieDateString(cookie.getExpires()));
275 } else if (cookie.getMaxAge() != null) {
276 if (cookie.getMaxAge() >= 0) {
277 header.append("; Max-Age=");
278 header.append(cookie.getMaxAge());
279 }
280 if (cookie.getMaxAge() == 0) {
281 Date expires = new Date();
282 expires.setTime(0);
283 header.append("; Expires=");
284 header.append(DateUtils.toOldCookieDateString(expires));
285 } else if (cookie.getMaxAge() > 0) {
286 Date expires = new Date();
287 expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
288 header.append("; Expires=");
289 header.append(DateUtils.toOldCookieDateString(expires));
290 }
291 }
292 if (cookie.isSameSite()) {
293 if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
294 header.append("; SameSite=");
295 header.append(cookie.getSameSiteMode());
296 }
297 }
298 return header.toString();
299
300 }
301
302 private static String addVersion1ResponseCookieToExchange(final Cookie cookie) {
303
304 final StringBuilder header = new StringBuilder(cookie.getName());
305 header.append("=");
306 if(cookie.getValue() != null) {
307 LegacyCookieSupport.maybeQuote(header, cookie.getValue());
308 }
309 header.append("; Version=1");
310 if (cookie.getPath() != null) {
311 header.append("; Path=");
312 LegacyCookieSupport.maybeQuote(header, cookie.getPath());
313 }
314 if (cookie.getDomain() != null) {
315 header.append("; Domain=");
316 LegacyCookieSupport.maybeQuote(header, cookie.getDomain());
317 }
318 if (cookie.isDiscard()) {
319 header.append("; Discard");
320 }
321 if (cookie.isSecure()) {
322 header.append("; Secure");
323 }
324 if (cookie.isHttpOnly()) {
325 header.append("; HttpOnly");
326 }
327 if (cookie.getMaxAge() != null) {
328 if (cookie.getMaxAge() >= 0) {
329 header.append("; Max-Age=");
330 header.append(cookie.getMaxAge());
331 }
332
333
334
335
336 if (cookie.getExpires() == null) {
337 if (cookie.getMaxAge() == 0) {
338 Date expires = new Date();
339 expires.setTime(0);
340 header.append("; Expires=");
341 header.append(DateUtils.toOldCookieDateString(expires));
342 } else if (cookie.getMaxAge() > 0) {
343 Date expires = new Date();
344 expires.setTime(expires.getTime() + cookie.getMaxAge() * 1000L);
345 header.append("; Expires=");
346 header.append(DateUtils.toOldCookieDateString(expires));
347 }
348 }
349 }
350 if (cookie.getExpires() != null) {
351 header.append("; Expires=");
352 header.append(DateUtils.toDateString(cookie.getExpires()));
353 }
354 if (cookie.getComment() != null && !cookie.getComment().isEmpty()) {
355 header.append("; Comment=");
356 LegacyCookieSupport.maybeQuote(header, cookie.getComment());
357 }
358 if (cookie.isSameSite()) {
359 if (cookie.getSameSiteMode() != null && !cookie.getSameSiteMode().isEmpty()) {
360 header.append("; SameSite=");
361 header.append(cookie.getSameSiteMode());
362 }
363 }
364 return header.toString();
365 }
366
367 public static void executeRootHandler(final HttpHandler handler, final HttpServerExchange exchange) {
368 try {
369 exchange.setInCall(true);
370 handler.handleRequest(exchange);
371 exchange.setInCall(false);
372 boolean resumed = exchange.isResumed();
373 if (exchange.isDispatched()) {
374 if (resumed) {
375 UndertowLogger.REQUEST_LOGGER.resumedAndDispatched();
376 exchange.setStatusCode(500);
377 exchange.endExchange();
378 return;
379 }
380 final Runnable dispatchTask = exchange.getDispatchTask();
381 Executor executor = exchange.getDispatchExecutor();
382 exchange.setDispatchExecutor(null);
383 exchange.unDispatch();
384 if (dispatchTask != null) {
385 executor = executor == null ? exchange.getConnection().getWorker() : executor;
386 try {
387 executor.execute(dispatchTask);
388 } catch (RejectedExecutionException e) {
389 UndertowLogger.REQUEST_LOGGER.debug("Failed to dispatch to worker", e);
390 exchange.setStatusCode(StatusCodes.SERVICE_UNAVAILABLE);
391 exchange.endExchange();
392 }
393 }
394 } else if (!resumed) {
395 exchange.endExchange();
396 } else {
397 exchange.runResumeReadWrite();
398 }
399 } catch (Throwable t) {
400 exchange.putAttachment(DefaultResponseListener.EXCEPTION, t);
401 exchange.setInCall(false);
402 if (!exchange.isResponseStarted()) {
403 exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
404 }
405 if(t instanceof IOException) {
406 UndertowLogger.REQUEST_IO_LOGGER.ioException((IOException) t);
407 } else {
408 UndertowLogger.REQUEST_LOGGER.undertowRequestFailed(t, exchange);
409 }
410 exchange.endExchange();
411 }
412 }
413
414
421 @Deprecated
422 public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer) {
423 try {
424 setExchangeRequestPath(exchange, encodedPath, charset, decode, allowEncodedSlash, decodeBuffer, exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_PARAMETERS, UndertowOptions.DEFAULT_MAX_PARAMETERS));
425 } catch (ParameterLimitException e) {
426 throw new RuntimeException(e);
427 }
428 }
429
436 public static void setExchangeRequestPath(final HttpServerExchange exchange, final String encodedPath, final String charset, boolean decode, final boolean allowEncodedSlash, StringBuilder decodeBuffer, int maxParameters) throws ParameterLimitException {
437 boolean requiresDecode = false;
438 final StringBuilder pathBuilder = new StringBuilder();
439 int currentPathPartIndex = 0;
440 for (int i = 0; i < encodedPath.length(); ++i) {
441 char c = encodedPath.charAt(i);
442 if (c == '?') {
443 String part;
444 String encodedPart = encodedPath.substring(currentPathPartIndex, i);
445 if (requiresDecode) {
446 part = URLUtils.decode(encodedPart, charset, allowEncodedSlash,false, decodeBuffer);
447 } else {
448 part = encodedPart;
449 }
450 pathBuilder.append(part);
451 part = pathBuilder.toString();
452 exchange.setRequestPath(part);
453 exchange.setRelativePath(part);
454 exchange.setRequestURI(encodedPath.substring(0, i));
455 final String qs = encodedPath.substring(i + 1);
456 exchange.setQueryString(qs);
457 URLUtils.parseQueryString(qs, exchange, charset, decode, maxParameters);
458 return;
459 } else if(c == ';') {
460 String part;
461 String encodedPart = encodedPath.substring(currentPathPartIndex, i);
462 if (requiresDecode) {
463 part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer);
464 } else {
465 part = encodedPart;
466 }
467 pathBuilder.append(part);
468 exchange.setRequestURI(encodedPath);
469 currentPathPartIndex = i + 1 + URLUtils.parsePathParams(encodedPath.substring(i + 1), exchange, charset, decode, maxParameters);
470 i = currentPathPartIndex -1 ;
471 } else if(c == '%' || c == '+') {
472 requiresDecode = decode;
473 }
474 }
475
476 String part;
477 String encodedPart = encodedPath.substring(currentPathPartIndex);
478 if (requiresDecode) {
479 part = URLUtils.decode(encodedPart, charset, allowEncodedSlash, false, decodeBuffer);
480 } else {
481 part = encodedPart;
482 }
483 pathBuilder.append(part);
484 part = pathBuilder.toString();
485 exchange.setRequestPath(part);
486 exchange.setRelativePath(part);
487 exchange.setRequestURI(encodedPath);
488 }
489
490
491
496 public static StreamSourceChannel getExistingRequestChannel(final HttpServerExchange exchange) {
497 return exchange.requestChannel;
498 }
499
500 public static boolean isEntityBodyAllowed(HttpServerExchange exchange){
501 int code = exchange.getStatusCode();
502 return isEntityBodyAllowed(code);
503 }
504
505 public static boolean isEntityBodyAllowed(int code) {
506 if(code >= 100 && code < 200) {
507 return false;
508 }
509 if(code == 204 || code == 304) {
510 return false;
511 }
512 return true;
513 }
514
515 public static void updateResponseBytesSent(HttpServerExchange exchange, long bytes) {
516 exchange.updateBytesSent(bytes);
517 }
518
519 public static ConduitStreamSinkChannel getConduitSinkChannel(HttpServerExchange exchange) {
520 return exchange.getConnection().getSinkChannel();
521 }
522
523
527 public static void verifyToken(HttpString header) {
528 int length = header.length();
529 for(int i = 0; i < length; ++i) {
530 byte c = header.byteAt(i);
531 if(!ALLOWED_TOKEN_CHARACTERS[c]) {
532 throw UndertowMessages.MESSAGES.invalidToken(c);
533 }
534 }
535 }
536
537
540 public static boolean isValidTokenCharacter(byte c) {
541 return ALLOWED_TOKEN_CHARACTERS[c];
542 }
543
544
545
549 public static boolean areRequestHeadersValid(HeaderMap headers) {
550 HeaderValues te = headers.get(Headers.TRANSFER_ENCODING);
551 HeaderValues cl = headers.get(Headers.CONTENT_LENGTH);
552 if(te != null && cl != null) {
553 return false;
554 } else if(te != null && te.size() > 1) {
555 return false;
556 } else if(cl != null && cl.size() > 1) {
557 return false;
558 }
559 return true;
560 }
561 }
562