1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.catalina.connector;
18
19 import java.io.BufferedReader;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.StringReader;
24 import java.io.UnsupportedEncodingException;
25 import java.nio.charset.Charset;
26 import java.nio.charset.StandardCharsets;
27 import java.security.Principal;
28 import java.text.SimpleDateFormat;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.Enumeration;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TimeZone;
40 import java.util.TreeMap;
41 import java.util.concurrent.ConcurrentHashMap;
42 import java.util.concurrent.atomic.AtomicBoolean;
43
44 import javax.naming.NamingException;
45 import javax.security.auth.Subject;
46 import javax.servlet.AsyncContext;
47 import javax.servlet.DispatcherType;
48 import javax.servlet.FilterChain;
49 import javax.servlet.MultipartConfigElement;
50 import javax.servlet.RequestDispatcher;
51 import javax.servlet.ServletContext;
52 import javax.servlet.ServletException;
53 import javax.servlet.ServletInputStream;
54 import javax.servlet.ServletRequest;
55 import javax.servlet.ServletRequestAttributeEvent;
56 import javax.servlet.ServletRequestAttributeListener;
57 import javax.servlet.ServletResponse;
58 import javax.servlet.SessionTrackingMode;
59 import javax.servlet.http.Cookie;
60 import javax.servlet.http.HttpServletMapping;
61 import javax.servlet.http.HttpServletRequest;
62 import javax.servlet.http.HttpServletRequestWrapper;
63 import javax.servlet.http.HttpServletResponse;
64 import javax.servlet.http.HttpSession;
65 import javax.servlet.http.HttpUpgradeHandler;
66 import javax.servlet.http.Part;
67 import javax.servlet.http.PushBuilder;
68
69 import org.apache.catalina.Container;
70 import org.apache.catalina.Context;
71 import org.apache.catalina.Globals;
72 import org.apache.catalina.Host;
73 import org.apache.catalina.Manager;
74 import org.apache.catalina.Realm;
75 import org.apache.catalina.Session;
76 import org.apache.catalina.TomcatPrincipal;
77 import org.apache.catalina.Wrapper;
78 import org.apache.catalina.core.ApplicationFilterChain;
79 import org.apache.catalina.core.ApplicationMapping;
80 import org.apache.catalina.core.ApplicationPart;
81 import org.apache.catalina.core.ApplicationPushBuilder;
82 import org.apache.catalina.core.ApplicationSessionCookieConfig;
83 import org.apache.catalina.core.AsyncContextImpl;
84 import org.apache.catalina.mapper.MappingData;
85 import org.apache.catalina.util.ParameterMap;
86 import org.apache.catalina.util.RequestUtil;
87 import org.apache.catalina.util.TLSUtil;
88 import org.apache.catalina.util.URLEncoder;
89 import org.apache.coyote.ActionCode;
90 import org.apache.coyote.UpgradeToken;
91 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
92 import org.apache.juli.logging.Log;
93 import org.apache.juli.logging.LogFactory;
94 import org.apache.tomcat.InstanceManager;
95 import org.apache.tomcat.util.ExceptionUtils;
96 import org.apache.tomcat.util.buf.B2CConverter;
97 import org.apache.tomcat.util.buf.ByteChunk;
98 import org.apache.tomcat.util.buf.MessageBytes;
99 import org.apache.tomcat.util.buf.StringUtils;
100 import org.apache.tomcat.util.buf.UDecoder;
101 import org.apache.tomcat.util.http.CookieProcessor;
102 import org.apache.tomcat.util.http.FastHttpDateFormat;
103 import org.apache.tomcat.util.http.Parameters;
104 import org.apache.tomcat.util.http.Parameters.FailReason;
105 import org.apache.tomcat.util.http.ServerCookie;
106 import org.apache.tomcat.util.http.ServerCookies;
107 import org.apache.tomcat.util.http.fileupload.FileItem;
108 import org.apache.tomcat.util.http.fileupload.FileUploadException;
109 import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory;
110 import org.apache.tomcat.util.http.fileupload.impl.InvalidContentTypeException;
111 import org.apache.tomcat.util.http.fileupload.impl.SizeException;
112 import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
113 import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext;
114 import org.apache.tomcat.util.http.parser.AcceptLanguage;
115 import org.apache.tomcat.util.net.SSLSupport;
116 import org.apache.tomcat.util.res.StringManager;
117 import org.ietf.jgss.GSSCredential;
118 import org.ietf.jgss.GSSException;
119
120 /**
121  * Wrapper object for the Coyote request.
122  *
123  * @author Remy Maucherat
124  * @author Craig R. McClanahan
125  */

