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.runtime;
19
20 import java.io.IOException;
21 import java.io.Writer;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.Set;
27
28 import javax.el.ELContext;
29 import javax.el.ELException;
30 import javax.el.ExpressionFactory;
31 import javax.el.ImportHandler;
32 import javax.el.ValueExpression;
33 import javax.servlet.RequestDispatcher;
34 import javax.servlet.Servlet;
35 import javax.servlet.ServletConfig;
36 import javax.servlet.ServletContext;
37 import javax.servlet.ServletException;
38 import javax.servlet.ServletRequest;
39 import javax.servlet.ServletResponse;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import javax.servlet.http.HttpSession;
43 import javax.servlet.jsp.JspException;
44 import javax.servlet.jsp.JspFactory;
45 import javax.servlet.jsp.JspWriter;
46 import javax.servlet.jsp.PageContext;
47 import javax.servlet.jsp.tagext.BodyContent;
48
49 import org.apache.jasper.Constants;
50 import org.apache.jasper.compiler.Localizer;
51 import org.apache.jasper.el.ELContextImpl;
52 import org.apache.jasper.runtime.JspContextWrapper.ELContextWrapper;
53
54 /**
55  * Implementation of the PageContext class from the JSP spec. Also doubles as a
56  * VariableResolver for the EL.
57  *
58  * @author Anil K. Vijendran
59  * @author Larry Cable
60  * @author Hans Bergsten
61  * @author Pierre Delisle
62  * @author Mark Roth
63  * @author Jan Luehe
64  * @author Jacob Hookom
65  */

