1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.servlet.spec;
20
21 import io.undertow.security.api.SecurityContext;
22 import io.undertow.security.idm.Account;
23 import io.undertow.server.HttpServerExchange;
24 import io.undertow.server.RequestTooBigException;
25 import io.undertow.server.handlers.form.FormData;
26 import io.undertow.server.handlers.form.FormDataParser;
27 import io.undertow.server.handlers.form.MultiPartParserDefinition;
28 import io.undertow.server.protocol.http.HttpAttachments;
29 import io.undertow.server.session.Session;
30 import io.undertow.server.session.SessionConfig;
31 import io.undertow.servlet.UndertowServletMessages;
32 import io.undertow.servlet.api.AuthorizationManager;
33 import io.undertow.servlet.api.Deployment;
34 import io.undertow.servlet.api.InstanceFactory;
35 import io.undertow.servlet.api.InstanceHandle;
36 import io.undertow.servlet.core.ManagedServlet;
37 import io.undertow.servlet.core.ServletUpgradeListener;
38 import io.undertow.servlet.handlers.ServletChain;
39 import io.undertow.servlet.handlers.ServletPathMatch;
40 import io.undertow.servlet.handlers.ServletRequestContext;
41 import io.undertow.servlet.util.EmptyEnumeration;
42 import io.undertow.servlet.util.IteratorEnumeration;
43 import io.undertow.util.AttachmentKey;
44 import io.undertow.util.CanonicalPathUtils;
45 import io.undertow.util.DateUtils;
46 import io.undertow.util.HeaderMap;
47 import io.undertow.util.HeaderValues;
48 import io.undertow.util.Headers;
49 import io.undertow.util.HttpString;
50 import io.undertow.util.LocaleUtils;
51
52 import java.io.BufferedReader;
53 import java.io.IOException;
54 import java.io.InputStreamReader;
55 import java.io.UnsupportedEncodingException;
56 import java.net.InetAddress;
57 import java.net.InetSocketAddress;
58 import java.nio.charset.Charset;
59 import java.nio.charset.StandardCharsets;
60 import java.nio.charset.UnsupportedCharsetException;
61 import java.security.AccessController;
62 import java.security.Principal;
63 import java.util.ArrayList;
64 import java.util.Collection;
65 import java.util.Collections;
66 import java.util.Date;
67 import java.util.Deque;
68 import java.util.Enumeration;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Iterator;
72 import java.util.List;
73 import java.util.Locale;
74 import java.util.Map;
75 import java.util.Set;
76 import javax.servlet.AsyncContext;
77 import javax.servlet.DispatcherType;
78 import javax.servlet.MultipartConfigElement;
79 import javax.servlet.RequestDispatcher;
80 import javax.servlet.ServletException;
81 import javax.servlet.ServletInputStream;
82 import javax.servlet.ServletRequest;
83 import javax.servlet.ServletRequestWrapper;
84 import javax.servlet.ServletResponse;
85 import javax.servlet.ServletResponseWrapper;
86 import javax.servlet.http.Cookie;
87 import javax.servlet.http.HttpServletMapping;
88 import javax.servlet.http.HttpServletRequest;
89 import javax.servlet.http.HttpServletResponse;
90 import javax.servlet.http.HttpSession;
91 import javax.servlet.http.HttpUpgradeHandler;
92 import javax.servlet.http.Part;
93 import javax.servlet.http.PushBuilder;
94
95 /**
96  * The http servlet request implementation. This class is not thread safe
97  *
98  * @author Stuart Douglas
99  */

