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 null, null, null,
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