001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jasper.compiler;
019:
020: import java.io.InputStream;
021: import java.util.Iterator;
022: import java.util.Vector;
023: import java.net.URL;
024:
025: import javax.servlet.ServletContext;
026:
027: import org.apache.jasper.JasperException;
028: import org.apache.jasper.xmlparser.ParserUtils;
029: import org.apache.jasper.xmlparser.TreeNode;
030: import org.apache.juli.logging.Log;
031: import org.apache.juli.logging.LogFactory;
032: import org.xml.sax.InputSource;
033:
034: /**
035: * Handles the jsp-config element in WEB_INF/web.xml. This is used
036: * for specifying the JSP configuration information on a JSP page
037: *
038: * @author Kin-man Chung
039: * @author Remy Maucherat
040: */
041:
042: public class JspConfig {
043:
044: private static final String WEB_XML = "/WEB-INF/web.xml";
045:
046: // Logger
047: private Log log = LogFactory.getLog(JspConfig.class);
048:
049: private Vector jspProperties = null;
050: private ServletContext ctxt;
051: private boolean initialized = false;
052:
053: private String defaultIsXml = null; // unspecified
054: private String defaultIsELIgnored = null; // unspecified
055: private String defaultIsScriptingInvalid = null;
056: private String defaultDeferedSyntaxAllowedAsLiteral = null;
057: private String defaultTrimDirectiveWhitespaces = null;
058: private JspProperty defaultJspProperty;
059:
060: public JspConfig(ServletContext ctxt) {
061: this .ctxt = ctxt;
062: }
063:
064: private double getVersion(TreeNode webApp) {
065: String v = webApp.findAttribute("version");
066: if (v != null) {
067: try {
068: return Double.parseDouble(v);
069: } catch (NumberFormatException e) {
070: }
071: }
072: return 2.3;
073: }
074:
075: private void processWebDotXml(ServletContext ctxt)
076: throws JasperException {
077:
078: InputStream is = null;
079:
080: try {
081: URL uri = ctxt.getResource(WEB_XML);
082: if (uri == null) {
083: // no web.xml
084: return;
085: }
086:
087: is = uri.openStream();
088: InputSource ip = new InputSource(is);
089: ip.setSystemId(uri.toExternalForm());
090:
091: ParserUtils pu = new ParserUtils();
092: TreeNode webApp = pu.parseXMLDocument(WEB_XML, ip);
093:
094: if (webApp == null || getVersion(webApp) < 2.4) {
095: defaultIsELIgnored = "true";
096: return;
097: }
098: TreeNode jspConfig = webApp.findChild("jsp-config");
099: if (jspConfig == null) {
100: return;
101: }
102:
103: jspProperties = new Vector();
104: Iterator jspPropertyList = jspConfig
105: .findChildren("jsp-property-group");
106: while (jspPropertyList.hasNext()) {
107:
108: TreeNode element = (TreeNode) jspPropertyList.next();
109: Iterator list = element.findChildren();
110:
111: Vector urlPatterns = new Vector();
112: String pageEncoding = null;
113: String scriptingInvalid = null;
114: String elIgnored = null;
115: String isXml = null;
116: Vector includePrelude = new Vector();
117: Vector includeCoda = new Vector();
118: String deferredSyntaxAllowedAsLiteral = null;
119: String trimDirectiveWhitespaces = null;
120:
121: while (list.hasNext()) {
122:
123: element = (TreeNode) list.next();
124: String tname = element.getName();
125:
126: if ("url-pattern".equals(tname))
127: urlPatterns.addElement(element.getBody());
128: else if ("page-encoding".equals(tname))
129: pageEncoding = element.getBody();
130: else if ("is-xml".equals(tname))
131: isXml = element.getBody();
132: else if ("el-ignored".equals(tname))
133: elIgnored = element.getBody();
134: else if ("scripting-invalid".equals(tname))
135: scriptingInvalid = element.getBody();
136: else if ("include-prelude".equals(tname))
137: includePrelude.addElement(element.getBody());
138: else if ("include-coda".equals(tname))
139: includeCoda.addElement(element.getBody());
140: else if ("deferred-syntax-allowed-as-literal"
141: .equals(tname))
142: deferredSyntaxAllowedAsLiteral = element
143: .getBody();
144: else if ("trim-directive-whitespaces".equals(tname))
145: trimDirectiveWhitespaces = element.getBody();
146: }
147:
148: if (urlPatterns.size() == 0) {
149: continue;
150: }
151:
152: // Add one JspPropertyGroup for each URL Pattern. This makes
153: // the matching logic easier.
154: for (int p = 0; p < urlPatterns.size(); p++) {
155: String urlPattern = (String) urlPatterns
156: .elementAt(p);
157: String path = null;
158: String extension = null;
159:
160: if (urlPattern.indexOf('*') < 0) {
161: // Exact match
162: path = urlPattern;
163: } else {
164: int i = urlPattern.lastIndexOf('/');
165: String file;
166: if (i >= 0) {
167: path = urlPattern.substring(0, i + 1);
168: file = urlPattern.substring(i + 1);
169: } else {
170: file = urlPattern;
171: }
172:
173: // pattern must be "*", or of the form "*.jsp"
174: if (file.equals("*")) {
175: extension = "*";
176: } else if (file.startsWith("*.")) {
177: extension = file.substring(file
178: .indexOf('.') + 1);
179: }
180:
181: // The url patterns are reconstructed as the follwoing:
182: // path != null, extension == null: / or /foo/bar.ext
183: // path == null, extension != null: *.ext
184: // path != null, extension == "*": /foo/*
185: boolean isStar = "*".equals(extension);
186: if ((path == null && (extension == null || isStar))
187: || (path != null && !isStar)) {
188: if (log.isWarnEnabled()) {
189: log
190: .warn(Localizer
191: .getMessage(
192: "jsp.warning.bad.urlpattern.propertygroup",
193: urlPattern));
194: }
195: continue;
196: }
197: }
198:
199: JspProperty property = new JspProperty(isXml,
200: elIgnored, scriptingInvalid, pageEncoding,
201: includePrelude, includeCoda,
202: deferredSyntaxAllowedAsLiteral,
203: trimDirectiveWhitespaces);
204: JspPropertyGroup propertyGroup = new JspPropertyGroup(
205: path, extension, property);
206:
207: jspProperties.addElement(propertyGroup);
208: }
209: }
210: } catch (Exception ex) {
211: throw new JasperException(ex);
212: } finally {
213: if (is != null) {
214: try {
215: is.close();
216: } catch (Throwable t) {
217: }
218: }
219: }
220: }
221:
222: private void init() throws JasperException {
223:
224: if (!initialized) {
225: processWebDotXml(ctxt);
226: defaultJspProperty = new JspProperty(defaultIsXml,
227: defaultIsELIgnored, defaultIsScriptingInvalid,
228: null, null, null,
229: defaultDeferedSyntaxAllowedAsLiteral,
230: defaultTrimDirectiveWhitespaces);
231: initialized = true;
232: }
233: }
234:
235: /**
236: * Select the property group that has more restrictive url-pattern.
237: * In case of tie, select the first.
238: */
239: private JspPropertyGroup selectProperty(JspPropertyGroup prev,
240: JspPropertyGroup curr) {
241: if (prev == null) {
242: return curr;
243: }
244: if (prev.getExtension() == null) {
245: // exact match
246: return prev;
247: }
248: if (curr.getExtension() == null) {
249: // exact match
250: return curr;
251: }
252: String prevPath = prev.getPath();
253: String currPath = curr.getPath();
254: if (prevPath == null && currPath == null) {
255: // Both specifies a *.ext, keep the first one
256: return prev;
257: }
258: if (prevPath == null && currPath != null) {
259: return curr;
260: }
261: if (prevPath != null && currPath == null) {
262: return prev;
263: }
264: if (prevPath.length() >= currPath.length()) {
265: return prev;
266: }
267: return curr;
268: }
269:
270: /**
271: * Find a property that best matches the supplied resource.
272: * @param uri the resource supplied.
273: * @return a JspProperty indicating the best match, or some default.
274: */
275: public JspProperty findJspProperty(String uri)
276: throws JasperException {
277:
278: init();
279:
280: // JSP Configuration settings do not apply to tag files
281: if (jspProperties == null || uri.endsWith(".tag")
282: || uri.endsWith(".tagx")) {
283: return defaultJspProperty;
284: }
285:
286: String uriPath = null;
287: int index = uri.lastIndexOf('/');
288: if (index >= 0) {
289: uriPath = uri.substring(0, index + 1);
290: }
291: String uriExtension = null;
292: index = uri.lastIndexOf('.');
293: if (index >= 0) {
294: uriExtension = uri.substring(index + 1);
295: }
296:
297: Vector includePreludes = new Vector();
298: Vector includeCodas = new Vector();
299:
300: JspPropertyGroup isXmlMatch = null;
301: JspPropertyGroup elIgnoredMatch = null;
302: JspPropertyGroup scriptingInvalidMatch = null;
303: JspPropertyGroup pageEncodingMatch = null;
304: JspPropertyGroup deferedSyntaxAllowedAsLiteralMatch = null;
305: JspPropertyGroup trimDirectiveWhitespacesMatch = null;
306:
307: Iterator iter = jspProperties.iterator();
308: while (iter.hasNext()) {
309:
310: JspPropertyGroup jpg = (JspPropertyGroup) iter.next();
311: JspProperty jp = jpg.getJspProperty();
312:
313: // (arrays will be the same length)
314: String extension = jpg.getExtension();
315: String path = jpg.getPath();
316:
317: if (extension == null) {
318: // exact match pattern: /a/foo.jsp
319: if (!uri.equals(path)) {
320: // not matched;
321: continue;
322: }
323: } else {
324: // Matching patterns *.ext or /p/*
325: if (path != null && uriPath != null
326: && !uriPath.startsWith(path)) {
327: // not matched
328: continue;
329: }
330: if (!extension.equals("*")
331: && !extension.equals(uriExtension)) {
332: // not matched
333: continue;
334: }
335: }
336: // We have a match
337: // Add include-preludes and include-codas
338: if (jp.getIncludePrelude() != null) {
339: includePreludes.addAll(jp.getIncludePrelude());
340: }
341: if (jp.getIncludeCoda() != null) {
342: includeCodas.addAll(jp.getIncludeCoda());
343: }
344:
345: // If there is a previous match for the same property, remember
346: // the one that is more restrictive.
347: if (jp.isXml() != null) {
348: isXmlMatch = selectProperty(isXmlMatch, jpg);
349: }
350: if (jp.isELIgnored() != null) {
351: elIgnoredMatch = selectProperty(elIgnoredMatch, jpg);
352: }
353: if (jp.isScriptingInvalid() != null) {
354: scriptingInvalidMatch = selectProperty(
355: scriptingInvalidMatch, jpg);
356: }
357: if (jp.getPageEncoding() != null) {
358: pageEncodingMatch = selectProperty(pageEncodingMatch,
359: jpg);
360: }
361: if (jp.isDeferedSyntaxAllowedAsLiteral() != null) {
362: deferedSyntaxAllowedAsLiteralMatch = selectProperty(
363: deferedSyntaxAllowedAsLiteralMatch, jpg);
364: }
365: if (jp.isTrimDirectiveWhitespaces() != null) {
366: trimDirectiveWhitespacesMatch = selectProperty(
367: trimDirectiveWhitespacesMatch, jpg);
368: }
369: }
370:
371: String isXml = defaultIsXml;
372: String isELIgnored = defaultIsELIgnored;
373: String isScriptingInvalid = defaultIsScriptingInvalid;
374: String pageEncoding = null;
375: String isDeferedSyntaxAllowedAsLiteral = defaultDeferedSyntaxAllowedAsLiteral;
376: String isTrimDirectiveWhitespaces = defaultTrimDirectiveWhitespaces;
377:
378: if (isXmlMatch != null) {
379: isXml = isXmlMatch.getJspProperty().isXml();
380: }
381: if (elIgnoredMatch != null) {
382: isELIgnored = elIgnoredMatch.getJspProperty().isELIgnored();
383: }
384: if (scriptingInvalidMatch != null) {
385: isScriptingInvalid = scriptingInvalidMatch.getJspProperty()
386: .isScriptingInvalid();
387: }
388: if (pageEncodingMatch != null) {
389: pageEncoding = pageEncodingMatch.getJspProperty()
390: .getPageEncoding();
391: }
392: if (deferedSyntaxAllowedAsLiteralMatch != null) {
393: isDeferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteralMatch
394: .getJspProperty().isDeferedSyntaxAllowedAsLiteral();
395: }
396: if (trimDirectiveWhitespacesMatch != null) {
397: isTrimDirectiveWhitespaces = trimDirectiveWhitespacesMatch
398: .getJspProperty().isTrimDirectiveWhitespaces();
399: }
400:
401: return new JspProperty(isXml, isELIgnored, isScriptingInvalid,
402: pageEncoding, includePreludes, includeCodas,
403: isDeferedSyntaxAllowedAsLiteral,
404: isTrimDirectiveWhitespaces);
405: }
406:
407: /**
408: * To find out if an uri matches an url pattern in jsp config. If so,
409: * then the uri is a JSP page. This is used primarily for jspc.
410: */
411: public boolean isJspPage(String uri) throws JasperException {
412:
413: init();
414: if (jspProperties == null) {
415: return false;
416: }
417:
418: String uriPath = null;
419: int index = uri.lastIndexOf('/');
420: if (index >= 0) {
421: uriPath = uri.substring(0, index + 1);
422: }
423: String uriExtension = null;
424: index = uri.lastIndexOf('.');
425: if (index >= 0) {
426: uriExtension = uri.substring(index + 1);
427: }
428:
429: Iterator iter = jspProperties.iterator();
430: while (iter.hasNext()) {
431:
432: JspPropertyGroup jpg = (JspPropertyGroup) iter.next();
433: JspProperty jp = jpg.getJspProperty();
434:
435: String extension = jpg.getExtension();
436: String path = jpg.getPath();
437:
438: if (extension == null) {
439: if (uri.equals(path)) {
440: // There is an exact match
441: return true;
442: }
443: } else {
444: if ((path == null || path.equals(uriPath))
445: && (extension.equals("*") || extension
446: .equals(uriExtension))) {
447: // Matches *, *.ext, /p/*, or /p/*.ext
448: return true;
449: }
450: }
451: }
452: return false;
453: }
454:
455: static class JspPropertyGroup {
456: private String path;
457: private String extension;
458: private JspProperty jspProperty;
459:
460: JspPropertyGroup(String path, String extension,
461: JspProperty jspProperty) {
462: this .path = path;
463: this .extension = extension;
464: this .jspProperty = jspProperty;
465: }
466:
467: public String getPath() {
468: return path;
469: }
470:
471: public String getExtension() {
472: return extension;
473: }
474:
475: public JspProperty getJspProperty() {
476: return jspProperty;
477: }
478: }
479:
480: static public class JspProperty {
481:
482: private String isXml;
483: private String elIgnored;
484: private String scriptingInvalid;
485: private String pageEncoding;
486: private Vector includePrelude;
487: private Vector includeCoda;
488: private String deferedSyntaxAllowedAsLiteral;
489: private String trimDirectiveWhitespaces;
490:
491: public JspProperty(String isXml, String elIgnored,
492: String scriptingInvalid, String pageEncoding,
493: Vector includePrelude, Vector includeCoda,
494: String deferedSyntaxAllowedAsLiteral,
495: String trimDirectiveWhitespaces) {
496:
497: this .isXml = isXml;
498: this .elIgnored = elIgnored;
499: this .scriptingInvalid = scriptingInvalid;
500: this .pageEncoding = pageEncoding;
501: this .includePrelude = includePrelude;
502: this .includeCoda = includeCoda;
503: this .deferedSyntaxAllowedAsLiteral = deferedSyntaxAllowedAsLiteral;
504: this .trimDirectiveWhitespaces = trimDirectiveWhitespaces;
505: }
506:
507: public String isXml() {
508: return isXml;
509: }
510:
511: public String isELIgnored() {
512: return elIgnored;
513: }
514:
515: public String isScriptingInvalid() {
516: return scriptingInvalid;
517: }
518:
519: public String getPageEncoding() {
520: return pageEncoding;
521: }
522:
523: public Vector getIncludePrelude() {
524: return includePrelude;
525: }
526:
527: public Vector getIncludeCoda() {
528: return includeCoda;
529: }
530:
531: public String isDeferedSyntaxAllowedAsLiteral() {
532: return deferedSyntaxAllowedAsLiteral;
533: }
534:
535: public String isTrimDirectiveWhitespaces() {
536: return trimDirectiveWhitespaces;
537: }
538: }
539: }
|