001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.catalina.ssi;
018:
019: import java.io.IOException;
020: import java.io.PrintWriter;
021: import java.io.Reader;
022: import java.io.StringWriter;
023: import java.util.HashMap;
024: import java.util.StringTokenizer;
025: import org.apache.catalina.util.IOTools;
026:
027: /**
028: * The entry point to SSI processing. This class does the actual parsing,
029: * delegating to the SSIMediator, SSICommand, and SSIExternalResolver as
030: * necessary[
031: *
032: * @author Dan Sandberg
033: * @author David Becker
034: * @version $Revision: 531303 $, $Date: 2007-04-23 02:24:01 +0200 (lun., 23 avr. 2007) $
035: */
036: public class SSIProcessor {
037: /** The start pattern */
038: protected final static String COMMAND_START = "<!--#";
039: /** The end pattern */
040: protected final static String COMMAND_END = "-->";
041: protected final static int BUFFER_SIZE = 4096;
042: protected SSIExternalResolver ssiExternalResolver;
043: protected HashMap commands = new HashMap();
044: protected int debug;
045:
046: public SSIProcessor(SSIExternalResolver ssiExternalResolver,
047: int debug) {
048: this .ssiExternalResolver = ssiExternalResolver;
049: this .debug = debug;
050: addBuiltinCommands();
051: }
052:
053: protected void addBuiltinCommands() {
054: addCommand("config", new SSIConfig());
055: addCommand("echo", new SSIEcho());
056: addCommand("exec", new SSIExec());
057: addCommand("include", new SSIInclude());
058: addCommand("flastmod", new SSIFlastmod());
059: addCommand("fsize", new SSIFsize());
060: addCommand("printenv", new SSIPrintenv());
061: addCommand("set", new SSISet());
062: SSIConditional ssiConditional = new SSIConditional();
063: addCommand("if", ssiConditional);
064: addCommand("elif", ssiConditional);
065: addCommand("endif", ssiConditional);
066: addCommand("else", ssiConditional);
067: }
068:
069: public void addCommand(String name, SSICommand command) {
070: commands.put(name, command);
071: }
072:
073: /**
074: * Process a file with server-side commands, reading from reader and
075: * writing the processed version to writer. NOTE: We really should be doing
076: * this in a streaming way rather than converting it to an array first.
077: *
078: * @param reader
079: * the reader to read the file containing SSIs from
080: * @param writer
081: * the writer to write the file with the SSIs processed.
082: * @return the most current modified date resulting from any SSI commands
083: * @throws IOException
084: * when things go horribly awry. Should be unlikely since the
085: * SSICommand usually catches 'normal' IOExceptions.
086: */
087: public long process(Reader reader, long lastModifiedDate,
088: PrintWriter writer) throws IOException {
089: SSIMediator ssiMediator = new SSIMediator(ssiExternalResolver,
090: lastModifiedDate, debug);
091: StringWriter stringWriter = new StringWriter();
092: IOTools.flow(reader, stringWriter);
093: String fileContents = stringWriter.toString();
094: stringWriter = null;
095: int index = 0;
096: boolean inside = false;
097: StringBuffer command = new StringBuffer();
098: try {
099: while (index < fileContents.length()) {
100: char c = fileContents.charAt(index);
101: if (!inside) {
102: if (c == COMMAND_START.charAt(0)
103: && charCmp(fileContents, index,
104: COMMAND_START)) {
105: inside = true;
106: index += COMMAND_START.length();
107: command.setLength(0); //clear the command string
108: } else {
109: if (!ssiMediator.getConditionalState().processConditionalCommandsOnly) {
110: writer.write(c);
111: }
112: index++;
113: }
114: } else {
115: if (c == COMMAND_END.charAt(0)
116: && charCmp(fileContents, index, COMMAND_END)) {
117: inside = false;
118: index += COMMAND_END.length();
119: String strCmd = parseCmd(command);
120: if (debug > 0) {
121: ssiExternalResolver.log(
122: "SSIProcessor.process -- processing command: "
123: + strCmd, null);
124: }
125: String[] paramNames = parseParamNames(command,
126: strCmd.length());
127: String[] paramValues = parseParamValues(
128: command, strCmd.length(),
129: paramNames.length);
130: //We need to fetch this value each time, since it may
131: // change
132: // during the loop
133: String configErrMsg = ssiMediator
134: .getConfigErrMsg();
135: SSICommand ssiCommand = (SSICommand) commands
136: .get(strCmd.toLowerCase());
137: String errorMessage = null;
138: if (ssiCommand == null) {
139: errorMessage = "Unknown command: " + strCmd;
140: } else if (paramValues == null) {
141: errorMessage = "Error parsing directive parameters.";
142: } else if (paramNames.length != paramValues.length) {
143: errorMessage = "Parameter names count does not match parameter values count on command: "
144: + strCmd;
145: } else {
146: // don't process the command if we are processing
147: // conditional
148: // commands only and the
149: // command is not conditional
150: if (!ssiMediator.getConditionalState().processConditionalCommandsOnly
151: || ssiCommand instanceof SSIConditional) {
152: long lmd = ssiCommand
153: .process(ssiMediator, strCmd,
154: paramNames,
155: paramValues, writer);
156: if (lmd > lastModifiedDate) {
157: lastModifiedDate = lmd;
158: }
159: }
160: }
161: if (errorMessage != null) {
162: ssiExternalResolver.log(errorMessage, null);
163: writer.write(configErrMsg);
164: }
165: } else {
166: command.append(c);
167: index++;
168: }
169: }
170: }
171: } catch (SSIStopProcessingException e) {
172: //If we are here, then we have already stopped processing, so all
173: // is good
174: }
175: return lastModifiedDate;
176: }
177:
178: /**
179: * Parse a StringBuffer and take out the param type token. Called from
180: * <code>requestHandler</code>
181: *
182: * @param cmd
183: * a value of type 'StringBuffer'
184: * @return a value of type 'String[]'
185: */
186: protected String[] parseParamNames(StringBuffer cmd, int start) {
187: int bIdx = start;
188: int i = 0;
189: int quotes = 0;
190: boolean inside = false;
191: StringBuffer retBuf = new StringBuffer();
192: while (bIdx < cmd.length()) {
193: if (!inside) {
194: while (bIdx < cmd.length() && isSpace(cmd.charAt(bIdx)))
195: bIdx++;
196: if (bIdx >= cmd.length())
197: break;
198: inside = !inside;
199: } else {
200: while (bIdx < cmd.length() && cmd.charAt(bIdx) != '=') {
201: retBuf.append(cmd.charAt(bIdx));
202: bIdx++;
203: }
204: retBuf.append('=');
205: inside = !inside;
206: quotes = 0;
207: boolean escaped = false;
208: for (; bIdx < cmd.length() && quotes != 2; bIdx++) {
209: char c = cmd.charAt(bIdx);
210: // Need to skip escaped characters
211: if (c == '\\' && !escaped) {
212: escaped = true;
213: bIdx++;
214: continue;
215: }
216: escaped = false;
217: if (c == '"')
218: quotes++;
219: }
220: }
221: }
222: StringTokenizer str = new StringTokenizer(retBuf.toString(),
223: "=");
224: String[] retString = new String[str.countTokens()];
225: while (str.hasMoreTokens()) {
226: retString[i++] = str.nextToken().trim();
227: }
228: return retString;
229: }
230:
231: /**
232: * Parse a StringBuffer and take out the param token. Called from
233: * <code>requestHandler</code>
234: *
235: * @param cmd
236: * a value of type 'StringBuffer'
237: * @return a value of type 'String[]'
238: */
239: protected String[] parseParamValues(StringBuffer cmd, int start,
240: int count) {
241: int valIndex = 0;
242: boolean inside = false;
243: String[] vals = new String[count];
244: StringBuffer sb = new StringBuffer();
245: char endQuote = 0;
246: for (int bIdx = start; bIdx < cmd.length(); bIdx++) {
247: if (!inside) {
248: while (bIdx < cmd.length()
249: && !isQuote(cmd.charAt(bIdx)))
250: bIdx++;
251: if (bIdx >= cmd.length())
252: break;
253: inside = !inside;
254: endQuote = cmd.charAt(bIdx);
255: } else {
256: boolean escaped = false;
257: for (; bIdx < cmd.length(); bIdx++) {
258: char c = cmd.charAt(bIdx);
259: // Check for escapes
260: if (c == '\\' && !escaped) {
261: escaped = true;
262: continue;
263: }
264: // If we reach the other " then stop
265: if (c == endQuote && !escaped)
266: break;
267: // Since parsing of attributes and var
268: // substitution is done in separate places,
269: // we need to leave escape in the string
270: if (c == '$' && escaped)
271: sb.append('\\');
272: escaped = false;
273: sb.append(c);
274: }
275: // If we hit the end without seeing a quote
276: // the signal an error
277: if (bIdx == cmd.length())
278: return null;
279: vals[valIndex++] = sb.toString();
280: sb.delete(0, sb.length()); // clear the buffer
281: inside = !inside;
282: }
283: }
284: return vals;
285: }
286:
287: /**
288: * Parse a StringBuffer and take out the command token. Called from
289: * <code>requestHandler</code>
290: *
291: * @param cmd
292: * a value of type 'StringBuffer'
293: * @return a value of type 'String', or null if there is none
294: */
295: private String parseCmd(StringBuffer cmd) {
296: int firstLetter = -1;
297: int lastLetter = -1;
298: for (int i = 0; i < cmd.length(); i++) {
299: char c = cmd.charAt(i);
300: if (Character.isLetter(c)) {
301: if (firstLetter == -1) {
302: firstLetter = i;
303: }
304: lastLetter = i;
305: } else if (isSpace(c)) {
306: if (lastLetter > -1) {
307: break;
308: }
309: } else {
310: break;
311: }
312: }
313: String command = null;
314: if (firstLetter != -1) {
315: command = cmd.substring(firstLetter, lastLetter + 1);
316: }
317: return command;
318: }
319:
320: protected boolean charCmp(String buf, int index, String command) {
321: return buf.regionMatches(index, command, 0, command.length());
322: }
323:
324: protected boolean isSpace(char c) {
325: return c == ' ' || c == '\n' || c == '\t' || c == '\r';
326: }
327:
328: protected boolean isQuote(char c) {
329: return c == '\'' || c == '\"' || c == '`';
330: }
331: }
|