100 public final class HttpServletRequestImpl implements HttpServletRequest {
101
102     @Deprecated
103     public static final AttachmentKey<Boolean> SECURE_REQUEST = HttpServerExchange.SECURE_REQUEST;
104
105     static final AttachmentKey<Boolean> REQUESTED_SESSION_ID_SET = AttachmentKey.create(Boolean.class);
106     static final AttachmentKey<String> REQUESTED_SESSION_ID = AttachmentKey.create(String.class);
107
108     private final HttpServerExchange exchange;
109     private final ServletContextImpl originalServletContext;
110     private ServletContextImpl servletContext;
111
112     private Map<String, Object> attributes = null;
113
114     private ServletInputStream servletInputStream;
115     private BufferedReader reader;
116
117     private Cookie[] cookies;
118     private List<Part> parts = null;
119     private volatile boolean asyncStarted = false;
120     private volatile AsyncContextImpl asyncContext = null;
121     private Map<String, Deque<String>> queryParameters;
122     private FormData parsedFormData;
123     private RuntimeException formParsingException;
124     private Charset characterEncoding;
125     private boolean readStarted;
126     private SessionConfig.SessionCookieSource sessionCookieSource;
127
128     public HttpServletRequestImpl(final HttpServerExchange exchange, final ServletContextImpl servletContext) {
129         this.exchange = exchange;
130         this.servletContext = servletContext;
131         this.originalServletContext = servletContext;
132     }
133
134     public HttpServerExchange getExchange() {
135         return exchange;
136     }
137
138     @Override
139     public String getAuthType() {
140         SecurityContext securityContext = exchange.getSecurityContext();
141
142         return securityContext != null ? securityContext.getMechanismName() : null;
143     }
144
145     @Override
146     public Cookie[] getCookies() {
147         if (cookies == null) {
148             Map<String, io.undertow.server.handlers.Cookie> cookies = exchange.getRequestCookies();
149             if (cookies.isEmpty()) {
150                 return null;
151             }
152             int count = cookies.size();
153             Cookie[] value = new Cookie[count];
154             int i = 0;
155             for (Map.Entry<String, io.undertow.server.handlers.Cookie> entry : cookies.entrySet()) {
156                 io.undertow.server.handlers.Cookie cookie = entry.getValue();
157                 try {
158                     Cookie c = new Cookie(cookie.getName(), cookie.getValue());
159                     if (cookie.getDomain() != null) {
160                         c.setDomain(cookie.getDomain());
161                     }
162                     c.setHttpOnly(cookie.isHttpOnly());
163                     if (cookie.getMaxAge() != null) {
164                         c.setMaxAge(cookie.getMaxAge());
165                     }
166                     if (cookie.getPath() != null) {
167                         c.setPath(cookie.getPath());
168                     }
169                     c.setSecure(cookie.isSecure());
170                     c.setVersion(cookie.getVersion());
171                     value[i++] = c;
172                 } catch (IllegalArgumentException e) {
173                     // Ignore bad cookie
174                 }
175             }
176             if( i < count ) {
177                 Cookie[] shrunkCookies = new Cookie[i];
178                 System.arraycopy(value, 0, shrunkCookies, 0, i);
179                 value = shrunkCookies;
180             }
181             this.cookies = value;
182         }
183         return cookies;
184     }
185
186     @Override
187     public long getDateHeader(final String name) {
188         String header = exchange.getRequestHeaders().getFirst(name);
189         if (header == null) {
190             return -1;
191         }
192         Date date = DateUtils.parseDate(header);
193         if (date == null) {
194             throw UndertowServletMessages.MESSAGES.headerCannotBeConvertedToDate(header);
195         }
196         return date.getTime();
197     }
198
199     @Override
200     public String getHeader(final String name) {
201         HeaderMap headers = exchange.getRequestHeaders();
202         return headers.getFirst(name);
203     }
204
205     public String getHeader(final HttpString name) {
206         HeaderMap headers = exchange.getRequestHeaders();
207         return headers.getFirst(name);
208     }
209
210
211     @Override
212     public Enumeration<String> getHeaders(final String name) {
213         List<String> headers = exchange.getRequestHeaders().get(name);
214         if (headers == null) {
215             return EmptyEnumeration.instance();
216         }
217         return new IteratorEnumeration<>(headers.iterator());
218     }
219
220     @Override
221     public Enumeration<String> getHeaderNames() {
222         final Set<String> headers = new HashSet<>();
223         for (final HttpString i : exchange.getRequestHeaders().getHeaderNames()) {
224             headers.add(i.toString());
225         }
226         return new IteratorEnumeration<>(headers.iterator());
227     }
228
229     @Override
230     public HttpServletMapping getHttpServletMapping() {
231         ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
232         ServletPathMatch match = src.getOriginalServletPathMatch();
233         if(getDispatcherType() == DispatcherType.FORWARD) {
234             match = src.getServletPathMatch();
235         }
236         String matchValue;
237         switch (match.getMappingMatch()) {
238             case EXACT:
239                 matchValue = match.getMatched();
240                 if(matchValue.startsWith("/")) {
241                     matchValue = matchValue.substring(1);
242                 }
243                 break;
244             case DEFAULT:
245             case CONTEXT_ROOT:
246                 matchValue = "";
247                 break;
248             case PATH:
249                 matchValue = match.getRemaining();
250                 if(matchValue.startsWith("/")) {
251                     matchValue = matchValue.substring(1);
252                 }
253                 break;
254             case EXTENSION:
255                 matchValue = match.getMatched().substring(0, match.getMatched().length() - match.getMatchString().length() + 1);
256                 if(matchValue.startsWith("/")) {
257                     matchValue = matchValue.substring(1);
258                 }
259                 break;
260             default:
261                 matchValue = match.getRemaining();
262         }
263         return new MappingImpl(matchValue, match.getMatchString(), match.getMappingMatch(), match.getServletChain().getManagedServlet().getServletInfo().getName());
264     }
265
266     @Override
267     public int getIntHeader(final String name) {
268         String header = getHeader(name);
269         if (header == null) {
270             return -1;
271         }
272         return Integer.parseInt(header);
273     }
274
275     @Override
276     public String getMethod() {
277         return exchange.getRequestMethod().toString();
278     }
279
280     @Override
281     public String getPathInfo() {
282         ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch();
283         if (match != null) {
284             return match.getRemaining();
285         }
286         return null;
287     }
288
289     @Override
290     public String getPathTranslated() {
291         return getRealPath(getPathInfo());
292     }
293
294     @Override
295     public String getContextPath() {
296         return servletContext.getContextPath();
297     }
298
299     @Override
300     public String getQueryString() {
301         return exchange.getQueryString().isEmpty() ? null : exchange.getQueryString();
302     }
303
304     @Override
305     public String getRemoteUser() {
306         Principal userPrincipal = getUserPrincipal();
307
308         return userPrincipal != null ? userPrincipal.getName() : null;
309     }
310
311     @Override
312     public boolean isUserInRole(final String role) {
313         if (role == null) {
314             return false;
315         }
316         //according to the servlet spec this aways returns false
317         if (role.equals("*")) {
318             return false;
319         }
320         SecurityContext sc = exchange.getSecurityContext();
321         Account account = sc.getAuthenticatedAccount();
322         if (account == null) {
323             return false;
324         }
325         ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
326
327         if (role.equals("**")) {
328             Set<String> roles = servletRequestContext.getDeployment().getDeploymentInfo().getSecurityRoles();
329             if (!roles.contains("**")) {
330                 return true;
331             }
332         }
333
334         final ServletChain servlet = servletRequestContext.getCurrentServlet();
335         final Deployment deployment = servletContext.getDeployment();
336         final AuthorizationManager authorizationManager = deployment.getDeploymentInfo().getAuthorizationManager();
337         return authorizationManager.isUserInRole(role, account, servlet.getManagedServlet().getServletInfo(), this, deployment);
338     }
339
340     @Override
341     public Principal getUserPrincipal() {
342         SecurityContext securityContext = exchange.getSecurityContext();
343         Principal result = null;
344         Account account = null;
345         if (securityContext != null && (account = securityContext.getAuthenticatedAccount()) != null) {
346             result = account.getPrincipal();
347         }
348         return result;
349     }
350
351     @Override
352     public String getRequestedSessionId() {
353         Boolean isRequestedSessionIdSaved = exchange.getAttachment(REQUESTED_SESSION_ID_SET);
354         if (isRequestedSessionIdSaved != null && isRequestedSessionIdSaved) {
355             return exchange.getAttachment(REQUESTED_SESSION_ID);
356         }
357         SessionConfig config = originalServletContext.getSessionConfig();
358         if(config instanceof ServletContextImpl.ServletContextSessionConfig) {
359             return ((ServletContextImpl.ServletContextSessionConfig)config).getDelegate().findSessionId(exchange);
360         }
361         return config.findSessionId(exchange);
362     }
363
364     @Override
365     public String changeSessionId() {
366         HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false);
367         if (session == null) {
368             throw UndertowServletMessages.MESSAGES.noSession();
369         }
370         String oldId = session.getId();
371         Session underlyingSession;
372         if(System.getSecurityManager() == null) {
373             underlyingSession = session.getSession();
374         } else {
375             underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session));
376         }
377         String newId = underlyingSession.changeSessionId(exchange, originalServletContext.getSessionConfig());
378         servletContext.getDeployment().getApplicationListeners().httpSessionIdChanged(session, oldId);
379         return newId;
380     }
381
382     @Override
383     public String getRequestURI() {
384         //we need the non-decoded string, which means we need to use exchange.getRequestURI()
385         if(exchange.isHostIncludedInRequestURI()) {
386             //we need to strip out the host part
387             String uri = exchange.getRequestURI();
388             int slashes =0;
389             for(int i = 0; i < uri.length(); ++i) {
390                 if(uri.charAt(i) == '/') {
391                     if(++slashes == 3) {
392                         return uri.substring(i);
393                     }
394                 }
395             }
396             return "/";
397         } else {
398             return exchange.getRequestURI();
399         }
400     }
401
402     @Override
403     public StringBuffer getRequestURL() {
404         return new StringBuffer(exchange.getRequestURL());
405     }
406
407     @Override
408     public String getServletPath() {
409         ServletPathMatch match = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getServletPathMatch();
410         if (match != null) {
411             return match.getMatched();
412         }
413         return "";
414     }
415
416     @Override
417     public HttpSession getSession(final boolean create) {
418         return servletContext.getSession(originalServletContext, exchange, create);
419     }
420
421     @Override
422     public HttpSession getSession() {
423         return getSession(true);
424     }
425
426
427     @Override
428     public boolean isRequestedSessionIdValid() {
429         HttpSessionImpl session = servletContext.getSession(originalServletContext, exchange, false);
430         if(session == null) {
431             return false;
432         }
433         if(session.isInvalid()) {
434             return false;
435         }
436         return session.getId().equals(getRequestedSessionId());
437     }
438
439     @Override
440     public boolean isRequestedSessionIdFromCookie() {
441         return sessionCookieSource() == SessionConfig.SessionCookieSource.COOKIE;
442     }
443
444     @Override
445     public boolean isRequestedSessionIdFromURL() {
446         return sessionCookieSource() == SessionConfig.SessionCookieSource.URL;
447     }
448
449     @Override
450     public boolean isRequestedSessionIdFromUrl() {
451         return isRequestedSessionIdFromURL();
452     }
453
454     @Override
455     public boolean authenticate(final HttpServletResponse response) throws IOException, ServletException {
456         if (response.isCommitted()) {
457             throw UndertowServletMessages.MESSAGES.responseAlreadyCommited();
458         }
459
460         SecurityContext sc = exchange.getSecurityContext();
461         sc.setAuthenticationRequired();
462         // TODO: this will set the status code and headers without going through any potential
463         // wrappers, is this a problem?
464         if (sc.authenticate()) {
465             if (sc.isAuthenticated()) {
466                 return true;
467             } else {
468                 throw UndertowServletMessages.MESSAGES.authenticationFailed();
469             }
470         } else {
471             if(!exchange.isResponseStarted() && exchange.getStatusCode() == 200) {
472                 throw UndertowServletMessages.MESSAGES.authenticationFailed();
473             } else {
474                 return false;
475             }
476         }
477     }
478
479     @Override
480     public void login(final String username, final String password) throws ServletException {
481         if (username == null || password == null) {
482             throw UndertowServletMessages.MESSAGES.loginFailed();
483         }
484         SecurityContext sc = exchange.getSecurityContext();
485         if (sc.isAuthenticated()) {
486             throw UndertowServletMessages.MESSAGES.userAlreadyLoggedIn();
487         }
488         boolean login = false;
489         try {
490             login = sc.login(username, password);
491         }
492         catch (SecurityException se) {
493             if (se.getCause() instanceof ServletException)
494                 throw (ServletException) se.getCause();
495             throw new ServletException(se);
496         }
497         if (!login) {
498             throw UndertowServletMessages.MESSAGES.loginFailed();
499         }
500     }
501
502     @Override
503     public void logout() throws ServletException {
504         SecurityContext sc = exchange.getSecurityContext();
505         sc.logout();
506         if(servletContext.getDeployment().getDeploymentInfo().isInvalidateSessionOnLogout()) {
507             HttpSession session = getSession(false);
508             if(session != null) {
509                 session.invalidate();
510             }
511         }
512     }
513
514     @Override
515     public Collection<Part> getParts() throws IOException, ServletException {
516         verifyMultipartServlet();
517         if (parts == null) {
518             loadParts();
519         }
520         return parts;
521     }
522
523     private void verifyMultipartServlet() {
524         ServletRequestContext src = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
525         MultipartConfigElement multipart = src.getServletPathMatch().getServletChain().getManagedServlet().getMultipartConfig();
526         if(multipart == null) {
527             throw UndertowServletMessages.MESSAGES.multipartConfigNotPresent();
528         }
529     }
530
531     @Override
532     public Part getPart(final String name) throws IOException, ServletException {
533         verifyMultipartServlet();
534         if (parts == null) {
535             loadParts();
536         }
537         for (Part part : parts) {
538             if (part.getName().equals(name)) {
539                 return part;
540             }
541         }
542         return null;
543     }
544
545     @Override
546     public <T extends HttpUpgradeHandler> T upgrade(final Class<T> handlerClass) throws IOException {
547         try {
548             InstanceFactory<T> factory = servletContext.getDeployment().getDeploymentInfo().getClassIntrospecter().createInstanceFactory(handlerClass);
549             final InstanceHandle<T> instance = factory.createInstance();
550             exchange.upgradeChannel(new ServletUpgradeListener<>(instance, servletContext.getDeployment(), exchange));
551             return instance.getInstance();
552         } catch (InstantiationException e) {
553             throw new RuntimeException(e);
554         } catch (NoSuchMethodException e) {
555             throw new RuntimeException(e);
556         }
557     }
558
559     private void loadParts() throws IOException, ServletException {
560         final ServletRequestContext requestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
561
562         if (parts == null) {
563             final List<Part> parts = new ArrayList<>();
564             String mimeType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
565             if (mimeType != null && mimeType.startsWith(MultiPartParserDefinition.MULTIPART_FORM_DATA)) {
566
567                 FormData formData = parseFormData();
568                 if(formData != null) {
569                     for (final String namedPart : formData) {
570                         for (FormData.FormValue part : formData.get(namedPart)) {
571                             parts.add(new PartImpl(namedPart,
572                                     part,
573                                     requestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getMultipartConfig(),
574                                     servletContext, this));
575                         }
576                     }
577                 }
578             } else {
579                 throw UndertowServletMessages.MESSAGES.notAMultiPartRequest();
580             }
581             this.parts = parts;
582         }
583     }
584
585     @Override
586     public Object getAttribute(final String name) {
587         if (attributes == null) {
588             return null;
589         }
590         return attributes.get(name);
591     }
592
593     @Override
594     public Enumeration<String> getAttributeNames() {
595         if (attributes == null) {
596             return EmptyEnumeration.instance();
597         }
598         return new IteratorEnumeration<>(attributes.keySet().iterator());
599     }
600
601     @Override
602     public String getCharacterEncoding() {
603         if (characterEncoding != null) {
604             return characterEncoding.name();
605         }
606
607         String characterEncodingFromHeader = getCharacterEncodingFromHeader();
608         if (characterEncodingFromHeader != null) {
609             return characterEncodingFromHeader;
610         }
611         // first check, web-app context level default request encoding
612         if (servletContext.getDeployment().getDeploymentInfo().getDefaultRequestEncoding() != null) {
613             return servletContext.getDeployment().getDeploymentInfo().getDefaultRequestEncoding();
614         }
615         // now check the container level default encoding
616         if (servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding() != null) {
617             return servletContext.getDeployment().getDeploymentInfo().getDefaultEncoding();
618         }
619         return null;
620     }
621
622     private String getCharacterEncodingFromHeader() {
623         String contentType = exchange.getRequestHeaders().getFirst(Headers.CONTENT_TYPE);
624         if (contentType == null) {
625             return null;
626         }
627
628         return Headers.extractQuotedValueFromHeader(contentType, "charset");
629     }
630
631     @Override
632     public void setCharacterEncoding(final String env) throws UnsupportedEncodingException {
633         if (readStarted) {
634             return;
635         }
636         try {
637             characterEncoding = Charset.forName(env);
638
639             final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getOriginalServletPathMatch().getServletChain().getManagedServlet();
640             final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange);
641             if (parser != null) {
642                 parser.setCharacterEncoding(env);
643             }
644         } catch (UnsupportedCharsetException e) {
645             throw new UnsupportedEncodingException();
646         }
647     }
648
649     @Override
650     public int getContentLength() {
651         long length = getContentLengthLong();
652         if(length > Integer.MAX_VALUE) {
653             return -1;
654         }
655         return (int)length;
656     }
657
658     @Override
659     public long getContentLengthLong() {
660         final String contentLength = getHeader(Headers.CONTENT_LENGTH);
661         if (contentLength == null || contentLength.isEmpty()) {
662             return -1;
663         }
664         return Long.parseLong(contentLength);
665     }
666
667     @Override
668     public String getContentType() {
669         return getHeader(Headers.CONTENT_TYPE);
670     }
671
672     @Override
673     public ServletInputStream getInputStream() throws IOException {
674         if (reader != null) {
675             throw UndertowServletMessages.MESSAGES.getReaderAlreadyCalled();
676         }
677         if(servletInputStream == null) {
678             servletInputStream = new ServletInputStreamImpl(this);
679         }
680         readStarted = true;
681         return servletInputStream;
682     }
683
684     public void closeAndDrainRequest() throws IOException {
685         try {
686             if (reader != null) {
687                 reader.close();
688             }
689             if (servletInputStream == null) {
690                 servletInputStream = new ServletInputStreamImpl(this);
691             }
692             servletInputStream.close();
693         } finally {
694             clearAttributes();
695         }
696     }
697
698     /**
699      * Frees any resources (namely buffers) that may be associated with this request.
700      *
701      */