66 public class PageContextImpl extends PageContext {
67
68     private static final JspFactory jspf = JspFactory.getDefaultFactory();
69
70     private BodyContentImpl[] outs;
71
72     private int depth;
73
74     // per-servlet state
75     private Servlet servlet;
76
77     private ServletConfig config;
78
79     private ServletContext context;
80
81     private JspApplicationContextImpl applicationContext;
82
83     private String errorPageURL;
84
85     // page-scope attributes
86     private final transient HashMap<String, Object> attributes;
87
88     // per-request state
89     private transient ServletRequest request;
90
91     private transient ServletResponse response;
92
93     private transient HttpSession session;
94
95     private transient ELContextImpl elContext;
96
97     private boolean isIncluded;
98
99
100     // initial output stream
101     private transient JspWriter out;
102
103     private transient JspWriterImpl baseOut;
104
105     /*
106      * Constructor.
107      */

108     PageContextImpl() {
109         this.outs = new BodyContentImpl[0];
110         this.attributes = new HashMap<>(16);
111         this.depth = -1;
112     }
113
114     @Override
115     public void initialize(Servlet servlet, ServletRequest request,
116             ServletResponse response, String errorPageURL,
117             boolean needsSession, int bufferSize, boolean autoFlush)
118             throws IOException {
119
120         // initialize state
121         this.servlet = servlet;
122         this.config = servlet.getServletConfig();
123         this.context = config.getServletContext();
124         this.errorPageURL = errorPageURL;
125         this.request = request;
126         this.response = response;
127
128         // initialize application context
129         this.applicationContext = JspApplicationContextImpl.getInstance(context);
130
131         // Setup session (if required)
132         if (request instanceof HttpServletRequest && needsSession)
133             this.session = ((HttpServletRequest) request).getSession();
134         if (needsSession && session == null)
135             throw new IllegalStateException(Localizer.getMessage("jsp.error.page.sessionRequired"));
136
137         // initialize the initial out ...
138         depth = -1;
139         if (bufferSize == JspWriter.DEFAULT_BUFFER) {
140             bufferSize = Constants.DEFAULT_BUFFER_SIZE;
141         }
142         if (this.baseOut == null) {
143             this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush);
144         } else {
145             this.baseOut.init(response, bufferSize, autoFlush);
146         }
147         this.out = baseOut;
148
149         // register names/values as per spec
150         setAttribute(OUT, this.out);
151         setAttribute(REQUEST, request);
152         setAttribute(RESPONSE, response);
153
154         if (session != null)
155             setAttribute(SESSION, session);
156
157         setAttribute(PAGE, servlet);
158         setAttribute(CONFIG, config);
159         setAttribute(PAGECONTEXT, this);
160         setAttribute(APPLICATION, context);
161
162         isIncluded = request.getAttribute(
163                 RequestDispatcher.INCLUDE_SERVLET_PATH) != null;
164     }
165
166     @Override
167     public void release() {
168         out = baseOut;
169         try {
170             if (isIncluded) {
171                 ((JspWriterImpl) out).flushBuffer();
172                 // push it into the including jspWriter
173             } else {
174                 // Old code:
175                 // out.flush();
176                 // Do not flush the buffer even if we're not included (i.e.
177                 // we are the main page. The servlet will flush it and close
178                 // the stream.
179                 ((JspWriterImpl) out).flushBuffer();
180             }
181         } catch (IOException ex) {
182             IllegalStateException ise = new IllegalStateException(Localizer.getMessage("jsp.error.flush"), ex);
183             throw ise;
184         } finally {
185             servlet = null;
186             config = null;
187             context = null;
188             applicationContext = null;
189             elContext = null;
190             errorPageURL = null;
191             request = null;
192             response = null;
193             depth = -1;
194             baseOut.recycle();
195             session = null;
196             attributes.clear();
197             for (BodyContentImpl body: outs) {
198                 body.recycle();
199             }
200         }
201     }
202
203     @Override
204     public Object getAttribute(final String name) {
205         return getAttribute(name, PAGE_SCOPE);
206     }
207
208     @Override
209     public Object getAttribute(final String name, final int scope) {
210
211         if (name == null) {
212             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
213         }
214
215         switch (scope) {
216         case PAGE_SCOPE:
217             return attributes.get(name);
218
219         case REQUEST_SCOPE:
220             return request.getAttribute(name);
221
222         case SESSION_SCOPE:
223             if (session == null) {
224                 throw new IllegalStateException(Localizer.getMessage("jsp.error.page.noSession"));
225             }
226             return session.getAttribute(name);
227
228         case APPLICATION_SCOPE:
229             return context.getAttribute(name);
230
231         default:
232             throw new IllegalArgumentException(Localizer.getMessage("jsp.error.page.invalid.scope"));
233         }
234     }
235
236     @Override
237     public void setAttribute(final String name, final Object attribute) {
238         setAttribute(name, attribute, PAGE_SCOPE);
239     }
240
241     @Override
242     public void setAttribute(final String name, final Object o, final int scope) {
243
244         if (name == null) {
245             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
246         }
247
248         if (o == null) {
249             removeAttribute(name, scope);
250         } else {
251             switch (scope) {
252             case PAGE_SCOPE:
253                 attributes.put(name, o);
254                 break;
255
256             case REQUEST_SCOPE:
257                 request.setAttribute(name, o);
258                 break;
259
260             case SESSION_SCOPE:
261                 if (session == null) {
262                     throw new IllegalStateException(Localizer
263                             .getMessage("jsp.error.page.noSession"));
264                 }
265                 session.setAttribute(name, o);
266                 break;
267
268             case APPLICATION_SCOPE:
269                 context.setAttribute(name, o);
270                 break;
271
272             default:
273                 throw new IllegalArgumentException("Invalid scope");
274             }
275         }
276     }
277
278     @Override
279     public void removeAttribute(final String name, final int scope) {
280
281         if (name == null) {
282             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
283         }
284
285         switch (scope) {
286         case PAGE_SCOPE:
287             attributes.remove(name);
288             break;
289
290         case REQUEST_SCOPE:
291             request.removeAttribute(name);
292             break;
293
294         case SESSION_SCOPE:
295             if (session == null) {
296                 throw new IllegalStateException(Localizer.getMessage("jsp.error.page.noSession"));
297             }
298             session.removeAttribute(name);
299             break;
300
301         case APPLICATION_SCOPE:
302             context.removeAttribute(name);
303             break;
304
305         default:
306             throw new IllegalArgumentException(Localizer.getMessage("jsp.error.page.invalid.scope"));
307         }
308     }
309
310     @Override
311     public int getAttributesScope(final String name) {
312
313         if (name == null) {
314             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
315         }
316
317         if (attributes.get(name) != null) {
318             return PAGE_SCOPE;
319         }
320
321         if (request.getAttribute(name) != null) {
322             return REQUEST_SCOPE;
323         }
324
325         if (session != null) {
326             try {
327                 if (session.getAttribute(name) != null)
328                     return SESSION_SCOPE;
329             } catch(IllegalStateException ise) {
330                 // Session has been invalidated.
331                 // Ignore and fall through to application scope.
332             }
333         }
334
335         if (context.getAttribute(name) != null) {
336             return APPLICATION_SCOPE;
337         }
338
339         return 0;
340     }
341
342     @Override
343     public Object findAttribute(final String name) {
344         if (name == null) {
345             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
346         }
347
348         Object o = attributes.get(name);
349         if (o != null) {
350             return o;
351         }
352
353         o = request.getAttribute(name);
354         if (o != null) {
355             return o;
356         }
357
358         if (session != null) {
359             try {
360                 o = session.getAttribute(name);
361             } catch(IllegalStateException ise) {
362                 // Session has been invalidated.
363                 // Ignore and fall through to application scope.
364             }
365             if (o != null) {
366                 return o;
367             }
368         }
369
370         return context.getAttribute(name);
371     }
372
373     @Override
374     public Enumeration<String> getAttributeNamesInScope(final int scope) {
375         switch (scope) {
376         case PAGE_SCOPE:
377             return Collections.enumeration(attributes.keySet());
378
379         case REQUEST_SCOPE:
380             return request.getAttributeNames();
381
382         case SESSION_SCOPE:
383             if (session == null) {
384                 throw new IllegalStateException(Localizer.getMessage("jsp.error.page.noSession"));
385             }
386             return session.getAttributeNames();
387
388         case APPLICATION_SCOPE:
389             return context.getAttributeNames();
390
391         default:
392             throw new IllegalArgumentException(Localizer.getMessage("jsp.error.page.invalid.scope"));
393         }
394     }
395
396     @Override
397     public void removeAttribute(final String name) {
398
399         if (name == null) {
400             throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
401         }
402
403         removeAttribute(name, PAGE_SCOPE);
404         removeAttribute(name, REQUEST_SCOPE);
405         if( session != null ) {
406             try {
407                 removeAttribute(name, SESSION_SCOPE);
408             } catch(IllegalStateException ise) {
409                 // Session has been invalidated.
410                 // Ignore and fall throw to application scope.
411             }
412         }
413         removeAttribute(name, APPLICATION_SCOPE);
414     }
415
416     @Override
417     public JspWriter getOut() {
418         return out;
419     }
420
421     @Override
422     public HttpSession getSession() {
423         return session;
424     }
425
426     @Override
427     public ServletConfig getServletConfig() {
428         return config;
429     }
430
431     @Override
432     public ServletContext getServletContext() {
433         return config.getServletContext();
434     }
435
436     @Override
437     public ServletRequest getRequest() {
438         return request;
439     }
440
441     @Override
442     public ServletResponse getResponse() {
443         return response;
444     }
445
446     /**
447      * Returns the exception associated with this page context, if any.
448      * <p>
449      * Added wrapping for Throwables to avoid ClassCastException: see Bugzilla
450      * 31171 for details.
451      *
452      * @return The Exception associated with this page context, if any.
453      */

