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