126 public class Request implements HttpServletRequest {
127
128     private static final Log log = LogFactory.getLog(Request.class);
129
130     /**
131      * Create a new Request object associated with the given Connector.
132      *
133      * @param connector The Connector with which this Request object will always
134      *                  be associated. In normal usage this must be non-null. In
135      *                  some test scenarios, it may be possible to use a null
136      *                  Connector without triggering an NPE.
137      */

138     public Request(Connector connector) {
139         this.connector = connector;
140
141         formats = new SimpleDateFormat[formatsTemplate.length];
142         for(int i = 0; i < formats.length; i++) {
143             formats[i] = (SimpleDateFormat) formatsTemplate[i].clone();
144         }
145     }
146
147
148     // ------------------------------------------------------------- Properties
149
150
151     /**
152      * Coyote request.
153      */

154     protected org.apache.coyote.Request coyoteRequest;
155
156     /**
157      * Set the Coyote request.
158      *
159      * @param coyoteRequest The Coyote request
160      */

161     public void setCoyoteRequest(org.apache.coyote.Request coyoteRequest) {
162         this.coyoteRequest = coyoteRequest;
163         inputBuffer.setRequest(coyoteRequest);
164     }
165
166     /**
167      * Get the Coyote request.
168      *
169      * @return the Coyote request object
170      */

171     public org.apache.coyote.Request getCoyoteRequest() {
172         return this.coyoteRequest;
173     }
174
175
176     // ----------------------------------------------------- Variables
177
178     /**
179      * @deprecated Unused. This will be removed in Tomcat 10.
180      */

181     @Deprecated
182     protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
183
184
185     /**
186      * The string manager for this package.
187      */

188     protected static final StringManager sm = StringManager.getManager(Request.class);
189
190
191     /**
192      * The set of cookies associated with this Request.
193      */

194     protected Cookie[] cookies = null;
195
196
197     /**
198      * The set of SimpleDateFormat formats to use in getDateHeader().
199      *
200      * Notice that because SimpleDateFormat is not thread-safe, we can't
201      * declare formats[] as a static variable.
202      *
203      * @deprecated Unused. This will be removed in Tomcat 10
204      */

205     @Deprecated
206     protected final SimpleDateFormat formats[];
207
208     @Deprecated
209     private static final SimpleDateFormat formatsTemplate[] = {
210         new SimpleDateFormat(FastHttpDateFormat.RFC1123_DATE, Locale.US),
211         new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
212         new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
213     };
214
215
216     /**
217      * The default Locale if none are specified.
218      */

219     protected static final Locale defaultLocale = Locale.getDefault();
220
221
222     /**
223      * The attributes associated with this Request, keyed by attribute name.
224      */

225     private final Map<String, Object> attributes = new ConcurrentHashMap<>();
226
227
228     /**
229      * Flag that indicates if SSL attributes have been parsed to improve
230      * performance for applications (usually frameworks) that make multiple
231      * calls to {@link Request#getAttributeNames()}.
232      */

233     protected boolean sslAttributesParsed = false;
234
235
236     /**
237      * The preferred Locales associated with this Request.
238      */

239     protected final ArrayList<Locale> locales = new ArrayList<>();
240
241
242     /**
243      * Internal notes associated with this request by Catalina components
244      * and event listeners.
245      */

246     private final transient HashMap<String, Object> notes = new HashMap<>();
247
248
249     /**
250      * Authentication type.
251      */

252     protected String authType = null;
253
254
255     /**
256      * The current dispatcher type.
257      */

258     protected DispatcherType internalDispatcherType = null;
259
260
261     /**
262      * The associated input buffer.
263      */

264     protected final InputBuffer inputBuffer = new InputBuffer();
265
266
267     /**
268      * ServletInputStream.
269      */

270     protected CoyoteInputStream inputStream =
271             new CoyoteInputStream(inputBuffer);
272
273
274     /**
275      * Reader.
276      */

277     protected CoyoteReader reader = new CoyoteReader(inputBuffer);
278
279
280     /**
281      * Using stream flag.
282      */

283     protected boolean usingInputStream = false;
284
285
286     /**
287      * Using reader flag.
288      */

289     protected boolean usingReader = false;
290
291
292     /**
293      * User principal.
294      */

295     protected Principal userPrincipal = null;
296
297
298     /**
299      * Request parameters parsed flag.
300      */

301     protected boolean parametersParsed = false;
302
303
304     /**
305      * Cookie headers parsed flag. Indicates that the cookie headers have been
306      * parsed into ServerCookies.
307      */

308     protected boolean cookiesParsed = false;
309
310
311     /**
312      * Cookie parsed flag. Indicates that the ServerCookies have been converted
313      * into user facing Cookie objects.
314      */

315     protected boolean cookiesConverted = false;
316
317
318     /**
319      * Secure flag.
320      */

321     protected boolean secure = false;
322
323
324     /**
325      * The Subject associated with the current AccessControlContext
326      */

327     protected transient Subject subject = null;
328
329
330     /**
331      * Post data buffer.
332      */

333     protected static final int CACHED_POST_LEN = 8192;
334     protected byte[] postData = null;
335
336
337     /**
338      * Hash map used in the getParametersMap method.
339      */

340     protected ParameterMap<String, String[]> parameterMap = new ParameterMap<>();
341
342
343     /**
344      * The parts, if any, uploaded with this request.
345      */

346     protected Collection<Part> parts = null;
347
348
349     /**
350      * The exception thrown, if any when parsing the parts.
351      */

352     protected Exception partsParseException = null;
353
354
355     /**
356      * The currently active session for this request.
357      */

358     protected Session session = null;
359
360
361     /**
362      * The current request dispatcher path.
363      */

364     protected Object requestDispatcherPath = null;
365
366
367     /**
368      * Was the requested session ID received in a cookie?
369      */

370     protected boolean requestedSessionCookie = false;
371
372
373     /**
374      * The requested session ID (if any) for this request.
375      */

376     protected String requestedSessionId = null;
377
378
379     /**
380      * Was the requested session ID received in a URL?
381      */

382     protected boolean requestedSessionURL = false;
383
384
385     /**
386      * Was the requested session ID obtained from the SSL session?
387      */

388     protected boolean requestedSessionSSL = false;
389
390
391     /**
392      * Parse locales.
393      */

394     protected boolean localesParsed = false;
395
396
397     /**
398      * Local port
399      */

400     protected int localPort = -1;
401
402     /**
403      * Remote address.
404      */

405     protected String remoteAddr = null;
406
407
408     /**
409      * Remote host.
410      */

411     protected String remoteHost = null;
412
413
414     /**
415      * Remote port
416      */

417     protected int remotePort = -1;
418
419     /**
420      * Local address
421      */

422     protected String localAddr = null;
423
424
425     /**
426      * Local address
427      */

428     protected String localName = null;
429
430     /**
431      * AsyncContext
432      */

433     private volatile AsyncContextImpl asyncContext = null;
434
435     protected Boolean asyncSupported = null;
436
437     private HttpServletRequest applicationRequest = null;
438
439
440     // --------------------------------------------------------- Public Methods
441
442     protected void addPathParameter(String name, String value) {
443         coyoteRequest.addPathParameter(name, value);
444     }
445
446     protected String getPathParameter(String name) {
447         return coyoteRequest.getPathParameter(name);
448     }
449
450     public void setAsyncSupported(boolean asyncSupported) {
451         this.asyncSupported = Boolean.valueOf(asyncSupported);
452     }
453
454     /**
455      * Release all object references, and initialize instance variables, in
456      * preparation for reuse of this object.
457      */

458     public void recycle() {
459
460         internalDispatcherType = null;
461         requestDispatcherPath = null;
462
463         authType = null;
464         inputBuffer.recycle();
465         usingInputStream = false;
466         usingReader = false;
467         userPrincipal = null;
468         subject = null;
469         parametersParsed = false;
470         if (parts != null) {
471             for (Part part: parts) {
472                 try {
473                     part.delete();
474                 } catch (IOException ignored) {
475                     // ApplicationPart.delete() never throws an IOEx
476                 }
477             }
478             parts = null;
479         }
480         partsParseException = null;
481         locales.clear();
482         localesParsed = false;
483         secure = false;
484         remoteAddr = null;
485         remoteHost = null;
486         remotePort = -1;
487         localPort = -1;
488         localAddr = null;
489         localName = null;
490
491         attributes.clear();
492         sslAttributesParsed = false;
493         notes.clear();
494
495         recycleSessionInfo();
496         recycleCookieInfo(false);
497
498         if (getDiscardFacades()) {
499             parameterMap = new ParameterMap<>();
500         } else {
501             parameterMap.setLocked(false);
502             parameterMap.clear();
503         }
504
505         mappingData.recycle();
506         applicationMapping.recycle();
507
508         applicationRequest = null;
509         if (getDiscardFacades()) {
510             if (facade != null) {
511                 facade.clear();
512                 facade = null;
513             }
514             if (inputStream != null) {
515                 inputStream.clear();
516                 inputStream = null;
517             }
518             if (reader != null) {
519                 reader.clear();
520                 reader = null;
521             }
522         }
523
524         asyncSupported = null;
525         if (asyncContext!=null) {
526             asyncContext.recycle();
527         }
528         asyncContext = null;
529     }
530
531
532     protected void recycleSessionInfo() {
533         if (session != null) {
534             try {
535                 session.endAccess();
536             } catch (Throwable t) {
537                 ExceptionUtils.handleThrowable(t);
538                 log.warn(sm.getString("coyoteRequest.sessionEndAccessFail"), t);
539             }
540         }
541         session = null;
542         requestedSessionCookie = false;
543         requestedSessionId = null;
544         requestedSessionURL = false;
545         requestedSessionSSL = false;
546     }
547
548
549     protected void recycleCookieInfo(boolean recycleCoyote) {
550         cookiesParsed = false;
551         cookiesConverted = false;
552         cookies = null;
553         if (recycleCoyote) {
554             getCoyoteRequest().getCookies().recycle();
555         }
556     }
557
558
559     // -------------------------------------------------------- Request Methods
560
561     /**
562      * Associated Catalina connector.
563      */

564     protected final Connector connector;
565
566     /**
567      * @return the Connector through which this Request was received.
568      */

569     public Connector getConnector() {
570         return this.connector;
571     }
572
573
574     /**
575      * Return the Context within which this Request is being processed.
576      * <p>
577      * This is available as soon as the appropriate Context is identified.
578      * Note that availability of a Context allows <code>getContextPath()</code>
579      * to return a value, and thus enables parsing of the request URI.
580      *
581      * @return the Context mapped with the request
582      */

583     public Context getContext() {
584         return mappingData.context;
585     }
586
587
588     /**
589      * Get the recycling strategy of the facade objects.
590      * @return the value of the flag as set on the connector, or
591      *   <code>true</code> if no connector is associated with this request
592      */

593     public boolean getDiscardFacades() {
594         return (connector == null) ? true : connector.getDiscardFacades();
595     }
596
597
598     /**
599      * Filter chain associated with the request.
600      */

601     protected FilterChain filterChain = null;
602
603     /**
604      * Get filter chain associated with the request.
605      *
606      * @return the associated filter chain
607      */

608     public FilterChain getFilterChain() {
609         return this.filterChain;
610     }
611
612     /**
613      * Set filter chain associated with the request.
614      *
615      * @param filterChain new filter chain
616      */

617     public void setFilterChain(FilterChain filterChain) {
618         this.filterChain = filterChain;
619     }
620
621
622     /**
623      * @return the Host within which this Request is being processed.
624      */

625     public Host getHost() {
626         return mappingData.host;
627     }
628
629
630     /**
631      * Mapping data.
632      */

633     protected final MappingData mappingData = new MappingData();
634     private final ApplicationMapping applicationMapping = new ApplicationMapping(mappingData);
635
636     /**
637      * @return mapping data.
638      */

639     public MappingData getMappingData() {
640         return mappingData;
641     }
642
643
644     /**
645      * The facade associated with this request.
646      */

647     protected RequestFacade facade = null;
648
649
650     /**
651      * @return the <code>ServletRequest</code> for which this object
652      * is the facade.  This method must be implemented by a subclass.
653      */

654     public HttpServletRequest getRequest() {
655         if (facade == null) {
656             facade = new RequestFacade(this);
657         }
658         if (applicationRequest == null) {
659             applicationRequest = facade;
660         }
661         return applicationRequest;
662     }
663
664
665     /**
666      * Set a wrapped HttpServletRequest to pass to the application. Components
667      * wishing to wrap the request should obtain the request via
668      * {@link #getRequest()}, wrap it and then call this method with the
669      * wrapped request.
670      *
671      * @param applicationRequest The wrapped request to pass to the application
672      */

673     public void setRequest(HttpServletRequest applicationRequest) {
674         // Check the wrapper wraps this request
675         ServletRequest r = applicationRequest;
676         while (r instanceof HttpServletRequestWrapper) {
677             r = ((HttpServletRequestWrapper) r).getRequest();
678         }
679         if (r != facade) {
680             throw new IllegalArgumentException(sm.getString("request.illegalWrap"));
681         }
682         this.applicationRequest = applicationRequest;
683     }
684
685
686     /**
687      * The response with which this request is associated.
688      */

689     protected org.apache.catalina.connector.Response response = null;
690
691     /**
692      * @return the Response with which this Request is associated.
693      */

694     public org.apache.catalina.connector.Response getResponse() {
695         return this.response;
696     }
697
698     /**
699      * Set the Response with which this Request is associated.
700      *
701      * @param response The new associated response
702      */

703     public void setResponse(org.apache.catalina.connector.Response response) {
704         this.response = response;
705     }
706
707     /**
708      * @return the input stream associated with this Request.
709      */

710     public InputStream getStream() {
711         if (inputStream == null) {
712             inputStream = new CoyoteInputStream(inputBuffer);
713         }
714         return inputStream;
715     }
716
717     /**
718      * URI byte to char converter.
719      */

720     protected B2CConverter URIConverter = null;
721
722     /**
723      * @return the URI converter.
724      */

725     protected B2CConverter getURIConverter() {
726         return URIConverter;
727     }
728
729     /**
730      * Set the URI converter.
731      *
732      * @param URIConverter the new URI converter
733      */

734     protected void setURIConverter(B2CConverter URIConverter) {
735         this.URIConverter = URIConverter;
736     }
737
738
739     /**
740      * @return the Wrapper within which this Request is being processed.
741      */

742     public Wrapper getWrapper() {
743         return mappingData.wrapper;
744     }
745
746
747     // ------------------------------------------------- Request Public Methods
748
749     /**
750      * Create and return a ServletInputStream to read the content
751      * associated with this Request.
752      *
753      * @return the created input stream
754      * @exception IOException if an input/output error occurs
755      */

756     public ServletInputStream createInputStream()
757             throws IOException {
758         if (inputStream == null) {
759             inputStream = new CoyoteInputStream(inputBuffer);
760         }
761         return inputStream;
762     }
763
764
765     /**
766      * Perform whatever actions are required to flush and close the input
767      * stream or reader, in a single operation.
768      *
769      * @exception IOException if an input/output error occurs
770      */

771     public void finishRequest() throws IOException {
772         if (response.getStatus() == HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE) {
773             checkSwallowInput();
774         }
775     }
776
777
778     /**
779      * @return the object bound with the specified name to the internal notes
780      * for this request, or <code>null</code> if no such binding exists.
781      *
782      * @param name Name of the note to be returned
783      */

784     public Object getNote(String name) {
785         return notes.get(name);
786     }
787
788
789     /**
790      * Remove any object bound to the specified name in the internal notes
791      * for this request.
792      *
793      * @param name Name of the note to be removed
794      */

795     public void removeNote(String name) {
796         notes.remove(name);
797     }
798
799
800     /**
801      * Set the port number of the server to process this request.
802      *
803      * @param port The server port
804      */

805     public void setLocalPort(int port) {
806         localPort = port;
807     }
808
809     /**
810      * Bind an object to a specified name in the internal notes associated
811      * with this request, replacing any existing binding for this name.
812      *
813      * @param name Name to which the object should be bound
814      * @param value Object to be bound to the specified name
815      */

816     public void setNote(String name, Object value) {
817         notes.put(name, value);
818     }
819
820
821     /**
822      * Set the IP address of the remote client associated with this Request.
823      *
824      * @param remoteAddr The remote IP address
825      */

826     public void setRemoteAddr(String remoteAddr) {
827         this.remoteAddr = remoteAddr;
828     }
829
830
831     /**
832      * Set the fully qualified name of the remote client associated with this
833      * Request.
834      *
835      * @param remoteHost The remote host name
836      */

837     public void setRemoteHost(String remoteHost) {
838         this.remoteHost = remoteHost;
839     }
840
841
842     /**
843      * Set the value to be returned by <code>isSecure()</code>
844      * for this Request.
845      *
846      * @param secure The new isSecure value
847      */

848     public void setSecure(boolean secure) {
849         this.secure = secure;
850     }
851
852
853     /**
854      * Set the port number of the server to process this request.
855      *
856      * @param port The server port
857      */

858     public void setServerPort(int port) {
859         coyoteRequest.setServerPort(port);
860     }
861
862
863     // ------------------------------------------------- ServletRequest Methods
864
865     /**
866      * @return the specified request attribute if it exists; otherwise, return
867      * <code>null</code>.
868      *
869      * @param name Name of the request attribute to return
870      */

871     @Override
872     public Object getAttribute(String name) {
873         // Special attributes
874         SpecialAttributeAdapter adapter = specialAttributes.get(name);
875         if (adapter != null) {
876             return adapter.get(this, name);
877         }
878
879         Object attr = attributes.get(name);
880
881         if (attr != null) {
882             return attr;
883         }
884
885         attr = coyoteRequest.getAttribute(name);
886         if (attr != null) {
887             return attr;
888         }
889         if (TLSUtil.isTLSRequestAttribute(name)) {
890             coyoteRequest.action(ActionCode.REQ_SSL_ATTRIBUTE, coyoteRequest);
891             attr = coyoteRequest.getAttribute(Globals.CERTIFICATES_ATTR);
892             if (attr != null) {
893                 attributes.put(Globals.CERTIFICATES_ATTR, attr);
894             }
895             attr = coyoteRequest.getAttribute(Globals.CIPHER_SUITE_ATTR);
896             if (attr != null) {
897                 attributes.put(Globals.CIPHER_SUITE_ATTR, attr);
898             }
899             attr = coyoteRequest.getAttribute(Globals.KEY_SIZE_ATTR);
900             if (attr != null) {
901                 attributes.put(Globals.KEY_SIZE_ATTR, attr);
902             }
903             attr = coyoteRequest.getAttribute(Globals.SSL_SESSION_ID_ATTR);
904             if (attr != null) {
905                 attributes.put(Globals.SSL_SESSION_ID_ATTR, attr);
906             }
907             attr = coyoteRequest.getAttribute(Globals.SSL_SESSION_MGR_ATTR);
908             if (attr != null) {
909                 attributes.put(Globals.SSL_SESSION_MGR_ATTR, attr);
910             }
911             attr = coyoteRequest.getAttribute(SSLSupport.PROTOCOL_VERSION_KEY);
912             if (attr != null) {
913                 attributes.put(SSLSupport.PROTOCOL_VERSION_KEY, attr);
914             }
915             attr = attributes.get(name);
916             sslAttributesParsed = true;
917         }
918         return attr;
919     }
920
921
922     @Override
923     public long getContentLengthLong() {
924         return coyoteRequest.getContentLengthLong();
925     }
926
927
928     /**
929      * Return the names of all request attributes for this Request, or an
930      * empty <code>Enumeration</code> if there are none. Note that the attribute
931      * names returned will only be those for the attributes set via
932      * {@link #setAttribute(String, Object)}. Tomcat internal attributes will
933      * not be included although they are accessible via
934      * {@link #getAttribute(String)}. The Tomcat internal attributes include:
935      * <ul>
936      * <li>{@link Globals#DISPATCHER_TYPE_ATTR}</li>
937      * <li>{@link Globals#DISPATCHER_REQUEST_PATH_ATTR}</li>
938      * <li>{@link Globals#ASYNC_SUPPORTED_ATTR}</li>
939      * <li>{@link Globals#CERTIFICATES_ATTR} (SSL connections only)</li>
940      * <li>{@link Globals#CIPHER_SUITE_ATTR} (SSL connections only)</li>
941      * <li>{@link Globals#KEY_SIZE_ATTR} (SSL connections only)</li>
942      * <li>{@link Globals#SSL_SESSION_ID_ATTR} (SSL connections only)</li>
943      * <li>{@link Globals#SSL_SESSION_MGR_ATTR} (SSL connections only)</li>
944      * <li>{@link Globals#PARAMETER_PARSE_FAILED_ATTR}</li>
945      * </ul>
946      * The underlying connector may also expose request attributes. These all
947      * have names starting with "org.apache.tomcat" and include:
948      * <ul>
949      * <li>{@link Globals#SENDFILE_SUPPORTED_ATTR}</li>
950      * </ul>
951      * Connector implementations may return some, all or none of these
952      * attributes and may also support additional attributes.
953      *
954      * @return the attribute names enumeration
955      */

956     @Override
957     public Enumeration<String> getAttributeNames() {
958         if (isSecure() && !sslAttributesParsed) {
959             getAttribute(Globals.CERTIFICATES_ATTR);
960         }
961         // Take a copy to prevent ConcurrentModificationExceptions if used to
962         // remove attributes
963         Set<String> names = new HashSet<>();
964         names.addAll(attributes.keySet());
965         return Collections.enumeration(names);
966     }
967
968
969     /**
970      * @return the character encoding for this Request.
971      */

972     @Override
973     public String getCharacterEncoding() {
974         String characterEncoding = coyoteRequest.getCharacterEncoding();
975         if (characterEncoding != null) {
976             return characterEncoding;
977         }
978
979         Context context = getContext();
980         if (context != null) {
981             return context.getRequestCharacterEncoding();
982         }
983
984         return null;
985     }
986
987
988     private Charset getCharset() {
989         Charset charset = null;
990         try {
991             charset = coyoteRequest.getCharset();
992         } catch (UnsupportedEncodingException e) {
993             // Ignore
994         }
995         if (charset != null) {
996             return charset;
997         }
998
999         Context context = getContext();
1000         if (context != null) {
1001             String encoding = context.getRequestCharacterEncoding();
1002             if (encoding != null) {
1003                 try {
1004                     return B2CConverter.getCharset(encoding);
1005                 } catch (UnsupportedEncodingException e) {
1006                     // Ignore
1007                 }
1008             }
1009         }
1010
1011         return org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
1012     }
1013
1014
1015     /**
1016      * @return the content length for this Request.
1017      */

1018     @Override
1019     public int getContentLength() {
1020         return coyoteRequest.getContentLength();
1021     }
1022
1023
1024     /**
1025      * @return the content type for this Request.
1026      */

1027     @Override
1028     public String getContentType() {
1029         return coyoteRequest.getContentType();
1030     }
1031
1032
1033     /**
1034      * Set the content type for this Request.
1035      *
1036      * @param contentType The content type
1037      */

1038     public void setContentType(String contentType) {
1039         coyoteRequest.setContentType(contentType);
1040     }
1041
1042
1043     /**
1044      * @return the servlet input stream for this Request.  The default
1045      * implementation returns a servlet input stream created by
1046      * <code>createInputStream()</code>.
1047      *
1048      * @exception IllegalStateException if <code>getReader()</code> has
1049      *  already been called for this request
1050      * @exception IOException if an input/output error occurs
1051      */

1052     @Override
1053     public ServletInputStream getInputStream() throws IOException {
1054
1055         if (usingReader) {
1056             throw new IllegalStateException(sm.getString("coyoteRequest.getInputStream.ise"));
1057         }
1058
1059         usingInputStream = true;
1060         if (inputStream == null) {
1061             inputStream = new CoyoteInputStream(inputBuffer);
1062         }
1063         return inputStream;
1064
1065     }
1066
1067
1068     /**
1069      * @return the preferred Locale that the client will accept content in,
1070      * based on the value for the first <code>Accept-Language</code> header
1071      * that was encountered.  If the request did not specify a preferred
1072      * language, the server's default Locale is returned.
1073      */

1074     @Override
1075     public Locale getLocale() {
1076
1077         if (!localesParsed) {
1078             parseLocales();
1079         }
1080
1081         if (locales.size() > 0) {
1082             return locales.get(0);
1083         }
1084
1085         return defaultLocale;
1086     }
1087
1088
1089     /**
1090      * @return the set of preferred Locales that the client will accept
1091      * content in, based on the values for any <code>Accept-Language</code>
1092      * headers that were encountered.  If the request did not specify a
1093      * preferred language, the server's default Locale is returned.
1094      */

1095     @Override
1096     public Enumeration<Locale> getLocales() {
1097
1098         if (!localesParsed) {
1099             parseLocales();
1100         }
1101
1102         if (locales.size() > 0) {
1103             return Collections.enumeration(locales);
1104         }
1105         ArrayList<Locale> results = new ArrayList<>();
1106         results.add(defaultLocale);
1107         return Collections.enumeration(results);
1108
1109     }
1110
1111
1112     /**
1113      * @return the value of the specified request parameter, if any; otherwise,
1114      * return <code>null</code>.  If there is more than one value defined,
1115      * return only the first one.
1116      *
1117      * @param name Name of the desired request parameter
1118      */

1119     @Override
1120     public String getParameter(String name) {
1121
1122         if (!parametersParsed) {
1123             parseParameters();
1124         }
1125
1126         return coyoteRequest.getParameters().getParameter(name);
1127
1128     }
1129
1130
1131
1132     /**
1133      * Returns a <code>Map</code> of the parameters of this request.
1134      * Request parameters are extra information sent with the request.
1135      * For HTTP servlets, parameters are contained in the query string
1136      * or posted form data.
1137      *
1138      * @return A <code>Map</code> containing parameter names as keys
1139      *  and parameter values as map values.
1140      */

1141     @Override
1142     public Map<String, String[]> getParameterMap() {
1143
1144         if (parameterMap.isLocked()) {
1145             return parameterMap;
1146         }
1147
1148         Enumeration<String> enumeration = getParameterNames();
1149         while (enumeration.hasMoreElements()) {
1150             String name = enumeration.nextElement();
1151             String[] values = getParameterValues(name);
1152             parameterMap.put(name, values);
1153         }
1154
1155         parameterMap.setLocked(true);
1156
1157         return parameterMap;
1158
1159     }
1160
1161
1162     /**
1163      * @return the names of all defined request parameters for this request.
1164      */

1165     @Override
1166     public Enumeration<String> getParameterNames() {
1167
1168         if (!parametersParsed) {
1169             parseParameters();
1170         }
1171
1172         return coyoteRequest.getParameters().getParameterNames();
1173
1174     }
1175
1176
1177     /**
1178      * @return the defined values for the specified request parameter, if any;
1179      * otherwise, return <code>null</code>.
1180      *
1181      * @param name Name of the desired request parameter
1182      */

1183     @Override
1184     public String[] getParameterValues(String name) {
1185
1186         if (!parametersParsed) {
1187             parseParameters();
1188         }
1189
1190         return coyoteRequest.getParameters().getParameterValues(name);
1191
1192     }
1193
1194
1195     /**
1196      * @return the protocol and version used to make this Request.
1197      */

1198     @Override
1199     public String getProtocol() {
1200         return coyoteRequest.protocol().toString();
1201     }
1202
1203
1204     /**
1205      * Read the Reader wrapping the input stream for this Request.  The
1206      * default implementation wraps a <code>BufferedReader</code> around the
1207      * servlet input stream returned by <code>createInputStream()</code>.
1208      *
1209      * @return a buffered reader for the request
1210      * @exception IllegalStateException if <code>getInputStream()</code>
1211      *  has already been called for this request
1212      * @exception IOException if an input/output error occurs
1213      */

1214     @Override
1215     public BufferedReader getReader() throws IOException {
1216
1217         if (usingInputStream) {
1218             throw new IllegalStateException(sm.getString("coyoteRequest.getReader.ise"));
1219         }
1220
1221         // InputBuffer has no easily accessible reference chain to the Context
1222         // to check for a default request character encoding at the Context.
1223         // Therefore, if a Context default should be used, it is set explicitly
1224         // here. Need to do this before setting usingReader.
1225         if (coyoteRequest.getCharacterEncoding() == null) {
1226             // Nothing currently set explicitly.
1227             // Check the content
1228             Context context = getContext();
1229             if (context != null) {
1230                 String enc = context.getRequestCharacterEncoding();
1231                 if (enc != null) {
1232                     // Explicitly set the context default so it is visible to
1233                     // InputBuffer when creating the Reader.
1234                     setCharacterEncoding(enc);
1235                 }
1236             }
1237         }
1238
1239         usingReader = true;
1240
1241         inputBuffer.checkConverter();
1242         if (reader == null) {
1243             reader = new CoyoteReader(inputBuffer);
1244         }
1245         return reader;
1246     }
1247
1248
1249     /**
1250      * @return the real path of the specified virtual path.
1251      *
1252      * @param path Path to be translated
1253      *
1254      * @deprecated As of version 2.1 of the Java Servlet API, use
1255      *  <code>ServletContext.getRealPath()</code>.
1256      */

1257     @Override
1258     @Deprecated
1259     public String getRealPath(String path) {
1260
1261         Context context = getContext();
1262         if (context == null) {
1263             return null;
1264         }
1265         ServletContext servletContext = context.getServletContext();
1266         if (servletContext == null) {
1267             return null;
1268         }
1269
1270         try {
1271             return servletContext.getRealPath(path);
1272         } catch (IllegalArgumentException e) {
1273             return null;
1274         }
1275     }
1276
1277
1278     /**
1279      * @return the remote IP address making this Request.
1280      */

1281     @Override
1282     public String getRemoteAddr() {
1283         if (remoteAddr == null) {
1284             coyoteRequest.action(ActionCode.REQ_HOST_ADDR_ATTRIBUTE, coyoteRequest);
1285             remoteAddr = coyoteRequest.remoteAddr().toString();
1286         }
1287         return remoteAddr;
1288     }
1289
1290
1291     /**
1292      * @return the remote host name making this Request.
1293      */

1294     @Override
1295     public String getRemoteHost() {
1296         if (remoteHost == null) {
1297             if (!connector.getEnableLookups()) {
1298                 remoteHost = getRemoteAddr();
1299             } else {
1300                 coyoteRequest.action(ActionCode.REQ_HOST_ATTRIBUTE, coyoteRequest);
1301                 remoteHost = coyoteRequest.remoteHost().toString();
1302             }
1303         }
1304         return remoteHost;
1305     }
1306
1307     /**
1308      * @return the Internet Protocol (IP) source port of the client
1309      * or last proxy that sent the request.
1310      */

1311     @Override
1312     public int getRemotePort(){
1313         if (remotePort == -1) {
1314             coyoteRequest.action(ActionCode.REQ_REMOTEPORT_ATTRIBUTE, coyoteRequest);
1315             remotePort = coyoteRequest.getRemotePort();
1316         }
1317         return remotePort;
1318     }
1319
1320     /**
1321      * @return the host name of the Internet Protocol (IP) interface on
1322      * which the request was received.
1323      */

1324     @Override
1325     public String getLocalName(){
1326         if (localName == null) {
1327             coyoteRequest.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, coyoteRequest);
1328             localName = coyoteRequest.localName().toString();
1329         }
1330         return localName;
1331     }
1332
1333     /**
1334      * @return the Internet Protocol (IP) address of the interface on
1335      * which the request  was received.
1336      */