454     @Override
455     public Exception getException() {
456         Throwable t = JspRuntimeLibrary.getThrowable(request);
457
458         // Only wrap if needed
459         if ((t != null) && (!(t instanceof Exception))) {
460             t = new JspException(t);
461         }
462
463         return (Exception) t;
464     }
465
466     @Override
467     public Object getPage() {
468         return servlet;
469     }
470
471     private final String getAbsolutePathRelativeToContext(String relativeUrlPath) {
472         String path = relativeUrlPath;
473
474         if (!path.startsWith("/")) {
475             String uri = (String) request.getAttribute(
476                     RequestDispatcher.INCLUDE_SERVLET_PATH);
477             if (uri == null)
478                 uri = ((HttpServletRequest) request).getServletPath();
479             String baseURI = uri.substring(0, uri.lastIndexOf('/'));
480             path = baseURI + '/' + path;
481         }
482
483         return path;
484     }
485
486     @Override
487     public void include(String relativeUrlPath) throws ServletException,
488             IOException {
489         JspRuntimeLibrary
490                 .include(request, response, relativeUrlPath, out, true);
491     }
492
493     @Override
494     public void include(final String relativeUrlPath, final boolean flush)
495             throws ServletException, IOException {
496         JspRuntimeLibrary.include(request, response, relativeUrlPath, out, flush);
497     }
498
499     @Override
500     @Deprecated
501     public javax.servlet.jsp.el.VariableResolver getVariableResolver() {
502         return new org.apache.jasper.el.VariableResolverImpl(
503                 this.getELContext());
504     }
505
506     @Override
507     public void forward(final String relativeUrlPath) throws ServletException, IOException {
508         // JSP.4.5 If the buffer was flushed, throw IllegalStateException
509         try {
510             out.clear();
511             baseOut.clear();
512         } catch (IOException ex) {
513             IllegalStateException ise = new IllegalStateException(Localizer.getMessage(
514                     "jsp.error.attempt_to_clear_flushed_buffer"));
515             ise.initCause(ex);
516             throw ise;
517         }
518
519         // Make sure that the response object is not the wrapper for include
520         while (response instanceof ServletResponseWrapperInclude) {
521             response = ((ServletResponseWrapperInclude) response).getResponse();
522         }
523
524         final String path = getAbsolutePathRelativeToContext(relativeUrlPath);
525         String includeUri = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
526
527         if (includeUri != null)
528             request.removeAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
529         try {
530             context.getRequestDispatcher(path).forward(request, response);
531         } finally {
532             if (includeUri != null) {
533                 request.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, includeUri);
534             }
535         }
536     }
537
538     @Override
539     public BodyContent pushBody() {
540         return (BodyContent) pushBody(null);
541     }
542
543     @Override
544     public JspWriter pushBody(Writer writer) {
545         depth++;
546         if (depth >= outs.length) {
547             BodyContentImpl[] newOuts = Arrays.copyOf(outs, depth + 1);
548             newOuts[depth] = new BodyContentImpl(out);
549             outs = newOuts;
550         }
551
552         outs[depth].setWriter(writer);
553         out = outs[depth];
554
555         // Update the value of the "out" attribute in the page scope
556         // attribute namespace of this PageContext
557         setAttribute(OUT, out);
558
559         return outs[depth];
560     }
561
562     @Override
563     public JspWriter popBody() {
564         depth--;
565         if (depth >= 0) {
566             out = outs[depth];
567         } else {
568             out = baseOut;
569         }
570
571         // Update the value of the "out" attribute in the page scope
572         // attribute namespace of this PageContext
573         setAttribute(OUT, out);
574
575         return out;
576     }
577
578     /**
579      * Provides programmatic access to the ExpressionEvaluator. The JSP
580      * Container must return a valid instance of an ExpressionEvaluator that can
581      * parse EL expressions.
582      */

