Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » servlets » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    * 
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    * 
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   
   18   
   19   package org.apache.catalina.servlets;
   20   
   21   import java.io.BufferedOutputStream;
   22   import java.io.BufferedReader;
   23   import java.io.File;
   24   import java.io.FileOutputStream;
   25   import java.io.IOException;
   26   import java.io.InputStream;
   27   import java.io.InputStreamReader;
   28   import java.io.OutputStream;
   29   import java.io.UnsupportedEncodingException;
   30   import java.net.URLDecoder;
   31   import java.util.ArrayList;
   32   import java.util.Date;
   33   import java.util.Enumeration;
   34   import java.util.Hashtable;
   35   import java.util.Locale;
   36   import java.util.StringTokenizer;
   37   import java.util.Vector;
   38   
   39   import javax.servlet.ServletConfig;
   40   import javax.servlet.ServletContext;
   41   import javax.servlet.ServletException;
   42   import javax.servlet.ServletOutputStream;
   43   import javax.servlet.UnavailableException;
   44   import javax.servlet.http.Cookie;
   45   import javax.servlet.http.HttpServlet;
   46   import javax.servlet.http.HttpServletRequest;
   47   import javax.servlet.http.HttpServletResponse;
   48   import javax.servlet.http.HttpSession;
   49   
   50   import org.apache.catalina.Globals;
   51   import org.apache.catalina.util.IOTools;
   52   
   53   
   54   /**
   55    *  CGI-invoking servlet for web applications, used to execute scripts which
   56    *  comply to the Common Gateway Interface (CGI) specification and are named
   57    *  in the path-info used to invoke this servlet.
   58    *
   59    * <p>
   60    * <i>Note: This code compiles and even works for simple CGI cases.
   61    *          Exhaustive testing has not been done.  Please consider it beta
   62    *          quality.  Feedback is appreciated to the author (see below).</i>
   63    * </p>
   64    * <p>
   65    *
   66    * <b>Example</b>:<br>
   67    * If an instance of this servlet was mapped (using
   68    *       <code>&lt;web-app&gt;/WEB-INF/web.xml</code>) to:
   69    * </p>
   70    * <p>
   71    * <code>
   72    * &lt;web-app&gt;/cgi-bin/*
   73    * </code>
   74    * </p>
   75    * <p>
   76    * then the following request:
   77    * </p>
   78    * <p>
   79    * <code>
   80    * http://localhost:8080/&lt;web-app&gt;/cgi-bin/dir1/script/pathinfo1
   81    * </code>
   82    * </p>
   83    * <p>
   84    * would result in the execution of the script
   85    * </p>
   86    * <p>
   87    * <code>
   88    * &lt;web-app-root&gt;/WEB-INF/cgi/dir1/script
   89    * </code>
   90    * </p>
   91    * <p>
   92    * with the script's <code>PATH_INFO</code> set to <code>/pathinfo1</code>.
   93    * </p>
   94    * <p>
   95    * Recommendation:  House all your CGI scripts under
   96    * <code>&lt;webapp&gt;/WEB-INF/cgi</code>.  This will ensure that you do not
   97    * accidentally expose your cgi scripts' code to the outside world and that
   98    * your cgis will be cleanly ensconced underneath the WEB-INF (i.e.,
   99    * non-content) area.
  100    * </p>
  101    * <p>
  102    * The default CGI location is mentioned above.  You have the flexibility to
  103    * put CGIs wherever you want, however:
  104    * </p>
  105    * <p>
  106    *   The CGI search path will start at
  107    *   webAppRootDir + File.separator + cgiPathPrefix
  108    *   (or webAppRootDir alone if cgiPathPrefix is
  109    *   null).
  110    * </p>
  111    * <p>
  112    *   cgiPathPrefix is defined by setting
  113    *   this servlet's cgiPathPrefix init parameter
  114    * </p>
  115    *
  116    * <p>
  117    *
  118    * <B>CGI Specification</B>:<br> derived from
  119    * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
  120    * A work-in-progress & expired Internet Draft.  Note no actual RFC describing
  121    * the CGI specification exists.  Where the behavior of this servlet differs
  122    * from the specification cited above, it is either documented here, a bug,
  123    * or an instance where the specification cited differs from Best
  124    * Community Practice (BCP).
  125    * Such instances should be well-documented here.  Please email the
  126    * <a href="mailto:tomcat-dev@jakarta.apache.org">Jakarta Tomcat group [tomcat-dev@jakarta.apache.org]</a>
  127    * with amendments.
  128    *
  129    * </p>
  130    * <p>
  131    *
  132    * <b>Canonical metavariables</b>:<br>
  133    * The CGI specification defines the following canonical metavariables:
  134    * <br>
  135    * [excerpt from CGI specification]
  136    * <PRE>
  137    *  AUTH_TYPE
  138    *  CONTENT_LENGTH
  139    *  CONTENT_TYPE
  140    *  GATEWAY_INTERFACE
  141    *  PATH_INFO
  142    *  PATH_TRANSLATED
  143    *  QUERY_STRING
  144    *  REMOTE_ADDR
  145    *  REMOTE_HOST
  146    *  REMOTE_IDENT
  147    *  REMOTE_USER
  148    *  REQUEST_METHOD
  149    *  SCRIPT_NAME
  150    *  SERVER_NAME
  151    *  SERVER_PORT
  152    *  SERVER_PROTOCOL
  153    *  SERVER_SOFTWARE
  154    * </PRE>
  155    * <p>
  156    * Metavariables with names beginning with the protocol name (<EM>e.g.</EM>,
  157    * "HTTP_ACCEPT") are also canonical in their description of request header
  158    * fields.  The number and meaning of these fields may change independently
  159    * of this specification.  (See also section 6.1.5 [of the CGI specification].)
  160    * </p>
  161    * [end excerpt]
  162    *
  163    * </p>
  164    * <h2> Implementation notes</h2>
  165    * <p>
  166    *
  167    * <b>standard input handling</b>: If your script accepts standard input,
  168    * then the client must start sending input within a certain timeout period,
  169    * otherwise the servlet will assume no input is coming and carry on running
  170    * the script.  The script's the standard input will be closed and handling of
  171    * any further input from the client is undefined.  Most likely it will be
  172    * ignored.  If this behavior becomes undesirable, then this servlet needs
  173    * to be enhanced to handle threading of the spawned process' stdin, stdout,
  174    * and stderr (which should not be too hard).
  175    * <br>
  176    * If you find your cgi scripts are timing out receiving input, you can set
  177    * the init parameter <code></code> of your webapps' cgi-handling servlet
  178    * to be
  179    * </p>
  180    * <p>
  181    *
  182    * <b>Metavariable Values</b>: According to the CGI specificion,
  183    * implementations may choose to represent both null or missing values in an
  184    * implementation-specific manner, but must define that manner.  This
  185    * implementation chooses to always define all required metavariables, but
  186    * set the value to "" for all metavariables whose value is either null or
  187    * undefined.  PATH_TRANSLATED is the sole exception to this rule, as per the
  188    * CGI Specification.
  189    *
  190    * </p>
  191    * <p>
  192    *
  193    * <b>NPH --  Non-parsed-header implementation</b>:  This implementation does
  194    * not support the CGI NPH concept, whereby server ensures that the data
  195    * supplied to the script are preceisely as supplied by the client and
  196    * unaltered by the server.
  197    * </p>
  198    * <p>
  199    * The function of a servlet container (including Tomcat) is specifically
  200    * designed to parse and possible alter CGI-specific variables, and as
  201    * such makes NPH functionality difficult to support.
  202    * </p>
  203    * <p>
  204    * The CGI specification states that compliant servers MAY support NPH output.
  205    * It does not state servers MUST support NPH output to be unconditionally
  206    * compliant.  Thus, this implementation maintains unconditional compliance
  207    * with the specification though NPH support is not present.
  208    * </p>
  209    * <p>
  210    *
  211    * The CGI specification is located at
  212    * <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>.
  213    *
  214    * </p>
  215    * <p>
  216    * <h3>TODO:</h3>
  217    * <ul>
  218    * <li> Support for setting headers (for example, Location headers don't work)
  219    * <li> Support for collapsing multiple header lines (per RFC 2616)
  220    * <li> Ensure handling of POST method does not interfere with 2.3 Filters
  221    * <li> Refactor some debug code out of core
  222    * <li> Ensure header handling preserves encoding
  223    * <li> Possibly rewrite CGIRunner.run()?
  224    * <li> Possibly refactor CGIRunner and CGIEnvironment as non-inner classes?
  225    * <li> Document handling of cgi stdin when there is no stdin
  226    * <li> Revisit IOException handling in CGIRunner.run()
  227    * <li> Better documentation
  228    * <li> Confirm use of ServletInputStream.available() in CGIRunner.run() is
  229    *      not needed
  230    * <li> Make checking for "." and ".." in servlet & cgi PATH_INFO less
  231    *      draconian
  232    * <li> [add more to this TODO list]
  233    * </ul>
  234    * </p>
  235    *
  236    * @author Martin T Dengler [root@martindengler.com]
  237    * @author Amy Roh
  238    * @version $Revision: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
  239    * @since Tomcat 4.0
  240    *
  241    */
  242   
  243   
  244   public final class CGIServlet extends HttpServlet {
  245   
  246       /* some vars below copied from Craig R. McClanahan's InvokerServlet */
  247   
  248       /** the debugging detail level for this servlet. */
  249       private int debug = 0;
  250   
  251       /**
  252        *  The CGI search path will start at
  253        *    webAppRootDir + File.separator + cgiPathPrefix
  254        *    (or webAppRootDir alone if cgiPathPrefix is
  255        *    null)
  256        */
  257       private String cgiPathPrefix = null;
  258   
  259       /** the executable to use with the script */
  260       private String cgiExecutable = "perl";
  261       
  262       /** the encoding to use for parameters */
  263       private String parameterEncoding = System.getProperty("file.encoding",
  264                                                             "UTF-8");
  265   
  266       /** object used to ensure multiple threads don't try to expand same file */
  267       static Object expandFileLock = new Object();
  268   
  269       /** the shell environment variables to be passed to the CGI script */
  270       static Hashtable<String,String> shellEnv = new Hashtable<String,String>();
  271   
  272       /**
  273        * Sets instance variables.
  274        * <P>
  275        * Modified from Craig R. McClanahan's InvokerServlet
  276        * </P>
  277        *
  278        * @param config                    a <code>ServletConfig</code> object
  279        *                                  containing the servlet's
  280        *                                  configuration and initialization
  281        *                                  parameters
  282        *
  283        * @exception ServletException      if an exception has occurred that
  284        *                                  interferes with the servlet's normal
  285        *                                  operation
  286        */
  287       public void init(ServletConfig config) throws ServletException {
  288   
  289           super.init(config);
  290   
  291           // Verify that we were not accessed using the invoker servlet
  292           String servletName = getServletConfig().getServletName();
  293           if (servletName == null)
  294               servletName = "";
  295           if (servletName.startsWith("org.apache.catalina.INVOKER."))
  296               throw new UnavailableException
  297                   ("Cannot invoke CGIServlet through the invoker");
  298           
  299           // Set our properties from the initialization parameters
  300           if (getServletConfig().getInitParameter("debug") != null)
  301               debug = Integer.parseInt(getServletConfig().getInitParameter("debug"));
  302           cgiPathPrefix = getServletConfig().getInitParameter("cgiPathPrefix");
  303           boolean passShellEnvironment = 
  304               Boolean.valueOf(getServletConfig().getInitParameter("passShellEnvironment")).booleanValue();
  305   
  306           if (passShellEnvironment) {
  307               try {
  308                   shellEnv.putAll(getShellEnvironment());
  309               } catch (IOException ioe) {
  310                   ServletException e = new ServletException(
  311                           "Unable to read shell environment variables", ioe);
  312                   throw e;
  313               }
  314           }
  315   
  316           if (getServletConfig().getInitParameter("executable") != null) {
  317               cgiExecutable = getServletConfig().getInitParameter("executable");
  318           }
  319   
  320           if (getServletConfig().getInitParameter("parameterEncoding") != null) {
  321               parameterEncoding = getServletConfig().getInitParameter("parameterEncoding");
  322           }
  323   
  324       }
  325   
  326   
  327   
  328       /**
  329        * Prints out important Servlet API and container information
  330        *
  331        * <p>
  332        * Copied from SnoopAllServlet by Craig R. McClanahan
  333        * </p>
  334        *
  335        * @param  out    ServletOutputStream as target of the information
  336        * @param  req    HttpServletRequest object used as source of information
  337        * @param  res    HttpServletResponse object currently not used but could
  338        *                provide future information
  339        *
  340        * @exception  IOException  if a write operation exception occurs
  341        *
  342        */
  343       protected void printServletEnvironment(ServletOutputStream out,
  344           HttpServletRequest req, HttpServletResponse res) throws IOException {
  345   
  346           // Document the properties from ServletRequest
  347           out.println("<h1>ServletRequest Properties</h1>");
  348           out.println("<ul>");
  349           Enumeration attrs = req.getAttributeNames();
  350           while (attrs.hasMoreElements()) {
  351               String attr = (String) attrs.nextElement();
  352               out.println("<li><b>attribute</b> " + attr + " = " +
  353                              req.getAttribute(attr));
  354           }
  355           out.println("<li><b>characterEncoding</b> = " +
  356                          req.getCharacterEncoding());
  357           out.println("<li><b>contentLength</b> = " +
  358                          req.getContentLength());
  359           out.println("<li><b>contentType</b> = " +
  360                          req.getContentType());
  361           Enumeration locales = req.getLocales();
  362           while (locales.hasMoreElements()) {
  363               Locale locale = (Locale) locales.nextElement();
  364               out.println("<li><b>locale</b> = " + locale);
  365           }
  366           Enumeration params = req.getParameterNames();
  367           while (params.hasMoreElements()) {
  368               String param = (String) params.nextElement();
  369               String values[] = req.getParameterValues(param);
  370               for (int i = 0; i < values.length; i++)
  371                   out.println("<li><b>parameter</b> " + param + " = " +
  372                                  values[i]);
  373           }
  374           out.println("<li><b>protocol</b> = " + req.getProtocol());
  375           out.println("<li><b>remoteAddr</b> = " + req.getRemoteAddr());
  376           out.println("<li><b>remoteHost</b> = " + req.getRemoteHost());
  377           out.println("<li><b>scheme</b> = " + req.getScheme());
  378           out.println("<li><b>secure</b> = " + req.isSecure());
  379           out.println("<li><b>serverName</b> = " + req.getServerName());
  380           out.println("<li><b>serverPort</b> = " + req.getServerPort());
  381           out.println("</ul>");
  382           out.println("<hr>");
  383   
  384           // Document the properties from HttpServletRequest
  385           out.println("<h1>HttpServletRequest Properties</h1>");
  386           out.println("<ul>");
  387           out.println("<li><b>authType</b> = " + req.getAuthType());
  388           out.println("<li><b>contextPath</b> = " +
  389                          req.getContextPath());
  390           Cookie cookies[] = req.getCookies();
  391           if (cookies!=null) {
  392               for (int i = 0; i < cookies.length; i++)
  393                   out.println("<li><b>cookie</b> " + cookies[i].getName() +" = " +cookies[i].getValue());
  394           }
  395           Enumeration headers = req.getHeaderNames();
  396           while (headers.hasMoreElements()) {
  397               String header = (String) headers.nextElement();
  398               out.println("<li><b>header</b> " + header + " = " +
  399                              req.getHeader(header));
  400           }
  401           out.println("<li><b>method</b> = " + req.getMethod());
  402           out.println("<li><a name=\"pathInfo\"><b>pathInfo</b></a> = "
  403                       + req.getPathInfo());
  404           out.println("<li><b>pathTranslated</b> = " +
  405                          req.getPathTranslated());
  406           out.println("<li><b>queryString</b> = " +
  407                          req.getQueryString());
  408           out.println("<li><b>remoteUser</b> = " +
  409                          req.getRemoteUser());
  410           out.println("<li><b>requestedSessionId</b> = " +
  411                          req.getRequestedSessionId());
  412           out.println("<li><b>requestedSessionIdFromCookie</b> = " +
  413                          req.isRequestedSessionIdFromCookie());
  414           out.println("<li><b>requestedSessionIdFromURL</b> = " +
  415                          req.isRequestedSessionIdFromURL());
  416           out.println("<li><b>requestedSessionIdValid</b> = " +
  417                          req.isRequestedSessionIdValid());
  418           out.println("<li><b>requestURI</b> = " +
  419                          req.getRequestURI());
  420           out.println("<li><b>servletPath</b> = " +
  421                          req.getServletPath());
  422           out.println("<li><b>userPrincipal</b> = " +
  423                          req.getUserPrincipal());
  424           out.println("</ul>");
  425           out.println("<hr>");
  426   
  427           // Document the servlet request attributes
  428           out.println("<h1>ServletRequest Attributes</h1>");
  429           out.println("<ul>");
  430           attrs = req.getAttributeNames();
  431           while (attrs.hasMoreElements()) {
  432               String attr = (String) attrs.nextElement();
  433               out.println("<li><b>" + attr + "</b> = " +
  434                              req.getAttribute(attr));
  435           }
  436           out.println("</ul>");
  437           out.println("<hr>");
  438   
  439           // Process the current session (if there is one)
  440           HttpSession session = req.getSession(false);
  441           if (session != null) {
  442   
  443               // Document the session properties
  444               out.println("<h1>HttpSession Properties</h1>");
  445               out.println("<ul>");
  446               out.println("<li><b>id</b> = " +
  447                              session.getId());
  448               out.println("<li><b>creationTime</b> = " +
  449                              new Date(session.getCreationTime()));
  450               out.println("<li><b>lastAccessedTime</b> = " +
  451                              new Date(session.getLastAccessedTime()));
  452               out.println("<li><b>maxInactiveInterval</b> = " +
  453                              session.getMaxInactiveInterval());
  454               out.println("</ul>");
  455               out.println("<hr>");
  456   
  457               // Document the session attributes
  458               out.println("<h1>HttpSession Attributes</h1>");
  459               out.println("<ul>");
  460               attrs = session.getAttributeNames();
  461               while (attrs.hasMoreElements()) {
  462                   String attr = (String) attrs.nextElement();
  463                   out.println("<li><b>" + attr + "</b> = " +
  464                                  session.getAttribute(attr));
  465               }
  466               out.println("</ul>");
  467               out.println("<hr>");
  468   
  469           }
  470   
  471           // Document the servlet configuration properties
  472           out.println("<h1>ServletConfig Properties</h1>");
  473           out.println("<ul>");
  474           out.println("<li><b>servletName</b> = " +
  475                          getServletConfig().getServletName());
  476           out.println("</ul>");
  477           out.println("<hr>");
  478   
  479           // Document the servlet configuration initialization parameters
  480           out.println("<h1>ServletConfig Initialization Parameters</h1>");
  481           out.println("<ul>");
  482           params = getServletConfig().getInitParameterNames();
  483           while (params.hasMoreElements()) {
  484               String param = (String) params.nextElement();
  485               String value = getServletConfig().getInitParameter(param);
  486               out.println("<li><b>" + param + "</b> = " + value);
  487           }
  488           out.println("</ul>");
  489           out.println("<hr>");
  490   
  491           // Document the servlet context properties
  492           out.println("<h1>ServletContext Properties</h1>");
  493           out.println("<ul>");
  494           out.println("<li><b>majorVersion</b> = " +
  495                          getServletContext().getMajorVersion());
  496           out.println("<li><b>minorVersion</b> = " +
  497                          getServletContext().getMinorVersion());
  498           out.println("<li><b>realPath('/')</b> = " +
  499                          getServletContext().getRealPath("/"));
  500           out.println("<li><b>serverInfo</b> = " +
  501                          getServletContext().getServerInfo());
  502           out.println("</ul>");
  503           out.println("<hr>");
  504   
  505           // Document the servlet context initialization parameters
  506           out.println("<h1>ServletContext Initialization Parameters</h1>");
  507           out.println("<ul>");
  508           params = getServletContext().getInitParameterNames();
  509           while (params.hasMoreElements()) {
  510               String param = (String) params.nextElement();
  511               String value = getServletContext().getInitParameter(param);
  512               out.println("<li><b>" + param + "</b> = " + value);
  513           }
  514           out.println("</ul>");
  515           out.println("<hr>");
  516   
  517           // Document the servlet context attributes
  518           out.println("<h1>ServletContext Attributes</h1>");
  519           out.println("<ul>");
  520           attrs = getServletContext().getAttributeNames();
  521           while (attrs.hasMoreElements()) {
  522               String attr = (String) attrs.nextElement();
  523               out.println("<li><b>" + attr + "</b> = " +
  524                              getServletContext().getAttribute(attr));
  525           }
  526           out.println("</ul>");
  527           out.println("<hr>");
  528   
  529   
  530   
  531       }
  532   
  533   
  534   
  535       /**
  536        * Provides CGI Gateway service -- delegates to <code>doGet</code>
  537        *
  538        * @param  req   HttpServletRequest passed in by servlet container
  539        * @param  res   HttpServletResponse passed in by servlet container
  540        *
  541        * @exception  ServletException  if a servlet-specific exception occurs
  542        * @exception  IOException  if a read/write exception occurs
  543        *
  544        * @see javax.servlet.http.HttpServlet
  545        *
  546        */
  547       protected void doPost(HttpServletRequest req, HttpServletResponse res)
  548           throws IOException, ServletException {
  549           doGet(req, res);
  550       }
  551   
  552   
  553   
  554       /**
  555        * Provides CGI Gateway service
  556        *
  557        * @param  req   HttpServletRequest passed in by servlet container
  558        * @param  res   HttpServletResponse passed in by servlet container
  559        *
  560        * @exception  ServletException  if a servlet-specific exception occurs
  561        * @exception  IOException  if a read/write exception occurs
  562        *
  563        * @see javax.servlet.http.HttpServlet
  564        *
  565        */
  566       protected void doGet(HttpServletRequest req, HttpServletResponse res)
  567           throws ServletException, IOException {
  568   
  569           // Verify that we were not accessed using the invoker servlet
  570           if (req.getAttribute(Globals.INVOKED_ATTR) != null)
  571               throw new UnavailableException
  572                   ("Cannot invoke CGIServlet through the invoker");
  573   
  574           CGIEnvironment cgiEnv = new CGIEnvironment(req, getServletContext());
  575   
  576           if (cgiEnv.isValid()) {
  577               CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(),
  578                                             cgiEnv.getEnvironment(),
  579                                             cgiEnv.getWorkingDirectory(),
  580                                             cgiEnv.getParameters());
  581               //if POST, we need to cgi.setInput
  582               //REMIND: how does this interact with Servlet API 2.3's Filters?!
  583               if ("POST".equals(req.getMethod())) {
  584                   cgi.setInput(req.getInputStream());
  585               }
  586               cgi.setResponse(res);
  587               cgi.run();
  588           }
  589   
  590           if (!cgiEnv.isValid()) {
  591               res.setStatus(404);
  592           }
  593    
  594           if (debug >= 10) {
  595   
  596               ServletOutputStream out = res.getOutputStream();
  597               out.println("<HTML><HEAD><TITLE>$Name$</TITLE></HEAD>");
  598               out.println("<BODY>$Header$<p>");
  599   
  600               if (cgiEnv.isValid()) {
  601                   out.println(cgiEnv.toString());
  602               } else {
  603                   out.println("<H3>");
  604                   out.println("CGI script not found or not specified.");
  605                   out.println("</H3>");
  606                   out.println("<H4>");
  607                   out.println("Check the <b>HttpServletRequest ");
  608                   out.println("<a href=\"#pathInfo\">pathInfo</a></b> ");
  609                   out.println("property to see if it is what you meant ");
  610                   out.println("it to be.  You must specify an existant ");
  611                   out.println("and executable file as part of the ");
  612                   out.println("path-info.");
  613                   out.println("</H4>");
  614                   out.println("<H4>");
  615                   out.println("For a good discussion of how CGI scripts ");
  616                   out.println("work and what their environment variables ");
  617                   out.println("mean, please visit the <a ");
  618                   out.println("href=\"http://cgi-spec.golux.com\">CGI ");
  619                   out.println("Specification page</a>.");
  620                   out.println("</H4>");
  621   
  622               }
  623   
  624               printServletEnvironment(out, req, res);
  625   
  626               out.println("</BODY></HTML>");
  627   
  628           }
  629   
  630   
  631       } //doGet
  632   
  633   
  634   
  635       /** For future testing use only; does nothing right now */
  636       public static void main(String[] args) {
  637           System.out.println("$Header$");
  638       }
  639   
  640       /**
  641        * Get all shell environment variables. Have to do it this rather ugly way
  642        * as the API to obtain is not available in 1.4 and earlier APIs.
  643        *
  644        * See <a href="http://www.rgagnon.com/javadetails/java-0150.html">Read environment
  645        * variables from an application</a> for original source and article.
  646        */
  647       private Hashtable<String,String> getShellEnvironment() throws IOException {
  648           Hashtable<String,String> envVars = new Hashtable<String,String>();
  649           Process p = null;
  650           Runtime r = Runtime.getRuntime();
  651           String OS = System.getProperty("os.name").toLowerCase();
  652           boolean ignoreCase;
  653   
  654           if (OS.indexOf("windows 9") > -1) {
  655               p = r.exec( "command.com /c set" );
  656               ignoreCase = true;
  657           } else if ( (OS.indexOf("nt") > -1)
  658                   || (OS.indexOf("windows 20") > -1)
  659                   || (OS.indexOf("windows xp") > -1) ) {
  660               // thanks to JuanFran for the xp fix!
  661               p = r.exec( "cmd.exe /c set" );
  662               ignoreCase = true;
  663           } else {
  664               // our last hope, we assume Unix (thanks to H. Ware for the fix)
  665               p = r.exec( "env" );
  666               ignoreCase = false;
  667           }
  668   
  669           BufferedReader br = new BufferedReader
  670               ( new InputStreamReader( p.getInputStream() ) );
  671           String line;
  672           while( (line = br.readLine()) != null ) {
  673               int idx = line.indexOf( '=' );
  674               String key = line.substring( 0, idx );
  675               String value = line.substring( idx+1 );
  676               if (ignoreCase) {
  677                   key = key.toUpperCase();
  678               }
  679               envVars.put(key, value);
  680           }
  681           return envVars;
  682       }
  683   
  684   
  685   
  686   
  687   
  688   
  689   
  690       /**
  691        * Encapsulates the CGI environment and rules to derive
  692        * that environment from the servlet container and request information.
  693        *
  694        * <p>
  695        * </p>
  696        *
  697        * @version  $Revision: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
  698        * @since    Tomcat 4.0
  699        *
  700        */
  701       protected class CGIEnvironment {
  702   
  703   
  704           /** context of the enclosing servlet */
  705           private ServletContext context = null;
  706   
  707           /** context path of enclosing servlet */
  708           private String contextPath = null;
  709   
  710           /** servlet URI of the enclosing servlet */
  711           private String servletPath = null;
  712   
  713           /** pathInfo for the current request */
  714           private String pathInfo = null;
  715   
  716           /** real file system directory of the enclosing servlet's web app */
  717           private String webAppRootDir = null;
  718   
  719           /** tempdir for context - used to expand scripts in unexpanded wars */
  720           private File tmpDir = null;
  721   
  722           /** derived cgi environment */
  723           private Hashtable env = null;
  724   
  725           /** cgi command to be invoked */
  726           private String command = null;
  727   
  728           /** cgi command's desired working directory */
  729           private File workingDirectory = null;
  730   
  731           /** cgi command's command line parameters */
  732           private ArrayList<String> cmdLineParameters = new ArrayList<String>();
  733   
  734           /** whether or not this object is valid or not */
  735           private boolean valid = false;
  736   
  737   
  738           /**
  739            * Creates a CGIEnvironment and derives the necessary environment,
  740            * query parameters, working directory, cgi command, etc.
  741            *
  742            * @param  req       HttpServletRequest for information provided by
  743            *                   the Servlet API
  744            * @param  context   ServletContext for information provided by the
  745            *                   Servlet API
  746            *
  747            */
  748           protected CGIEnvironment(HttpServletRequest req,
  749                                    ServletContext context) throws IOException {
  750               setupFromContext(context);
  751               setupFromRequest(req);
  752   
  753               this.valid = setCGIEnvironment(req);
  754   
  755               if (this.valid) {
  756                   workingDirectory = new File(command.substring(0,
  757                         command.lastIndexOf(File.separator)));
  758               }
  759   
  760           }
  761   
  762   
  763   
  764           /**
  765            * Uses the ServletContext to set some CGI variables
  766            *
  767            * @param  context   ServletContext for information provided by the
  768            *                   Servlet API
  769            */
  770           protected void setupFromContext(ServletContext context) {
  771               this.context = context;
  772               this.webAppRootDir = context.getRealPath("/");
  773               this.tmpDir = (File) context.getAttribute(Globals.WORK_DIR_ATTR);
  774           }
  775   
  776   
  777   
  778           /**
  779            * Uses the HttpServletRequest to set most CGI variables
  780            *
  781            * @param  req   HttpServletRequest for information provided by
  782            *               the Servlet API
  783            * @throws UnsupportedEncodingException 
  784            */
  785           protected void setupFromRequest(HttpServletRequest req)
  786                   throws UnsupportedEncodingException {
  787   
  788               boolean isIncluded = false;
  789   
  790               // Look to see if this request is an include
  791               if (req.getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR) != null) {
  792                   isIncluded = true;
  793               }
  794               if (isIncluded) {
  795                   this.contextPath = (String) req.getAttribute(
  796                           Globals.INCLUDE_CONTEXT_PATH_ATTR);
  797                   this.servletPath = (String) req.getAttribute(
  798                           Globals.INCLUDE_SERVLET_PATH_ATTR);
  799                   this.pathInfo = (String) req.getAttribute(
  800                           Globals.INCLUDE_PATH_INFO_ATTR);
  801               } else {
  802                   this.contextPath = req.getContextPath();
  803                   this.servletPath = req.getServletPath();
  804                   this.pathInfo = req.getPathInfo();
  805               }
  806               // If getPathInfo() returns null, must be using extension mapping
  807               // In this case, pathInfo should be same as servletPath
  808               if (this.pathInfo == null) {
  809                   this.pathInfo = this.servletPath;
  810               }
  811   
  812               // If the request method is GET, POST or HEAD and the query string
  813               // does not contain an unencoded "=" this is an indexed query.
  814               // The parsed query string becomes the command line parameters
  815               // for the cgi command.
  816               if (req.getMethod().equals("GET")
  817                   || req.getMethod().equals("POST")
  818                   || req.getMethod().equals("HEAD")) {
  819                   String qs;
  820                   if (isIncluded) {
  821                       qs = (String) req.getAttribute(
  822                               Globals.INCLUDE_QUERY_STRING_ATTR);
  823                   } else {
  824                       qs = req.getQueryString();
  825                   }
  826                   if (qs != null && qs.indexOf("=") == -1) {
  827                       StringTokenizer qsTokens = new StringTokenizer(qs, "+");
  828                       while ( qsTokens.hasMoreTokens() ) {
  829                           cmdLineParameters.add(URLDecoder.decode(qsTokens.nextToken(),
  830                                                 parameterEncoding));
  831                       }
  832                   }
  833               }
  834           }
  835   
  836   
  837           /**
  838            * Resolves core information about the cgi script.
  839            *
  840            * <p>
  841            * Example URI:
  842            * <PRE> /servlet/cgigateway/dir1/realCGIscript/pathinfo1 </PRE>
  843            * <ul>
  844            * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript
  845            * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript
  846            * <LI><b>cgiName</b> = /dir1/realCGIscript
  847            * <LI><b>name</b> = realCGIscript
  848            * </ul>
  849            * </p>
  850            * <p>
  851            * CGI search algorithm: search the real path below
  852            *    &lt;my-webapp-root&gt; and find the first non-directory in
  853            *    the getPathTranslated("/"), reading/searching from left-to-right.
  854            *</p>
  855            *<p>
  856            *   The CGI search path will start at
  857            *   webAppRootDir + File.separator + cgiPathPrefix
  858            *   (or webAppRootDir alone if cgiPathPrefix is
  859            *   null).
  860            *</p>
  861            *<p>
  862            *   cgiPathPrefix is defined by setting
  863            *   this servlet's cgiPathPrefix init parameter
  864            *
  865            *</p>
  866            *
  867            * @param pathInfo       String from HttpServletRequest.getPathInfo()
  868            * @param webAppRootDir  String from context.getRealPath("/")
  869            * @param contextPath    String as from
  870            *                       HttpServletRequest.getContextPath()
  871            * @param servletPath    String as from
  872            *                       HttpServletRequest.getServletPath()
  873            * @param cgiPathPrefix  subdirectory of webAppRootDir below which
  874            *                       the web app's CGIs may be stored; can be null.
  875            *                       The CGI search path will start at
  876            *                       webAppRootDir + File.separator + cgiPathPrefix
  877            *                       (or webAppRootDir alone if cgiPathPrefix is
  878            *                       null).  cgiPathPrefix is defined by setting
  879            *                       the servlet's cgiPathPrefix init parameter.
  880            *
  881            *
  882            * @return
  883            * <ul>
  884            * <li>
  885            * <code>path</code> -    full file-system path to valid cgi script,
  886            *                        or null if no cgi was found
  887            * <li>
  888            * <code>scriptName</code> -
  889            *                        CGI variable SCRIPT_NAME; the full URL path
  890            *                        to valid cgi script or null if no cgi was
  891            *                        found
  892            * <li>
  893            * <code>cgiName</code> - servlet pathInfo fragment corresponding to
  894            *                        the cgi script itself, or null if not found
  895            * <li>
  896            * <code>name</code> -    simple name (no directories) of the
  897            *                        cgi script, or null if no cgi was found
  898            * </ul>
  899            *
  900            * @since Tomcat 4.0
  901            */
  902           protected String[] findCGI(String pathInfo, String webAppRootDir,
  903                                      String contextPath, String servletPath,
  904                                      String cgiPathPrefix) {
  905               String path = null;
  906               String name = null;
  907               String scriptname = null;
  908               String cginame = "";
  909   
  910               if ((webAppRootDir != null)
  911                   && (webAppRootDir.lastIndexOf(File.separator) ==
  912                       (webAppRootDir.length() - 1))) {
  913                       //strip the trailing "/" from the webAppRootDir
  914                       webAppRootDir =
  915                       webAppRootDir.substring(0, (webAppRootDir.length() - 1));
  916               }
  917   
  918               if (cgiPathPrefix != null) {
  919                   webAppRootDir = webAppRootDir + File.separator
  920                       + cgiPathPrefix;
  921               }
  922   
  923               if (debug >= 2) {
  924                   log("findCGI: path=" + pathInfo + ", " + webAppRootDir);
  925               }
  926   
  927               File currentLocation = new File(webAppRootDir);
  928               StringTokenizer dirWalker =
  929               new StringTokenizer(pathInfo, "/");
  930               if (debug >= 3) {
  931                   log("findCGI: currentLoc=" + currentLocation);
  932               }
  933               while (!currentLocation.isFile() && dirWalker.hasMoreElements()) {
  934                   if (debug >= 3) {
  935                       log("findCGI: currentLoc=" + currentLocation);
  936                   }
  937                   String nextElement = (String) dirWalker.nextElement();
  938                   currentLocation = new File(currentLocation, nextElement);
  939                   cginame = cginame + "/" + nextElement;
  940               }
  941               if (!currentLocation.isFile()) {
  942                   return new String[] { null, null, null, null };
  943               } else {
  944                   if (debug >= 2) {
  945                       log("findCGI: FOUND cgi at " + currentLocation);
  946                   }
  947                   path = currentLocation.getAbsolutePath();
  948                   name = currentLocation.getName();
  949   
  950                   if (".".equals(contextPath)) {
  951                       scriptname = servletPath;
  952                   } else {
  953                       scriptname = contextPath + servletPath;
  954                   }
  955                   if (!servletPath.equals(cginame)) {
  956                       scriptname = scriptname + cginame;
  957                   }
  958               }
  959   
  960               if (debug >= 1) {
  961                   log("findCGI calc: name=" + name + ", path=" + path
  962                       + ", scriptname=" + scriptname + ", cginame=" + cginame);
  963               }
  964               return new String[] { path, scriptname, cginame, name };
  965           }
  966   
  967           /**
  968            * Constructs the CGI environment to be supplied to the invoked CGI
  969            * script; relies heavliy on Servlet API methods and findCGI
  970            *
  971            * @param    req request associated with the CGI
  972            *           invokation
  973            *
  974            * @return   true if environment was set OK, false if there
  975            *           was a problem and no environment was set
  976            */
  977           protected boolean setCGIEnvironment(HttpServletRequest req) throws IOException {
  978   
  979               /*
  980                * This method is slightly ugly; c'est la vie.
  981                * "You cannot stop [ugliness], you can only hope to contain [it]"
  982                * (apologies to Marv Albert regarding MJ)
  983                */
  984   
  985               Hashtable<String,String> envp = new Hashtable<String,String>();
  986   
  987               // Add the shell environment variables (if any)
  988               envp.putAll(shellEnv);
  989   
  990               // Add the CGI environment variables
  991               String sPathInfoOrig = null;
  992               String sPathTranslatedOrig = null;
  993               String sPathInfoCGI = null;
  994               String sPathTranslatedCGI = null;
  995               String sCGIFullPath = null;
  996               String sCGIScriptName = null;
  997               String sCGIFullName = null;
  998               String sCGIName = null;
  999               String[] sCGINames;
 1000   
 1001   
 1002               sPathInfoOrig = this.pathInfo;
 1003               sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
 1004   
 1005               sPathTranslatedOrig = req.getPathTranslated();
 1006               sPathTranslatedOrig =
 1007                   sPathTranslatedOrig == null ? "" : sPathTranslatedOrig;
 1008   
 1009               if (webAppRootDir == null ) {
 1010                   // The app has not been deployed in exploded form
 1011                   webAppRootDir = tmpDir.toString();
 1012                   expandCGIScript();
 1013               } 
 1014               
 1015               sCGINames = findCGI(sPathInfoOrig,
 1016                                   webAppRootDir,
 1017                                   contextPath,
 1018                                   servletPath,
 1019                                   cgiPathPrefix);
 1020   
 1021               sCGIFullPath = sCGINames[0];
 1022               sCGIScriptName = sCGINames[1];
 1023               sCGIFullName = sCGINames[2];
 1024               sCGIName = sCGINames[3];
 1025   
 1026               if (sCGIFullPath == null
 1027                   || sCGIScriptName == null
 1028                   || sCGIFullName == null
 1029                   || sCGIName == null) {
 1030                   return false;
 1031               }
 1032   
 1033               envp.put("SERVER_SOFTWARE", "TOMCAT");
 1034   
 1035               envp.put("SERVER_NAME", nullsToBlanks(req.getServerName()));
 1036   
 1037               envp.put("GATEWAY_INTERFACE", "CGI/1.1");
 1038   
 1039               envp.put("SERVER_PROTOCOL", nullsToBlanks(req.getProtocol()));
 1040   
 1041               int port = req.getServerPort();
 1042               Integer iPort = (port == 0 ? new Integer(-1) : new Integer(port));
 1043               envp.put("SERVER_PORT", iPort.toString());
 1044   
 1045               envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod()));
 1046   
 1047               envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI()));
 1048   
 1049   
 1050               /*-
 1051                * PATH_INFO should be determined by using sCGIFullName:
 1052                * 1) Let sCGIFullName not end in a "/" (see method findCGI)
 1053                * 2) Let sCGIFullName equal the pathInfo fragment which
 1054                *    corresponds to the actual cgi script.
 1055                * 3) Thus, PATH_INFO = request.getPathInfo().substring(
 1056                *                      sCGIFullName.length())
 1057                *
 1058                * (see method findCGI, where the real work is done)
 1059                *
 1060                */
 1061               if (pathInfo == null
 1062                   || (pathInfo.substring(sCGIFullName.length()).length() <= 0)) {
 1063                   sPathInfoCGI = "";
 1064               } else {
 1065                   sPathInfoCGI = pathInfo.substring(sCGIFullName.length());
 1066               }
 1067               envp.put("PATH_INFO", sPathInfoCGI);
 1068   
 1069   
 1070               /*-
 1071                * PATH_TRANSLATED must be determined after PATH_INFO (and the
 1072                * implied real cgi-script) has been taken into account.
 1073                *
 1074                * The following example demonstrates:
 1075                *
 1076                * servlet info   = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2
 1077                * cgifullpath    = /servlet/cgigw/dir1/dir2/cgi1
 1078                * path_info      = /trans1/trans2
 1079                * webAppRootDir  = servletContext.getRealPath("/")
 1080                *
 1081                * path_translated = servletContext.getRealPath("/trans1/trans2")
 1082                *
 1083                * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI
 1084                * (unless sPathInfoCGI is null or blank, then the CGI
 1085                * specification dictates that the PATH_TRANSLATED metavariable
 1086                * SHOULD NOT be defined.
 1087                *
 1088                */
 1089               if (sPathInfoCGI != null && !("".equals(sPathInfoCGI))) {
 1090                   sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
 1091               } else {
 1092                   sPathTranslatedCGI = null;
 1093               }
 1094               if (sPathTranslatedCGI == null || "".equals(sPathTranslatedCGI)) {
 1095                   //NOOP
 1096               } else {
 1097                   envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI));
 1098               }
 1099   
 1100   
 1101               envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName));
 1102   
 1103               envp.put("QUERY_STRING", nullsToBlanks(req.getQueryString()));
 1104   
 1105               envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost()));
 1106   
 1107               envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr()));
 1108   
 1109               envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType()));
 1110   
 1111               envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser()));
 1112   
 1113               envp.put("REMOTE_IDENT", ""); //not necessary for full compliance
 1114   
 1115               envp.put("CONTENT_TYPE", nullsToBlanks(req.getContentType()));
 1116   
 1117   
 1118               /* Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined
 1119                * if there is no content, so we cannot put 0 or -1 in as per the
 1120                * Servlet API spec.
 1121                */
 1122               int contentLength = req.getContentLength();
 1123               String sContentLength = (contentLength <= 0 ? "" :
 1124                                        (new Integer(contentLength)).toString());
 1125               envp.put("CONTENT_LENGTH", sContentLength);
 1126   
 1127   
 1128               Enumeration headers = req.getHeaderNames();
 1129               String header = null;
 1130               while (headers.hasMoreElements()) {
 1131                   header = null;
 1132                   header = ((String) headers.nextElement()).toUpperCase();
 1133                   //REMIND: rewrite multiple headers as if received as single
 1134                   //REMIND: change character set
 1135                   //REMIND: I forgot what the previous REMIND means
 1136                   if ("AUTHORIZATION".equalsIgnoreCase(header) ||
 1137                       "PROXY_AUTHORIZATION".equalsIgnoreCase(header)) {
 1138                       //NOOP per CGI specification section 11.2
 1139                   } else {
 1140                       envp.put("HTTP_" + header.replace('-', '_'),
 1141                                req.getHeader(header));
 1142                   }
 1143               }
 1144   
 1145               File fCGIFullPath = new File(sCGIFullPath);
 1146               command = fCGIFullPath.getCanonicalPath();
 1147   
 1148               envp.put("X_TOMCAT_SCRIPT_PATH", command);  //for kicks
 1149   
 1150               envp.put("SCRIPT_FILENAME", command);  //for PHP
 1151   
 1152               this.env = envp;
 1153   
 1154               return true;
 1155   
 1156           }
 1157   
 1158           /**
 1159            * Extracts requested resource from web app archive to context work 
 1160            * directory to enable CGI script to be executed.
 1161            */
 1162           protected void expandCGIScript() {
 1163               StringBuffer srcPath = new StringBuffer();
 1164               StringBuffer destPath = new StringBuffer();
 1165               InputStream is = null;
 1166   
 1167               // paths depend on mapping
 1168               if (cgiPathPrefix == null ) {
 1169                   srcPath.append(pathInfo);
 1170                   is = context.getResourceAsStream(srcPath.toString());
 1171                   destPath.append(tmpDir);
 1172                   destPath.append(pathInfo);
 1173               } else {
 1174                   // essentially same search algorithm as findCGI()
 1175                   srcPath.append(cgiPathPrefix);
 1176                   StringTokenizer pathWalker =
 1177                           new StringTokenizer (pathInfo, "/");
 1178                   // start with first element
 1179                   while (pathWalker.hasMoreElements() && (is == null)) {
 1180                       srcPath.append("/");
 1181                       srcPath.append(pathWalker.nextElement());
 1182                       is = context.getResourceAsStream(srcPath.toString());
 1183                   }
 1184                   destPath.append(tmpDir);
 1185                   destPath.append("/");
 1186                   destPath.append(srcPath);
 1187               }
 1188   
 1189               if (is == null) {
 1190                   // didn't find anything, give up now
 1191                   if (debug >= 2) {
 1192                       log("expandCGIScript: source '" + srcPath + "' not found");
 1193                   }
 1194                    return;
 1195               }
 1196   
 1197               File f = new File(destPath.toString());
 1198               if (f.exists()) {
 1199                   // Don't need to expand if it already exists
 1200                   return;
 1201               } 
 1202   
 1203               // create directories
 1204               String dirPath = new String (destPath.toString().substring(
 1205                       0,destPath.toString().lastIndexOf("/")));
 1206               File dir = new File(dirPath);
 1207               dir.mkdirs();
 1208   
 1209               try {
 1210                   synchronized (expandFileLock) {
 1211                       // make sure file doesn't exist
 1212                       if (f.exists()) {
 1213                           return;
 1214                       }
 1215   
 1216                       // create file
 1217                       if (!f.createNewFile()) {
 1218                           return;
 1219                       }
 1220                       FileOutputStream fos = new FileOutputStream(f);
 1221   
 1222                       // copy data
 1223                       IOTools.flow(is, fos);
 1224                       is.close();
 1225                       fos.close();
 1226                       if (debug >= 2) {
 1227                           log("expandCGIScript: expanded '" + srcPath + "' to '" + destPath + "'");
 1228                       }
 1229                   }
 1230               } catch (IOException ioe) {
 1231                   // delete in case file is corrupted 
 1232                   if (f.exists()) {
 1233                       f.delete();
 1234                   }
 1235               }
 1236           }
 1237   
 1238   
 1239           /**
 1240            * Print important CGI environment information in a easy-to-read HTML
 1241            * table
 1242            *
 1243            * @return  HTML string containing CGI environment info
 1244            *
 1245            */
 1246           public String toString() {
 1247   
 1248               StringBuffer sb = new StringBuffer();
 1249   
 1250               sb.append("<TABLE border=2>");
 1251   
 1252               sb.append("<tr><th colspan=2 bgcolor=grey>");
 1253               sb.append("CGIEnvironment Info</th></tr>");
 1254   
 1255               sb.append("<tr><td>Debug Level</td><td>");
 1256               sb.append(debug);
 1257               sb.append("</td></tr>");
 1258   
 1259               sb.append("<tr><td>Validity:</td><td>");
 1260               sb.append(isValid());
 1261               sb.append("</td></tr>");
 1262   
 1263               if (isValid()) {
 1264                   Enumeration envk = env.keys();
 1265                   while (envk.hasMoreElements()) {
 1266                       String s = (String) envk.nextElement();
 1267                       sb.append("<tr><td>");
 1268                       sb.append(s);
 1269                       sb.append("</td><td>");
 1270                       sb.append(blanksToString((String) env.get(s),
 1271                                                "[will be set to blank]"));
 1272                       sb.append("</td></tr>");
 1273                   }
 1274               }
 1275   
 1276               sb.append("<tr><td colspan=2><HR></td></tr>");
 1277   
 1278               sb.append("<tr><td>Derived Command</td><td>");
 1279               sb.append(nullsToBlanks(command));
 1280               sb.append("</td></tr>");
 1281   
 1282               sb.append("<tr><td>Working Directory</td><td>");
 1283               if (workingDirectory != null) {
 1284                   sb.append(workingDirectory.toString());
 1285               }
 1286               sb.append("</td></tr>");
 1287   
 1288               sb.append("<tr><td>Command Line Params</td><td>");
 1289               for (int i=0; i < cmdLineParameters.size(); i++) {
 1290                   String param = (String) cmdLineParameters.get(i);
 1291                   sb.append("<p>");
 1292                   sb.append(param);
 1293                   sb.append("</p>");
 1294               }
 1295               sb.append("</td></tr>");
 1296   
 1297               sb.append("</TABLE><p>end.");
 1298   
 1299               return sb.toString();
 1300           }
 1301   
 1302   
 1303   
 1304           /**
 1305            * Gets derived command string
 1306            *
 1307            * @return  command string
 1308            *
 1309            */
 1310           protected String getCommand() {
 1311               return command;
 1312           }
 1313   
 1314   
 1315   
 1316           /**
 1317            * Gets derived CGI working directory
 1318            *
 1319            * @return  working directory
 1320            *
 1321            */
 1322           protected File getWorkingDirectory() {
 1323               return workingDirectory;
 1324           }
 1325   
 1326   
 1327   
 1328           /**
 1329            * Gets derived CGI environment
 1330            *
 1331            * @return   CGI environment
 1332            *
 1333            */
 1334           protected Hashtable getEnvironment() {
 1335               return env;
 1336           }
 1337   
 1338   
 1339   
 1340           /**
 1341            * Gets derived CGI query parameters
 1342            *
 1343            * @return   CGI query parameters
 1344            *
 1345            */
 1346           protected ArrayList getParameters() {
 1347               return cmdLineParameters;
 1348           }
 1349   
 1350   
 1351   
 1352           /**
 1353            * Gets validity status
 1354            *
 1355            * @return   true if this environment is valid, false
 1356            *           otherwise
 1357            *
 1358            */
 1359           protected boolean isValid() {
 1360               return valid;
 1361           }
 1362   
 1363   
 1364   
 1365           /**
 1366            * Converts null strings to blank strings ("")
 1367            *
 1368            * @param    s string to be converted if necessary
 1369            * @return   a non-null string, either the original or the empty string
 1370            *           ("") if the original was <code>null</code>
 1371            */
 1372           protected String nullsToBlanks(String s) {
 1373               return nullsToString(s, "");
 1374           }
 1375   
 1376   
 1377   
 1378           /**
 1379            * Converts null strings to another string
 1380            *
 1381            * @param    couldBeNull string to be converted if necessary
 1382            * @param    subForNulls string to return instead of a null string
 1383            * @return   a non-null string, either the original or the substitute
 1384            *           string if the original was <code>null</code>
 1385            */
 1386           protected String nullsToString(String couldBeNull,
 1387                                          String subForNulls) {
 1388               return (couldBeNull == null ? subForNulls : couldBeNull);
 1389           }
 1390   
 1391   
 1392   
 1393           /**
 1394            * Converts blank strings to another string
 1395            *
 1396            * @param    couldBeBlank string to be converted if necessary
 1397            * @param    subForBlanks string to return instead of a blank string
 1398            * @return   a non-null string, either the original or the substitute
 1399            *           string if the original was <code>null</code> or empty ("")
 1400            */
 1401           protected String blanksToString(String couldBeBlank,
 1402                                         String subForBlanks) {
 1403               return (("".equals(couldBeBlank) || couldBeBlank == null)
 1404                       ? subForBlanks
 1405                       : couldBeBlank);
 1406           }
 1407   
 1408   
 1409   
 1410       } //class CGIEnvironment
 1411   
 1412   
 1413   
 1414   
 1415   
 1416   
 1417       /**
 1418        * Encapsulates the knowledge of how to run a CGI script, given the
 1419        * script's desired environment and (optionally) input/output streams
 1420        *
 1421        * <p>
 1422        *
 1423        * Exposes a <code>run</code> method used to actually invoke the
 1424        * CGI.
 1425        *
 1426        * </p>
 1427        * <p>
 1428        *
 1429        * The CGI environment and settings are derived from the information
 1430        * passed to the constuctor.
 1431        *
 1432        * </p>
 1433        * <p>
 1434        *
 1435        * The input and output streams can be set by the <code>setInput</code>
 1436        * and <code>setResponse</code> methods, respectively.
 1437        * </p>
 1438        *
 1439        * @version   $Revision: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
 1440        */
 1441   
 1442       protected class CGIRunner {
 1443   
 1444           /** script/command to be executed */
 1445           private String command = null;
 1446   
 1447           /** environment used when invoking the cgi script */
 1448           private Hashtable env = null;
 1449   
 1450           /** working directory used when invoking the cgi script */
 1451           private File wd = null;
 1452   
 1453           /** command line parameters to be passed to the invoked script */
 1454           private ArrayList params = null;
 1455   
 1456           /** stdin to be passed to cgi script */
 1457           private InputStream stdin = null;
 1458   
 1459           /** response object used to set headers & get output stream */
 1460           private HttpServletResponse response = null;
 1461   
 1462           /** boolean tracking whether this object has enough info to run() */
 1463           private boolean readyToRun = false;
 1464   
 1465   
 1466   
 1467   
 1468           /**
 1469            *  Creates a CGIRunner and initializes its environment, working
 1470            *  directory, and query parameters.
 1471            *  <BR>
 1472            *  Input/output streams (optional) are set using the
 1473            *  <code>setInput</code> and <code>setResponse</code> methods,
 1474            *  respectively.
 1475            *
 1476            * @param  command  string full path to command to be executed
 1477            * @param  env      Hashtable with the desired script environment
 1478            * @param  wd       File with the script's desired working directory
 1479            * @param  params   ArrayList with the script's query command line
 1480            *                  paramters as strings
 1481            */
 1482           protected CGIRunner(String command, Hashtable env, File wd,
 1483                               ArrayList params) {
 1484               this.command = command;
 1485               this.env = env;
 1486               this.wd = wd;
 1487               this.params = params;
 1488               updateReadyStatus();
 1489           }
 1490   
 1491   
 1492   
 1493           /**
 1494            * Checks & sets ready status
 1495            */
 1496           protected void updateReadyStatus() {
 1497               if (command != null
 1498                   && env != null
 1499                   && wd != null
 1500                   && params != null
 1501                   && response != null) {
 1502                   readyToRun = true;
 1503               } else {
 1504                   readyToRun = false;
 1505               }
 1506           }
 1507   
 1508   
 1509   
 1510           /**
 1511            * Gets ready status
 1512            *
 1513            * @return   false if not ready (<code>run</code> will throw
 1514            *           an exception), true if ready
 1515            */
 1516           protected boolean isReady() {
 1517               return readyToRun;
 1518           }
 1519   
 1520   
 1521   
 1522           /**
 1523            * Sets HttpServletResponse object used to set headers and send
 1524            * output to
 1525            *
 1526            * @param  response   HttpServletResponse to be used
 1527            *
 1528            */
 1529           protected void setResponse(HttpServletResponse response) {
 1530               this.response = response;
 1531               updateReadyStatus();
 1532           }
 1533   
 1534   
 1535   
 1536           /**
 1537            * Sets standard input to be passed on to the invoked cgi script
 1538            *
 1539            * @param  stdin   InputStream to be used
 1540            *
 1541            */
 1542           protected void setInput(InputStream stdin) {
 1543               this.stdin = stdin;
 1544               updateReadyStatus();
 1545           }
 1546   
 1547   
 1548   
 1549           /**
 1550            * Converts a Hashtable to a String array by converting each
 1551            * key/value pair in the Hashtable to a String in the form
 1552            * "key=value" (hashkey + "=" + hash.get(hashkey).toString())
 1553            *
 1554            * @param  h   Hashtable to convert
 1555            *
 1556            * @return     converted string array
 1557            *
 1558            * @exception  NullPointerException   if a hash key has a null value
 1559            *
 1560            */
 1561           protected String[] hashToStringArray(Hashtable h)
 1562               throws NullPointerException {
 1563               Vector<String> v = new Vector<String>();
 1564               Enumeration e = h.keys();
 1565               while (e.hasMoreElements()) {
 1566                   String k = e.nextElement().toString();
 1567                   v.add(k + "=" + h.get(k));
 1568               }
 1569               String[] strArr = new String[v.size()];
 1570               v.copyInto(strArr);
 1571               return strArr;
 1572           }
 1573   
 1574   
 1575   
 1576           /**
 1577            * Executes a CGI script with the desired environment, current working
 1578            * directory, and input/output streams
 1579            *
 1580            * <p>
 1581            * This implements the following CGI specification recommedations:
 1582            * <UL>
 1583            * <LI> Servers SHOULD provide the "<code>query</code>" component of
 1584            *      the script-URI as command-line arguments to scripts if it
 1585            *      does not contain any unencoded "=" characters and the
 1586            *      command-line arguments can be generated in an unambiguous
 1587            *      manner.
 1588            * <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
 1589            *      of the "<code>auth-scheme</code>" token of the
 1590            *      "<code>Authorization</code>" if it was supplied as part of the
 1591            *      request header.  See <code>getCGIEnvironment</code> method.
 1592            * <LI> Where applicable, servers SHOULD set the current working
 1593            *      directory to the directory in which the script is located
 1594            *      before invoking it.
 1595            * <LI> Server implementations SHOULD define their behavior for the
 1596            *      following cases:
 1597            *     <ul>
 1598            *     <LI> <u>Allowed characters in pathInfo</u>:  This implementation
 1599            *             does not allow ASCII NUL nor any character which cannot
 1600            *             be URL-encoded according to internet standards;
 1601            *     <LI> <u>Allowed characters in path segments</u>: This
 1602            *             implementation does not allow non-terminal NULL
 1603            *             segments in the the path -- IOExceptions may be thrown;
 1604            *     <LI> <u>"<code>.</code>" and "<code>..</code>" path
 1605            *             segments</u>:
 1606            *             This implementation does not allow "<code>.</code>" and
 1607            *             "<code>..</code>" in the the path, and such characters
 1608            *             will result in an IOException being thrown;
 1609            *     <LI> <u>Implementation limitations</u>: This implementation
 1610            *             does not impose any limitations except as documented
 1611            *             above.  This implementation may be limited by the
 1612            *             servlet container used to house this implementation.
 1613            *             In particular, all the primary CGI variable values
 1614            *             are derived either directly or indirectly from the
 1615            *             container's implementation of the Servlet API methods.
 1616            *     </ul>
 1617            * </UL>
 1618            * </p>
 1619            *
 1620            * @exception IOException if problems during reading/writing occur
 1621            *
 1622            * @see    java.lang.Runtime#exec(String command, String[] envp,
 1623            *                                File dir)
 1624            */
 1625           protected void run() throws IOException {
 1626   
 1627               /*
 1628                * REMIND:  this method feels too big; should it be re-written?
 1629                */
 1630   
 1631               if (!isReady()) {
 1632                   throw new IOException(this.getClass().getName()
 1633                                         + ": not ready to run.");
 1634               }
 1635   
 1636               if (debug >= 1 ) {
 1637                   log("runCGI(envp=[" + env + "], command=" + command + ")");
 1638               }
 1639   
 1640               if ((command.indexOf(File.separator + "." + File.separator) >= 0)
 1641                   || (command.indexOf(File.separator + "..") >= 0)
 1642                   || (command.indexOf(".." + File.separator) >= 0)) {
 1643                   throw new IOException(this.getClass().getName()
 1644                                         + "Illegal Character in CGI command "
 1645                                         + "path ('.' or '..') detected.  Not "
 1646                                         + "running CGI [" + command + "].");
 1647               }
 1648   
 1649               /* original content/structure of this section taken from
 1650                * http://developer.java.sun.com/developer/
 1651                *                               bugParade/bugs/4216884.html
 1652                * with major modifications by Martin Dengler
 1653                */
 1654               Runtime rt = null;
 1655               InputStream cgiOutput = null;
 1656               BufferedReader commandsStdErr = null;
 1657               BufferedOutputStream commandsStdIn = null;
 1658               Process proc = null;
 1659               int bufRead = -1;
 1660   
 1661               //create query arguments
 1662               StringBuffer cmdAndArgs = new StringBuffer();
 1663               if (command.indexOf(" ") < 0) {
 1664                   cmdAndArgs.append(command);
 1665               } else {
 1666                   // Spaces used as delimiter, so need to use quotes
 1667                   cmdAndArgs.append("\"");
 1668                   cmdAndArgs.append(command);
 1669                   cmdAndArgs.append("\"");
 1670               }
 1671   
 1672               for (int i=0; i < params.size(); i++) {
 1673                   cmdAndArgs.append(" ");
 1674                   String param = (String) params.get(i);
 1675                   if (param.indexOf(" ") < 0) {
 1676                       cmdAndArgs.append(param);
 1677                   } else {
 1678                       // Spaces used as delimiter, so need to use quotes
 1679                       cmdAndArgs.append("\"");
 1680                       cmdAndArgs.append(param);
 1681                       cmdAndArgs.append("\"");
 1682                   }
 1683               }
 1684   
 1685               StringBuffer command = new StringBuffer(cgiExecutable);
 1686               command.append(" ");
 1687               command.append(cmdAndArgs.toString());
 1688               cmdAndArgs = command;
 1689   
 1690               try {
 1691                   rt = Runtime.getRuntime();
 1692                   proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
 1693       
 1694                   String sContentLength = (String) env.get("CONTENT_LENGTH");
 1695   
 1696                   if(!"".equals(sContentLength)) {
 1697                       commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
 1698                       IOTools.flow(stdin, commandsStdIn);
 1699                       commandsStdIn.flush();
 1700                       commandsStdIn.close();
 1701                   }
 1702   
 1703                   /* we want to wait for the process to exit,  Process.waitFor()
 1704                    * is useless in our situation; see
 1705                    * http://developer.java.sun.com/developer/
 1706                    *                               bugParade/bugs/4223650.html
 1707                    */
 1708   
 1709                   boolean isRunning = true;
 1710                   commandsStdErr = new BufferedReader
 1711                       (new InputStreamReader(proc.getErrorStream()));
 1712                   final BufferedReader stdErrRdr = commandsStdErr ;
 1713   
 1714                   new Thread() {
 1715                       public void run () {
 1716                           sendToLog(stdErrRdr) ;
 1717                       } ;
 1718                   }.start() ;
 1719   
 1720                   InputStream cgiHeaderStream =
 1721                       new HTTPHeaderInputStream(proc.getInputStream());
 1722                   BufferedReader cgiHeaderReader =
 1723                       new BufferedReader(new InputStreamReader(cgiHeaderStream));
 1724               
 1725                   while (isRunning) {
 1726                       try {
 1727                           //set headers
 1728                           String line = null;
 1729                           while (((line = cgiHeaderReader.readLine()) != null)
 1730                                  && !("".equals(line))) {
 1731                               if (debug >= 2) {
 1732                                   log("runCGI: addHeader(\"" + line + "\")");
 1733                               }
 1734                               if (line.startsWith("HTTP")) {
 1735                                   response.setStatus(getSCFromHttpStatusLine(line));
 1736                               } else if (line.indexOf(":") >= 0) {
 1737                                   String header =
 1738                                       line.substring(0, line.indexOf(":")).trim();
 1739                                   String value =
 1740                                       line.substring(line.indexOf(":") + 1).trim(); 
 1741                                   if (header.equalsIgnoreCase("status")) {
 1742                                       response.setStatus(getSCFromCGIStatusHeader(value));
 1743                                   } else {
 1744                                       response.addHeader(header , value);
 1745                                   }
 1746                               } else {
 1747                                   log("runCGI: bad header line \"" + line + "\"");
 1748                               }
 1749                           }
 1750       
 1751                           //write output
 1752                           byte[] bBuf = new byte[2048];
 1753       
 1754                           OutputStream out = response.getOutputStream();
 1755                           cgiOutput = proc.getInputStream();
 1756       
 1757                           try {
 1758                               while ((bufRead = cgiOutput.read(bBuf)) != -1) {
 1759                                   if (debug >= 4) {
 1760                                       log("runCGI: output " + bufRead +
 1761                                           " bytes of data");
 1762                                   }
 1763                                   out.write(bBuf, 0, bufRead);
 1764                               }
 1765                           } finally {
 1766                               // Attempt to consume any leftover byte if something bad happens,
 1767                               // such as a socket disconnect on the servlet side; otherwise, the
 1768                               // external process could hang
 1769                               if (bufRead != -1) {
 1770                                   while ((bufRead = cgiOutput.read(bBuf)) != -1) {}
 1771                               }
 1772                           }
 1773           
 1774                           proc.exitValue(); // Throws exception if alive
 1775       
 1776                           isRunning = false;
 1777       
 1778                       } catch (IllegalThreadStateException e) {
 1779                           try {
 1780                               Thread.sleep(500);
 1781                           } catch (InterruptedException ignored) {
 1782                           }
 1783                       }
 1784                   } //replacement for Process.waitFor()
 1785       
 1786                   // Close the output stream used
 1787                   cgiOutput.close();
 1788               }
 1789               catch (IOException e){
 1790                   log ("Caught exception " + e);
 1791                   throw e;
 1792               }
 1793               finally{
 1794                   if (debug > 4) {
 1795                       log ("Running finally block");
 1796                   }
 1797                   if (proc != null){
 1798                       proc.destroy();
 1799                       proc = null;
 1800                   }
 1801               }
 1802           }
 1803   
 1804           /**
 1805            * Parses the Status-Line and extracts the status code.
 1806            * 
 1807            * @param line The HTTP Status-Line (RFC2616, section 6.1)
 1808            * @return The extracted status code or the code representing an
 1809            * internal error if a valid status code cannot be extracted. 
 1810            */
 1811           private int getSCFromHttpStatusLine(String line) {
 1812               int statusStart = line.indexOf(' ') + 1;
 1813               
 1814               if (statusStart < 1 || line.length() < statusStart + 3) {
 1815                   // Not a valid HTTP Status-Line
 1816                   log ("runCGI: invalid HTTP Status-Line:" + line);
 1817                   return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 1818               }
 1819               
 1820               String status = line.substring(statusStart, statusStart + 3);
 1821               
 1822               int statusCode;
 1823               try {
 1824                   statusCode = Integer.parseInt(status);
 1825               } catch (NumberFormatException nfe) {
 1826                   // Not a valid status code
 1827                   log ("runCGI: invalid status code:" + status);
 1828                   return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 1829               }
 1830               
 1831               return statusCode;
 1832           }
 1833   
 1834           /**
 1835            * Parses the CGI Status Header value and extracts the status code.
 1836            * 
 1837            * @param value The CGI Status value of the form <code>
 1838            *             digit digit digit SP reason-phrase</code>
 1839            * @return The extracted status code or the code representing an
 1840            * internal error if a valid status code cannot be extracted. 
 1841            */
 1842           private int getSCFromCGIStatusHeader(String value) {
 1843               if (value.length() < 3) {
 1844                   // Not a valid status value
 1845                   log ("runCGI: invalid status value:" + value);
 1846                   return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 1847               }
 1848               
 1849               String status = value.substring(0, 3);
 1850               
 1851               int statusCode;
 1852               try {
 1853                   statusCode = Integer.parseInt(status);
 1854               } catch (NumberFormatException nfe) {
 1855                   // Not a valid status code
 1856                   log ("runCGI: invalid status code:" + status);
 1857                   return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 1858               }
 1859               
 1860               return statusCode;
 1861           }
 1862           
 1863           private void sendToLog(BufferedReader rdr) {
 1864               String line = null;
 1865               int lineCount = 0 ;
 1866               try {
 1867                   while ((line = rdr.readLine()) != null) {
 1868                       log("runCGI (stderr):" +  line) ;
 1869                       lineCount++ ;
 1870                   }
 1871               } catch (IOException e) {
 1872                   log("sendToLog error", e) ;
 1873               } finally {
 1874                   try {
 1875                       rdr.close() ;
 1876                   } catch (IOException ce) {
 1877                       log("sendToLog error", ce) ;
 1878                   } ;
 1879               } ;
 1880               if ( lineCount > 0 && debug > 2) {
 1881                   log("runCGI: " + lineCount + " lines received on stderr") ;
 1882               } ;
 1883           }
 1884       } //class CGIRunner
 1885   
 1886       /**
 1887        * This is an input stream specifically for reading HTTP headers. It reads
 1888        * upto and including the two blank lines terminating the headers. It
 1889        * allows the content to be read using bytes or characters as appropriate.
 1890        */
 1891       protected class HTTPHeaderInputStream extends InputStream {
 1892           private static final int STATE_CHARACTER = 0;
 1893           private static final int STATE_FIRST_CR = 1;
 1894           private static final int STATE_FIRST_LF = 2;
 1895           private static final int STATE_SECOND_CR = 3;
 1896           private static final int STATE_HEADER_END = 4;
 1897           
 1898           private InputStream input;
 1899           private int state;
 1900           
 1901           HTTPHeaderInputStream(InputStream theInput) {
 1902               input = theInput;
 1903               state = STATE_CHARACTER;
 1904           }
 1905   
 1906           /**
 1907            * @see java.io.InputStream#read()
 1908            */
 1909           public int read() throws IOException {
 1910               if (state == STATE_HEADER_END) {
 1911                   return -1;
 1912               }
 1913   
 1914               int i = input.read();
 1915   
 1916               // Update the state
 1917               // State machine looks like this
 1918               //
 1919               //    -------->--------
 1920               //   |      (CR)       |
 1921               //   |                 |
 1922               //  CR1--->---         |
 1923               //   |        |        |
 1924               //   ^(CR)    |(LF)    |
 1925               //   |        |        |
 1926               // CHAR--->--LF1--->--EOH
 1927               //      (LF)  |  (LF)  |
 1928               //            |(CR)    ^(LF)
 1929               //            |        |
 1930               //          (CR2)-->---
 1931               
 1932               if (i == 10) {
 1933                   // LF
 1934                   switch(state) {
 1935                       case STATE_CHARACTER:
 1936                           state = STATE_FIRST_LF;
 1937                           break;
 1938                       case STATE_FIRST_CR:
 1939                           state = STATE_FIRST_LF;
 1940                           break;
 1941                       case STATE_FIRST_LF:
 1942                       case STATE_SECOND_CR:
 1943                           state = STATE_HEADER_END;
 1944                           break;
 1945                   }
 1946   
 1947               } else if (i == 13) {
 1948                   // CR
 1949                   switch(state) {
 1950                       case STATE_CHARACTER:
 1951                           state = STATE_FIRST_CR;
 1952                           break;
 1953                       case STATE_FIRST_CR:
 1954                           state = STATE_HEADER_END;
 1955                           break;
 1956                       case STATE_FIRST_LF:
 1957                           state = STATE_SECOND_CR;
 1958                           break;
 1959                   }
 1960   
 1961               } else {
 1962                   state = STATE_CHARACTER;
 1963               }
 1964               
 1965               return i;            
 1966           }
 1967       }  // class HTTPHeaderInputStream
 1968   
 1969   } //class CGIServlet

Save This Page
Home » apache-tomcat-6.0.16-src » org.apache » catalina » servlets » [javadoc | source]