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.compiler;
19
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Vector;
23
24 import javax.servlet.ServletContext;
25 import javax.servlet.descriptor.JspConfigDescriptor;
26 import javax.servlet.descriptor.JspPropertyGroupDescriptor;
27
28 import org.apache.juli.logging.Log;
29 import org.apache.juli.logging.LogFactory;
30
31 /**
32  * Handles the jsp-config element in WEB_INF/web.xml.  This is used
33  * for specifying the JSP configuration information on a JSP page
34  *
35  * @author Kin-man Chung
36  * @author Remy Maucherat
37  */

38
39 public class JspConfig {
40
41     // Logger
42     private final Log log = LogFactory.getLog(JspConfig.class); // must not be static
43
44     private Vector<JspPropertyGroup> jspProperties = null;
45     private final ServletContext ctxt;
46     private volatile boolean initialized = false;
47
48     private static final String defaultIsXml = null;    // unspecified
49     private String defaultIsELIgnored = null;           // unspecified
50     private static final String defaultIsScriptingInvalid = null;
51     private String defaultDeferedSyntaxAllowedAsLiteral = null;
52     private static final String defaultTrimDirectiveWhitespaces = null;
53     private static final String defaultDefaultContentType = null;
54     private static final String defaultBuffer = null;
55     private static final String defaultErrorOnUndeclaredNamespace = "false";
56     private JspProperty defaultJspProperty;
57
58     public JspConfig(ServletContext ctxt) {
59         this.ctxt = ctxt;
60     }
61
62     private void processWebDotXml() {
63
64         // Very, very unlikely but just in case...
65         if (ctxt.getEffectiveMajorVersion() < 2) {
66             defaultIsELIgnored = "true";
67             defaultDeferedSyntaxAllowedAsLiteral = "true";
68             return;
69         }
70         if (ctxt.getEffectiveMajorVersion() == 2) {
71             if (ctxt.getEffectiveMinorVersion() < 5) {
72                 defaultDeferedSyntaxAllowedAsLiteral = "true";
73             }
74             if (ctxt.getEffectiveMinorVersion() < 4) {
75                 defaultIsELIgnored = "true";
76                 return;
77             }
78         }
79
80         JspConfigDescriptor jspConfig = ctxt.getJspConfigDescriptor();
81
82         if (jspConfig == null) {
83             return;
84         }
85
86         jspProperties = new Vector<>();
87         Collection<JspPropertyGroupDescriptor> jspPropertyGroups =
88                 jspConfig.getJspPropertyGroups();
89
90         for (JspPropertyGroupDescriptor jspPropertyGroup : jspPropertyGroups) {
91
92             Collection<String> urlPatterns = jspPropertyGroup.getUrlPatterns();
93
94             if (urlPatterns.size() == 0) {
95                 continue;
96             }
97
98             JspProperty property = new JspProperty(jspPropertyGroup.getIsXml(),
99                     jspPropertyGroup.getElIgnored(),
100                     jspPropertyGroup.getScriptingInvalid(),
101                     jspPropertyGroup.getPageEncoding(),
102                     jspPropertyGroup.getIncludePreludes(),
103                     jspPropertyGroup.getIncludeCodas(),
104                     jspPropertyGroup.getDeferredSyntaxAllowedAsLiteral(),
105                     jspPropertyGroup.getTrimDirectiveWhitespaces(),
106                     jspPropertyGroup.getDefaultContentType(),
107                     jspPropertyGroup.getBuffer(),
108                     jspPropertyGroup.getErrorOnUndeclaredNamespace());
109
110             // Add one JspPropertyGroup for each URL Pattern.  This makes
111             // the matching logic easier.
112             for (String urlPattern : urlPatterns) {
113                 String path = null;
114                 String extension = null;
115
116                 if (urlPattern.indexOf('*') < 0) {
117                     // Exact match
118                     path = urlPattern;
119                 } else {
120                     int i = urlPattern.lastIndexOf('/');
121                     String file;
122                     if (i >= 0) {
123                         path = urlPattern.substring(0,i+1);
124                         file = urlPattern.substring(i+1);
125                     } else {
126                         file = urlPattern;
127                     }
128
129                     // pattern must be "*", or of the form "*.jsp"
130                     if (file.equals("*")) {
131                         extension = "*";
132                     } else if (file.startsWith("*.")) {
133                         extension = file.substring(file.indexOf('.')+1);
134                     }
135
136                     // The url patterns are reconstructed as the following:
137                     // path != null, extension == null:  / or /foo/bar.ext
138                     // path == null, extension != null:  *.ext
139                     // path != null, extension == "*":   /foo/*
140                     boolean isStar = "*".equals(extension);
141                     if ((path == null && (extension == null || isStar))
142                             || (path != null && !isStar)) {
143                         if (log.isWarnEnabled()) {
144                             log.warn(Localizer.getMessage(
145                                     "jsp.warning.bad.urlpattern.propertygroup",
146                                     urlPattern));
147                         }
148                         continue;
149                     }
150                 }
151
152                 JspPropertyGroup propertyGroup =
153                     new JspPropertyGroup(path, extension, property);
154
155                 jspProperties.addElement(propertyGroup);
156             }
157         }
158     }
159
160     private void init() {
161
162         if (!initialized) {
163             synchronized (this) {
164                 if (!initialized) {
165                     processWebDotXml();
166                     defaultJspProperty = new JspProperty(defaultIsXml,
167                             defaultIsELIgnored,
168                             defaultIsScriptingInvalid,
169                             nullnullnull,
170                             defaultDeferedSyntaxAllowedAsLiteral,
171                             defaultTrimDirectiveWhitespaces,
172                             defaultDefaultContentType,
173                             defaultBuffer,
174                             defaultErrorOnUndeclaredNamespace);
175                     initialized = true;
176                 }
177             }
178         }
179     }
180
181     /**
182      * Select the property group that has more restrictive url-pattern.
183      * In case of tie, select the first.
184      */

185     @SuppressWarnings("null"// NPE not possible
186     private JspPropertyGroup selectProperty(JspPropertyGroup prev,
187             JspPropertyGroup curr) {
188         if (prev == null) {
189             return curr;
190         }
191         if (prev.getExtension() == null) {
192             // exact match
193             return prev;
194         }
195         if (curr.getExtension() == null) {
196             // exact match
197             return curr;
198         }
199         String prevPath = prev.getPath();
200         String currPath = curr.getPath();
201         if (prevPath == null && currPath == null) {
202             // Both specifies a *.ext, keep the first one
203             return prev;
204         }
205         if (prevPath == null && currPath != null) {
206             return curr;
207         }
208         if (prevPath != null && currPath == null) {
209             return prev;
210         }
211         if (prevPath.length() >= currPath.length()) {
212             return prev;
213         }
214         return curr;
215     }
216
217
218     /**
219      * Find a property that best matches the supplied resource.
220      * @param uri the resource supplied.
221      * @return a JspProperty indicating the best match, or some default.
222      */

223     public JspProperty findJspProperty(String uri) {
224
225         init();
226
227         // JSP Configuration settings do not apply to tag files
228         if (jspProperties == null || uri.endsWith(".tag")
229                 || uri.endsWith(".tagx")) {
230             return defaultJspProperty;
231         }
232
233         String uriPath = null;
234         int index = uri.lastIndexOf('/');
235         if (index >=0 ) {
236             uriPath = uri.substring(0, index+1);
237         }
238         String uriExtension = null;
239         index = uri.lastIndexOf('.');
240         if (index >=0) {
241             uriExtension = uri.substring(index+1);
242         }
243
244         Collection<String> includePreludes = new ArrayList<>();
245         Collection<String> includeCodas = new ArrayList<>();
246
247         JspPropertyGroup isXmlMatch = null;
248         JspPropertyGroup elIgnoredMatch = null;
249         JspPropertyGroup scriptingInvalidMatch = null;
250         JspPropertyGroup pageEncodingMatch = null;
251         JspPropertyGroup deferedSyntaxAllowedAsLiteralMatch = null;
252         JspPropertyGroup trimDirectiveWhitespacesMatch = null;
253         JspPropertyGroup defaultContentTypeMatch = null;
254         JspPropertyGroup bufferMatch = null;
255         JspPropertyGroup errorOnUndeclaredNamespaceMatch = null;
256
257         for (JspPropertyGroup jpg : jspProperties) {
258             JspProperty jp = jpg.getJspProperty();
259
260             // (arrays will be the same length)
261             String extension = jpg.getExtension();
262             String path = jpg.getPath();
263
264             if (extension == null) {
265                 // exact match pattern: /a/foo.jsp
266                 if (!uri.equals(path)) {
267                     // not matched;
268                     continue;
269                 }
270             } else {
271                 // Matching patterns *.ext or /p/*
272                 if (path != null && uriPath != null &&
273                         ! uriPath.startsWith(path)) {
274                     // not matched
275                     continue;
276                 }
277                 if (!extension.equals("*") &&
278                         !extension.equals(uriExtension)) {
279                     // not matched
280                     continue;
281                 }
282             }
283             // We have a match
284             // Add include-preludes and include-codas
285             if (jp.getIncludePrelude() != null) {
286                 includePreludes.addAll(jp.getIncludePrelude());
287             }
288             if (jp.getIncludeCoda() != null) {
289                 includeCodas.addAll(jp.getIncludeCoda());
290             }
291
292             // If there is a previous match for the same property, remember
293             // the one that is more restrictive.
294             if (jp.isXml() != null) {
295                 isXmlMatch = selectProperty(isXmlMatch, jpg);
296             }
297             if (jp.isELIgnored() != null) {
298                 elIgnoredMatch = selectProperty(elIgnoredMatch, jpg);
299             }
300             if (jp.isScriptingInvalid() != null) {
301                 scriptingInvalidMatch =
302                     selectProperty(scriptingInvalidMatch, jpg);
303             }
304             if (jp.getPageEncoding() != null) {
305                 pageEncodingMatch = selectProperty(pageEncodingMatch, jpg);
306             }
307             if (jp.isDeferedSyntaxAllowedAsLiteral() != null) {
308                 deferedSyntaxAllowedAsLiteralMatch =
309                     selectProperty(deferedSyntaxAllowedAsLiteralMatch, jpg);
310             }
311             if (jp.isTrimDirectiveWhitespaces() != null) {
312                 trimDirectiveWhitespacesMatch =
313                     selectProperty(trimDirectiveWhitespacesMatch, jpg);
314             }
315             if (jp.getDefaultContentType() != null) {
316                 defaultContentTypeMatch =
317                     selectProperty(defaultContentTypeMatch, jpg);
318             }
319             if (jp.getBuffer() != null) {
320                 bufferMatch = selectProperty(bufferMatch, jpg);
321             }
322             if (jp.isErrorOnUndeclaredNamespace() != null) {
323                 errorOnUndeclaredNamespaceMatch =
324                     selectProperty(errorOnUndeclaredNamespaceMatch, jpg);
325             }
326         }
327
328
329         String isXml = defaultIsXml;
330         String isELIgnored = defaultIsELIgnored;
331         String isScriptingInvalid = defaultIsScriptingInvalid;
332         String pageEncoding = null;
333         String isDeferedSyntaxAllowedAsLiteral =
334             defaultDeferedSyntaxAllowedAsLiteral;
335         String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces;
336         String defaultContentType = defaultDefaultContentType;
337         String buffer = defaultBuffer;
338         String errorOnUndeclaredNamespace = defaultErrorOnUndeclaredNamespace;
339
340         if (isXmlMatch != null) {
341             isXml = isXmlMatch.getJspProperty().isXml();
342         }
343         if (elIgnoredMatch != null) {
344             isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored();
345         }
346         if (scriptingInvalidMatch != null) {
347             isScriptingInvalid =
348                 scriptingInvalidMatch.getJspProperty().isScriptingInvalid();
349         }
350         if (pageEncodingMatch != null) {
351             pageEncoding = pageEncodingMatch.getJspProperty().getPageEncoding();
352         }
353         if (deferedSyntaxAllowedAsLiteralMatch != null) {
354             isDeferedSyntaxAllowedAsLiteral =
355                 deferedSyntaxAllowedAsLiteralMatch.getJspProperty().isDeferedSyntaxAllowedAsLiteral();
356         }
357         if (trimDirectiveWhitespacesMatch != null) {
358             isTrimDirectiveWhitespaces =
359                 trimDirectiveWhitespacesMatch.getJspProperty().isTrimDirectiveWhitespaces();
360         }
361         if (defaultContentTypeMatch != null) {
362             defaultContentType =
363                 defaultContentTypeMatch.getJspProperty().getDefaultContentType();
364         }
365         if (bufferMatch != null) {
366             buffer = bufferMatch.getJspProperty().getBuffer();
367         }
368         if (errorOnUndeclaredNamespaceMatch != null) {
369             errorOnUndeclaredNamespace =
370                 errorOnUndeclaredNamespaceMatch.getJspProperty().isErrorOnUndeclaredNamespace();
371         }
372
373         return new JspProperty(isXml, isELIgnored, isScriptingInvalid,
374                 pageEncoding, includePreludes, includeCodas,
375                 isDeferedSyntaxAllowedAsLiteral, isTrimDirectiveWhitespaces,
376                 defaultContentType, buffer, errorOnUndeclaredNamespace);
377     }
378
379     /**
380      * To find out if a uri matches a url pattern in jsp config.  If so,
381      * then the uri is a JSP page.  This is used primarily for jspc.
382      * @param uri The path to check
383      * @return <code>true</code> if the path denotes a JSP page
384      */

385     public boolean isJspPage(String uri) {
386
387         init();
388         if (jspProperties == null) {
389             return false;
390         }
391
392         String uriPath = null;
393         int index = uri.lastIndexOf('/');
394         if (index >=0 ) {
395             uriPath = uri.substring(0, index+1);
396         }
397         String uriExtension = null;
398         index = uri.lastIndexOf('.');
399         if (index >=0) {
400             uriExtension = uri.substring(index+1);
401         }
402
403         for (JspPropertyGroup jpg : jspProperties) {
404
405             String extension = jpg.getExtension();
406             String path = jpg.getPath();
407
408             if (extension == null) {
409                 if (uri.equals(path)) {
410                     // There is an exact match
411                     return true;
412                 }
413             } else {
414                 if ((path == null || path.equals(uriPath)) &&
415                         (extension.equals("*") || extension.equals(uriExtension))) {
416                     // Matches *, *.ext, /p/*, or /p/*.ext
417                     return true;
418                 }
419             }
420         }
421         return false;
422     }
423
424     public static class JspPropertyGroup {
425         private final String path;
426         private final String extension;
427         private final JspProperty jspProperty;
428
429         JspPropertyGroup(String path, String extension,
430                 JspProperty jspProperty) {
431             this.path = path;
432             this.extension = extension;
433             this.jspProperty = jspProperty;
434         }
435
436         public String getPath() {
437             return path;
438         }
439
440         public String getExtension() {
441             return extension;
442         }
443
444         public JspProperty getJspProperty() {
445             return jspProperty;
446         }
447     }
448
449     public static class JspProperty {
450
451         private final String isXml;
452         private final String elIgnored;
453         private final String scriptingInvalid;
454         private final String pageEncoding;
455         private final Collection<String> includePrelude;
456         private final Collection<String> includeCoda;
457         private final String deferedSyntaxAllowedAsLiteral;
458         private final String trimDirectiveWhitespaces;
459         private final String defaultContentType;
460         private final String buffer;
461         private final String errorOnUndeclaredNamespace;
462
463         public JspProperty(String isXml, String elIgnored,
464                 String scriptingInvalid, String pageEncoding,
465                 Collection<String> includePrelude, Collection<String> includeCoda,
466                 String deferedSyntaxAllowedAsLiteral,
467                 String trimDirectiveWhitespaces,
468                 String defaultContentType,
469                 String buffer,
470                 String errorOnUndeclaredNamespace) {
471
472             this.isXml = isXml;
473             this.elIgnored = elIgnored;
474             this.scriptingInvalid = scriptingInvalid;
475             this.pageEncoding = pageEncoding;
476             this.includePrelude = includePrelude;
477             this.includeCoda = includeCoda;
478             this.deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral;
479             this.trimDirectiveWhitespaces = trimDirectiveWhitespaces;
480             this.defaultContentType = defaultContentType;
481             this.buffer = buffer;
482             this.errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
483         }
484
485         public String isXml() {
486             return isXml;
487         }
488
489         public String isELIgnored() {
490             return elIgnored;
491         }
492
493         public String isScriptingInvalid() {
494             return scriptingInvalid;
495         }
496
497         public String getPageEncoding() {
498             return pageEncoding;
499         }
500
501         public Collection<String> getIncludePrelude() {
502             return includePrelude;
503         }
504
505         public Collection<String> getIncludeCoda() {
506             return includeCoda;
507         }
508
509         public String isDeferedSyntaxAllowedAsLiteral() {
510             return deferedSyntaxAllowedAsLiteral;
511         }
512
513         public String isTrimDirectiveWhitespaces() {
514             return trimDirectiveWhitespaces;
515         }
516
517         public String getDefaultContentType() {
518             return defaultContentType;
519         }
520
521         public String getBuffer() {
522             return buffer;
523         }
524
525         public String isErrorOnUndeclaredNamespace() {
526             return errorOnUndeclaredNamespace;
527         }
528     }
529 }
530