583     @Override
584     @Deprecated
585     public javax.servlet.jsp.el.ExpressionEvaluator getExpressionEvaluator() {
586         return new org.apache.jasper.el.ExpressionEvaluatorImpl(
587                 this.applicationContext.getExpressionFactory());
588     }
589
590     @Override
591     public void handlePageException(Exception ex) throws IOException,
592             ServletException {
593         // Should never be called since handleException() called with a
594         // Throwable in the generated servlet.
595         handlePageException((Throwable) ex);
596     }
597
598     @Override
599     @SuppressWarnings("deprecation"// Still jave to support old JSP EL
600     public void handlePageException(final Throwable t) throws IOException, ServletException {
601         if (t == null) {
602             throw new NullPointerException(Localizer.getMessage("jsp.error.page.nullThrowable"));
603         }
604
605         if (errorPageURL != null && !errorPageURL.equals("")) {
606
607             /*
608              * Set request attributes. Do not set the
609              * javax.servlet.error.exception attribute here (instead, set in the
610              * generated servlet code for the error page) in order to prevent
611              * the ErrorReportValve, which is invoked as part of forwarding the
612              * request to the error page, from throwing it if the response has
613              * not been committed (the response will have been committed if the
614              * error page is a JSP page).
615              */

616             request.setAttribute(PageContext.EXCEPTION, t);
617             request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
618                     Integer.valueOf(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
619             request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
620                     ((HttpServletRequest) request).getRequestURI());
621             request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,
622                     config.getServletName());
623             try {
624                 forward(errorPageURL);
625             } catch (IllegalStateException ise) {
626                 include(errorPageURL);
627             }
628
629             // The error page could be inside an include.
630
631             Object newException = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
632
633             // t==null means the attribute was not set.
634             if ((newException != null) && (newException == t)) {
635                 request.removeAttribute(RequestDispatcher.ERROR_EXCEPTION);
636             }
637
638             // now clear the error code - to prevent double handling.
639             request.removeAttribute(RequestDispatcher.ERROR_STATUS_CODE);
640             request.removeAttribute(RequestDispatcher.ERROR_REQUEST_URI);
641             request.removeAttribute(RequestDispatcher.ERROR_SERVLET_NAME);
642             request.removeAttribute(PageContext.EXCEPTION);
643
644         } else {
645             // Otherwise throw the exception wrapped inside a ServletException.
646             // Set the exception as the root cause in the ServletException
647             // to get a stack trace for the real problem
648             if (t instanceof IOException)
649                 throw (IOException) t;
650             if (t instanceof ServletException)
651                 throw (ServletException) t;
652             if (t instanceof RuntimeException)
653                 throw (RuntimeException) t;
654
655             Throwable rootCause = null;
656             if (t instanceof JspException || t instanceof ELException ||
657                     t instanceof javax.servlet.jsp.el.ELException) {
658                 rootCause = t.getCause();
659             }
660
661             if (rootCause != null) {
662                 throw new ServletException(
663                         t.getClass().getName() + ": " + t.getMessage(), rootCause);
664             }
665
666             throw new ServletException(t);
667         }
668     }
669
670     /**
671      * Proprietary method to evaluate EL expressions. XXX - This method should
672      * go away once the EL interpreter moves out of JSTL and into its own
673      * project. For now, this is necessary because the standard machinery is too
674      * slow.
675      *
676      * @param expression
677      *            The expression to be evaluated
678      * @param expectedType
679      *            The expected resulting type
680      * @param pageContext
681      *            The page context
682      * @param functionMap
683      *            Maps prefix and name to Method
684      * @return The result of the evaluation
685      * @throws ELException If an error occurs during the evaluation
686      */

