001: /*
002: * SSIProcessor.java
003: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIProcessor.java,v 1.1 2002/05/24 04:38:58 billbarker Exp $
004: * $Revision: 1.1 $
005: * $Date: 2002/05/24 04:38:58 $
006: *
007: * ====================================================================
008: *
009: * The Apache Software License, Version 1.1
010: *
011: * Copyright (c) 1999 The Apache Software Foundation. All rights
012: * reserved.
013: *
014: * Redistribution and use in source and binary forms, with or without
015: * modification, are permitted provided that the following conditions
016: * are met:
017: *
018: * 1. Redistributions of source code must retain the above copyright
019: * notice, this list of conditions and the following disclaimer.
020: *
021: * 2. Redistributions in binary form must reproduce the above copyright
022: * notice, this list of conditions and the following disclaimer in
023: * the documentation and/or other materials provided with the
024: * distribution.
025: *
026: * 3. The end-user documentation included with the redistribution, if
027: * any, must include the following acknowlegement:
028: * "This product includes software developed by the
029: * Apache Software Foundation (http://www.apache.org/)."
030: * Alternately, this acknowlegement may appear in the software itself,
031: * if and wherever such third-party acknowlegements normally appear.
032: *
033: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
034: * Foundation" must not be used to endorse or promote products derived
035: * from this software without prior written permission. For written
036: * permission, please contact apache@apache.org.
037: *
038: * 5. Products derived from this software may not be called "Apache"
039: * nor may "Apache" appear in their names without prior written
040: * permission of the Apache Group.
041: *
042: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
043: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
044: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
045: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
046: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
047: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
048: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
049: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
050: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
051: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
052: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
053: * SUCH DAMAGE.
054: * ====================================================================
055: *
056: * This software consists of voluntary contributions made by many
057: * individuals on behalf of the Apache Software Foundation. For more
058: * information on the Apache Software Foundation, please see
059: * <http://www.apache.org/>.
060: *
061: * [Additional notices, if required by prior licensing conditions]
062: *
063: */
064: package org.apache.catalina.ssi;
065:
066: import java.io.IOException;
067: import java.io.PrintWriter;
068: import java.io.Reader;
069: import java.io.StringWriter;
070: import java.io.Writer;
071: import java.util.Date;
072: import java.util.HashMap;
073: import java.util.StringTokenizer;
074: import org.apache.catalina.util.IOTools;
075:
076: /**
077: * The entry point to SSI processing. This class does the actual parsing, delegating to the SSIMediator, SSICommand, and
078: * SSIExternalResolver as necessary[
079: *
080: * @author Dan Sandberg
081: * @version $Revision: 1.1 $, $Date: 2002/05/24 04:38:58 $
082: *
083: */
084: public class SSIProcessor {
085: /** The start pattern */
086: protected final static String COMMAND_START = "<!--#";
087:
088: /** The end pattern */
089: protected final static String COMMAND_END = "-->";
090: protected final static int BUFFER_SIZE = 4096;
091:
092: protected SSIExternalResolver ssiExternalResolver;
093: protected HashMap commands = new HashMap();
094: protected int debug;
095:
096: public SSIProcessor(SSIExternalResolver ssiExternalResolver,
097: int debug) {
098: this .ssiExternalResolver = ssiExternalResolver;
099: this .debug = debug;
100: addBuiltinCommands();
101: }
102:
103: protected void addBuiltinCommands() {
104: addCommand("config", new SSIConfig());
105: addCommand("echo", new SSIEcho());
106: addCommand("exec", new SSIExec());
107: addCommand("include", new SSIInclude());
108: addCommand("flastmod", new SSIFlastmod());
109: addCommand("fsize", new SSIFsize());
110: addCommand("printenv", new SSIPrintenv());
111: addCommand("set", new SSISet());
112: }
113:
114: public void addCommand(String name, SSICommand command) {
115: commands.put(name, command);
116: }
117:
118: /**
119: * Process a file with server-side commands, reading from reader and writing the processed
120: * version to writer.
121: *
122: * NOTE: We really should be doing this in a streaming way rather than converting it to an array first.
123: *
124: * @param reader the reader to read the file containing SSIs from
125: * @param writer the writer to write the file with the SSIs processed.
126: * @throws IOException when things go horribly awry. Should be unlikely since
127: * the SSICommand usually catches 'normal' IOExceptions.
128: */
129: public void process(Reader reader, Date lastModifiedDate,
130: PrintWriter writer) throws IOException {
131: SSIMediator ssiMediator = new SSIMediator(ssiExternalResolver,
132: lastModifiedDate, debug);
133:
134: StringWriter stringWriter = new StringWriter();
135: IOTools.flow(reader, stringWriter);
136: String fileContents = stringWriter.toString();
137: stringWriter = null;
138:
139: int index = 0;
140: boolean inside = false;
141: StringBuffer command = new StringBuffer();
142: try {
143: while (index < fileContents.length()) {
144: char c = fileContents.charAt(index);
145: if (!inside) {
146: if (c == COMMAND_START.charAt(0)
147: && charCmp(fileContents, index,
148: COMMAND_START)) {
149: inside = true;
150: index += COMMAND_START.length();
151: command.setLength(0); //clear the command string
152: } else {
153: writer.write(c);
154: index++;
155: }
156: } else {
157: if (c == COMMAND_END.charAt(0)
158: && charCmp(fileContents, index, COMMAND_END)) {
159: inside = false;
160: index += COMMAND_END.length();
161: String strCmd = parseCmd(command);
162: if (debug > 0) {
163: ssiExternalResolver.log(
164: "SSIProcessor.process -- processing command: "
165: + strCmd, null);
166: }
167: String[] paramNames = parseParamNames(command,
168: strCmd.length());
169: String[] paramValues = parseParamValues(
170: command, strCmd.length());
171:
172: //We need to fetch this value each time, since it may change during the loop
173: String configErrMsg = ssiMediator
174: .getConfigErrMsg();
175: SSICommand ssiCommand = (SSICommand) commands
176: .get(strCmd.toLowerCase());
177: if (ssiCommand != null) {
178: if (paramNames.length == paramValues.length) {
179: ssiCommand
180: .process(ssiMediator,
181: paramNames,
182: paramValues, writer);
183: } else {
184: ssiExternalResolver.log(
185: "Parameter names count does not match parameter values count on command: "
186: + strCmd, null);
187: writer.write(configErrMsg);
188: }
189: } else {
190: ssiExternalResolver.log("Unknown command: "
191: + strCmd, null);
192: writer.write(configErrMsg);
193: }
194: } else {
195: command.append(c);
196: index++;
197: }
198: }
199: }
200: } catch (SSIStopProcessingException e) {
201: //If we are here, then we have already stopped processing, so all is good
202: }
203: }
204:
205: /**
206: * Parse a StringBuffer and take out the param type token.
207: * Called from <code>requestHandler</code>
208: * @param cmd a value of type 'StringBuffer'
209: * @return a value of type 'String[]'
210: */
211: protected String[] parseParamNames(StringBuffer cmd, int start) {
212: int bIdx = start;
213: int i = 0;
214: int quotes = 0;
215: boolean inside = false;
216: StringBuffer retBuf = new StringBuffer();
217:
218: while (bIdx < cmd.length()) {
219: if (!inside) {
220: while (bIdx < cmd.length() && isSpace(cmd.charAt(bIdx)))
221: bIdx++;
222:
223: if (bIdx >= cmd.length())
224: break;
225:
226: inside = !inside;
227: } else {
228: while (bIdx < cmd.length() && cmd.charAt(bIdx) != '=') {
229: retBuf.append(cmd.charAt(bIdx));
230: bIdx++;
231: }
232:
233: retBuf.append('"');
234: inside = !inside;
235: quotes = 0;
236:
237: while (bIdx < cmd.length() && quotes != 2) {
238: if (cmd.charAt(bIdx) == '"')
239: quotes++;
240:
241: bIdx++;
242: }
243: }
244: }
245:
246: StringTokenizer str = new StringTokenizer(retBuf.toString(),
247: "\"");
248: String[] retString = new String[str.countTokens()];
249:
250: while (str.hasMoreTokens()) {
251: retString[i++] = str.nextToken().trim();
252: }
253:
254: return retString;
255: }
256:
257: /**
258: * Parse a StringBuffer and take out the param token.
259: * Called from <code>requestHandler</code>
260: * @param cmd a value of type 'StringBuffer'
261: * @return a value of type 'String[]'
262: */
263: protected String[] parseParamValues(StringBuffer cmd, int start) {
264: int bIdx = start;
265: int i = 0;
266: int quotes = 0;
267: boolean inside = false;
268: StringBuffer retBuf = new StringBuffer();
269:
270: while (bIdx < cmd.length()) {
271: if (!inside) {
272: while (bIdx < cmd.length() && cmd.charAt(bIdx) != '"')
273: bIdx++;
274:
275: if (bIdx >= cmd.length())
276: break;
277:
278: inside = !inside;
279: } else {
280: while (bIdx < cmd.length() && cmd.charAt(bIdx) != '"') {
281: retBuf.append(cmd.charAt(bIdx));
282: bIdx++;
283: }
284:
285: retBuf.append('"');
286: inside = !inside;
287: }
288:
289: bIdx++;
290: }
291:
292: StringTokenizer str = new StringTokenizer(retBuf.toString(),
293: "\"");
294: String[] retString = new String[str.countTokens()];
295:
296: while (str.hasMoreTokens()) {
297: retString[i++] = str.nextToken();
298: }
299:
300: return retString;
301: }
302:
303: /**
304: * Parse a StringBuffer and take out the command token.
305: * Called from <code>requestHandler</code>
306: * @param cmd a value of type 'StringBuffer'
307: * @return a value of type 'String', or null if there is none
308: */
309: private String parseCmd(StringBuffer cmd) {
310: int firstLetter = -1;
311: int lastLetter = -1;
312: for (int i = 0; i < cmd.length(); i++) {
313: char c = cmd.charAt(i);
314: if (Character.isLetter(c)) {
315: if (firstLetter == -1) {
316: firstLetter = i;
317: }
318: lastLetter = i;
319: } else if (isSpace(c)) {
320: if (lastLetter > -1) {
321: break;
322: }
323: } else {
324: break;
325: }
326: }
327:
328: String command = null;
329: if (firstLetter != -1) {
330: command = cmd.substring(firstLetter, lastLetter + 1);
331: }
332: return command;
333: }
334:
335: protected boolean charCmp(String buf, int index, String command) {
336: return buf.regionMatches(index, command, 0, command.length());
337: }
338:
339: protected boolean isSpace(char c) {
340: return c == ' ' || c == '\n' || c == '\t' || c == '\r';
341: }
342: }
|