1337     @Override
1338     public String getLocalAddr(){
1339         if (localAddr == null) {
1340             coyoteRequest.action(ActionCode.REQ_LOCAL_ADDR_ATTRIBUTE, coyoteRequest);
1341             localAddr = coyoteRequest.localAddr().toString();
1342         }
1343         return localAddr;
1344     }
1345
1346
1347     /**
1348      * @return the Internet Protocol (IP) port number of the interface
1349      * on which the request was received.
1350      */

1351     @Override
1352     public int getLocalPort(){
1353         if (localPort == -1){
1354             coyoteRequest.action(ActionCode.REQ_LOCALPORT_ATTRIBUTE, coyoteRequest);
1355             localPort = coyoteRequest.getLocalPort();
1356         }
1357         return localPort;
1358     }
1359
1360     /**
1361      * @return a RequestDispatcher that wraps the resource at the specified
1362      * path, which may be interpreted as relative to the current request path.
1363      *
1364      * @param path Path of the resource to be wrapped
1365      */

1366     @Override
1367     public RequestDispatcher getRequestDispatcher(String path) {
1368
1369         Context context = getContext();
1370         if (context == null) {
1371             return null;
1372         }
1373
1374         if (path == null) {
1375             return null;
1376         }
1377
1378         int fragmentPos = path.indexOf('#');
1379         if (fragmentPos > -1) {
1380             log.warn(sm.getString("request.fragmentInDispatchPath", path));
1381             path = path.substring(0, fragmentPos);
1382         }
1383
1384         // If the path is already context-relative, just pass it through
1385         if (path.startsWith("/")) {
1386             return context.getServletContext().getRequestDispatcher(path);
1387         }
1388
1389         /*
1390          * Relative to what, exactly?
1391          *
1392          * From the Servlet 4.0 Javadoc:
1393          * - The pathname specified may be relative, although it cannot extend
1394          *   outside the current servlet context.
1395          * - If it is relative, it must be relative against the current servlet
1396          *
1397          * From Section 9.1 of the spec:
1398          * - The servlet container uses information in the request object to
1399          *   transform the given relative path against the current servlet to a
1400          *   complete path.
1401          *
1402          * It is undefined whether the requestURI is used or whether servletPath
1403          * and pathInfo are used. Given that the RequestURI includes the
1404          * contextPath (and extracting that is messy) , using the servletPath and
1405          * pathInfo looks to be the more reasonable choice.
1406          */

1407
1408         // Convert a request-relative path to a context-relative one
1409         String servletPath = (String) getAttribute(
1410                 RequestDispatcher.INCLUDE_SERVLET_PATH);
1411         if (servletPath == null) {
1412             servletPath = getServletPath();
1413         }
1414
1415         // Add the path info, if there is any
1416         String pathInfo = getPathInfo();
1417         String requestPath = null;
1418
1419         if (pathInfo == null) {
1420             requestPath = servletPath;
1421         } else {
1422             requestPath = servletPath + pathInfo;
1423         }
1424
1425         int pos = requestPath.lastIndexOf('/');
1426         String relative = null;
1427         if (context.getDispatchersUseEncodedPaths()) {
1428             if (pos >= 0) {
1429                 relative = URLEncoder.DEFAULT.encode(
1430                         requestPath.substring(0, pos + 1), StandardCharsets.UTF_8) + path;
1431             } else {
1432                 relative = URLEncoder.DEFAULT.encode(requestPath, StandardCharsets.UTF_8) + path;
1433             }
1434         } else {
1435             if (pos >= 0) {
1436                 relative = requestPath.substring(0, pos + 1) + path;
1437             } else {
1438                 relative = requestPath + path;
1439             }
1440         }
1441
1442         return context.getServletContext().getRequestDispatcher(relative);
1443     }
1444
1445
1446     /**
1447      * @return the scheme used to make this Request.
1448      */

