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.core;
18
19 import java.io.InputStream;
20 import java.lang.reflect.InvocationTargetException;
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import java.nio.charset.StandardCharsets;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.EnumSet;
27 import java.util.Enumeration;
28 import java.util.EventListener;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.concurrent.ConcurrentHashMap;
35
36 import javax.naming.NamingException;
37 import javax.servlet.Filter;
38 import javax.servlet.FilterRegistration;
39 import javax.servlet.RequestDispatcher;
40 import javax.servlet.Servlet;
41 import javax.servlet.ServletContext;
42 import javax.servlet.ServletContextAttributeEvent;
43 import javax.servlet.ServletContextAttributeListener;
44 import javax.servlet.ServletContextListener;
45 import javax.servlet.ServletException;
46 import javax.servlet.ServletRegistration;
47 import javax.servlet.ServletRegistration.Dynamic;
48 import javax.servlet.ServletRequestAttributeListener;
49 import javax.servlet.ServletRequestListener;
50 import javax.servlet.ServletSecurityElement;
51 import javax.servlet.SessionCookieConfig;
52 import javax.servlet.SessionTrackingMode;
53 import javax.servlet.annotation.ServletSecurity;
54 import javax.servlet.descriptor.JspConfigDescriptor;
55 import javax.servlet.http.HttpServletMapping;
56 import javax.servlet.http.HttpSessionAttributeListener;
57 import javax.servlet.http.HttpSessionIdListener;
58 import javax.servlet.http.HttpSessionListener;
59
60 import org.apache.catalina.Container;
61 import org.apache.catalina.Context;
62 import org.apache.catalina.Engine;
63 import org.apache.catalina.Globals;
64 import org.apache.catalina.LifecycleState;
65 import org.apache.catalina.Service;
66 import org.apache.catalina.WebResourceRoot;
67 import org.apache.catalina.Wrapper;
68 import org.apache.catalina.connector.Connector;
69 import org.apache.catalina.mapper.MappingData;
70 import org.apache.catalina.util.Introspection;
71 import org.apache.catalina.util.ServerInfo;
72 import org.apache.catalina.util.URLEncoder;
73 import org.apache.tomcat.util.ExceptionUtils;
74 import org.apache.tomcat.util.buf.CharChunk;
75 import org.apache.tomcat.util.buf.MessageBytes;
76 import org.apache.tomcat.util.buf.UDecoder;
77 import org.apache.tomcat.util.descriptor.web.FilterDef;
78 import org.apache.tomcat.util.http.RequestUtil;
79 import org.apache.tomcat.util.res.StringManager;
80
81
82 /**
83  * Standard implementation of <code>ServletContext</code> that represents
84  * a web application's execution environment.  An instance of this class is
85  * associated with each instance of <code>StandardContext</code>.
86  *
87  * @author Craig R. McClanahan
88  * @author Remy Maucherat
89  */

