View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server.handler;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.net.URLClassLoader;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.Enumeration;
31  import java.util.EventListener;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.concurrent.CopyOnWriteArrayList;
40  
41  import javax.servlet.RequestDispatcher;
42  import javax.servlet.Servlet;
43  import javax.servlet.ServletContext;
44  import javax.servlet.ServletContextAttributeEvent;
45  import javax.servlet.ServletContextAttributeListener;
46  import javax.servlet.ServletContextEvent;
47  import javax.servlet.ServletContextListener;
48  import javax.servlet.ServletException;
49  import javax.servlet.ServletRequestAttributeListener;
50  import javax.servlet.ServletRequestEvent;
51  import javax.servlet.ServletRequestListener;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.http.HttpServletResponse;
54  
55  import org.eclipse.jetty.http.HttpException;
56  import org.eclipse.jetty.http.MimeTypes;
57  import org.eclipse.jetty.io.Buffer;
58  import org.eclipse.jetty.server.AbstractHttpConnection;
59  import org.eclipse.jetty.server.Dispatcher;
60  import org.eclipse.jetty.server.DispatcherType;
61  import org.eclipse.jetty.server.Handler;
62  import org.eclipse.jetty.server.HandlerContainer;
63  import org.eclipse.jetty.server.Request;
64  import org.eclipse.jetty.server.Server;
65  import org.eclipse.jetty.util.Attributes;
66  import org.eclipse.jetty.util.AttributesMap;
67  import org.eclipse.jetty.util.LazyList;
68  import org.eclipse.jetty.util.Loader;
69  import org.eclipse.jetty.util.StringUtil;
70  import org.eclipse.jetty.util.TypeUtil;
71  import org.eclipse.jetty.util.URIUtil;
72  import org.eclipse.jetty.util.component.AggregateLifeCycle;
73  import org.eclipse.jetty.util.component.Dumpable;
74  import org.eclipse.jetty.util.log.Log;
75  import org.eclipse.jetty.util.log.Logger;
76  import org.eclipse.jetty.util.resource.Resource;
77  
78  /* ------------------------------------------------------------ */
79  /**
80   * ContextHandler.
81   *
82   * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
83   *
84   * <p>
85   * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
86   * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
87   * <p>
88   * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
89   * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
90   * 
91   * @org.apache.xbean.XBean description="Creates a basic HTTP context"
92   */
93  public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
94  {
95      private static final Logger LOG = Log.getLogger(ContextHandler.class);
96  
97      private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
98  
99      /**
100      * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
101      * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
102      * for the attribute value.
103      */
104     public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
105 
106     /* ------------------------------------------------------------ */
107     /**
108      * Get the current ServletContext implementation.
109      *
110      * @return ServletContext implementation
111      */
112     public static Context getCurrentContext()
113     {
114         return __context.get();
115     }
116 
117     protected Context _scontext;
118 
119     private final AttributesMap _attributes;
120     private final AttributesMap _contextAttributes;
121     private final Map<String, String> _initParams;
122     private ClassLoader _classLoader;
123     private String _contextPath = "/";
124     private String _displayName;
125     private Resource _baseResource;
126     private MimeTypes _mimeTypes;
127     private Map<String, String> _localeEncodingMap;
128     private String[] _welcomeFiles;
129     private ErrorHandler _errorHandler;
130     private String[] _vhosts;
131     private Set<String> _connectors;
132     private EventListener[] _eventListeners;
133     private Logger _logger;
134     private boolean _allowNullPathInfo;
135     private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
136     private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
137     private boolean _compactPath = false;
138     private boolean _aliasesAllowed = false;
139 
140     private Object _contextListeners;
141     private Object _contextAttributeListeners;
142     private Object _requestListeners;
143     private Object _requestAttributeListeners;
144     private Map<String, Object> _managedAttributes;
145     private String[] _protectedTargets;
146     private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
147 
148     private boolean _shutdown = false;
149     private boolean _available = true;
150     private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE
151 
152     private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3;
153 
154     /* ------------------------------------------------------------ */
155     /**
156      *
157      */
158     public ContextHandler()
159     {
160         super();
161         _scontext = new Context();
162         _attributes = new AttributesMap();
163         _contextAttributes = new AttributesMap();
164         _initParams = new HashMap<String, String>();
165         addAliasCheck(new ApproveNonExistentDirectoryAliases());
166     }
167 
168     /* ------------------------------------------------------------ */
169     /**
170      *
171      */
172     protected ContextHandler(Context context)
173     {
174         super();
175         _scontext = context;
176         _attributes = new AttributesMap();
177         _contextAttributes = new AttributesMap();
178         _initParams = new HashMap<String, String>();
179         addAliasCheck(new ApproveNonExistentDirectoryAliases());
180     }
181 
182     /* ------------------------------------------------------------ */
183     /**
184      *
185      */
186     public ContextHandler(String contextPath)
187     {
188         this();
189         setContextPath(contextPath);
190     }
191 
192     /* ------------------------------------------------------------ */
193     /**
194      *
195      */
196     public ContextHandler(HandlerContainer parent, String contextPath)
197     {
198         this();
199         setContextPath(contextPath);
200         if (parent instanceof HandlerWrapper)
201             ((HandlerWrapper)parent).setHandler(this);
202         else if (parent instanceof HandlerCollection)
203             ((HandlerCollection)parent).addHandler(this);
204     }
205 
206     /* ------------------------------------------------------------ */
207     @Override
208     public void dump(Appendable out, String indent) throws IOException
209     {
210         dumpThis(out);
211         dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(),
212                 _attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet());
213     }
214 
215     /* ------------------------------------------------------------ */
216     public Context getServletContext()
217     {
218         return _scontext;
219     }
220 
221     /* ------------------------------------------------------------ */
222     /**
223      * @return the allowNullPathInfo true if /context is not redirected to /context/
224      */
225     public boolean getAllowNullPathInfo()
226     {
227         return _allowNullPathInfo;
228     }
229 
230     /* ------------------------------------------------------------ */
231     /**
232      * @param allowNullPathInfo
233      *            true if /context is not redirected to /context/
234      */
235     public void setAllowNullPathInfo(boolean allowNullPathInfo)
236     {
237         _allowNullPathInfo = allowNullPathInfo;
238     }
239 
240     /* ------------------------------------------------------------ */
241     @Override
242     public void setServer(Server server)
243     {
244         if (_errorHandler != null)
245         {
246             Server old_server = getServer();
247             if (old_server != null && old_server != server)
248                 old_server.getContainer().update(this,_errorHandler,null,"error",true);
249             super.setServer(server);
250             if (server != null && server != old_server)
251                 server.getContainer().update(this,null,_errorHandler,"error",true);
252             _errorHandler.setServer(server);
253         }
254         else
255             super.setServer(server);
256     }
257 
258     /* ------------------------------------------------------------ */
259     /**
260      * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
261      * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
262      * matching virtual host name.
263      *
264      * @param vhosts
265      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
266      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
267      */
268     public void setVirtualHosts(String[] vhosts)
269     {
270         if (vhosts == null)
271         {
272             _vhosts = vhosts;
273         }
274         else
275         {
276             _vhosts = new String[vhosts.length];
277             for (int i = 0; i < vhosts.length; i++)
278                 _vhosts[i] = normalizeHostname(vhosts[i]);
279         }
280     }
281 
282     /* ------------------------------------------------------------ */
283     /** Either set virtual hosts or add to an existing set of virtual hosts.
284      *
285      * @param virtualHosts
286      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
287      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
288      */
289     public void addVirtualHosts(String[] virtualHosts)
290     {
291         if (virtualHosts == null)  // since this is add, we don't null the old ones
292         {
293             return;
294         }
295         else
296         {
297             List<String> currentVirtualHosts = null;
298             if (_vhosts != null)
299             {
300                 currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
301             }
302             else
303             {
304                 currentVirtualHosts = new ArrayList<String>();
305             }
306 
307             for (int i = 0; i < virtualHosts.length; i++)
308             {
309                 String normVhost = normalizeHostname(virtualHosts[i]);
310                 if (!currentVirtualHosts.contains(normVhost))
311                 {
312                     currentVirtualHosts.add(normVhost);
313                 }
314             }
315             _vhosts = currentVirtualHosts.toArray(new String[0]);
316         }
317     }
318 
319     /* ------------------------------------------------------------ */
320     /**
321      * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
322      *
323      *  @param virtualHosts
324      *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
325      *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
326      */
327     public void removeVirtualHosts(String[] virtualHosts)
328     {
329         if (virtualHosts == null)
330         {
331             return; // do nothing
332         }
333         else if ( _vhosts == null || _vhosts.length == 0)
334         {
335             return; // do nothing
336         }
337         else
338         {
339             List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
340 
341             for (int i = 0; i < virtualHosts.length; i++)
342             {
343                 String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
344                 if (existingVirtualHosts.contains(toRemoveVirtualHost))
345                 {
346                     existingVirtualHosts.remove(toRemoveVirtualHost);
347                 }
348             }
349 
350             if (existingVirtualHosts.isEmpty())
351             {
352                 _vhosts = null; // if we ended up removing them all, just null out _vhosts
353             }
354             else
355             {
356                 _vhosts = existingVirtualHosts.toArray(new String[0]);
357             }
358         }
359     }
360 
361     /* ------------------------------------------------------------ */
362     /**
363      * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
364      * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
365      * matching virtual host name.
366      *
367      * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
368      *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
369      */
370     public String[] getVirtualHosts()
371     {
372         return _vhosts;
373     }
374 
375     /* ------------------------------------------------------------ */
376     /**
377      * @return an array of connector names that this context will accept a request from.
378      */
379     public String[] getConnectorNames()
380     {
381         if (_connectors == null || _connectors.size() == 0)
382             return null;
383 
384         return _connectors.toArray(new String[_connectors.size()]);
385     }
386 
387     /* ------------------------------------------------------------ */
388     /**
389      * Set the names of accepted connectors.
390      *
391      * Names are either "host:port" or a specific configured name for a connector.
392      *
393      * @param connectors
394      *            If non null, an array of connector names that this context will accept a request from.
395      */
396     public void setConnectorNames(String[] connectors)
397     {
398         if (connectors == null || connectors.length == 0)
399             _connectors = null;
400         else
401             _connectors = new HashSet<String>(Arrays.asList(connectors));
402     }
403 
404     /* ------------------------------------------------------------ */
405     /*
406      * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
407      */
408     public Object getAttribute(String name)
409     {
410         return _attributes.getAttribute(name);
411     }
412 
413     /* ------------------------------------------------------------ */
414     /*
415      * @see javax.servlet.ServletContext#getAttributeNames()
416      */
417     @SuppressWarnings("unchecked")
418     public Enumeration getAttributeNames()
419     {
420         return AttributesMap.getAttributeNamesCopy(_attributes);
421     }
422 
423     /* ------------------------------------------------------------ */
424     /**
425      * @return Returns the attributes.
426      */
427     public Attributes getAttributes()
428     {
429         return _attributes;
430     }
431 
432     /* ------------------------------------------------------------ */
433     /**
434      * @return Returns the classLoader.
435      */
436     public ClassLoader getClassLoader()
437     {
438         return _classLoader;
439     }
440 
441     /* ------------------------------------------------------------ */
442     /**
443      * Make best effort to extract a file classpath from the context classloader
444      *
445      * @return Returns the classLoader.
446      */
447     public String getClassPath()
448     {
449         if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
450             return null;
451         URLClassLoader loader = (URLClassLoader)_classLoader;
452         URL[] urls = loader.getURLs();
453         StringBuilder classpath = new StringBuilder();
454         for (int i = 0; i < urls.length; i++)
455         {
456             try
457             {
458                 Resource resource = newResource(urls[i]);
459                 File file = resource.getFile();
460                 if (file != null && file.exists())
461                 {
462                     if (classpath.length() > 0)
463                         classpath.append(File.pathSeparatorChar);
464                     classpath.append(file.getAbsolutePath());
465                 }
466             }
467             catch (IOException e)
468             {
469                 LOG.debug(e);
470             }
471         }
472         if (classpath.length() == 0)
473             return null;
474         return classpath.toString();
475     }
476 
477     /* ------------------------------------------------------------ */
478     /**
479      * @return Returns the _contextPath.
480      */
481     public String getContextPath()
482     {
483         return _contextPath;
484     }
485 
486     /* ------------------------------------------------------------ */
487     /*
488      * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
489      */
490     public String getInitParameter(String name)
491     {
492         return _initParams.get(name);
493     }
494 
495     /* ------------------------------------------------------------ */
496     /*
497      */
498     public String setInitParameter(String name, String value)
499     {
500         return _initParams.put(name,value);
501     }
502 
503     /* ------------------------------------------------------------ */
504     /*
505      * @see javax.servlet.ServletContext#getInitParameterNames()
506      */
507     @SuppressWarnings("rawtypes")
508     public Enumeration getInitParameterNames()
509     {
510         return Collections.enumeration(_initParams.keySet());
511     }
512 
513     /* ------------------------------------------------------------ */
514     /**
515      * @return Returns the initParams.
516      */
517     public Map<String, String> getInitParams()
518     {
519         return _initParams;
520     }
521 
522     /* ------------------------------------------------------------ */
523     /*
524      * @see javax.servlet.ServletContext#getServletContextName()
525      */
526     public String getDisplayName()
527     {
528         return _displayName;
529     }
530 
531     /* ------------------------------------------------------------ */
532     public EventListener[] getEventListeners()
533     {
534         return _eventListeners;
535     }
536 
537     /* ------------------------------------------------------------ */
538     /**
539      * Set the context event listeners.
540      *
541      * @param eventListeners
542      *            the event listeners
543      * @see ServletContextListener
544      * @see ServletContextAttributeListener
545      * @see ServletRequestListener
546      * @see ServletRequestAttributeListener
547      */
548     public void setEventListeners(EventListener[] eventListeners)
549     {
550         _contextListeners = null;
551         _contextAttributeListeners = null;
552         _requestListeners = null;
553         _requestAttributeListeners = null;
554 
555         _eventListeners = eventListeners;
556 
557         for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
558         {
559             EventListener listener = _eventListeners[i];
560 
561             if (listener instanceof ServletContextListener)
562                 _contextListeners = LazyList.add(_contextListeners,listener);
563 
564             if (listener instanceof ServletContextAttributeListener)
565                 _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);
566 
567             if (listener instanceof ServletRequestListener)
568                 _requestListeners = LazyList.add(_requestListeners,listener);
569 
570             if (listener instanceof ServletRequestAttributeListener)
571                 _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
572         }
573     }
574 
575     /* ------------------------------------------------------------ */
576     /**
577      * Add a context event listeners.
578      *
579      * @see ServletContextListener
580      * @see ServletContextAttributeListener
581      * @see ServletRequestListener
582      * @see ServletRequestAttributeListener
583      */
584     public void addEventListener(EventListener listener)
585     {
586         setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class));
587     }
588 
589     /* ------------------------------------------------------------ */
590     /**
591      * @return true if this context is accepting new requests
592      */
593     public boolean isShutdown()
594     {
595         synchronized (this)
596         {
597             return !_shutdown;
598         }
599     }
600 
601     /* ------------------------------------------------------------ */
602     /**
603      * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
604      * requests can complete, but no new requests are accepted.
605      *
606      * @param shutdown
607      *            true if this context is (not?) accepting new requests
608      */
609     public void setShutdown(boolean shutdown)
610     {
611         synchronized (this)
612         {
613             _shutdown = shutdown;
614             _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
615         }
616     }
617 
618     /* ------------------------------------------------------------ */
619     /**
620      * @return false if this context is unavailable (sends 503)
621      */
622     public boolean isAvailable()
623     {
624         synchronized (this)
625         {
626             return _available;
627         }
628     }
629 
630     /* ------------------------------------------------------------ */
631     /**
632      * Set Available status.
633      */
634     public void setAvailable(boolean available)
635     {
636         synchronized (this)
637         {
638             _available = available;
639             _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
640         }
641     }
642 
643     /* ------------------------------------------------------------ */
644     public Logger getLogger()
645     {
646         return _logger;
647     }
648 
649     /* ------------------------------------------------------------ */
650     public void setLogger(Logger logger)
651     {
652         _logger = logger;
653     }
654 
655     /* ------------------------------------------------------------ */
656     /*
657      * @see org.eclipse.thread.AbstractLifeCycle#doStart()
658      */
659     @Override
660     protected void doStart() throws Exception
661     {
662         _availability = __STOPPED;
663 
664         if (_contextPath == null)
665             throw new IllegalStateException("Null contextPath");
666 
667         _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
668         ClassLoader old_classloader = null;
669         Thread current_thread = null;
670         Context old_context = null;
671 
672         try
673         {
674             // Set the classloader
675             if (_classLoader != null)
676             {
677                 current_thread = Thread.currentThread();
678                 old_classloader = current_thread.getContextClassLoader();
679                 current_thread.setContextClassLoader(_classLoader);
680             }
681 
682             if (_mimeTypes == null)
683                 _mimeTypes = new MimeTypes();
684 
685             old_context = __context.get();
686             __context.set(_scontext);
687 
688             // defers the calling of super.doStart()
689             startContext();
690 
691             synchronized(this)
692             {
693                 _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE;
694             }
695         }
696         finally
697         {
698             __context.set(old_context);
699 
700             // reset the classloader
701             if (_classLoader != null)
702             {
703                 current_thread.setContextClassLoader(old_classloader);
704             }
705 
706         }
707     }
708 
709     /* ------------------------------------------------------------ */
710     /**
711      * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
712      * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
713      *
714      * @see org.eclipse.jetty.server.handler.ContextHandler.Context
715      */
716     protected void startContext() throws Exception
717     {
718         String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
719         if (managedAttributes != null)
720         {
721             _managedAttributes = new HashMap<String, Object>();
722             String[] attributes = managedAttributes.split(",");
723             for (String attribute : attributes)
724                 _managedAttributes.put(attribute,null);
725 
726             Enumeration e = _scontext.getAttributeNames();
727             while (e.hasMoreElements())
728             {
729                 String name = (String)e.nextElement();
730                 Object value = _scontext.getAttribute(name);
731                 checkManagedAttribute(name,value);
732             }
733         }
734 
735         super.doStart();
736 
737         if (_errorHandler != null)
738             _errorHandler.start();
739 
740         // Context listeners
741         if (_contextListeners != null)
742         {
743             ServletContextEvent event = new ServletContextEvent(_scontext);
744             for (int i = 0; i < LazyList.size(_contextListeners); i++)
745             {
746                 ((ServletContextListener)LazyList.get(_contextListeners,i)).contextInitialized(event);
747             }
748         }
749 
750         LOG.info("started {}",this);
751     }
752 
753     /* ------------------------------------------------------------ */
754     /*
755      * @see org.eclipse.thread.AbstractLifeCycle#doStop()
756      */
757     @Override
758     protected void doStop() throws Exception
759     {
760         _availability = __STOPPED;
761 
762         ClassLoader old_classloader = null;
763         Thread current_thread = null;
764 
765         Context old_context = __context.get();
766         __context.set(_scontext);
767         try
768         {
769             // Set the classloader
770             if (_classLoader != null)
771             {
772                 current_thread = Thread.currentThread();
773                 old_classloader = current_thread.getContextClassLoader();
774                 current_thread.setContextClassLoader(_classLoader);
775             }
776 
777             super.doStop();
778 
779             // Context listeners
780             if (_contextListeners != null)
781             {
782                 ServletContextEvent event = new ServletContextEvent(_scontext);
783                 for (int i = LazyList.size(_contextListeners); i-- > 0;)
784                 {
785                     ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
786                 }
787             }
788 
789             if (_errorHandler != null)
790                 _errorHandler.stop();
791 
792             Enumeration e = _scontext.getAttributeNames();
793             while (e.hasMoreElements())
794             {
795                 String name = (String)e.nextElement();
796                 checkManagedAttribute(name,null);
797             }
798         }
799         finally
800         {
801             LOG.info("stopped {}",this);
802             __context.set(old_context);
803             // reset the classloader
804             if (_classLoader != null)
805                 current_thread.setContextClassLoader(old_classloader);
806         }
807 
808         _contextAttributes.clearAttributes();
809     }
810 
811     /* ------------------------------------------------------------ */
812     /*
813      * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
814      */
815     public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
816     {
817         DispatcherType dispatch = baseRequest.getDispatcherType();
818 
819         switch (_availability)
820         {
821             case __STOPPED:
822             case __SHUTDOWN:
823                 return false;
824             case __UNAVAILABLE:
825                 baseRequest.setHandled(true);
826                 response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
827                 return false;
828             default:
829                 if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
830                     return false;
831         }
832 
833         // Check the vhosts
834         if (_vhosts != null && _vhosts.length > 0)
835         {
836             String vhost = normalizeHostname(baseRequest.getServerName());
837 
838             boolean match = false;
839 
840             // TODO non-linear lookup
841             for (int i = 0; !match && i < _vhosts.length; i++)
842             {
843                 String contextVhost = _vhosts[i];
844                 if (contextVhost == null)
845                     continue;
846                 if (contextVhost.startsWith("*."))
847                 {
848                     // wildcard only at the beginning, and only for one additional subdomain level
849                     match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
850                 }
851                 else
852                     match = contextVhost.equalsIgnoreCase(vhost);
853             }
854             if (!match)
855                 return false;
856         }
857 
858         // Check the connector
859         if (_connectors != null && _connectors.size() > 0)
860         {
861             String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName();
862             if (connector == null || !_connectors.contains(connector))
863                 return false;
864         }
865 
866         // Are we not the root context?
867         if (_contextPath.length() > 1)
868         {
869             // reject requests that are not for us
870             if (!target.startsWith(_contextPath))
871                 return false;
872             if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
873                 return false;
874 
875             // redirect null path infos
876             if (!_allowNullPathInfo && _contextPath.length() == target.length())
877             {
878                 // context request must end with /
879                 baseRequest.setHandled(true);
880                 if (baseRequest.getQueryString() != null)
881                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
882                 else
883                     response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
884                 return false;
885             }
886         }
887 
888         return true;
889     }
890 
891     /* ------------------------------------------------------------ */
892     /**
893      * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
894      *      javax.servlet.http.HttpServletResponse)
895      */
896     @Override
897     public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
898     {
899         if (LOG.isDebugEnabled())
900             LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
901 
902         Context old_context = null;
903         String old_context_path = null;
904         String old_servlet_path = null;
905         String old_path_info = null;
906         ClassLoader old_classloader = null;
907         Thread current_thread = null;
908         String pathInfo = target;
909 
910         DispatcherType dispatch = baseRequest.getDispatcherType();
911 
912         old_context = baseRequest.getContext();
913 
914         // Are we already in this context?
915         if (old_context != _scontext)
916         {
917             // check the target.
918             if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch))
919             {
920                 if (_compactPath)
921                     target = URIUtil.compactPath(target);
922                 if (!checkContext(target,baseRequest,response))
923                     return;
924 
925                 if (target.length() > _contextPath.length())
926                 {
927                     if (_contextPath.length() > 1)
928                         target = target.substring(_contextPath.length());
929                     pathInfo = target;
930                 }
931                 else if (_contextPath.length() == 1)
932                 {
933                     target = URIUtil.SLASH;
934                     pathInfo = URIUtil.SLASH;
935                 }
936                 else
937                 {
938                     target = URIUtil.SLASH;
939                     pathInfo = null;
940                 }
941             }
942 
943             // Set the classloader
944             if (_classLoader != null)
945             {
946                 current_thread = Thread.currentThread();
947                 old_classloader = current_thread.getContextClassLoader();
948                 current_thread.setContextClassLoader(_classLoader);
949             }
950         }
951 
952         try
953         {
954             old_context_path = baseRequest.getContextPath();
955             old_servlet_path = baseRequest.getServletPath();
956             old_path_info = baseRequest.getPathInfo();
957 
958             // Update the paths
959             baseRequest.setContext(_scontext);
960             __context.set(_scontext);
961             if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
962             {
963                 if (_contextPath.length() == 1)
964                     baseRequest.setContextPath("");
965                 else
966                     baseRequest.setContextPath(_contextPath);
967                 baseRequest.setServletPath(null);
968                 baseRequest.setPathInfo(pathInfo);
969             }
970 
971             if (LOG.isDebugEnabled())
972                 LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
973 
974             // start manual inline of nextScope(target,baseRequest,request,response);
975             if (never())
976                 nextScope(target,baseRequest,request,response);
977             else if (_nextScope != null)
978                 _nextScope.doScope(target,baseRequest,request,response);
979             else if (_outerScope != null)
980                 _outerScope.doHandle(target,baseRequest,request,response);
981             else
982                 doHandle(target,baseRequest,request,response);
983             // end manual inline (pathentic attempt to reduce stack depth)
984         }
985         finally
986         {
987             if (old_context != _scontext)
988             {
989                 // reset the classloader
990                 if (_classLoader != null)
991                 {
992                     current_thread.setContextClassLoader(old_classloader);
993                 }
994 
995                 // reset the context and servlet path.
996                 baseRequest.setContext(old_context);
997                 __context.set(old_context);
998                 baseRequest.setContextPath(old_context_path);
999                 baseRequest.setServletPath(old_servlet_path);
1000                 baseRequest.setPathInfo(old_path_info);
1001             }
1002         }
1003     }
1004 
1005     /* ------------------------------------------------------------ */
1006     /**
1007      * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
1008      *      javax.servlet.http.HttpServletResponse)
1009      */
1010     @Override
1011     public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
1012     {
1013         final DispatcherType dispatch = baseRequest.getDispatcherType();
1014         final boolean new_context = baseRequest.takeNewContext();
1015         try
1016         {
1017             if (new_context)
1018             {
1019                 // Handle the REALLY SILLY request events!
1020                 if (_requestAttributeListeners != null)
1021                 {
1022                     final int s = LazyList.size(_requestAttributeListeners);
1023                     for (int i = 0; i < s; i++)
1024                         baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1025                 }
1026 
1027                 if (_requestListeners != null)
1028                 {
1029                     final int s = LazyList.size(_requestListeners);
1030                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1031                     for (int i = 0; i < s; i++)
1032                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
1033                 }
1034             }
1035 
1036             if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
1037                 throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
1038 
1039             // start manual inline of nextHandle(target,baseRequest,request,response);
1040             // noinspection ConstantIfStatement
1041             if (never())
1042                 nextHandle(target,baseRequest,request,response);
1043             else if (_nextScope != null && _nextScope == _handler)
1044                 _nextScope.doHandle(target,baseRequest,request,response);
1045             else if (_handler != null)
1046                 _handler.handle(target,baseRequest,request,response);
1047             // end manual inline
1048         }
1049         catch (HttpException e)
1050         {
1051             LOG.debug(e);
1052             baseRequest.setHandled(true);
1053             response.sendError(e.getStatus(),e.getReason());
1054         }
1055         finally
1056         {
1057             // Handle more REALLY SILLY request events!
1058             if (new_context)
1059             {
1060                 if (_requestListeners != null)
1061                 {
1062                     final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
1063                     for (int i = LazyList.size(_requestListeners); i-- > 0;)
1064                         ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
1065                 }
1066 
1067                 if (_requestAttributeListeners != null)
1068                 {
1069                     for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
1070                         baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
1071                 }
1072             }
1073         }
1074     }
1075 
1076     /* ------------------------------------------------------------ */
1077     /*
1078      * Handle a runnable in this context
1079      */
1080     public void handle(Runnable runnable)
1081     {
1082         ClassLoader old_classloader = null;
1083         Thread current_thread = null;
1084         Context old_context = null;
1085         try
1086         {
1087             old_context = __context.get();
1088             __context.set(_scontext);
1089 
1090             // Set the classloader
1091             if (_classLoader != null)
1092             {
1093                 current_thread = Thread.currentThread();
1094                 old_classloader = current_thread.getContextClassLoader();
1095                 current_thread.setContextClassLoader(_classLoader);
1096             }
1097 
1098             runnable.run();
1099         }
1100         finally
1101         {
1102             __context.set(old_context);
1103             if (old_classloader != null)
1104             {
1105                 current_thread.setContextClassLoader(old_classloader);
1106             }
1107         }
1108     }
1109 
1110     /* ------------------------------------------------------------ */
1111     /**
1112      * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
1113      * the target is protected, 404 is returned. 
1114      */
1115     /* ------------------------------------------------------------ */
1116     public boolean isProtectedTarget(String target)
1117     {
1118         if (target == null || _protectedTargets == null)
1119             return false;
1120         
1121         while (target.startsWith("//"))
1122             target=URIUtil.compactPath(target);
1123         
1124         boolean isProtected = false;
1125         int i=0;
1126         while (!isProtected && i<_protectedTargets.length)
1127         {
1128             isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
1129         }
1130         return isProtected;
1131     }
1132     
1133     
1134     public void setProtectedTargets (String[] targets)
1135     {
1136         if (targets == null)
1137         {
1138             _protectedTargets = null;
1139             return;
1140         }
1141         
1142         _protectedTargets = new String[targets.length];
1143         System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
1144     }
1145     
1146     public String[] getProtectedTargets ()
1147     {
1148         if (_protectedTargets == null)
1149             return null;
1150         
1151         String[] tmp = new String[_protectedTargets.length];
1152         System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
1153         return tmp;
1154     }
1155     
1156 
1157     /* ------------------------------------------------------------ */
1158     /*
1159      * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
1160      */
1161     public void removeAttribute(String name)
1162     {
1163         checkManagedAttribute(name,null);
1164         _attributes.removeAttribute(name);
1165     }
1166 
1167     /* ------------------------------------------------------------ */
1168     /*
1169      * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
1170      * a context. No attribute listener events are triggered by this API.
1171      *
1172      * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
1173      */
1174     public void setAttribute(String name, Object value)
1175     {
1176         checkManagedAttribute(name,value);
1177         _attributes.setAttribute(name,value);
1178     }
1179 
1180     /* ------------------------------------------------------------ */
1181     /**
1182      * @param attributes
1183      *            The attributes to set.
1184      */
1185     public void setAttributes(Attributes attributes)
1186     {
1187         _attributes.clearAttributes();
1188         _attributes.addAll(attributes);
1189         Enumeration e = _attributes.getAttributeNames();
1190         while (e.hasMoreElements())
1191         {
1192             String name = (String)e.nextElement();
1193             checkManagedAttribute(name,attributes.getAttribute(name));
1194         }
1195     }
1196 
1197     /* ------------------------------------------------------------ */
1198     public void clearAttributes()
1199     {
1200         Enumeration e = _attributes.getAttributeNames();
1201         while (e.hasMoreElements())
1202         {
1203             String name = (String)e.nextElement();
1204             checkManagedAttribute(name,null);
1205         }
1206         _attributes.clearAttributes();
1207     }
1208 
1209     /* ------------------------------------------------------------ */
1210     public void checkManagedAttribute(String name, Object value)
1211     {
1212         if (_managedAttributes != null && _managedAttributes.containsKey(name))
1213         {
1214             setManagedAttribute(name,value);
1215         }
1216     }
1217 
1218     /* ------------------------------------------------------------ */
1219     public void setManagedAttribute(String name, Object value)
1220     {
1221         Object old = _managedAttributes.put(name,value);
1222         getServer().getContainer().update(this,old,value,name,true);
1223     }
1224 
1225     /* ------------------------------------------------------------ */
1226     /**
1227      * @param classLoader
1228      *            The classLoader to set.
1229      */
1230     public void setClassLoader(ClassLoader classLoader)
1231     {
1232         _classLoader = classLoader;
1233     }
1234 
1235     /* ------------------------------------------------------------ */
1236     /**
1237      * @param contextPath
1238      *            The _contextPath to set.
1239      */
1240     public void setContextPath(String contextPath)
1241     {
1242         if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
1243             throw new IllegalArgumentException("ends with /");
1244         _contextPath = contextPath;
1245 
1246         if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
1247         {
1248             Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
1249             for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
1250                 ((ContextHandlerCollection)contextCollections[h]).mapContexts();
1251         }
1252     }
1253 
1254     /* ------------------------------------------------------------ */
1255     /**
1256      * @param servletContextName
1257      *            The servletContextName to set.
1258      */
1259     public void setDisplayName(String servletContextName)
1260     {
1261         _displayName = servletContextName;
1262     }
1263 
1264     /* ------------------------------------------------------------ */
1265     /**
1266      * @return Returns the resourceBase.
1267      */
1268     public Resource getBaseResource()
1269     {
1270         if (_baseResource == null)
1271             return null;
1272         return _baseResource;
1273     }
1274 
1275     /* ------------------------------------------------------------ */
1276     /**
1277      * @return Returns the base resource as a string.
1278      */
1279     public String getResourceBase()
1280     {
1281         if (_baseResource == null)
1282             return null;
1283         return _baseResource.toString();
1284     }
1285 
1286     /* ------------------------------------------------------------ */
1287     /**
1288      * @param base
1289      *            The resourceBase to set.
1290      */
1291     public void setBaseResource(Resource base)
1292     {
1293         _baseResource = base;
1294     }
1295 
1296     /* ------------------------------------------------------------ */
1297     /**
1298      * @param resourceBase
1299      *            The base resource as a string.
1300      */
1301     public void setResourceBase(String resourceBase)
1302     {
1303         try
1304         {
1305             setBaseResource(newResource(resourceBase));
1306         }
1307         catch (Exception e)
1308         {
1309             LOG.warn(e.toString());
1310             LOG.debug(e);
1311             throw new IllegalArgumentException(resourceBase);
1312         }
1313     }
1314 
1315     /* ------------------------------------------------------------ */
1316     /**
1317      * @return True if aliases are allowed
1318      */
1319     public boolean isAliases()
1320     {
1321         return _aliasesAllowed;
1322     }
1323 
1324     /* ------------------------------------------------------------ */
1325     /**
1326      * @param aliases
1327      *            aliases are allowed
1328      */
1329     public void setAliases(boolean aliases)
1330     {
1331         _aliasesAllowed = aliases;
1332     }
1333 
1334     /* ------------------------------------------------------------ */
1335     /**
1336      * @return Returns the mimeTypes.
1337      */
1338     public MimeTypes getMimeTypes()
1339     {
1340         if (_mimeTypes == null)
1341             _mimeTypes = new MimeTypes();
1342         return _mimeTypes;
1343     }
1344 
1345     /* ------------------------------------------------------------ */
1346     /**
1347      * @param mimeTypes
1348      *            The mimeTypes to set.
1349      */
1350     public void setMimeTypes(MimeTypes mimeTypes)
1351     {
1352         _mimeTypes = mimeTypes;
1353     }
1354 
1355     /* ------------------------------------------------------------ */
1356     /**
1357      */
1358     public void setWelcomeFiles(String[] files)
1359     {
1360         _welcomeFiles = files;
1361     }
1362 
1363     /* ------------------------------------------------------------ */
1364     /**
1365      * @return The names of the files which the server should consider to be welcome files in this context.
1366      * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
1367      * @see #setWelcomeFiles
1368      */
1369     public String[] getWelcomeFiles()
1370     {
1371         return _welcomeFiles;
1372     }
1373 
1374     /* ------------------------------------------------------------ */
1375     /**
1376      * @return Returns the errorHandler.
1377      */
1378     public ErrorHandler getErrorHandler()
1379     {
1380         return _errorHandler;
1381     }
1382 
1383     /* ------------------------------------------------------------ */
1384     /**
1385      * @param errorHandler
1386      *            The errorHandler to set.
1387      */
1388     public void setErrorHandler(ErrorHandler errorHandler)
1389     {
1390         if (errorHandler != null)
1391             errorHandler.setServer(getServer());
1392         if (getServer() != null)
1393             getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true);
1394         _errorHandler = errorHandler;
1395     }
1396 
1397     /* ------------------------------------------------------------ */
1398     public int getMaxFormContentSize()
1399     {
1400         return _maxFormContentSize;
1401     }
1402 
1403     /* ------------------------------------------------------------ */
1404     /**
1405      * Set the maximum size of a form post, to protect against DOS attacks from large forms.
1406      * @param maxSize
1407      */
1408     public void setMaxFormContentSize(int maxSize)
1409     {
1410         _maxFormContentSize = maxSize;
1411     }
1412 
1413     /* ------------------------------------------------------------ */
1414     public int getMaxFormKeys()
1415     {
1416         return _maxFormKeys;
1417     }
1418 
1419     /* ------------------------------------------------------------ */
1420     /**
1421      * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
1422      * @param max
1423      */
1424     public void setMaxFormKeys(int max)
1425     {
1426         _maxFormKeys = max;
1427     }
1428 
1429     /* ------------------------------------------------------------ */
1430     /**
1431      * @return True if URLs are compacted to replace multiple '/'s with a single '/'
1432      */
1433     public boolean isCompactPath()
1434     {
1435         return _compactPath;
1436     }
1437 
1438     /* ------------------------------------------------------------ */
1439     /**
1440      * @param compactPath
1441      *            True if URLs are compacted to replace multiple '/'s with a single '/'
1442      */
1443     public void setCompactPath(boolean compactPath)
1444     {
1445         _compactPath = compactPath;
1446     }
1447 
1448     /* ------------------------------------------------------------ */
1449     @Override
1450     public String toString()
1451     {
1452         String[] vhosts = getVirtualHosts();
1453 
1454         StringBuilder b = new StringBuilder();
1455 
1456         Package pkg = getClass().getPackage();
1457         if (pkg != null)
1458         {
1459             String p = pkg.getName();
1460             if (p != null && p.length() > 0)
1461             {
1462                 String[] ss = p.split("\\.");
1463                 for (String s : ss)
1464                     b.append(s.charAt(0)).append('.');
1465             }
1466         }
1467         b.append(getClass().getSimpleName());
1468         b.append('{').append(getContextPath()).append(',').append(getBaseResource());
1469 
1470         if (vhosts != null && vhosts.length > 0)
1471             b.append(',').append(vhosts[0]);
1472         b.append('}');
1473 
1474         return b.toString();
1475     }
1476 
1477     /* ------------------------------------------------------------ */
1478     public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
1479     {
1480         if (className == null)
1481             return null;
1482 
1483         if (_classLoader == null)
1484             return Loader.loadClass(this.getClass(),className);
1485 
1486         return _classLoader.loadClass(className);
1487     }
1488 
1489     /* ------------------------------------------------------------ */
1490     public void addLocaleEncoding(String locale, String encoding)
1491     {
1492         if (_localeEncodingMap == null)
1493             _localeEncodingMap = new HashMap<String, String>();
1494         _localeEncodingMap.put(locale,encoding);
1495     }
1496 
1497     public String getLocaleEncoding(String locale)
1498     {
1499         if (_localeEncodingMap == null)
1500             return null;
1501         String encoding = _localeEncodingMap.get(locale);
1502         return encoding;
1503     }
1504 
1505     /* ------------------------------------------------------------ */
1506     /**
1507      * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
1508      * language is looked up.
1509      *
1510      * @param locale
1511      *            a <code>Locale</code> value
1512      * @return a <code>String</code> representing the character encoding for the locale or null if none found.
1513      */
1514     public String getLocaleEncoding(Locale locale)
1515     {
1516         if (_localeEncodingMap == null)
1517             return null;
1518         String encoding = _localeEncodingMap.get(locale.toString());
1519         if (encoding == null)
1520             encoding = _localeEncodingMap.get(locale.getLanguage());
1521         return encoding;
1522     }
1523 
1524     /* ------------------------------------------------------------ */
1525     /*
1526      */
1527     public Resource getResource(String path) throws MalformedURLException
1528     {
1529         if (path == null || !path.startsWith(URIUtil.SLASH))
1530             throw new MalformedURLException(path);
1531 
1532         if (_baseResource == null)
1533             return null;
1534 
1535         try
1536         {
1537             path = URIUtil.canonicalPath(path);
1538             Resource resource = _baseResource.addPath(path);
1539             
1540             if (checkAlias(path,resource))
1541                 return resource;
1542             return null;
1543         }
1544         catch (Exception e)
1545         {
1546             LOG.ignore(e);
1547         }
1548 
1549         return null;
1550     }
1551 
1552     /* ------------------------------------------------------------ */
1553     public boolean checkAlias(String path, Resource resource)
1554     {
1555         // Is the resource aliased?
1556         if (!_aliasesAllowed && resource.getAlias() != null)
1557         {
1558             if (LOG.isDebugEnabled())
1559                 LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
1560 
1561             // alias checks
1562             for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
1563             {
1564                 AliasCheck check = i.next();
1565                 if (check.check(path,resource))
1566                 {
1567                     if (LOG.isDebugEnabled())
1568                         LOG.debug("Aliased resource: " + resource + " approved by " + check);
1569                     return true;
1570                 }
1571             }
1572             return false;
1573         }
1574         return true;
1575     }
1576     
1577     /* ------------------------------------------------------------ */
1578     /**
1579      * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
1580      */
1581     public Resource newResource(URL url) throws IOException
1582     {
1583         return Resource.newResource(url);
1584     }
1585 
1586     /* ------------------------------------------------------------ */
1587     /**
1588      * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
1589      *
1590      * @param urlOrPath
1591      *            The URL or path to convert
1592      * @return The Resource for the URL/path
1593      * @throws IOException
1594      *             The Resource could not be created.
1595      */
1596     public Resource newResource(String urlOrPath) throws IOException
1597     {
1598         return Resource.newResource(urlOrPath);
1599     }
1600 
1601     /* ------------------------------------------------------------ */
1602     /*
1603      */
1604     public Set<String> getResourcePaths(String path)
1605     {
1606         try
1607         {
1608             path = URIUtil.canonicalPath(path);
1609             Resource resource = getResource(path);
1610 
1611             if (resource != null && resource.exists())
1612             {
1613                 if (!path.endsWith(URIUtil.SLASH))
1614                     path = path + URIUtil.SLASH;
1615 
1616                 String[] l = resource.list();
1617                 if (l != null)
1618                 {
1619                     HashSet<String> set = new HashSet<String>();
1620                     for (int i = 0; i < l.length; i++)
1621                         set.add(path + l[i]);
1622                     return set;
1623                 }
1624             }
1625         }
1626         catch (Exception e)
1627         {
1628             LOG.ignore(e);
1629         }
1630         return Collections.emptySet();
1631     }
1632 
1633     /* ------------------------------------------------------------ */
1634     private String normalizeHostname(String host)
1635     {
1636         if (host == null)
1637             return null;
1638 
1639         if (host.endsWith("."))
1640             return host.substring(0,host.length() - 1);
1641 
1642         return host;
1643     }
1644     
1645     /* ------------------------------------------------------------ */
1646     /**
1647      * Add an AliasCheck instance to possibly permit aliased resources
1648      * @param check The alias checker
1649      */
1650     public void addAliasCheck(AliasCheck check)
1651     {
1652         _aliasChecks.add(check);
1653     }
1654     
1655     /* ------------------------------------------------------------ */
1656     /**
1657      * @return Mutable list of Alias checks
1658      */
1659     public List<AliasCheck> getAliasChecks()
1660     {
1661         return _aliasChecks;
1662     }
1663 
1664     /* ------------------------------------------------------------ */
1665     /**
1666      * Context.
1667      * <p>
1668      * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
1669      * </p>
1670      *
1671      *
1672      */
1673     public class Context implements ServletContext
1674     {
1675         /* ------------------------------------------------------------ */
1676         protected Context()
1677         {
1678         }
1679 
1680         /* ------------------------------------------------------------ */
1681         public ContextHandler getContextHandler()
1682         {
1683             // TODO reduce visibility of this method
1684             return ContextHandler.this;
1685         }
1686 
1687         /* ------------------------------------------------------------ */
1688         /*
1689          * @see javax.servlet.ServletContext#getContext(java.lang.String)
1690          */
1691         public ServletContext getContext(String uripath)
1692         {
1693             List<ContextHandler> contexts = new ArrayList<ContextHandler>();
1694             Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
1695             String matched_path = null;
1696 
1697             for (Handler handler : handlers)
1698             {
1699                 if (handler == null)
1700                     continue;
1701                 ContextHandler ch = (ContextHandler)handler;
1702                 String context_path = ch.getContextPath();
1703 
1704                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1705                         || "/".equals(context_path))
1706                 {
1707                     // look first for vhost matching context only
1708                     if (getVirtualHosts() != null && getVirtualHosts().length > 0)
1709                     {
1710                         if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
1711                         {
1712                             for (String h1 : getVirtualHosts())
1713                                 for (String h2 : ch.getVirtualHosts())
1714                                     if (h1.equals(h2))
1715                                     {
1716                                         if (matched_path == null || context_path.length() > matched_path.length())
1717                                         {
1718                                             contexts.clear();
1719                                             matched_path = context_path;
1720                                         }
1721 
1722                                         if (matched_path.equals(context_path))
1723                                             contexts.add(ch);
1724                                     }
1725                         }
1726                     }
1727                     else
1728                     {
1729                         if (matched_path == null || context_path.length() > matched_path.length())
1730                         {
1731                             contexts.clear();
1732                             matched_path = context_path;
1733                         }
1734 
1735                         if (matched_path.equals(context_path))
1736                             contexts.add(ch);
1737                     }
1738                 }
1739             }
1740 
1741             if (contexts.size() > 0)
1742                 return contexts.get(0)._scontext;
1743 
1744             // try again ignoring virtual hosts
1745             matched_path = null;
1746             for (Handler handler : handlers)
1747             {
1748                 if (handler == null)
1749                     continue;
1750                 ContextHandler ch = (ContextHandler)handler;
1751                 String context_path = ch.getContextPath();
1752 
1753                 if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
1754                         || "/".equals(context_path))
1755                 {
1756                     if (matched_path == null || context_path.length() > matched_path.length())
1757                     {
1758                         contexts.clear();
1759                         matched_path = context_path;
1760                     }
1761 
1762                     if (matched_path.equals(context_path))
1763                         contexts.add(ch);
1764                 }
1765             }
1766 
1767             if (contexts.size() > 0)
1768                 return contexts.get(0)._scontext;
1769             return null;
1770         }
1771 
1772         /* ------------------------------------------------------------ */
1773         /*
1774          * @see javax.servlet.ServletContext#getMajorVersion()
1775          */
1776         public int getMajorVersion()
1777         {
1778             return 2;
1779         }
1780 
1781         /* ------------------------------------------------------------ */
1782         /*
1783          * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
1784          */
1785         public String getMimeType(String file)
1786         {
1787             if (_mimeTypes == null)
1788                 return null;
1789             Buffer mime = _mimeTypes.getMimeByExtension(file);
1790             if (mime != null)
1791                 return mime.toString();
1792             return null;
1793         }
1794 
1795         /* ------------------------------------------------------------ */
1796         /*
1797          * @see javax.servlet.ServletContext#getMinorVersion()
1798          */
1799         public int getMinorVersion()
1800         {
1801             return 5;
1802         }
1803 
1804         /* ------------------------------------------------------------ */
1805         /*
1806          * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
1807          */
1808         public RequestDispatcher getNamedDispatcher(String name)
1809         {
1810             return null;
1811         }
1812 
1813         /* ------------------------------------------------------------ */
1814         /*
1815          * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
1816          */
1817         public RequestDispatcher getRequestDispatcher(String uriInContext)
1818         {
1819             if (uriInContext == null)
1820                 return null;
1821 
1822             if (!uriInContext.startsWith("/"))
1823                 return null;
1824 
1825             try
1826             {
1827                 String query = null;
1828                 int q = 0;
1829                 if ((q = uriInContext.indexOf('?')) > 0)
1830                 {
1831                     query = uriInContext.substring(q + 1);
1832                     uriInContext = uriInContext.substring(0,q);
1833                 }
1834                 // if ((q = uriInContext.indexOf(';')) > 0)
1835                 //     uriInContext = uriInContext.substring(0,q);
1836 
1837                 String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
1838                 String uri = URIUtil.addPaths(getContextPath(),uriInContext);
1839                 ContextHandler context = ContextHandler.this;
1840                 return new Dispatcher(context,uri,pathInContext,query);
1841             }
1842             catch (Exception e)
1843             {
1844                 LOG.ignore(e);
1845             }
1846             return null;
1847         }
1848 
1849         /* ------------------------------------------------------------ */
1850         /*
1851          * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
1852          */
1853         public String getRealPath(String path)
1854         {
1855             if (path == null)
1856                 return null;
1857             if (path.length() == 0)
1858                 path = URIUtil.SLASH;
1859             else if (path.charAt(0) != '/')
1860                 path = URIUtil.SLASH + path;
1861 
1862             try
1863             {
1864                 Resource resource = ContextHandler.this.getResource(path);
1865                 if (resource != null)
1866                 {
1867                     File file = resource.getFile();
1868                     if (file != null)
1869                         return file.getCanonicalPath();
1870                 }
1871             }
1872             catch (Exception e)
1873             {
1874                 LOG.ignore(e);
1875             }
1876 
1877             return null;
1878         }
1879 
1880         /* ------------------------------------------------------------ */
1881         public URL getResource(String path) throws MalformedURLException
1882         {
1883             Resource resource = ContextHandler.this.getResource(path);
1884             if (resource != null && resource.exists())
1885                 return resource.getURL();
1886             return null;
1887         }
1888 
1889         /* ------------------------------------------------------------ */
1890         /*
1891          * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
1892          */
1893         public InputStream getResourceAsStream(String path)
1894         {
1895             try
1896             {
1897                 URL url = getResource(path);
1898                 if (url == null)
1899                     return null;
1900                 Resource r = Resource.newResource(url);
1901                 return r.getInputStream();
1902             }
1903             catch (Exception e)
1904             {
1905                 LOG.ignore(e);
1906                 return null;
1907             }
1908         }
1909 
1910         /* ------------------------------------------------------------ */
1911         /*
1912          * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
1913          */
1914         public Set getResourcePaths(String path)
1915         {
1916             return ContextHandler.this.getResourcePaths(path);
1917         }
1918 
1919         /* ------------------------------------------------------------ */
1920         /*
1921          * @see javax.servlet.ServletContext#getServerInfo()
1922          */
1923         public String getServerInfo()
1924         {
1925             return "jetty/" + Server.getVersion();
1926         }
1927 
1928         /* ------------------------------------------------------------ */
1929         /*
1930          * @see javax.servlet.ServletContext#getServlet(java.lang.String)
1931          */
1932         public Servlet getServlet(String name) throws ServletException
1933         {
1934             return null;
1935         }
1936 
1937         /* ------------------------------------------------------------ */
1938         /*
1939          * @see javax.servlet.ServletContext#getServletNames()
1940          */
1941         @SuppressWarnings("unchecked")
1942         public Enumeration getServletNames()
1943         {
1944             return Collections.enumeration(Collections.EMPTY_LIST);
1945         }
1946 
1947         /* ------------------------------------------------------------ */
1948         /*
1949          * @see javax.servlet.ServletContext#getServlets()
1950          */
1951         @SuppressWarnings("unchecked")
1952         public Enumeration getServlets()
1953         {
1954             return Collections.enumeration(Collections.EMPTY_LIST);
1955         }
1956 
1957         /* ------------------------------------------------------------ */
1958         /*
1959          * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
1960          */
1961         public void log(Exception exception, String msg)
1962         {
1963             _logger.warn(msg,exception);
1964         }
1965 
1966         /* ------------------------------------------------------------ */
1967         /*
1968          * @see javax.servlet.ServletContext#log(java.lang.String)
1969          */
1970         public void log(String msg)
1971         {
1972             _logger.info(msg);
1973         }
1974 
1975         /* ------------------------------------------------------------ */
1976         /*
1977          * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
1978          */
1979         public void log(String message, Throwable throwable)
1980         {
1981             _logger.warn(message,throwable);
1982         }
1983 
1984         /* ------------------------------------------------------------ */
1985         /*
1986          * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
1987          */
1988         public String getInitParameter(String name)
1989         {
1990             return ContextHandler.this.getInitParameter(name);
1991         }
1992 
1993         /* ------------------------------------------------------------ */
1994         /*
1995          * @see javax.servlet.ServletContext#getInitParameterNames()
1996          */
1997         @SuppressWarnings("unchecked")
1998         public Enumeration getInitParameterNames()
1999         {
2000             return ContextHandler.this.getInitParameterNames();
2001         }
2002 
2003         /* ------------------------------------------------------------ */
2004         /*
2005          * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
2006          */
2007         public synchronized Object getAttribute(String name)
2008         {
2009             Object o = ContextHandler.this.getAttribute(name);
2010             if (o == null && _contextAttributes != null)
2011                 o = _contextAttributes.getAttribute(name);
2012             return o;
2013         }
2014 
2015         /* ------------------------------------------------------------ */
2016         /*
2017          * @see javax.servlet.ServletContext#getAttributeNames()
2018          */
2019         @SuppressWarnings("unchecked")
2020         public synchronized Enumeration getAttributeNames()
2021         {
2022             HashSet<String> set = new HashSet<String>();
2023             if (_contextAttributes != null)
2024             {
2025                 Enumeration<String> e = _contextAttributes.getAttributeNames();
2026                 while (e.hasMoreElements())
2027                     set.add(e.nextElement());
2028             }
2029             Enumeration<String> e = _attributes.getAttributeNames();
2030             while (e.hasMoreElements())
2031                 set.add(e.nextElement());
2032 
2033             return Collections.enumeration(set);
2034         }
2035 
2036         /* ------------------------------------------------------------ */
2037         /*
2038          * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
2039          */
2040         public synchronized void setAttribute(String name, Object value)
2041         {
2042             checkManagedAttribute(name,value);
2043             Object old_value = _contextAttributes.getAttribute(name);
2044 
2045             if (value == null)
2046                 _contextAttributes.removeAttribute(name);
2047             else
2048                 _contextAttributes.setAttribute(name,value);
2049 
2050             if (_contextAttributeListeners != null)
2051             {
2052                 ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
2053 
2054                 for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2055                 {
2056                     ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
2057 
2058                     if (old_value == null)
2059                         l.attributeAdded(event);
2060                     else if (value == null)
2061                         l.attributeRemoved(event);
2062                     else
2063                         l.attributeReplaced(event);
2064                 }
2065             }
2066         }
2067 
2068         /* ------------------------------------------------------------ */
2069         /*
2070          * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
2071          */
2072         public synchronized void removeAttribute(String name)
2073         {
2074             checkManagedAttribute(name,null);
2075 
2076             if (_contextAttributes == null)
2077             {
2078                 // Set it on the handler
2079                 _attributes.removeAttribute(name);
2080                 return;
2081             }
2082 
2083             Object old_value = _contextAttributes.getAttribute(name);
2084             _contextAttributes.removeAttribute(name);
2085             if (old_value != null)
2086             {
2087                 if (_contextAttributeListeners != null)
2088                 {
2089                     ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
2090 
2091                     for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
2092                         ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
2093                 }
2094             }
2095         }
2096 
2097         /* ------------------------------------------------------------ */
2098         /*
2099          * @see javax.servlet.ServletContext#getServletContextName()
2100          */
2101         public String getServletContextName()
2102         {
2103             String name = ContextHandler.this.getDisplayName();
2104             if (name == null)
2105                 name = ContextHandler.this.getContextPath();
2106             return name;
2107         }
2108 
2109         /* ------------------------------------------------------------ */
2110         public String getContextPath()
2111         {
2112             if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
2113                 return "";
2114 
2115             return _contextPath;
2116         }
2117 
2118         /* ------------------------------------------------------------ */
2119         @Override
2120         public String toString()
2121         {
2122             return "ServletContext@" + ContextHandler.this.toString();
2123         }
2124 
2125         /* ------------------------------------------------------------ */
2126         public boolean setInitParameter(String name, String value)
2127         {
2128             if (ContextHandler.this.getInitParameter(name) != null)
2129                 return false;
2130             ContextHandler.this.getInitParams().put(name,value);
2131             return true;
2132         }
2133 
2134     }
2135 
2136     private static class CLDump implements Dumpable
2137     {
2138         final ClassLoader _loader;
2139 
2140         CLDump(ClassLoader loader)
2141         {
2142             _loader = loader;
2143         }
2144 
2145         public String dump()
2146         {
2147             return AggregateLifeCycle.dump(this);
2148         }
2149 
2150         public void dump(Appendable out, String indent) throws IOException
2151         {
2152             out.append(String.valueOf(_loader)).append("\n");
2153 
2154             if (_loader != null)
2155             {
2156                 Object parent = _loader.getParent();
2157                 if (parent != null)
2158                 {
2159                     if (!(parent instanceof Dumpable))
2160                         parent = new CLDump((ClassLoader)parent);
2161 
2162                     if (_loader instanceof URLClassLoader)
2163                         AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
2164                     else
2165                         AggregateLifeCycle.dump(out,indent,Collections.singleton(parent));
2166                 }
2167             }
2168         }
2169     }
2170     
2171     
2172     /* ------------------------------------------------------------ */
2173     /** Interface to check aliases
2174      */
2175     public interface AliasCheck
2176     {
2177         /* ------------------------------------------------------------ */
2178         /** Check an alias
2179          * @param path The path the aliased resource was created for
2180          * @param resource The aliased resourced
2181          * @return True if the resource is OK to be served.
2182          */
2183         boolean check(String path, Resource resource);
2184     }
2185     
2186     
2187     /* ------------------------------------------------------------ */
2188     /** Approve Aliases with same suffix.
2189      * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
2190      * approved because both the resource and alias end with ".html".
2191      */
2192     @Deprecated
2193     public static class ApproveSameSuffixAliases implements AliasCheck
2194     {
2195         {
2196             LOG.warn("ApproveSameSuffixAlias is not safe for production");
2197         }
2198         
2199         public boolean check(String path, Resource resource)
2200         {
2201             int dot = path.lastIndexOf('.');
2202             if (dot<0)
2203                 return false;
2204             String suffix=path.substring(dot);
2205             return resource.toString().endsWith(suffix);
2206         }
2207     }
2208     
2209     
2210     /* ------------------------------------------------------------ */
2211     /** Approve Aliases with a path prefix.
2212      * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
2213      * approved because both the resource and alias end with "/foobar.html".
2214      */
2215     @Deprecated
2216     public static class ApprovePathPrefixAliases implements AliasCheck
2217     {
2218         {
2219             LOG.warn("ApprovePathPrefixAliases is not safe for production");
2220         }
2221         
2222         public boolean check(String path, Resource resource)
2223         {
2224             int slash = path.lastIndexOf('/');
2225             if (slash<0 || slash==path.length()-1)
2226                 return false;
2227             String suffix=path.substring(slash);
2228             return resource.toString().endsWith(suffix);
2229         }
2230     }
2231     
2232     /* ------------------------------------------------------------ */
2233     /** Approve Aliases of a non existent directory.
2234      * If a directory "/foobar/" does not exist, then the resource is 
2235      * aliased to "/foobar".  Accept such aliases.
2236      */
2237     public static class ApproveNonExistentDirectoryAliases implements AliasCheck
2238     {
2239         public boolean check(String path, Resource resource)
2240         {
2241             if (resource.exists())
2242                 return false;
2243             
2244             String a=resource.getAlias().toString();
2245             String r=resource.getURL().toString();
2246             
2247             if (a.length()>r.length())
2248                 return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
2249             else
2250                 return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
2251         }
2252     }
2253     
2254 }