1449     @Override
1450     public String getScheme() {
1451         return coyoteRequest.scheme().toString();
1452     }
1453
1454
1455     /**
1456      * @return the server name responding to this Request.
1457      */

1458     @Override
1459     public String getServerName() {
1460         return coyoteRequest.serverName().toString();
1461     }
1462
1463
1464     /**
1465      * @return the server port responding to this Request.
1466      */

1467     @Override
1468     public int getServerPort() {
1469         return coyoteRequest.getServerPort();
1470     }
1471
1472
1473     /**
1474      * @return <code>true</code> if this request was received on a secure connection.
1475      */

1476     @Override
1477     public boolean isSecure() {
1478         return secure;
1479     }
1480
1481
1482     /**
1483      * Remove the specified request attribute if it exists.
1484      *
1485      * @param name Name of the request attribute to remove
1486      */

1487     @Override
1488     public void removeAttribute(String name) {
1489         // Remove the specified attribute
1490         // Pass special attributes to the native layer
1491         if (name.startsWith("org.apache.tomcat.")) {
1492             coyoteRequest.getAttributes().remove(name);
1493         }
1494
1495         boolean found = attributes.containsKey(name);
1496         if (found) {
1497             Object value = attributes.get(name);
1498             attributes.remove(name);
1499
1500             // Notify interested application event listeners
1501             notifyAttributeRemoved(name, value);
1502         }
1503     }
1504
1505
1506     /**
1507      * Set the specified request attribute to the specified value.
1508      *
1509      * @param name Name of the request attribute to set
1510      * @param value The associated value
1511      */

1512     @Override
1513     public void setAttribute(String name, Object value) {
1514
1515         // Name cannot be null
1516         if (name == null) {
1517             throw new IllegalArgumentException(sm.getString("coyoteRequest.setAttribute.namenull"));
1518         }
1519
1520         // Null value is the same as removeAttribute()
1521         if (value == null) {
1522             removeAttribute(name);
1523             return;
1524         }
1525
1526         // Special attributes
1527         SpecialAttributeAdapter adapter = specialAttributes.get(name);
1528         if (adapter != null) {
1529             adapter.set(this, name, value);
1530             return;
1531         }
1532
1533         // Add or replace the specified attribute
1534         // Do the security check before any updates are made
1535         if (Globals.IS_SECURITY_ENABLED &&
1536                 name.equals(Globals.SENDFILE_FILENAME_ATTR)) {
1537             // Use the canonical file name to avoid any possible symlink and
1538             // relative path issues
1539             String canonicalPath;
1540             try {
1541                 canonicalPath = new File(value.toString()).getCanonicalPath();
1542             } catch (IOException e) {
1543                 throw new SecurityException(sm.getString(
1544                         "coyoteRequest.sendfileNotCanonical", value), e);
1545             }
1546             // Sendfile is performed in Tomcat's security context so need to
1547             // check if the web app is permitted to access the file while still
1548             // in the web app's security context
1549             System.getSecurityManager().checkRead(canonicalPath);
1550             // Update the value so the canonical path is used
1551             value = canonicalPath;
1552         }
1553
1554         Object oldValue = attributes.put(name, value);
1555
1556         // Pass special attributes to the native layer
1557         if (name.startsWith("org.apache.tomcat.")) {
1558             coyoteRequest.setAttribute(name, value);
1559         }
1560
1561         // Notify interested application event listeners
1562         notifyAttributeAssigned(name, value, oldValue);
1563     }
1564
1565
1566     /**
1567      * Notify interested listeners that attribute has been assigned a value.
1568      *
1569      * @param name Attribute name
1570      * @param value New attribute value
1571      * @param oldValue Old attribute value
1572      */

1573     private void notifyAttributeAssigned(String name, Object value,
1574             Object oldValue) {
1575         Context context = getContext();
1576         if (context == null) {
1577             return;
1578         }
1579         Object listeners[] = context.getApplicationEventListeners();
1580         if ((listeners == null) || (listeners.length == 0)) {
1581             return;
1582         }
1583         boolean replaced = (oldValue != null);
1584         ServletRequestAttributeEvent event = null;
1585         if (replaced) {
1586             event = new ServletRequestAttributeEvent(
1587                     context.getServletContext(), getRequest(), name, oldValue);
1588         } else {
1589             event = new ServletRequestAttributeEvent(
1590                     context.getServletContext(), getRequest(), name, value);
1591         }
1592
1593         for (int i = 0; i < listeners.length; i++) {
1594             if (!(listeners[i] instanceof ServletRequestAttributeListener)) {
1595                 continue;
1596             }
1597             ServletRequestAttributeListener listener =
1598                     (ServletRequestAttributeListener) listeners[i];
1599             try {
1600                 if (replaced) {
1601                     listener.attributeReplaced(event);
1602                 } else {
1603                     listener.attributeAdded(event);
1604                 }
1605             } catch (Throwable t) {
1606                 ExceptionUtils.handleThrowable(t);
1607                 // Error valve will pick this exception up and display it to user
1608                 attributes.put(RequestDispatcher.ERROR_EXCEPTION, t);
1609                 context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);
1610             }
1611         }
1612     }
1613
1614
1615     /**
1616      * Notify interested listeners that attribute has been removed.
1617      *
1618      * @param name Attribute name
1619      * @param value Attribute value
1620      */

1621     private void notifyAttributeRemoved(String name, Object value) {
1622         Context context = getContext();
1623         Object listeners[] = context.getApplicationEventListeners();
1624         if ((listeners == null) || (listeners.length == 0)) {
1625             return;
1626         }
1627         ServletRequestAttributeEvent event =
1628                 new ServletRequestAttributeEvent(context.getServletContext(),
1629                         getRequest(), name, value);
1630         for (int i = 0; i < listeners.length; i++) {
1631             if (!(listeners[i] instanceof ServletRequestAttributeListener)) {
1632                 continue;
1633             }
1634             ServletRequestAttributeListener listener =
1635                     (ServletRequestAttributeListener) listeners[i];
1636             try {
1637                 listener.attributeRemoved(event);
1638             } catch (Throwable t) {
1639                 ExceptionUtils.handleThrowable(t);
1640                 // Error valve will pick this exception up and display it to user
1641                 attributes.put(RequestDispatcher.ERROR_EXCEPTION, t);
1642                 context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);
1643             }
1644         }
1645     }
1646
1647
1648     /**
1649      * Overrides the name of the character encoding used in the body of
1650      * this request.  This method must be called prior to reading request
1651      * parameters or reading input using <code>getReader()</code>.
1652      *
1653      * @param enc The character encoding to be used
1654      *
1655      * @exception UnsupportedEncodingException if the specified encoding
1656      *  is not supported
1657      *
1658      * @since Servlet 2.3
1659      */

1660     @Override
1661     public void setCharacterEncoding(String enc) throws UnsupportedEncodingException {
1662
1663         if (usingReader) {
1664             return;
1665         }
1666
1667         // Confirm that the encoding name is valid
1668         Charset charset = B2CConverter.getCharset(enc);
1669
1670         // Save the validated encoding
1671         coyoteRequest.setCharset(charset);
1672     }
1673
1674
1675     @Override
1676     public ServletContext getServletContext() {
1677         return getContext().getServletContext();
1678      }
1679
1680     @Override
1681     public AsyncContext startAsync() {
1682         return startAsync(getRequest(),response.getResponse());
1683     }
1684
1685     @Override
1686     public AsyncContext startAsync(ServletRequest request,
1687             ServletResponse response) {
1688         if (!isAsyncSupported()) {
1689             IllegalStateException ise =
1690                     new IllegalStateException(sm.getString("request.asyncNotSupported"));
1691             log.warn(sm.getString("coyoteRequest.noAsync",
1692                     StringUtils.join(getNonAsyncClassNames())), ise);
1693             throw ise;
1694         }
1695
1696         if (asyncContext == null) {
1697             asyncContext = new AsyncContextImpl(this);
1698         }
1699
1700         asyncContext.setStarted(getContext(), request, response,
1701                 request==getRequest() && response==getResponse().getResponse());
1702         asyncContext.setTimeout(getConnector().getAsyncTimeout());
1703
1704         return asyncContext;
1705     }
1706
1707
1708     private Set<String> getNonAsyncClassNames() {
1709         Set<String> result = new HashSet<>();
1710
1711         Wrapper wrapper = getWrapper();
1712         if (!wrapper.isAsyncSupported()) {
1713             result.add(wrapper.getServletClass());
1714         }
1715
1716         FilterChain filterChain = getFilterChain();
1717         if (filterChain instanceof ApplicationFilterChain) {
1718             ((ApplicationFilterChain) filterChain).findNonAsyncFilters(result);
1719         } else {
1720             result.add(sm.getString("coyoteRequest.filterAsyncSupportUnknown"));
1721         }
1722
1723         Container c = wrapper;
1724         while (c != null) {
1725             c.getPipeline().findNonAsyncValves(result);
1726             c = c.getParent();
1727         }
1728
1729         return result;
1730     }
1731
1732     @Override
1733     public boolean isAsyncStarted() {
1734         if (asyncContext == null) {
1735             return false;
1736         }
1737
1738         return asyncContext.isStarted();
1739     }
1740
1741     public boolean isAsyncDispatching() {
1742         if (asyncContext == null) {
1743             return false;
1744         }
1745
1746         AtomicBoolean result = new AtomicBoolean(false);
1747         coyoteRequest.action(ActionCode.ASYNC_IS_DISPATCHING, result);
1748         return result.get();
1749     }
1750
1751     public boolean isAsyncCompleting() {
1752         if (asyncContext == null) {
1753             return false;
1754         }
1755
1756         AtomicBoolean result = new AtomicBoolean(false);
1757         coyoteRequest.action(ActionCode.ASYNC_IS_COMPLETING, result);
1758         return result.get();
1759     }
1760
1761     public boolean isAsync() {
1762         if (asyncContext == null) {
1763             return false;
1764         }
1765
1766         AtomicBoolean result = new AtomicBoolean(false);
1767         coyoteRequest.action(ActionCode.ASYNC_IS_ASYNC, result);
1768         return result.get();
1769     }
1770
1771     @Override
1772     public boolean isAsyncSupported() {
1773         if (this.asyncSupported == null) {
1774             return true;
1775         }
1776
1777         return asyncSupported.booleanValue();
1778     }
1779
1780     @Override
1781     public AsyncContext getAsyncContext() {
1782         if (!isAsyncStarted()) {
1783             throw new IllegalStateException(sm.getString("request.notAsync"));
1784         }
1785         return asyncContext;
1786     }
1787
1788     public AsyncContextImpl getAsyncContextInternal() {
1789         return asyncContext;
1790     }
1791
1792     @Override
1793     public DispatcherType getDispatcherType() {
1794         if (internalDispatcherType == null) {
1795             return DispatcherType.REQUEST;
1796         }
1797
1798         return this.internalDispatcherType;
1799     }
1800
1801     // ---------------------------------------------------- HttpRequest Methods
1802
1803
1804     /**
1805      * Add a Cookie to the set of Cookies associated with this Request.
1806      *
1807      * @param cookie The new cookie
1808      */

1809     public void addCookie(Cookie cookie) {
1810
1811         if (!cookiesConverted) {
1812             convertCookies();
1813         }
1814
1815         int size = 0;
1816         if (cookies != null) {
1817             size = cookies.length;
1818         }
1819
1820         Cookie[] newCookies = new Cookie[size + 1];
1821         if (cookies != null) {
1822             System.arraycopy(cookies, 0, newCookies, 0, size);
1823         }
1824         newCookies[size] = cookie;
1825
1826         cookies = newCookies;
1827
1828     }
1829
1830
1831     /**
1832      * Add a Locale to the set of preferred Locales for this Request.  The
1833      * first added Locale will be the first one returned by getLocales().
1834      *
1835      * @param locale The new preferred Locale
1836      */