90 public class ApplicationContext implements ServletContext {
91
92     protected static final boolean STRICT_SERVLET_COMPLIANCE;
93
94     protected static final boolean GET_RESOURCE_REQUIRE_SLASH;
95
96
97     static {
98         STRICT_SERVLET_COMPLIANCE = Globals.STRICT_SERVLET_COMPLIANCE;
99
100         String requireSlash = System.getProperty(
101                 "org.apache.catalina.core.ApplicationContext.GET_RESOURCE_REQUIRE_SLASH");
102         if (requireSlash == null) {
103             GET_RESOURCE_REQUIRE_SLASH = STRICT_SERVLET_COMPLIANCE;
104         } else {
105             GET_RESOURCE_REQUIRE_SLASH = Boolean.parseBoolean(requireSlash);
106         }
107     }
108
109     // ----------------------------------------------------------- Constructors
110
111
112     /**
113      * Construct a new instance of this class, associated with the specified
114      * Context instance.
115      *
116      * @param context The associated Context instance
117      */

118     public ApplicationContext(StandardContext context) {
119         super();
120         this.context = context;
121         this.service = ((Engine) context.getParent().getParent()).getService();
122         this.sessionCookieConfig = new ApplicationSessionCookieConfig(context);
123
124         // Populate session tracking modes
125         populateSessionTrackingModes();
126     }
127
128
129     // ----------------------------------------------------- Instance Variables
130
131
132     /**
133      * The context attributes for this context.
134      */

135     protected Map<String,Object> attributes = new ConcurrentHashMap<>();
136
137
138     /**
139      * List of read only attributes for this context.
140      */

141     private final Map<String,String> readOnlyAttributes = new ConcurrentHashMap<>();
142
143
144     /**
145      * The Context instance with which we are associated.
146      */

147     private final StandardContext context;
148
149
150     /**
151      * The Service instance with which we are associated.
152      */

153     private final Service service;
154
155
156     /**
157      * Empty String collection to serve as the basis for empty enumerations.
158      */

159     private static final List<String> emptyString = Collections.emptyList();
160
161
162     /**
163      * Empty Servlet collection to serve as the basis for empty enumerations.
164      */

165     private static final List<Servlet> emptyServlet = Collections.emptyList();
166
167
168     /**
169      * The facade around this object.
170      */

171     private final ServletContext facade = new ApplicationContextFacade(this);
172
173
174     /**
175      * The merged context initialization parameters for this Context.
176      */

177     private final Map<String,String> parameters = new ConcurrentHashMap<>();
178
179
180     /**
181      * The string manager for this package.
182      */

183     private static final StringManager sm = StringManager.getManager(Constants.Package);
184
185
186     /**
187      * Thread local data used during request dispatch.
188      */

189     private final ThreadLocal<DispatchData> dispatchData = new ThreadLocal<>();
190
191
192     /**
193      * Session Cookie config
194      */

195     private SessionCookieConfig sessionCookieConfig;
196
197     /**
198      * Session tracking modes
199      */

200     private Set<SessionTrackingMode> sessionTrackingModes = null;
201     private Set<SessionTrackingMode> defaultSessionTrackingModes = null;
202     private Set<SessionTrackingMode> supportedSessionTrackingModes = null;
203
204     /**
205      * Flag that indicates if a new {@link ServletContextListener} may be added
206      * to the application. Once the first {@link ServletContextListener} is
207      * called, no more may be added.
208      */

209     private boolean newServletContextListenerAllowed = true;
210
211
212     // ------------------------------------------------- ServletContext Methods
213
214     @Override
215     public Object getAttribute(String name) {
216         return attributes.get(name);
217     }
218
219
220     @Override
221     public Enumeration<String> getAttributeNames() {
222         Set<String> names = new HashSet<>();
223         names.addAll(attributes.keySet());
224         return Collections.enumeration(names);
225     }
226
227
228     @Override
229     public ServletContext getContext(String uri) {
230
231         // Validate the format of the specified argument
232         if (uri == null || !uri.startsWith("/")) {
233             return null;
234         }
235
236         Context child = null;
237         try {
238             // Look for an exact match
239             Container host = context.getParent();
240             child = (Context) host.findChild(uri);
241
242             // Non-running contexts should be ignored.
243             if (child != null && !child.getState().isAvailable()) {
244                 child = null;
245             }
246
247             // Remove any version information and use the mapper
248             if (child == null) {
249                 int i = uri.indexOf("##");
250                 if (i > -1) {
251                     uri = uri.substring(0, i);
252                 }
253                 // Note: This could be more efficient with a dedicated Mapper
254                 //       method but such an implementation would require some
255                 //       refactoring of the Mapper to avoid copy/paste of
256                 //       existing code.
257                 MessageBytes hostMB = MessageBytes.newInstance();
258                 hostMB.setString(host.getName());
259
260                 MessageBytes pathMB = MessageBytes.newInstance();
261                 pathMB.setString(uri);
262
263                 MappingData mappingData = new MappingData();
264                 service.getMapper().map(hostMB, pathMB, null, mappingData);
265                 child = mappingData.context;
266             }
267         } catch (Throwable t) {
268             ExceptionUtils.handleThrowable(t);
269             return null;
270         }
271
272         if (child == null) {
273             return null;
274         }
275
276         if (context.getCrossContext()) {
277             // If crossContext is enabled, can always return the context
278             return child.getServletContext();
279         } else if (child == context) {
280             // Can still return the current context
281             return context.getServletContext();
282         } else {
283             // Nothing to return
284             return null;
285         }
286     }
287
288
289     @Override
290     public String getContextPath() {
291         return context.getPath();
292     }
293
294
295     @Override
296     public String getInitParameter(final String name) {
297         // Special handling for XML settings as the context setting must
298         // always override anything that might have been set by an application.
299         if (Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM.equals(name) &&
300                 context.getTldValidation()) {
301             return "true";
302         }
303         if (Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM.equals(name)) {
304             if (!context.getXmlBlockExternal()) {
305                 // System admin has explicitly changed the default
306                 return "false";
307             }
308         }
309         return parameters.get(name);
310     }
311
312
313     @Override
314     public Enumeration<String> getInitParameterNames() {
315         Set<String> names = new HashSet<>();
316         names.addAll(parameters.keySet());
317         // Special handling for XML settings as these attributes will always be
318         // available if they have been set on the context
319         if (context.getTldValidation()) {
320             names.add(Globals.JASPER_XML_VALIDATION_TLD_INIT_PARAM);
321         }
322         if (!context.getXmlBlockExternal()) {
323             names.add(Globals.JASPER_XML_BLOCK_EXTERNAL_INIT_PARAM);
324         }
325         return Collections.enumeration(names);
326     }
327
328
329     @Override
330     public int getMajorVersion() {
331         return Constants.MAJOR_VERSION;
332     }
333
334
335     @Override
336     public int getMinorVersion() {
337         return Constants.MINOR_VERSION;
338     }
339
340
341     /**
342      * Return the MIME type of the specified file, or <code>null</code> if
343      * the MIME type cannot be determined.
344      *
345      * @param file Filename for which to identify a MIME type
346      */

347     @Override
348     public String getMimeType(String file) {
349
350         if (file == null)
351             return null;
352         int period = file.lastIndexOf('.');
353         if (period < 0)
354             return null;
355         String extension = file.substring(period + 1);
356         if (extension.length() < 1)
357             return null;
358         return context.findMimeMapping(extension);
359
360     }
361
362
363     /**
364      * Return a <code>RequestDispatcher</code> object that acts as a
365      * wrapper for the named servlet.
366      *
367      * @param name Name of the servlet for which a dispatcher is requested
368      */

369     @Override
370     public RequestDispatcher getNamedDispatcher(String name) {
371
372         // Validate the name argument
373         if (name == null)
374             return null;
375
376         // Create and return a corresponding request dispatcher
377         Wrapper wrapper = (Wrapper) context.findChild(name);
378         if (wrapper == null)
379             return null;
380
381         return new ApplicationDispatcher(wrapper, nullnullnullnullnull, name);
382
383     }
384
385
386     @Override
387     public String getRealPath(String path) {
388         String validatedPath = validateResourcePath(path, true);
389         return context.getRealPath(validatedPath);
390     }
391
392
393     @Override
394     public RequestDispatcher getRequestDispatcher(final String path) {
395
396         // Validate the path argument
397         if (path == null) {
398             return null;
399         }
400         if (!path.startsWith("/")) {
401             throw new IllegalArgumentException(
402                     sm.getString("applicationContext.requestDispatcher.iae", path));
403         }
404
405         // Same processing order as InputBuffer / CoyoteAdapter
406         // First remove query string
407         String uri;
408         String queryString;
409         int pos = path.indexOf('?');
410         if (pos >= 0) {
411             uri = path.substring(0, pos);
412             queryString = path.substring(pos + 1);
413         } else {
414             uri = path;
415             queryString = null;
416         }
417
418         // Remove path parameters
419         String uriNoParams = stripPathParams(uri);
420
421         // Then normalize
422         String normalizedUri = RequestUtil.normalize(uriNoParams);
423         if (normalizedUri == null) {
424             return null;
425         }
426
427         // Mapping is against the normalized uri
428
429         if (getContext().getDispatchersUseEncodedPaths()) {
430             // Decode
431             String decodedUri = UDecoder.URLDecode(normalizedUri);
432
433             // Security check to catch attempts to encode /../ sequences
434             normalizedUri = RequestUtil.normalize(decodedUri);
435             if (!decodedUri.equals(normalizedUri)) {
436                 getContext().getLogger().warn(
437                         sm.getString("applicationContext.illegalDispatchPath", path),
438                         new IllegalArgumentException());
439                 return null;
440             }
441
442             // URI needs to include the context path
443             uri = URLEncoder.DEFAULT.encode(getContextPath(), StandardCharsets.UTF_8) + uri;
444         } else {
445             // uri is passed to the constructor for ApplicationDispatcher and is
446             // ultimately used as the value for getRequestURI() which returns
447             // encoded values. Therefore, since the value passed in for path
448             // was decoded, encode uri here.
449             uri = URLEncoder.DEFAULT.encode(getContextPath() + uri, StandardCharsets.UTF_8);
450         }
451
452         // Use the thread local URI and mapping data
453         DispatchData dd = dispatchData.get();
454         if (dd == null) {
455             dd = new DispatchData();
456             dispatchData.set(dd);
457         }
458
459         MessageBytes uriMB = dd.uriMB;
460         uriMB.recycle();
461
462         // Use the thread local mapping data
463         MappingData mappingData = dd.mappingData;
464
465         try {
466             // Map the URI
467             CharChunk uriCC = uriMB.getCharChunk();
468             try {
469                 uriCC.append(context.getPath());
470                 uriCC.append(normalizedUri);
471                 service.getMapper().map(context, uriMB, mappingData);
472                 if (mappingData.wrapper == null) {
473                     return null;
474                 }
475             } catch (Exception e) {
476                 // Should never happen
477                 log(sm.getString("applicationContext.mapping.error"), e);
478                 return null;
479             }
480
481             Wrapper wrapper = mappingData.wrapper;
482             String wrapperPath = mappingData.wrapperPath.toString();
483             String pathInfo = mappingData.pathInfo.toString();
484             HttpServletMapping mapping = new ApplicationMapping(mappingData).getHttpServletMapping();
485
486             // Construct a RequestDispatcher to process this request
487             return new ApplicationDispatcher(wrapper, uri, wrapperPath, pathInfo,
488                     queryString, mapping, null);
489         } finally {
490             // Recycle thread local data at the end of the request so references
491             // are not held to a completed request as there is potential for
492             // that to trigger a memory leak if a context is unloaded.
493             mappingData.recycle();
494         }
495     }
496
497
498     // Package private to facilitate testing
499     static String stripPathParams(String input) {
500         // Shortcut
501         if (input.indexOf(';') < 0) {
502             return input;
503         }
504
505         StringBuilder sb = new StringBuilder(input.length());
506         int pos = 0;
507         int limit = input.length();
508         while (pos < limit) {
509             int nextSemiColon = input.indexOf(';', pos);
510             if (nextSemiColon < 0) {
511                 nextSemiColon = limit;
512             }
513             sb.append(input.substring(pos, nextSemiColon));
514             int followingSlash = input.indexOf('/', nextSemiColon);
515             if (followingSlash < 0) {
516                 pos = limit;
517             } else {
518                 pos = followingSlash;
519             }
520         }
521
522         return sb.toString();
523     }
524
525
526     @Override
527     public URL getResource(String path) throws MalformedURLException {
528
529         String validatedPath = validateResourcePath(path, false);
530
531         if (validatedPath == null) {
532             throw new MalformedURLException(
533                     sm.getString("applicationContext.requestDispatcher.iae", path));
534         }
535
536         WebResourceRoot resources = context.getResources();
537         if (resources != null) {
538             return resources.getResource(validatedPath).getURL();
539         }
540
541         return null;
542     }
543
544
545     @Override
546     public InputStream getResourceAsStream(String path) {
547
548         String validatedPath = validateResourcePath(path, false);
549
550         if (validatedPath == null) {
551             return null;
552         }
553
554         WebResourceRoot resources = context.getResources();
555         if (resources != null) {
556             return resources.getResource(validatedPath).getInputStream();
557         }
558
559         return null;
560     }
561
562
563     /*
564      * Returns null if the input path is not valid or a path that will be
565      * acceptable to resources.getResource().
566      */

567     private String validateResourcePath(String path, boolean allowEmptyPath) {
568         if (path == null) {
569             return null;
570         }
571
572         if (path.length() == 0 && allowEmptyPath) {
573             return path;
574         }
575
576         if (!path.startsWith("/")) {
577             if (GET_RESOURCE_REQUIRE_SLASH) {
578                 return null;
579             } else {
580                 return "/" + path;
581             }
582         }
583
584         return path;
585     }
586
587
588     @Override
589     public Set<String> getResourcePaths(String path) {
590
591         // Validate the path argument
592         if (path == null) {
593             return null;
594         }
595         if (!path.startsWith("/")) {
596             throw new IllegalArgumentException (sm.getString("applicationContext.resourcePaths.iae", path));
597         }
598
599         WebResourceRoot resources = context.getResources();
600         if (resources != null) {
601             return resources.listWebAppPaths(path);
602         }
603
604         return null;
605     }
606
607
608     @Override
609     public String getServerInfo() {
610         return ServerInfo.getServerInfo();
611     }
612
613
614     @Override
615     @Deprecated
616     public Servlet getServlet(String name) {
617         return null;
618     }
619
620
621     @Override
622     public String getServletContextName() {
623         return context.getDisplayName();
624     }
625
626
627     @Override
628     @Deprecated
629     public Enumeration<String> getServletNames() {
630         return Collections.enumeration(emptyString);
631     }
632
633
634     @Override
635     @Deprecated
636     public Enumeration<Servlet> getServlets() {
637         return Collections.enumeration(emptyServlet);
638     }
639
640
641     @Override
642     public void log(String message) {
643         context.getLogger().info(message);
644     }
645
646
647     @Override
648     @Deprecated
649     public void log(Exception exception, String message) {
650         context.getLogger().error(message, exception);
651     }
652
653
654     @Override
655     public void log(String message, Throwable throwable) {
656         context.getLogger().error(message, throwable);
657     }
658
659
660     @Override
661     public void removeAttribute(String name) {
662
663         Object value = null;
664
665         // Remove the specified attribute
666         // Check for read only attribute
667         if (readOnlyAttributes.containsKey(name)){
668             return;
669         }
670         value = attributes.remove(name);
671         if (value == null) {
672             return;
673         }
674
675         // Notify interested application event listeners
676         Object listeners[] = context.getApplicationEventListeners();
677         if ((listeners == null) || (listeners.length == 0)) {
678             return;
679         }
680         ServletContextAttributeEvent event = new ServletContextAttributeEvent(
681                 context.getServletContext(), name, value);
682         for (Object obj : listeners) {
683             if (!(obj instanceof ServletContextAttributeListener)) {
684                 continue;
685             }
686             ServletContextAttributeListener listener = (ServletContextAttributeListener) obj;
687             try {
688                 context.fireContainerEvent("beforeContextAttributeRemoved", listener);
689                 listener.attributeRemoved(event);
690                 context.fireContainerEvent("afterContextAttributeRemoved", listener);
691             } catch (Throwable t) {
692                 ExceptionUtils.handleThrowable(t);
693                 context.fireContainerEvent("afterContextAttributeRemoved", listener);
694                 // FIXME - should we do anything besides log these?
695                 log(sm.getString("applicationContext.attributeEvent"), t);
696             }
697         }
698     }
699
700
701     @Override
702     public void setAttribute(String name, Object value) {
703         // Name cannot be null
704         if (name == null) {
705             throw new NullPointerException(sm.getString("applicationContext.setAttribute.namenull"));
706         }
707
708         // Null value is the same as removeAttribute()
709         if (value == null) {
710             removeAttribute(name);
711             return;
712         }
713
714         // Add or replace the specified attribute
715         // Check for read only attribute
716         if (readOnlyAttributes.containsKey(name)) {
717             return;
718         }
719
720         Object oldValue = attributes.put(name, value);
721         boolean replaced = oldValue != null;
722
723         // Notify interested application event listeners
724         Object listeners[] = context.getApplicationEventListeners();
725         if ((listeners == null) || (listeners.length == 0)) {
726             return;
727         }
728         ServletContextAttributeEvent event = null;
729         if (replaced) {
730             event = new ServletContextAttributeEvent(context.getServletContext(), name, oldValue);
731         } else {
732             event = new ServletContextAttributeEvent(context.getServletContext(), name, value);
733         }
734
735         for (Object obj : listeners) {
736             if (!(obj instanceof ServletContextAttributeListener)) {
737                 continue;
738             }
739             ServletContextAttributeListener listener = (ServletContextAttributeListener) obj;
740             try {
741                 if (replaced) {
742                     context.fireContainerEvent("beforeContextAttributeReplaced", listener);
743                     listener.attributeReplaced(event);
744                     context.fireContainerEvent("afterContextAttributeReplaced", listener);
745                 } else {
746                     context.fireContainerEvent("beforeContextAttributeAdded", listener);
747                     listener.attributeAdded(event);
748                     context.fireContainerEvent("afterContextAttributeAdded", listener);
749                 }
750             } catch (Throwable t) {
751                 ExceptionUtils.handleThrowable(t);
752                 if (replaced) {
753                     context.fireContainerEvent("afterContextAttributeReplaced", listener);
754                 } else {
755                     context.fireContainerEvent("afterContextAttributeAdded", listener);
756                 }
757                 // FIXME - should we do anything besides log these?
758                 log(sm.getString("applicationContext.attributeEvent"), t);
759             }
760         }
761     }
762
763
764     @Override
765     public FilterRegistration.Dynamic addFilter(String filterName, String className) {
766         return addFilter(filterName, className, null);
767     }
768
769
770     @Override
771     public FilterRegistration.Dynamic addFilter(String filterName, Filter filter) {
772         return addFilter(filterName, null, filter);
773     }
774
775
776     @Override
777     public FilterRegistration.Dynamic addFilter(String filterName,
778             Class<? extends Filter> filterClass) {
779         return addFilter(filterName, filterClass.getName(), null);
780     }
781
782
783     private FilterRegistration.Dynamic addFilter(String filterName,
784             String filterClass, Filter filter) throws IllegalStateException {
785
786         if (filterName == null || filterName.equals("")) {
787             throw new IllegalArgumentException(sm.getString(
788                     "applicationContext.invalidFilterName", filterName));
789         }
790
791         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
792             //TODO Spec breaking enhancement to ignore this restriction
793             throw new IllegalStateException(
794                     sm.getString("applicationContext.addFilter.ise",
795                             getContextPath()));
796         }
797
798         FilterDef filterDef = context.findFilterDef(filterName);
799
800         // Assume a 'complete' FilterRegistration is one that has a class and
801         // a name
802         if (filterDef == null) {
803             filterDef = new FilterDef();
804             filterDef.setFilterName(filterName);
805             context.addFilterDef(filterDef);
806         } else {
807             if (filterDef.getFilterName() != null &&
808                     filterDef.getFilterClass() != null) {
809                 return null;
810             }
811         }
812
813         if (filter == null) {
814             filterDef.setFilterClass(filterClass);
815         } else {
816             filterDef.setFilterClass(filter.getClass().getName());
817             filterDef.setFilter(filter);
818         }
819
820         return new ApplicationFilterRegistration(filterDef, context);
821     }
822
823
824     @Override
825     public <T extends Filter> T createFilter(Class<T> c) throws ServletException {
826         try {
827             @SuppressWarnings("unchecked")
828             T filter = (T) context.getInstanceManager().newInstance(c.getName());
829             return filter;
830         } catch (InvocationTargetException e) {
831             ExceptionUtils.handleThrowable(e.getCause());
832             throw new ServletException(e);
833         } catch (ReflectiveOperationException | NamingException e) {
834             throw new ServletException(e);
835         }
836     }
837
838
839     @Override
840     public FilterRegistration getFilterRegistration(String filterName) {
841         FilterDef filterDef = context.findFilterDef(filterName);
842         if (filterDef == null) {
843             return null;
844         }
845         return new ApplicationFilterRegistration(filterDef, context);
846     }
847
848
849     @Override
850     public ServletRegistration.Dynamic addServlet(String servletName, String className) {
851         return addServlet(servletName, className, nullnull);
852     }
853
854
855     @Override
856     public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet) {
857         return addServlet(servletName, null, servlet, null);
858     }
859
860
861     @Override
862     public ServletRegistration.Dynamic addServlet(String servletName,
863             Class<? extends Servlet> servletClass) {
864         return addServlet(servletName, servletClass.getName(), nullnull);
865     }
866
867
868     @Override
869     public Dynamic addJspFile(String jspName, String jspFile) {
870
871         // jspName is validated in addServlet()
872         if (jspFile == null || !jspFile.startsWith("/")) {
873             throw new IllegalArgumentException(
874                     sm.getString("applicationContext.addJspFile.iae", jspFile));
875         }
876
877         String jspServletClassName = null;
878         Map<String,String> jspFileInitParams = new HashMap<>();
879
880         Wrapper jspServlet = (Wrapper) context.findChild("jsp");
881
882         if (jspServlet == null) {
883             // No JSP servlet currently defined.
884             // Use default JSP Servlet class name
885             jspServletClassName = Constants.JSP_SERVLET_CLASS;
886         } else {
887             // JSP Servlet defined.
888             // Use same JSP Servlet class name
889             jspServletClassName = jspServlet.getServletClass();
890             // Use same init parameters
891             String[] params = jspServlet.findInitParameters();
892             for (String param : params) {
893                 jspFileInitParams.put(param, jspServlet.findInitParameter(param));
894             }
895         }
896
897         // Add init parameter to specify JSP file
898         jspFileInitParams.put("jspFile", jspFile);
899
900         return addServlet(jspName, jspServletClassName, null, jspFileInitParams);
901     }
902
903
904     private ServletRegistration.Dynamic addServlet(String servletName, String servletClass,
905             Servlet servlet, Map<String,String> initParams) throws IllegalStateException {
906
907         if (servletName == null || servletName.equals("")) {
908             throw new IllegalArgumentException(sm.getString(
909                     "applicationContext.invalidServletName", servletName));
910         }
911
912         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
913             //TODO Spec breaking enhancement to ignore this restriction
914             throw new IllegalStateException(
915                     sm.getString("applicationContext.addServlet.ise",
916                             getContextPath()));
917         }
918
919         Wrapper wrapper = (Wrapper) context.findChild(servletName);
920
921         // Assume a 'complete' ServletRegistration is one that has a class and
922         // a name
923         if (wrapper == null) {
924             wrapper = context.createWrapper();
925             wrapper.setName(servletName);
926             context.addChild(wrapper);
927         } else {
928             if (wrapper.getName() != null &&
929                     wrapper.getServletClass() != null) {
930                 if (wrapper.isOverridable()) {
931                     wrapper.setOverridable(false);
932                 } else {
933                     return null;
934                 }
935             }
936         }
937
938         ServletSecurity annotation = null;
939         if (servlet == null) {
940             wrapper.setServletClass(servletClass);
941             Class<?> clazz = Introspection.loadClass(context, servletClass);
942             if (clazz != null) {
943                 annotation = clazz.getAnnotation(ServletSecurity.class);
944             }
945         } else {
946             wrapper.setServletClass(servlet.getClass().getName());
947             wrapper.setServlet(servlet);
948             if (context.wasCreatedDynamicServlet(servlet)) {
949                 annotation = servlet.getClass().getAnnotation(ServletSecurity.class);
950             }
951         }
952
953         if (initParams != null) {
954             for (Map.Entry<String, String> initParam: initParams.entrySet()) {
955                 wrapper.addInitParameter(initParam.getKey(), initParam.getValue());
956             }
957         }
958
959         ServletRegistration.Dynamic registration =
960                 new ApplicationServletRegistration(wrapper, context);
961         if (annotation != null) {
962             registration.setServletSecurity(new ServletSecurityElement(annotation));
963         }
964         return registration;
965     }
966
967
968     @Override
969     public <T extends Servlet> T createServlet(Class<T> c) throws ServletException {
970         try {
971             @SuppressWarnings("unchecked")
972             T servlet = (T) context.getInstanceManager().newInstance(c.getName());
973             context.dynamicServletCreated(servlet);
974             return servlet;
975         } catch (InvocationTargetException e) {
976             ExceptionUtils.handleThrowable(e.getCause());
977             throw new ServletException(e);
978         } catch (ReflectiveOperationException | NamingException e) {
979             throw new ServletException(e);
980         }
981     }
982
983
984     @Override
985     public ServletRegistration getServletRegistration(String servletName) {
986         Wrapper wrapper = (Wrapper) context.findChild(servletName);
987         if (wrapper == null) {
988             return null;
989         }
990
991         return new ApplicationServletRegistration(wrapper, context);
992     }
993
994
995     @Override
996     public Set<SessionTrackingMode> getDefaultSessionTrackingModes() {
997         return defaultSessionTrackingModes;
998     }
999
1000
1001     private void populateSessionTrackingModes() {
1002         // URL re-writing is always enabled by default
1003         defaultSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
1004         supportedSessionTrackingModes = EnumSet.of(SessionTrackingMode.URL);
1005
1006         if (context.getCookies()) {
1007             defaultSessionTrackingModes.add(SessionTrackingMode.COOKIE);
1008             supportedSessionTrackingModes.add(SessionTrackingMode.COOKIE);
1009         }
1010
1011         // SSL not enabled by default as it can only used on its own
1012         // Context > Host > Engine > Service
1013         Connector[] connectors = service.findConnectors();
1014         // Need at least one SSL enabled connector to use the SSL session ID.
1015         for (Connector connector : connectors) {
1016             if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) {
1017                 supportedSessionTrackingModes.add(SessionTrackingMode.SSL);
1018                 break;
1019             }
1020         }
1021     }
1022
1023
1024     @Override
1025     public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() {
1026         if (sessionTrackingModes != null) {
1027             return sessionTrackingModes;
1028         }
1029         return defaultSessionTrackingModes;
1030     }
1031
1032
1033     @Override
1034     public SessionCookieConfig getSessionCookieConfig() {
1035         return sessionCookieConfig;
1036     }
1037
1038
1039     @Override
1040     public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes) {
1041
1042         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1043             throw new IllegalStateException(
1044                     sm.getString("applicationContext.setSessionTracking.ise",
1045                             getContextPath()));
1046         }
1047
1048         // Check that only supported tracking modes have been requested
1049         for (SessionTrackingMode sessionTrackingMode : sessionTrackingModes) {
1050             if (!supportedSessionTrackingModes.contains(sessionTrackingMode)) {
1051                 throw new IllegalArgumentException(sm.getString(
1052                         "applicationContext.setSessionTracking.iae.invalid",
1053                         sessionTrackingMode.toString(), getContextPath()));
1054             }
1055         }
1056
1057         // Check SSL has not be configured with anything else
1058         if (sessionTrackingModes.contains(SessionTrackingMode.SSL)) {
1059             if (sessionTrackingModes.size() > 1) {
1060                 throw new IllegalArgumentException(sm.getString(
1061                         "applicationContext.setSessionTracking.iae.ssl",
1062                         getContextPath()));
1063             }
1064         }
1065
1066         this.sessionTrackingModes = sessionTrackingModes;
1067     }
1068
1069
1070     @Override
1071     public boolean setInitParameter(String name, String value) {
1072         // Name cannot be null
1073         if (name == null) {
1074             throw new NullPointerException(sm.getString("applicationContext.setAttribute.namenull"));
1075         }
1076         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1077             throw new IllegalStateException(
1078                     sm.getString("applicationContext.setInitParam.ise",
1079                             getContextPath()));
1080         }
1081
1082         return parameters.putIfAbsent(name, value) == null;
1083     }
1084
1085
1086     @Override
1087     public void addListener(Class<? extends EventListener> listenerClass) {
1088         EventListener listener;
1089         try {
1090             listener = createListener(listenerClass);
1091         } catch (ServletException e) {
1092             throw new IllegalArgumentException(sm.getString(
1093                     "applicationContext.addListener.iae.init",
1094                     listenerClass.getName()), e);
1095         }
1096         addListener(listener);
1097     }
1098
1099
1100     @Override
1101     public void addListener(String className) {
1102
1103         try {
1104             if (context.getInstanceManager() != null) {
1105                 Object obj = context.getInstanceManager().newInstance(className);
1106
1107                 if (!(obj instanceof EventListener)) {
1108                     throw new IllegalArgumentException(sm.getString(
1109                             "applicationContext.addListener.iae.wrongType",
1110                             className));
1111                 }
1112
1113                 EventListener listener = (EventListener) obj;
1114                 addListener(listener);
1115             }
1116         } catch (InvocationTargetException e) {
1117             ExceptionUtils.handleThrowable(e.getCause());
1118             throw new IllegalArgumentException(sm.getString(
1119                     "applicationContext.addListener.iae.cnfe", className),
1120                     e);
1121         } catch (ReflectiveOperationException| NamingException e) {
1122             throw new IllegalArgumentException(sm.getString(
1123                     "applicationContext.addListener.iae.cnfe", className),
1124                     e);
1125         }
1126
1127     }
1128
1129
1130     @Override
1131     public <T extends EventListener> void addListener(T t) {
1132         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1133             throw new IllegalStateException(
1134                     sm.getString("applicationContext.addListener.ise",
1135                             getContextPath()));
1136         }
1137
1138         boolean match = false;
1139         if (t instanceof ServletContextAttributeListener ||
1140                 t instanceof ServletRequestListener ||
1141                 t instanceof ServletRequestAttributeListener ||
1142                 t instanceof HttpSessionIdListener ||
1143                 t instanceof HttpSessionAttributeListener) {
1144             context.addApplicationEventListener(t);
1145             match = true;
1146         }
1147
1148         if (t instanceof HttpSessionListener ||
1149                 (t instanceof ServletContextListener && newServletContextListenerAllowed)) {
1150             // Add listener directly to the list of instances rather than to
1151             // the list of class names.
1152             context.addApplicationLifecycleListener(t);
1153             match = true;
1154         }
1155
1156         if (match) return;
1157
1158         if (t instanceof ServletContextListener) {
1159             throw new IllegalArgumentException(sm.getString(
1160                     "applicationContext.addListener.iae.sclNotAllowed",
1161                     t.getClass().getName()));
1162         } else {
1163             throw new IllegalArgumentException(sm.getString(
1164                     "applicationContext.addListener.iae.wrongType",
1165                     t.getClass().getName()));
1166         }
1167     }
1168
1169
1170     @Override
1171     public <T extends EventListener> T createListener(Class<T> c)
1172             throws ServletException {
1173         try {
1174             @SuppressWarnings("unchecked")
1175             T listener = (T) context.getInstanceManager().newInstance(c);
1176             if (listener instanceof ServletContextListener ||
1177                     listener instanceof ServletContextAttributeListener ||
1178                     listener instanceof ServletRequestListener ||
1179                     listener instanceof ServletRequestAttributeListener ||
1180                     listener instanceof HttpSessionListener ||
1181                     listener instanceof HttpSessionIdListener ||
1182                     listener instanceof HttpSessionAttributeListener) {
1183                 return listener;
1184             }
1185             throw new IllegalArgumentException(sm.getString(
1186                     "applicationContext.addListener.iae.wrongType",
1187                     listener.getClass().getName()));
1188         } catch (InvocationTargetException e) {
1189             ExceptionUtils.handleThrowable(e.getCause());
1190             throw new ServletException(e);
1191         } catch (ReflectiveOperationException | NamingException e) {
1192             throw new ServletException(e);
1193         }
1194     }
1195
1196
1197     @Override
1198     public void declareRoles(String... roleNames) {
1199
1200         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1201             //TODO Spec breaking enhancement to ignore this restriction
1202             throw new IllegalStateException(
1203                     sm.getString("applicationContext.addRole.ise",
1204                             getContextPath()));
1205         }
1206
1207         if (roleNames == null) {
1208             throw new IllegalArgumentException(
1209                     sm.getString("applicationContext.roles.iae",
1210                             getContextPath()));
1211         }
1212
1213         for (String role : roleNames) {
1214             if (role == null || "".equals(role)) {
1215                 throw new IllegalArgumentException(
1216                         sm.getString("applicationContext.role.iae",
1217                                 getContextPath()));
1218             }
1219             context.addSecurityRole(role);
1220         }
1221     }
1222
1223
1224     @Override
1225     public ClassLoader getClassLoader() {
1226         ClassLoader result = context.getLoader().getClassLoader();
1227         if (Globals.IS_SECURITY_ENABLED) {
1228             ClassLoader tccl = Thread.currentThread().getContextClassLoader();
1229             ClassLoader parent = result;
1230             while (parent != null) {
1231                 if (parent == tccl) {
1232                     break;
1233                 }
1234                 parent = parent.getParent();
1235             }
1236             if (parent == null) {
1237                 System.getSecurityManager().checkPermission(
1238                         new RuntimePermission("getClassLoader"));
1239             }
1240         }
1241
1242         return result;
1243     }
1244
1245
1246     @Override
1247     public int getEffectiveMajorVersion() {
1248         return context.getEffectiveMajorVersion();
1249     }
1250
1251
1252     @Override
1253     public int getEffectiveMinorVersion() {
1254         return context.getEffectiveMinorVersion();
1255     }
1256
1257
1258     @Override
1259     public Map<String, ? extends FilterRegistration> getFilterRegistrations() {
1260         Map<String, ApplicationFilterRegistration> result = new HashMap<>();
1261
1262         FilterDef[] filterDefs = context.findFilterDefs();
1263         for (FilterDef filterDef : filterDefs) {
1264             result.put(filterDef.getFilterName(),
1265                     new ApplicationFilterRegistration(filterDef, context));
1266         }
1267
1268         return result;
1269     }
1270
1271
1272     @Override
1273     public JspConfigDescriptor getJspConfigDescriptor() {
1274         return context.getJspConfigDescriptor();
1275     }
1276
1277
1278     @Override
1279     public Map<String, ? extends ServletRegistration> getServletRegistrations() {
1280         Map<String, ApplicationServletRegistration> result = new HashMap<>();
1281
1282         Container[] wrappers = context.findChildren();
1283         for (Container wrapper : wrappers) {
1284             result.put(wrapper.getName(),
1285                     new ApplicationServletRegistration(
1286                             (Wrapper) wrapper, context));
1287         }
1288
1289         return result;
1290     }
1291
1292
1293     @Override
1294     public String getVirtualServerName() {
1295         // Constructor will fail if context or its parent is null
1296         Container host = context.getParent();
1297         Container engine = host.getParent();
1298         return engine.getName() + "/" + host.getName();
1299     }
1300
1301
1302     @Override
1303     public int getSessionTimeout() {
1304         return context.getSessionTimeout();
1305     }
1306
1307
1308     @Override
1309     public void setSessionTimeout(int sessionTimeout) {
1310         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1311             throw new IllegalStateException(
1312                     sm.getString("applicationContext.setSessionTimeout.ise",
1313                             getContextPath()));
1314         }
1315
1316         context.setSessionTimeout(sessionTimeout);
1317     }
1318
1319
1320     @Override
1321     public String getRequestCharacterEncoding() {
1322         return context.getRequestCharacterEncoding();
1323     }
1324
1325
1326     @Override
1327     public void setRequestCharacterEncoding(String encoding) {
1328         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1329             throw new IllegalStateException(
1330                     sm.getString("applicationContext.setRequestEncoding.ise",
1331                             getContextPath()));
1332         }
1333
1334         context.setRequestCharacterEncoding(encoding);
1335     }
1336
1337
1338     @Override
1339     public String getResponseCharacterEncoding() {
1340         return context.getResponseCharacterEncoding();
1341     }
1342
1343
1344     @Override
1345     public void setResponseCharacterEncoding(String encoding) {
1346         if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
1347             throw new IllegalStateException(
1348                     sm.getString("applicationContext.setResponseEncoding.ise",
1349                             getContextPath()));
1350         }
1351
1352         context.setResponseCharacterEncoding(encoding);
1353     }
1354
1355
1356     // -------------------------------------------------------- Package Methods
1357     protected StandardContext getContext() {
1358         return this.context;
1359     }
1360
1361     /**
1362      * Clear all application-created attributes.
1363      */

