com.caucho.jsp.JspParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resin Show documentation
Show all versions of resin Show documentation
Resin Java Application Server
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.jsp;
import com.caucho.java.LineMap;
import com.caucho.jsp.java.JspNode;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;
import com.caucho.util.LineCompileException;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.xml.QName;
import com.caucho.xml.XmlChar;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Parses the JSP page. Both the XML and JSP tags are understood. However,
* escaping is always done using JSP rules.
*/
public class JspParser {
static L10N L = new L10N(JspParser.class);
private static final Logger log
= Logger.getLogger(JspParser.class.getName());
public static final String JSP_NS = "http://java.sun.com/JSP/Page";
public static final String JSTL_CORE_URI = "http://java.sun.com/jsp/jstl/core";
public static final String JSTL_FMT_URI = "http://java.sun.com/jsp/jstl/fmt";
public static final QName PREFIX = new QName("prefix");
public static final QName TAGLIB = new QName("taglib");
public static final QName TAGDIR = new QName("tagdir");
public static final QName URI = new QName("uri");
public static final QName JSP_DECLARATION =
new QName("jsp", "declaration", JSP_NS);
public static final QName JSP_SCRIPTLET =
new QName("jsp", "scriptlet", JSP_NS);
public static final QName JSP_EXPRESSION =
new QName("jsp", "expression", JSP_NS);
public static final QName JSP_DIRECTIVE_PAGE =
new QName("jsp", "directive.page", JSP_NS);
public static final QName JSP_DIRECTIVE_INCLUDE =
new QName("jsp", "directive.include", JSP_NS);
public static final QName JSP_DIRECTIVE_CACHE =
new QName("jsp", "directive.cache", JSP_NS);
public static final QName JSP_DIRECTIVE_TAGLIB =
new QName("jsp", "directive.taglib", JSP_NS);
public static final QName JSP_DIRECTIVE_ATTRIBUTE =
new QName("jsp", "directive.attribute", JSP_NS);
public static final QName JSP_DIRECTIVE_VARIABLE =
new QName("jsp", "directive.variable", JSP_NS);
public static final QName JSP_DIRECTIVE_TAG =
new QName("jsp", "directive.tag", JSP_NS);
public static final QName JSTL_CORE_OUT =
new QName("resin-c", "out", "urn:jsptld:" + JSTL_CORE_URI);
public static final QName JSTL_CORE_CHOOSE =
new QName("resin-c", "choose", "urn:jsptld:" + JSTL_CORE_URI);
public static final QName JSTL_CORE_WHEN =
new QName("resin-c", "when", "urn:jsptld:" + JSTL_CORE_URI);
public static final QName JSTL_CORE_OTHERWISE =
new QName("resin-c", "otherwise", "urn:jsptld:" + JSTL_CORE_URI);
public static final QName JSTL_CORE_FOREACH =
new QName("resin-c", "forEach", "urn:jsptld:" + JSTL_CORE_URI);
private static final int TAG_UNKNOWN = 0;
private static final int TAG_JSP = 1;
private static final int TAG_RAW = 2;
private ParseState _parseState;
private JspBuilder _jspBuilder;
private ParseTagManager _tagManager;
private LineMap _lineMap;
private ArrayList _preludeList = new ArrayList();
private ArrayList _codaList = new ArrayList();
private ArrayList _includes = new ArrayList();
private Set _prefixes = new HashSet();
// jsp/18cy, jsp/18cz
private Set _localPrefixes = new HashSet();
private Path _jspPath;
private ReadStream _stream;
private String _uriPwd;
private String _contextPath = "";
private String _filename = "";
private int _line;
private int _lineStart;
private int _charCount;
private int _startText;
private int _peek = -1;
private boolean _seenCr = false;
private Namespace _namespaces
= new Namespace(null, "jsp", JSP_NS);
private boolean _isXml;
private boolean _isTop = true;
private CharBuffer _tag = new CharBuffer();
private CharBuffer _value = new CharBuffer();
private CharBuffer _text = new CharBuffer();
/**
* Sets the JSP builder, which receives the SAX-like events from
* JSP parser.
*/
void setJspBuilder(JspBuilder builder)
{
_jspBuilder = builder;
}
/**
* Sets the context path for error messages.
*/
void setContextPath(String contextPath)
{
_contextPath = contextPath;
}
/**
* Sets the parse state, which stores state information for the parsing.
*/
void setParseState(ParseState parseState)
{
_parseState = parseState;
}
/**
* Sets the parse state, which stores state information for the parsing.
*/
ParseState getParseState()
{
return _parseState;
}
/**
* Sets the tag manager
*/
void setTagManager(ParseTagManager manager)
{
_tagManager = manager;
}
/**
* Returns true if JSP EL expressions are enabled.
*/
private boolean isELIgnored()
{
return _parseState.isELIgnored();
}
/**
* Returns true if Velocity-style statements are enabled.
*/
private boolean isVelocity()
{
return _parseState.isVelocityEnabled();
}
/**
* Returns true if JSP EL expressions are enabled.
*/
private boolean isDeferredSyntaxAllowedAsLiteral()
{
return _parseState.isDeferredSyntaxAllowedAsLiteral();
}
/**
* Adds a prelude.
*/
public void addPrelude(String prelude)
{
_preludeList.add(prelude);
}
/**
* Adds a coda.
*/
public void addCoda(String coda)
{
_codaList.add(coda);
}
/**
* Starts parsing the JSP page.
*
* @param path the JSP source file
* @param uri the URI for the JSP source file.
*/
void parse(Path path, String uri)
throws Exception
{
_parseState.pushNamespace("jsp", JSP_NS);
_isXml = _parseState.isXml();
_filename = _contextPath + uri;
if (uri != null) {
int p = uri.lastIndexOf('/');
_uriPwd = p <= 0 ? "/" : uri.substring(0, p + 1);
}
else {
_uriPwd = "/";
}
_parseState.setUriPwd(_uriPwd);
ReadStream is = path.openRead();
path.setUserPath(uri);
try {
parseJsp(is);
} finally {
is.close();
for (int i = 0; i < _includes.size(); i++) {
Include inc = _includes.get(i);
inc._stream.close();
}
}
}
/**
* Starts parsing the JSP page as a tag.
*
* @param path the JSP source file
* @param uri the URI for the JSP source file.
*/
void parseTag(Path path, String uri)
throws Exception
{
_parseState.setTag(true);
parse(path, uri);
}
/**
* Top-level JSP parser.
*
* @param stream the read stream containing the JSP file
*
* @return an XML DOM containing the JSP.
*/
private void parseJsp(ReadStream stream)
throws Exception
{
_text.clear();
_includes.clear();
String uriPwd = _uriPwd;
for (int i = _codaList.size() - 1; i >= 0; i--)
pushInclude(_codaList.get(i), true);
addInclude(stream, uriPwd);
for (int i = _preludeList.size() - 1; i >= 0; i--)
pushInclude(_preludeList.get(i), true);
setLocation();
_jspBuilder.startDocument();
String pageEncoding = _parseState.getPageEncoding();
int ch;
if (pageEncoding != null) {
_parseState.setPageEncoding(pageEncoding);
stream.setEncoding(pageEncoding);
}
switch ((ch = stream.read())) {
case 0xfe:
if ((ch = stream.read()) != 0xff) {
throw error(L.l("Expected 0xff in UTF-16 header. UTF-16 pages with the initial byte 0xfe expect 0xff immediately following. The 0xfe 0xff sequence is used by some application to suggest UTF-16 encoding without a directive."));
}
else {
//_parseState.setContentType("text/html; charset=UTF-16BE");
log.finer(L.l("JSP '{0}': setting page encoding using BOM 'fe ff' -> 'UTF-16BE'", _jspPath.toString()));
_parseState.setBom(0xfeff);
_parseState.setPageEncoding("UTF-16BE");
stream.setEncoding("UTF-16BE");
}
break;
case 0xff:
if ((ch = stream.read()) != 0xfe) {
throw error(L.l("Expected 0xfe in UTF-16 header. UTF-16 pages with the initial byte 0xff expect 0xfe immediately following. The 0xff 0xfe sequence is used by some application to suggest UTF-16 encoding without a directive."));
}
else {
//_parseState.setContentType("text/html; charset=UTF-16LE");
log.finer(L.l("JSP '{0}': setting page encoding using BOM 'ff fe' -> 'UTF-16LE'", _jspPath.toString()));
_parseState.setBom(0xfffe);
_parseState.setPageEncoding("UTF-16LE");
stream.setEncoding("UTF-16LE");
}
break;
case 0xef:
if ((ch = stream.read()) != 0xbb) {
stream.unread();
stream.unread();
}
else if ((ch = stream.read()) != 0xbf) {
throw error(L.l("Expected 0xbf in UTF-8 header. UTF-8 pages with the initial byte 0xbb expect 0xbf immediately following. The 0xbb 0xbf sequence is used by some application to suggest UTF-8 encoding without a directive."));
}
else {
// jsp/002a, #3062
// _parseState.setContentType("text/html; charset=UTF-8");
log.finer(L.l("JSP '{0}': setting page encoding using BOM 'ef bb bf' -> 'UTF-8'", _jspPath.toString()));
_parseState.setBom(0xefbbbf);
_parseState.setPageEncoding("UTF-8");
stream.setEncoding("UTF-8");
}
break;
case -1:
break;
default:
stream.unread();
break;
}
ch = read();
ch = parseXmlDeclaration(ch);
try {
parseNode(ch);
} finally {
for (int i = 0; i < _includes.size(); i++) {
Include inc = _includes.get(i);
inc._stream.close();
}
}
setLocation();
_jspBuilder.endDocument();
}
private int parseXmlDeclaration(int ch)
throws IOException, JspParseException
{
if (ch != '<')
return ch;
else if ((ch = read()) != '?') {
unread(ch);
return '<';
}
else if ((ch = read()) != 'x') {
addText("");
return ch;
}
else if ((ch = read()) != 'm') {
addText("') {
addText('?');
return ch;
}
else {
addText("?>");
if (encoding != null) {
_stream.setEncoding(encoding);
_parseState.setPageEncoding(encoding);
}
return read();
}
}
private void parseNode(int ch)
throws IOException, JspParseException
{
while (ch != -1) {
switch (ch) {
case '<':
{
switch ((ch = read())) {
case '%':
if (_isXml)
throw error(L.l("'<%' syntax is not allowed in JSP/XML syntax."));
parseScriptlet();
_startText = _charCount;
// escape '\\' after scriptlet at end of line
if ((ch = read()) == '\\') {
if ((ch = read()) == '\n') {
ch = read();
}
else if (ch == '\r') {
if ((ch = read()) == '\n')
ch = read();
}
else
addText('\\');
}
break;
case '/':
ch = parseCloseTag();
break;
case '\\':
if ((ch = read()) == '%') {
addText("<%");
ch = read();
}
else
addText("<\\");
break;
case '!':
if (! _isXml)
addText(" and are legal. You can use '&!' to escape '' expected at end of file. For XML, the top-level tag must have a matching closing tag.",
activeNode.getNodeName()));
*/
}
/**
* JSTL-style expressions. Currently understood:
*
*
* ${a * b} - any arbitrary expression
*
*/
private int parseJspExpression()
throws IOException, JspParseException
{
addText();
Path jspPath = _jspPath;
String filename = _filename;
int line = _line;
CharBuffer cb = CharBuffer.allocate();
int ch;
cb.append("${");
for (ch = read(); ch >= 0 && ch != '}'; ch = read())
cb.append((char) ch);
cb.append("}");
ch = read();
String prefix = _parseState.findPrefix(JSTL_CORE_URI);
if (prefix == null) {
prefix = "resin-c";
/*
_jspBuilder.startElement(JSP_DIRECTIVE_TAGLIB);
_jspBuilder.attribute(new QName("prefix"), prefix);
_jspBuilder.attribute(new QName("uri"), JSTL_CORE_URI);
_jspBuilder.endAttributes();
_jspBuilder.endElement(JSP_DIRECTIVE_TAGLIB.getName());
*/
_jspBuilder.addNamespace(prefix, JSTL_CORE_URI);
processTaglib(prefix, JSTL_CORE_URI);
}
setLocation(jspPath, filename, line);
_jspBuilder.startElement(JSTL_CORE_OUT);
_jspBuilder.attribute(new QName("value"), cb.close());
_jspBuilder.attribute(new QName("escapeXml"), "false");
_jspBuilder.endAttributes();
_jspBuilder.endElement(JSTL_CORE_OUT.getName());
return ch;
}
private int parseVelocity(int ch)
throws IOException, JspParseException
{
if (ch == '{') {
return parseVelocityScriptlet();
}
else if ('a' <= ch && ch <= 'z') {
ch = readName(ch);
String name = _tag.toString();
if (name.equals("if")) {
ch = parseVelocityIf("if");
}
else if (name.equals("elseif")) {
addText();
setLocation();
JspNode node = _jspBuilder.getCurrentNode();
if (! "resin-c:when".equals(node.getTagName()))
throw error(L.l("#elseif is missing a corresponding #if. Velocity-style #if syntax needs matching #if ... #elseif ... #else ... #end. The #if statements must also nest properly with any tags."));
_jspBuilder.endElement("resin-c:when");
ch = parseVelocityIf("elseif");
}
else if (name.equals("else")) {
addText();
setLocation();
_jspBuilder.endElement("resin-c:when");
setLocation(_jspPath, _filename, _lineStart);
_lineStart = _line;
_jspBuilder.startElement(JSTL_CORE_OTHERWISE);
_jspBuilder.endAttributes();
ch = skipWhitespaceToEndOfLine(ch);
}
else if (name.equals("foreach")) {
ch = parseVelocityForeach("resin-c:forEach");
}
else if (name.equals("end")) {
addText();
JspNode node = _jspBuilder.getCurrentNode();
String nodeName = null;
if (node != null)
nodeName = node.getTagName();
if (nodeName.equals("resin-c:when") ||
nodeName.equals("resin-c:otherwise")) {
_jspBuilder.endElement(nodeName);
_jspBuilder.endElement(JSTL_CORE_CHOOSE.getName());
}
else if (nodeName.equals("resin-c:forEach"))
_jspBuilder.endElement(nodeName);
else {
throw error(L.l("#end is missing a corresponding #if or #foreach. Velocity-style #if syntax needs matching #if ... #elseif ... #else ... #end. The #if statements must also nest properly with any tags."));
}
ch = skipWhitespaceToEndOfLine(ch);
}
else {
addText('#');
addText(name);
}
}
else
addText('#');
return ch;
}
/**
* This syntax isn't part of velocity.
*
*
* #{ int foo = 3; }#
*
*/
private int parseVelocityScriptlet()
throws IOException, JspParseException
{
addText();
setLocation(_jspPath, _filename, _line);
_lineStart = _line;
_jspBuilder.startElement(JSP_SCRIPTLET);
_jspBuilder.endAttributes();
int ch = read();
while (ch >= 0) {
if (ch == '}') {
ch = read();
if (ch == '#')
break;
else
addText('}');
}
else {
addText((char) ch);
ch = read();
}
}
createText();
_jspBuilder.endElement(JSP_SCRIPTLET.getName());
ch = read();
if (ch == '\r') {
ch = read();
if (ch == '\n')
return read();
else
return ch;
}
else if (ch == '\n')
return read();
else
return ch;
}
/**
* parses a #foreach statement
*
*
* #foreach ([Type] var in expr)
* ...
* #end
*
*
*
* #foreach ([Type] var in [min .. max])
* ...
* #end
*
*/
private int parseVelocityForeach(String eltName)
throws IOException, JspParseException
{
int ch;
for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
}
if (ch != '(')
throw error(L.l("Expected `(' after #foreach at `{0}'. The velocity-style #foreach syntax needs parentheses: #foreach ($a in expr)",
badChar(ch)));
addText();
processTaglib("resin-c", JSTL_CORE_URI);
setLocation(_jspPath, _filename, _lineStart);
_lineStart = _line;
_jspBuilder.startElement(JSTL_CORE_FOREACH);
CharBuffer cb = CharBuffer.allocate();
parseVelocityName(cb);
if (cb.length() == 0) {
throw error(L.l("Expected iteration variable for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
badChar(ch)));
}
String name = cb.toString();
cb.clear();
parseVelocityName(cb);
if (cb.length() == 0) {
throw error(L.l("Expected 'in' for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
badChar(ch)));
}
String value = cb.toString();
if (! value.equals("in")) {
throw error(L.l("Expected 'in' for #foreach at `{0}'. The velocity-style #foreach syntax is: #foreach ($a in expr)",
badChar(ch)));
}
if (name.startsWith("$"))
name = name.substring(1);
_jspBuilder.attribute(new QName("var"), name);
cb.clear();
parseVelocityExpr(cb, ')');
String expr = cb.close();
if (expr.indexOf("..") > 0) {
int h = 0;
for (; Character.isWhitespace(expr.charAt(h)); h++) {
}
if (expr.charAt(h) != '[')
throw error(L.l("Expected '[' for #foreach `{0}'. The velocity-style #foreach syntax is: #foreach ([Type] $a in [min .. max])",
badChar(expr.charAt(h))));
int t = expr.length() - 1;
for (; Character.isWhitespace(expr.charAt(t)); t--) {
}
if (expr.charAt(t) != ']')
throw error(L.l("Expected ']' for #foreach `{0}'. The velocity-style #foreach syntax is: #foreach ($a in [min .. max])",
badChar(expr.charAt(t))));
int p = expr.indexOf("..");
String min = expr.substring(h + 1, p);
String max = expr.substring(p + 2, t);
_jspBuilder.attribute(new QName("begin"), "${" + min + "}");
_jspBuilder.attribute(new QName("end"), "${" + max + "}");
}
else {
_jspBuilder.attribute(new QName("items"), "${" + expr + "}");
}
_jspBuilder.endAttributes();
return skipWhitespaceToEndOfLine(read());
}
/**
* parses an #if statement
*/
private int parseVelocityIf(String eltName)
throws IOException, JspParseException
{
int ch;
for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
}
if (ch != '(')
throw error(L.l("Expected `(' after #if at `{0}'. The velocity-style #if syntax needs parentheses: #if (...)",
badChar(ch)));
addText();
processTaglib("resin-c", JSTL_CORE_URI);
setLocation(_jspPath, _filename, _line);
if (eltName.equals("if")) {
_jspBuilder.startElement(JSTL_CORE_CHOOSE);
_jspBuilder.endAttributes();
}
_jspBuilder.startElement(JSTL_CORE_WHEN);
_lineStart = _line;
CharBuffer cb = CharBuffer.allocate();
parseVelocityExpr(cb, ')');
_jspBuilder.attribute(new QName("test"), "${" + cb.close() + "}");
_jspBuilder.endAttributes();
return skipWhitespaceToEndOfLine(read());
}
private int parseVelocityName(CharBuffer cb)
throws IOException, JspParseException
{
int ch;
for (ch = read(); XmlChar.isWhitespace(ch); ch = read()) {
}
for (; Character.isJavaIdentifierPart((char) ch); ch = read())
cb.append((char) ch);
return ch;
}
private int parseVelocityMin(CharBuffer cb)
throws IOException, JspParseException
{
int ch;
for (ch = read(); ch >= 0; ch = read()) {
if (ch != '$')
cb.append((char) ch);
if (ch == '(') {
ch = parseVelocityExpr(cb, ')');
cb.append((char) ch);
}
else if (ch == '[') {
ch = parseVelocityExpr(cb, ']');
cb.append((char) ch);
}
else if (ch == '.') {
ch = read();
if (ch == '.')
return ch;
else {
cb.append('.');
_peek = ch;
}
}
}
return ch;
}
private int parseVelocityExpr(CharBuffer cb, int end)
throws IOException, JspParseException
{
int ch;
for (ch = read(); ch >= 0 && ch != end; ch = read()) {
if (ch != '$')
cb.append((char) ch);
if (ch == '(') {
ch = parseVelocityExpr(cb, ')');
cb.append((char) ch);
}
else if (ch == '[') {
ch = parseVelocityExpr(cb, ']');
cb.append((char) ch);
}
}
return ch;
}
/**
* Parses a <![CDATA[ block. All text in the CDATA is uninterpreted.
*/
private void parseCdata()
throws IOException, JspParseException
{
int ch;
ch = readName(read());
String name = _tag.toString();
if (! name.equals("CDATA"))
throw error(L.l("Expected .",
String.valueOf(ch)));
String filename = _filename;
int line = _line;
while ((ch = read()) >= 0) {
while (ch == ']') {
if ((ch = read()) != ']')
addText(']');
else if ((ch = read()) != '>')
addText("]]");
else
return;
}
addText((char) ch);
}
throw error(L.l("Expected closing ]]> at end of file to match = 0) {
switch (ch) {
case '\\':
addText('\\');
ch = read();
if (ch >= 0)
addText((char) ch);
ch = read();
break;
case '%':
ch = read();
if (ch == '>') {
createText();
setLocation();
_jspBuilder.endElement(eltName.getName());
return;
}
else if (ch == '\\') {
ch = read();
if (ch == '>') {
addText("%");
}
else
addText("%\\");
}
else
addText('%');
break;
default:
addText((char) ch);
ch = read();
break;
}
}
createText();
setLocation();
_jspBuilder.endElement(eltName.getName());
}
/**
* Parses the JSP directive syntax.
*/
private void parseDirective()
throws IOException, JspParseException
{
String language = null;
int ch = skipWhitespace(read());
String directive = "";
if (XmlChar.isNameStart(ch)) {
ch = readName(ch);
directive = _tag.toString();
}
else
throw error(L.l("Expected jsp directive name at '{0}'. JSP directive syntax is <%@ name attr1='value1' ... %>",
badChar(ch)));
QName qname;
if (directive.equals("page"))
qname = JSP_DIRECTIVE_PAGE;
else if (directive.equals("include"))
qname = JSP_DIRECTIVE_INCLUDE;
else if (directive.equals("taglib"))
qname = JSP_DIRECTIVE_TAGLIB;
else if (directive.equals("cache"))
qname = JSP_DIRECTIVE_CACHE;
else if (directive.equals("attribute"))
qname = JSP_DIRECTIVE_ATTRIBUTE;
else if (directive.equals("variable"))
qname = JSP_DIRECTIVE_VARIABLE;
else if (directive.equals("tag"))
qname = JSP_DIRECTIVE_TAG;
else
throw error(L.l("'{0}' is an unknown jsp directive. Only <%@ page ... %>, <%@ include ... %>, <%@ taglib ... %>, and <%@ cache ... %> are known.", directive));
unread(ch);
ArrayList keys = new ArrayList();
ArrayList values = new ArrayList();
ArrayList prefixes = new ArrayList();
ArrayList uris = new ArrayList();
parseAttributes(keys, values, prefixes, uris);
ch = skipWhitespace(read());
if (ch != '%' || (ch = read()) != '>') {
throw error(L.l("expected '%>' at {0}. JSP directive syntax is '<%@ name attr1='value1' ... %>'. (Started at line {1})",
badChar(ch), _lineStart));
}
setLocation(_jspPath, _filename, _lineStart);
_lineStart = _line;
_jspBuilder.startElement(qname);
for (int i = 0; i < keys.size(); i++) {
_jspBuilder.attribute(keys.get(i), values.get(i));
}
_jspBuilder.endAttributes();
if (qname.equals(JSP_DIRECTIVE_TAGLIB))
processTaglibDirective(keys, values);
setLocation();
_jspBuilder.endElement(qname.getName());
if (qname.equals(JSP_DIRECTIVE_PAGE)
|| qname.equals(JSP_DIRECTIVE_TAG)) {
String contentEncoding = _parseState.getPageEncoding();
if (contentEncoding == null)
contentEncoding = _parseState.getCharEncoding();
if (contentEncoding != null) {
try {
_stream.setEncoding(contentEncoding);
} catch (Exception e) {
log.log(Level.FINER, e.toString(), e);
throw error(L.l("unknown content encoding '{0}'", contentEncoding),
e);
}
}
}
/*
if (directive.equals("include"))
parseIncludeDirective(elt);
else if (directive.equals("taglib"))
parseTaglibDirective(elt);
*/
}
/**
* Parses an XML comment.
*/
private void parseComment()
throws IOException, JspParseException
{
int ch = read();
while (ch >= 0) {
if (ch == '-') {
ch = read();
while (ch == '-') {
if ((ch = read()) == '-')
continue;
else if (ch == '%' && (ch = read()) == '>')
return;
else if (ch == '-')
ch = read();
}
}
else
ch = read();
}
}
private void parseXmlComment()
throws IOException, JspParseException
{
int ch;
while ((ch = read()) >= 0) {
while (ch == '-') {
if ((ch = read()) == '-' && (ch = read()) == '>')
return;
}
}
}
/**
* Parses the open tag.
*/
private void parseOpenTag(String name, int ch, boolean isXml)
throws IOException, JspParseException
{
addText();
ch = skipWhitespace(ch);
ArrayList keys = new ArrayList();
ArrayList values = new ArrayList();
ArrayList prefixes = new ArrayList();
ArrayList uris = new ArrayList();
unread(ch);
parseAttributes(keys, values, prefixes, uris);
QName qname = getElementQName(name);
setLocation(_jspPath, _filename, _lineStart);
_lineStart = _line;
_jspBuilder.startElement(qname);
for (int i = 0; i < keys.size(); i++) {
QName key = keys.get(i);
String value = values.get(i);
_jspBuilder.attribute(key, value);
}
_jspBuilder.endAttributes();
for (int i = 0; i < prefixes.size(); i++) {
String prefix = prefixes.get(i);
String uri = uris.get(i);
_jspBuilder.addNamespace(prefix, uri);
}
if (qname.equals(JSP_DIRECTIVE_TAGLIB))
processTaglibDirective(keys, values);
ch = skipWhitespace(read());
JspNode node = _jspBuilder.getCurrentNode();
if (ch == '/') {
if ((ch = read()) != '>')
throw error(L.l("expected '/>' at '{0}' (for tag '<{1}>' at line {2}). The XML empty tag syntax is: ",
badChar(ch), name, String.valueOf(_lineStart)));
setLocation();
_jspBuilder.endElement(qname.getName());
}
else if (ch != '>')
throw error(L.l("expected '>' at '{0}' (for tag '<{1}>' at line {2}). The XML tag syntax is: ",
badChar(ch), name, String.valueOf(_lineStart)));
// If tagdependent and not XML mode, then read the raw text.
else if ("tagdependent".equals(node.getBodyContent()) && ! _isXml) {
String tail = "" + name + ">";
for (ch = read(); ch >= 0; ch = read()) {
_text.append((char) ch);
if (_text.endsWith(tail)) {
_text.setLength(_text.length() - tail.length());
addText();
_jspBuilder.endElement(qname.getName());
return;
}
}
throw error(L.l("expected '{0}' at end of file (for tag <{1}> at line {2}). Tags with 'tagdependent' content need close tags.",
tail, String.valueOf(_lineStart)));
}
}
/**
* Returns the full QName for the JSP page's name.
*/
private QName getElementQName(String name)
{
int p = name.lastIndexOf(':');
if (p > 0) {
String prefix = name.substring(0, p);
String url = Namespace.find(_namespaces, prefix);
_prefixes.add(prefix);
if (url != null)
return new QName(prefix, name.substring(p + 1), url);
else
return new QName("", name, "");
}
else {
String url = Namespace.find(_namespaces, "");
if (url != null)
return new QName("", name, url);
else
return new QName("", name, "");
}
}
/**
* Returns the full QName for the JSP page's name.
*/
private QName getAttributeQName(String name)
{
int p = name.lastIndexOf(':');
if (p > 0) {
String prefix = name.substring(0, p);
String url = Namespace.find(_namespaces, prefix);
if (url != null)
return new QName(prefix, name.substring(p + 1), url);
else
return new QName("", name, "");
}
else
return new QName("", name, "");
}
/**
* Parses the attributes of an element.
*/
private void parseAttributes(ArrayList names,
ArrayList values,
ArrayList prefixes,
ArrayList uris)
throws IOException, JspParseException
{
int ch = skipWhitespace(read());
while (XmlChar.isNameStart(ch)) {
ch = readName(ch);
String key = _tag.toString();
readValue(key, ch, _isXml);
String value = _value.toString();
if (key.startsWith("xmlns:")) {
String prefix = key.substring(6);
_jspBuilder.startPrefixMapping(prefix, value);
//_parseState.pushNamespace(prefix, value);
prefixes.add(prefix);
uris.add(value);
_namespaces = new Namespace(_namespaces, prefix, value);
}
else if (key.equals("xmlns")) {
_jspBuilder.startPrefixMapping("", value);
//_parseState.pushNamespace(prefix, value);
//_parseState.pushNamespace("", value);
_namespaces = new Namespace(_namespaces, "", value);
}
else {
names.add(getAttributeQName(key));
values.add(value);
}
ch = skipWhitespace(read());
}
unread(ch);
}
/**
* Reads an attribute value.
*/
private void readValue(String attribute, int ch, boolean isXml)
throws IOException, JspParseException
{
boolean isRuntimeAttribute = false;
_value.clear();
ch = skipWhitespace(ch);
if (ch != '=') {
unread(ch);
return;
}
ch = skipWhitespace(read());
if (ch != '\'' && ch != '"') {
if (XmlChar.isNameChar(ch)) {
ch = readName(ch);
throw error(L.l("'{0}' attribute value must be quoted at '{1}'. JSP attribute syntax is either attr=\"value\" or attr='value'.",
attribute, _tag));
}
else
throw error(L.l("'{0}' attribute value must be quoted at '{1}'. JSP attribute syntax is either attr=\"value\" or attr='value'.",
attribute, badChar(ch)));
}
int end = ch;
int lastCh = 0;
ch = read();
if (ch != '<') {
}
else if ((ch = read()) != '%')
_value.append('<');
else if ((ch = read()) != '=')
_value.append("<%");
else {
_value.append("<%");
isRuntimeAttribute = true;
}
while (ch != -1 && (ch != end || isRuntimeAttribute)) {
if (ch == '\\') {
switch ((ch = read())) {
case '\\':
case '\'':
case '\"':
// jsp/1505 vs jsp/184s
// _value.append('\\');
_value.append((char) ch);
ch = read();
break;
case '>':
if (lastCh == '%') {
_value.append('>');
ch = read();
}
else
_value.append('\\');
break;
case '%':
if (lastCh == '<') {
_value.append('%');
ch = read();
}
else
_value.append('\\');
break;
default:
_value.append('\\');
break;
}
}
else if (ch == '%' && isRuntimeAttribute) {
_value.append('%');
ch = read();
if (ch == '>')
isRuntimeAttribute = false;
}
else if (ch == '&' && isXml) {
lastCh = -1;
_value.append((char) parseEntity());
ch = read();
}
else if (ch == '&') {
if ((ch = read()) == 'a') {
if ((ch = read()) != 'p')
_value.append("&a");
else if ((ch = read()) != 'o')
_value.append("&ap");
else if ((ch = read()) != 's')
_value.append("&apo");
else if ((ch = read()) != ';')
_value.append("&apos");
else {
_value.append('\'');
ch = read();
}
}
else if (ch == 'q') {
if ((ch = read()) != 'u')
_value.append("&q");
else if ((ch = read()) != 'o')
_value.append("&qu");
else if ((ch = read()) != 't')
_value.append("&quo");
else if ((ch = read()) != ';')
_value.append(""");
else {
_value.append('"');
ch = read();
}
}
else {
_value.append('&');
}
}
else {
_value.append((char) ch);
lastCh = ch;
ch = read();
}
}
}
/**
* Parses an XML entity.
*/
int parseEntity()
throws IOException, JspParseException
{
int ch = read();
if (_isXml && ch == '#') {
int value = 0;
for (ch = read(); ch >= '0' && ch <= '9'; ch = read())
value = 10 * value + ch - '0';
if (ch != ';')
throw error(L.l("expected ';' at '{0}' in character entity. The XML character entities syntax is nn;",
badChar(ch)));
return (char) value;
}
CharBuffer cb = CharBuffer.allocate();
for (; ch >= 'a' && ch <= 'z'; ch = read()) {
cb.append((char) ch);
}
if (ch != ';') {
log.warning(L.l("expected ';' at '{0}' in entity '&{1}'. The XML entity syntax is &name;",
badChar(ch), cb));
}
String entity = cb.close();
if (entity.equals("lt"))
return '<';
else if (entity.equals("gt"))
return '>';
else if (entity.equals("amp"))
return '&';
else if (entity.equals("apos"))
return '\'';
else if (entity.equals("quot"))
return '"';
else
throw error(L.l("unknown entity '&{0};'. XML only recognizes the special entities <, >, &, ' and "", entity));
}
private int parseCloseTag()
throws IOException, JspParseException
{
int ch;
if (! XmlChar.isNameStart(ch = read())) {
addText("");
return ch;
}
ch = readName(ch);
String name = _tag.toString();
if (! _isXml && getTag(name) == TAG_UNKNOWN) {
addText("");
addText(name);
return ch;
}
ch = skipWhitespace(ch);
if (ch != '>')
throw error(L.l("expected '>' at {0}. The XML close tag syntax is .", badChar(ch)));
JspNode node = _jspBuilder.getCurrentNode();
String nodeName = node.getTagName();
if (nodeName.equals(name)) {
}
else if (nodeName.equals("resin-c:when")) {
throw error(L.l("#if expects closing #end before {0}> (#if at {1}). #if statements require #end before the enclosing tag closes.",
name, String.valueOf(node.getStartLine())));
}
else if (nodeName.equals("resin-c:otherwise")) {
throw error(L.l("#else expects closing #end before {0}> (#else at {1}). #if statements require #end before the enclosing tag closes.",
name, String.valueOf(node.getStartLine())));
}
else {
throw error(L.l("expected {0}> at {1}>. Closing tags must match opened tags.",
nodeName, name));
}
addText();
setLocation();
_jspBuilder.endElement(name);
return read();
}
private void processTaglibDirective(ArrayList keys,
ArrayList values)
throws IOException, JspParseException
{
int p = keys.indexOf(PREFIX);
if (p < 0)
throw error(L.l("The taglib directive requires a 'prefix' attribute. 'prefix' is the XML prefix for all tags in the taglib."));
String prefix = values.get(p);
if (_prefixes.contains(prefix)
&& _parseState.getQName(prefix) == null) {
throw error(L.l("The taglib prefix '{0}' must be defined before it is used.",
prefix));
}
if (_localPrefixes.contains(prefix))
throw error(L.l(
"<{0}> cannot occur after an action that uses the same prefix: {1}.",
JSP_DIRECTIVE_TAGLIB.getName(),
prefix));
String uri = null;
p = keys.indexOf(URI);
if (p >= 0)
uri = values.get(p);
String tagdir = null;
p = keys.indexOf(TAGDIR);
if (p >= 0)
tagdir = values.get(p);
if (uri != null)
processTaglib(prefix, uri);
else if (tagdir != null)
processTaglibDir(prefix, tagdir);
}
/**
* Adds a new known taglib prefix to the current namespace.
*/
private void processTaglib(String prefix, String uri)
throws JspParseException
{
Taglib taglib = null;
int colon = uri.indexOf(':');
int slash = uri.indexOf('/');
String location = null;
if (colon > 0 && colon < slash)
location = uri;
else if (slash == 0)
location = uri;
else
location = _uriPwd + uri;
try {
taglib = _tagManager.addTaglib(prefix, uri, location);
String tldURI = "urn:jsptld:" + uri;
_parseState.pushNamespace(prefix, tldURI);
_namespaces = new Namespace(_namespaces, prefix, tldURI);
return;
} catch (JspParseException e) {
throw error(e);
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
if (colon > 0 && colon < slash)
throw error(L.l("Unknown taglib '{0}'. Taglibs specified with an absolute URI must either be:\n1) specified in the web.xml\n2) defined in a jar's .tld in META-INF\n3) defined in a .tld in WEB-INF\n4) predefined by Resin",
uri));
}
/**
* Adds a new known tag dir to the current namespace.
*/
private void processTaglibDir(String prefix, String tagDir)
throws JspParseException
{
Taglib taglib = null;
try {
taglib = _tagManager.addTaglibDir(prefix, tagDir);
String tagURI = "urn:jsptagdir:" + tagDir;
_parseState.pushNamespace(prefix, tagURI);
_namespaces = new Namespace(_namespaces, prefix, tagURI);
return;
} catch (JspParseException e) {
throw error(e);
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
}
}
private void processIncludeDirective(ArrayList keys, ArrayList values)
throws IOException, JspParseException
{
int p = keys.indexOf("file");
if (p < 0)
throw error(L.l("The include directive requires a 'file' attribute."));
String file = (String) values.get(p);
pushInclude(file);
}
public void pushInclude(String value)
throws IOException, JspParseException
{
pushInclude(value, false);
}
public void pushInclude(String value, boolean allowDuplicate)
throws IOException, JspParseException
{
if (value.equals(""))
throw error("include directive needs 'file' attribute. Use either <%@ include file='myfile.jsp' %> or ");
Path include;
if (value.length() > 0 && value.charAt(0) == '/')
include = _parseState.resolvePath(value);
else
include = _parseState.resolvePath(_uriPwd + value);
String newUrl = _uriPwd;
if (value.startsWith("/"))
newUrl = value;
else
newUrl = _uriPwd + value;
include.setUserPath(newUrl);
String newUrlPwd;
int p = newUrl.lastIndexOf('/');
newUrlPwd = newUrl.substring(0, p + 1);
if (_jspPath != null && _jspPath.equals(include) && ! allowDuplicate)
throw error(L.l("circular include of '{0}' forbidden. A JSP file may not include itself.", include));
for (int i = 0; i < _includes.size(); i++) {
Include inc = _includes.get(i);
if (inc._stream != null && inc._stream.getPath() != null
&& inc._stream.getPath().equals(include) && ! allowDuplicate)
throw error(L.l("circular include of '{0}'. A JSP file may not include itself.", include));
}
try {
addInclude(include.openRead(), newUrlPwd);
} catch (IOException e) {
log.log(Level.WARNING, e.toString(), e);
if (include.exists())
throw error(L.l("can't open include of '{0}'. '{1}' exists but it's not readable.",
value, include.getNativePath()));
else
throw error(L.l("can't open include of '{0}'. '{1}' does not exist.",
value, include.getNativePath()));
}
}
private void addInclude(ReadStream stream, String newUrlPwd)
throws IOException, JspParseException
{
addText();
readLf();
Include inc = null;
if (_stream != null) {
inc = new Include(_localPrefixes,
_stream,
_line,
_uriPwd,
_parseState.isLocalScriptingInvalid());
_parseState.setLocalScriptingInvalid(false);
_includes.add(inc);
_localPrefixes = new HashSet();
}
_parseState.addDepend(stream.getPath());
try {
String encoding = _stream.getEncoding();
if (encoding != null)
stream.setEncoding(encoding);
} catch (Exception e) {
}
_stream = stream;
_filename = stream.getUserPath();
_jspPath = stream.getPath();
_line = 1;
_lineStart = _line;
_uriPwd = newUrlPwd;
_parseState.setUriPwd(_uriPwd);
}
/**
* Skips whitespace characters.
*
* @param ch the current character
*
* @return the first non-whitespace character
*/
private int skipWhitespace(int ch)
throws IOException, JspParseException
{
for (; XmlChar.isWhitespace(ch); ch = read()) {
}
return ch;
}
/**
* Skips whitespace to end of line
*
* @param ch the current character
*
* @return the first non-whitespace character
*/
private int skipWhitespaceToEndOfLine(int ch)
throws IOException, JspParseException
{
for (; XmlChar.isWhitespace(ch); ch = read()) {
if (ch == '\n')
return read();
else if (ch == '\r') {
ch = read();
if (ch == '\n')
return read();
else
return ch;
}
}
return ch;
}
private void addText(char ch)
throws JspParseException
{
_text.append(ch);
}
private void addText(String s)
throws JspParseException
{
_text.append(s);
}
private void addText()
throws JspParseException
{
if (_text.length() > 0) {
createText();
}
_startText = _charCount;
_lineStart = _line;
}
private void createText()
throws JspParseException
{
String string = _text.toString();
setLocation(_jspPath, _filename, _lineStart);
if (_parseState.isTrimWhitespace() && isWhitespace(string)) {
}
else {
_jspBuilder.text(string, _filename, _lineStart, _line);
}
_lineStart = _line;
_text.clear();
_startText = _charCount;
}
private boolean isWhitespace(String s)
{
int length = s.length();
for (int i = 0; i < length; i++) {
if (! Character.isWhitespace(s.charAt(i)))
return false;
}
return true;
}
/**
* Checks to see if the element name represents a tag.
*/
private int getTag(String name)
throws JspParseException
{
int p = name.indexOf(':');
if (p < 0)
return TAG_UNKNOWN;
String prefix = name.substring(0, p);
String local = name.substring(p + 1);
_prefixes.add(prefix);
_localPrefixes.add(prefix);
String url = Namespace.find(_namespaces, prefix);
if (url != null)
return TAG_JSP;
else
return TAG_UNKNOWN;
/*
QName qname;
if (url != null)
qname = new QName(prefix, local, url);
else
qname = new QName(prefix, local, null);
TagInfo tag = _tagManager.getTag(qname);
if (tag != null)
return TAG_JSP;
else
return TAG_UNKNOWN;
*/
}
private void unread(int ch)
{
_peek = ch;
}
/**
* Reads the next character we're in the middle of cr/lf.
*/
private void readLf() throws IOException, JspParseException
{
if (_seenCr) {
int ch = read();
if (ch != '\n')
_peek = ch;
}
}
/**
* Reads the next character.
*/
private int read() throws IOException, JspParseException
{
int ch;
if (_peek >= 0) {
ch = _peek;
_peek = -1;
return ch;
}
try {
if ((ch = _stream.readChar()) >= 0) {
_charCount++;
if (ch == '\r') {
_line++;
_charCount = 0;
_seenCr = true;
}
else if (ch == '\n' && _seenCr) {
_seenCr = false;
_charCount = 0;
}
else if (ch == '\n') {
_line++;
_charCount = 0;
}
else {
_seenCr = false;
}
return ch;
}
} catch (IOException e) {
throw error(e.toString());
}
_stream.close();
_seenCr = false;
if (_includes.size() > 0) {
setLocation(_jspPath, _filename, _line);
Include include = _includes.get(_includes.size() - 1);
_includes.remove(_includes.size() - 1);
_stream = include._stream;
_filename = _stream.getUserPath();
_jspPath = _stream.getPath();
_line = include._line;
_lineStart = _line;
_uriPwd = include._uriPwd;
_localPrefixes = include._localPrefixes;
_parseState.setUriPwd(_uriPwd);
_parseState.setLocalScriptingInvalid(include._oldLocalScriptingDisabled);
setLocation(_jspPath, _filename, _line);
return read();
}
return -1;
}
void clear(Path appDir, String errorPage)
{
}
/**
* Creates an error message adding the filename and line.
*
* @param e the exception
*/
public JspParseException error(Exception e)
{
String message = e.getMessage();
if (e instanceof JspParseException) {
log.log(Level.FINE, e.toString(), e);
}
if (e instanceof JspLineParseException)
return (JspLineParseException) e;
else if (e instanceof LineCompileException)
return new JspLineParseException(e);
if (_lineMap == null)
return new JspLineParseException(_filename + ":" + _line + ": " + message,
e);
else {
LineMap.Line line = _lineMap.getLine(_line);
return new JspLineParseException(line.getSourceFilename() + ":" +
line.getSourceLine(_line) + ": " +
message,
e);
}
}
/**
* Creates an error message adding the filename and line.
*
* @param message the error message
*/
public JspParseException error(String message)
{
JspGenerator gen = _jspBuilder.getGenerator();
if (_lineMap == null) {
if (gen != null)
return new JspLineParseException(_filename + ":" + _line + ": " + message + gen.getSourceLines(_jspPath, _line));
else
return new JspLineParseException(_filename + ":" + _line + ": " + message);
}
else {
LineMap.Line line = _lineMap.getLine(_line);
return new JspLineParseException(line.getSourceFilename() + ":" +
line.getSourceLine(_line) + ": " +
message);
}
}
/**
* Creates an error message adding the filename and line.
*
* @param message the error message
*/
public JspParseException error(String message, Throwable e)
{
if (_lineMap == null)
return new JspLineParseException(_filename + ":" + _line + ": " + message, e);
else {
LineMap.Line line = _lineMap.getLine(_line);
return new JspLineParseException(line.getSourceFilename() + ":" +
line.getSourceLine(_line) + ": " +
message,
e);
}
}
/**
* Sets the current location in the original file
*/
private void setLocation()
{
setLocation(_jspPath, _filename, _line);
}
/**
* Sets the current location in the original file
*
* @param filename the filename
* @param line the line in the source file
*/
private void setLocation(Path jspPath, String filename, int line)
{
if (_lineMap == null) {
_jspBuilder.setLocation(jspPath, filename, line);
}
else {
LineMap.Line srcLine = _lineMap.getLine(line);
if (srcLine != null) {
_jspBuilder.setLocation(jspPath,
srcLine.getSourceFilename(),
srcLine.getSourceLine(line));
}
}
}
private String badChar(int ch)
{
if (ch < 0)
return "end of file";
else if (ch == '\n' || ch == '\r')
return "end of line";
else if (ch >= 0x20 && ch <= 0x7f)
return "'" + (char) ch + "'";
else
return "'" + (char) ch + "' (\\u" + hex(ch) + ")";
}
private String hex(int value)
{
CharBuffer cb = new CharBuffer();
for (int b = 3; b >= 0; b--) {
int v = (value >> (4 * b)) & 0xf;
if (v < 10)
cb.append((char) (v + '0'));
else
cb.append((char) (v - 10 + 'a'));
}
return cb.toString();
}
class Include {
ReadStream _stream;
int _line;
String _uriPwd;
Set _localPrefixes;
boolean _oldLocalScriptingDisabled;
Include(Set prefixes,
ReadStream stream,
int line,
String uriPwd,
boolean oldLocalScriptingDisabled
)
{
_stream = stream;
_line = line;
_uriPwd = uriPwd;
_localPrefixes = prefixes;
_oldLocalScriptingDisabled = oldLocalScriptingDisabled;
}
}
}