FileDocCategorySizeDatePackage
ProcessHelper.javaAPI DocGlassfish v2 API18399Fri May 04 22:32:30 BST 2007org.apache.catalina.util

ProcessHelper

public class ProcessHelper extends Object
Encapsulates the knowledge of how to run a CGI script, given the script's desired environment and (optionally) input/output streams

Exposes a run method used to actually invoke the CGI.

The CGI environment and settings are derived from the information passed to the constuctor.

The input and output streams can be set by the setInput and setResponse methods, respectively.

author
Martin Dengler [root@martindengler.com]
version
$Revision: 1.5 $, $Date: 2007/05/05 05:32:30 $

Fields Summary
private static com.sun.org.apache.commons.logging.Log
log
private String
command
script/command to be executed
private Hashtable
env
environment used when invoking the cgi script
private File
wd
working directory used when invoking the cgi script
private Hashtable
params
query parameters to be passed to the invoked script
private InputStream
stdin
stdin to be passed to cgi script
private HttpServletResponse
response
response object used to set headers & get output stream
private boolean
readyToRun
boolean tracking whether this object has enough info to run()
private int
debug
the debugging detail level for this instance.
private int
iClientInputTimeout
the time in ms to wait for the client to send us CGI input data
Constructors Summary
public ProcessHelper(String command, Hashtable env, File wd, Hashtable params)
Creates a ProcessHelper and initializes its environment, working directory, and query parameters.
Input/output streams (optional) are set using the setInput and setResponse methods, respectively.

param
command string full path to command to be executed
param
env Hashtable with the desired script environment
param
wd File with the script's desired working directory
param
params Hashtable with the script's query parameters
param
res HttpServletResponse object for setting headers based on CGI script output


                                                                                                                          
      
                      
    this.command = command;
    this.env = env;
    this.wd = wd;
    this.params = params;
    updateReadyStatus();
Methods Summary
public intgetIClientInputTimeout()

        return iClientInputTimeout;
    
protected java.lang.StringgetPostInput(java.util.Hashtable params)
Gets a string for input to a POST cgi script

param
params Hashtable of query parameters to be passed to the CGI script
return
for use as input to the CGI script

    String lineSeparator = System.getProperty("line.separator");
    Enumeration paramNames = params.keys();
    StringBuffer postInput = new StringBuffer("");
    StringBuffer qs = new StringBuffer("");
    if (paramNames != null && paramNames.hasMoreElements()) {
        while (paramNames.hasMoreElements()) {
            String k = (String) paramNames.nextElement();
            String v = params.get(k).toString();
            if ((k.indexOf("=") < 0) && (v.indexOf("=") < 0)) {
                postInput.append(k);
                qs.append(k);
                postInput.append("=");
                qs.append("=");
                postInput.append(v);
                qs.append(v);
                postInput.append(lineSeparator);
                qs.append("&");
            }
        }
    }
    qs.append(lineSeparator);
    return qs.append(postInput).toString();
private java.lang.String[]hashToStringArray(java.util.Hashtable h)
Converts a Hashtable to a String array by converting each key/value pair in the Hashtable to a String in the form "key=value" (hashkey + "=" + hash.get(hashkey).toString())

param
h Hashtable to convert
return
converted string array
exception
NullPointerException if a hash key has a null value

    Vector v = new Vector();
    Enumeration e = h.keys();
    while (e.hasMoreElements()) {
        String k = e.nextElement().toString();
        v.add(k + "=" + h.get(k));
    }
    String[] strArr = new String[v.size()];
    v.copyInto(strArr);
    return strArr;
public booleanisReady()
Gets ready status

return
false if not ready (run will throw an exception), true if ready

    return readyToRun;
private voidlog(java.lang.String s)

    if (log.isDebugEnabled())
        log.debug(s);
public voidrun()
Executes a process script with the desired environment, current working directory, and input/output streams