1837     public void addLocale(Locale locale) {
1838         locales.add(locale);
1839     }
1840
1841
1842     /**
1843      * Clear the collection of Cookies associated with this Request.
1844      */

1845     public void clearCookies() {
1846         cookiesParsed = true;
1847         cookiesConverted = true;
1848         cookies = null;
1849     }
1850
1851
1852     /**
1853      * Clear the collection of Locales associated with this Request.
1854      */

1855     public void clearLocales() {
1856         locales.clear();
1857     }
1858
1859
1860     /**
1861      * Set the authentication type used for this request, if any; otherwise
1862      * set the type to <code>null</code>.  Typical values are "BASIC",
1863      * "DIGEST", or "SSL".
1864      *
1865      * @param type The authentication type used
1866      */

1867     public void setAuthType(String type) {
1868         this.authType = type;
1869     }
1870
1871
1872     /**
1873      * Set the path information for this Request.  This will normally be called
1874      * when the associated Context is mapping the Request to a particular
1875      * Wrapper.
1876      *
1877      * @param path The path information
1878      */

1879     public void setPathInfo(String path) {
1880         mappingData.pathInfo.setString(path);
1881     }
1882
1883
1884     /**
1885      * Set a flag indicating whether or not the requested session ID for this
1886      * request came in through a cookie.  This is normally called by the
1887      * HTTP Connector, when it parses the request headers.
1888      *
1889      * @param flag The new flag
1890      */

1891     public void setRequestedSessionCookie(boolean flag) {
1892
1893         this.requestedSessionCookie = flag;
1894
1895     }
1896
1897
1898     /**
1899      * Set the requested session ID for this request.  This is normally called
1900      * by the HTTP Connector, when it parses the request headers.
1901      *
1902      * @param id The new session id
1903      */

1904     public void setRequestedSessionId(String id) {
1905
1906         this.requestedSessionId = id;
1907
1908     }
1909
1910
1911     /**
1912      * Set a flag indicating whether or not the requested session ID for this
1913      * request came in through a URL.  This is normally called by the
1914      * HTTP Connector, when it parses the request headers.
1915      *
1916      * @param flag The new flag
1917      */

1918     public void setRequestedSessionURL(boolean flag) {
1919
1920         this.requestedSessionURL = flag;
1921
1922     }
1923
1924
1925     /**
1926      * Set a flag indicating whether or not the requested session ID for this
1927      * request came in through SSL.  This is normally called by the
1928      * HTTP Connector, when it parses the request headers.
1929      *
1930      * @param flag The new flag
1931      */

1932     public void setRequestedSessionSSL(boolean flag) {
1933
1934         this.requestedSessionSSL = flag;
1935
1936     }
1937
1938
1939     /**
1940      * Get the decoded request URI.
1941      *
1942      * @return the URL decoded request URI
1943      */

1944     public String getDecodedRequestURI() {
1945         return coyoteRequest.decodedURI().toString();
1946     }
1947
1948
1949     /**
1950      * Get the decoded request URI.
1951      *
1952      * @return the URL decoded request URI
1953      */

1954     public MessageBytes getDecodedRequestURIMB() {
1955         return coyoteRequest.decodedURI();
1956     }
1957
1958
1959     /**
1960      * Set the Principal who has been authenticated for this Request.  This
1961      * value is also used to calculate the value to be returned by the
1962      * <code>getRemoteUser()</code> method.
1963      *
1964      * @param principal The user Principal
1965      */

1966     public void setUserPrincipal(final Principal principal) {
1967         if (Globals.IS_SECURITY_ENABLED && principal != null) {
1968             if (subject == null) {
1969                 final HttpSession session = getSession(false);
1970                 if (session == null) {
1971                     // Cache the subject in the request
1972                     subject = newSubject(principal);
1973                 } else {
1974                     // Cache the subject in the request and the session
1975                     subject = (Subject) session.getAttribute(Globals.SUBJECT_ATTR);
1976                     if (subject == null) {
1977                         subject = newSubject(principal);
1978                         session.setAttribute(Globals.SUBJECT_ATTR, subject);
1979                     } else {
1980                         subject.getPrincipals().add(principal);
1981                     }
1982                 }
1983             } else {
1984                 subject.getPrincipals().add(principal);
1985             }
1986         }
1987         userPrincipal = principal;
1988     }
1989
1990
1991     private Subject newSubject(final Principal principal) {
1992         final Subject result = new Subject();
1993         result.getPrincipals().add(principal);
1994         return result;
1995     }
1996
1997
1998     // --------------------------------------------- HttpServletRequest Methods
1999
2000     @Override
2001     public boolean isTrailerFieldsReady() {
2002         return coyoteRequest.isTrailerFieldsReady();
2003     }
2004
2005
2006     @Override
2007     public Map<String, String> getTrailerFields() {
2008         if (!isTrailerFieldsReady()) {
2009             throw new IllegalStateException(sm.getString("coyoteRequest.trailersNotReady"));
2010         }
2011         Map<String,String> result = new HashMap<>();
2012         result.putAll(coyoteRequest.getTrailerFields());
2013         return result;
2014     }
2015
2016
2017     @Override
2018     public PushBuilder newPushBuilder() {
2019         return newPushBuilder(this);
2020     }
2021
2022
2023     public PushBuilder newPushBuilder(HttpServletRequest request) {
2024         AtomicBoolean result = new AtomicBoolean();
2025         coyoteRequest.action(ActionCode.IS_PUSH_SUPPORTED, result);
2026         if (result.get()) {
2027             return new ApplicationPushBuilder(this, request);
2028         } else {
2029             return null;
2030         }
2031     }
2032
2033
2034     @SuppressWarnings("unchecked")
2035     @Override
2036     public <T extends HttpUpgradeHandler> T upgrade(
2037             Class<T> httpUpgradeHandlerClass) throws java.io.IOException, ServletException {
2038         T handler;
2039         InstanceManager instanceManager = null;
2040         try {
2041             // Do not go through the instance manager for internal Tomcat classes since they don't
2042             // need injection
2043             if (InternalHttpUpgradeHandler.class.isAssignableFrom(httpUpgradeHandlerClass)) {
2044                 handler = httpUpgradeHandlerClass.getConstructor().newInstance();
2045             } else {
2046                 instanceManager = getContext().getInstanceManager();
2047                 handler = (T) instanceManager.newInstance(httpUpgradeHandlerClass);
2048             }
2049         } catch (ReflectiveOperationException | NamingException | IllegalArgumentException |
2050                 SecurityException e) {
2051             throw new ServletException(e);
2052         }
2053         UpgradeToken upgradeToken = new UpgradeToken(handler,
2054                 getContext(), instanceManager);
2055
2056         coyoteRequest.action(ActionCode.UPGRADE, upgradeToken);
2057
2058         // Output required by RFC2616. Protocol specific headers should have
2059         // already been set.
2060         response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
2061
2062         return handler;
2063     }
2064
2065     /**
2066      * Return the authentication type used for this Request.
2067      */

2068     @Override
2069     public String getAuthType() {
2070         return authType;
2071     }
2072
2073
2074     /**
2075      * Return the portion of the request URI used to select the Context
2076      * of the Request. The value returned is not decoded which also implies it
2077      * is not normalised.
2078      */

2079     @Override
2080     public String getContextPath() {
2081         int lastSlash = mappingData.contextSlashCount;
2082         // Special case handling for the root context
2083         if (lastSlash == 0) {
2084             return "";
2085         }
2086
2087         String canonicalContextPath = getServletContext().getContextPath();
2088
2089         String uri = getRequestURI();
2090         int pos = 0;
2091         if (!getContext().getAllowMultipleLeadingForwardSlashInPath()) {
2092             // Ensure that the returned value only starts with a single '/'.
2093             // This prevents the value being misinterpreted as a protocol-
2094             // relative URI if used with sendRedirect().
2095             do {
2096                 pos++;
2097             } while (pos < uri.length() && uri.charAt(pos) == '/');
2098             pos--;
2099             uri = uri.substring(pos);
2100         }
2101
2102         char[] uriChars = uri.toCharArray();
2103         // Need at least the number of slashes in the context path
2104         while (lastSlash > 0) {
2105             pos = nextSlash(uriChars, pos + 1);
2106             if (pos == -1) {
2107                 break;
2108             }
2109             lastSlash--;
2110         }
2111         // Now allow for path parameters, normalization and/or encoding.
2112         // Essentially, keep extending the candidate path up to the next slash
2113         // until the decoded and normalized candidate path (with the path
2114         // parameters removed) is the same as the canonical path.
2115         String candidate;
2116         if (pos == -1) {
2117             candidate = uri;
2118         } else {
2119             candidate = uri.substring(0, pos);
2120         }
2121         candidate = removePathParameters(candidate);
2122         candidate = UDecoder.URLDecode(candidate, connector.getURICharset());
2123         candidate = org.apache.tomcat.util.http.RequestUtil.normalize(candidate);
2124         boolean match = canonicalContextPath.equals(candidate);
2125         while (!match && pos != -1) {
2126             pos = nextSlash(uriChars, pos + 1);
2127             if (pos == -1) {
2128                 candidate = uri;
2129             } else {
2130                 candidate = uri.substring(0, pos);
2131             }
2132             candidate = removePathParameters(candidate);
2133             candidate = UDecoder.URLDecode(candidate, connector.getURICharset());
2134             candidate = org.apache.tomcat.util.http.RequestUtil.normalize(candidate);
2135             match = canonicalContextPath.equals(candidate);
2136         }
2137         if (match) {
2138             if (pos == -1) {
2139                 return uri;
2140             } else {
2141                 return uri.substring(0, pos);
2142             }
2143         } else {
2144             // Should never happen
2145             throw new IllegalStateException(sm.getString(
2146                     "coyoteRequest.getContextPath.ise", canonicalContextPath, uri));
2147         }
2148     }
2149
2150
2151     private String removePathParameters(String input) {
2152         int nextSemiColon = input.indexOf(';');
2153         // Shortcut
2154         if (nextSemiColon == -1) {
2155             return input;
2156         }
2157         StringBuilder result = new StringBuilder(input.length());
2158         result.append(input.substring(0, nextSemiColon));
2159         while (true) {
2160             int nextSlash = input.indexOf('/', nextSemiColon);
2161             if (nextSlash == -1) {
2162                 break;
2163             }
2164             nextSemiColon = input.indexOf(';', nextSlash);
2165             if (nextSemiColon == -1) {
2166                 result.append(input.substring(nextSlash));
2167                 break;
2168             } else {
2169                 result.append(input.substring(nextSlash, nextSemiColon));
2170             }
2171         }
2172
2173         return result.toString();
2174     }
2175
2176
2177     private int nextSlash(char[] uri, int startPos) {
2178         int len = uri.length;
2179         int pos = startPos;
2180         while (pos < len) {
2181             if (uri[pos] == '/') {
2182                 return pos;
2183             } else if (UDecoder.ALLOW_ENCODED_SLASH && uri[pos] == '%' && pos + 2 < len &&
2184                     uri[pos+1] == '2' && (uri[pos + 2] == 'f' || uri[pos + 2] == 'F')) {
2185                 return pos;
2186             }
2187             pos++;
2188         }
2189         return -1;
2190     }
2191
2192
2193     /**
2194      * Return the set of Cookies received with this Request. Triggers parsing of
2195      * the Cookie HTTP headers followed by conversion to Cookie objects if this
2196      * has not already been performed.
2197      *
2198      * @return the array of cookies
2199      */

2200     @Override
2201     public Cookie[] getCookies() {
2202         if (!cookiesConverted) {
2203             convertCookies();
2204         }
2205         return cookies;
2206     }
2207
2208
2209     /**
2210      * Return the server representation of the cookies associated with this
2211      * request. Triggers parsing of the Cookie HTTP headers (but not conversion
2212      * to Cookie objects) if the headers have not yet been parsed.
2213      *
2214      * @return the server cookies
2215      */

2216     public ServerCookies getServerCookies() {
2217         parseCookies();
2218         return coyoteRequest.getCookies();
2219     }
2220
2221
2222     /**
2223      * Return the value of the specified date header, if any; otherwise
2224      * return -1.
2225      *
2226      * @param name Name of the requested date header
2227      * @return the date as a long
2228      *
2229      * @exception IllegalArgumentException if the specified header value
2230      *  cannot be converted to a date
2231      */

2232     @Override
2233     public long getDateHeader(String name) {
2234
2235         String value = getHeader(name);
2236         if (value == null) {
2237             return -1L;
2238         }
2239
2240         // Attempt to convert the date header in a variety of formats
2241         long result = FastHttpDateFormat.parseDate(value);
2242         if (result != (-1L)) {
2243             return result;
2244         }
2245         throw new IllegalArgumentException(value);
2246
2247     }
2248
2249
2250     /**
2251      * Return the first value of the specified header, if any; otherwise,
2252      * return <code>null</code>
2253      *
2254      * @param name Name of the requested header
2255      * @return the header value
2256      */

