ProcessHelperpublic 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.
|
Fields Summary |
---|
private static com.sun.org.apache.commons.logging.Log | log | private String | commandscript/command to be executed | private Hashtable | envenvironment used when invoking the cgi script | private File | wdworking directory used when invoking the cgi script | private Hashtable | paramsquery parameters to be passed to the invoked script | private InputStream | stdinstdin to be passed to cgi script | private HttpServletResponse | responseresponse object used to set headers & get output stream | private boolean | readyToRunboolean tracking whether this object has enough info to run() | private int | debugthe debugging detail level for this instance. | private int | iClientInputTimeoutthe 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.
this.command = command;
this.env = env;
this.wd = wd;
this.params = params;
updateReadyStatus();
|
Methods Summary |
---|
public int | getIClientInputTimeout()
return iClientInputTimeout;
| protected java.lang.String | getPostInput(java.util.Hashtable params)Gets a string for input to a POST 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())
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 boolean | isReady()Gets ready status
return readyToRun;
| private void | log(java.lang.String s)
if (log.isDebugEnabled())
log.debug(s);
| public void | run()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)
/*
* 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 void | setIClientInputTimeout(int iClientInputTimeout)
this.iClientInputTimeout = iClientInputTimeout;
| public void | setInput(java.io.InputStream stdin)Sets standard input to be passed on to the invoked cgi script
this.stdin = stdin;
updateReadyStatus();
| public void | setResponse(javax.servlet.http.HttpServletResponse response)Sets HttpServletResponse object used to set headers and send
output to
this.response = response;
updateReadyStatus();
| protected void | updateReadyStatus()Checks & sets ready status
if (command != null
&& env != null
&& wd != null
&& params != null
&& response != null) {
readyToRun = true;
} else {
readyToRun = false;
}
|
|