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.UnsupportedEncodingException;
021: import java.net.URL;
022: import java.net.URLConnection;
023: import java.net.URLDecoder;
024: import java.util.Collection;
025: import java.util.Date;
026: import java.util.Enumeration;
027: import javax.servlet.RequestDispatcher;
028: import javax.servlet.ServletContext;
029: import javax.servlet.ServletException;
030: import javax.servlet.http.HttpServletRequest;
031: import javax.servlet.http.HttpServletResponse;
032: import org.apache.catalina.connector.Request;
033: import org.apache.coyote.Constants;
034:
035: /**
036: * An implementation of SSIExternalResolver that is used with servlets.
037: *
038: * @author Dan Sandberg
039: * @author David Becker
040: * @version $Revision: 531303 $, $Date: 2007-04-23 02:24:01 +0200 (lun., 23 avr. 2007) $
041: */
042: public class SSIServletExternalResolver implements SSIExternalResolver {
043: protected final String VARIABLE_NAMES[] = { "AUTH_TYPE",
044: "CONTENT_LENGTH", "CONTENT_TYPE", "DOCUMENT_NAME",
045: "DOCUMENT_URI", "GATEWAY_INTERFACE", "HTTP_ACCEPT",
046: "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE",
047: "HTTP_CONNECTION", "HTTP_HOST", "HTTP_REFERER",
048: "HTTP_USER_AGENT", "PATH_INFO", "PATH_TRANSLATED",
049: "QUERY_STRING", "QUERY_STRING_UNESCAPED", "REMOTE_ADDR",
050: "REMOTE_HOST", "REMOTE_PORT", "REMOTE_USER",
051: "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_FILENAME",
052: "SCRIPT_NAME", "SERVER_ADDR", "SERVER_NAME", "SERVER_PORT",
053: "SERVER_PROTOCOL", "SERVER_SOFTWARE", "UNIQUE_ID" };
054: protected ServletContext context;
055: protected HttpServletRequest req;
056: protected HttpServletResponse res;
057: protected boolean isVirtualWebappRelative;
058: protected int debug;
059: protected String inputEncoding;
060:
061: public SSIServletExternalResolver(ServletContext context,
062: HttpServletRequest req, HttpServletResponse res,
063: boolean isVirtualWebappRelative, int debug,
064: String inputEncoding) {
065: this .context = context;
066: this .req = req;
067: this .res = res;
068: this .isVirtualWebappRelative = isVirtualWebappRelative;
069: this .debug = debug;
070: this .inputEncoding = inputEncoding;
071: }
072:
073: public void log(String message, Throwable throwable) {
074: //We can't assume that Servlet.log( message, null )
075: //is the same as Servlet.log( message ), since API
076: //doesn't seem to say so.
077: if (throwable != null) {
078: context.log(message, throwable);
079: } else {
080: context.log(message);
081: }
082: }
083:
084: public void addVariableNames(Collection variableNames) {
085: for (int i = 0; i < VARIABLE_NAMES.length; i++) {
086: String variableName = VARIABLE_NAMES[i];
087: String variableValue = getVariableValue(variableName);
088: if (variableValue != null) {
089: variableNames.add(variableName);
090: }
091: }
092: Enumeration e = req.getAttributeNames();
093: while (e.hasMoreElements()) {
094: String name = (String) e.nextElement();
095: if (!isNameReserved(name)) {
096: variableNames.add(name);
097: }
098: }
099: }
100:
101: protected Object getReqAttributeIgnoreCase(String targetName) {
102: Object object = null;
103: if (!isNameReserved(targetName)) {
104: object = req.getAttribute(targetName);
105: if (object == null) {
106: Enumeration e = req.getAttributeNames();
107: while (e.hasMoreElements()) {
108: String name = (String) e.nextElement();
109: if (targetName.equalsIgnoreCase(name)
110: && !isNameReserved(name)) {
111: object = req.getAttribute(name);
112: if (object != null) {
113: break;
114: }
115: }
116: }
117: }
118: }
119: return object;
120: }
121:
122: protected boolean isNameReserved(String name) {
123: return name.startsWith("java.") || name.startsWith("javax.")
124: || name.startsWith("sun.");
125: }
126:
127: public void setVariableValue(String name, String value) {
128: if (!isNameReserved(name)) {
129: req.setAttribute(name, value);
130: }
131: }
132:
133: public String getVariableValue(String name) {
134: String retVal = null;
135: Object object = getReqAttributeIgnoreCase(name);
136: if (object != null) {
137: retVal = object.toString();
138: } else {
139: retVal = getCGIVariable(name);
140: }
141: return retVal;
142: }
143:
144: protected String getCGIVariable(String name) {
145: String retVal = null;
146: String[] nameParts = name.toUpperCase().split("_");
147: int requiredParts = 2;
148: if (nameParts.length == 1) {
149: if (nameParts[0].equals("PATH")) {
150: requiredParts = 1;
151: retVal = null; // Not implemented
152: }
153: } else if (nameParts[0].equals("AUTH")) {
154: if (nameParts[1].equals("TYPE")) {
155: retVal = req.getAuthType();
156: }
157: } else if (nameParts[0].equals("CONTENT")) {
158: if (nameParts[1].equals("LENGTH")) {
159: int contentLength = req.getContentLength();
160: if (contentLength >= 0) {
161: retVal = Integer.toString(contentLength);
162: }
163: } else if (nameParts[1].equals("TYPE")) {
164: retVal = req.getContentType();
165: }
166: } else if (nameParts[0].equals("DOCUMENT")) {
167: if (nameParts[1].equals("NAME")) {
168: String requestURI = req.getRequestURI();
169: retVal = requestURI.substring(requestURI
170: .lastIndexOf('/') + 1);
171: } else if (nameParts[1].equals("URI")) {
172: retVal = req.getRequestURI();
173: }
174: } else if (name.equalsIgnoreCase("GATEWAY_INTERFACE")) {
175: retVal = "CGI/1.1";
176: } else if (nameParts[0].equals("HTTP")) {
177: if (nameParts[1].equals("ACCEPT")) {
178: String accept = null;
179: if (nameParts.length == 2) {
180: accept = "Accept";
181: } else if (nameParts[2].equals("ENCODING")) {
182: requiredParts = 3;
183: accept = "Accept-Encoding";
184: } else if (nameParts[2].equals("LANGUAGE")) {
185: requiredParts = 3;
186: accept = "Accept-Language";
187: }
188: if (accept != null) {
189: Enumeration acceptHeaders = req.getHeaders(accept);
190: if (acceptHeaders != null)
191: if (acceptHeaders.hasMoreElements()) {
192: StringBuffer rv = new StringBuffer(
193: (String) acceptHeaders
194: .nextElement());
195: while (acceptHeaders.hasMoreElements()) {
196: rv.append(", ");
197: rv.append((String) acceptHeaders
198: .nextElement());
199: }
200: retVal = rv.toString();
201: }
202: }
203: } else if (nameParts[1].equals("CONNECTION")) {
204: retVal = req.getHeader("Connection");
205: } else if (nameParts[1].equals("HOST")) {
206: retVal = req.getHeader("Host");
207: } else if (nameParts[1].equals("REFERER")) {
208: retVal = req.getHeader("Referer");
209: } else if (nameParts[1].equals("USER"))
210: if (nameParts.length == 3)
211: if (nameParts[2].equals("AGENT")) {
212: requiredParts = 3;
213: retVal = req.getHeader("User-Agent");
214: }
215:
216: } else if (nameParts[0].equals("PATH")) {
217: if (nameParts[1].equals("INFO")) {
218: retVal = req.getPathInfo();
219: } else if (nameParts[1].equals("TRANSLATED")) {
220: retVal = req.getPathTranslated();
221: }
222: } else if (nameParts[0].equals("QUERY")) {
223: if (nameParts[1].equals("STRING")) {
224: String queryString = req.getQueryString();
225: if (nameParts.length == 2) {
226: //apache displays this as an empty string rather than (none)
227: retVal = nullToEmptyString(queryString);
228: } else if (nameParts[2].equals("UNESCAPED")) {
229: requiredParts = 3;
230: if (queryString != null) {
231: // Use default as a last resort
232: String queryStringEncoding = Constants.DEFAULT_CHARACTER_ENCODING;
233:
234: String uriEncoding = null;
235: boolean useBodyEncodingForURI = false;
236:
237: // Get encoding settings from request / connector if
238: // possible
239: String requestEncoding = req
240: .getCharacterEncoding();
241: if (req instanceof Request) {
242: uriEncoding = ((Request) req)
243: .getConnector().getURIEncoding();
244: useBodyEncodingForURI = ((Request) req)
245: .getConnector()
246: .getUseBodyEncodingForURI();
247: }
248:
249: // If valid, apply settings from request / connector
250: if (uriEncoding != null) {
251: queryStringEncoding = uriEncoding;
252: } else if (useBodyEncodingForURI) {
253: if (requestEncoding != null) {
254: queryStringEncoding = requestEncoding;
255: }
256: }
257:
258: try {
259: retVal = URLDecoder.decode(queryString,
260: queryStringEncoding);
261: } catch (UnsupportedEncodingException e) {
262: retVal = queryString;
263: }
264: }
265: }
266: }
267: } else if (nameParts[0].equals("REMOTE")) {
268: if (nameParts[1].equals("ADDR")) {
269: retVal = req.getRemoteAddr();
270: } else if (nameParts[1].equals("HOST")) {
271: retVal = req.getRemoteHost();
272: } else if (nameParts[1].equals("IDENT")) {
273: retVal = null; // Not implemented
274: } else if (nameParts[1].equals("PORT")) {
275: retVal = Integer.toString(req.getRemotePort());
276: } else if (nameParts[1].equals("USER")) {
277: retVal = req.getRemoteUser();
278: }
279: } else if (nameParts[0].equals("REQUEST")) {
280: if (nameParts[1].equals("METHOD")) {
281: retVal = req.getMethod();
282: } else if (nameParts[1].equals("URI")) {
283: // If this is an error page, get the original URI
284: retVal = (String) req
285: .getAttribute("javax.servlet.forward.request_uri");
286: if (retVal == null)
287: retVal = req.getRequestURI();
288: }
289: } else if (nameParts[0].equals("SCRIPT")) {
290: String scriptName = req.getServletPath();
291: if (nameParts[1].equals("FILENAME")) {
292: retVal = context.getRealPath(scriptName);
293: } else if (nameParts[1].equals("NAME")) {
294: retVal = scriptName;
295: }
296: } else if (nameParts[0].equals("SERVER")) {
297: if (nameParts[1].equals("ADDR")) {
298: retVal = req.getLocalAddr();
299: }
300: if (nameParts[1].equals("NAME")) {
301: retVal = req.getServerName();
302: } else if (nameParts[1].equals("PORT")) {
303: retVal = Integer.toString(req.getServerPort());
304: } else if (nameParts[1].equals("PROTOCOL")) {
305: retVal = req.getProtocol();
306: } else if (nameParts[1].equals("SOFTWARE")) {
307: StringBuffer rv = new StringBuffer(context
308: .getServerInfo());
309: rv.append(" ");
310: rv.append(System.getProperty("java.vm.name"));
311: rv.append("/");
312: rv.append(System.getProperty("java.vm.version"));
313: rv.append(" ");
314: rv.append(System.getProperty("os.name"));
315: retVal = rv.toString();
316: }
317: } else if (name.equalsIgnoreCase("UNIQUE_ID")) {
318: retVal = req.getRequestedSessionId();
319: }
320: if (requiredParts != nameParts.length)
321: return null;
322: return retVal;
323: }
324:
325: public Date getCurrentDate() {
326: return new Date();
327: }
328:
329: protected String nullToEmptyString(String string) {
330: String retVal = string;
331: if (retVal == null) {
332: retVal = "";
333: }
334: return retVal;
335: }
336:
337: protected String getPathWithoutFileName(String servletPath) {
338: String retVal = null;
339: int lastSlash = servletPath.lastIndexOf('/');
340: if (lastSlash >= 0) {
341: //cut off file namee
342: retVal = servletPath.substring(0, lastSlash + 1);
343: }
344: return retVal;
345: }
346:
347: protected String getPathWithoutContext(String servletPath) {
348: String retVal = null;
349: int secondSlash = servletPath.indexOf('/', 1);
350: if (secondSlash >= 0) {
351: //cut off context
352: retVal = servletPath.substring(secondSlash);
353: }
354: return retVal;
355: }
356:
357: protected String getAbsolutePath(String path) throws IOException {
358: String pathWithoutContext = SSIServletRequestUtil
359: .getRelativePath(req);
360: String prefix = getPathWithoutFileName(pathWithoutContext);
361: if (prefix == null) {
362: throw new IOException(
363: "Couldn't remove filename from path: "
364: + pathWithoutContext);
365: }
366: String fullPath = prefix + path;
367: String retVal = SSIServletRequestUtil.normalize(fullPath);
368: if (retVal == null) {
369: throw new IOException(
370: "Normalization yielded null on path: " + fullPath);
371: }
372: return retVal;
373: }
374:
375: protected ServletContextAndPath getServletContextAndPathFromNonVirtualPath(
376: String nonVirtualPath) throws IOException {
377: if (nonVirtualPath.startsWith("/")
378: || nonVirtualPath.startsWith("\\")) {
379: throw new IOException(
380: "A non-virtual path can't be absolute: "
381: + nonVirtualPath);
382: }
383: if (nonVirtualPath.indexOf("../") >= 0) {
384: throw new IOException(
385: "A non-virtual path can't contain '../' : "
386: + nonVirtualPath);
387: }
388: String path = getAbsolutePath(nonVirtualPath);
389: ServletContextAndPath csAndP = new ServletContextAndPath(
390: context, path);
391: return csAndP;
392: }
393:
394: protected ServletContextAndPath getServletContextAndPathFromVirtualPath(
395: String virtualPath) throws IOException {
396:
397: if (!virtualPath.startsWith("/")
398: && !virtualPath.startsWith("\\")) {
399: return new ServletContextAndPath(context,
400: getAbsolutePath(virtualPath));
401: } else {
402: String normalized = SSIServletRequestUtil
403: .normalize(virtualPath);
404: if (isVirtualWebappRelative) {
405: return new ServletContextAndPath(context, normalized);
406: } else {
407: ServletContext normContext = context
408: .getContext(normalized);
409: if (normContext == null) {
410: throw new IOException(
411: "Couldn't get context for path: "
412: + normalized);
413: }
414: //If it's the root context, then there is no context element
415: // to remove,
416: // ie:
417: // '/file1.shtml' vs '/appName1/file1.shtml'
418: if (!isRootContext(normContext)) {
419: String noContext = getPathWithoutContext(normalized);
420: if (noContext == null) {
421: throw new IOException(
422: "Couldn't remove context from path: "
423: + normalized);
424: }
425: return new ServletContextAndPath(normContext,
426: noContext);
427: } else {
428: return new ServletContextAndPath(normContext,
429: normalized);
430: }
431: }
432: }
433: }
434:
435: //Assumes servletContext is not-null
436: //Assumes that identity comparison will be true for the same context
437: //Assuming the above, getContext("/") will be non-null as long as the root
438: // context is
439: // accessible.
440: //If it isn't, then servletContext can't be the root context anyway, hence
441: // they will
442: // not match.
443: protected boolean isRootContext(ServletContext servletContext) {
444: return servletContext == servletContext.getContext("/");
445: }
446:
447: protected ServletContextAndPath getServletContextAndPath(
448: String originalPath, boolean virtual) throws IOException {
449: ServletContextAndPath csAndP = null;
450: if (debug > 0) {
451: log("SSIServletExternalResolver.getServletContextAndPath( "
452: + originalPath + ", " + virtual + ")", null);
453: }
454: if (virtual) {
455: csAndP = getServletContextAndPathFromVirtualPath(originalPath);
456: } else {
457: csAndP = getServletContextAndPathFromNonVirtualPath(originalPath);
458: }
459: return csAndP;
460: }
461:
462: protected URLConnection getURLConnection(String originalPath,
463: boolean virtual) throws IOException {
464: ServletContextAndPath csAndP = getServletContextAndPath(
465: originalPath, virtual);
466: ServletContext context = csAndP.getServletContext();
467: String path = csAndP.getPath();
468: URL url = context.getResource(path);
469: if (url == null) {
470: throw new IOException("Context did not contain resource: "
471: + path);
472: }
473: URLConnection urlConnection = url.openConnection();
474: return urlConnection;
475: }
476:
477: public long getFileLastModified(String path, boolean virtual)
478: throws IOException {
479: long lastModified = 0;
480: try {
481: URLConnection urlConnection = getURLConnection(path,
482: virtual);
483: lastModified = urlConnection.getLastModified();
484: } catch (IOException e) {
485: // Ignore this. It will always fail for non-file based includes
486: }
487: return lastModified;
488: }
489:
490: public long getFileSize(String path, boolean virtual)
491: throws IOException {
492: long fileSize = -1;
493: try {
494: URLConnection urlConnection = getURLConnection(path,
495: virtual);
496: fileSize = urlConnection.getContentLength();
497: } catch (IOException e) {
498: // Ignore this. It will always fail for non-file based includes
499: }
500: return fileSize;
501: }
502:
503: //We are making lots of unnecessary copies of the included data here. If
504: //someone ever complains that this is slow, we should connect the included
505: // stream to the print writer that SSICommand uses.
506: public String getFileText(String originalPath, boolean virtual)
507: throws IOException {
508: try {
509: ServletContextAndPath csAndP = getServletContextAndPath(
510: originalPath, virtual);
511: ServletContext context = csAndP.getServletContext();
512: String path = csAndP.getPath();
513: RequestDispatcher rd = context.getRequestDispatcher(path);
514: if (rd == null) {
515: throw new IOException(
516: "Couldn't get request dispatcher for path: "
517: + path);
518: }
519: ByteArrayServletOutputStream basos = new ByteArrayServletOutputStream();
520: ResponseIncludeWrapper responseIncludeWrapper = new ResponseIncludeWrapper(
521: context, req, res, basos);
522: rd.include(req, responseIncludeWrapper);
523: //We can't assume the included servlet flushed its output
524: responseIncludeWrapper.flushOutputStreamOrWriter();
525: byte[] bytes = basos.toByteArray();
526:
527: //Assume platform default encoding unless otherwise specified
528: String retVal;
529: if (inputEncoding == null) {
530: retVal = new String(bytes);
531: } else {
532: retVal = new String(bytes, inputEncoding);
533: }
534:
535: //make an assumption that an empty response is a failure. This is
536: // a problem
537: // if a truly empty file
538: //were included, but not sure how else to tell.
539: if (retVal.equals("")
540: && !req.getMethod().equalsIgnoreCase(
541: org.apache.coyote.http11.Constants.HEAD)) {
542: throw new IOException("Couldn't find file: " + path);
543: }
544: return retVal;
545: } catch (ServletException e) {
546: throw new IOException("Couldn't include file: "
547: + originalPath + " because of ServletException: "
548: + e.getMessage());
549: }
550: }
551:
552: protected class ServletContextAndPath {
553: protected ServletContext servletContext;
554: protected String path;
555:
556: public ServletContextAndPath(ServletContext servletContext,
557: String path) {
558: this .servletContext = servletContext;
559: this .path = path;
560: }
561:
562: public ServletContext getServletContext() {
563: return servletContext;
564: }
565:
566: public String getPath() {
567: return path;
568: }
569: }
570: }
|