2257     @Override
2258     public String getHeader(String name) {
2259         return coyoteRequest.getHeader(name);
2260     }
2261
2262
2263     /**
2264      * Return all of the values of the specified header, if any; otherwise,
2265      * return an empty enumeration.
2266      *
2267      * @param name Name of the requested header
2268      * @return the enumeration with the header values
2269      */

2270     @Override
2271     public Enumeration<String> getHeaders(String name) {
2272         return coyoteRequest.getMimeHeaders().values(name);
2273     }
2274
2275
2276     /**
2277      * @return the names of all headers received with this request.
2278      */

2279     @Override
2280     public Enumeration<String> getHeaderNames() {
2281         return coyoteRequest.getMimeHeaders().names();
2282     }
2283
2284
2285     /**
2286      * Return the value of the specified header as an integer, or -1 if there
2287      * is no such header for this request.
2288      *
2289      * @param name Name of the requested header
2290      * @return the header value as an int
2291      *
2292      * @exception IllegalArgumentException if the specified header value
2293      *  cannot be converted to an integer
2294      */

2295     @Override
2296     public int getIntHeader(String name) {
2297
2298         String value = getHeader(name);
2299         if (value == null) {
2300             return -1;
2301         }
2302
2303         return Integer.parseInt(value);
2304     }
2305
2306
2307     @Override
2308     public HttpServletMapping getHttpServletMapping() {
2309         return applicationMapping.getHttpServletMapping();
2310     }
2311
2312
2313     /**
2314      * @return the HTTP request method used in this Request.
2315      */

2316     @Override
2317     public String getMethod() {
2318         return coyoteRequest.method().toString();
2319     }
2320
2321
2322     /**
2323      * @return the path information associated with this Request.
2324      */

2325     @Override
2326     public String getPathInfo() {
2327         return mappingData.pathInfo.toString();
2328     }
2329
2330
2331     /**
2332      * @return the extra path information for this request, translated
2333      * to a real path.
2334      */

2335     @Override
2336     public String getPathTranslated() {
2337
2338         Context context = getContext();
2339         if (context == null) {
2340             return null;
2341         }
2342
2343         if (getPathInfo() == null) {
2344             return null;
2345         }
2346
2347         return context.getServletContext().getRealPath(getPathInfo());
2348     }
2349
2350
2351     /**
2352      * @return the query string associated with this request.
2353      */

2354     @Override
2355     public String getQueryString() {
2356         return coyoteRequest.queryString().toString();
2357     }
2358
2359
2360     /**
2361      * @return the name of the remote user that has been authenticated
2362      * for this Request.
2363      */

2364     @Override
2365     public String getRemoteUser() {
2366
2367         if (userPrincipal == null) {
2368             return null;
2369         }
2370
2371         return userPrincipal.getName();
2372     }
2373
2374
2375     /**
2376      * Get the request path.
2377      *
2378      * @return the request path
2379      */

2380     public MessageBytes getRequestPathMB() {
2381         return mappingData.requestPath;
2382     }
2383
2384
2385     /**
2386      * @return the session identifier included in this request, if any.
2387      */

2388     @Override
2389     public String getRequestedSessionId() {
2390         return requestedSessionId;
2391     }
2392
2393
2394     /**
2395      * @return the request URI for this request.
2396      */

2397     @Override
2398     public String getRequestURI() {
2399         return coyoteRequest.requestURI().toString();
2400     }
2401
2402
2403     @Override
2404     public StringBuffer getRequestURL() {
2405         return RequestUtil.getRequestURL(this);
2406     }
2407
2408
2409     /**
2410      * @return the portion of the request URI used to select the servlet
2411      * that will process this request.
2412      */

2413     @Override
2414     public String getServletPath() {
2415         return mappingData.wrapperPath.toString();
2416     }
2417
2418
2419     /**
2420      * @return the session associated with this Request, creating one
2421      * if necessary.
2422      */

2423     @Override
2424     public HttpSession getSession() {
2425         Session session = doGetSession(true);
2426         if (session == null) {
2427             return null;
2428         }
2429
2430         return session.getSession();
2431     }
2432
2433
2434     /**
2435      * @return the session associated with this Request, creating one
2436      * if necessary and requested.
2437      *
2438      * @param create Create a new session if one does not exist
2439      */

2440     @Override
2441     public HttpSession getSession(boolean create) {
2442         Session session = doGetSession(create);
2443         if (session == null) {
2444             return null;
2445         }
2446
2447         return session.getSession();
2448     }
2449
2450
2451     /**
2452      * @return <code>true</code> if the session identifier included in this
2453      * request came from a cookie.
2454      */

2455     @Override
2456     public boolean isRequestedSessionIdFromCookie() {
2457
2458         if (requestedSessionId == null) {
2459             return false;
2460         }
2461
2462         return requestedSessionCookie;
2463     }
2464
2465
2466     /**
2467      * @return <code>true</code> if the session identifier included in this
2468      * request came from the request URI.
2469      */

2470     @Override
2471     public boolean isRequestedSessionIdFromURL() {
2472
2473         if (requestedSessionId == null) {
2474             return false;
2475         }
2476
2477         return requestedSessionURL;
2478     }
2479
2480
2481     /**
2482      * @return <code>true</code> if the session identifier included in this
2483      * request came from the request URI.
2484      *
2485      * @deprecated As of Version 2.1 of the Java Servlet API, use
2486      *  <code>isRequestedSessionIdFromURL()</code> instead.
2487      */

2488     @Override
2489     @Deprecated
2490     public boolean isRequestedSessionIdFromUrl() {
2491         return isRequestedSessionIdFromURL();
2492     }
2493
2494
2495     /**
2496      * @return <code>true</code> if the session identifier included in this
2497      * request identifies a valid session.
2498      */

2499     @Override
2500     public boolean isRequestedSessionIdValid() {
2501
2502         if (requestedSessionId == null) {
2503             return false;
2504         }
2505
2506         Context context = getContext();
2507         if (context == null) {
2508             return false;
2509         }
2510
2511         Manager manager = context.getManager();
2512         if (manager == null) {
2513             return false;
2514         }
2515
2516         Session session = null;
2517         try {
2518             session = manager.findSession(requestedSessionId);
2519         } catch (IOException e) {
2520             // Can't find the session
2521         }
2522
2523         if ((session == null) || !session.isValid()) {
2524             // Check for parallel deployment contexts
2525             if (getMappingData().contexts == null) {
2526                 return false;
2527             } else {
2528                 for (int i = (getMappingData().contexts.length); i > 0; i--) {
2529                     Context ctxt = getMappingData().contexts[i - 1];
2530                     try {
2531                         if (ctxt.getManager().findSession(requestedSessionId) !=
2532                                 null) {
2533                             return true;
2534                         }
2535                     } catch (IOException e) {
2536                         // Ignore
2537                     }
2538                 }
2539                 return false;
2540             }
2541         }
2542
2543         return true;
2544     }
2545
2546
2547     /**
2548      * @return <code>true</code> if the authenticated user principal
2549      * possesses the specified role name.
2550      *
2551      * @param role Role name to be validated
2552      */

2553     @Override
2554     public boolean isUserInRole(String role) {
2555
2556         // Have we got an authenticated principal at all?
2557         if (userPrincipal == null) {
2558             return false;
2559         }
2560
2561         // Identify the Realm we will use for checking role assignments
2562         Context context = getContext();
2563         if (context == null) {
2564             return false;
2565         }
2566
2567         // If the role is "*" then the return value must be false
2568         // Servlet 31, section 13.3
2569         if ("*".equals(role)) {
2570             return false;
2571         }
2572
2573         // If the role is "**" then, unless the application defines a role with
2574         // that name, only check if the user is authenticated
2575         if ("**".equals(role) && !context.findSecurityRole("**")) {
2576             return userPrincipal != null;
2577         }
2578
2579         Realm realm = context.getRealm();
2580         if (realm == null) {
2581             return false;
2582         }
2583
2584         // Check for a role defined directly as a <security-role>
2585         return realm.hasRole(getWrapper(), userPrincipal, role);
2586     }
2587
2588
2589     /**
2590      * @return the principal that has been authenticated for this Request.
2591      */

2592     public Principal getPrincipal() {
2593         return userPrincipal;
2594     }
2595
2596
2597     /**
2598      * @return the principal that has been authenticated for this Request.
2599      */

2600     @Override
2601     public Principal getUserPrincipal() {
2602         if (userPrincipal instanceof TomcatPrincipal) {
2603             GSSCredential gssCredential =
2604                     ((TomcatPrincipal) userPrincipal).getGssCredential();
2605             if (gssCredential != null) {
2606                 int left = -1;
2607                 try {
2608                     left = gssCredential.getRemainingLifetime();
2609                 } catch (GSSException e) {
2610                     log.warn(sm.getString("coyoteRequest.gssLifetimeFail",
2611                             userPrincipal.getName()), e);
2612                 }
2613                 if (left == 0) {
2614                     // GSS credential has expired. Need to re-authenticate.
2615                     try {
2616                         logout();
2617                     } catch (ServletException e) {
2618                         // Should never happen (no code called by logout()
2619                         // throws a ServletException
2620                     }
2621                     return null;
2622                 }
2623             }
2624             return ((TomcatPrincipal) userPrincipal).getUserPrincipal();
2625         }
2626
2627         return userPrincipal;
2628     }
2629
2630
2631     /**
2632      * @return the session associated with this Request, creating one
2633      * if necessary.
2634      */

2635     public Session getSessionInternal() {
2636         return doGetSession(true);
2637     }
2638
2639
2640     /**
2641      * Change the ID of the session that this request is associated with. There
2642      * are several things that may trigger an ID change. These include moving
2643      * between nodes in a cluster and session fixation prevention during the
2644      * authentication process.
2645      *
2646      * @param newSessionId   The session to change the session ID for
2647      */

2648     public void changeSessionId(String newSessionId) {
2649         // This should only ever be called if there was an old session ID but
2650         // double check to be sure
2651         if (requestedSessionId != null && requestedSessionId.length() > 0) {
2652             requestedSessionId = newSessionId;
2653         }
2654
2655         Context context = getContext();
2656         if (context != null &&
2657                 !context.getServletContext()
2658                         .getEffectiveSessionTrackingModes()
2659                         .contains(SessionTrackingMode.COOKIE)) {
2660             return;
2661         }
2662
2663         if (response != null) {
2664             Cookie newCookie = ApplicationSessionCookieConfig.createSessionCookie(context,
2665                     newSessionId, isSecure());
2666             response.addSessionCookieInternal(newCookie);
2667         }
2668     }
2669
2670
2671     @Override
2672     public String changeSessionId() {
2673
2674         Session session = this.getSessionInternal(false);
2675         if (session == null) {
2676             throw new IllegalStateException(
2677                 sm.getString("coyoteRequest.changeSessionId"));
2678         }
2679
2680         Manager manager = this.getContext().getManager();
2681
2682         String newSessionId = manager.rotateSessionId(session);
2683         this.changeSessionId(newSessionId);
2684
2685         return newSessionId;
2686     }
2687
2688     /**
2689      * @return the session associated with this Request, creating one
2690      * if necessary and requested.
2691      *
2692      * @param create Create a new session if one does not exist
2693      */

2694     public Session getSessionInternal(boolean create) {
2695         return doGetSession(create);
2696     }
2697
2698
2699     /**
2700      * @return <code>true</code> if we have parsed parameters
2701      */

2702     public boolean isParametersParsed() {
2703         return parametersParsed;
2704     }
2705
2706
2707     /**
2708      * @return <code>true</code> if an attempt has been made to read the request
2709      *         body and all of the request body has been read.
2710      */

2711     public boolean isFinished() {
2712         return coyoteRequest.isFinished();
2713     }
2714
2715
2716     /**
2717      * Check the configuration for aborted uploads and if configured to do so,
2718      * disable the swallowing of any remaining input and close the connection
2719      * once the response has been written.
2720      */

2721     protected void checkSwallowInput() {
2722         Context context = getContext();
2723         if (context != null && !context.getSwallowAbortedUploads()) {
2724             coyoteRequest.action(ActionCode.DISABLE_SWALLOW_INPUT, null);
2725         }
2726     }
2727
2728     /**
2729      * {@inheritDoc}
2730      */

2731     @Override
2732     public boolean authenticate(HttpServletResponse response)
2733             throws IOException, ServletException {
2734         if (response.isCommitted()) {
2735             throw new IllegalStateException(
2736                     sm.getString("coyoteRequest.authenticate.ise"));
2737         }
2738
2739         return getContext().getAuthenticator().authenticate(this, response);
2740     }
2741
2742     /**
2743      * {@inheritDoc}
2744      */

