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
18 package org.apache.jasper.servlet;
19
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.lang.reflect.Constructor;
23 import java.net.MalformedURLException;
24 import java.security.AccessController;
25 import java.security.PrivilegedActionException;
26 import java.security.PrivilegedExceptionAction;
27
28 import javax.servlet.RequestDispatcher;
29 import javax.servlet.ServletConfig;
30 import javax.servlet.ServletContext;
31 import javax.servlet.ServletException;
32 import javax.servlet.http.HttpServlet;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.apache.jasper.Constants;
37 import org.apache.jasper.EmbeddedServletOptions;
38 import org.apache.jasper.Options;
39 import org.apache.jasper.compiler.JspRuntimeContext;
40 import org.apache.jasper.compiler.Localizer;
41 import org.apache.jasper.runtime.ExceptionUtils;
42 import org.apache.jasper.security.SecurityUtil;
43 import org.apache.juli.logging.Log;
44 import org.apache.juli.logging.LogFactory;
45 import org.apache.tomcat.PeriodicEventListener;
46 import org.apache.tomcat.util.security.Escape;
47
48 /**
49  * The JSP engine (a.k.a Jasper).
50  *
51  * The servlet container is responsible for providing a
52  * URLClassLoader for the web application context Jasper
53  * is being used in. Jasper will try get the Tomcat
54  * ServletContext attribute for its ServletContext class
55  * loader, if that fails, it uses the parent class loader.
56  * In either case, it must be a URLClassLoader.
57  *
58  * @author Anil K. Vijendran
59  * @author Harish Prabandham
60  * @author Remy Maucherat
61  * @author Kin-man Chung
62  * @author Glenn Nielsen
63  */

