View Javadoc

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