2745     @Override
2746     public void login(String username, String password)
2747             throws ServletException {
2748         if (getAuthType() != null || getRemoteUser() != null ||
2749                 getUserPrincipal() != null) {
2750             throw new ServletException(
2751                     sm.getString("coyoteRequest.alreadyAuthenticated"));
2752         }
2753
2754         getContext().getAuthenticator().login(username, password, this);
2755     }
2756
2757     /**
2758      * {@inheritDoc}
2759      */

2760     @Override
2761     public void logout() throws ServletException {
2762         getContext().getAuthenticator().logout(this);
2763     }
2764
2765     /**
2766      * {@inheritDoc}
2767      */

2768     @Override
2769     public Collection<Part> getParts() throws IOException, IllegalStateException,
2770             ServletException {
2771
2772         parseParts(true);
2773
2774         if (partsParseException != null) {
2775             if (partsParseException instanceof IOException) {
2776                 throw (IOException) partsParseException;
2777             } else if (partsParseException instanceof IllegalStateException) {
2778                 throw (IllegalStateException) partsParseException;
2779             } else if (partsParseException instanceof ServletException) {
2780                 throw (ServletException) partsParseException;
2781             }
2782         }
2783
2784         return parts;
2785     }
2786
2787     private void parseParts(boolean explicit) {
2788
2789         // Return immediately if the parts have already been parsed
2790         if (parts != null || partsParseException != null) {
2791             return;
2792         }
2793
2794         Context context = getContext();
2795         MultipartConfigElement mce = getWrapper().getMultipartConfigElement();
2796
2797         if (mce == null) {
2798             if(context.getAllowCasualMultipartParsing()) {
2799                 mce = new MultipartConfigElement(null, connector.getMaxPostSize(),
2800                         connector.getMaxPostSize(), connector.getMaxPostSize());
2801             } else {
2802                 if (explicit) {
2803                     partsParseException = new IllegalStateException(
2804                             sm.getString("coyoteRequest.noMultipartConfig"));
2805                     return;
2806                 } else {
2807                     parts = Collections.emptyList();
2808                     return;
2809                 }
2810             }
2811         }
2812
2813         Parameters parameters = coyoteRequest.getParameters();
2814         parameters.setLimit(getConnector().getMaxParameterCount());
2815
2816         boolean success = false;
2817         try {
2818             File location;
2819             String locationStr = mce.getLocation();
2820             if (locationStr == null || locationStr.length() == 0) {
2821                 location = ((File) context.getServletContext().getAttribute(
2822                         ServletContext.TEMPDIR));
2823             } else {
2824                 // If relative, it is relative to TEMPDIR
2825                 location = new File(locationStr);
2826                 if (!location.isAbsolute()) {
2827                     location = new File(
2828                             (File) context.getServletContext().getAttribute(ServletContext.TEMPDIR),
2829                             locationStr).getAbsoluteFile();
2830                 }
2831             }
2832
2833             if (!location.exists() && context.getCreateUploadTargets()) {
2834                 log.warn(sm.getString("coyoteRequest.uploadCreate",
2835                         location.getAbsolutePath(), getMappingData().wrapper.getName()));
2836                 if (!location.mkdirs()) {
2837                     log.warn(sm.getString("coyoteRequest.uploadCreateFail",
2838                             location.getAbsolutePath()));
2839                 }
2840             }
2841
2842             if (!location.isDirectory()) {
2843                 parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);
2844                 partsParseException = new IOException(
2845                         sm.getString("coyoteRequest.uploadLocationInvalid",
2846                                 location));
2847                 return;
2848             }
2849
2850
2851             // Create a new file upload handler
2852             DiskFileItemFactory factory = new DiskFileItemFactory();
2853             try {
2854                 factory.setRepository(location.getCanonicalFile());
2855             } catch (IOException ioe) {
2856                 parameters.setParseFailedReason(FailReason.IO_ERROR);
2857                 partsParseException = ioe;
2858                 return;
2859             }
2860             factory.setSizeThreshold(mce.getFileSizeThreshold());
2861
2862             ServletFileUpload upload = new ServletFileUpload();
2863             upload.setFileItemFactory(factory);
2864             upload.setFileSizeMax(mce.getMaxFileSize());
2865             upload.setSizeMax(mce.getMaxRequestSize());
2866
2867             parts = new ArrayList<>();
2868             try {
2869                 List<FileItem> items =
2870                         upload.parseRequest(new ServletRequestContext(this));
2871                 int maxPostSize = getConnector().getMaxPostSize();
2872                 int postSize = 0;
2873                 Charset charset = getCharset();
2874                 for (FileItem item : items) {
2875                     ApplicationPart part = new ApplicationPart(item, location);
2876                     parts.add(part);
2877                     if (part.getSubmittedFileName() == null) {
2878                         String name = part.getName();
2879                         String value = null;
2880                         try {
2881                             value = part.getString(charset.name());
2882                         } catch (UnsupportedEncodingException uee) {
2883                             // Not possible
2884                         }
2885                         if (maxPostSize >= 0) {
2886                             // Have to calculate equivalent size. Not completely
2887                             // accurate but close enough.
2888                             postSize += name.getBytes(charset).length;
2889                             if (value != null) {
2890                                 // Equals sign
2891                                 postSize++;
2892                                 // Value length
2893                                 postSize += part.getSize();
2894                             }
2895                             // Value separator
2896                             postSize++;
2897                             if (postSize > maxPostSize) {
2898                                 parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
2899                                 throw new IllegalStateException(sm.getString(
2900                                         "coyoteRequest.maxPostSizeExceeded"));
2901                             }
2902                         }
2903                         parameters.addParameter(name, value);
2904                     }
2905                 }
2906
2907                 success = true;
2908             } catch (InvalidContentTypeException e) {
2909                 parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE);
2910                 partsParseException = new ServletException(e);
2911             } catch (SizeException e) {
2912                 parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
2913                 checkSwallowInput();
2914                 partsParseException = new IllegalStateException(e);
2915             } catch (FileUploadException e) {
2916                 parameters.setParseFailedReason(FailReason.IO_ERROR);
2917                 partsParseException = new IOException(e);
2918             } catch (IllegalStateException e) {
2919                 // addParameters() will set parseFailedReason
2920                 checkSwallowInput();
2921                 partsParseException = e;
2922             }
2923         } finally {
2924             // This might look odd but is correct. setParseFailedReason() only
2925             // sets the failure reason if none is currently set. This code could
2926             // be more efficient but it is written this way to be robust with
2927             // respect to changes in the remainder of the method.
2928             if (partsParseException != null || !success) {
2929                 parameters.setParseFailedReason(FailReason.UNKNOWN);
2930             }
2931         }
2932     }
2933
2934
2935     /**
2936      * {@inheritDoc}
2937      */

2938     @Override
2939     public Part getPart(String name) throws IOException, IllegalStateException,
2940             ServletException {
2941         for (Part part : getParts()) {
2942             if (name.equals(part.getName())) {
2943                 return part;
2944             }
2945         }
2946         return null;
2947     }
2948
2949
2950     // ------------------------------------------------------ Protected Methods
2951
2952     protected Session doGetSession(boolean create) {
2953
2954         // There cannot be a session if no context has been assigned yet
2955         Context context = getContext();
2956         if (context == null) {
2957             return null;
2958         }
2959
2960         // Return the current session if it exists and is valid
2961         if ((session != null) && !session.isValid()) {
2962             session = null;
2963         }
2964         if (session != null) {
2965             return session;
2966         }
2967
2968         // Return the requested session if it exists and is valid
2969         Manager manager = context.getManager();
2970         if (manager == null) {
2971             return null;      // Sessions are not supported
2972         }
2973         if (requestedSessionId != null) {
2974             try {
2975                 session = manager.findSession(requestedSessionId);
2976             } catch (IOException e) {
2977                 session = null;
2978             }
2979             if ((session != null) && !session.isValid()) {
2980                 session = null;
2981             }
2982             if (session != null) {
2983                 session.access();
2984                 return session;
2985             }
2986         }
2987
2988         // Create a new session if requested and the response is not committed
2989         if (!create) {
2990             return null;
2991         }
2992         boolean trackModesIncludesCookie =
2993                 context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
2994         if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
2995             throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
2996         }
2997
2998         // Re-use session IDs provided by the client in very limited
2999         // circumstances.
3000         String sessionId = getRequestedSessionId();
3001         if (requestedSessionSSL) {
3002             // If the session ID has been obtained from the SSL handshake then
3003             // use it.
3004         } else if (("/".equals(context.getSessionCookiePath())
3005                 && isRequestedSessionIdFromCookie())) {
3006             /* This is the common(ish) use case: using the same session ID with
3007              * multiple web applications on the same host. Typically this is
3008              * used by Portlet implementations. It only works if sessions are
3009              * tracked via cookies. The cookie must have a path of "/" else it
3010              * won't be provided for requests to all web applications.
3011              *
3012              * Any session ID provided by the client should be for a session
3013              * that already exists somewhere on the host. Check if the context
3014              * is configured for this to be confirmed.
3015              */

3016             if (context.getValidateClientProvidedNewSessionId()) {
3017                 boolean found = false;
3018                 for (Container container : getHost().findChildren()) {
3019                     Manager m = ((Context) container).getManager();
3020                     if (m != null) {
3021                         try {
3022                             if (m.findSession(sessionId) != null) {
3023                                 found = true;
3024                                 break;
3025                             }
3026                         } catch (IOException e) {
3027                             // Ignore. Problems with this manager will be
3028                             // handled elsewhere.
3029                         }
3030                     }
3031                 }
3032                 if (!found) {
3033                     sessionId = null;
3034                 }
3035             }
3036         } else {
3037             sessionId = null;
3038         }
3039         session = manager.createSession(sessionId);
3040
3041         // Creating a new session cookie based on that session
3042         if (session != null && trackModesIncludesCookie) {
3043             Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
3044                     context, session.getIdInternal(), isSecure());
3045
3046             response.addSessionCookieInternal(cookie);
3047         }
3048
3049         if (session == null) {
3050             return null;
3051         }
3052
3053         session.access();
3054         return session;
3055     }
3056
3057     protected String unescape(String s) {
3058         if (s==null) {
3059             return null;
3060         }
3061         if (s.indexOf('\\') == -1) {
3062             return s;
3063         }
3064         StringBuilder buf = new StringBuilder();
3065         for (int i=0; i<s.length(); i++) {
3066             char c = s.charAt(i);
3067             if (c!='\\') {
3068                 buf.append(c);
3069             } else {
3070                 if (++i >= s.length()) {
3071                     throw new IllegalArgumentException();//invalid escape, hence invalid cookie
3072                 }
3073                 c = s.charAt(i);
3074                 buf.append(c);
3075             }
3076         }
3077         return buf.toString();
3078     }
3079
3080     /**
3081      * Parse cookies. This only parses the cookies into the memory efficient
3082      * ServerCookies structure. It does not populate the Cookie objects.
3083      */

3084     protected void parseCookies() {
3085         if (cookiesParsed) {
3086             return;
3087         }
3088
3089         cookiesParsed = true;
3090
3091         ServerCookies serverCookies = coyoteRequest.getCookies();
3092         serverCookies.setLimit(connector.getMaxCookieCount());
3093         CookieProcessor cookieProcessor = getContext().getCookieProcessor();
3094         cookieProcessor.parseCookieHeader(coyoteRequest.getMimeHeaders(), serverCookies);
3095     }
3096
3097     /**
3098      * Converts the parsed cookies (parsing the Cookie headers first if they
3099      * have not been parsed) into Cookie objects.
3100      */

3101     protected void convertCookies() {
3102         if (cookiesConverted) {
3103             return;
3104         }
3105
3106         cookiesConverted = true;
3107
3108         if (getContext() == null) {
3109             return;
3110         }
3111
3112         parseCookies();
3113
3114         ServerCookies serverCookies = coyoteRequest.getCookies();
3115         CookieProcessor cookieProcessor = getContext().getCookieProcessor();
3116
3117         int count = serverCookies.getCookieCount();
3118         if (count <= 0) {
3119             return;
3120         }
3121
3122         cookies = new Cookie[count];
3123
3124         int idx=0;
3125         for (int i = 0; i < count; i++) {
3126             ServerCookie scookie = serverCookies.getCookie(i);
3127             try {
3128                 // We must unescape the '\\' escape character
3129                 Cookie cookie = new Cookie(scookie.getName().toString(),null);
3130                 int version = scookie.getVersion();
3131                 cookie.setVersion(version);
3132                 scookie.getValue().getByteChunk().setCharset(cookieProcessor.getCharset());
3133                 cookie.setValue(unescape(scookie.getValue().toString()));
3134                 cookie.setPath(unescape(scookie.getPath().toString()));
3135                 String domain = scookie.getDomain().toString();
3136                 if (domain!=null) {
3137                     cookie.setDomain(unescape(domain));//avoid NPE
3138                 }
3139                 String comment = scookie.getComment().toString();
3140                 cookie.setComment(version==1?unescape(comment):null);
3141                 cookies[idx++] = cookie;
3142             } catch(IllegalArgumentException e) {
3143                 // Ignore bad cookie
3144             }
3145         }
3146         if( idx < count ) {
3147             Cookie [] ncookies = new Cookie[idx];
3148             System.arraycopy(cookies, 0, ncookies, 0, idx);
3149             cookies = ncookies;
3150         }
3151     }
3152
3153
3154     /**
3155      * Parse request parameters.
3156      */