702     public void freeResources() throws IOException {
703         try {
704             if(reader != null) {
705                 reader.close();
706             }
707             if(servletInputStream != null) {
708                 servletInputStream.close();
709             }
710         } finally {
711             clearAttributes();
712         }
713     }
714
715     @Override
716     public String getParameter(final String name) {
717         if(queryParameters == null) {
718             queryParameters = exchange.getQueryParameters();
719         }
720         Deque<String> params = queryParameters.get(name);
721         if (params == null) {
722             final FormData parsedFormData = parseFormData();
723             if (parsedFormData != null) {
724                 FormData.FormValue res = parsedFormData.getFirst(name);
725                 if (res == null || res.isFileItem()) {
726                     return null;
727                 } else {
728                     return res.getValue();
729                 }
730             }
731             return null;
732         }
733         return params.getFirst();
734     }
735
736     @Override
737     public Enumeration<String> getParameterNames() {
738         if (queryParameters == null) {
739             queryParameters = exchange.getQueryParameters();
740         }
741         final Set<String> parameterNames = new HashSet<>(queryParameters.keySet());
742         final FormData parsedFormData = parseFormData();
743         if (parsedFormData != null) {
744             Iterator<String> it = parsedFormData.iterator();
745             while (it.hasNext()) {
746                 String name = it.next();
747                 for(FormData.FormValue param : parsedFormData.get(name)) {
748                     if(!param.isFileItem()) {
749                         parameterNames.add(name);
750                         break;
751                     }
752                 }
753             }
754         }
755         return new IteratorEnumeration<>(parameterNames.iterator());
756     }
757
758     @Override
759     public String[] getParameterValues(final String name) {
760         if (queryParameters == null) {
761             queryParameters = exchange.getQueryParameters();
762         }
763         final List<String> ret = new ArrayList<>();
764         Deque<String> params = queryParameters.get(name);
765         if (params != null) {
766             for (String param : params) {
767                 ret.add(param);
768             }
769         }
770         final FormData parsedFormData = parseFormData();
771         if (parsedFormData != null) {
772             Deque<FormData.FormValue> res = parsedFormData.get(name);
773             if (res != null) {
774                 for (FormData.FormValue value : res) {
775                     if(!value.isFileItem()) {
776                         ret.add(value.getValue());
777                     }
778                 }
779             }
780         }
781         if (ret.isEmpty()) {
782             return null;
783         }
784         return ret.toArray(new String[ret.size()]);
785     }
786
787     @Override
788     public Map<String, String[]> getParameterMap() {
789         if (queryParameters == null) {
790             queryParameters = exchange.getQueryParameters();
791         }
792         final Map<String, ArrayList<String>> arrayMap = new HashMap<>();
793         for (Map.Entry<String, Deque<String>> entry : queryParameters.entrySet()) {
794             arrayMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
795         }
796
797         final FormData parsedFormData = parseFormData();
798         if (parsedFormData != null) {
799             Iterator<String> it = parsedFormData.iterator();
800             while (it.hasNext()) {
801                 final String name = it.next();
802                 Deque<FormData.FormValue> val = parsedFormData.get(name);
803                 if (arrayMap.containsKey(name)) {
804                     ArrayList<String> existing = arrayMap.get(name);
805                     for (final FormData.FormValue v : val) {
806                         if(!v.isFileItem()) {
807                             existing.add(v.getValue());
808                         }
809                     }
810                 } else {
811                     final ArrayList<String> values = new ArrayList<>();
812                     for (final FormData.FormValue v : val) {
813                         if(!v.isFileItem()) {
814                             values.add(v.getValue());
815                         }
816                     }
817                     if (!values.isEmpty()) {
818                         arrayMap.put(name, values);
819                     }
820                 }
821             }
822         }
823         final Map<String, String[]> ret = new HashMap<>();
824         for(Map.Entry<String, ArrayList<String>> entry : arrayMap.entrySet()) {
825             ret.put(entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]));
826         }
827         return ret;
828     }
829
830     private FormData parseFormData() {
831         if(formParsingException != null) {
832             throw formParsingException;
833         }
834         if (parsedFormData == null) {
835             if (readStarted) {
836                 return null;
837             }
838             final ManagedServlet originalServlet = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getCurrentServlet().getManagedServlet();
839             final FormDataParser parser = originalServlet.getFormParserFactory().createParser(exchange);
840             if (parser == null) {
841                 return null;
842             }
843             readStarted = true;
844             try {
845                 return parsedFormData = parser.parseBlocking();
846             } catch (RequestTooBigException | MultiPartParserDefinition.FileTooLargeException e) {
847                 throw formParsingException = new IllegalStateException(e);
848             } catch (RuntimeException e) {
849                 throw formParsingException = e;
850             } catch (IOException e) {
851                 throw formParsingException = new RuntimeException(e);
852             }
853         }
854         return parsedFormData;
855     }
856
857     @Override
858     public String getProtocol() {
859         return exchange.getProtocol().toString();
860     }
861
862     @Override
863     public String getScheme() {
864         return exchange.getRequestScheme();
865     }
866
867     @Override
868     public String getServerName() {
869         return exchange.getHostName();
870     }
871
872     @Override
873     public int getServerPort() {
874         return exchange.getHostPort();
875     }
876
877     @Override
878     public BufferedReader getReader() throws IOException {
879         if (reader == null) {
880             if (servletInputStream != null) {
881                 throw UndertowServletMessages.MESSAGES.getInputStreamAlreadyCalled();
882             }
883             Charset charSet = null;
884             if (this.characterEncoding != null) {
885                 charSet = this.characterEncoding;
886             } else {
887                 final String c = getCharacterEncoding();
888                 if (c != null) {
889                     try {
890                         charSet = Charset.forName(c);
891                     } catch (UnsupportedCharsetException e) {
892                         throw new UnsupportedEncodingException(e.getMessage());
893                     }
894                 }
895             }
896
897             reader = new BufferedReader(charSet == null ? new InputStreamReader(exchange.getInputStream(), StandardCharsets.ISO_8859_1)
898                     : new InputStreamReader(exchange.getInputStream(), charSet));
899         }
900         readStarted = true;
901         return reader;
902     }
903
904     @Override
905     public String getRemoteAddr() {
906         InetSocketAddress sourceAddress = exchange.getSourceAddress();
907         if(sourceAddress == null) {
908             return "";
909         }
910         InetAddress address = sourceAddress.getAddress();
911         if(address == null) {
912             //this is unresolved, so we just return the host name
913             //not exactly spec, but if the name should be resolved then a PeerNameResolvingHandler should be used
914             //and this is probably better than just returning null
915             return sourceAddress.getHostString();
916         }
917         return address.getHostAddress();
918     }
919
920     @Override
921     public String getRemoteHost() {
922         InetSocketAddress sourceAddress = exchange.getSourceAddress();
923         if(sourceAddress == null) {
924             return "";
925         }
926         return sourceAddress.getHostString();
927     }
928
929     @Override
930     public void setAttribute(final String name, final Object object) {
931         if(object == null) {
932             removeAttribute(name);
933             return;
934         }
935         if (attributes == null) {
936             attributes = new HashMap<>();
937         }
938         Object existing = attributes.put(name, object);
939         if (existing != null) {
940             servletContext.getDeployment().getApplicationListeners().servletRequestAttributeReplaced(this, name, existing);
941         } else {
942             servletContext.getDeployment().getApplicationListeners().servletRequestAttributeAdded(this, name, object);
943         }
944     }
945
946     @Override
947     public void removeAttribute(final String name) {
948         if (attributes == null) {
949             return;
950         }
951         Object exiting = attributes.remove(name);
952         servletContext.getDeployment().getApplicationListeners().servletRequestAttributeRemoved(this, name, exiting);
953     }
954
955     @Override
956     public Locale getLocale() {
957         return getLocales().nextElement();
958     }
959
960     @Override
961     public Enumeration<Locale> getLocales() {
962         final List<String> acceptLanguage = exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE);
963         List<Locale> ret = LocaleUtils.getLocalesFromHeader(acceptLanguage);
964         if(ret.isEmpty()) {
965             return new IteratorEnumeration<>(Collections.singletonList(Locale.getDefault()).iterator());
966         }
967         return new IteratorEnumeration<>(ret.iterator());
968     }
969
970     @Override
971     public boolean isSecure() {
972         return exchange.isSecure();
973     }
974
975     @Override
976     public RequestDispatcher getRequestDispatcher(final String path) {
977         String realPath;
978         if (path.startsWith("/")) {
979             realPath = path;
980         } else {
981             String current = exchange.getRelativePath();
982             int lastSlash = current.lastIndexOf("/");
983             if (lastSlash != -1) {
984                 current = current.substring(0, lastSlash + 1);
985             }
986             realPath = CanonicalPathUtils.canonicalize(current + path);
987         }
988         return new RequestDispatcherImpl(realPath, servletContext);
989     }
990
991     @Override
992     public String getRealPath(final String path) {
993         return servletContext.getRealPath(path);
994     }
995
996     @Override
997     public int getRemotePort() {
998         return exchange.getSourceAddress().getPort();
999     }
1000
1001     /**
1002      * String java.net.InetAddress.getHostName()
1003      * Gets the host name for this IP address.
1004      * If this InetAddress was created with a host name, this host name will be remembered and returned; otherwise, a reverse name lookup will be performed and the result will be returned based on the system configured name lookup service. If a lookup of the name service is required, call getCanonicalHostName.
1005      * If there is a security manager, its checkConnect method is first called with the hostname and -1 as its arguments to see if the operation is allowed. If the operation is not allowed, it will return the textual representation of the IP address.
1006      * @see InetAddres#getHostName
1007      */