1364     protected void clearAttributes() {
1365
1366         // Create list of attributes to be removed
1367         List<String> list = new ArrayList<>();
1368         for (String s : attributes.keySet()) {
1369             list.add(s);
1370         }
1371
1372         // Remove application originated attributes
1373         // (read only attributes will be left in place)
1374         for (String key : list) {
1375             removeAttribute(key);
1376         }
1377
1378     }
1379
1380
1381     /**
1382      * @return the facade associated with this ApplicationContext.
1383      */

1384     protected ServletContext getFacade() {
1385         return this.facade;
1386     }
1387
1388
1389     /**
1390      * Set an attribute as read only.
1391      */

1392     void setAttributeReadOnly(String name) {
1393
1394         if (attributes.containsKey(name))
1395             readOnlyAttributes.put(name, name);
1396
1397     }
1398
1399
1400     protected void setNewServletContextListenerAllowed(boolean allowed) {
1401         this.newServletContextListenerAllowed = allowed;
1402     }
1403
1404     /**
1405      * Internal class used as thread-local storage when doing path
1406      * mapping during dispatch.
1407      */

1408     private static final class DispatchData {
1409
1410         public MessageBytes uriMB;
1411         public MappingData mappingData;
1412
1413         public DispatchData() {
1414             uriMB = MessageBytes.newInstance();
1415             CharChunk uriCC = uriMB.getCharChunk();
1416             uriCC.setLimit(-1);
1417             mappingData = new MappingData();
1418         }
1419     }
1420 }
1421