1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.jasper.compiler;
19
20 import org.apache.struts2.jasper.JasperException;
21 import org.apache.struts2.jasper.JspCompilationContext;
22 import org.apache.struts2.jasper.xmlparser.XMLEncodingDetector;
23 import org.xml.sax.Attributes;
24
25 import java.io.FileNotFoundException;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.net.JarURLConnection;
29 import java.net.URL;
30 import java.util.Stack;
31 import java.util.jar.JarFile;
32
33 /***
34 * Controller for the parsing of a JSP page.
35 * <p/>
36 * The same ParserController instance is used for a JSP page and any JSP
37 * segments included by it (via an include directive), where each segment may
38 * be provided in standard or XML syntax. This class selects and invokes the
39 * appropriate parser for the JSP page and its included segments.
40 *
41 * @author Pierre Delisle
42 * @author Jan Luehe
43 */
44 class ParserController implements TagConstants {
45
46 private static final String CHARSET = "charset=";
47
48 private JspCompilationContext ctxt;
49 private Compiler compiler;
50 private ErrorDispatcher err;
51
52
53
54
55 private boolean isXml;
56
57
58
59
60
61 private Stack baseDirStack = new Stack();
62
63 private boolean isEncodingSpecifiedInProlog;
64
65 private String sourceEnc;
66
67 private boolean isDefaultPageEncoding;
68 private boolean isTagFile;
69 private boolean directiveOnly;
70
71
72
73
74 public ParserController(JspCompilationContext ctxt, Compiler compiler) {
75 this.ctxt = ctxt;
76 this.compiler = compiler;
77 this.err = compiler.getErrorDispatcher();
78 }
79
80 public JspCompilationContext getJspCompilationContext() {
81 return ctxt;
82 }
83
84 public Compiler getCompiler() {
85 return compiler;
86 }
87
88 /***
89 * Parses a JSP page or tag file. This is invoked by the compiler.
90 *
91 * @param inFileName The path to the JSP page or tag file to be parsed.
92 */
93 public Node.Nodes parse(String inFileName)
94 throws FileNotFoundException, JasperException, IOException {
95
96
97
98
99 isTagFile = ctxt.isTagFile();
100 directiveOnly = false;
101 return doParse(inFileName, null, ctxt.getTagFileJarUrl());
102 }
103
104 /***
105 * Processes an include directive with the given path.
106 *
107 * @param inFileName The path to the resource to be included.
108 * @param parent The parent node of the include directive.
109 * @param jarFile The JAR file from which to read the included resource,
110 * or null of the included resource is to be read from the filesystem
111 */
112 public Node.Nodes parse(String inFileName, Node parent,
113 URL jarFileUrl)
114 throws FileNotFoundException, JasperException, IOException {
115
116
117 return doParse(inFileName, parent, jarFileUrl);
118 }
119
120 /***
121 * Extracts tag file directive information from the tag file with the
122 * given name.
123 * <p/>
124 * This is invoked by the compiler
125 *
126 * @param inFileName The name of the tag file to be parsed.
127 */
128 public Node.Nodes parseTagFileDirectives(String inFileName)
129 throws FileNotFoundException, JasperException, IOException {
130 boolean isTagFileSave = isTagFile;
131 boolean directiveOnlySave = directiveOnly;
132 isTagFile = true;
133 directiveOnly = true;
134 Node.Nodes page = doParse(inFileName, null,
135 (URL) ctxt.getTagFileJarUrls().get(inFileName));
136 directiveOnly = directiveOnlySave;
137 isTagFile = isTagFileSave;
138 return page;
139 }
140
141 /***
142 * Parses the JSP page or tag file with the given path name.
143 *
144 * @param inFileName The name of the JSP page or tag file to be parsed.
145 * @param parent The parent node (non-null when processing an include
146 * directive)
147 * @param isTagFile true if file to be parsed is tag file, and false if it
148 * is a regular JSP page
149 * @param directivesOnly true if the file to be parsed is a tag file and
150 * we are only interested in the directives needed for constructing a
151 * TagFileInfo.
152 * @param jarFile The JAR file from which to read the JSP page or tag file,
153 * or null if the JSP page or tag file is to be read from the filesystem
154 */
155 private Node.Nodes doParse(String inFileName,
156 Node parent,
157 URL jarFileUrl)
158 throws FileNotFoundException, JasperException, IOException {
159
160 Node.Nodes parsedPage = null;
161 isEncodingSpecifiedInProlog = false;
162 isDefaultPageEncoding = false;
163
164 JarFile jarFile = getJarFile(jarFileUrl);
165 String absFileName = resolveFileName(inFileName);
166 String jspConfigPageEnc = getJspConfigPageEncoding(absFileName);
167
168
169
170 determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc);
171
172 if (parent != null) {
173
174 compiler.getPageInfo().addDependant(absFileName);
175 }
176
177 if (isXml && isEncodingSpecifiedInProlog) {
178
179
180
181
182
183
184 if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc)
185 && (!jspConfigPageEnc.startsWith("UTF-16")
186 || !sourceEnc.startsWith("UTF-16"))) {
187 err.jspError("jsp.error.prolog_config_encoding_mismatch",
188 sourceEnc, jspConfigPageEnc);
189 }
190 }
191
192
193 if (isXml) {
194
195
196
197 parsedPage = JspDocumentParser.parse(this, absFileName,
198 jarFile, parent,
199 isTagFile, directiveOnly,
200 sourceEnc,
201 jspConfigPageEnc,
202 isEncodingSpecifiedInProlog);
203 } else {
204
205 InputStreamReader inStreamReader = null;
206 try {
207 inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
208 jarFile, ctxt, err);
209 JspReader jspReader = new JspReader(ctxt, absFileName,
210 sourceEnc, inStreamReader,
211 err);
212 parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
213 directiveOnly, jarFileUrl,
214 sourceEnc, jspConfigPageEnc,
215 isDefaultPageEncoding);
216 } finally {
217 if (inStreamReader != null) {
218 try {
219 inStreamReader.close();
220 } catch (Exception any) {
221 }
222 }
223 }
224 }
225
226 if (jarFile != null) {
227 try {
228 jarFile.close();
229 } catch (Throwable t) {
230 }
231 }
232
233 baseDirStack.pop();
234
235 return parsedPage;
236 }
237
238
239
240
241
242
243
244
245
246
247
248 private String getJspConfigPageEncoding(String absFileName)
249 throws JasperException {
250
251 JspConfig jspConfig = ctxt.getOptions().getJspConfig();
252 JspConfig.JspProperty jspProperty
253 = jspConfig.findJspProperty(absFileName);
254 return jspProperty.getPageEncoding();
255 }
256
257 /***
258 * Determines the syntax (standard or XML) and page encoding properties
259 * for the given file, and stores them in the 'isXml' and 'sourceEnc'
260 * instance variables, respectively.
261 */
262 private void determineSyntaxAndEncoding(String absFileName,
263 JarFile jarFile,
264 String jspConfigPageEnc)
265 throws JasperException, IOException {
266
267 isXml = false;
268
269
270
271
272
273
274 boolean isExternal = false;
275
276
277
278
279
280 boolean revert = false;
281
282 JspConfig jspConfig = ctxt.getOptions().getJspConfig();
283 JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(
284 absFileName);
285 if (jspProperty.isXml() != null) {
286
287 isXml = JspUtil.booleanValue(jspProperty.isXml());
288 isExternal = true;
289 } else if (absFileName.endsWith(".jspx")
290 || absFileName.endsWith(".tagx")) {
291 isXml = true;
292 isExternal = true;
293 }
294
295 if (isExternal && !isXml) {
296
297
298 sourceEnc = jspConfigPageEnc;
299 if (sourceEnc != null) {
300 return;
301 }
302
303 sourceEnc = "ISO-8859-1";
304 } else {
305
306 Object[] ret = XMLEncodingDetector.getEncoding(absFileName,
307 jarFile, ctxt, err);
308 sourceEnc = (String) ret[0];
309 if (((Boolean) ret[1]).booleanValue()) {
310 isEncodingSpecifiedInProlog = true;
311 }
312
313 if (!isXml && sourceEnc.equals("UTF-8")) {
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330 sourceEnc = "ISO-8859-1";
331 revert = true;
332 }
333 }
334
335 if (isXml) {
336
337
338
339 return;
340 }
341
342
343
344
345
346
347
348
349
350
351 JspReader jspReader = null;
352 try {
353 jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile,
354 err);
355 } catch (FileNotFoundException ex) {
356 throw new JasperException(ex);
357 }
358 jspReader.setSingleFile(true);
359 Mark startMark = jspReader.mark();
360 if (!isExternal) {
361 jspReader.reset(startMark);
362 if (hasJspRoot(jspReader)) {
363 isXml = true;
364 if (revert) sourceEnc = "UTF-8";
365 return;
366 } else {
367 isXml = false;
368 }
369 }
370
371
372
373
374
375
376
377 sourceEnc = jspConfigPageEnc;
378 if (sourceEnc == null) {
379 sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark);
380 if (sourceEnc == null) {
381
382 sourceEnc = "ISO-8859-1";
383 isDefaultPageEncoding = true;
384 }
385 }
386 }
387
388
389
390
391
392
393
394
395
396 private String getPageEncodingForJspSyntax(JspReader jspReader,
397 Mark startMark)
398 throws JasperException {
399
400 String encoding = null;
401 String saveEncoding = null;
402
403 jspReader.reset(startMark);
404
405
406
407
408
409 while (true) {
410 if (jspReader.skipUntil("<") == null) {
411 break;
412 }
413
414 if (jspReader.matches("%--")) {
415 if (jspReader.skipUntil("--%>") == null) {
416
417 break;
418 }
419 continue;
420 }
421 boolean isDirective = jspReader.matches("%@");
422 if (isDirective) {
423 jspReader.skipSpaces();
424 } else {
425 isDirective = jspReader.matches("jsp:directive.");
426 }
427 if (!isDirective) {
428 continue;
429 }
430
431
432 if (jspReader.matches("tag ") || jspReader.matches("page")) {
433
434 jspReader.skipSpaces();
435 Attributes attrs = Parser.parseAttributes(this, jspReader);
436 encoding = getPageEncodingFromDirective(attrs, "pageEncoding");
437 if (encoding != null) {
438 break;
439 }
440 encoding = getPageEncodingFromDirective(attrs, "contentType");
441 if (encoding != null) {
442 saveEncoding = encoding;
443 }
444 }
445 }
446
447 if (encoding == null) {
448 encoding = saveEncoding;
449 }
450
451 return encoding;
452 }
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468 private String getPageEncodingFromDirective(Attributes attrs,
469 String attrName) {
470 String value = attrs.getValue(attrName);
471 if (attrName.equals("pageEncoding")) {
472 return value;
473 }
474
475
476 String contentType = value;
477 String encoding = null;
478 if (contentType != null) {
479 int loc = contentType.indexOf(CHARSET);
480 if (loc != -1) {
481 encoding = contentType.substring(loc + CHARSET.length());
482 }
483 }
484
485 return encoding;
486 }
487
488
489
490
491
492
493
494 private String resolveFileName(String inFileName) {
495 String fileName = inFileName.replace('//', '/');
496 String baseDir =
497 fileName.substring(0, fileName.lastIndexOf("/") + 1);
498 baseDirStack.push(baseDir);
499 return fileName;
500 }
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515 private boolean hasJspRoot(JspReader reader) throws JasperException {
516
517
518 Mark start = null;
519 while ((start = reader.skipUntil("<")) != null) {
520 int c = reader.nextChar();
521 if (c != '!' && c != '?') break;
522 }
523 if (start == null) {
524 return false;
525 }
526 Mark stop = reader.skipUntil(":root");
527 if (stop == null) {
528 return false;
529 }
530
531 String prefix = reader.getText(start, stop).substring(1);
532
533 start = stop;
534 stop = reader.skipUntil(">");
535 if (stop == null) {
536 return false;
537 }
538
539
540 String root = reader.getText(start, stop);
541 String xmlnsDecl = "xmlns:" + prefix;
542 int index = root.indexOf(xmlnsDecl);
543 if (index == -1) {
544 return false;
545 }
546 index += xmlnsDecl.length();
547 while (index < root.length()
548 && Character.isWhitespace(root.charAt(index))) {
549 index++;
550 }
551 if (index < root.length() && root.charAt(index) == '=') {
552 index++;
553 while (index < root.length()
554 && Character.isWhitespace(root.charAt(index))) {
555 index++;
556 }
557 if (index < root.length() && root.charAt(index++) == '"'
558 && root.regionMatches(index, JSP_URI, 0,
559 JSP_URI.length())) {
560 return true;
561 }
562 }
563
564 return false;
565 }
566
567 private JarFile getJarFile(URL jarFileUrl) throws IOException {
568 JarFile jarFile = null;
569
570 if (jarFileUrl != null) {
571 JarURLConnection conn = (JarURLConnection) jarFileUrl.openConnection();
572 conn.setUseCaches(false);
573 conn.connect();
574 jarFile = conn.getJarFile();
575 }
576
577 return jarFile;
578 }
579
580 }