0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.servlets;
0019:
0020: import java.io.BufferedOutputStream;
0021: import java.io.BufferedReader;
0022: import java.io.File;
0023: import java.io.FileOutputStream;
0024: import java.io.IOException;
0025: import java.io.InputStream;
0026: import java.io.InputStreamReader;
0027: import java.io.OutputStream;
0028: import java.io.UnsupportedEncodingException;
0029: import java.net.URLDecoder;
0030: import java.util.ArrayList;
0031: import java.util.Date;
0032: import java.util.Enumeration;
0033: import java.util.Hashtable;
0034: import java.util.Locale;
0035: import java.util.StringTokenizer;
0036: import java.util.Vector;
0037:
0038: import javax.servlet.ServletConfig;
0039: import javax.servlet.ServletContext;
0040: import javax.servlet.ServletException;
0041: import javax.servlet.ServletOutputStream;
0042: import javax.servlet.UnavailableException;
0043: import javax.servlet.http.Cookie;
0044: import javax.servlet.http.HttpServlet;
0045: import javax.servlet.http.HttpServletRequest;
0046: import javax.servlet.http.HttpServletResponse;
0047: import javax.servlet.http.HttpSession;
0048:
0049: import org.apache.catalina.Globals;
0050: import org.apache.catalina.util.IOTools;
0051:
0052: /**
0053: * CGI-invoking servlet for web applications, used to execute scripts which
0054: * comply to the Common Gateway Interface (CGI) specification and are named
0055: * in the path-info used to invoke this servlet.
0056: *
0057: * <p>
0058: * <i>Note: This code compiles and even works for simple CGI cases.
0059: * Exhaustive testing has not been done. Please consider it beta
0060: * quality. Feedback is appreciated to the author (see below).</i>
0061: * </p>
0062: * <p>
0063: *
0064: * <b>Example</b>:<br>
0065: * If an instance of this servlet was mapped (using
0066: * <code><web-app>/WEB-INF/web.xml</code>) to:
0067: * </p>
0068: * <p>
0069: * <code>
0070: * <web-app>/cgi-bin/*
0071: * </code>
0072: * </p>
0073: * <p>
0074: * then the following request:
0075: * </p>
0076: * <p>
0077: * <code>
0078: * http://localhost:8080/<web-app>/cgi-bin/dir1/script/pathinfo1
0079: * </code>
0080: * </p>
0081: * <p>
0082: * would result in the execution of the script
0083: * </p>
0084: * <p>
0085: * <code>
0086: * <web-app-root>/WEB-INF/cgi/dir1/script
0087: * </code>
0088: * </p>
0089: * <p>
0090: * with the script's <code>PATH_INFO</code> set to <code>/pathinfo1</code>.
0091: * </p>
0092: * <p>
0093: * Recommendation: House all your CGI scripts under
0094: * <code><webapp>/WEB-INF/cgi</code>. This will ensure that you do not
0095: * accidentally expose your cgi scripts' code to the outside world and that
0096: * your cgis will be cleanly ensconced underneath the WEB-INF (i.e.,
0097: * non-content) area.
0098: * </p>
0099: * <p>
0100: * The default CGI location is mentioned above. You have the flexibility to
0101: * put CGIs wherever you want, however:
0102: * </p>
0103: * <p>
0104: * The CGI search path will start at
0105: * webAppRootDir + File.separator + cgiPathPrefix
0106: * (or webAppRootDir alone if cgiPathPrefix is
0107: * null).
0108: * </p>
0109: * <p>
0110: * cgiPathPrefix is defined by setting
0111: * this servlet's cgiPathPrefix init parameter
0112: * </p>
0113: *
0114: * <p>
0115: *
0116: * <B>CGI Specification</B>:<br> derived from
0117: * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
0118: * A work-in-progress & expired Internet Draft. Note no actual RFC describing
0119: * the CGI specification exists. Where the behavior of this servlet differs
0120: * from the specification cited above, it is either documented here, a bug,
0121: * or an instance where the specification cited differs from Best
0122: * Community Practice (BCP).
0123: * Such instances should be well-documented here. Please email the
0124: * <a href="mailto:tomcat-dev@jakarta.apache.org">Jakarta Tomcat group [tomcat-dev@jakarta.apache.org]</a>
0125: * with amendments.
0126: *
0127: * </p>
0128: * <p>
0129: *
0130: * <b>Canonical metavariables</b>:<br>
0131: * The CGI specification defines the following canonical metavariables:
0132: * <br>
0133: * [excerpt from CGI specification]
0134: * <PRE>
0135: * AUTH_TYPE
0136: * CONTENT_LENGTH
0137: * CONTENT_TYPE
0138: * GATEWAY_INTERFACE
0139: * PATH_INFO
0140: * PATH_TRANSLATED
0141: * QUERY_STRING
0142: * REMOTE_ADDR
0143: * REMOTE_HOST
0144: * REMOTE_IDENT
0145: * REMOTE_USER
0146: * REQUEST_METHOD
0147: * SCRIPT_NAME
0148: * SERVER_NAME
0149: * SERVER_PORT
0150: * SERVER_PROTOCOL
0151: * SERVER_SOFTWARE
0152: * </PRE>
0153: * <p>
0154: * Metavariables with names beginning with the protocol name (<EM>e.g.</EM>,
0155: * "HTTP_ACCEPT") are also canonical in their description of request header
0156: * fields. The number and meaning of these fields may change independently
0157: * of this specification. (See also section 6.1.5 [of the CGI specification].)
0158: * </p>
0159: * [end excerpt]
0160: *
0161: * </p>
0162: * <h2> Implementation notes</h2>
0163: * <p>
0164: *
0165: * <b>standard input handling</b>: If your script accepts standard input,
0166: * then the client must start sending input within a certain timeout period,
0167: * otherwise the servlet will assume no input is coming and carry on running
0168: * the script. The script's the standard input will be closed and handling of
0169: * any further input from the client is undefined. Most likely it will be
0170: * ignored. If this behavior becomes undesirable, then this servlet needs
0171: * to be enhanced to handle threading of the spawned process' stdin, stdout,
0172: * and stderr (which should not be too hard).
0173: * <br>
0174: * If you find your cgi scripts are timing out receiving input, you can set
0175: * the init parameter <code></code> of your webapps' cgi-handling servlet
0176: * to be
0177: * </p>
0178: * <p>
0179: *
0180: * <b>Metavariable Values</b>: According to the CGI specificion,
0181: * implementations may choose to represent both null or missing values in an
0182: * implementation-specific manner, but must define that manner. This
0183: * implementation chooses to always define all required metavariables, but
0184: * set the value to "" for all metavariables whose value is either null or
0185: * undefined. PATH_TRANSLATED is the sole exception to this rule, as per the
0186: * CGI Specification.
0187: *
0188: * </p>
0189: * <p>
0190: *
0191: * <b>NPH -- Non-parsed-header implementation</b>: This implementation does
0192: * not support the CGI NPH concept, whereby server ensures that the data
0193: * supplied to the script are preceisely as supplied by the client and
0194: * unaltered by the server.
0195: * </p>
0196: * <p>
0197: * The function of a servlet container (including Tomcat) is specifically
0198: * designed to parse and possible alter CGI-specific variables, and as
0199: * such makes NPH functionality difficult to support.
0200: * </p>
0201: * <p>
0202: * The CGI specification states that compliant servers MAY support NPH output.
0203: * It does not state servers MUST support NPH output to be unconditionally
0204: * compliant. Thus, this implementation maintains unconditional compliance
0205: * with the specification though NPH support is not present.
0206: * </p>
0207: * <p>
0208: *
0209: * The CGI specification is located at
0210: * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
0211: *
0212: * </p>
0213: * <p>
0214: * <h3>TODO:</h3>
0215: * <ul>
0216: * <li> Support for setting headers (for example, Location headers don't work)
0217: * <li> Support for collapsing multiple header lines (per RFC 2616)
0218: * <li> Ensure handling of POST method does not interfere with 2.3 Filters
0219: * <li> Refactor some debug code out of core
0220: * <li> Ensure header handling preserves encoding
0221: * <li> Possibly rewrite CGIRunner.run()?
0222: * <li> Possibly refactor CGIRunner and CGIEnvironment as non-inner classes?
0223: * <li> Document handling of cgi stdin when there is no stdin
0224: * <li> Revisit IOException handling in CGIRunner.run()
0225: * <li> Better documentation
0226: * <li> Confirm use of ServletInputStream.available() in CGIRunner.run() is
0227: * not needed
0228: * <li> Make checking for "." and ".." in servlet & cgi PATH_INFO less
0229: * draconian
0230: * <li> [add more to this TODO list]
0231: * </ul>
0232: * </p>
0233: *
0234: * @author Martin T Dengler [root@martindengler.com]
0235: * @author Amy Roh
0236: * @version $Revision: 543681 $, $Date: 2007-06-02 02:42:59 +0200 (sam., 02 juin 2007) $
0237: * @since Tomcat 4.0
0238: *
0239: */
0240:
0241: public final class CGIServlet extends HttpServlet {
0242:
0243: /* some vars below copied from Craig R. McClanahan's InvokerServlet */
0244:
0245: /** the debugging detail level for this servlet. */
0246: private int debug = 0;
0247:
0248: /**
0249: * The CGI search path will start at
0250: * webAppRootDir + File.separator + cgiPathPrefix
0251: * (or webAppRootDir alone if cgiPathPrefix is
0252: * null)
0253: */
0254: private String cgiPathPrefix = null;
0255:
0256: /** the executable to use with the script */
0257: private String cgiExecutable = "perl";
0258:
0259: /** the encoding to use for parameters */
0260: private String parameterEncoding = System.getProperty(
0261: "file.encoding", "UTF-8");
0262:
0263: /** object used to ensure multiple threads don't try to expand same file */
0264: static Object expandFileLock = new Object();
0265:
0266: /** the shell environment variables to be passed to the CGI script */
0267: static Hashtable<String, String> shellEnv = new Hashtable<String, String>();
0268:
0269: /**
0270: * Sets instance variables.
0271: * <P>
0272: * Modified from Craig R. McClanahan's InvokerServlet
0273: * </P>
0274: *
0275: * @param config a <code>ServletConfig</code> object
0276: * containing the servlet's
0277: * configuration and initialization
0278: * parameters
0279: *
0280: * @exception ServletException if an exception has occurred that
0281: * interferes with the servlet's normal
0282: * operation
0283: */
0284: public void init(ServletConfig config) throws ServletException {
0285:
0286: super .init(config);
0287:
0288: // Verify that we were not accessed using the invoker servlet
0289: String servletName = getServletConfig().getServletName();
0290: if (servletName == null)
0291: servletName = "";
0292: if (servletName.startsWith("org.apache.catalina.INVOKER."))
0293: throw new UnavailableException(
0294: "Cannot invoke CGIServlet through the invoker");
0295:
0296: // Set our properties from the initialization parameters
0297: if (getServletConfig().getInitParameter("debug") != null)
0298: debug = Integer.parseInt(getServletConfig()
0299: .getInitParameter("debug"));
0300: cgiPathPrefix = getServletConfig().getInitParameter(
0301: "cgiPathPrefix");
0302: boolean passShellEnvironment = Boolean.valueOf(
0303: getServletConfig().getInitParameter(
0304: "passShellEnvironment")).booleanValue();
0305:
0306: if (passShellEnvironment) {
0307: try {
0308: shellEnv.putAll(getShellEnvironment());
0309: } catch (IOException ioe) {
0310: ServletException e = new ServletException(
0311: "Unable to read shell environment variables",
0312: ioe);
0313: throw e;
0314: }
0315: }
0316:
0317: if (getServletConfig().getInitParameter("executable") != null) {
0318: cgiExecutable = getServletConfig().getInitParameter(
0319: "executable");
0320: }
0321:
0322: if (getServletConfig().getInitParameter("parameterEncoding") != null) {
0323: parameterEncoding = getServletConfig().getInitParameter(
0324: "parameterEncoding");
0325: }
0326:
0327: }
0328:
0329: /**
0330: * Prints out important Servlet API and container information
0331: *
0332: * <p>
0333: * Copied from SnoopAllServlet by Craig R. McClanahan
0334: * </p>
0335: *
0336: * @param out ServletOutputStream as target of the information
0337: * @param req HttpServletRequest object used as source of information
0338: * @param res HttpServletResponse object currently not used but could
0339: * provide future information
0340: *
0341: * @exception IOException if a write operation exception occurs
0342: *
0343: */
0344: protected void printServletEnvironment(ServletOutputStream out,
0345: HttpServletRequest req, HttpServletResponse res)
0346: throws IOException {
0347:
0348: // Document the properties from ServletRequest
0349: out.println("<h1>ServletRequest Properties</h1>");
0350: out.println("<ul>");
0351: Enumeration attrs = req.getAttributeNames();
0352: while (attrs.hasMoreElements()) {
0353: String attr = (String) attrs.nextElement();
0354: out.println("<li><b>attribute</b> " + attr + " = "
0355: + req.getAttribute(attr));
0356: }
0357: out.println("<li><b>characterEncoding</b> = "
0358: + req.getCharacterEncoding());
0359: out.println("<li><b>contentLength</b> = "
0360: + req.getContentLength());
0361: out.println("<li><b>contentType</b> = " + req.getContentType());
0362: Enumeration locales = req.getLocales();
0363: while (locales.hasMoreElements()) {
0364: Locale locale = (Locale) locales.nextElement();
0365: out.println("<li><b>locale</b> = " + locale);
0366: }
0367: Enumeration params = req.getParameterNames();
0368: while (params.hasMoreElements()) {
0369: String param = (String) params.nextElement();
0370: String values[] = req.getParameterValues(param);
0371: for (int i = 0; i < values.length; i++)
0372: out.println("<li><b>parameter</b> " + param + " = "
0373: + values[i]);
0374: }
0375: out.println("<li><b>protocol</b> = " + req.getProtocol());
0376: out.println("<li><b>remoteAddr</b> = " + req.getRemoteAddr());
0377: out.println("<li><b>remoteHost</b> = " + req.getRemoteHost());
0378: out.println("<li><b>scheme</b> = " + req.getScheme());
0379: out.println("<li><b>secure</b> = " + req.isSecure());
0380: out.println("<li><b>serverName</b> = " + req.getServerName());
0381: out.println("<li><b>serverPort</b> = " + req.getServerPort());
0382: out.println("</ul>");
0383: out.println("<hr>");
0384:
0385: // Document the properties from HttpServletRequest
0386: out.println("<h1>HttpServletRequest Properties</h1>");
0387: out.println("<ul>");
0388: out.println("<li><b>authType</b> = " + req.getAuthType());
0389: out.println("<li><b>contextPath</b> = " + req.getContextPath());
0390: Cookie cookies[] = req.getCookies();
0391: if (cookies != null) {
0392: for (int i = 0; i < cookies.length; i++)
0393: out.println("<li><b>cookie</b> " + cookies[i].getName()
0394: + " = " + cookies[i].getValue());
0395: }
0396: Enumeration headers = req.getHeaderNames();
0397: while (headers.hasMoreElements()) {
0398: String header = (String) headers.nextElement();
0399: out.println("<li><b>header</b> " + header + " = "
0400: + req.getHeader(header));
0401: }
0402: out.println("<li><b>method</b> = " + req.getMethod());
0403: out.println("<li><a name=\"pathInfo\"><b>pathInfo</b></a> = "
0404: + req.getPathInfo());
0405: out.println("<li><b>pathTranslated</b> = "
0406: + req.getPathTranslated());
0407: out.println("<li><b>queryString</b> = " + req.getQueryString());
0408: out.println("<li><b>remoteUser</b> = " + req.getRemoteUser());
0409: out.println("<li><b>requestedSessionId</b> = "
0410: + req.getRequestedSessionId());
0411: out.println("<li><b>requestedSessionIdFromCookie</b> = "
0412: + req.isRequestedSessionIdFromCookie());
0413: out.println("<li><b>requestedSessionIdFromURL</b> = "
0414: + req.isRequestedSessionIdFromURL());
0415: out.println("<li><b>requestedSessionIdValid</b> = "
0416: + req.isRequestedSessionIdValid());
0417: out.println("<li><b>requestURI</b> = " + req.getRequestURI());
0418: out.println("<li><b>servletPath</b> = " + req.getServletPath());
0419: out.println("<li><b>userPrincipal</b> = "
0420: + req.getUserPrincipal());
0421: out.println("</ul>");
0422: out.println("<hr>");
0423:
0424: // Document the servlet request attributes
0425: out.println("<h1>ServletRequest Attributes</h1>");
0426: out.println("<ul>");
0427: attrs = req.getAttributeNames();
0428: while (attrs.hasMoreElements()) {
0429: String attr = (String) attrs.nextElement();
0430: out.println("<li><b>" + attr + "</b> = "
0431: + req.getAttribute(attr));
0432: }
0433: out.println("</ul>");
0434: out.println("<hr>");
0435:
0436: // Process the current session (if there is one)
0437: HttpSession session = req.getSession(false);
0438: if (session != null) {
0439:
0440: // Document the session properties
0441: out.println("<h1>HttpSession Properties</h1>");
0442: out.println("<ul>");
0443: out.println("<li><b>id</b> = " + session.getId());
0444: out.println("<li><b>creationTime</b> = "
0445: + new Date(session.getCreationTime()));
0446: out.println("<li><b>lastAccessedTime</b> = "
0447: + new Date(session.getLastAccessedTime()));
0448: out.println("<li><b>maxInactiveInterval</b> = "
0449: + session.getMaxInactiveInterval());
0450: out.println("</ul>");
0451: out.println("<hr>");
0452:
0453: // Document the session attributes
0454: out.println("<h1>HttpSession Attributes</h1>");
0455: out.println("<ul>");
0456: attrs = session.getAttributeNames();
0457: while (attrs.hasMoreElements()) {
0458: String attr = (String) attrs.nextElement();
0459: out.println("<li><b>" + attr + "</b> = "
0460: + session.getAttribute(attr));
0461: }
0462: out.println("</ul>");
0463: out.println("<hr>");
0464:
0465: }
0466:
0467: // Document the servlet configuration properties
0468: out.println("<h1>ServletConfig Properties</h1>");
0469: out.println("<ul>");
0470: out.println("<li><b>servletName</b> = "
0471: + getServletConfig().getServletName());
0472: out.println("</ul>");
0473: out.println("<hr>");
0474:
0475: // Document the servlet configuration initialization parameters
0476: out.println("<h1>ServletConfig Initialization Parameters</h1>");
0477: out.println("<ul>");
0478: params = getServletConfig().getInitParameterNames();
0479: while (params.hasMoreElements()) {
0480: String param = (String) params.nextElement();
0481: String value = getServletConfig().getInitParameter(param);
0482: out.println("<li><b>" + param + "</b> = " + value);
0483: }
0484: out.println("</ul>");
0485: out.println("<hr>");
0486:
0487: // Document the servlet context properties
0488: out.println("<h1>ServletContext Properties</h1>");
0489: out.println("<ul>");
0490: out.println("<li><b>majorVersion</b> = "
0491: + getServletContext().getMajorVersion());
0492: out.println("<li><b>minorVersion</b> = "
0493: + getServletContext().getMinorVersion());
0494: out.println("<li><b>realPath('/')</b> = "
0495: + getServletContext().getRealPath("/"));
0496: out.println("<li><b>serverInfo</b> = "
0497: + getServletContext().getServerInfo());
0498: out.println("</ul>");
0499: out.println("<hr>");
0500:
0501: // Document the servlet context initialization parameters
0502: out
0503: .println("<h1>ServletContext Initialization Parameters</h1>");
0504: out.println("<ul>");
0505: params = getServletContext().getInitParameterNames();
0506: while (params.hasMoreElements()) {
0507: String param = (String) params.nextElement();
0508: String value = getServletContext().getInitParameter(param);
0509: out.println("<li><b>" + param + "</b> = " + value);
0510: }
0511: out.println("</ul>");
0512: out.println("<hr>");
0513:
0514: // Document the servlet context attributes
0515: out.println("<h1>ServletContext Attributes</h1>");
0516: out.println("<ul>");
0517: attrs = getServletContext().getAttributeNames();
0518: while (attrs.hasMoreElements()) {
0519: String attr = (String) attrs.nextElement();
0520: out.println("<li><b>" + attr + "</b> = "
0521: + getServletContext().getAttribute(attr));
0522: }
0523: out.println("</ul>");
0524: out.println("<hr>");
0525:
0526: }
0527:
0528: /**
0529: * Provides CGI Gateway service -- delegates to <code>doGet</code>
0530: *
0531: * @param req HttpServletRequest passed in by servlet container
0532: * @param res HttpServletResponse passed in by servlet container
0533: *
0534: * @exception ServletException if a servlet-specific exception occurs
0535: * @exception IOException if a read/write exception occurs
0536: *
0537: * @see javax.servlet.http.HttpServlet
0538: *
0539: */
0540: protected void doPost(HttpServletRequest req,
0541: HttpServletResponse res) throws IOException,
0542: ServletException {
0543: doGet(req, res);
0544: }
0545:
0546: /**
0547: * Provides CGI Gateway service
0548: *
0549: * @param req HttpServletRequest passed in by servlet container
0550: * @param res HttpServletResponse passed in by servlet container
0551: *
0552: * @exception ServletException if a servlet-specific exception occurs
0553: * @exception IOException if a read/write exception occurs
0554: *
0555: * @see javax.servlet.http.HttpServlet
0556: *
0557: */
0558: protected void doGet(HttpServletRequest req, HttpServletResponse res)
0559: throws ServletException, IOException {
0560:
0561: // Verify that we were not accessed using the invoker servlet
0562: if (req.getAttribute(Globals.INVOKED_ATTR) != null)
0563: throw new UnavailableException(
0564: "Cannot invoke CGIServlet through the invoker");
0565:
0566: CGIEnvironment cgiEnv = new CGIEnvironment(req,
0567: getServletContext());
0568:
0569: if (cgiEnv.isValid()) {
0570: CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(), cgiEnv
0571: .getEnvironment(), cgiEnv.getWorkingDirectory(),
0572: cgiEnv.getParameters());
0573: //if POST, we need to cgi.setInput
0574: //REMIND: how does this interact with Servlet API 2.3's Filters?!
0575: if ("POST".equals(req.getMethod())) {
0576: cgi.setInput(req.getInputStream());
0577: }
0578: cgi.setResponse(res);
0579: cgi.run();
0580: }
0581:
0582: if (!cgiEnv.isValid()) {
0583: res.setStatus(404);
0584: }
0585:
0586: if (debug >= 10) {
0587:
0588: ServletOutputStream out = res.getOutputStream();
0589: out.println("<HTML><HEAD><TITLE>$Name$</TITLE></HEAD>");
0590: out.println("<BODY>$Header$<p>");
0591:
0592: if (cgiEnv.isValid()) {
0593: out.println(cgiEnv.toString());
0594: } else {
0595: out.println("<H3>");
0596: out.println("CGI script not found or not specified.");
0597: out.println("</H3>");
0598: out.println("<H4>");
0599: out.println("Check the <b>HttpServletRequest ");
0600: out.println("<a href=\"#pathInfo\">pathInfo</a></b> ");
0601: out.println("property to see if it is what you meant ");
0602: out.println("it to be. You must specify an existant ");
0603: out.println("and executable file as part of the ");
0604: out.println("path-info.");
0605: out.println("</H4>");
0606: out.println("<H4>");
0607: out
0608: .println("For a good discussion of how CGI scripts ");
0609: out
0610: .println("work and what their environment variables ");
0611: out.println("mean, please visit the <a ");
0612: out.println("href=\"http://cgi-spec.golux.com\">CGI ");
0613: out.println("Specification page</a>.");
0614: out.println("</H4>");
0615:
0616: }
0617:
0618: printServletEnvironment(out, req, res);
0619:
0620: out.println("</BODY></HTML>");
0621:
0622: }
0623:
0624: } //doGet
0625:
0626: /** For future testing use only; does nothing right now */
0627: public static void main(String[] args) {
0628: System.out.println("$Header$");
0629: }
0630:
0631: /**
0632: * Get all shell environment variables. Have to do it this rather ugly way
0633: * as the API to obtain is not available in 1.4 and earlier APIs.
0634: *
0635: * See <a href="http://www.rgagnon.com/javadetails/java-0150.html">Read environment
0636: * variables from an application</a> for original source and article.
0637: */
0638: private Hashtable<String, String> getShellEnvironment()
0639: throws IOException {
0640: Hashtable<String, String> envVars = new Hashtable<String, String>();
0641: Process p = null;
0642: Runtime r = Runtime.getRuntime();
0643: String OS = System.getProperty("os.name").toLowerCase();
0644: boolean ignoreCase;
0645:
0646: if (OS.indexOf("windows 9") > -1) {
0647: p = r.exec("command.com /c set");
0648: ignoreCase = true;
0649: } else if ((OS.indexOf("nt") > -1)
0650: || (OS.indexOf("windows 20") > -1)
0651: || (OS.indexOf("windows xp") > -1)) {
0652: // thanks to JuanFran for the xp fix!
0653: p = r.exec("cmd.exe /c set");
0654: ignoreCase = true;
0655: } else {
0656: // our last hope, we assume Unix (thanks to H. Ware for the fix)
0657: p = r.exec("env");
0658: ignoreCase = false;
0659: }
0660:
0661: BufferedReader br = new BufferedReader(new InputStreamReader(p
0662: .getInputStream()));
0663: String line;
0664: while ((line = br.readLine()) != null) {
0665: int idx = line.indexOf('=');
0666: String key = line.substring(0, idx);
0667: String value = line.substring(idx + 1);
0668: if (ignoreCase) {
0669: key = key.toUpperCase();
0670: }
0671: envVars.put(key, value);
0672: }
0673: return envVars;
0674: }
0675:
0676: /**
0677: * Encapsulates the CGI environment and rules to derive
0678: * that environment from the servlet container and request information.
0679: *
0680: * <p>
0681: * </p>
0682: *
0683: * @version $Revision: 543681 $, $Date: 2007-06-02 02:42:59 +0200 (sam., 02 juin 2007) $
0684: * @since Tomcat 4.0
0685: *
0686: */
0687: protected class CGIEnvironment {
0688:
0689: /** context of the enclosing servlet */
0690: private ServletContext context = null;
0691:
0692: /** context path of enclosing servlet */
0693: private String contextPath = null;
0694:
0695: /** servlet URI of the enclosing servlet */
0696: private String servletPath = null;
0697:
0698: /** pathInfo for the current request */
0699: private String pathInfo = null;
0700:
0701: /** real file system directory of the enclosing servlet's web app */
0702: private String webAppRootDir = null;
0703:
0704: /** tempdir for context - used to expand scripts in unexpanded wars */
0705: private File tmpDir = null;
0706:
0707: /** derived cgi environment */
0708: private Hashtable env = null;
0709:
0710: /** cgi command to be invoked */
0711: private String command = null;
0712:
0713: /** cgi command's desired working directory */
0714: private File workingDirectory = null;
0715:
0716: /** cgi command's command line parameters */
0717: private ArrayList<String> cmdLineParameters = new ArrayList<String>();
0718:
0719: /** whether or not this object is valid or not */
0720: private boolean valid = false;
0721:
0722: /**
0723: * Creates a CGIEnvironment and derives the necessary environment,
0724: * query parameters, working directory, cgi command, etc.
0725: *
0726: * @param req HttpServletRequest for information provided by
0727: * the Servlet API
0728: * @param context ServletContext for information provided by the
0729: * Servlet API
0730: *
0731: */
0732: protected CGIEnvironment(HttpServletRequest req,
0733: ServletContext context) throws IOException {
0734: setupFromContext(context);
0735: setupFromRequest(req);
0736:
0737: this .valid = setCGIEnvironment(req);
0738:
0739: if (this .valid) {
0740: workingDirectory = new File(command.substring(0,
0741: command.lastIndexOf(File.separator)));
0742: }
0743:
0744: }
0745:
0746: /**
0747: * Uses the ServletContext to set some CGI variables
0748: *
0749: * @param context ServletContext for information provided by the
0750: * Servlet API
0751: */
0752: protected void setupFromContext(ServletContext context) {
0753: this .context = context;
0754: this .webAppRootDir = context.getRealPath("/");
0755: this .tmpDir = (File) context
0756: .getAttribute(Globals.WORK_DIR_ATTR);
0757: }
0758:
0759: /**
0760: * Uses the HttpServletRequest to set most CGI variables
0761: *
0762: * @param req HttpServletRequest for information provided by
0763: * the Servlet API
0764: * @throws UnsupportedEncodingException
0765: */
0766: protected void setupFromRequest(HttpServletRequest req)
0767: throws UnsupportedEncodingException {
0768:
0769: this .contextPath = req.getContextPath();
0770: this .servletPath = req.getServletPath();
0771: this .pathInfo = req.getPathInfo();
0772: // If getPathInfo() returns null, must be using extension mapping
0773: // In this case, pathInfo should be same as servletPath
0774: if (this .pathInfo == null) {
0775: this .pathInfo = this .servletPath;
0776: }
0777:
0778: // If the request method is GET, POST or HEAD and the query string
0779: // does not contain an unencoded "=" this is an indexed query.
0780: // The parsed query string becomes the command line parameters
0781: // for the cgi command.
0782: if (req.getMethod().equals("GET")
0783: || req.getMethod().equals("POST")
0784: || req.getMethod().equals("HEAD")) {
0785: String qs = req.getQueryString();
0786: if (qs != null && qs.indexOf("=") == -1) {
0787: StringTokenizer qsTokens = new StringTokenizer(qs,
0788: "+");
0789: while (qsTokens.hasMoreTokens()) {
0790: cmdLineParameters.add(URLDecoder
0791: .decode(qsTokens.nextToken(),
0792: parameterEncoding));
0793: }
0794: }
0795: }
0796: }
0797:
0798: /**
0799: * Resolves core information about the cgi script.
0800: *
0801: * <p>
0802: * Example URI:
0803: * <PRE> /servlet/cgigateway/dir1/realCGIscript/pathinfo1 </PRE>
0804: * <ul>
0805: * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript
0806: * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript
0807: * <LI><b>cgiName</b> = /dir1/realCGIscript
0808: * <LI><b>name</b> = realCGIscript
0809: * </ul>
0810: * </p>
0811: * <p>
0812: * CGI search algorithm: search the real path below
0813: * <my-webapp-root> and find the first non-directory in
0814: * the getPathTranslated("/"), reading/searching from left-to-right.
0815: *</p>
0816: *<p>
0817: * The CGI search path will start at
0818: * webAppRootDir + File.separator + cgiPathPrefix
0819: * (or webAppRootDir alone if cgiPathPrefix is
0820: * null).
0821: *</p>
0822: *<p>
0823: * cgiPathPrefix is defined by setting
0824: * this servlet's cgiPathPrefix init parameter
0825: *
0826: *</p>
0827: *
0828: * @param pathInfo String from HttpServletRequest.getPathInfo()
0829: * @param webAppRootDir String from context.getRealPath("/")
0830: * @param contextPath String as from
0831: * HttpServletRequest.getContextPath()
0832: * @param servletPath String as from
0833: * HttpServletRequest.getServletPath()
0834: * @param cgiPathPrefix subdirectory of webAppRootDir below which
0835: * the web app's CGIs may be stored; can be null.
0836: * The CGI search path will start at
0837: * webAppRootDir + File.separator + cgiPathPrefix
0838: * (or webAppRootDir alone if cgiPathPrefix is
0839: * null). cgiPathPrefix is defined by setting
0840: * the servlet's cgiPathPrefix init parameter.
0841: *
0842: *
0843: * @return
0844: * <ul>
0845: * <li>
0846: * <code>path</code> - full file-system path to valid cgi script,
0847: * or null if no cgi was found
0848: * <li>
0849: * <code>scriptName</code> -
0850: * CGI variable SCRIPT_NAME; the full URL path
0851: * to valid cgi script or null if no cgi was
0852: * found
0853: * <li>
0854: * <code>cgiName</code> - servlet pathInfo fragment corresponding to
0855: * the cgi script itself, or null if not found
0856: * <li>
0857: * <code>name</code> - simple name (no directories) of the
0858: * cgi script, or null if no cgi was found
0859: * </ul>
0860: *
0861: * @since Tomcat 4.0
0862: */
0863: protected String[] findCGI(String pathInfo,
0864: String webAppRootDir, String contextPath,
0865: String servletPath, String cgiPathPrefix) {
0866: String path = null;
0867: String name = null;
0868: String scriptname = null;
0869: String cginame = "";
0870:
0871: if ((webAppRootDir != null)
0872: && (webAppRootDir.lastIndexOf(File.separator) == (webAppRootDir
0873: .length() - 1))) {
0874: //strip the trailing "/" from the webAppRootDir
0875: webAppRootDir = webAppRootDir.substring(0,
0876: (webAppRootDir.length() - 1));
0877: }
0878:
0879: if (cgiPathPrefix != null) {
0880: webAppRootDir = webAppRootDir + File.separator
0881: + cgiPathPrefix;
0882: }
0883:
0884: if (debug >= 2) {
0885: log("findCGI: path=" + pathInfo + ", " + webAppRootDir);
0886: }
0887:
0888: File currentLocation = new File(webAppRootDir);
0889: StringTokenizer dirWalker = new StringTokenizer(pathInfo,
0890: "/");
0891: if (debug >= 3) {
0892: log("findCGI: currentLoc=" + currentLocation);
0893: }
0894: while (!currentLocation.isFile()
0895: && dirWalker.hasMoreElements()) {
0896: if (debug >= 3) {
0897: log("findCGI: currentLoc=" + currentLocation);
0898: }
0899: String nextElement = (String) dirWalker.nextElement();
0900: currentLocation = new File(currentLocation, nextElement);
0901: cginame = cginame + "/" + nextElement;
0902: }
0903: if (!currentLocation.isFile()) {
0904: return new String[] { null, null, null, null };
0905: } else {
0906: if (debug >= 2) {
0907: log("findCGI: FOUND cgi at " + currentLocation);
0908: }
0909: path = currentLocation.getAbsolutePath();
0910: name = currentLocation.getName();
0911:
0912: if (".".equals(contextPath)) {
0913: scriptname = servletPath;
0914: } else {
0915: scriptname = contextPath + servletPath;
0916: }
0917: if (!servletPath.equals(cginame)) {
0918: scriptname = scriptname + cginame;
0919: }
0920: }
0921:
0922: if (debug >= 1) {
0923: log("findCGI calc: name=" + name + ", path=" + path
0924: + ", scriptname=" + scriptname + ", cginame="
0925: + cginame);
0926: }
0927: return new String[] { path, scriptname, cginame, name };
0928: }
0929:
0930: /**
0931: * Constructs the CGI environment to be supplied to the invoked CGI
0932: * script; relies heavliy on Servlet API methods and findCGI
0933: *
0934: * @param req request associated with the CGI
0935: * invokation
0936: *
0937: * @return true if environment was set OK, false if there
0938: * was a problem and no environment was set
0939: */
0940: protected boolean setCGIEnvironment(HttpServletRequest req)
0941: throws IOException {
0942:
0943: /*
0944: * This method is slightly ugly; c'est la vie.
0945: * "You cannot stop [ugliness], you can only hope to contain [it]"
0946: * (apologies to Marv Albert regarding MJ)
0947: */
0948:
0949: Hashtable<String, String> envp = new Hashtable<String, String>();
0950:
0951: // Add the shell environment variables (if any)
0952: envp.putAll(shellEnv);
0953:
0954: // Add the CGI environment variables
0955: String sPathInfoOrig = null;
0956: String sPathTranslatedOrig = null;
0957: String sPathInfoCGI = null;
0958: String sPathTranslatedCGI = null;
0959: String sCGIFullPath = null;
0960: String sCGIScriptName = null;
0961: String sCGIFullName = null;
0962: String sCGIName = null;
0963: String[] sCGINames;
0964:
0965: sPathInfoOrig = this .pathInfo;
0966: sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
0967:
0968: sPathTranslatedOrig = req.getPathTranslated();
0969: sPathTranslatedOrig = sPathTranslatedOrig == null ? ""
0970: : sPathTranslatedOrig;
0971:
0972: if (webAppRootDir == null) {
0973: // The app has not been deployed in exploded form
0974: webAppRootDir = tmpDir.toString();
0975: expandCGIScript();
0976: }
0977:
0978: sCGINames = findCGI(sPathInfoOrig, webAppRootDir,
0979: contextPath, servletPath, cgiPathPrefix);
0980:
0981: sCGIFullPath = sCGINames[0];
0982: sCGIScriptName = sCGINames[1];
0983: sCGIFullName = sCGINames[2];
0984: sCGIName = sCGINames[3];
0985:
0986: if (sCGIFullPath == null || sCGIScriptName == null
0987: || sCGIFullName == null || sCGIName == null) {
0988: return false;
0989: }
0990:
0991: envp.put("SERVER_SOFTWARE", "TOMCAT");
0992:
0993: envp.put("SERVER_NAME", nullsToBlanks(req.getServerName()));
0994:
0995: envp.put("GATEWAY_INTERFACE", "CGI/1.1");
0996:
0997: envp.put("SERVER_PROTOCOL",
0998: nullsToBlanks(req.getProtocol()));
0999:
1000: int port = req.getServerPort();
1001: Integer iPort = (port == 0 ? new Integer(-1) : new Integer(
1002: port));
1003: envp.put("SERVER_PORT", iPort.toString());
1004:
1005: envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod()));
1006:
1007: envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI()));
1008:
1009: /*-
1010: * PATH_INFO should be determined by using sCGIFullName:
1011: * 1) Let sCGIFullName not end in a "/" (see method findCGI)
1012: * 2) Let sCGIFullName equal the pathInfo fragment which
1013: * corresponds to the actual cgi script.
1014: * 3) Thus, PATH_INFO = request.getPathInfo().substring(
1015: * sCGIFullName.length())
1016: *
1017: * (see method findCGI, where the real work is done)
1018: *
1019: */
1020: if (pathInfo == null
1021: || (pathInfo.substring(sCGIFullName.length())
1022: .length() <= 0)) {
1023: sPathInfoCGI = "";
1024: } else {
1025: sPathInfoCGI = pathInfo
1026: .substring(sCGIFullName.length());
1027: }
1028: envp.put("PATH_INFO", sPathInfoCGI);
1029:
1030: /*-
1031: * PATH_TRANSLATED must be determined after PATH_INFO (and the
1032: * implied real cgi-script) has been taken into account.
1033: *
1034: * The following example demonstrates:
1035: *
1036: * servlet info = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2
1037: * cgifullpath = /servlet/cgigw/dir1/dir2/cgi1
1038: * path_info = /trans1/trans2
1039: * webAppRootDir = servletContext.getRealPath("/")
1040: *
1041: * path_translated = servletContext.getRealPath("/trans1/trans2")
1042: *
1043: * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI
1044: * (unless sPathInfoCGI is null or blank, then the CGI
1045: * specification dictates that the PATH_TRANSLATED metavariable
1046: * SHOULD NOT be defined.
1047: *
1048: */
1049: if (sPathInfoCGI != null && !("".equals(sPathInfoCGI))) {
1050: sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
1051: } else {
1052: sPathTranslatedCGI = null;
1053: }
1054: if (sPathTranslatedCGI == null
1055: || "".equals(sPathTranslatedCGI)) {
1056: //NOOP
1057: } else {
1058: envp.put("PATH_TRANSLATED",
1059: nullsToBlanks(sPathTranslatedCGI));
1060: }
1061:
1062: envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName));
1063:
1064: envp.put("QUERY_STRING",
1065: nullsToBlanks(req.getQueryString()));
1066:
1067: envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost()));
1068:
1069: envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr()));
1070:
1071: envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType()));
1072:
1073: envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser()));
1074:
1075: envp.put("REMOTE_IDENT", ""); //not necessary for full compliance
1076:
1077: envp.put("CONTENT_TYPE",
1078: nullsToBlanks(req.getContentType()));
1079:
1080: /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
1081: * if there is no content, so we cannot put 0 or -1 in as per the
1082: * Servlet API spec.
1083: */
1084: int contentLength = req.getContentLength();
1085: String sContentLength = (contentLength <= 0 ? ""
1086: : (new Integer(contentLength)).toString());
1087: envp.put("CONTENT_LENGTH", sContentLength);
1088:
1089: Enumeration headers = req.getHeaderNames();
1090: String header = null;
1091: while (headers.hasMoreElements()) {
1092: header = null;
1093: header = ((String) headers.nextElement()).toUpperCase();
1094: //REMIND: rewrite multiple headers as if received as single
1095: //REMIND: change character set
1096: //REMIND: I forgot what the previous REMIND means
1097: if ("AUTHORIZATION".equalsIgnoreCase(header)
1098: || "PROXY_AUTHORIZATION"
1099: .equalsIgnoreCase(header)) {
1100: //NOOP per CGI specification section 11.2
1101: } else {
1102: envp.put("HTTP_" + header.replace('-', '_'), req
1103: .getHeader(header));
1104: }
1105: }
1106:
1107: File fCGIFullPath = new File(sCGIFullPath);
1108: command = fCGIFullPath.getCanonicalPath();
1109:
1110: envp.put("X_TOMCAT_SCRIPT_PATH", command); //for kicks
1111:
1112: envp.put("SCRIPT_FILENAME", command); //for PHP
1113:
1114: this .env = envp;
1115:
1116: return true;
1117:
1118: }
1119:
1120: /**
1121: * Extracts requested resource from web app archive to context work
1122: * directory to enable CGI script to be executed.
1123: */
1124: protected void expandCGIScript() {
1125: StringBuffer srcPath = new StringBuffer();
1126: StringBuffer destPath = new StringBuffer();
1127: InputStream is = null;
1128:
1129: // paths depend on mapping
1130: if (cgiPathPrefix == null) {
1131: srcPath.append(pathInfo);
1132: is = context.getResourceAsStream(srcPath.toString());
1133: destPath.append(tmpDir);
1134: destPath.append(pathInfo);
1135: } else {
1136: // essentially same search algorithm as findCGI()
1137: srcPath.append(cgiPathPrefix);
1138: StringTokenizer pathWalker = new StringTokenizer(
1139: pathInfo, "/");
1140: // start with first element
1141: while (pathWalker.hasMoreElements() && (is == null)) {
1142: srcPath.append("/");
1143: srcPath.append(pathWalker.nextElement());
1144: is = context
1145: .getResourceAsStream(srcPath.toString());
1146: }
1147: destPath.append(tmpDir);
1148: destPath.append("/");
1149: destPath.append(srcPath);
1150: }
1151:
1152: if (is == null) {
1153: // didn't find anything, give up now
1154: if (debug >= 2) {
1155: log("expandCGIScript: source '" + srcPath
1156: + "' not found");
1157: }
1158: return;
1159: }
1160:
1161: File f = new File(destPath.toString());
1162: if (f.exists()) {
1163: // Don't need to expand if it already exists
1164: return;
1165: }
1166:
1167: // create directories
1168: String dirPath = new String(destPath.toString().substring(
1169: 0, destPath.toString().lastIndexOf("/")));
1170: File dir = new File(dirPath);
1171: dir.mkdirs();
1172:
1173: try {
1174: synchronized (expandFileLock) {
1175: // make sure file doesn't exist
1176: if (f.exists()) {
1177: return;
1178: }
1179:
1180: // create file
1181: if (!f.createNewFile()) {
1182: return;
1183: }
1184: FileOutputStream fos = new FileOutputStream(f);
1185:
1186: // copy data
1187: IOTools.flow(is, fos);
1188: is.close();
1189: fos.close();
1190: if (debug >= 2) {
1191: log("expandCGIScript: expanded '" + srcPath
1192: + "' to '" + destPath + "'");
1193: }
1194: }
1195: } catch (IOException ioe) {
1196: // delete in case file is corrupted
1197: if (f.exists()) {
1198: f.delete();
1199: }
1200: }
1201: }
1202:
1203: /**
1204: * Print important CGI environment information in a easy-to-read HTML
1205: * table
1206: *
1207: * @return HTML string containing CGI environment info
1208: *
1209: */
1210: public String toString() {
1211:
1212: StringBuffer sb = new StringBuffer();
1213:
1214: sb.append("<TABLE border=2>");
1215:
1216: sb.append("<tr><th colspan=2 bgcolor=grey>");
1217: sb.append("CGIEnvironment Info</th></tr>");
1218:
1219: sb.append("<tr><td>Debug Level</td><td>");
1220: sb.append(debug);
1221: sb.append("</td></tr>");
1222:
1223: sb.append("<tr><td>Validity:</td><td>");
1224: sb.append(isValid());
1225: sb.append("</td></tr>");
1226:
1227: if (isValid()) {
1228: Enumeration envk = env.keys();
1229: while (envk.hasMoreElements()) {
1230: String s = (String) envk.nextElement();
1231: sb.append("<tr><td>");
1232: sb.append(s);
1233: sb.append("</td><td>");
1234: sb.append(blanksToString((String) env.get(s),
1235: "[will be set to blank]"));
1236: sb.append("</td></tr>");
1237: }
1238: }
1239:
1240: sb.append("<tr><td colspan=2><HR></td></tr>");
1241:
1242: sb.append("<tr><td>Derived Command</td><td>");
1243: sb.append(nullsToBlanks(command));
1244: sb.append("</td></tr>");
1245:
1246: sb.append("<tr><td>Working Directory</td><td>");
1247: if (workingDirectory != null) {
1248: sb.append(workingDirectory.toString());
1249: }
1250: sb.append("</td></tr>");
1251:
1252: sb.append("<tr><td>Command Line Params</td><td>");
1253: for (int i = 0; i < cmdLineParameters.size(); i++) {
1254: String param = (String) cmdLineParameters.get(i);
1255: sb.append("<p>");
1256: sb.append(param);
1257: sb.append("</p>");
1258: }
1259: sb.append("</td></tr>");
1260:
1261: sb.append("</TABLE><p>end.");
1262:
1263: return sb.toString();
1264: }
1265:
1266: /**
1267: * Gets derived command string
1268: *
1269: * @return command string
1270: *
1271: */
1272: protected String getCommand() {
1273: return command;
1274: }
1275:
1276: /**
1277: * Gets derived CGI working directory
1278: *
1279: * @return working directory
1280: *
1281: */
1282: protected File getWorkingDirectory() {
1283: return workingDirectory;
1284: }
1285:
1286: /**
1287: * Gets derived CGI environment
1288: *
1289: * @return CGI environment
1290: *
1291: */
1292: protected Hashtable getEnvironment() {
1293: return env;
1294: }
1295:
1296: /**
1297: * Gets derived CGI query parameters
1298: *
1299: * @return CGI query parameters
1300: *
1301: */
1302: protected ArrayList getParameters() {
1303: return cmdLineParameters;
1304: }
1305:
1306: /**
1307: * Gets validity status
1308: *
1309: * @return true if this environment is valid, false
1310: * otherwise
1311: *
1312: */
1313: protected boolean isValid() {
1314: return valid;
1315: }
1316:
1317: /**
1318: * Converts null strings to blank strings ("")
1319: *
1320: * @param s string to be converted if necessary
1321: * @return a non-null string, either the original or the empty string
1322: * ("") if the original was <code>null</code>
1323: */
1324: protected String nullsToBlanks(String s) {
1325: return nullsToString(s, "");
1326: }
1327:
1328: /**
1329: * Converts null strings to another string
1330: *
1331: * @param couldBeNull string to be converted if necessary
1332: * @param subForNulls string to return instead of a null string
1333: * @return a non-null string, either the original or the substitute
1334: * string if the original was <code>null</code>
1335: */
1336: protected String nullsToString(String couldBeNull,
1337: String subForNulls) {
1338: return (couldBeNull == null ? subForNulls : couldBeNull);
1339: }
1340:
1341: /**
1342: * Converts blank strings to another string
1343: *
1344: * @param couldBeBlank string to be converted if necessary
1345: * @param subForBlanks string to return instead of a blank string
1346: * @return a non-null string, either the original or the substitute
1347: * string if the original was <code>null</code> or empty ("")
1348: */
1349: protected String blanksToString(String couldBeBlank,
1350: String subForBlanks) {
1351: return (("".equals(couldBeBlank) || couldBeBlank == null) ? subForBlanks
1352: : couldBeBlank);
1353: }
1354:
1355: } //class CGIEnvironment
1356:
1357: /**
1358: * Encapsulates the knowledge of how to run a CGI script, given the
1359: * script's desired environment and (optionally) input/output streams
1360: *
1361: * <p>
1362: *
1363: * Exposes a <code>run</code> method used to actually invoke the
1364: * CGI.
1365: *
1366: * </p>
1367: * <p>
1368: *
1369: * The CGI environment and settings are derived from the information
1370: * passed to the constuctor.
1371: *
1372: * </p>
1373: * <p>
1374: *
1375: * The input and output streams can be set by the <code>setInput</code>
1376: * and <code>setResponse</code> methods, respectively.
1377: * </p>
1378: *
1379: * @version $Revision: 543681 $, $Date: 2007-06-02 02:42:59 +0200 (sam., 02 juin 2007) $
1380: */
1381:
1382: protected class CGIRunner {
1383:
1384: /** script/command to be executed */
1385: private String command = null;
1386:
1387: /** environment used when invoking the cgi script */
1388: private Hashtable env = null;
1389:
1390: /** working directory used when invoking the cgi script */
1391: private File wd = null;
1392:
1393: /** command line parameters to be passed to the invoked script */
1394: private ArrayList params = null;
1395:
1396: /** stdin to be passed to cgi script */
1397: private InputStream stdin = null;
1398:
1399: /** response object used to set headers & get output stream */
1400: private HttpServletResponse response = null;
1401:
1402: /** boolean tracking whether this object has enough info to run() */
1403: private boolean readyToRun = false;
1404:
1405: /**
1406: * Creates a CGIRunner and initializes its environment, working
1407: * directory, and query parameters.
1408: * <BR>
1409: * Input/output streams (optional) are set using the
1410: * <code>setInput</code> and <code>setResponse</code> methods,
1411: * respectively.
1412: *
1413: * @param command string full path to command to be executed
1414: * @param env Hashtable with the desired script environment
1415: * @param wd File with the script's desired working directory
1416: * @param params ArrayList with the script's query command line
1417: * paramters as strings
1418: */
1419: protected CGIRunner(String command, Hashtable env, File wd,
1420: ArrayList params) {
1421: this .command = command;
1422: this .env = env;
1423: this .wd = wd;
1424: this .params = params;
1425: updateReadyStatus();
1426: }
1427:
1428: /**
1429: * Checks & sets ready status
1430: */
1431: protected void updateReadyStatus() {
1432: if (command != null && env != null && wd != null
1433: && params != null && response != null) {
1434: readyToRun = true;
1435: } else {
1436: readyToRun = false;
1437: }
1438: }
1439:
1440: /**
1441: * Gets ready status
1442: *
1443: * @return false if not ready (<code>run</code> will throw
1444: * an exception), true if ready
1445: */
1446: protected boolean isReady() {
1447: return readyToRun;
1448: }
1449:
1450: /**
1451: * Sets HttpServletResponse object used to set headers and send
1452: * output to
1453: *
1454: * @param response HttpServletResponse to be used
1455: *
1456: */
1457: protected void setResponse(HttpServletResponse response) {
1458: this .response = response;
1459: updateReadyStatus();
1460: }
1461:
1462: /**
1463: * Sets standard input to be passed on to the invoked cgi script
1464: *
1465: * @param stdin InputStream to be used
1466: *
1467: */
1468: protected void setInput(InputStream stdin) {
1469: this .stdin = stdin;
1470: updateReadyStatus();
1471: }
1472:
1473: /**
1474: * Converts a Hashtable to a String array by converting each
1475: * key/value pair in the Hashtable to a String in the form
1476: * "key=value" (hashkey + "=" + hash.get(hashkey).toString())
1477: *
1478: * @param h Hashtable to convert
1479: *
1480: * @return converted string array
1481: *
1482: * @exception NullPointerException if a hash key has a null value
1483: *
1484: */
1485: protected String[] hashToStringArray(Hashtable h)
1486: throws NullPointerException {
1487: Vector<String> v = new Vector<String>();
1488: Enumeration e = h.keys();
1489: while (e.hasMoreElements()) {
1490: String k = e.nextElement().toString();
1491: v.add(k + "=" + h.get(k));
1492: }
1493: String[] strArr = new String[v.size()];
1494: v.copyInto(strArr);
1495: return strArr;
1496: }
1497:
1498: /**
1499: * Executes a CGI script with the desired environment, current working
1500: * directory, and input/output streams
1501: *
1502: * <p>
1503: * This implements the following CGI specification recommedations:
1504: * <UL>
1505: * <LI> Servers SHOULD provide the "<code>query</code>" component of
1506: * the script-URI as command-line arguments to scripts if it
1507: * does not contain any unencoded "=" characters and the
1508: * command-line arguments can be generated in an unambiguous
1509: * manner.
1510: * <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
1511: * of the "<code>auth-scheme</code>" token of the
1512: * "<code>Authorization</code>" if it was supplied as part of the
1513: * request header. See <code>getCGIEnvironment</code> method.
1514: * <LI> Where applicable, servers SHOULD set the current working
1515: * directory to the directory in which the script is located
1516: * before invoking it.
1517: * <LI> Server implementations SHOULD define their behavior for the
1518: * following cases:
1519: * <ul>
1520: * <LI> <u>Allowed characters in pathInfo</u>: This implementation
1521: * does not allow ASCII NUL nor any character which cannot
1522: * be URL-encoded according to internet standards;
1523: * <LI> <u>Allowed characters in path segments</u>: This
1524: * implementation does not allow non-terminal NULL
1525: * segments in the the path -- IOExceptions may be thrown;
1526: * <LI> <u>"<code>.</code>" and "<code>..</code>" path
1527: * segments</u>:
1528: * This implementation does not allow "<code>.</code>" and
1529: * "<code>..</code>" in the the path, and such characters
1530: * will result in an IOException being thrown;
1531: * <LI> <u>Implementation limitations</u>: This implementation
1532: * does not impose any limitations except as documented
1533: * above. This implementation may be limited by the
1534: * servlet container used to house this implementation.
1535: * In particular, all the primary CGI variable values
1536: * are derived either directly or indirectly from the
1537: * container's implementation of the Servlet API methods.
1538: * </ul>
1539: * </UL>
1540: * </p>
1541: *
1542: * @exception IOException if problems during reading/writing occur
1543: *
1544: * @see java.lang.Runtime#exec(String command, String[] envp,
1545: * File dir)
1546: */
1547: protected void run() throws IOException {
1548:
1549: /*
1550: * REMIND: this method feels too big; should it be re-written?
1551: */
1552:
1553: if (!isReady()) {
1554: throw new IOException(this .getClass().getName()
1555: + ": not ready to run.");
1556: }
1557:
1558: if (debug >= 1) {
1559: log("runCGI(envp=[" + env + "], command=" + command
1560: + ")");
1561: }
1562:
1563: if ((command.indexOf(File.separator + "." + File.separator) >= 0)
1564: || (command.indexOf(File.separator + "..") >= 0)
1565: || (command.indexOf(".." + File.separator) >= 0)) {
1566: throw new IOException(this .getClass().getName()
1567: + "Illegal Character in CGI command "
1568: + "path ('.' or '..') detected. Not "
1569: + "running CGI [" + command + "].");
1570: }
1571:
1572: /* original content/structure of this section taken from
1573: * http://developer.java.sun.com/developer/
1574: * bugParade/bugs/4216884.html
1575: * with major modifications by Martin Dengler
1576: */
1577: Runtime rt = null;
1578: InputStream cgiOutput = null;
1579: BufferedReader commandsStdErr = null;
1580: BufferedOutputStream commandsStdIn = null;
1581: Process proc = null;
1582: int bufRead = -1;
1583:
1584: //create query arguments
1585: StringBuffer cmdAndArgs = new StringBuffer();
1586: if (command.indexOf(" ") < 0) {
1587: cmdAndArgs.append(command);
1588: } else {
1589: // Spaces used as delimiter, so need to use quotes
1590: cmdAndArgs.append("\"");
1591: cmdAndArgs.append(command);
1592: cmdAndArgs.append("\"");
1593: }
1594:
1595: for (int i = 0; i < params.size(); i++) {
1596: cmdAndArgs.append(" ");
1597: String param = (String) params.get(i);
1598: if (param.indexOf(" ") < 0) {
1599: cmdAndArgs.append(param);
1600: } else {
1601: // Spaces used as delimiter, so need to use quotes
1602: cmdAndArgs.append("\"");
1603: cmdAndArgs.append(param);
1604: cmdAndArgs.append("\"");
1605: }
1606: }
1607:
1608: StringBuffer command = new StringBuffer(cgiExecutable);
1609: command.append(" ");
1610: command.append(cmdAndArgs.toString());
1611: cmdAndArgs = command;
1612:
1613: try {
1614: rt = Runtime.getRuntime();
1615: proc = rt.exec(cmdAndArgs.toString(),
1616: hashToStringArray(env), wd);
1617:
1618: String sContentLength = (String) env
1619: .get("CONTENT_LENGTH");
1620:
1621: if (!"".equals(sContentLength)) {
1622: commandsStdIn = new BufferedOutputStream(proc
1623: .getOutputStream());
1624: IOTools.flow(stdin, commandsStdIn);
1625: commandsStdIn.flush();
1626: commandsStdIn.close();
1627: }
1628:
1629: /* we want to wait for the process to exit, Process.waitFor()
1630: * is useless in our situation; see
1631: * http://developer.java.sun.com/developer/
1632: * bugParade/bugs/4223650.html
1633: */
1634:
1635: boolean isRunning = true;
1636: commandsStdErr = new BufferedReader(
1637: new InputStreamReader(proc.getErrorStream()));
1638: final BufferedReader stdErrRdr = commandsStdErr;
1639:
1640: new Thread() {
1641: public void run() {
1642: sendToLog(stdErrRdr);
1643: };
1644: }.start();
1645:
1646: InputStream cgiHeaderStream = new HTTPHeaderInputStream(
1647: proc.getInputStream());
1648: BufferedReader cgiHeaderReader = new BufferedReader(
1649: new InputStreamReader(cgiHeaderStream));
1650:
1651: while (isRunning) {
1652: try {
1653: //set headers
1654: String line = null;
1655: while (((line = cgiHeaderReader.readLine()) != null)
1656: && !("".equals(line))) {
1657: if (debug >= 2) {
1658: log("runCGI: addHeader(\"" + line
1659: + "\")");
1660: }
1661: if (line.startsWith("HTTP")) {
1662: response
1663: .setStatus(getSCFromHttpStatusLine(line));
1664: } else if (line.indexOf(":") >= 0) {
1665: String header = line.substring(0,
1666: line.indexOf(":")).trim();
1667: String value = line.substring(
1668: line.indexOf(":") + 1).trim();
1669: if (header.equalsIgnoreCase("status")) {
1670: response
1671: .setStatus(getSCFromCGIStatusHeader(value));
1672: } else {
1673: response.addHeader(header, value);
1674: }
1675: } else {
1676: log("runCGI: bad header line \"" + line
1677: + "\"");
1678: }
1679: }
1680:
1681: //write output
1682: byte[] bBuf = new byte[2048];
1683:
1684: OutputStream out = response.getOutputStream();
1685: cgiOutput = proc.getInputStream();
1686:
1687: try {
1688: while ((bufRead = cgiOutput.read(bBuf)) != -1) {
1689: if (debug >= 4) {
1690: log("runCGI: output " + bufRead
1691: + " bytes of data");
1692: }
1693: out.write(bBuf, 0, bufRead);
1694: }
1695: } finally {
1696: // Attempt to consume any leftover byte if something bad happens,
1697: // such as a socket disconnect on the servlet side; otherwise, the
1698: // external process could hang
1699: if (bufRead != -1) {
1700: while ((bufRead = cgiOutput.read(bBuf)) != -1) {
1701: }
1702: }
1703: }
1704:
1705: proc.exitValue(); // Throws exception if alive
1706:
1707: isRunning = false;
1708:
1709: } catch (IllegalThreadStateException e) {
1710: try {
1711: Thread.sleep(500);
1712: } catch (InterruptedException ignored) {
1713: }
1714: }
1715: } //replacement for Process.waitFor()
1716:
1717: // Close the output stream used
1718: cgiOutput.close();
1719: } catch (IOException e) {
1720: log("Caught exception " + e);
1721: throw e;
1722: } finally {
1723: if (debug > 4) {
1724: log("Running finally block");
1725: }
1726: if (proc != null) {
1727: proc.destroy();
1728: proc = null;
1729: }
1730: }
1731: }
1732:
1733: /**
1734: * Parses the Status-Line and extracts the status code.
1735: *
1736: * @param line The HTTP Status-Line (RFC2616, section 6.1)
1737: * @return The extracted status code or the code representing an
1738: * internal error if a valid status code cannot be extracted.
1739: */
1740: private int getSCFromHttpStatusLine(String line) {
1741: int statusStart = line.indexOf(' ') + 1;
1742:
1743: if (statusStart < 1 || line.length() < statusStart + 3) {
1744: // Not a valid HTTP Status-Line
1745: log("runCGI: invalid HTTP Status-Line:" + line);
1746: return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1747: }
1748:
1749: String status = line
1750: .substring(statusStart, statusStart + 3);
1751:
1752: int statusCode;
1753: try {
1754: statusCode = Integer.parseInt(status);
1755: } catch (NumberFormatException nfe) {
1756: // Not a valid status code
1757: log("runCGI: invalid status code:" + status);
1758: return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1759: }
1760:
1761: return statusCode;
1762: }
1763:
1764: /**
1765: * Parses the CGI Status Header value and extracts the status code.
1766: *
1767: * @param value The CGI Status value of the form <code>
1768: * digit digit digit SP reason-phrase</code>
1769: * @return The extracted status code or the code representing an
1770: * internal error if a valid status code cannot be extracted.
1771: */
1772: private int getSCFromCGIStatusHeader(String value) {
1773: if (value.length() < 3) {
1774: // Not a valid status value
1775: log("runCGI: invalid status value:" + value);
1776: return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1777: }
1778:
1779: String status = value.substring(0, 3);
1780:
1781: int statusCode;
1782: try {
1783: statusCode = Integer.parseInt(status);
1784: } catch (NumberFormatException nfe) {
1785: // Not a valid status code
1786: log("runCGI: invalid status code:" + status);
1787: return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
1788: }
1789:
1790: return statusCode;
1791: }
1792:
1793: private void sendToLog(BufferedReader rdr) {
1794: String line = null;
1795: int lineCount = 0;
1796: try {
1797: while ((line = rdr.readLine()) != null) {
1798: log("runCGI (stderr):" + line);
1799: lineCount++;
1800: }
1801: } catch (IOException e) {
1802: log("sendToLog error", e);
1803: } finally {
1804: try {
1805: rdr.close();
1806: } catch (IOException ce) {
1807: log("sendToLog error", ce);
1808: }
1809: ;
1810: }
1811: ;
1812: if (lineCount > 0 && debug > 2) {
1813: log("runCGI: " + lineCount
1814: + " lines received on stderr");
1815: }
1816: ;
1817: }
1818: } //class CGIRunner
1819:
1820: /**
1821: * This is an input stream specifically for reading HTTP headers. It reads
1822: * upto and including the two blank lines terminating the headers. It
1823: * allows the content to be read using bytes or characters as appropriate.
1824: */
1825: protected class HTTPHeaderInputStream extends InputStream {
1826: private static final int STATE_CHARACTER = 0;
1827: private static final int STATE_FIRST_CR = 1;
1828: private static final int STATE_FIRST_LF = 2;
1829: private static final int STATE_SECOND_CR = 3;
1830: private static final int STATE_HEADER_END = 4;
1831:
1832: private InputStream input;
1833: private int state;
1834:
1835: HTTPHeaderInputStream(InputStream theInput) {
1836: input = theInput;
1837: state = STATE_CHARACTER;
1838: }
1839:
1840: /**
1841: * @see java.io.InputStream#read()
1842: */
1843: public int read() throws IOException {
1844: if (state == STATE_HEADER_END) {
1845: return -1;
1846: }
1847:
1848: int i = input.read();
1849:
1850: // Update the state
1851: // State machine looks like this
1852: //
1853: // -------->--------
1854: // | (CR) |
1855: // | |
1856: // CR1--->--- |
1857: // | | |
1858: // ^(CR) |(LF) |
1859: // | | |
1860: // CHAR--->--LF1--->--EOH
1861: // (LF) | (LF) |
1862: // |(CR) ^(LF)
1863: // | |
1864: // (CR2)-->---
1865:
1866: if (i == 10) {
1867: // LF
1868: switch (state) {
1869: case STATE_CHARACTER:
1870: state = STATE_FIRST_LF;
1871: break;
1872: case STATE_FIRST_CR:
1873: state = STATE_FIRST_LF;
1874: break;
1875: case STATE_FIRST_LF:
1876: case STATE_SECOND_CR:
1877: state = STATE_HEADER_END;
1878: break;
1879: }
1880:
1881: } else if (i == 13) {
1882: // CR
1883: switch (state) {
1884: case STATE_CHARACTER:
1885: state = STATE_FIRST_CR;
1886: break;
1887: case STATE_FIRST_CR:
1888: state = STATE_HEADER_END;
1889: break;
1890: case STATE_FIRST_LF:
1891: state = STATE_SECOND_CR;
1892: break;
1893: }
1894:
1895: } else {
1896: state = STATE_CHARACTER;
1897: }
1898:
1899: return i;
1900: }
1901: } // class HTTPHeaderInputStream
1902:
1903: } //class CGIServlet
|