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.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Hashtable;
24 import java.util.Iterator;
25
26 import javax.el.ELException;
27 import javax.el.ExpressionFactory;
28 import javax.el.FunctionMapper;
29 import javax.servlet.jsp.tagext.FunctionInfo;
30 import javax.servlet.jsp.tagext.JspFragment;
31 import javax.servlet.jsp.tagext.PageData;
32 import javax.servlet.jsp.tagext.TagAttributeInfo;
33 import javax.servlet.jsp.tagext.TagData;
34 import javax.servlet.jsp.tagext.TagExtraInfo;
35 import javax.servlet.jsp.tagext.TagInfo;
36 import javax.servlet.jsp.tagext.TagLibraryInfo;
37 import javax.servlet.jsp.tagext.ValidationMessage;
38
39 import org.apache.el.lang.ELSupport;
40 import org.apache.jasper.Constants;
41 import org.apache.jasper.JasperException;
42 import org.apache.jasper.el.ELContextImpl;
43 import org.xml.sax.Attributes;
44
45 /**
46 * Performs validation on the page elements. Attributes are checked for
47 * mandatory presence, entry value validity, and consistency. As a side effect,
48 * some page global value (such as those from page direcitves) are stored, for
49 * later use.
50 *
51 * @author Kin-man Chung
52 * @author Jan Luehe
53 * @author Shawn Bayern
54 * @author Mark Roth
55 */
56 class Validator {
57
58 /**
59 * A visitor to validate and extract page directive info
60 */
61 static class DirectiveVisitor extends Node.Visitor {
62
63 private PageInfo pageInfo;
64
65 private ErrorDispatcher err;
66
67 private static final JspUtil.ValidAttribute[] pageDirectiveAttrs = {
68 new JspUtil.ValidAttribute("language"),
69 new JspUtil.ValidAttribute("extends"),
70 new JspUtil.ValidAttribute("import"),
71 new JspUtil.ValidAttribute("session"),
72 new JspUtil.ValidAttribute("buffer"),
73 new JspUtil.ValidAttribute("autoFlush"),
74 new JspUtil.ValidAttribute("isThreadSafe"),
75 new JspUtil.ValidAttribute("info"),
76 new JspUtil.ValidAttribute("errorPage"),
77 new JspUtil.ValidAttribute("isErrorPage"),
78 new JspUtil.ValidAttribute("contentType"),
79 new JspUtil.ValidAttribute("pageEncoding"),
80 new JspUtil.ValidAttribute("isELIgnored"),
81 new JspUtil.ValidAttribute("deferredSyntaxAllowedAsLiteral"),
82 new JspUtil.ValidAttribute("trimDirectiveWhitespaces")
83 };
84
85 private boolean pageEncodingSeen = false;
86
87 /*
88 * Constructor
89 */
90 DirectiveVisitor(Compiler compiler) throws JasperException {
91 this.pageInfo = compiler.getPageInfo();
92 this.err = compiler.getErrorDispatcher();
93 }
94
95 public void visit(Node.IncludeDirective n) throws JasperException {
96 // Since pageDirectiveSeen flag only applies to the Current page
97 // save it here and restore it after the file is included.
98 boolean pageEncodingSeenSave = pageEncodingSeen;
99 pageEncodingSeen = false;
100 visitBody(n);
101 pageEncodingSeen = pageEncodingSeenSave;
102 }
103
104 public void visit(Node.PageDirective n) throws JasperException {
105
106 JspUtil.checkAttributes("Page directive", n, pageDirectiveAttrs,
107 err);
108
109 // JSP.2.10.1
110 Attributes attrs = n.getAttributes();
111 for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
112 String attr = attrs.getQName(i);
113 String value = attrs.getValue(i);
114
115 if ("language".equals(attr)) {
116 if (pageInfo.getLanguage(false) == null) {
117 pageInfo.setLanguage(value, n, err, true);
118 } else if (!pageInfo.getLanguage(false).equals(value)) {
119 err.jspError(n, "jsp.error.page.conflict.language",
120 pageInfo.getLanguage(false), value);
121 }
122 } else if ("extends".equals(attr)) {
123 if (pageInfo.getExtends(false) == null) {
124 pageInfo.setExtends(value, n);
125 } else if (!pageInfo.getExtends(false).equals(value)) {
126 err.jspError(n, "jsp.error.page.conflict.extends",
127 pageInfo.getExtends(false), value);
128 }
129 } else if ("contentType".equals(attr)) {
130 if (pageInfo.getContentType() == null) {
131 pageInfo.setContentType(value);
132 } else if (!pageInfo.getContentType().equals(value)) {
133 err.jspError(n, "jsp.error.page.conflict.contenttype",
134 pageInfo.getContentType(), value);
135 }
136 } else if ("session".equals(attr)) {
137 if (pageInfo.getSession() == null) {
138 pageInfo.setSession(value, n, err);
139 } else if (!pageInfo.getSession().equals(value)) {
140 err.jspError(n, "jsp.error.page.conflict.session",
141 pageInfo.getSession(), value);
142 }
143 } else if ("buffer".equals(attr)) {
144 if (pageInfo.getBufferValue() == null) {
145 pageInfo.setBufferValue(value, n, err);
146 } else if (!pageInfo.getBufferValue().equals(value)) {
147 err.jspError(n, "jsp.error.page.conflict.buffer",
148 pageInfo.getBufferValue(), value);
149 }
150 } else if ("autoFlush".equals(attr)) {
151 if (pageInfo.getAutoFlush() == null) {
152 pageInfo.setAutoFlush(value, n, err);
153 } else if (!pageInfo.getAutoFlush().equals(value)) {
154 err.jspError(n, "jsp.error.page.conflict.autoflush",
155 pageInfo.getAutoFlush(), value);
156 }
157 } else if ("isThreadSafe".equals(attr)) {
158 if (pageInfo.getIsThreadSafe() == null) {
159 pageInfo.setIsThreadSafe(value, n, err);
160 } else if (!pageInfo.getIsThreadSafe().equals(value)) {
161 err.jspError(n, "jsp.error.page.conflict.isthreadsafe",
162 pageInfo.getIsThreadSafe(), value);
163 }
164 } else if ("isELIgnored".equals(attr)) {
165 if (pageInfo.getIsELIgnored() == null) {
166 pageInfo.setIsELIgnored(value, n, err, true);
167 } else if (!pageInfo.getIsELIgnored().equals(value)) {
168 err.jspError(n, "jsp.error.page.conflict.iselignored",
169 pageInfo.getIsELIgnored(), value);
170 }
171 } else if ("isErrorPage".equals(attr)) {
172 if (pageInfo.getIsErrorPage() == null) {
173 pageInfo.setIsErrorPage(value, n, err);
174 } else if (!pageInfo.getIsErrorPage().equals(value)) {
175 err.jspError(n, "jsp.error.page.conflict.iserrorpage",
176 pageInfo.getIsErrorPage(), value);
177 }
178 } else if ("errorPage".equals(attr)) {
179 if (pageInfo.getErrorPage() == null) {
180 pageInfo.setErrorPage(value);
181 } else if (!pageInfo.getErrorPage().equals(value)) {
182 err.jspError(n, "jsp.error.page.conflict.errorpage",
183 pageInfo.getErrorPage(), value);
184 }
185 } else if ("info".equals(attr)) {
186 if (pageInfo.getInfo() == null) {
187 pageInfo.setInfo(value);
188 } else if (!pageInfo.getInfo().equals(value)) {
189 err.jspError(n, "jsp.error.page.conflict.info",
190 pageInfo.getInfo(), value);
191 }
192 } else if ("pageEncoding".equals(attr)) {
193 if (pageEncodingSeen)
194 err.jspError(n, "jsp.error.page.multi.pageencoding");
195 // 'pageEncoding' can occur at most once per file
196 pageEncodingSeen = true;
197 String actual = comparePageEncodings(value, n);
198 n.getRoot().setPageEncoding(actual);
199 } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
200 if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
201 pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
202 err, true);
203 } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
204 .equals(value)) {
205 err
206 .jspError(
207 n,
208 "jsp.error.page.conflict.deferredsyntaxallowedasliteral",
209 pageInfo
210 .getDeferredSyntaxAllowedAsLiteral(),
211 value);
212 }
213 } else if ("trimDirectiveWhitespaces".equals(attr)) {
214 if (pageInfo.getTrimDirectiveWhitespaces() == null) {
215 pageInfo.setTrimDirectiveWhitespaces(value, n, err,
216 true);
217 } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
218 value)) {
219 err
220 .jspError(
221 n,
222 "jsp.error.page.conflict.trimdirectivewhitespaces",
223 pageInfo.getTrimDirectiveWhitespaces(),
224 value);
225 }
226 }
227 }
228
229 // Check for bad combinations
230 if (pageInfo.getBuffer() == 0 && !pageInfo.isAutoFlush())
231 err.jspError(n, "jsp.error.page.badCombo");
232
233 // Attributes for imports for this node have been processed by
234 // the parsers, just add them to pageInfo.
235 pageInfo.addImports(n.getImports());
236 }
237
238 public void visit(Node.TagDirective n) throws JasperException {
239 // Note: Most of the validation is done in TagFileProcessor
240 // when it created a TagInfo object from the
241 // tag file in which the directive appeared.
242
243 // This method does additional processing to collect page info
244
245 Attributes attrs = n.getAttributes();
246 for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
247 String attr = attrs.getQName(i);
248 String value = attrs.getValue(i);
249
250 if ("language".equals(attr)) {
251 if (pageInfo.getLanguage(false) == null) {
252 pageInfo.setLanguage(value, n, err, false);
253 } else if (!pageInfo.getLanguage(false).equals(value)) {
254 err.jspError(n, "jsp.error.tag.conflict.language",
255 pageInfo.getLanguage(false), value);
256 }
257 } else if ("isELIgnored".equals(attr)) {
258 if (pageInfo.getIsELIgnored() == null) {
259 pageInfo.setIsELIgnored(value, n, err, false);
260 } else if (!pageInfo.getIsELIgnored().equals(value)) {
261 err.jspError(n, "jsp.error.tag.conflict.iselignored",
262 pageInfo.getIsELIgnored(), value);
263 }
264 } else if ("pageEncoding".equals(attr)) {
265 if (pageEncodingSeen)
266 err.jspError(n, "jsp.error.tag.multi.pageencoding");
267 pageEncodingSeen = true;
268 compareTagEncodings(value, n);
269 n.getRoot().setPageEncoding(value);
270 } else if ("deferredSyntaxAllowedAsLiteral".equals(attr)) {
271 if (pageInfo.getDeferredSyntaxAllowedAsLiteral() == null) {
272 pageInfo.setDeferredSyntaxAllowedAsLiteral(value, n,
273 err, false);
274 } else if (!pageInfo.getDeferredSyntaxAllowedAsLiteral()
275 .equals(value)) {
276 err
277 .jspError(
278 n,
279 "jsp.error.tag.conflict.deferredsyntaxallowedasliteral",
280 pageInfo
281 .getDeferredSyntaxAllowedAsLiteral(),
282 value);
283 }
284 } else if ("trimDirectiveWhitespaces".equals(attr)) {
285 if (pageInfo.getTrimDirectiveWhitespaces() == null) {
286 pageInfo.setTrimDirectiveWhitespaces(value, n, err,
287 false);
288 } else if (!pageInfo.getTrimDirectiveWhitespaces().equals(
289 value)) {
290 err
291 .jspError(
292 n,
293 "jsp.error.tag.conflict.trimdirectivewhitespaces",
294 pageInfo.getTrimDirectiveWhitespaces(),
295 value);
296 }
297 }
298 }
299
300 // Attributes for imports for this node have been processed by
301 // the parsers, just add them to pageInfo.
302 pageInfo.addImports(n.getImports());
303 }
304
305 public void visit(Node.AttributeDirective n) throws JasperException {
306 // Do nothing, since this attribute directive has already been
307 // validated by TagFileProcessor when it created a TagInfo object
308 // from the tag file in which the directive appeared
309 }
310
311 public void visit(Node.VariableDirective n) throws JasperException {
312 // Do nothing, since this variable directive has already been
313 // validated by TagFileProcessor when it created a TagInfo object
314 // from the tag file in which the directive appeared
315 }
316
317 /*
318 * Compares page encodings specified in various places, and throws
319 * exception in case of page encoding mismatch.
320 *
321 * @param pageDirEnc The value of the pageEncoding attribute of the page
322 * directive @param pageDir The page directive node
323 *
324 * @throws JasperException in case of page encoding mismatch
325 */
326 private String comparePageEncodings(String pageDirEnc,
327 Node.PageDirective pageDir) throws JasperException {
328
329 Node.Root root = pageDir.getRoot();
330 String configEnc = root.getJspConfigPageEncoding();
331
332 /*
333 * Compare the 'pageEncoding' attribute of the page directive with
334 * the encoding specified in the JSP config element whose URL
335 * pattern matches this page. Treat "UTF-16", "UTF-16BE", and
336 * "UTF-16LE" as identical.
337 */
338 if (configEnc != null) {
339 if (!pageDirEnc.equals(configEnc)
340 && (!pageDirEnc.startsWith("UTF-16") || !configEnc
341 .startsWith("UTF-16"))) {
342 err.jspError(pageDir,
343 "jsp.error.config_pagedir_encoding_mismatch",
344 configEnc, pageDirEnc);
345 } else {
346 return configEnc;
347 }
348 }
349
350 /*
351 * Compare the 'pageEncoding' attribute of the page directive with
352 * the encoding specified in the XML prolog (only for XML syntax,
353 * and only if JSP document contains XML prolog with encoding
354 * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
355 * identical.
356 */
357 if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
358 String pageEnc = root.getPageEncoding();
359 if (!pageDirEnc.equals(pageEnc)
360 && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
361 .startsWith("UTF-16"))) {
362 err.jspError(pageDir,
363 "jsp.error.prolog_pagedir_encoding_mismatch",
364 pageEnc, pageDirEnc);
365 } else {
366 return pageEnc;
367 }
368 }
369
370 return pageDirEnc;
371 }
372
373 /*
374 * Compares page encodings specified in various places, and throws
375 * exception in case of page encoding mismatch.
376 *
377 * @param pageDirEnc The value of the pageEncoding attribute of the page
378 * directive @param pageDir The page directive node
379 *
380 * @throws JasperException in case of page encoding mismatch
381 */
382 private void compareTagEncodings(String pageDirEnc,
383 Node.TagDirective pageDir) throws JasperException {
384
385 Node.Root root = pageDir.getRoot();
386
387 /*
388 * Compare the 'pageEncoding' attribute of the page directive with
389 * the encoding specified in the XML prolog (only for XML syntax,
390 * and only if JSP document contains XML prolog with encoding
391 * declaration). Treat "UTF-16", "UTF-16BE", and "UTF-16LE" as
392 * identical.
393 */
394 if ((root.isXmlSyntax() && root.isEncodingSpecifiedInProlog()) || root.isBomPresent()) {
395 String pageEnc = root.getPageEncoding();
396 if (!pageDirEnc.equals(pageEnc)
397 && (!pageDirEnc.startsWith("UTF-16") || !pageEnc
398 .startsWith("UTF-16"))) {
399 err.jspError(pageDir,
400 "jsp.error.prolog_pagedir_encoding_mismatch",
401 pageEnc, pageDirEnc);
402 }
403 }
404 }
405
406 }
407
408 /**
409 * A visitor for validating nodes other than page directives
410 */
411 static class ValidateVisitor extends Node.Visitor {
412
413 private PageInfo pageInfo;
414
415 private ErrorDispatcher err;
416
417 private TagInfo tagInfo;
418
419 private ClassLoader loader;
420
421 private final StringBuffer buf = new StringBuffer(32);
422
423 private static final JspUtil.ValidAttribute[] jspRootAttrs = {
424 new JspUtil.ValidAttribute("xsi:schemaLocation"),
425 new JspUtil.ValidAttribute("version", true) };
426
427 private static final JspUtil.ValidAttribute[] includeDirectiveAttrs = { new JspUtil.ValidAttribute(
428 "file", true) };
429
430 private static final JspUtil.ValidAttribute[] taglibDirectiveAttrs = {
431 new JspUtil.ValidAttribute("uri"),
432 new JspUtil.ValidAttribute("tagdir"),
433 new JspUtil.ValidAttribute("prefix", true) };
434
435 private static final JspUtil.ValidAttribute[] includeActionAttrs = {
436 new JspUtil.ValidAttribute("page", true, true),
437 new JspUtil.ValidAttribute("flush") };
438
439 private static final JspUtil.ValidAttribute[] paramActionAttrs = {
440 new JspUtil.ValidAttribute("name", true),
441 new JspUtil.ValidAttribute("value", true, true) };
442
443 private static final JspUtil.ValidAttribute[] forwardActionAttrs = { new JspUtil.ValidAttribute(
444 "page", true, true) };
445
446 private static final JspUtil.ValidAttribute[] getPropertyAttrs = {
447 new JspUtil.ValidAttribute("name", true),
448 new JspUtil.ValidAttribute("property", true) };
449
450 private static final JspUtil.ValidAttribute[] setPropertyAttrs = {
451 new JspUtil.ValidAttribute("name", true),
452 new JspUtil.ValidAttribute("property", true),
453 new JspUtil.ValidAttribute("value", false, true),
454 new JspUtil.ValidAttribute("param") };
455
456 private static final JspUtil.ValidAttribute[] useBeanAttrs = {
457 new JspUtil.ValidAttribute("id", true),
458 new JspUtil.ValidAttribute("scope"),
459 new JspUtil.ValidAttribute("class"),
460 new JspUtil.ValidAttribute("type"),
461 new JspUtil.ValidAttribute("beanName", false, true) };
462
463 private static final JspUtil.ValidAttribute[] plugInAttrs = {
464 new JspUtil.ValidAttribute("type", true),
465 new JspUtil.ValidAttribute("code", true),
466 new JspUtil.ValidAttribute("codebase"),
467 new JspUtil.ValidAttribute("align"),
468 new JspUtil.ValidAttribute("archive"),
469 new JspUtil.ValidAttribute("height", false, true),
470 new JspUtil.ValidAttribute("hspace"),
471 new JspUtil.ValidAttribute("jreversion"),
472 new JspUtil.ValidAttribute("name"),
473 new JspUtil.ValidAttribute("vspace"),
474 new JspUtil.ValidAttribute("width", false, true),
475 new JspUtil.ValidAttribute("nspluginurl"),
476 new JspUtil.ValidAttribute("iepluginurl") };
477
478 private static final JspUtil.ValidAttribute[] attributeAttrs = {
479 new JspUtil.ValidAttribute("name", true),
480 new JspUtil.ValidAttribute("trim") };
481
482 private static final JspUtil.ValidAttribute[] invokeAttrs = {
483 new JspUtil.ValidAttribute("fragment", true),
484 new JspUtil.ValidAttribute("var"),
485 new JspUtil.ValidAttribute("varReader"),
486 new JspUtil.ValidAttribute("scope") };
487
488 private static final JspUtil.ValidAttribute[] doBodyAttrs = {
489 new JspUtil.ValidAttribute("var"),
490 new JspUtil.ValidAttribute("varReader"),
491 new JspUtil.ValidAttribute("scope") };
492
493 private static final JspUtil.ValidAttribute[] jspOutputAttrs = {
494 new JspUtil.ValidAttribute("omit-xml-declaration"),
495 new JspUtil.ValidAttribute("doctype-root-element"),
496 new JspUtil.ValidAttribute("doctype-public"),
497 new JspUtil.ValidAttribute("doctype-system") };
498
499 /*
500 * Constructor
501 */
502 ValidateVisitor(Compiler compiler) {
503 this.pageInfo = compiler.getPageInfo();
504 this.err = compiler.getErrorDispatcher();
505 this.tagInfo = compiler.getCompilationContext().getTagInfo();
506 this.loader = compiler.getCompilationContext().getClassLoader();
507 }
508
509 public void visit(Node.JspRoot n) throws JasperException {
510 JspUtil.checkAttributes("Jsp:root", n, jspRootAttrs, err);
511 String version = n.getTextAttribute("version");
512 if (!version.equals("1.2") && !version.equals("2.0") && !version.equals("2.1")) {
513 err.jspError(n, "jsp.error.jsproot.version.invalid", version);
514 }
515 visitBody(n);
516 }
517
518 public void visit(Node.IncludeDirective n) throws JasperException {
519 JspUtil.checkAttributes("Include directive", n,
520 includeDirectiveAttrs, err);
521 visitBody(n);
522 }
523
524 public void visit(Node.TaglibDirective n) throws JasperException {
525 JspUtil.checkAttributes("Taglib directive", n,
526 taglibDirectiveAttrs, err);
527 // Either 'uri' or 'tagdir' attribute must be specified
528 String uri = n.getAttributeValue("uri");
529 String tagdir = n.getAttributeValue("tagdir");
530 if (uri == null && tagdir == null) {
531 err.jspError(n, "jsp.error.taglibDirective.missing.location");
532 }
533 if (uri != null && tagdir != null) {
534 err
535 .jspError(n,
536 "jsp.error.taglibDirective.both_uri_and_tagdir");
537 }
538 }
539
540 public void visit(Node.ParamAction n) throws JasperException {
541 JspUtil.checkAttributes("Param action", n, paramActionAttrs, err);
542 // make sure the value of the 'name' attribute is not a
543 // request-time expression
544 throwErrorIfExpression(n, "name", "jsp:param");
545 n.setValue(getJspAttribute(null, "value", null, null, n
546 .getAttributeValue("value"), java.lang.String.class, n,
547 false));
548 visitBody(n);
549 }
550
551 public void visit(Node.ParamsAction n) throws JasperException {
552 // Make sure we've got at least one nested jsp:param
553 Node.Nodes subElems = n.getBody();
554 if (subElems == null) {
555 err.jspError(n, "jsp.error.params.emptyBody");
556 }
557 visitBody(n);
558 }
559
560 public void visit(Node.IncludeAction n) throws JasperException {
561 JspUtil.checkAttributes("Include action", n, includeActionAttrs,
562 err);
563 n.setPage(getJspAttribute(null, "page", null, null, n
564 .getAttributeValue("page"), java.lang.String.class, n,
565 false));
566 visitBody(n);
567 };
568
569 public void visit(Node.ForwardAction n) throws JasperException {
570 JspUtil.checkAttributes("Forward", n, forwardActionAttrs, err);
571 n.setPage(getJspAttribute(null, "page", null, null, n
572 .getAttributeValue("page"), java.lang.String.class, n,
573 false));
574 visitBody(n);
575 }
576
577 public void visit(Node.GetProperty n) throws JasperException {
578 JspUtil.checkAttributes("GetProperty", n, getPropertyAttrs, err);
579 }
580
581 public void visit(Node.SetProperty n) throws JasperException {
582 JspUtil.checkAttributes("SetProperty", n, setPropertyAttrs, err);
583 String property = n.getTextAttribute("property");
584 String param = n.getTextAttribute("param");
585 String value = n.getAttributeValue("value");
586
587 n.setValue(getJspAttribute(null, "value", null, null, value,
588 java.lang.Object.class, n, false));
589
590 boolean valueSpecified = n.getValue() != null;
591
592 if ("*".equals(property)) {
593 if (param != null || valueSpecified)
594 err.jspError(n, "jsp.error.setProperty.invalid");
595
596 } else if (param != null && valueSpecified) {
597 err.jspError(n, "jsp.error.setProperty.invalid");
598 }
599
600 visitBody(n);
601 }
602
603 public void visit(Node.UseBean n) throws JasperException {
604 JspUtil.checkAttributes("UseBean", n, useBeanAttrs, err);
605
606 String name = n.getTextAttribute("id");
607 String scope = n.getTextAttribute("scope");
608 JspUtil.checkScope(scope, n, err);
609 String className = n.getTextAttribute("class");
610 String type = n.getTextAttribute("type");
611 BeanRepository beanInfo = pageInfo.getBeanRepository();
612
613 if (className == null && type == null)
614 err.jspError(n, "jsp.error.usebean.missingType");
615
616 if (beanInfo.checkVariable(name))
617 err.jspError(n, "jsp.error.usebean.duplicate");
618
619 if ("session".equals(scope) && !pageInfo.isSession())
620 err.jspError(n, "jsp.error.usebean.noSession");
621
622 Node.JspAttribute jattr = getJspAttribute(null, "beanName", null,
623 null, n.getAttributeValue("beanName"),
624 java.lang.String.class, n, false);
625 n.setBeanName(jattr);
626 if (className != null && jattr != null)
627 err.jspError(n, "jsp.error.usebean.notBoth");
628
629 if (className == null)
630 className = type;
631
632 beanInfo.addBean(n, name, className, scope);
633
634 visitBody(n);
635 }
636
637 public void visit(Node.PlugIn n) throws JasperException {
638 JspUtil.checkAttributes("Plugin", n, plugInAttrs, err);
639
640 throwErrorIfExpression(n, "type", "jsp:plugin");
641 throwErrorIfExpression(n, "code", "jsp:plugin");
642 throwErrorIfExpression(n, "codebase", "jsp:plugin");
643 throwErrorIfExpression(n, "align", "jsp:plugin");
644 throwErrorIfExpression(n, "archive", "jsp:plugin");
645 throwErrorIfExpression(n, "hspace", "jsp:plugin");
646 throwErrorIfExpression(n, "jreversion", "jsp:plugin");
647 throwErrorIfExpression(n, "name", "jsp:plugin");
648 throwErrorIfExpression(n, "vspace", "jsp:plugin");
649 throwErrorIfExpression(n, "nspluginurl", "jsp:plugin");
650 throwErrorIfExpression(n, "iepluginurl", "jsp:plugin");
651
652 String type = n.getTextAttribute("type");
653 if (type == null)
654 err.jspError(n, "jsp.error.plugin.notype");
655 if (!type.equals("bean") && !type.equals("applet"))
656 err.jspError(n, "jsp.error.plugin.badtype");
657 if (n.getTextAttribute("code") == null)
658 err.jspError(n, "jsp.error.plugin.nocode");
659
660 Node.JspAttribute width = getJspAttribute(null, "width", null,
661 null, n.getAttributeValue("width"), java.lang.String.class,
662 n, false);
663 n.setWidth(width);
664
665 Node.JspAttribute height = getJspAttribute(null, "height", null,
666 null, n.getAttributeValue("height"),
667 java.lang.String.class, n, false);
668 n.setHeight(height);
669
670 visitBody(n);
671 }
672
673 public void visit(Node.NamedAttribute n) throws JasperException {
674 JspUtil.checkAttributes("Attribute", n, attributeAttrs, err);
675 visitBody(n);
676 }
677
678 public void visit(Node.JspBody n) throws JasperException {
679 visitBody(n);
680 }
681
682 public void visit(Node.Declaration n) throws JasperException {
683 if (pageInfo.isScriptingInvalid()) {
684 err.jspError(n.getStart(), "jsp.error.no.scriptlets");
685 }
686 }
687
688 public void visit(Node.Expression n) throws JasperException {
689 if (pageInfo.isScriptingInvalid()) {
690 err.jspError(n.getStart(), "jsp.error.no.scriptlets");
691 }
692 }
693
694 public void visit(Node.Scriptlet n) throws JasperException {
695 if (pageInfo.isScriptingInvalid()) {
696 err.jspError(n.getStart(), "jsp.error.no.scriptlets");
697 }
698 }
699
700 public void visit(Node.ELExpression n) throws JasperException {
701 // exit if we are ignoring EL all together
702 if (pageInfo.isELIgnored())
703 return;
704
705 // JSP.2.2 - '#{' not allowed in template text
706 if (n.getType() == '#') {
707 if (!pageInfo.isDeferredSyntaxAllowedAsLiteral()
708 && (tagInfo == null
709 || ((tagInfo != null) && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
710 || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"))))) {
711 err.jspError(n, "jsp.error.el.template.deferred");
712 } else {
713 return;
714 }
715 }
716
717 // build expression
718 StringBuffer expr = this.getBuffer();
719 expr.append(n.getType()).append('{').append(n.getText())
720 .append('}');
721 ELNode.Nodes el = ELParser.parse(expr.toString());
722
723 // validate/prepare expression
724 prepareExpression(el, n, expr.toString());
725
726 // store it
727 n.setEL(el);
728 }
729
730 public void visit(Node.UninterpretedTag n) throws JasperException {
731 if (n.getNamedAttributeNodes().size() != 0) {
732 err.jspError(n, "jsp.error.namedAttribute.invalidUse");
733 }
734
735 Attributes attrs = n.getAttributes();
736 if (attrs != null) {
737 int attrSize = attrs.getLength();
738 Node.JspAttribute[] jspAttrs = new Node.JspAttribute[attrSize];
739 for (int i = 0; i < attrSize; i++) {
740 jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
741 attrs.getURI(i), attrs.getLocalName(i), attrs
742 .getValue(i), java.lang.Object.class, n,
743 false);
744 }
745 n.setJspAttributes(jspAttrs);
746 }
747
748 visitBody(n);
749 }
750
751 public void visit(Node.CustomTag n) throws JasperException {
752
753 TagInfo tagInfo = n.getTagInfo();
754 if (tagInfo == null) {
755 err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
756 }
757
758 /*
759 * The bodyconet of a SimpleTag cannot be JSP.
760 */
761 if (n.implementsSimpleTag()
762 && tagInfo.getBodyContent().equalsIgnoreCase(
763 TagInfo.BODY_CONTENT_JSP)) {
764 err.jspError(n, "jsp.error.simpletag.badbodycontent", tagInfo
765 .getTagClassName());
766 }
767
768 /*
769 * If the tag handler declares in the TLD that it supports dynamic
770 * attributes, it also must implement the DynamicAttributes
771 * interface.
772 */
773 if (tagInfo.hasDynamicAttributes()
774 && !n.implementsDynamicAttributes()) {
775 err.jspError(n, "jsp.error.dynamic.attributes.not.implemented",
776 n.getQName());
777 }
778
779 /*
780 * Make sure all required attributes are present, either as
781 * attributes or named attributes (<jsp:attribute>). Also make sure
782 * that the same attribute is not specified in both attributes or
783 * named attributes.
784 */
785 TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
786 String customActionUri = n.getURI();
787 Attributes attrs = n.getAttributes();
788 int attrsSize = (attrs == null) ? 0 : attrs.getLength();
789 for (int i = 0; i < tldAttrs.length; i++) {
790 String attr = null;
791 if (attrs != null) {
792 attr = attrs.getValue(tldAttrs[i].getName());
793 if (attr == null) {
794 attr = attrs.getValue(customActionUri, tldAttrs[i]
795 .getName());
796 }
797 }
798 Node.NamedAttribute na = n.getNamedAttributeNode(tldAttrs[i]
799 .getName());
800
801 if (tldAttrs[i].isRequired() && attr == null && na == null) {
802 err.jspError(n, "jsp.error.missing_attribute", tldAttrs[i]
803 .getName(), n.getLocalName());
804 }
805 if (attr != null && na != null) {
806 err.jspError(n, "jsp.error.duplicate.name.jspattribute",
807 tldAttrs[i].getName());
808 }
809 }
810
811 Node.Nodes naNodes = n.getNamedAttributeNodes();
812 int jspAttrsSize = naNodes.size() + attrsSize;
813 Node.JspAttribute[] jspAttrs = null;
814 if (jspAttrsSize > 0) {
815 jspAttrs = new Node.JspAttribute[jspAttrsSize];
816 }
817 Hashtable<String, Object> tagDataAttrs = new Hashtable<String, Object>(attrsSize);
818
819 checkXmlAttributes(n, jspAttrs, tagDataAttrs);
820 checkNamedAttributes(n, jspAttrs, attrsSize, tagDataAttrs);
821
822 TagData tagData = new TagData(tagDataAttrs);
823
824 // JSP.C1: It is a (translation time) error for an action that
825 // has one or more variable subelements to have a TagExtraInfo
826 // class that returns a non-null object.
827 TagExtraInfo tei = tagInfo.getTagExtraInfo();
828 if (tei != null && tei.getVariableInfo(tagData) != null
829 && tei.getVariableInfo(tagData).length > 0
830 && tagInfo.getTagVariableInfos().length > 0) {
831 err.jspError("jsp.error.non_null_tei_and_var_subelems", n
832 .getQName());
833 }
834
835 n.setTagData(tagData);
836 n.setJspAttributes(jspAttrs);
837
838 visitBody(n);
839 }
840
841 public void visit(Node.JspElement n) throws JasperException {
842
843 Attributes attrs = n.getAttributes();
844 if (attrs == null) {
845 err.jspError(n, "jsp.error.jspelement.missing.name");
846 }
847 int xmlAttrLen = attrs.getLength();
848
849 Node.Nodes namedAttrs = n.getNamedAttributeNodes();
850
851 // XML-style 'name' attribute, which is mandatory, must not be
852 // included in JspAttribute array
853 int jspAttrSize = xmlAttrLen - 1 + namedAttrs.size();
854
855 Node.JspAttribute[] jspAttrs = new Node.JspAttribute[jspAttrSize];
856 int jspAttrIndex = 0;
857
858 // Process XML-style attributes
859 for (int i = 0; i < xmlAttrLen; i++) {
860 if ("name".equals(attrs.getLocalName(i))) {
861 n.setNameAttribute(getJspAttribute(null, attrs.getQName(i),
862 attrs.getURI(i), attrs.getLocalName(i), attrs
863 .getValue(i), java.lang.String.class, n,
864 false));
865 } else {
866 if (jspAttrIndex < jspAttrSize) {
867 jspAttrs[jspAttrIndex++] = getJspAttribute(null, attrs
868 .getQName(i), attrs.getURI(i), attrs
869 .getLocalName(i), attrs.getValue(i),
870 java.lang.Object.class, n, false);
871 }
872 }
873 }
874 if (n.getNameAttribute() == null) {
875 err.jspError(n, "jsp.error.jspelement.missing.name");
876 }
877
878 // Process named attributes
879 for (int i = 0; i < namedAttrs.size(); i++) {
880 Node.NamedAttribute na = (Node.NamedAttribute) namedAttrs
881 .getNode(i);
882 jspAttrs[jspAttrIndex++] = new Node.JspAttribute(na, null,
883 false);
884 }
885
886 n.setJspAttributes(jspAttrs);
887
888 visitBody(n);
889 }
890
891 public void visit(Node.JspOutput n) throws JasperException {
892 JspUtil.checkAttributes("jsp:output", n, jspOutputAttrs, err);
893
894 if (n.getBody() != null) {
895 err.jspError(n, "jsp.error.jspoutput.nonemptybody");
896 }
897
898 String omitXmlDecl = n.getAttributeValue("omit-xml-declaration");
899 String doctypeName = n.getAttributeValue("doctype-root-element");
900 String doctypePublic = n.getAttributeValue("doctype-public");
901 String doctypeSystem = n.getAttributeValue("doctype-system");
902
903 String omitXmlDeclOld = pageInfo.getOmitXmlDecl();
904 String doctypeNameOld = pageInfo.getDoctypeName();
905 String doctypePublicOld = pageInfo.getDoctypePublic();
906 String doctypeSystemOld = pageInfo.getDoctypeSystem();
907
908 if (omitXmlDecl != null && omitXmlDeclOld != null
909 && !omitXmlDecl.equals(omitXmlDeclOld)) {
910 err.jspError(n, "jsp.error.jspoutput.conflict",
911 "omit-xml-declaration", omitXmlDeclOld, omitXmlDecl);
912 }
913
914 if (doctypeName != null && doctypeNameOld != null
915 && !doctypeName.equals(doctypeNameOld)) {
916 err.jspError(n, "jsp.error.jspoutput.conflict",
917 "doctype-root-element", doctypeNameOld, doctypeName);
918 }
919
920 if (doctypePublic != null && doctypePublicOld != null
921 && !doctypePublic.equals(doctypePublicOld)) {
922 err.jspError(n, "jsp.error.jspoutput.conflict",
923 "doctype-public", doctypePublicOld, doctypePublic);
924 }
925
926 if (doctypeSystem != null && doctypeSystemOld != null
927 && !doctypeSystem.equals(doctypeSystemOld)) {
928 err.jspError(n, "jsp.error.jspoutput.conflict",
929 "doctype-system", doctypeSystemOld, doctypeSystem);
930 }
931
932 if (doctypeName == null && doctypeSystem != null
933 || doctypeName != null && doctypeSystem == null) {
934 err.jspError(n, "jsp.error.jspoutput.doctypenamesystem");
935 }
936
937 if (doctypePublic != null && doctypeSystem == null) {
938 err.jspError(n, "jsp.error.jspoutput.doctypepulicsystem");
939 }
940
941 if (omitXmlDecl != null) {
942 pageInfo.setOmitXmlDecl(omitXmlDecl);
943 }
944 if (doctypeName != null) {
945 pageInfo.setDoctypeName(doctypeName);
946 }
947 if (doctypeSystem != null) {
948 pageInfo.setDoctypeSystem(doctypeSystem);
949 }
950 if (doctypePublic != null) {
951 pageInfo.setDoctypePublic(doctypePublic);
952 }
953 }
954
955 public void visit(Node.InvokeAction n) throws JasperException {
956
957 JspUtil.checkAttributes("Invoke", n, invokeAttrs, err);
958
959 String scope = n.getTextAttribute("scope");
960 JspUtil.checkScope(scope, n, err);
961
962 String var = n.getTextAttribute("var");
963 String varReader = n.getTextAttribute("varReader");
964 if (scope != null && var == null && varReader == null) {
965 err.jspError(n, "jsp.error.missing_var_or_varReader");
966 }
967 if (var != null && varReader != null) {
968 err.jspError(n, "jsp.error.var_and_varReader");
969 }
970 }
971
972 public void visit(Node.DoBodyAction n) throws JasperException {
973
974 JspUtil.checkAttributes("DoBody", n, doBodyAttrs, err);
975
976 String scope = n.getTextAttribute("scope");
977 JspUtil.checkScope(scope, n, err);
978
979 String var = n.getTextAttribute("var");
980 String varReader = n.getTextAttribute("varReader");
981 if (scope != null && var == null && varReader == null) {
982 err.jspError(n, "jsp.error.missing_var_or_varReader");
983 }
984 if (var != null && varReader != null) {
985 err.jspError(n, "jsp.error.var_and_varReader");
986 }
987 }
988
989 /*
990 * Make sure the given custom action does not have any invalid
991 * attributes.
992 *
993 * A custom action and its declared attributes always belong to the same
994 * namespace, which is identified by the prefix name of the custom tag
995 * invocation. For example, in this invocation:
996 *
997 * <my:test a="1" b="2" c="3"/>, the action
998 *
999 * "test" and its attributes "a", "b", and "c" all belong to the
1000 * namespace identified by the prefix "my". The above invocation would
1001 * be equivalent to:
1002 *
1003 * <my:test my:a="1" my:b="2" my:c="3"/>
1004 *
1005 * An action attribute may have a prefix different from that of the
1006 * action invocation only if the underlying tag handler supports dynamic
1007 * attributes, in which case the attribute with the different prefix is
1008 * considered a dynamic attribute.
1009 */
1010 private void checkXmlAttributes(Node.CustomTag n,
1011 Node.JspAttribute[] jspAttrs, Hashtable<String, Object> tagDataAttrs)
1012 throws JasperException {
1013
1014 TagInfo tagInfo = n.getTagInfo();
1015 if (tagInfo == null) {
1016 err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
1017 }
1018 TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
1019 Attributes attrs = n.getAttributes();
1020
1021 boolean checkDeferred = !pageInfo.isDeferredSyntaxAllowedAsLiteral()
1022 && !(tagInfo.getTagLibrary().getRequiredVersion().equals("2.0")
1023 || tagInfo.getTagLibrary().getRequiredVersion().equals("1.2"));
1024
1025 for (int i = 0; attrs != null && i < attrs.getLength(); i++) {
1026 boolean found = false;
1027
1028 boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("%="))
1029 || (!n.getRoot().isXmlSyntax() && attrs.getValue(i).startsWith("<%=")));
1030 boolean elExpression = false;
1031 boolean deferred = false;
1032 boolean deferredValueIsLiteral = false;
1033
1034 ELNode.Nodes el = null;
1035 if (!runtimeExpression) {
1036 el = ELParser.parse(attrs.getValue(i));
1037 Iterator<ELNode> nodes = el.iterator();
1038 while (nodes.hasNext()) {
1039 ELNode node = nodes.next();
1040 if (node instanceof ELNode.Root) {
1041 if (((ELNode.Root) node).getType() == '$') {
1042 elExpression = true;
1043 } else if (checkDeferred && ((ELNode.Root) node).getType() == '#') {
1044 elExpression = true;
1045 deferred = true;
1046 if (pageInfo.isELIgnored()) {
1047 deferredValueIsLiteral = true;
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054 boolean expression = runtimeExpression
1055 || (elExpression && (!pageInfo.isELIgnored() || (!"true".equalsIgnoreCase(pageInfo.getIsELIgnored()) && checkDeferred && deferred)));
1056
1057 for (int j = 0; tldAttrs != null && j < tldAttrs.length; j++) {
1058 if (attrs.getLocalName(i).equals(tldAttrs[j].getName())
1059 && (attrs.getURI(i) == null
1060 || attrs.getURI(i).length() == 0 || attrs
1061 .getURI(i).equals(n.getURI()))) {
1062
1063 if (tldAttrs[j].canBeRequestTime()
1064 || tldAttrs[j].isDeferredMethod() || tldAttrs[j].isDeferredValue()) { // JSP 2.1
1065
1066 if (!expression) {
1067
1068 if (deferredValueIsLiteral && !pageInfo.isDeferredSyntaxAllowedAsLiteral()) {
1069 err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
1070 tldAttrs[j].getName());
1071 }
1072
1073 String expectedType = null;
1074 if (tldAttrs[j].isDeferredMethod()) {
1075 // The String litteral must be castable to what is declared as type
1076 // for the attribute
1077 String m = tldAttrs[j].getMethodSignature();
1078 if (m != null) {
1079 int rti = m.trim().indexOf(' ');
1080 if (rti > 0) {
1081 expectedType = m.substring(0, rti).trim();
1082 }
1083 } else {
1084 expectedType = "java.lang.Object";
1085 }
1086 }
1087 if (tldAttrs[j].isDeferredValue()) {
1088 // The String litteral must be castable to what is declared as type
1089 // for the attribute
1090 expectedType = tldAttrs[j].getExpectedTypeName();
1091 }
1092 if (expectedType != null) {
1093 Class expectedClass = String.class;
1094 try {
1095 expectedClass = JspUtil.toClass(expectedType, loader);
1096 } catch (ClassNotFoundException e) {
1097 err.jspError
1098 (n, "jsp.error.unknown_attribute_type",
1099 tldAttrs[j].getName(), expectedType);
1100 }
1101 // Check casting
1102 try {
1103 ELSupport.checkType(attrs.getValue(i), expectedClass);
1104 } catch (Exception e) {
1105 err.jspError
1106 (n, "jsp.error.coerce_to_type",
1107 tldAttrs[j].getName(), expectedType, attrs.getValue(i));
1108 }
1109 }
1110
1111 jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
1112 attrs.getQName(i), attrs.getURI(i), attrs
1113 .getLocalName(i),
1114 attrs.getValue(i), false, null, false);
1115 } else {
1116
1117 if (deferred && !tldAttrs[j].isDeferredMethod() && !tldAttrs[j].isDeferredValue()) {
1118 // No deferred expressions allowed for this attribute
1119 err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
1120 tldAttrs[j].getName());
1121 }
1122 if (!deferred && !tldAttrs[j].canBeRequestTime()) {
1123 // Only deferred expressions are allowed for this attribute
1124 err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
1125 tldAttrs[j].getName());
1126 }
1127
1128 Class expectedType = String.class;
1129 try {
1130 String typeStr = tldAttrs[j].getTypeName();
1131 if (tldAttrs[j].isFragment()) {
1132 expectedType = JspFragment.class;
1133 } else if (typeStr != null) {
1134 expectedType = JspUtil.toClass(typeStr,
1135 loader);
1136 }
1137 if (elExpression) {
1138 // El expression
1139 validateFunctions(el, n);
1140 jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
1141 attrs.getQName(i), attrs.getURI(i),
1142 attrs.getLocalName(i),
1143 attrs.getValue(i), false, el, false);
1144 ELContextImpl ctx = new ELContextImpl();
1145 ctx.setFunctionMapper(getFunctionMapper(el));
1146 try {
1147 jspAttrs[i].validateEL(this.pageInfo.getExpressionFactory(), ctx);
1148 } catch (ELException e) {
1149 this.err.jspError(n.getStart(),
1150 "jsp.error.invalid.expression",
1151 attrs.getValue(i), e.toString());
1152 }
1153 } else {
1154 // Runtime expression
1155 jspAttrs[i] = getJspAttribute(tldAttrs[j],
1156 attrs.getQName(i), attrs.getURI(i),
1157 attrs.getLocalName(i), attrs
1158 .getValue(i), expectedType, n,
1159 false);
1160 }
1161 } catch (ClassNotFoundException e) {
1162 err.jspError
1163 (n, "jsp.error.unknown_attribute_type",
1164 tldAttrs[j].getName(), tldAttrs[j].getTypeName());
1165 }
1166 }
1167
1168 } else {
1169 // Attribute does not accept any expressions.
1170 // Make sure its value does not contain any.
1171 if (expression) {
1172 err.jspError(n, "jsp.error.attribute.custom.non_rt_with_expr",
1173 tldAttrs[j].getName());
1174 }
1175 jspAttrs[i] = new Node.JspAttribute(tldAttrs[j],
1176 attrs.getQName(i), attrs.getURI(i), attrs
1177 .getLocalName(i),
1178 attrs.getValue(i), false, null, false);
1179 }
1180 if (expression) {
1181 tagDataAttrs.put(attrs.getQName(i),
1182 TagData.REQUEST_TIME_VALUE);
1183 } else {
1184 tagDataAttrs.put(attrs.getQName(i), attrs
1185 .getValue(i));
1186 }
1187 found = true;
1188 break;
1189 }
1190 }
1191 if (!found) {
1192 if (tagInfo.hasDynamicAttributes()) {
1193 jspAttrs[i] = getJspAttribute(null, attrs.getQName(i),
1194 attrs.getURI(i), attrs.getLocalName(i), attrs
1195 .getValue(i), java.lang.Object.class,
1196 n, true);
1197 } else {
1198 err.jspError(n, "jsp.error.bad_attribute", attrs
1199 .getQName(i), n.getLocalName());
1200 }
1201 }
1202 }
1203 }
1204
1205 /*
1206 * Make sure the given custom action does not have any invalid named
1207 * attributes
1208 */
1209 private void checkNamedAttributes(Node.CustomTag n,
1210 Node.JspAttribute[] jspAttrs, int start,
1211 Hashtable<String, Object> tagDataAttrs)
1212 throws JasperException {
1213
1214 TagInfo tagInfo = n.getTagInfo();
1215 if (tagInfo == null) {
1216 err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
1217 }
1218 TagAttributeInfo[] tldAttrs = tagInfo.getAttributes();
1219 Node.Nodes naNodes = n.getNamedAttributeNodes();
1220
1221 for (int i = 0; i < naNodes.size(); i++) {
1222 Node.NamedAttribute na = (Node.NamedAttribute) naNodes
1223 .getNode(i);
1224 boolean found = false;
1225 for (int j = 0; j < tldAttrs.length; j++) {
1226 /*
1227 * See above comment about namespace matches. For named
1228 * attributes, we use the prefix instead of URI as the match
1229 * criterion, because in the case of a JSP document, we'd
1230 * have to keep track of which namespaces are in scope when
1231 * parsing a named attribute, in order to determine the URI
1232 * that the prefix of the named attribute's name matches to.
1233 */
1234 String attrPrefix = na.getPrefix();
1235 if (na.getLocalName().equals(tldAttrs[j].getName())
1236 && (attrPrefix == null || attrPrefix.length() == 0 || attrPrefix
1237 .equals(n.getPrefix()))) {
1238 jspAttrs[start + i] = new Node.JspAttribute(na,
1239 tldAttrs[j], false);
1240 NamedAttributeVisitor nav = null;
1241 if (na.getBody() != null) {
1242 nav = new NamedAttributeVisitor();
1243 na.getBody().visit(nav);
1244 }
1245 if (nav != null && nav.hasDynamicContent()) {
1246 tagDataAttrs.put(na.getName(),
1247 TagData.REQUEST_TIME_VALUE);
1248 } else {
1249 tagDataAttrs.put(na.getName(), na.getText());
1250 }
1251 found = true;
1252 break;
1253 }
1254 }
1255 if (!found) {
1256 if (tagInfo.hasDynamicAttributes()) {
1257 jspAttrs[start + i] = new Node.JspAttribute(na, null,
1258 true);
1259 } else {
1260 err.jspError(n, "jsp.error.bad_attribute",
1261 na.getName(), n.getLocalName());
1262 }
1263 }
1264 }
1265 }
1266
1267 /**
1268 * Preprocess attributes that can be expressions. Expression delimiters
1269 * are stripped.
1270 * <p>
1271 * If value is null, checks if there are any NamedAttribute subelements
1272 * in the tree node, and if so, constructs a JspAttribute out of a child
1273 * NamedAttribute node.
1274 */
1275 private Node.JspAttribute getJspAttribute(TagAttributeInfo tai,
1276 String qName, String uri, String localName, String value,
1277 Class expectedType, Node n, boolean dynamic)
1278 throws JasperException {
1279
1280 Node.JspAttribute result = null;
1281
1282 // XXX Is it an error to see "%=foo%" in non-Xml page?
1283 // (We won't see "<%=foo%> in xml page because '<' is not a
1284 // valid attribute value in xml).
1285
1286 if (value != null) {
1287 if (n.getRoot().isXmlSyntax() && value.startsWith("%=")) {
1288 result = new Node.JspAttribute(tai, qName, uri, localName,
1289 value.substring(2, value.length() - 1), true, null,
1290 dynamic);
1291 } else if (!n.getRoot().isXmlSyntax()
1292 && value.startsWith("<%=")) {
1293 result = new Node.JspAttribute(tai, qName, uri, localName,
1294 value.substring(3, value.length() - 2), true, null,
1295 dynamic);
1296 } else {
1297 // The attribute can contain expressions but is not a
1298 // scriptlet expression; thus, we want to run it through
1299 // the expression interpreter
1300
1301 // validate expression syntax if string contains
1302 // expression(s)
1303 ELNode.Nodes el = ELParser.parse(value);
1304
1305 boolean deferred = false;
1306 Iterator<ELNode> nodes = el.iterator();
1307 while (nodes.hasNext()) {
1308 ELNode node = nodes.next();
1309 if (node instanceof ELNode.Root) {
1310 if (((ELNode.Root) node).getType() == '#') {
1311 deferred = true;
1312 }
1313 }
1314 }
1315
1316 if (el.containsEL() && !pageInfo.isELIgnored()
1317 && ((!pageInfo.isDeferredSyntaxAllowedAsLiteral() && deferred)
1318 || !deferred)) {
1319
1320 validateFunctions(el, n);
1321
1322 result = new Node.JspAttribute(tai, qName, uri,
1323 localName, value, false, el, dynamic);
1324
1325 ELContextImpl ctx = new ELContextImpl();
1326 ctx.setFunctionMapper(getFunctionMapper(el));
1327
1328 try {
1329 result.validateEL(this.pageInfo
1330 .getExpressionFactory(), ctx);
1331 } catch (ELException e) {
1332 this.err.jspError(n.getStart(),
1333 "jsp.error.invalid.expression", value, e
1334 .toString());
1335 }
1336
1337 } else {
1338 value = value.replace(Constants.ESC, '$');
1339 result = new Node.JspAttribute(tai, qName, uri,
1340 localName, value, false, null, dynamic);
1341 }
1342 }
1343 } else {
1344 // Value is null. Check for any NamedAttribute subnodes
1345 // that might contain the value for this attribute.
1346 // Otherwise, the attribute wasn't found so we return null.
1347
1348 Node.NamedAttribute namedAttributeNode = n
1349 .getNamedAttributeNode(qName);
1350 if (namedAttributeNode != null) {
1351 result = new Node.JspAttribute(namedAttributeNode, tai,
1352 dynamic);
1353 }
1354 }
1355
1356 return result;
1357 }
1358
1359 /*
1360 * Return an empty StringBuffer [not thread-safe]
1361 */
1362 private StringBuffer getBuffer() {
1363 this.buf.setLength(0);
1364 return this.buf;
1365 }
1366
1367 /*
1368 * Checks to see if the given attribute value represents a runtime or EL
1369 * expression.
1370 */
1371 private boolean isExpression(Node n, String value, boolean checkDeferred) {
1372
1373 boolean runtimeExpression = ((n.getRoot().isXmlSyntax() && value.startsWith("%="))
1374 || (!n.getRoot().isXmlSyntax() && value.startsWith("<%=")));
1375 boolean elExpression = false;
1376
1377 if (!runtimeExpression && !pageInfo.isELIgnored()) {
1378 Iterator<ELNode> nodes = ELParser.parse(value).iterator();
1379 while (nodes.hasNext()) {
1380 ELNode node = nodes.next();
1381 if (node instanceof ELNode.Root) {
1382 if (((ELNode.Root) node).getType() == '$') {
1383 elExpression = true;
1384 } else if (checkDeferred && !pageInfo.isDeferredSyntaxAllowedAsLiteral()
1385 && ((ELNode.Root) node).getType() == '#') {
1386 elExpression = true;
1387 }
1388 }
1389 }
1390 }
1391
1392 return runtimeExpression || elExpression;
1393
1394 }
1395
1396 /*
1397 * Throws exception if the value of the attribute with the given name in
1398 * the given node is given as an RT or EL expression, but the spec
1399 * requires a static value.
1400 */
1401 private void throwErrorIfExpression(Node n, String attrName,
1402 String actionName) throws JasperException {
1403 if (n.getAttributes() != null
1404 && n.getAttributes().getValue(attrName) != null
1405 && isExpression(n, n.getAttributes().getValue(attrName), true)) {
1406 err.jspError(n,
1407 "jsp.error.attribute.standard.non_rt_with_expr",
1408 attrName, actionName);
1409 }
1410 }
1411
1412 private static class NamedAttributeVisitor extends Node.Visitor {
1413 private boolean hasDynamicContent;
1414
1415 public void doVisit(Node n) throws JasperException {
1416 if (!(n instanceof Node.JspText)
1417 && !(n instanceof Node.TemplateText)) {
1418 hasDynamicContent = true;
1419 }
1420 visitBody(n);
1421 }
1422
1423 public boolean hasDynamicContent() {
1424 return hasDynamicContent;
1425 }
1426 }
1427
1428 private String findUri(String prefix, Node n) {
1429
1430 for (Node p = n; p != null; p = p.getParent()) {
1431 Attributes attrs = p.getTaglibAttributes();
1432 if (attrs == null) {
1433 continue;
1434 }
1435 for (int i = 0; i < attrs.getLength(); i++) {
1436 String name = attrs.getQName(i);
1437 int k = name.indexOf(':');
1438 if (prefix == null && k < 0) {
1439 // prefix not specified and a default ns found
1440 return attrs.getValue(i);
1441 }
1442 if (prefix != null && k >= 0
1443 && prefix.equals(name.substring(k + 1))) {
1444 return attrs.getValue(i);
1445 }
1446 }
1447 }
1448 return null;
1449 }
1450
1451 /**
1452 * Validate functions in EL expressions
1453 */
1454 private void validateFunctions(ELNode.Nodes el, Node n)
1455 throws JasperException {
1456
1457 class FVVisitor extends ELNode.Visitor {
1458
1459 Node n;
1460
1461 FVVisitor(Node n) {
1462 this.n = n;
1463 }
1464
1465 public void visit(ELNode.Function func) throws JasperException {
1466 String prefix = func.getPrefix();
1467 String function = func.getName();
1468 String uri = null;
1469
1470 if (n.getRoot().isXmlSyntax()) {
1471 uri = findUri(prefix, n);
1472 } else if (prefix != null) {
1473 uri = pageInfo.getURI(prefix);
1474 }
1475
1476 if (uri == null) {
1477 if (prefix == null) {
1478 err.jspError(n, "jsp.error.noFunctionPrefix",
1479 function);
1480 } else {
1481 err
1482 .jspError(
1483 n,
1484 "jsp.error.attribute.invalidPrefix",
1485 prefix);
1486 }
1487 }
1488 TagLibraryInfo taglib = pageInfo.getTaglib(uri);
1489 FunctionInfo funcInfo = null;
1490 if (taglib != null) {
1491 funcInfo = taglib.getFunction(function);
1492 }
1493 if (funcInfo == null) {
1494 err.jspError(n, "jsp.error.noFunction", function);
1495 }
1496 // Skip TLD function uniqueness check. Done by Schema ?
1497 func.setUri(uri);
1498 func.setFunctionInfo(funcInfo);
1499 processSignature(func);
1500 }
1501 }
1502
1503 el.visit(new FVVisitor(n));
1504 }
1505
1506 private void prepareExpression(ELNode.Nodes el, Node n, String expr)
1507 throws JasperException {
1508 validateFunctions(el, n);
1509
1510 // test it out
1511 ELContextImpl ctx = new ELContextImpl();
1512 ctx.setFunctionMapper(this.getFunctionMapper(el));
1513 ExpressionFactory ef = this.pageInfo.getExpressionFactory();
1514 try {
1515 ef.createValueExpression(ctx, expr, Object.class);
1516 } catch (ELException e) {
1517
1518 }
1519 }
1520
1521 private void processSignature(ELNode.Function func)
1522 throws JasperException {
1523 func.setMethodName(getMethod(func));
1524 func.setParameters(getParameters(func));
1525 }
1526
1527 /**
1528 * Get the method name from the signature.
1529 */
1530 private String getMethod(ELNode.Function func) throws JasperException {
1531 FunctionInfo funcInfo = func.getFunctionInfo();
1532 String signature = funcInfo.getFunctionSignature();
1533
1534 int start = signature.indexOf(' ');
1535 if (start < 0) {
1536 err.jspError("jsp.error.tld.fn.invalid.signature", func
1537 .getPrefix(), func.getName());
1538 }
1539 int end = signature.indexOf('(');
1540 if (end < 0) {
1541 err.jspError(
1542 "jsp.error.tld.fn.invalid.signature.parenexpected",
1543 func.getPrefix(), func.getName());
1544 }
1545 return signature.substring(start + 1, end).trim();
1546 }
1547
1548 /**
1549 * Get the parameters types from the function signature.
1550 *
1551 * @return An array of parameter class names
1552 */
1553 private String[] getParameters(ELNode.Function func)
1554 throws JasperException {
1555 FunctionInfo funcInfo = func.getFunctionInfo();
1556 String signature = funcInfo.getFunctionSignature();
1557 ArrayList<String> params = new ArrayList<String>();
1558 // Signature is of the form
1559 // <return-type> S <method-name S? '('
1560 // < <arg-type> ( ',' <arg-type> )* )? ')'
1561 int start = signature.indexOf('(') + 1;
1562 boolean lastArg = false;
1563 while (true) {
1564 int p = signature.indexOf(',', start);
1565 if (p < 0) {
1566 p = signature.indexOf(')', start);
1567 if (p < 0) {
1568 err.jspError("jsp.error.tld.fn.invalid.signature", func
1569 .getPrefix(), func.getName());
1570 }
1571 lastArg = true;
1572 }
1573 String arg = signature.substring(start, p).trim();
1574 if (!"".equals(arg)) {
1575 params.add(arg);
1576 }
1577 if (lastArg) {
1578 break;
1579 }
1580 start = p + 1;
1581 }
1582 return (String[]) params.toArray(new String[params.size()]);
1583 }
1584
1585 private FunctionMapper getFunctionMapper(ELNode.Nodes el)
1586 throws JasperException {
1587
1588 class ValidateFunctionMapper extends FunctionMapper {
1589
1590 private HashMap<String, Method> fnmap = new HashMap<String, Method>();
1591
1592 public void mapFunction(String fnQName, Method method) {
1593 fnmap.put(fnQName, method);
1594 }
1595
1596 public Method resolveFunction(String prefix, String localName) {
1597 return this.fnmap.get(prefix + ":" + localName);
1598 }
1599 }
1600
1601 class MapperELVisitor extends ELNode.Visitor {
1602 ValidateFunctionMapper fmapper;
1603
1604 MapperELVisitor(ValidateFunctionMapper fmapper) {
1605 this.fmapper = fmapper;
1606 }
1607
1608 public void visit(ELNode.Function n) throws JasperException {
1609
1610 Class c = null;
1611 Method method = null;
1612 try {
1613 c = loader.loadClass(n.getFunctionInfo()
1614 .getFunctionClass());
1615 } catch (ClassNotFoundException e) {
1616 err.jspError("jsp.error.function.classnotfound", n
1617 .getFunctionInfo().getFunctionClass(), n
1618 .getPrefix()
1619 + ':' + n.getName(), e.getMessage());
1620 }
1621 String paramTypes[] = n.getParameters();
1622 int size = paramTypes.length;
1623 Class params[] = new Class[size];
1624 int i = 0;
1625 try {
1626 for (i = 0; i < size; i++) {
1627 params[i] = JspUtil.toClass(paramTypes[i], loader);
1628 }
1629 method = c.getDeclaredMethod(n.getMethodName(), params);
1630 } catch (ClassNotFoundException e) {
1631 err.jspError("jsp.error.signature.classnotfound",
1632 paramTypes[i], n.getPrefix() + ':'
1633 + n.getName(), e.getMessage());
1634 } catch (NoSuchMethodException e) {
1635 err.jspError("jsp.error.noFunctionMethod", n
1636 .getMethodName(), n.getName(), c.getName());
1637 }
1638 fmapper.mapFunction(n.getPrefix() + ':' + n.getName(),
1639 method);
1640 }
1641 }
1642
1643 ValidateFunctionMapper fmapper = new ValidateFunctionMapper();
1644 el.visit(new MapperELVisitor(fmapper));
1645 return fmapper;
1646 }
1647 } // End of ValidateVisitor
1648
1649 /**
1650 * A visitor for validating TagExtraInfo classes of all tags
1651 */
1652 static class TagExtraInfoVisitor extends Node.Visitor {
1653
1654 private ErrorDispatcher err;
1655
1656 /*
1657 * Constructor
1658 */
1659 TagExtraInfoVisitor(Compiler compiler) {
1660 this.err = compiler.getErrorDispatcher();
1661 }
1662
1663 public void visit(Node.CustomTag n) throws JasperException {
1664 TagInfo tagInfo = n.getTagInfo();
1665 if (tagInfo == null) {
1666 err.jspError(n, "jsp.error.missing.tagInfo", n.getQName());
1667 }
1668
1669 ValidationMessage[] errors = tagInfo.validate(n.getTagData());
1670 if (errors != null && errors.length != 0) {
1671 StringBuffer errMsg = new StringBuffer();
1672 errMsg.append("<h3>");
1673 errMsg.append(Localizer.getMessage(
1674 "jsp.error.tei.invalid.attributes", n.getQName()));
1675 errMsg.append("</h3>");
1676 for (int i = 0; i < errors.length; i++) {
1677 errMsg.append("<p>");
1678 if (errors[i].getId() != null) {
1679 errMsg.append(errors[i].getId());
1680 errMsg.append(": ");
1681 }
1682 errMsg.append(errors[i].getMessage());
1683 errMsg.append("</p>");
1684 }
1685
1686 err.jspError(n, errMsg.toString());
1687 }
1688
1689 visitBody(n);
1690 }
1691 }
1692
1693 public static void validate(Compiler compiler, Node.Nodes page)
1694 throws JasperException {
1695
1696 /*
1697 * Visit the page/tag directives first, as they are global to the page
1698 * and are position independent.
1699 */
1700 page.visit(new DirectiveVisitor(compiler));
1701
1702 // Determine the default output content type
1703 PageInfo pageInfo = compiler.getPageInfo();
1704 String contentType = pageInfo.getContentType();
1705
1706 if (contentType == null || contentType.indexOf("charset=") < 0) {
1707 boolean isXml = page.getRoot().isXmlSyntax();
1708 String defaultType;
1709 if (contentType == null) {
1710 defaultType = isXml ? "text/xml" : "text/html";
1711 } else {
1712 defaultType = contentType;
1713 }
1714
1715 String charset = null;
1716 if (isXml) {
1717 charset = "UTF-8";
1718 } else {
1719 if (!page.getRoot().isDefaultPageEncoding()) {
1720 charset = page.getRoot().getPageEncoding();
1721 }
1722 }
1723
1724 if (charset != null) {
1725 pageInfo.setContentType(defaultType + ";charset=" + charset);
1726 } else {
1727 pageInfo.setContentType(defaultType);
1728 }
1729 }
1730
1731 /*
1732 * Validate all other nodes. This validation step includes checking a
1733 * custom tag's mandatory and optional attributes against information in
1734 * the TLD (first validation step for custom tags according to
1735 * JSP.10.5).
1736 */
1737 page.visit(new ValidateVisitor(compiler));
1738
1739 /*
1740 * Invoke TagLibraryValidator classes of all imported tags (second
1741 * validation step for custom tags according to JSP.10.5).
1742 */
1743 validateXmlView(new PageDataImpl(page, compiler), compiler);
1744
1745 /*
1746 * Invoke TagExtraInfo method isValid() for all imported tags (third
1747 * validation step for custom tags according to JSP.10.5).
1748 */
1749 page.visit(new TagExtraInfoVisitor(compiler));
1750
1751 }
1752
1753 // *********************************************************************
1754 // Private (utility) methods
1755
1756 /**
1757 * Validate XML view against the TagLibraryValidator classes of all imported
1758 * tag libraries.
1759 */
1760 private static void validateXmlView(PageData xmlView, Compiler compiler)
1761 throws JasperException {
1762
1763 StringBuffer errMsg = null;
1764 ErrorDispatcher errDisp = compiler.getErrorDispatcher();
1765
1766 for (Iterator iter = compiler.getPageInfo().getTaglibs().iterator(); iter
1767 .hasNext();) {
1768
1769 Object o = iter.next();
1770 if (!(o instanceof TagLibraryInfoImpl))
1771 continue;
1772 TagLibraryInfoImpl tli = (TagLibraryInfoImpl) o;
1773
1774 ValidationMessage[] errors = tli.validate(xmlView);
1775 if ((errors != null) && (errors.length != 0)) {
1776 if (errMsg == null) {
1777 errMsg = new StringBuffer();
1778 }
1779 errMsg.append("<h3>");
1780 errMsg.append(Localizer.getMessage(
1781 "jsp.error.tlv.invalid.page", tli.getShortName(),
1782 compiler.getPageInfo().getJspFile()));
1783 errMsg.append("</h3>");
1784 for (int i = 0; i < errors.length; i++) {
1785 if (errors[i] != null) {
1786 errMsg.append("<p>");
1787 errMsg.append(errors[i].getId());
1788 errMsg.append(": ");
1789 errMsg.append(errors[i].getMessage());
1790 errMsg.append("</p>");
1791 }
1792 }
1793 }
1794 }
1795
1796 if (errMsg != null) {
1797 errDisp.jspError(errMsg.toString());
1798 }
1799 }
1800 }