3157     protected void parseParameters() {
3158
3159         parametersParsed = true;
3160
3161         Parameters parameters = coyoteRequest.getParameters();
3162         boolean success = false;
3163         try {
3164             // Set this every time in case limit has been changed via JMX
3165             parameters.setLimit(getConnector().getMaxParameterCount());
3166
3167             // getCharacterEncoding() may have been overridden to search for
3168             // hidden form field containing request encoding
3169             Charset charset = getCharset();
3170
3171             boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
3172             parameters.setCharset(charset);
3173             if (useBodyEncodingForURI) {
3174                 parameters.setQueryStringCharset(charset);
3175             }
3176             // Note: If !useBodyEncodingForURI, the query string encoding is
3177             //       that set towards the start of CoyoyeAdapter.service()
3178
3179             parameters.handleQueryParameters();
3180
3181             if (usingInputStream || usingReader) {
3182                 success = true;
3183                 return;
3184             }
3185
3186             String contentType = getContentType();
3187             if (contentType == null) {
3188                 contentType = "";
3189             }
3190             int semicolon = contentType.indexOf(';');
3191             if (semicolon >= 0) {
3192                 contentType = contentType.substring(0, semicolon).trim();
3193             } else {
3194                 contentType = contentType.trim();
3195             }
3196
3197             if ("multipart/form-data".equals(contentType)) {
3198                 parseParts(false);
3199                 success = true;
3200                 return;
3201             }
3202
3203             if( !getConnector().isParseBodyMethod(getMethod()) ) {
3204                 success = true;
3205                 return;
3206             }
3207
3208             if (!("application/x-www-form-urlencoded".equals(contentType))) {
3209                 success = true;
3210                 return;
3211             }
3212
3213             int len = getContentLength();
3214
3215             if (len > 0) {
3216                 int maxPostSize = connector.getMaxPostSize();
3217                 if ((maxPostSize >= 0) && (len > maxPostSize)) {
3218                     Context context = getContext();
3219                     if (context != null && context.getLogger().isDebugEnabled()) {
3220                         context.getLogger().debug(
3221                                 sm.getString("coyoteRequest.postTooLarge"));
3222                     }
3223                     checkSwallowInput();
3224                     parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
3225                     return;
3226                 }
3227                 byte[] formData = null;
3228                 if (len < CACHED_POST_LEN) {
3229                     if (postData == null) {
3230                         postData = new byte[CACHED_POST_LEN];
3231                     }
3232                     formData = postData;
3233                 } else {
3234                     formData = new byte[len];
3235                 }
3236                 try {
3237                     if (readPostBody(formData, len) != len) {
3238                         parameters.setParseFailedReason(FailReason.REQUEST_BODY_INCOMPLETE);
3239                         return;
3240                     }
3241                 } catch (IOException e) {
3242                     // Client disconnect
3243                     Context context = getContext();
3244                     if (context != null && context.getLogger().isDebugEnabled()) {
3245                         context.getLogger().debug(
3246                                 sm.getString("coyoteRequest.parseParameters"), e);
3247                     }
3248                     parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
3249                     return;
3250                 }
3251                 parameters.processParameters(formData, 0, len);
3252             } else if ("chunked".equalsIgnoreCase(
3253                     coyoteRequest.getHeader("transfer-encoding"))) {
3254                 byte[] formData = null;
3255                 try {
3256                     formData = readChunkedPostBody();
3257                 } catch (IllegalStateException ise) {
3258                     // chunkedPostTooLarge error
3259                     parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);
3260                     Context context = getContext();
3261                     if (context != null && context.getLogger().isDebugEnabled()) {
3262                         context.getLogger().debug(
3263                                 sm.getString("coyoteRequest.parseParameters"),
3264                                 ise);
3265                     }
3266                     return;
3267                 } catch (IOException e) {
3268                     // Client disconnect
3269                     parameters.setParseFailedReason(FailReason.CLIENT_DISCONNECT);
3270                     Context context = getContext();
3271                     if (context != null && context.getLogger().isDebugEnabled()) {
3272                         context.getLogger().debug(
3273                                 sm.getString("coyoteRequest.parseParameters"), e);
3274                     }
3275                     return;
3276                 }
3277                 if (formData != null) {
3278                     parameters.processParameters(formData, 0, formData.length);
3279                 }
3280             }
3281             success = true;
3282         } finally {
3283             if (!success) {
3284                 parameters.setParseFailedReason(FailReason.UNKNOWN);
3285             }
3286         }
3287
3288     }
3289
3290
3291     /**
3292      * Read post body in an array.
3293      *
3294      * @param body The bytes array in which the body will be read
3295      * @param len The body length
3296      * @return the bytes count that has been read
3297      * @throws IOException if an IO exception occurred
3298      */

3299     protected int readPostBody(byte[] body, int len)
3300             throws IOException {
3301
3302         int offset = 0;
3303         do {
3304             int inputLen = getStream().read(body, offset, len - offset);
3305             if (inputLen <= 0) {
3306                 return offset;
3307             }
3308             offset += inputLen;
3309         } while ((len - offset) > 0);
3310         return len;
3311
3312     }
3313
3314
3315     /**
3316      * Read chunked post body.
3317      *
3318      * @return the post body as a bytes array
3319      * @throws IOException if an IO exception occurred
3320      */

3321     protected byte[] readChunkedPostBody() throws IOException {
3322         ByteChunk body = new ByteChunk();
3323
3324         byte[] buffer = new byte[CACHED_POST_LEN];
3325
3326         int len = 0;
3327         while (len > -1) {
3328             len = getStream().read(buffer, 0, CACHED_POST_LEN);
3329             if (connector.getMaxPostSize() >= 0 &&
3330                     (body.getLength() + len) > connector.getMaxPostSize()) {
3331                 // Too much data
3332                 checkSwallowInput();
3333                 throw new IllegalStateException(
3334                         sm.getString("coyoteRequest.chunkedPostTooLarge"));
3335             }
3336             if (len > 0) {
3337                 body.append(buffer, 0, len);
3338             }
3339         }
3340         if (body.getLength() == 0) {
3341             return null;
3342         }
3343         if (body.getLength() < body.getBuffer().length) {
3344             int length = body.getLength();
3345             byte[] result = new byte[length];
3346             System.arraycopy(body.getBuffer(), 0, result, 0, length);
3347             return result;
3348         }
3349
3350         return body.getBuffer();
3351     }
3352
3353
3354     /**
3355      * Parse request locales.
3356      */

3357     protected void parseLocales() {
3358
3359         localesParsed = true;
3360
3361         // Store the accumulated languages that have been requested in
3362         // a local collection, sorted by the quality value (so we can
3363         // add Locales in descending order).  The values will be ArrayLists
3364         // containing the corresponding Locales to be added
3365         TreeMap<Double, ArrayList<Locale>> locales = new TreeMap<>();
3366
3367         Enumeration<String> values = getHeaders("accept-language");
3368
3369         while (values.hasMoreElements()) {
3370             String value = values.nextElement();
3371             parseLocalesHeader(value, locales);
3372         }
3373
3374         // Process the quality values in highest->lowest order (due to
3375         // negating the Double value when creating the key)
3376         for (ArrayList<Locale> list : locales.values()) {
3377             for (Locale locale : list) {
3378                 addLocale(locale);
3379             }
3380         }
3381     }
3382
3383
3384     /**
3385      * Parse accept-language header value.
3386      *
3387      * @param value the header value
3388      * @param locales the map that will hold the result
3389      */

3390     protected void parseLocalesHeader(String value, TreeMap<Double, ArrayList<Locale>> locales) {
3391
3392         List<AcceptLanguage> acceptLanguages;
3393         try {
3394             acceptLanguages = AcceptLanguage.parse(new StringReader(value));
3395         } catch (IOException e) {
3396             // Mal-formed headers are ignore. Do the same in the unlikely event
3397             // of an IOException.
3398             return;
3399         }
3400
3401         for (AcceptLanguage acceptLanguage : acceptLanguages) {
3402             // Add a new Locale to the list of Locales for this quality level
3403             Double key = Double.valueOf(-acceptLanguage.getQuality());  // Reverse the order
3404             ArrayList<Locale> values = locales.get(key);
3405             if (values == null) {
3406                 values = new ArrayList<>();
3407                 locales.put(key, values);
3408             }
3409             values.add(acceptLanguage.getLocale());
3410         }
3411     }
3412
3413
3414     // ----------------------------------------------------- Special attributes handling
3415
3416     private static interface SpecialAttributeAdapter {
3417         Object get(Request request, String name);
3418
3419         void set(Request request, String name, Object value);
3420
3421         // None of special attributes support removal
3422         // void remove(Request request, String name);
3423     }
3424
3425     private static final Map<String, SpecialAttributeAdapter> specialAttributes = new HashMap<>();
3426
3427     static {
3428         specialAttributes.put(Globals.DISPATCHER_TYPE_ATTR,
3429                 new SpecialAttributeAdapter() {
3430                     @Override
3431                     public Object get(Request request, String name) {
3432                         return (request.internalDispatcherType == null) ? DispatcherType.REQUEST
3433                                 : request.internalDispatcherType;
3434                     }
3435
3436                     @Override
3437                     public void set(Request request, String name, Object value) {
3438                         request.internalDispatcherType = (DispatcherType) value;
3439                     }
3440                 });
3441         specialAttributes.put(Globals.DISPATCHER_REQUEST_PATH_ATTR,
3442                 new SpecialAttributeAdapter() {
3443                     @Override
3444                     public Object get(Request request, String name) {
3445                         return (request.requestDispatcherPath == null) ? request
3446                                 .getRequestPathMB().toString()
3447                                 : request.requestDispatcherPath.toString();
3448                     }
3449
3450                     @Override
3451                     public void set(Request request, String name, Object value) {
3452                         request.requestDispatcherPath = value;
3453                     }
3454                 });
3455         specialAttributes.put(Globals.ASYNC_SUPPORTED_ATTR,
3456                 new SpecialAttributeAdapter() {
3457                     @Override
3458                     public Object get(Request request, String name) {
3459                         return request.asyncSupported;
3460                     }
3461
3462                     @Override
3463                     public void set(Request request, String name, Object value) {
3464                         Boolean oldValue = request.asyncSupported;
3465                         request.asyncSupported = (Boolean)value;
3466                         request.notifyAttributeAssigned(name, value, oldValue);
3467                     }
3468                 });
3469         specialAttributes.put(Globals.GSS_CREDENTIAL_ATTR,
3470                 new SpecialAttributeAdapter() {
3471                     @Override
3472                     public Object get(Request request, String name) {
3473                         if (request.userPrincipal instanceof TomcatPrincipal) {
3474                             return ((TomcatPrincipal) request.userPrincipal)
3475                                     .getGssCredential();
3476                         }
3477                         return null;
3478                     }
3479
3480                     @Override
3481                     public void set(Request request, String name, Object value) {
3482                         // NO-OP
3483                     }
3484                 });
3485         specialAttributes.put(Globals.PARAMETER_PARSE_FAILED_ATTR,
3486                 new SpecialAttributeAdapter() {
3487                     @Override
3488                     public Object get(Request request, String name) {
3489                         if (request.getCoyoteRequest().getParameters()
3490                                 .isParseFailed()) {
3491                             return Boolean.TRUE;
3492                         }
3493                         return null;
3494                     }
3495
3496                     @Override
3497                     public void set(Request request, String name, Object value) {
3498                         // NO-OP
3499                     }
3500                 });
3501         specialAttributes.put(Globals.PARAMETER_PARSE_FAILED_REASON_ATTR,
3502                 new SpecialAttributeAdapter() {
3503                     @Override
3504                     public Object get(Request request, String name) {
3505                         return request.getCoyoteRequest().getParameters().getParseFailedReason();
3506                     }
3507
3508                     @Override
3509                     public void set(Request request, String name, Object value) {
3510                         // NO-OP
3511                     }
3512                 });
3513         specialAttributes.put(Globals.SENDFILE_SUPPORTED_ATTR,
3514                 new SpecialAttributeAdapter() {
3515                     @Override
3516                     public Object get(Request request, String name) {
3517                         return Boolean.valueOf(
3518                                 request.getConnector().getProtocolHandler(
3519                                         ).isSendfileSupported() && request.getCoyoteRequest().getSendfile());
3520                     }
3521                     @Override
3522                     public void set(Request request, String name, Object value) {
3523                         // NO-OP
3524                     }
3525                 });
3526
3527         for (SimpleDateFormat sdf : formatsTemplate) {
3528             sdf.setTimeZone(GMT_ZONE);
3529         }
3530     }
3531 }
3532