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