This implements the following CGI specification recommedations:

  • Servers SHOULD provide the "query" component of the script-URI as command-line arguments to scripts if it does not contain any unencoded "=" characters and the command-line arguments can be generated in an unambiguous manner.
  • Servers SHOULD set the AUTH_TYPE metavariable to the value of the "auth-scheme" token of the "Authorization" if it was supplied as part of the request header. See getCGIEnvironment method.
  • Where applicable, servers SHOULD set the current working directory to the directory in which the script is located before invoking it.
  • Server implementations SHOULD define their behavior for the following cases:
    • Allowed characters in pathInfo: This implementation does not allow ASCII NUL nor any character which cannot be URL-encoded according to internet standards;
    • Allowed characters in path segments: This implementation does not allow non-terminal NULL segments in the the path -- IOExceptions may be thrown;
    • "." and ".." path segments: This implementation does not allow "." and ".." in the the path, and such characters will result in an IOException being thrown;
    • Implementation limitations: This implementation does not impose any limitations except as documented above. This implementation may be limited by the servlet container used to house this implementation. In particular, all the primary CGI variable values are derived either directly or indirectly from the container's implementation of the Servlet API methods.

For more information, see java.lang.Runtime#exec(String command, String[] envp, File dir)

exception
IOException if problems during reading/writing occur


    /*
     * REMIND:  this method feels too big; should it be re-written?
     */

    if (!isReady()) {
        throw new IOException(this.getClass().getName()
                              + ": not ready to run.");
    }

    if (debug >= 1 ) {
        log("runCGI(envp=[" + env + "], command=" + command + ")");
    }

    if ((command.indexOf(File.separator + "." + File.separator) >= 0)
        || (command.indexOf(File.separator + "..") >= 0)
        || (command.indexOf(".." + File.separator) >= 0)) {
        throw new IOException(this.getClass().getName()
                              + "Illegal Character in CGI command "
                              + "path ('.' or '..') detected.  Not "
                              + "running CGI [" + command + "].");
    }

    /* original content/structure of this section taken from
     * http://developer.java.sun.com/developer/
     *                               bugParade/bugs/4216884.html
     * with major modifications by Martin Dengler
     */
    Runtime rt = null;
    BufferedReader commandsStdOut = null;
    BufferedReader commandsStdErr = null;
    BufferedOutputStream commandsStdIn = null;
    Process proc = null;
    byte[] bBuf = new byte[1024];
    char[] cBuf = new char[1024];
    int bufRead = -1;

    //create query arguments
    Enumeration paramNames = params.keys();
    StringBuffer cmdAndArgs = new StringBuffer(command);
    if (paramNames != null && paramNames.hasMoreElements()) {
        cmdAndArgs.append(" ");
        while (paramNames.hasMoreElements()) {
            String k = (String) paramNames.nextElement();
            String v = params.get(k).toString();
            if ((k.indexOf("=") < 0) && (v.indexOf("=") < 0)) {
                cmdAndArgs.append(k);
                cmdAndArgs.append("=");
                v = java.net.URLEncoder.encode(v);
                cmdAndArgs.append(v);
                cmdAndArgs.append(" ");
            }
        }
    }

    String postIn = getPostInput(params);
    int contentLength = (postIn.length()
            + System.getProperty("line.separator").length());
    if ("POST".equals(env.get("REQUEST_METHOD"))) {
        env.put("CONTENT_LENGTH", Integer.valueOf(contentLength));
    }

    rt = Runtime.getRuntime();
    proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);


    /*
     * provide input to cgi
     * First  -- parameters
     * Second -- any remaining input
     */
    commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
    if (debug >= 2 ) {
        log("runCGI stdin=[" + stdin + "], qs="
            + env.get("QUERY_STRING"));
    }
    if ("POST".equals(env.get("REQUEST_METHOD"))) {
        if (debug >= 2) {
            log("runCGI: writing ---------------\n");
            log(postIn);
            log("runCGI: new content_length=" + contentLength
                + "---------------\n");
        }
        commandsStdIn.write(postIn.getBytes());
    }
    if (stdin != null) {
        //REMIND: document this
        /* assume if nothing is available after a time, that nothing is
         * coming...
         */
        if (stdin.available() <= 0) {
            if (debug >= 2 ) {
                log("runCGI stdin is NOT available ["
                    + stdin.available() + "]");
            }
            try {
                Thread.currentThread().sleep(iClientInputTimeout);
            } catch (InterruptedException ignored) {
            }
        }
        if (stdin.available() > 0) {
            if (debug >= 2 ) {
                log("runCGI stdin IS available ["
                    + stdin.available() + "]");
            }
            bBuf = new byte[1024];
            bufRead = -1;
            try {
                while ((bufRead = stdin.read(bBuf)) != -1) {
                    if (debug >= 2 ) {
                        log("runCGI: read [" + bufRead
                            + "] bytes from stdin");
                    }
                    commandsStdIn.write(bBuf, 0, bufRead);
                }
                if (debug >= 2 ) {
                    log("runCGI: DONE READING from stdin");
                }
            } catch (IOException ioe) {
                //REMIND: replace with logging
                //REMIND: should I throw this exception?
                log("runCGI: couldn't write all bytes.");
                ioe.printStackTrace();
            }
        }
    }
    commandsStdIn.flush();
    commandsStdIn.close();

    /* we want to wait for the process to exit,  Process.waitFor()
     * is useless in our situation; see
     * http://developer.java.sun.com/developer/
     *                               bugParade/bugs/4223650.html
     */

    boolean isRunning = true;
    commandsStdOut = new BufferedReader
        (new InputStreamReader(proc.getInputStream()));
    commandsStdErr = new BufferedReader
        (new InputStreamReader(proc.getErrorStream()));
    BufferedWriter servletContainerStdout = null;

    try {
        if (response.getOutputStream() != null) {
            servletContainerStdout =
                new BufferedWriter(new OutputStreamWriter
                    (response.getOutputStream()));
        }
    } catch (IOException ignored) {
        //NOOP: no output will be written
    }

    while (isRunning) {

        try {
            //read stderr first
            cBuf = new char[1024];
            while ((bufRead = commandsStdErr.read(cBuf)) != -1) {
                if (servletContainerStdout != null) {
                    servletContainerStdout.write(cBuf, 0, bufRead);
                }
            }

            //set headers
            String line = null;
            while (((line = commandsStdOut.readLine()) != null)
                   && !("".equals(line))) {
                if (debug >= 2) {
                    log("runCGI: addHeader(\"" + line + "\")");
                }
                if (line.startsWith("HTTP")) {
                    //TODO: should set status codes (NPH support)
                    /*
                     * response.setStatus(getStatusCode(line));
                     */
                } else {
                    response.addHeader
                        (line.substring(0, line.indexOf(":")).trim(),
                         line.substring(line.indexOf(":") + 1).trim());
                }
            }

            //write output
            cBuf = new char[1024];
            while ((bufRead = commandsStdOut.read(cBuf)) != -1) {
                if (servletContainerStdout != null) {
                    if (debug >= 4) {
                        log("runCGI: write(\"" + cBuf + "\")");
                    }
                    servletContainerStdout.write(cBuf, 0, bufRead);
                }
            }

            if (servletContainerStdout != null) {
                servletContainerStdout.flush();
            }

            proc.exitValue(); // Throws exception if alive

            isRunning = false;

        } catch (IllegalThreadStateException e) {
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException ignored) {
            }
        }
    } //replacement for Process.waitFor()


public voidsetIClientInputTimeout(int iClientInputTimeout)

        this.iClientInputTimeout = iClientInputTimeout;
    
public voidsetInput(java.io.InputStream stdin)
Sets standard input to be passed on to the invoked cgi script

param
stdin InputStream to be used

    this.stdin = stdin;
    updateReadyStatus();
public voidsetResponse(javax.servlet.http.HttpServletResponse response)
Sets HttpServletResponse object used to set headers and send output to

param
response HttpServletResponse to be used

    this.response = response;
    updateReadyStatus();
protected voidupdateReadyStatus()
Checks & sets ready status

    if (command != null
        && env != null
        && wd != null
        && params != null
        && response != null) {
        readyToRun = true;
    } else {
        readyToRun = false;
    }