687     public static Object proprietaryEvaluate(final String expression,
688             final Class<?> expectedType, final PageContext pageContext,
689             final ProtectedFunctionMapper functionMap)
690             throws ELException {
691         final ExpressionFactory exprFactory = jspf.getJspApplicationContext(pageContext.getServletContext()).getExpressionFactory();
692         ELContext ctx = pageContext.getELContext();
693         ELContextImpl ctxImpl;
694         if (ctx instanceof ELContextWrapper) {
695             ctxImpl = (ELContextImpl) ((ELContextWrapper) ctx).getWrappedELContext();
696         } else {
697             ctxImpl = (ELContextImpl) ctx;
698         }
699         ctxImpl.setFunctionMapper(functionMap);
700         ValueExpression ve = exprFactory.createValueExpression(ctx, expression, expectedType);
701         return ve.getValue(ctx);
702     }
703
704     @Override
705     public ELContext getELContext() {
706         if (elContext == null) {
707             elContext = applicationContext.createELContext(this);
708             if (servlet instanceof JspSourceImports) {
709                 ImportHandler ih = elContext.getImportHandler();
710                 Set<String> packageImports = ((JspSourceImports) servlet).getPackageImports();
711                 if (packageImports != null) {
712                     for (String packageImport : packageImports) {
713                         ih.importPackage(packageImport);
714                     }
715                 }
716                 Set<String> classImports = ((JspSourceImports) servlet).getClassImports();
717                 if (classImports != null) {
718                     for (String classImport : classImports) {
719                         ih.importClass(classImport);
720                     }
721                 }
722             }
723         }
724         return this.elContext;
725     }
726 }
727