1
18
19 package io.undertow.servlet.spec;
20
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.io.UnsupportedEncodingException;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.nio.charset.StandardCharsets;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.HashSet;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.TreeMap;
36 import java.util.function.Supplier;
37
38 import javax.servlet.ServletException;
39 import javax.servlet.ServletOutputStream;
40 import javax.servlet.SessionTrackingMode;
41 import javax.servlet.http.HttpServletResponse;
42 import javax.servlet.http.HttpSession;
43
44 import io.undertow.UndertowLogger;
45 import io.undertow.server.Connectors;
46 import io.undertow.server.HttpServerExchange;
47 import io.undertow.server.ResponseCommitListener;
48 import io.undertow.server.handlers.Cookie;
49 import io.undertow.server.protocol.http.HttpAttachments;
50 import io.undertow.servlet.UndertowServletMessages;
51 import io.undertow.servlet.handlers.ServletRequestContext;
52 import io.undertow.util.CanonicalPathUtils;
53 import io.undertow.util.DateUtils;
54 import io.undertow.util.HeaderMap;
55 import io.undertow.util.HeaderValues;
56 import io.undertow.util.Headers;
57 import io.undertow.util.HttpString;
58 import io.undertow.util.Protocols;
59 import io.undertow.util.RedirectBuilder;
60 import io.undertow.util.StatusCodes;
61
62 import static io.undertow.util.URLUtils.isAbsoluteUrl;
63
64
65
68 public final class HttpServletResponseImpl implements HttpServletResponse {
69
70 private final HttpServerExchange exchange;
71 private final ServletContextImpl originalServletContext;
72 private volatile ServletContextImpl servletContext;
73
74 private ServletOutputStreamImpl servletOutputStream;
75 private ResponseState responseState = ResponseState.NONE;
76 private PrintWriter writer;
77 private Integer bufferSize;
78 private long contentLength = -1;
79 private boolean insideInclude = false;
80 private Locale locale;
81 private boolean responseDone = false;
82
83 private boolean ignoredFlushPerformed = false;
84
85 private boolean treatAsCommitted = false;
86
87 private boolean charsetSet = false;
88 private String contentType;
89 private String charset;
90 private Supplier<Map<String, String>> trailerSupplier;
91
92 private Map<String, Map<String, Cookie>> duplicateCookies;
93
94 public HttpServletResponseImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) {
95 this.exchange = exchange;
96 this.servletContext = servletContext;
97 this.originalServletContext = servletContext;
98 }
99
100 public HttpServerExchange getExchange() {
101 return exchange;
102 }
103
104 @Override
105 public void addCookie(final javax.servlet.http.Cookie cookie) {
106 if (insideInclude) {
107 return;
108 }
109 final ServletCookieAdaptor servletCookieAdaptor = new ServletCookieAdaptor(cookie);
110 if (cookie.getVersion() == 0) {
111 servletCookieAdaptor.setVersion(servletContext.getDeployment().getDeploymentInfo().getDefaultCookieVersion());
112 }
113
114 if (exchange.getResponseCookies().containsKey(servletCookieAdaptor.getName())) {
115 final String cookieName = servletCookieAdaptor.getName();
116 final String path = servletCookieAdaptor.getPath();
117 final String domain = servletCookieAdaptor.getDomain();
118 final Cookie otherCookie = exchange.getResponseCookies().get(cookieName);
119 final String otherCookiePath = otherCookie.getPath();
120 final String otherCookieDomain = otherCookie.getDomain();
121
122 if ((path == otherCookiePath || (path != null && path.equals(otherCookiePath))) &&
123 (domain == otherCookieDomain || (domain != null && domain.equals(otherCookieDomain)))) {
124 exchange.setResponseCookie(servletCookieAdaptor);
125 }
126
127 else {
128 final Map<String, Cookie> cookiesByPathPlusDomain;
129 if (duplicateCookies == null) {
130 duplicateCookies = new TreeMap<>();
131 exchange.addResponseCommitListener(
132 new DuplicateCookieCommitListener());
133 }
134 if (duplicateCookies.containsKey(cookieName)) {
135 cookiesByPathPlusDomain = duplicateCookies.get(cookieName);
136 } else {
137 cookiesByPathPlusDomain = new TreeMap<>();
138 duplicateCookies.put(cookieName, cookiesByPathPlusDomain);
139 }
140 String pathPlusDomain = (otherCookiePath == null ? "null" : otherCookiePath) +
141 (otherCookieDomain == null? "null" : otherCookieDomain);
142 cookiesByPathPlusDomain.put(pathPlusDomain, otherCookie);
143 }
144 }
145 exchange.setResponseCookie(servletCookieAdaptor);
146 }
147
148 @Override
149 public boolean containsHeader(final String name) {
150 return exchange.getResponseHeaders().contains(name);
151 }
152
153 @Override
154 public String encodeUrl(final String url) {
155 return encodeURL(url);
156 }
157
158 @Override
159 public String encodeRedirectUrl(final String url) {
160 return encodeRedirectURL(url);
161 }
162
163 @Override
164 public void sendError(final int sc, final String msg) throws IOException {
165 if(insideInclude) {
166
167 return;
168 }
169 ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
170 if (responseStarted()) {
171 if(src.getErrorCode() > 0) {
172 return;
173 }
174 throw UndertowServletMessages.MESSAGES.responseAlreadyCommited();
175 }
176 if(servletContext.getDeployment().getDeploymentInfo().isSendCustomReasonPhraseOnError()) {
177 exchange.setReasonPhrase(msg);
178 }
179 writer = null;
180 responseState = ResponseState.NONE;
181 exchange.setStatusCode(sc);
182 if(src.isRunningInsideHandler()) {
183
184 treatAsCommitted = true;
185 src.setError(sc, msg);
186 } else {
187
188 doErrorDispatch(sc, msg);
189 }
190 }
191
192 public void doErrorDispatch(int sc, String error) throws IOException {
193 writer = null;
194 responseState = ResponseState.NONE;
195 resetBuffer();
196 treatAsCommitted = false;
197 final String location = servletContext.getDeployment().getErrorPages().getErrorLocation(sc);
198 if (location != null) {
199 RequestDispatcherImpl requestDispatcher = new RequestDispatcherImpl(location, servletContext);
200 final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
201 try {
202 requestDispatcher.error(servletRequestContext, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet().getServletInfo().getName(), error);
203 } catch (ServletException e) {
204 throw new RuntimeException(e);
205 }
206 } else if (error != null) {
207 setContentType("text/html");
208 setCharacterEncoding("UTF-8");
209 if(servletContext.getDeployment().getDeploymentInfo().isEscapeErrorMessage()) {
210 getWriter().write("<html><head><title>Error</title></head><body>" + escapeHtml(error) + "</body></html>");
211 } else {
212 getWriter().write("<html><head><title>Error</title></head><body>" + error + "</body></html>");
213 }
214 getWriter().close();
215 }
216 responseDone();
217 }
218
219 @Override
220 public void sendError(final int sc) throws IOException {
221 sendError(sc, StatusCodes.getReason(sc));
222 }
223
224 @Override
225 public void sendRedirect(final String location) throws IOException {
226 if (responseStarted()) {
227 throw UndertowServletMessages.MESSAGES.responseAlreadyCommited();
228 }
229 resetBuffer();
230 setStatus(StatusCodes.FOUND);
231 String realPath;
232 if (isAbsoluteUrl(location)) {
233 exchange.getResponseHeaders().put(Headers.LOCATION, location);
234 } else {
235 if (location.startsWith("/")) {
236 realPath = location;
237 } else {
238 String current = exchange.getRelativePath();
239 int lastSlash = current.lastIndexOf("/");
240 if (lastSlash != -1) {
241 current = current.substring(0, lastSlash + 1);
242 }
243 realPath = CanonicalPathUtils.canonicalize(servletContext.getContextPath() + current + location);
244 }
245 String loc = exchange.getRequestScheme() + ": + exchange.getHostAndPort() + realPath;
246 exchange.getResponseHeaders().put(Headers.LOCATION, loc);
247 }
248 responseDone();
249 }
250
251 @Override
252 public void setDateHeader(final String name, final long date) {
253 setHeader(name, DateUtils.toDateString(new Date(date)));
254 }
255
256 @Override
257 public void addDateHeader(final String name, final long date) {
258 addHeader(name, DateUtils.toDateString(new Date(date)));
259 }
260
261 @Override
262 public void setHeader(final String name, final String value) {
263 if(name == null) {
264 throw UndertowServletMessages.MESSAGES.headerNameWasNull();
265 }
266 setHeader(HttpString.tryFromString(name), value);
267 }
268
269
270 public void setHeader(final HttpString name, final String value) {
271 if(name == null) {
272 throw UndertowServletMessages.MESSAGES.headerNameWasNull();
273 }
274 if (insideInclude || ignoredFlushPerformed) {
275 return;
276 }
277 if(name.equals(Headers.CONTENT_TYPE)) {
278 setContentType(value);
279 } else {
280 exchange.getResponseHeaders().put(name, value);
281 }
282 }
283
284 @Override
285 public void addHeader(final String name, final String value) {
286 if(name == null) {
287 throw UndertowServletMessages.MESSAGES.headerNameWasNull();
288 }
289 addHeader(HttpString.tryFromString(name), value);
290 }
291
292 public void addHeader(final HttpString name, final String value) {
293 if(name == null) {
294 throw UndertowServletMessages.MESSAGES.headerNameWasNull();
295 }
296 if (insideInclude || ignoredFlushPerformed || treatAsCommitted) {
297 return;
298 }
299 if(name.equals(Headers.CONTENT_TYPE) && !exchange.getResponseHeaders().contains(Headers.CONTENT_TYPE)) {
300 setContentType(value);
301 } else {
302 exchange.getResponseHeaders().add(name, value);
303 }
304 }
305
306 @Override
307 public void setIntHeader(final String name, final int value) {
308 setHeader(name, Integer.toString(value));
309 }
310
311 @Override
312 public void addIntHeader(final String name, final int value) {
313 addHeader(name, Integer.toString(value));
314 }
315
316 @Override
317 public void setStatus(final int sc) {
318 if (insideInclude || treatAsCommitted) {
319 return;
320 }
321 if (responseStarted()) {
322 return;
323 }
324 exchange.setStatusCode(sc);
325 }
326
327 @Override
328 public void setStatus(final int sc, final String sm) {
329 setStatus(sc);
330 if(!insideInclude && servletContext.getDeployment().getDeploymentInfo().isSendCustomReasonPhraseOnError()) {
331 exchange.setReasonPhrase(sm);
332 }
333 }
334
335 @Override
336 public int getStatus() {
337 return exchange.getStatusCode();
338 }
339
340 @Override
341 public String getHeader(final String name) {
342 return exchange.getResponseHeaders().getFirst(name);
343 }
344
345 @Override
346 public Collection<String> getHeaders(final String name) {
347 HeaderValues headers = exchange.getResponseHeaders().get(name);
348 if(headers == null) {
349 return Collections.emptySet();
350 }
351 return new ArrayList<>(headers);
352 }
353
354 @Override
355 public Collection<String> getHeaderNames() {
356 final Set<String> headers = new HashSet<>();
357 for (final HttpString i : exchange.getResponseHeaders().getHeaderNames()) {
358 headers.add(i.toString());
359 }
360 return headers;
361 }
362
363 @Override
364 public String getCharacterEncoding() {
365 if (charset != null) {
366 return charset;
367 }
368
369 if (servletContext.getDeployment().getDeploymentInfo().getDefaultResponseEncoding() != null) {
370 return servletContext.getDeployment().getDeploymentInfo().getDefaultResponseEncoding();
371 }
372
373 if (servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding() != null) {
374 return servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding();
375 }
376
377
378 return StandardCharsets.ISO_8859_1.name();
379 }
380
381 @Override
382 public String getContentType() {
383 if (contentType != null) {
384 if (charsetSet) {
385 return contentType + ";charset=" + getCharacterEncoding();
386 } else {
387 return contentType;
388 }
389 }
390 return null;
391 }
392
393 @Override
394 public ServletOutputStream getOutputStream() {
395 if (responseState == ResponseState.WRITER) {
396 throw UndertowServletMessages.MESSAGES.getWriterAlreadyCalled();
397 }
398 responseState = ResponseState.STREAM;
399 createOutputStream();
400 return servletOutputStream;
401 }
402
403 @Override
404 public PrintWriter getWriter() throws IOException {
405 if (writer == null) {
406 if (!charsetSet) {
407
408 setCharacterEncoding(getCharacterEncoding());
409 }
410 if (responseState == ResponseState.STREAM) {
411 throw UndertowServletMessages.MESSAGES.getOutputStreamAlreadyCalled();
412 }
413 responseState = ResponseState.WRITER;
414 createOutputStream();
415 final ServletPrintWriter servletPrintWriter = new ServletPrintWriter(servletOutputStream, getCharacterEncoding());
416 writer = ServletPrintWriterDelegate.newInstance(servletPrintWriter);
417 }
418 return writer;
419 }
420
421 private void createOutputStream() {
422 if (servletOutputStream == null) {
423 if (bufferSize == null) {
424 servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY));
425 } else {
426 servletOutputStream = new ServletOutputStreamImpl(exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY), bufferSize);
427 }
428 }
429 }
430
431 @Override
432 public void setCharacterEncoding(final String charset) {
433 if (insideInclude || responseStarted() || writer != null || isCommitted()) {
434 return;
435 }
436 charsetSet = charset != null;
437 this.charset = charset;
438 if (contentType != null) {
439 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType());
440 }
441 }
442
443 @Override
444 public void setContentLength(final int len) {
445 setContentLengthLong((long) len);
446 }
447
448 @Override
449 public void setContentLengthLong(final long len) {
450 if (insideInclude || responseStarted()) {
451 return;
452 }
453 if(len >= 0) {
454 exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, Long.toString(len));
455 } else {
456 exchange.getResponseHeaders().remove(Headers.CONTENT_LENGTH);
457 }
458 this.contentLength = len;
459 }
460
461 boolean isIgnoredFlushPerformed() {
462 return ignoredFlushPerformed;
463 }
464
465 void setIgnoredFlushPerformed(boolean ignoredFlushPerformed) {
466 this.ignoredFlushPerformed = ignoredFlushPerformed;
467 }
468
469 private boolean responseStarted() {
470 return exchange.isResponseStarted() || ignoredFlushPerformed || treatAsCommitted;
471 }
472
473 @Override
474 public void setContentType(final String type) {
475 if (type == null || insideInclude || responseStarted()) {
476 return;
477 }
478 ContentTypeInfo ct = servletContext.parseContentType(type);
479 contentType = ct.getContentType();
480 boolean useCharset = false;
481 if(ct.getCharset() != null && writer == null && !isCommitted()) {
482 charset = ct.getCharset();
483 charsetSet = true;
484 useCharset = true;
485 }
486 if(useCharset || !charsetSet) {
487 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader());
488 } else if(ct.getCharset() == null) {
489 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getHeader() + "; charset=" + charset);
490 }else {
491 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, ct.getContentType() + "; charset=" + charset);
492 }
493 }
494
495 @Override
496 public void setBufferSize(final int size) {
497 if (servletOutputStream != null) {
498 servletOutputStream.setBufferSize(size);
499 }
500 this.bufferSize = size;
501 }
502
503 @Override
504 public int getBufferSize() {
505 if (bufferSize == null) {
506 return exchange.getConnection().getBufferSize();
507 }
508 return bufferSize;
509 }
510
511 @Override
512 public void flushBuffer() throws IOException {
513 if (writer != null) {
514 writer.flush();
515 } else if (servletOutputStream != null) {
516 servletOutputStream.flush();
517 } else {
518 createOutputStream();
519 servletOutputStream.flush();
520 }
521 }
522
523 public void closeStreamAndWriter() throws IOException {
524 if(treatAsCommitted) {
525 return;
526 }
527 if (writer != null) {
528 writer.close();
529 } else {
530 if (servletOutputStream == null) {
531 createOutputStream();
532 }
533
534 servletOutputStream.close();
535 }
536 }
537
538 public void freeResources() throws IOException {
539 if(writer != null) {
540 writer.close();
541 }
542 if(servletOutputStream != null) {
543 servletOutputStream.close();
544 }
545 }
546
547 @Override
548 public void resetBuffer() {
549 if (servletOutputStream != null) {
550 servletOutputStream.resetBuffer();
551 }
552 if (writer != null) {
553 final ServletPrintWriter servletPrintWriter;
554 try {
555 servletPrintWriter = new ServletPrintWriter(servletOutputStream, getCharacterEncoding());
556 writer = ServletPrintWriterDelegate.newInstance(servletPrintWriter);
557 } catch (UnsupportedEncodingException e) {
558 throw new RuntimeException(e);
559 }
560 }
561 }
562
563 @Override
564 public boolean isCommitted() {
565 return responseStarted();
566 }
567
568 @Override
569 public void reset() {
570 if (servletOutputStream != null) {
571 servletOutputStream.resetBuffer();
572 }
573 writer = null;
574 responseState = ResponseState.NONE;
575 exchange.getResponseHeaders().clear();
576 exchange.setStatusCode(StatusCodes.OK);
577 treatAsCommitted = false;
578 }
579
580 @Override
581 public void setLocale(final Locale loc) {
582 if (insideInclude || responseStarted()) {
583 return;
584 }
585 this.locale = loc;
586 exchange.getResponseHeaders().put(Headers.CONTENT_LANGUAGE, loc.getLanguage() + "-" + loc.getCountry());
587 if (!charsetSet && writer == null) {
588 final Map<String, String> localeCharsetMapping = servletContext.getDeployment().getDeploymentInfo().getLocaleCharsetMapping();
589
590
591 String charset = localeCharsetMapping.get(locale.toString());
592 if (charset == null) {
593 charset = localeCharsetMapping.get(locale.getLanguage() + "_"
594 + locale.getCountry());
595 if (charset == null) {
596 charset = localeCharsetMapping.get(locale.getLanguage());
597 }
598 }
599 if (charset != null) {
600 this.charset = charset;
601 if (contentType != null) {
602 exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType());
603 }
604 }
605 }
606
607 }
608
609 @Override
610 public Locale getLocale() {
611 if (locale != null) {
612 return locale;
613 }
614 return Locale.getDefault();
615 }
616
617 public void responseDone() {
618 if (responseDone || treatAsCommitted) {
619 return;
620 }
621 responseDone = true;
622 try {
623 closeStreamAndWriter();
624 } catch (IOException e) {
625 UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
626 } finally {
627 servletContext.updateSessionAccessTime(exchange);
628 }
629 }
630
631 public boolean isInsideInclude() {
632 return insideInclude;
633 }
634
635 public void setInsideInclude(final boolean insideInclude) {
636 this.insideInclude = insideInclude;
637 }
638
639 public void setServletContext(final ServletContextImpl servletContext) {
640 this.servletContext = servletContext;
641 }
642
643 public ServletContextImpl getServletContext() {
644 return servletContext;
645 }
646
647 public String encodeURL(String url) {
648 String absolute = toAbsolute(url);
649 if (isEncodeable(absolute)) {
650
651 if (url.equalsIgnoreCase("")) {
652 url = absolute;
653 }
654 return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId());
655 } else {
656 return (url);
657 }
658
659 }
660
661
667 public String encodeRedirectURL(String url) {
668 if (isEncodeable(toAbsolute(url))) {
669 return originalServletContext.getSessionConfig().rewriteUrl(url, servletContext.getSession(originalServletContext, exchange, true).getId());
670 } else {
671 return url;
672 }
673 }
674
675
684 private String toAbsolute(String location) {
685
686 if (location == null) {
687 return location;
688 }
689
690 boolean leadingSlash = location.startsWith("/");
691
692 if (leadingSlash || !hasScheme(location)) {
693 return RedirectBuilder.redirect(exchange, location, false);
694 } else {
695 return location;
696 }
697
698 }
699
700
703 private boolean hasScheme(String uri) {
704 int len = uri.length();
705 for (int i = 0; i < len; i++) {
706 char c = uri.charAt(i);
707 if (c == ':') {
708 return i > 0;
709 } else if (!Character.isLetterOrDigit(c) &&
710 (c != '+' && c != '-' && c != '.')) {
711 return false;
712 }
713 }
714 return false;
715 }
716
717
730 private boolean isEncodeable(final String location) {
731
732 if (location == null)
733 return (false);
734
735
736 if (location.startsWith("#"))
737 return (false);
738
739
740 final HttpServletRequestImpl hreq = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalRequest();
741
742
743 if (!originalServletContext.getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {
744 return false;
745 }
746
747 final HttpSession session = hreq.getSession(false);
748 if (session == null) {
749 return false;
750 } else if(hreq.isRequestedSessionIdFromCookie()) {
751 return false;
752 } else if (!hreq.isRequestedSessionIdFromURL() && !session.isNew()) {
753 return false;
754 }
755
756 return doIsEncodeable(hreq, session, location);
757 }
758
759 private boolean doIsEncodeable(HttpServletRequestImpl hreq, HttpSession session,
760 String location) {
761
762 URL url = null;
763 try {
764 url = new URL(location);
765 } catch (MalformedURLException e) {
766 return false;
767 }
768
769
770 if (!hreq.getScheme().equalsIgnoreCase(url.getProtocol())) {
771 return false;
772 }
773 if (!hreq.getServerName().equalsIgnoreCase(url.getHost())) {
774 return false;
775 }
776 int serverPort = hreq.getServerPort();
777 if (serverPort == -1) {
778 if ("https".equals(hreq.getScheme())) {
779 serverPort = 443;
780 } else {
781 serverPort = 80;
782 }
783 }
784 int urlPort = url.getPort();
785 if (urlPort == -1) {
786 if ("https".equals(url.getProtocol())) {
787 urlPort = 443;
788 } else {
789 urlPort = 80;
790 }
791 }
792 if (serverPort != urlPort) {
793 return false;
794 }
795
796 String file = url.getFile();
797 if (file == null) {
798 return false;
799 }
800 String tok = originalServletContext.getSessionCookieConfig().getName().toLowerCase() + "=" + session.getId();
801 if (file.contains(tok)) {
802 return false;
803 }
804
805
806 return true;
807
808 }
809
810 public long getContentLength() {
811 return contentLength;
812 }
813
814 public enum ResponseState {
815 NONE,
816 STREAM,
817 WRITER
818 }
819
820 private static String escapeHtml(String msg) {
821 return msg.replace("<", "<").replace(">", ">").replace("&", "&");
822 }
823
824 public boolean isTreatAsCommitted() {
825 return treatAsCommitted;
826 }
827
828 @Override
829 public void setTrailerFields(Supplier<Map<String, String>> supplier) {
830 if(exchange.isResponseStarted()) {
831 throw UndertowServletMessages.MESSAGES.responseAlreadyCommited();
832 }
833 if(exchange.getProtocol() == Protocols.HTTP_1_0) {
834 throw UndertowServletMessages.MESSAGES.trailersNotSupported("HTTP/1.0 request");
835 } else if(exchange.getProtocol() == Protocols.HTTP_1_1) {
836 if(exchange.getResponseHeaders().contains(Headers.CONTENT_LENGTH)) {
837 throw UndertowServletMessages.MESSAGES.trailersNotSupported("not chunked");
838 }
839 }
840 this.trailerSupplier = supplier;
841 exchange.putAttachment(HttpAttachments.RESPONSE_TRAILER_SUPPLIER, () -> {
842 HeaderMap trailers = new HeaderMap();
843 Map<String, String> map = supplier.get();
844 for(Map.Entry<String, String> e : map.entrySet()) {
845 trailers.put(new HttpString(e.getKey()), e.getValue());
846 }
847 return trailers;
848 });
849 }
850
851 @Override
852 public Supplier<Map<String, String>> getTrailerFields() {
853 return trailerSupplier;
854 }
855
856 private class DuplicateCookieCommitListener implements
857 ResponseCommitListener {
858
859 @Override
860 public void beforeCommit(HttpServerExchange exchange) {
861 for (Map.Entry<String, Map<String, Cookie>> duplicateCookiesEntry : duplicateCookies.entrySet()) {
862 for (Map.Entry<String, Cookie> cookiesByPathEntry : duplicateCookiesEntry.getValue().entrySet()) {
863 Connectors.addCookie(exchange, cookiesByPathEntry.getValue());
864 }
865 }
866 }
867 }
868 }
869