001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.ssi;
018:
019: import java.io.IOException;
020: import java.net.URL;
021: import java.net.URLConnection;
022: import java.net.URLDecoder;
023: import java.util.Collection;
024: import java.util.Date;
025: import java.util.Enumeration;
026:
027: import javax.servlet.RequestDispatcher;
028: import javax.servlet.ServletContext;
029: import javax.servlet.ServletException;
030: import javax.servlet.http.HttpServlet;
031: import javax.servlet.http.HttpServletRequest;
032: import javax.servlet.http.HttpServletResponse;
033:
034: /**
035: * An implementation of SSIExternalResolver that is used with servlets.
036: *
037: * @author Dan Sandberg
038: * @version $Revision: 1.3 $, $Date: 2004/02/27 14:58:47 $
039: */
040: public class SSIServletExternalResolver implements SSIExternalResolver {
041: protected final String VARIABLE_NAMES[] = { "AUTH_TYPE",
042: "CONTENT_LENGTH", "CONTENT_TYPE", "DOCUMENT_NAME",
043: "DOCUMENT_URI", "GATEWAY_INTERFACE", "PATH_INFO",
044: "PATH_TRANSLATED", "QUERY_STRING",
045: "QUERY_STRING_UNESCAPED", "REMOTE_ADDR", "REMOTE_HOST",
046: "REMOTE_USER", "REQUEST_METHOD", "SCRIPT_NAME",
047: "SERVER_NAME", "SERVER_PORT", "SERVER_PROTOCOL",
048: "SERVER_SOFTWARE" };
049:
050: protected HttpServlet servlet;
051: protected HttpServletRequest req;
052: protected HttpServletResponse res;
053: protected boolean isVirtualWebappRelative;
054: protected int debug;
055:
056: public SSIServletExternalResolver(HttpServlet servlet,
057: HttpServletRequest req, HttpServletResponse res,
058: boolean isVirtualWebappRelative, int debug) {
059: this .servlet = servlet;
060: this .req = req;
061: this .res = res;
062: this .isVirtualWebappRelative = isVirtualWebappRelative;
063: this .debug = debug;
064: }
065:
066: public void log(String message, Throwable throwable) {
067: //We can't assume that Servlet.log( message, null )
068: //is the same as Servlet.log( message ), since API
069: //doesn't seem to say so.
070: if (throwable != null) {
071: servlet.log(message, throwable);
072: } else {
073: servlet.log(message);
074: }
075: }
076:
077: public void addVariableNames(Collection variableNames) {
078: for (int i = 0; i < VARIABLE_NAMES.length; i++) {
079: String variableName = VARIABLE_NAMES[i];
080: String variableValue = getVariableValue(variableName);
081: if (variableValue != null) {
082: variableNames.add(variableName);
083: }
084: }
085: Enumeration e = req.getAttributeNames();
086: while (e.hasMoreElements()) {
087: String name = (String) e.nextElement();
088: if (!isNameReserved(name)) {
089: variableNames.add(name);
090: }
091: }
092: }
093:
094: protected Object getReqAttributeIgnoreCase(String targetName) {
095: Object object = null;
096:
097: if (!isNameReserved(targetName)) {
098: object = req.getAttribute(targetName);
099: if (object == null) {
100: Enumeration e = req.getAttributeNames();
101: while (e.hasMoreElements()) {
102: String name = (String) e.nextElement();
103: if (targetName.equalsIgnoreCase(name)
104: && !isNameReserved(name)) {
105:
106: object = req.getAttribute(name);
107: if (object != null) {
108: break;
109: }
110: }
111: }
112: }
113: }
114: return object;
115: }
116:
117: protected boolean isNameReserved(String name) {
118: return name.startsWith("java.") || name.startsWith("javax.")
119: || name.startsWith("sun.");
120: }
121:
122: public void setVariableValue(String name, String value) {
123: if (!isNameReserved(name)) {
124: req.setAttribute(name, value);
125: }
126: }
127:
128: public String getVariableValue(String name) {
129: String retVal = null;
130:
131: Object object = getReqAttributeIgnoreCase(name);
132: if (object != null) {
133: retVal = object.toString();
134: } else {
135: retVal = getCGIVariable(name);
136: }
137: return retVal;
138: }
139:
140: protected String getCGIVariable(String name) {
141: String retVal = null;
142:
143: if (name.equalsIgnoreCase("AUTH_TYPE")) {
144: retVal = req.getAuthType();
145: } else if (name.equalsIgnoreCase("CONTENT_LENGTH")) {
146: int contentLength = req.getContentLength();
147: if (contentLength >= 0) {
148: retVal = Integer.toString(contentLength);
149: }
150: } else if (name.equalsIgnoreCase("CONTENT_TYPE")) {
151: retVal = req.getContentType();
152: } else if (name.equalsIgnoreCase("DOCUMENT_NAME")) {
153: String requestURI = req.getRequestURI();
154: retVal = requestURI
155: .substring(requestURI.lastIndexOf('/') + 1);
156: } else if (name.equalsIgnoreCase("DOCUMENT_URI")) {
157: retVal = req.getRequestURI();
158: } else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
159: retVal = "CGI/1.1";
160: } else if (name.equalsIgnoreCase("PATH_INFO")) {
161: retVal = req.getPathInfo();
162: } else if (name.equalsIgnoreCase("PATH_TRANSLATED")) {
163: retVal = req.getPathTranslated();
164: } else if (name.equalsIgnoreCase("QUERY_STRING")) {
165: //apache displays this as an empty string rather than (none)
166: retVal = nullToEmptyString(req.getQueryString());
167: } else if (name.equalsIgnoreCase("QUERY_STRING_UNESCAPED")) {
168: String queryString = req.getQueryString();
169: if (queryString != null) {
170: retVal = URLDecoder.decode(queryString);
171: }
172: } else if (name.equalsIgnoreCase("REMOTE_ADDR")) {
173: retVal = req.getRemoteAddr();
174: } else if (name.equalsIgnoreCase("REMOTE_HOST")) {
175: retVal = req.getRemoteHost();
176: } else if (name.equalsIgnoreCase("REMOTE_USER")) {
177: retVal = req.getRemoteUser();
178: } else if (name.equalsIgnoreCase("REQUEST_METHOD")) {
179: retVal = req.getMethod();
180: } else if (name.equalsIgnoreCase("SCRIPT_NAME")) {
181: retVal = req.getServletPath();
182: } else if (name.equalsIgnoreCase("SERVER_NAME")) {
183: retVal = req.getServerName();
184: } else if (name.equalsIgnoreCase("SERVER_PORT")) {
185: retVal = Integer.toString(req.getServerPort());
186: } else if (name.equalsIgnoreCase("SERVER_PROTOCOL")) {
187: retVal = req.getProtocol();
188: } else if (name.equalsIgnoreCase("SERVER_SOFTWARE")) {
189: ServletContext servletContext = servlet.getServletContext();
190: retVal = servletContext.getServerInfo();
191: }
192: return retVal;
193: }
194:
195: public Date getCurrentDate() {
196: return new Date();
197: }
198:
199: protected String nullToEmptyString(String string) {
200: String retVal = string;
201:
202: if (retVal == null) {
203: retVal = "";
204: }
205: return retVal;
206: }
207:
208: protected String getPathWithoutFileName(String servletPath) {
209: String retVal = null;
210:
211: int lastSlash = servletPath.lastIndexOf('/');
212: if (lastSlash >= 0) {
213: //cut off file namee
214: retVal = servletPath.substring(0, lastSlash + 1);
215: }
216: return retVal;
217: }
218:
219: protected String getPathWithoutContext(String servletPath) {
220: String retVal = null;
221:
222: int secondSlash = servletPath.indexOf('/', 1);
223: if (secondSlash >= 0) {
224: //cut off context
225: retVal = servletPath.substring(secondSlash);
226: }
227: return retVal;
228: }
229:
230: protected String getAbsolutePath(String path) throws IOException {
231: String pathWithoutContext = SSIServletRequestUtil
232: .getRelativePath(req);
233: String prefix = getPathWithoutFileName(pathWithoutContext);
234: if (prefix == null) {
235: throw new IOException(
236: "Couldn't remove filename from path: "
237: + pathWithoutContext);
238: }
239: String fullPath = prefix + path;
240: String retVal = SSIServletRequestUtil.normalize(fullPath);
241:
242: if (retVal == null) {
243: throw new IOException(
244: "Normalization yielded null on path: " + fullPath);
245: }
246: return retVal;
247: }
248:
249: protected ServletContextAndPath getServletContextAndPathFromNonVirtualPath(
250: String nonVirtualPath) throws IOException {
251: if (nonVirtualPath.startsWith("/")
252: || nonVirtualPath.startsWith("\\")) {
253: throw new IOException(
254: "A non-virtual path can't be absolute: "
255: + nonVirtualPath);
256: }
257:
258: if (nonVirtualPath.indexOf("../") >= 0) {
259: throw new IOException(
260: "A non-virtual path can't contain '../' : "
261: + nonVirtualPath);
262: }
263:
264: String path = getAbsolutePath(nonVirtualPath);
265:
266: ServletContext servletContext = servlet.getServletContext();
267: ServletContextAndPath csAndP = new ServletContextAndPath(
268: servletContext, path);
269: return csAndP;
270: }
271:
272: protected ServletContextAndPath getServletContextAndPathFromVirtualPath(
273: String virtualPath) throws IOException {
274: ServletContext servletContext = servlet.getServletContext();
275: String path = null;
276:
277: if (!virtualPath.startsWith("/")
278: && !virtualPath.startsWith("\\")) {
279: path = getAbsolutePath(virtualPath);
280: } else {
281: String normalized = SSIServletRequestUtil
282: .normalize(virtualPath);
283: if (isVirtualWebappRelative) {
284: path = normalized;
285: } else {
286: servletContext = servletContext.getContext(normalized);
287: if (servletContext == null) {
288: throw new IOException(
289: "Couldn't get context for path: "
290: + normalized);
291: }
292:
293: //If it's the root context, then there is no context element to remove, ie:
294: // '/file1.shtml' vs '/appName1/file1.shtml'
295: if (!isRootContext(servletContext)) {
296: path = getPathWithoutContext(normalized);
297: if (path == null) {
298: throw new IOException(
299: "Couldn't remove context from path: "
300: + normalized);
301: }
302: } else {
303: path = normalized;
304: }
305: }
306: }
307: return new ServletContextAndPath(servletContext, path);
308: }
309:
310: //Assumes servletContext is not-null
311: //Assumes that identity comparison will be true for the same context
312: //Assuming the above, getContext("/") will be non-null as long as the root context is accessible.
313: //If it isn't, then servletContext can't be the root context anyway, hence they will not match.
314: protected boolean isRootContext(ServletContext servletContext) {
315: return servletContext == servletContext.getContext("/");
316: }
317:
318: protected ServletContextAndPath getServletContextAndPath(
319: String originalPath, boolean virtual) throws IOException {
320: ServletContextAndPath csAndP = null;
321:
322: if (debug > 0) {
323: log("SSIServletExternalResolver.getServletContextAndPath( "
324: + originalPath + ", " + virtual + ")", null);
325: }
326: if (virtual) {
327: csAndP = getServletContextAndPathFromVirtualPath(originalPath);
328: } else {
329: csAndP = getServletContextAndPathFromNonVirtualPath(originalPath);
330: }
331: return csAndP;
332: }
333:
334: protected URLConnection getURLConnection(String originalPath,
335: boolean virtual) throws IOException {
336: ServletContextAndPath csAndP = getServletContextAndPath(
337: originalPath, virtual);
338: ServletContext context = csAndP.getServletContext();
339: String path = csAndP.getPath();
340:
341: URL url = context.getResource(path);
342: if (url == null) {
343: throw new IOException("Context did not contain resource: "
344: + path);
345: }
346: URLConnection urlConnection = url.openConnection();
347: return urlConnection;
348: }
349:
350: public long getFileLastModified(String path, boolean virtual)
351: throws IOException {
352: long lastModified = 0;
353:
354: URLConnection urlConnection = getURLConnection(path, virtual);
355: lastModified = urlConnection.getLastModified();
356: return lastModified;
357: }
358:
359: public long getFileSize(String path, boolean virtual)
360: throws IOException {
361: long fileSize = -1;
362:
363: URLConnection urlConnection = getURLConnection(path, virtual);
364: fileSize = urlConnection.getContentLength();
365: return fileSize;
366: }
367:
368: //We are making lots of unnecessary copies of the included data here. If someone ever complains that this
369: //is slow, we should connect the included stream to the print writer that SSICommand uses.
370: public String getFileText(String originalPath, boolean virtual)
371: throws IOException {
372: try {
373: ServletContextAndPath csAndP = getServletContextAndPath(
374: originalPath, virtual);
375: ServletContext context = csAndP.getServletContext();
376: String path = csAndP.getPath();
377:
378: RequestDispatcher rd = context.getRequestDispatcher(path);
379: if (rd == null) {
380: throw new IOException(
381: "Couldn't get request dispatcher for path: "
382: + path);
383: }
384: ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream();
385: ResponseIncludeWrapper responseIncludeWrapper = new ResponseIncludeWrapper(
386: res, basos);
387: rd.include(req, responseIncludeWrapper);
388:
389: //We can't assume the included servlet flushed its output
390: responseIncludeWrapper.flushOutputStreamOrWriter();
391: byte[] bytes = basos.toByteArray();
392:
393: //Assume that the default encoding is what was used to encode the bytes. Questionable.
394: String retVal = new String(bytes);
395:
396: //make an assumption that an empty response is a failure. This is a problem if a truly empty file
397: //were included, but not sure how else to tell.
398: if (retVal.equals("")) {
399: throw new IOException("Couldn't find file: " + path);
400: }
401: return retVal;
402: } catch (ServletException e) {
403: throw new IOException("Couldn't include file: "
404: + originalPath + " because of ServletException: "
405: + e.getMessage());
406: }
407: }
408:
409: protected class ServletContextAndPath {
410: protected ServletContext servletContext;
411: protected String path;
412:
413: public ServletContextAndPath(ServletContext servletContext,
414: String path) {
415: this .servletContext = servletContext;
416: this .path = path;
417: }
418:
419: public ServletContext getServletContext() {
420: return servletContext;
421: }
422:
423: public String getPath() {
424: return path;
425: }
426: }
427: }
|