1008     @Override
1009     public String getLocalName() {
1010         return exchange.getDestinationAddress().getHostName();
1011     }
1012
1013     @Override
1014     public String getLocalAddr() {
1015         InetSocketAddress destinationAddress = exchange.getDestinationAddress();
1016         if (destinationAddress == null) {
1017             return "";
1018         }
1019         InetAddress address = destinationAddress.getAddress();
1020         if (address == null) {
1021             //this is unresolved, so we just return the host name
1022             return destinationAddress.getHostString();
1023         }
1024         return address.getHostAddress();
1025     }
1026
1027     @Override
1028     public int getLocalPort() {
1029         return exchange.getDestinationAddress().getPort();
1030     }
1031
1032     @Override
1033     public ServletContextImpl getServletContext() {
1034         return servletContext;
1035     }
1036
1037     @Override
1038     public AsyncContext startAsync() throws IllegalStateException {
1039         if (!isAsyncSupported()) {
1040             throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed();
1041         } else if (asyncStarted) {
1042             throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted();
1043         }
1044         asyncStarted = true;
1045         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
1046         return asyncContext = new AsyncContextImpl(exchange, servletRequestContext.getServletRequest(), servletRequestContext.getServletResponse(), servletRequestContext, false, asyncContext);
1047     }
1048
1049     @Override
1050     public AsyncContext startAsync(final ServletRequest servletRequest, final ServletResponse servletResponse) throws IllegalStateException {
1051         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
1052         if (!servletContext.getDeployment().getDeploymentInfo().isAllowNonStandardWrappers()) {
1053             if (servletRequestContext.getOriginalRequest() != servletRequest) {
1054                 if (!(servletRequest instanceof ServletRequestWrapper)) {
1055                     throw UndertowServletMessages.MESSAGES.requestWasNotOriginalOrWrapper(servletRequest);
1056                 }
1057             }
1058             if (servletRequestContext.getOriginalResponse() != servletResponse) {
1059                 if (!(servletResponse instanceof ServletResponseWrapper)) {
1060                     throw UndertowServletMessages.MESSAGES.responseWasNotOriginalOrWrapper(servletResponse);
1061                 }
1062             }
1063         }
1064         if (!isAsyncSupported()) {
1065             throw UndertowServletMessages.MESSAGES.startAsyncNotAllowed();
1066         } else if (asyncStarted) {
1067             throw UndertowServletMessages.MESSAGES.asyncAlreadyStarted();
1068         }
1069         asyncStarted = true;
1070         servletRequestContext.setServletRequest(servletRequest);
1071         servletRequestContext.setServletResponse(servletResponse);
1072         return asyncContext = new AsyncContextImpl(exchange, servletRequest, servletResponse, servletRequestContext, true, asyncContext);
1073     }
1074
1075     @Override
1076     public boolean isAsyncStarted() {
1077         return asyncStarted;
1078     }
1079
1080     @Override
1081     public boolean isAsyncSupported() {
1082         return exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).isAsyncSupported();
1083     }
1084
1085     @Override
1086     public AsyncContextImpl getAsyncContext() {
1087         if (!isAsyncStarted()) {
1088             throw UndertowServletMessages.MESSAGES.asyncNotStarted();
1089         }
1090         return asyncContext;
1091     }
1092
1093     public AsyncContextImpl getAsyncContextInternal() {
1094         return asyncContext;
1095     }
1096
1097     @Override
1098     public DispatcherType getDispatcherType() {
1099         return exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY).getDispatcherType();
1100     }
1101
1102
1103     public Map<String, Deque<String>> getQueryParameters() {
1104         if (queryParameters == null) {
1105             queryParameters = exchange.getQueryParameters();
1106         }
1107         return queryParameters;
1108     }
1109
1110     public void setQueryParameters(final Map<String, Deque<String>> queryParameters) {
1111         this.queryParameters = queryParameters;
1112     }
1113
1114     public void setServletContext(final ServletContextImpl servletContext) {
1115         this.servletContext = servletContext;
1116     }
1117
1118     void asyncRequestDispatched() {
1119         asyncStarted = false;
1120     }
1121
1122     public String getOriginalRequestURI() {
1123         String uri = (String) getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
1124         if(uri != null) {
1125             return uri;
1126         }
1127         uri = (String) getAttribute(AsyncContext.ASYNC_REQUEST_URI);
1128         if(uri != null) {
1129             return uri;
1130         }
1131         return getRequestURI();
1132     }
1133
1134
1135     public String getOriginalServletPath() {
1136         String uri = (String) getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH);
1137         if(uri != null) {
1138             return uri;
1139         }
1140         uri = (String) getAttribute(AsyncContext.ASYNC_SERVLET_PATH);
1141         if(uri != null) {
1142             return uri;
1143         }
1144         return getServletPath();
1145     }
1146
1147     public String getOriginalPathInfo() {
1148         String uri = (String) getAttribute(RequestDispatcher.FORWARD_PATH_INFO);
1149         if(uri != null) {
1150             return uri;
1151         }
1152         uri = (String) getAttribute(AsyncContext.ASYNC_PATH_INFO);
1153         if(uri != null) {
1154             return uri;
1155         }
1156         return getPathInfo();
1157     }
1158
1159     public String getOriginalContextPath() {
1160         String uri = (String) getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH);
1161         if(uri != null) {
1162             return uri;
1163         }
1164         uri = (String) getAttribute(AsyncContext.ASYNC_CONTEXT_PATH);
1165         if(uri != null) {
1166             return uri;
1167         }
1168         return getContextPath();
1169     }
1170
1171     public String getOriginalQueryString() {
1172         String uri = (String) getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
1173         if(uri != null) {
1174             return uri;
1175         }
1176         uri = (String) getAttribute(AsyncContext.ASYNC_QUERY_STRING);
1177         if(uri != null) {
1178             return uri;
1179         }
1180         return getQueryString();
1181     }
1182
1183     private SessionConfig.SessionCookieSource sessionCookieSource() {
1184         HttpSession session = getSession(false);
1185         if(session == null) {
1186             return SessionConfig.SessionCookieSource.NONE;
1187         }
1188         if(sessionCookieSource == null) {
1189             sessionCookieSource = originalServletContext.getSessionConfig().sessionCookieSource(exchange);
1190         }
1191         return sessionCookieSource;
1192     }
1193
1194     @Override
1195     public String toString() {
1196         return "HttpServletRequestImpl [ " + getMethod() + ' ' + getRequestURI() + " ]";
1197     }
1198
1199     public void clearAttributes() {
1200         if(attributes != null) {
1201             this.attributes.clear();
1202         }
1203     }
1204
1205     @Override
1206     public PushBuilder newPushBuilder() {
1207         if(exchange.getConnection().isPushSupported()) {
1208             return new PushBuilderImpl(this);
1209         }
1210         return null;
1211     }
1212
1213     @Override
1214     public Map<String, String> getTrailerFields() {
1215         HeaderMap trailers = exchange.getAttachment(HttpAttachments.REQUEST_TRAILERS);
1216         if(trailers == null) {
1217             return Collections.emptyMap();
1218         }
1219         Map<String, String> ret = new HashMap<>();
1220         for(HeaderValues entry : trailers) {
1221             ret.put(entry.getHeaderName().toString().toLowerCase(Locale.ENGLISH), entry.getFirst());
1222         }
1223         return ret;
1224     }
1225
1226     @Override
1227     public boolean isTrailerFieldsReady() {
1228         if(exchange.isRequestComplete()) {
1229             return true;
1230         }
1231         return !exchange.getConnection().isRequestTrailerFieldsSupported();
1232     }
1233 }
1234