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(null, null, jspFile, true);
135 return null;
136 }
137 });
138 } else {
139 serviceJspFile(null, null, 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