001: /*
002: * SSIServletExternalResolver.java
003: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/ssi/SSIServletExternalResolver.java,v 1.2 2002/05/26 00:00:55 remm Exp $
004: * $Revision: 1.2 $
005: * $Date: 2002/05/26 00:00:55 $
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:
065: package org.apache.catalina.ssi;
066:
067: import java.io.IOException;
068: import java.io.InputStream;
069: import java.io.InputStreamReader;
070: import java.io.OutputStream;
071: import java.io.OutputStreamWriter;
072: import java.io.BufferedInputStream;
073: import java.io.BufferedReader;
074: import java.io.ByteArrayOutputStream;
075: import java.io.PrintWriter;
076: import java.io.Reader;
077: import java.io.StringWriter;
078: import java.io.Writer;
079: import java.net.URL;
080: import java.net.URLConnection;
081: import java.net.URLDecoder;
082: import java.util.ArrayList;
083: import java.util.Collection;
084: import java.util.Date;
085: import java.util.Enumeration;
086: import java.util.HashMap;
087: import java.util.Locale;
088: import java.text.SimpleDateFormat;
089: import java.util.StringTokenizer;
090: import java.util.TimeZone;
091: import javax.servlet.RequestDispatcher;
092: import javax.servlet.ServletException;
093: import javax.servlet.ServletContext;
094: import javax.servlet.ServletOutputStream;
095: import javax.servlet.http.HttpServlet;
096: import javax.servlet.http.HttpServletRequest;
097: import javax.servlet.http.HttpServletResponse;
098:
099: /**
100: * An implementation of SSIExternalResolver that is used with servlets.
101: *
102: * @author Dan Sandberg
103: * @version $Revision: 1.2 $, $Date: 2002/05/26 00:00:55 $
104: */
105: public class SSIServletExternalResolver implements SSIExternalResolver {
106: protected final String VARIABLE_NAMES[] = { "AUTH_TYPE",
107: "CONTENT_LENGTH", "CONTENT_TYPE", "DOCUMENT_NAME",
108: "DOCUMENT_URI", "GATEWAY_INTERFACE", "PATH_INFO",
109: "PATH_TRANSLATED", "QUERY_STRING",
110: "QUERY_STRING_UNESCAPED", "REMOTE_ADDR", "REMOTE_HOST",
111: "REMOTE_USER", "REQUEST_METHOD", "SCRIPT_NAME",
112: "SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL",
113: "SERVER_SOFTWARE" };
114:
115: protected HttpServlet servlet;
116: protected HttpServletRequest req;
117: protected HttpServletResponse res;
118: protected boolean isVirtualWebappRelative;
119: protected int debug;
120:
121: public SSIServletExternalResolver(HttpServlet servlet,
122: HttpServletRequest req, HttpServletResponse res,
123: boolean isVirtualWebappRelative, int debug) {
124: this .servlet = servlet;
125: this .req = req;
126: this .res = res;
127: this .isVirtualWebappRelative = isVirtualWebappRelative;
128: this .debug = debug;
129: }
130:
131: public void log(String message, Throwable throwable) {
132: //We can't assume that Servlet.log( message, null )
133: //is the same as Servlet.log( message ), since API
134: //doesn't seem to say so.
135: if (throwable != null) {
136: servlet.log(message, throwable);
137: } else {
138: servlet.log(message);
139: }
140: }
141:
142: public void addVariableNames(Collection variableNames) {
143: for (int i = 0; i < VARIABLE_NAMES.length; i++) {
144: String variableName = VARIABLE_NAMES[i];
145: String variableValue = getVariableValue(variableName);
146: if (variableValue != null) {
147: variableNames.add(variableName);
148: }
149: }
150: Enumeration e = req.getAttributeNames();
151: while (e.hasMoreElements()) {
152: String name = (String) e.nextElement();
153: if (!isNameReserved(name)) {
154: variableNames.add(name);
155: }
156: }
157: }
158:
159: protected Object getReqAttributeIgnoreCase(String targetName) {
160: Object object = null;
161:
162: if (!isNameReserved(targetName)) {
163: object = req.getAttribute(targetName);
164: if (object == null) {
165: Enumeration e = req.getAttributeNames();
166: while (e.hasMoreElements()) {
167: String name = (String) e.nextElement();
168: if (targetName.equalsIgnoreCase(name)
169: && !isNameReserved(name)) {
170:
171: object = req.getAttribute(name);
172: if (object != null) {
173: break;
174: }
175: }
176: }
177: }
178: }
179: return object;
180: }
181:
182: protected boolean isNameReserved(String name) {
183: return name.startsWith("java.") || name.startsWith("javax.")
184: || name.startsWith("sun.");
185: }
186:
187: public void setVariableValue(String name, String value) {
188: if (!isNameReserved(name)) {
189: req.setAttribute(name, value);
190: }
191: }
192:
193: public String getVariableValue(String name) {
194: String retVal = null;
195:
196: Object object = getReqAttributeIgnoreCase(name);
197: if (object != null) {
198: retVal = object.toString();
199: } else {
200: retVal = getCGIVariable(name);
201: }
202: return retVal;
203: }
204:
205: protected String getCGIVariable(String name) {
206: String retVal = null;
207:
208: if (name.equalsIgnoreCase("AUTH_TYPE")) {
209: retVal = req.getAuthType();
210: } else if (name.equalsIgnoreCase("CONTENT_LENGTH")) {
211: int contentLength = req.getContentLength();
212: if (contentLength >= 0) {
213: retVal = Integer.toString(contentLength);
214: }
215: } else if (name.equalsIgnoreCase("CONTENT_TYPE")) {
216: retVal = req.getContentType();
217: } else if (name.equalsIgnoreCase("DOCUMENT_NAME")) {
218: String requestURI = req.getRequestURI();
219: retVal = requestURI
220: .substring(requestURI.lastIndexOf('/') + 1);
221: } else if (name.equalsIgnoreCase("DOCUMENT_URI")) {
222: retVal = req.getRequestURI();
223: } else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
224: retVal = "CGI/1.1";
225: } else if (name.equalsIgnoreCase("PATH_INFO")) {
226: retVal = req.getPathInfo();
227: } else if (name.equalsIgnoreCase("PATH_TRANSLATED")) {
228: retVal = req.getPathTranslated();
229: } else if (name.equalsIgnoreCase("QUERY_STRING")) {
230: //apache displays this as an empty string rather than (none)
231: retVal = nullToEmptyString(req.getQueryString());
232: } else if (name.equalsIgnoreCase("QUERY_STRING_UNESCAPED")) {
233: String queryString = req.getQueryString();
234: if (queryString != null) {
235: retVal = URLDecoder.decode(queryString);
236: }
237: } else if (name.equalsIgnoreCase("REMOTE_ADDR")) {
238: retVal = req.getRemoteAddr();
239: } else if (name.equalsIgnoreCase("REMOTE_HOST")) {
240: retVal = req.getRemoteHost();
241: } else if (name.equalsIgnoreCase("REMOTE_USER")) {
242: retVal = req.getRemoteUser();
243: } else if (name.equalsIgnoreCase("REQUEST_METHOD")) {
244: retVal = req.getMethod();
245: } else if (name.equalsIgnoreCase("SCRIPT_NAME")) {
246: retVal = req.getServletPath();
247: } else if (name.equalsIgnoreCase("SERVER_NAME")) {
248: retVal = req.getServerName();
249: } else if (name.equalsIgnoreCase("SERVER_PORT")) {
250: retVal = Integer.toString(req.getServerPort());
251: } else if (name.equalsIgnoreCase("SERVER_PROTOCOL")) {
252: retVal = req.getProtocol();
253: } else if (name.equalsIgnoreCase("SERVER_SOFTWARE")) {
254: ServletContext servletContext = servlet.getServletContext();
255: retVal = servletContext.getServerInfo();
256: }
257: return retVal;
258: }
259:
260: public Date getCurrentDate() {
261: return new Date();
262: }
263:
264: protected String nullToEmptyString(String string) {
265: String retVal = string;
266:
267: if (retVal == null) {
268: retVal = "";
269: }
270: return retVal;
271: }
272:
273: protected String getPathWithoutFileName(String servletPath) {
274: String retVal = null;
275:
276: int lastSlash = servletPath.lastIndexOf('/');
277: if (lastSlash >= 0) {
278: //cut off file namee
279: retVal = servletPath.substring(0, lastSlash + 1);
280: }
281: return retVal;
282: }
283:
284: protected String getPathWithoutContext(String servletPath) {
285: String retVal = null;
286:
287: int secondSlash = servletPath.indexOf('/', 1);
288: if (secondSlash >= 0) {
289: //cut off context
290: retVal = servletPath.substring(secondSlash);
291: }
292: return retVal;
293: }
294:
295: protected String getAbsolutePath(String path) throws IOException {
296: String pathWithoutContext = SSIServletRequestUtil
297: .getRelativePath(req);
298: String prefix = getPathWithoutFileName(pathWithoutContext);
299: if (prefix == null) {
300: throw new IOException(
301: "Couldn't remove filename from path: "
302: + pathWithoutContext);
303: }
304: String fullPath = prefix + path;
305: String retVal = SSIServletRequestUtil.normalize(fullPath);
306:
307: if (retVal == null) {
308: throw new IOException(
309: "Normalization yielded null on path: " + fullPath);
310: }
311: return retVal;
312: }
313:
314: protected ServletContextAndPath getServletContextAndPathFromNonVirtualPath(
315: String nonVirtualPath) throws IOException {
316: if (nonVirtualPath.startsWith("/")
317: || nonVirtualPath.startsWith("\\")) {
318: throw new IOException(
319: "A non-virtual path can't be absolute: "
320: + nonVirtualPath);
321: }
322:
323: if (nonVirtualPath.indexOf("../") >= 0) {
324: throw new IOException(
325: "A non-virtual path can't contain '../' : "
326: + nonVirtualPath);
327: }
328:
329: String path = getAbsolutePath(nonVirtualPath);
330:
331: ServletContext servletContext = servlet.getServletContext();
332: ServletContextAndPath csAndP = new ServletContextAndPath(
333: servletContext, path);
334: return csAndP;
335: }
336:
337: protected ServletContextAndPath getServletContextAndPathFromVirtualPath(
338: String virtualPath) throws IOException {
339: ServletContext servletContext = servlet.getServletContext();
340: String path = null;
341:
342: if (!virtualPath.startsWith("/")
343: && !virtualPath.startsWith("\\")) {
344: path = getAbsolutePath(virtualPath);
345: } else {
346: String normalized = SSIServletRequestUtil
347: .normalize(virtualPath);
348: if (isVirtualWebappRelative) {
349: path = normalized;
350: } else {
351: servletContext = servletContext.getContext(normalized);
352: if (servletContext == null) {
353: throw new IOException(
354: "Couldn't get context for path: "
355: + normalized);
356: }
357:
358: //If it's the root context, then there is no context element to remove, ie:
359: // '/file1.shtml' vs '/appName1/file1.shtml'
360: if (!isRootContext(servletContext)) {
361: path = getPathWithoutContext(normalized);
362: if (path == null) {
363: throw new IOException(
364: "Couldn't remove context from path: "
365: + normalized);
366: }
367: } else {
368: path = normalized;
369: }
370: }
371: }
372: return new ServletContextAndPath(servletContext, path);
373: }
374:
375: //Assumes servletContext is not-null
376: //Assumes that identity comparison will be true for the same context
377: //Assuming the above, getContext("/") will be non-null as long as the root context is accessible.
378: //If it isn't, then servletContext can't be the root context anyway, hence they will not match.
379: protected boolean isRootContext(ServletContext servletContext) {
380: return servletContext == servletContext.getContext("/");
381: }
382:
383: protected ServletContextAndPath getServletContextAndPath(
384: String originalPath, boolean virtual) throws IOException {
385: ServletContextAndPath csAndP = null;
386:
387: if (debug > 0) {
388: log("SSIServletExternalResolver.getServletContextAndPath( "
389: + originalPath + ", " + virtual + ")", null);
390: }
391: if (virtual) {
392: csAndP = getServletContextAndPathFromVirtualPath(originalPath);
393: } else {
394: csAndP = getServletContextAndPathFromNonVirtualPath(originalPath);
395: }
396: return csAndP;
397: }
398:
399: protected URLConnection getURLConnection(String originalPath,
400: boolean virtual) throws IOException {
401: ServletContextAndPath csAndP = getServletContextAndPath(
402: originalPath, virtual);
403: ServletContext context = csAndP.getServletContext();
404: String path = csAndP.getPath();
405:
406: URL url = context.getResource(path);
407: if (url == null) {
408: throw new IOException("Context did not contain resource: "
409: + path);
410: }
411: URLConnection urlConnection = url.openConnection();
412: return urlConnection;
413: }
414:
415: public long getFileLastModified(String path, boolean virtual)
416: throws IOException {
417: long lastModified = 0;
418:
419: URLConnection urlConnection = getURLConnection(path, virtual);
420: lastModified = urlConnection.getLastModified();
421: return lastModified;
422: }
423:
424: public long getFileSize(String path, boolean virtual)
425: throws IOException {
426: long fileSize = -1;
427:
428: URLConnection urlConnection = getURLConnection(path, virtual);
429: fileSize = urlConnection.getContentLength();
430: return fileSize;
431: }
432:
433: //We are making lots of unnecessary copies of the included data here. If someone ever complains that this
434: //is slow, we should connect the included stream to the print writer that SSICommand uses.
435: public String getFileText(String originalPath, boolean virtual)
436: throws IOException {
437: try {
438: ServletContextAndPath csAndP = getServletContextAndPath(
439: originalPath, virtual);
440: ServletContext context = csAndP.getServletContext();
441: String path = csAndP.getPath();
442:
443: RequestDispatcher rd = context.getRequestDispatcher(path);
444: if (rd == null) {
445: throw new IOException(
446: "Couldn't get request dispatcher for path: "
447: + path);
448: }
449: ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream();
450: ResponseIncludeWrapper responseIncludeWrapper = new ResponseIncludeWrapper(
451: res, basos);
452: rd.include(req, responseIncludeWrapper);
453:
454: //We can't assume the included servlet flushed its output
455: responseIncludeWrapper.flushOutputStreamOrWriter();
456: byte[] bytes = basos.toByteArray();
457:
458: //Assume that the default encoding is what was used to encode the bytes. Questionable.
459: String retVal = new String(bytes);
460:
461: //make an assumption that an empty response is a failure. This is a problem if a truly empty file
462: //were included, but not sure how else to tell.
463: if (retVal.equals("")) {
464: throw new IOException("Couldn't find file: " + path);
465: }
466: return retVal;
467: } catch (ServletException e) {
468: throw new IOException("Couldn't include file: "
469: + originalPath + " because of ServletException: "
470: + e.getMessage());
471: }
472: }
473:
474: protected class ServletContextAndPath {
475: protected ServletContext servletContext;
476: protected String path;
477:
478: public ServletContextAndPath(ServletContext servletContext,
479: String path) {
480: this .servletContext = servletContext;
481: this .path = path;
482: }
483:
484: public ServletContext getServletContext() {
485: return servletContext;
486: }
487:
488: public String getPath() {
489: return path;
490: }
491: }
492: }
|