64 public class JspServlet extends HttpServlet implements PeriodicEventListener {
65
66     private static final long serialVersionUID = 1L;
67
68     // Logger
69     private final transient Log log = LogFactory.getLog(JspServlet.class);
70
71     private transient ServletContext context;
72     private ServletConfig config;
73     private transient Options options;
74     private transient JspRuntimeContext rctxt;
75     // jspFile for a jsp configured explicitly as a servlet, in environments where this
76     // configuration is translated into an init-param for this servlet.
77     private String jspFile;
78
79
80     /*
81      * Initializes this JspServlet.
82      */

83     @Override
84     public void init(ServletConfig config) throws ServletException {
85
86         super.init(config);
87         this.config = config;
88         this.context = config.getServletContext();
89
90         // Initialize the JSP Runtime Context
91         // Check for a custom Options implementation
92         String engineOptionsName = config.getInitParameter("engineOptionsClass");
93         if (Constants.IS_SECURITY_ENABLED && engineOptionsName != null) {
94             log.info(Localizer.getMessage(
95                     "jsp.info.ignoreSetting""engineOptionsClass", engineOptionsName));
96             engineOptionsName = null;
97         }
98         if (engineOptionsName != null) {
99             // Instantiate the indicated Options implementation
100             try {
101                 ClassLoader loader = Thread.currentThread().getContextClassLoader();
102                 Class<?> engineOptionsClass = loader.loadClass(engineOptionsName);
103                 Class<?>[] ctorSig = { ServletConfig.class, ServletContext.class };
104                 Constructor<?> ctor = engineOptionsClass.getConstructor(ctorSig);
105                 Object[] args = { config, context };
106                 options = (Options) ctor.newInstance(args);
107             } catch (Throwable e) {
108                 e = ExceptionUtils.unwrapInvocationTargetException(e);
109                 ExceptionUtils.handleThrowable(e);
110                 // Need to localize this.
111                 log.warn(Localizer.getMessage("jsp.warning.engineOptionsClass", engineOptionsName), e);
112                 // Use the default Options implementation
113                 options = new EmbeddedServletOptions(config, context);
114             }
115         } else {
116             // Use the default Options implementation
117             options = new EmbeddedServletOptions(config, context);
118         }
119         rctxt = new JspRuntimeContext(context, options);
120         if (config.getInitParameter("jspFile") != null) {
121             jspFile = config.getInitParameter("jspFile");
122             try {
123                 if (null == context.getResource(jspFile)) {
124                     return;
125                 }
126             } catch (MalformedURLException e) {
127                 throw new ServletException(Localizer.getMessage("jsp.error.no.jsp", jspFile), e);
128             }
129             try {
130                 if (SecurityUtil.isPackageProtectionEnabled()){
131                    AccessController.doPrivileged(new PrivilegedExceptionAction<Object>(){
132                         @Override
133                         public Object run() throws IOException, ServletException {
134                             serviceJspFile(nullnull, jspFile, true);
135                             return null;
136                         }
137                     });
138                 } else {
139                     serviceJspFile(nullnull, jspFile, true);
140                 }
141             } catch (IOException e) {
142                 throw new ServletException(Localizer.getMessage("jsp.error.precompilation", jspFile), e);
143             } catch (PrivilegedActionException e) {
144                 Throwable t = e.getCause();
145                 if (t instanceof ServletException) throw (ServletException)t;
146                 throw new ServletException(Localizer.getMessage("jsp.error.precompilation", jspFile), e);
147             }
148         }
149
150         if (log.isDebugEnabled()) {
151             log.debug(Localizer.getMessage("jsp.message.scratch.dir.is",
152                     options.getScratchDir().toString()));
153             log.debug(Localizer.getMessage("jsp.message.dont.modify.servlets"));
154         }
155     }
156
157
158     /**
159      * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
160      * the number of JSPs that have been loaded into the webapp with which
161      * this JspServlet is associated.
162      *
163      * <p>This info may be used for monitoring purposes.
164      *
165      * @return The number of JSPs that have been loaded into the webapp with
166      * which this JspServlet is associated
167      */

168     public int getJspCount() {
169         return this.rctxt.getJspCount();
170     }
171
172
173     /**
174      * Resets the JSP reload counter.
175      *
176      * @param count Value to which to reset the JSP reload counter
177      */

178     public void setJspReloadCount(int count) {
179         this.rctxt.setJspReloadCount(count);
180     }
181
182
183     /**
184      * Gets the number of JSPs that have been reloaded.
185      *
186      * <p>This info may be used for monitoring purposes.
187      *
188      * @return The number of JSPs (in the webapp with which this JspServlet is
189      * associated) that have been reloaded
190      */

191     public int getJspReloadCount() {
192         return this.rctxt.getJspReloadCount();
193     }
194
195
196     /**
197      * Gets the number of JSPs that are in the JSP limiter queue
198      *
199      * <p>This info may be used for monitoring purposes.
200      *
201      * @return The number of JSPs (in the webapp with which this JspServlet is
202      * associated) that are in the JSP limiter queue
203      */

204     public int getJspQueueLength() {
205         return this.rctxt.getJspQueueLength();
206     }
207
208
209     /**
210      * Gets the number of JSPs that have been unloaded.
211      *
212      * <p>This info may be used for monitoring purposes.
213      *
214      * @return The number of JSPs (in the webapp with which this JspServlet is
215      * associated) that have been unloaded
216      */

217     public int getJspUnloadCount() {
218         return this.rctxt.getJspUnloadCount();
219     }
220
221
222     /**
223      * <p>Look for a <em>precompilation request</em> as described in
224      * Section 8.4.2 of the JSP 1.2 Specification.  <strong>WARNING</strong> -
225      * we cannot use <code>request.getParameter()</code> for this, because
226      * that will trigger parsing all of the request parameters, and not give
227      * a servlet the opportunity to call
228      * <code>request.setCharacterEncoding()</code> first.</p>
229      *
230      * @param request The servlet request we are processing
231      *
232      * @exception ServletException if an invalid parameter value for the
233      *  <code>jsp_precompile</code> parameter name is specified
234      */

235     boolean preCompile(HttpServletRequest request) throws ServletException {
236
237         String queryString = request.getQueryString();
238         if (queryString == null) {
239             return false;
240         }
241         int start = queryString.indexOf(Constants.PRECOMPILE);
242         if (start < 0) {
243             return false;
244         }
245         queryString =
246             queryString.substring(start + Constants.PRECOMPILE.length());
247         if (queryString.length() == 0) {
248             return true;             // ?jsp_precompile
249         }
250         if (queryString.startsWith("&")) {
251             return true;             // ?jsp_precompile&foo=bar...
252         }
253         if (!queryString.startsWith("=")) {
254             return false;            // part of some other name or value
255         }
256         int limit = queryString.length();
257         int ampersand = queryString.indexOf('&');
258         if (ampersand > 0) {
259             limit = ampersand;
260         }
261         String value = queryString.substring(1, limit);
262         if (value.equals("true")) {
263             return true;             // ?jsp_precompile=true
264         } else if (value.equals("false")) {
265             // Spec says if jsp_precompile=false, the request should not
266             // be delivered to the JSP page; the easiest way to implement
267             // this is to set the flag to true, and precompile the page anyway.
268             // This still conforms to the spec, since it says the
269             // precompilation request can be ignored.
270             return true;             // ?jsp_precompile=false
271         } else {
272             throw new ServletException(Localizer.getMessage("jsp.error.precompilation.parameter",
273                     Constants.PRECOMPILE, value));
274         }
275
276     }
277
278
279     @Override
280     public void service (HttpServletRequest request, HttpServletResponse response)
281             throws ServletException, IOException {
282
283         // jspFile may be configured as an init-param for this servlet instance
284         String jspUri = jspFile;
285
286         if (jspUri == null) {
287             /*
288              * Check to see if the requested JSP has been the target of a
289              * RequestDispatcher.include()
290              */

291             jspUri = (String) request.getAttribute(
292                     RequestDispatcher.INCLUDE_SERVLET_PATH);
293             if (jspUri != null) {
294                 /*
295                  * Requested JSP has been target of
296                  * RequestDispatcher.include(). Its path is assembled from the
297                  * relevant javax.servlet.include.* request attributes
298                  */

299                 String pathInfo = (String) request.getAttribute(
300                         RequestDispatcher.INCLUDE_PATH_INFO);
301                 if (pathInfo != null) {
302                     jspUri += pathInfo;
303                 }
304             } else {
305                 /*
306                  * Requested JSP has not been the target of a
307                  * RequestDispatcher.include(). Reconstruct its path from the
308                  * request's getServletPath() and getPathInfo()
309                  */

310                 jspUri = request.getServletPath();
311                 String pathInfo = request.getPathInfo();
312                 if (pathInfo != null) {
313                     jspUri += pathInfo;
314                 }
315             }
316         }
317
318         if (log.isDebugEnabled()) {
319             log.debug("JspEngine --> " + jspUri);
320             log.debug("\t     ServletPath: " + request.getServletPath());
321             log.debug("\t        PathInfo: " + request.getPathInfo());
322             log.debug("\t        RealPath: " + context.getRealPath(jspUri));
323             log.debug("\t      RequestURI: " + request.getRequestURI());
324             log.debug("\t     QueryString: " + request.getQueryString());
325         }
326
327         try {
328             boolean precompile = preCompile(request);
329             serviceJspFile(request, response, jspUri, precompile);
330         } catch (RuntimeException e) {
331             throw e;
332         } catch (ServletException e) {
333             throw e;
334         } catch (IOException e) {
335             throw e;
336         } catch (Throwable e) {
337             ExceptionUtils.handleThrowable(e);
338             throw new ServletException(e);
339         }
340
341     }
342
343     @Override
344     public void destroy() {
345         if (log.isDebugEnabled()) {
346             log.debug("JspServlet.destroy()");
347         }
348
349         rctxt.destroy();
350     }
351
352
353     @Override
354     public void periodicEvent() {
355         rctxt.checkUnload();
356         rctxt.checkCompile();
357     }
358
359     // -------------------------------------------------------- Private Methods
360
361     private void serviceJspFile(HttpServletRequest request,
362                                 HttpServletResponse response, String jspUri,
363                                 boolean precompile)
364         throws ServletException, IOException {
365
366         JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
367         if (wrapper == null) {
368             synchronized(this) {
369                 wrapper = rctxt.getWrapper(jspUri);
370                 if (wrapper == null) {
371                     // Check if the requested JSP page exists, to avoid
372                     // creating unnecessary directories and files.
373                     if (null == context.getResource(jspUri)) {
374                         handleMissingResource(request, response, jspUri);
375                         return;
376                     }
377                     wrapper = new JspServletWrapper(config, options, jspUri,
378                                                     rctxt);
379                     rctxt.addWrapper(jspUri,wrapper);
380                 }
381             }
382         }
383
384         try {
385             wrapper.service(request, response, precompile);
386         } catch (FileNotFoundException fnfe) {
387             handleMissingResource(request, response, jspUri);
388         }
389
390     }
391
392
393     private void handleMissingResource(HttpServletRequest request,
394             HttpServletResponse response, String jspUri)
395             throws ServletException, IOException {
396
397         String includeRequestUri =
398             (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
399
400         if (includeRequestUri != null) {
401             // This file was included. Throw an exception as
402             // a response.sendError() will be ignored
403             String msg =
404                 Localizer.getMessage("jsp.error.file.not.found",jspUri);
405             // Strictly, filtering this is an application
406             // responsibility but just in case...
407             throw new ServletException(Escape.htmlElementContent(msg));
408         } else {
409             try {
410                 response.sendError(HttpServletResponse.SC_NOT_FOUND,
411                         request.getRequestURI());
412             } catch (IllegalStateException ise) {
413                 log.error(Localizer.getMessage("jsp.error.file.not.found",
414                         jspUri));
415             }
416         }
417     }
418
419
420 }
421