|
end.");
return sb.toString();
}
/**
* Gets derived command string
*
* @return command string
*
*/
protected String getCommand() {
return command;
}
/**
* Gets derived CGI working directory
*
* @return working directory
*
*/
protected File getWorkingDirectory() {
return workingDirectory;
}
/**
* Gets derived CGI environment
*
* @return CGI environment
*
*/
protected Hashtable getEnvironment() {
return env;
}
/**
* Gets derived CGI query parameters
*
* @return CGI query parameters
*
*/
protected ArrayList getParameters() {
return cmdLineParameters;
}
/**
* Gets validity status
*
* @return true if this environment is valid, false
* otherwise
*
*/
protected boolean isValid() {
return valid;
}
/**
* Converts null strings to blank strings ("")
*
* @param s string to be converted if necessary
* @return a non-null string, either the original or the empty string
* ("") if the original was <code>null
*/
protected String nullsToBlanks(String s) {
return nullsToString(s, "");
}
/**
* Converts null strings to another string
*
* @param couldBeNull string to be converted if necessary
* @param subForNulls string to return instead of a null string
* @return a non-null string, either the original or the substitute
* string if the original was <code>null
*/
protected String nullsToString(String couldBeNull,
String subForNulls) {
return (couldBeNull == null ? subForNulls : couldBeNull);
}
/**
* Converts blank strings to another string
*
* @param couldBeBlank string to be converted if necessary
* @param subForBlanks string to return instead of a blank string
* @return a non-null string, either the original or the substitute
* string if the original was <code>null or empty ("")
*/
protected String blanksToString(String couldBeBlank,
String subForBlanks) {
return (("".equals(couldBeBlank) || couldBeBlank == null)
? subForBlanks
: couldBeBlank);
}
} //class CGIEnvironment
/**
* Encapsulates the knowledge of how to run a CGI script, given the
* script's desired environment and (optionally) input/output streams
*
* <p>
*
* Exposes a <code>run method used to actually invoke the
* CGI.
*
* </p>
* <p>
*
* The CGI environment and settings are derived from the information
* passed to the constuctor.
*
* </p>
* <p>
*
* The input and output streams can be set by the <code>setInput
* and <code>setResponse methods, respectively.
* </p>
*
* @version $Revision: 595801 $, $Date: 2007-11-16 21:07:07 +0100 (ven., 16 nov. 2007) $
*/
protected class CGIRunner {
/** script/command to be executed */
private String command = null;
/** environment used when invoking the cgi script */
private Hashtable env = null;
/** working directory used when invoking the cgi script */
private File wd = null;
/** command line parameters to be passed to the invoked script */
private ArrayList params = null;
/** stdin to be passed to cgi script */
private InputStream stdin = null;
/** response object used to set headers & get output stream */
private HttpServletResponse response = null;
/** boolean tracking whether this object has enough info to run() */
private boolean readyToRun = false;
/**
* Creates a CGIRunner and initializes its environment, working
* directory, and query parameters.
* <BR>
* Input/output streams (optional) are set using the
* <code>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 ArrayList with the script's query command line
* paramters as strings
*/
protected CGIRunner(String command, Hashtable env, File wd,
ArrayList params) {
this.command = command;
this.env = env;
this.wd = wd;
this.params = params;
updateReadyStatus();
}
/**
* Checks & sets ready status
*/
protected void updateReadyStatus() {
if (command != null
&& env != null
&& wd != null
&& params != null
&& response != null) {
readyToRun = true;
} else {
readyToRun = false;
}
}
/**
* Gets ready status
*
* @return false if not ready (<code>run will throw
* an exception), true if ready
*/
protected boolean isReady() {
return readyToRun;
}
/**
* Sets HttpServletResponse object used to set headers and send
* output to
*
* @param response HttpServletResponse to be used
*
*/
protected void setResponse(HttpServletResponse response) {
this.response = response;
updateReadyStatus();
}
/**
* Sets standard input to be passed on to the invoked cgi script
*
* @param stdin InputStream to be used
*
*/
protected void setInput(InputStream stdin) {
this.stdin = stdin;
updateReadyStatus();
}
/**
* 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
*
*/
protected String[] hashToStringArray(Hashtable h)
throws NullPointerException {
Vector<String> v = new Vectorquery
" 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.
* <LI> Servers SHOULD set the AUTH_TYPE metavariable to the value
* of the "<code>auth-scheme" token of the
* "<code>Authorization" if it was supplied as part of the
* request header. See <code>getCGIEnvironment method.
* <LI> Where applicable, servers SHOULD set the current working
* directory to the directory in which the script is located
* before invoking it.
* <LI> Server implementations SHOULD define their behavior for the
* following cases:
* <ul>
* <LI> Allowed characters in pathInfo: This implementation
* does not allow ASCII NUL nor any character which cannot
* be URL-encoded according to internet standards;
* <LI> Allowed characters in path segments: This
* implementation does not allow non-terminal NULL
* segments in the the path -- IOExceptions may be thrown;
* <LI> ".
" and "..
" path
* segments</u>:
* This implementation does not allow "<code>." and
* "<code>.." in the the path, and such characters
* will result in an IOException being thrown;
* <LI> 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.
* </ul>
* </UL>
* </p>
*
* @exception IOException if problems during reading/writing occur
*
* @see java.lang.Runtime#exec(String command, String[] envp,
* File dir)
*/
protected void run() throws IOException {
/*
* 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;
InputStream cgiOutput = null;
BufferedReader commandsStdErr = null;
BufferedOutputStream commandsStdIn = null;
Process proc = null;
int bufRead = -1;
//create query arguments
StringBuffer cmdAndArgs = new StringBuffer();
if (command.indexOf(" ") < 0) {
cmdAndArgs.append(command);
} else {
// Spaces used as delimiter, so need to use quotes
cmdAndArgs.append("\"");
cmdAndArgs.append(command);
cmdAndArgs.append("\"");
}
for (int i=0; i < params.size(); i++) {
cmdAndArgs.append(" ");
String param = (String) params.get(i);
if (param.indexOf(" ") < 0) {
cmdAndArgs.append(param);
} else {
// Spaces used as delimiter, so need to use quotes
cmdAndArgs.append("\"");
cmdAndArgs.append(param);
cmdAndArgs.append("\"");
}
}
StringBuffer command = new StringBuffer(cgiExecutable);
command.append(" ");
command.append(cmdAndArgs.toString());
cmdAndArgs = command;
try {
rt = Runtime.getRuntime();
proc = rt.exec(cmdAndArgs.toString(), hashToStringArray(env), wd);
String sContentLength = (String) env.get("CONTENT_LENGTH");
if(!"".equals(sContentLength)) {
commandsStdIn = new BufferedOutputStream(proc.getOutputStream());
IOTools.flow(stdin, commandsStdIn);
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;
commandsStdErr = new BufferedReader
(new InputStreamReader(proc.getErrorStream()));
final BufferedReader stdErrRdr = commandsStdErr ;
new Thread() {
public void run () {
sendToLog(stdErrRdr) ;
} ;
}.start() ;
InputStream cgiHeaderStream =
new HTTPHeaderInputStream(proc.getInputStream());
BufferedReader cgiHeaderReader =
new BufferedReader(new InputStreamReader(cgiHeaderStream));
while (isRunning) {
try {
//set headers
String line = null;
while (((line = cgiHeaderReader.readLine()) != null)
&& !("".equals(line))) {
if (debug >= 2) {
log("runCGI: addHeader(\"" + line + "\")");
}
if (line.startsWith("HTTP")) {
response.setStatus(getSCFromHttpStatusLine(line));
} else if (line.indexOf(":") >= 0) {
String header =
line.substring(0, line.indexOf(":")).trim();
String value =
line.substring(line.indexOf(":") + 1).trim();
if (header.equalsIgnoreCase("status")) {
response.setStatus(getSCFromCGIStatusHeader(value));
} else {
response.addHeader(header , value);
}
} else {
log("runCGI: bad header line \"" + line + "\"");
}
}
//write output
byte[] bBuf = new byte[2048];
OutputStream out = response.getOutputStream();
cgiOutput = proc.getInputStream();
try {
while ((bufRead = cgiOutput.read(bBuf)) != -1) {
if (debug >= 4) {
log("runCGI: output " + bufRead +
" bytes of data");
}
out.write(bBuf, 0, bufRead);
}
} finally {
// Attempt to consume any leftover byte if something bad happens,
// such as a socket disconnect on the servlet side; otherwise, the
// external process could hang
if (bufRead != -1) {
while ((bufRead = cgiOutput.read(bBuf)) != -1) {}
}
}
proc.exitValue(); // Throws exception if alive
isRunning = false;
} catch (IllegalThreadStateException e) {
try {
Thread.sleep(500);
} catch (InterruptedException ignored) {
}
}
} //replacement for Process.waitFor()
// Close the output stream used
cgiOutput.close();
}
catch (IOException e){
log ("Caught exception " + e);
throw e;
}
finally{
if (debug > 4) {
log ("Running finally block");
}
if (proc != null){
proc.destroy();
proc = null;
}
}
}
/**
* Parses the Status-Line and extracts the status code.
*
* @param line The HTTP Status-Line (RFC2616, section 6.1)
* @return The extracted status code or the code representing an
* internal error if a valid status code cannot be extracted.
*/
private int getSCFromHttpStatusLine(String line) {
int statusStart = line.indexOf(' ') + 1;
if (statusStart < 1 || line.length() < statusStart + 3) {
// Not a valid HTTP Status-Line
log ("runCGI: invalid HTTP Status-Line:" + line);
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
String status = line.substring(statusStart, statusStart + 3);
int statusCode;
try {
statusCode = Integer.parseInt(status);
} catch (NumberFormatException nfe) {
// Not a valid status code
log ("runCGI: invalid status code:" + status);
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
return statusCode;
}
/**
* Parses the CGI Status Header value and extracts the status code.
*
* @param value The CGI Status value of the form <code>
* digit digit digit SP reason-phrase</code>
* @return The extracted status code or the code representing an
* internal error if a valid status code cannot be extracted.
*/
private int getSCFromCGIStatusHeader(String value) {
if (value.length() < 3) {
// Not a valid status value
log ("runCGI: invalid status value:" + value);
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
String status = value.substring(0, 3);
int statusCode;
try {
statusCode = Integer.parseInt(status);
} catch (NumberFormatException nfe) {
// Not a valid status code
log ("runCGI: invalid status code:" + status);
return HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
}
return statusCode;
}
private void sendToLog(BufferedReader rdr) {
String line = null;
int lineCount = 0 ;
try {
while ((line = rdr.readLine()) != null) {
log("runCGI (stderr):" + line) ;
lineCount++ ;
}
} catch (IOException e) {
log("sendToLog error", e) ;
} finally {
try {
rdr.close() ;
} catch (IOException ce) {
log("sendToLog error", ce) ;
} ;
} ;
if ( lineCount > 0 && debug > 2) {
log("runCGI: " + lineCount + " lines received on stderr") ;
} ;
}
} //class CGIRunner
/**
* This is an input stream specifically for reading HTTP headers. It reads
* upto and including the two blank lines terminating the headers. It
* allows the content to be read using bytes or characters as appropriate.
*/
protected class HTTPHeaderInputStream extends InputStream {
private static final int STATE_CHARACTER = 0;
private static final int STATE_FIRST_CR = 1;
private static final int STATE_FIRST_LF = 2;
private static final int STATE_SECOND_CR = 3;
private static final int STATE_HEADER_END = 4;
private InputStream input;
private int state;
HTTPHeaderInputStream(InputStream theInput) {
input = theInput;
state = STATE_CHARACTER;
}
/**
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
if (state == STATE_HEADER_END) {
return -1;
}
int i = input.read();
// Update the state
// State machine looks like this
//
// -------->--------
// | (CR) |
// | |
// CR1--->--- |
// | | |
// ^(CR) |(LF) |
// | | |
// CHAR--->--LF1--->--EOH
// (LF) | (LF) |
// |(CR) ^(LF)
// | |
// (CR2)-->---
if (i == 10) {
// LF
switch(state) {
case STATE_CHARACTER:
state = STATE_FIRST_LF;
break;
case STATE_FIRST_CR:
state = STATE_FIRST_LF;
break;
case STATE_FIRST_LF:
case STATE_SECOND_CR:
state = STATE_HEADER_END;
break;
}
} else if (i == 13) {
// CR
switch(state) {
case STATE_CHARACTER:
state = STATE_FIRST_CR;
break;
case STATE_FIRST_CR:
state = STATE_HEADER_END;
break;
case STATE_FIRST_LF:
state = STATE_SECOND_CR;
break;
}
} else {
state = STATE_CHARACTER;
}
return i;
}
} // class HTTPHeaderInputStream
} //class CGIServlet
Here is a short list of links related to this Tomcat CGIServlet.java source code file:
Tomcat example source code file (CGIServlet.java)
The Tomcat CGIServlet.java source code/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.servlets; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Hashtable; import java.util.Locale; import java.util.StringTokenizer; import java.util.Vector; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.UnavailableException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.catalina.Globals; import org.apache.catalina.util.IOTools; /** * CGI-invoking servlet for web applications, used to execute scripts which * comply to the Common Gateway Interface (CGI) specification and are named * in the path-info used to invoke this servlet. * * <p> * <i>Note: This code compiles and even works for simple CGI cases. * Exhaustive testing has not been done. Please consider it beta * quality. Feedback is appreciated to the author (see below).</i> * </p> * <p> * * <b>Example:* <ul> * <LI>path = $CATALINA_HOME/mywebapp/dir1/realCGIscript * <LI>scriptName = /servlet/cgigateway/dir1/realCGIscript * <LI>cgiName = /dir1/realCGIscript * <LI>name = realCGIscript * </ul> * </p> * <p> * CGI search algorithm: search the real path below * <my-webapp-root> and find the first non-directory in * the getPathTranslated("/"), reading/searching from left-to-right. *</p> *<p> * The CGI search path will start at * webAppRootDir + File.separator + cgiPathPrefix * (or webAppRootDir alone if cgiPathPrefix is * null). *</p> *<p> * cgiPathPrefix is defined by setting * this servlet's cgiPathPrefix init parameter * *</p> * * @param pathInfo String from HttpServletRequest.getPathInfo() * @param webAppRootDir String from context.getRealPath("/") * @param contextPath String as from * HttpServletRequest.getContextPath() * @param servletPath String as from * HttpServletRequest.getServletPath() * @param cgiPathPrefix subdirectory of webAppRootDir below which * the web app's CGIs may be stored; can be null. * The CGI search path will start at * webAppRootDir + File.separator + cgiPathPrefix * (or webAppRootDir alone if cgiPathPrefix is * null). cgiPathPrefix is defined by setting * the servlet's cgiPathPrefix init parameter. * * * @return * <ul> * <li> * <code>path - full file-system path to valid cgi script, * or null if no cgi was found * <li> * <code>scriptName - * CGI variable SCRIPT_NAME; the full URL path * to valid cgi script or null if no cgi was * found * <li> * <code>cgiName - servlet pathInfo fragment corresponding to * the cgi script itself, or null if not found * <li> * <code>name - simple name (no directories) of the * cgi script, or null if no cgi was found * </ul> * * @since Tomcat 4.0 */ protected String[] findCGI(String pathInfo, String webAppRootDir, String contextPath, String servletPath, String cgiPathPrefix) { String path = null; String name = null; String scriptname = null; String cginame = ""; if ((webAppRootDir != null) && (webAppRootDir.lastIndexOf(File.separator) == (webAppRootDir.length() - 1))) { //strip the trailing "/" from the webAppRootDir webAppRootDir = webAppRootDir.substring(0, (webAppRootDir.length() - 1)); } if (cgiPathPrefix != null) { webAppRootDir = webAppRootDir + File.separator + cgiPathPrefix; } if (debug >= 2) { log("findCGI: path=" + pathInfo + ", " + webAppRootDir); } File currentLocation = new File(webAppRootDir); StringTokenizer dirWalker = new StringTokenizer(pathInfo, "/"); if (debug >= 3) { log("findCGI: currentLoc=" + currentLocation); } while (!currentLocation.isFile() && dirWalker.hasMoreElements()) { if (debug >= 3) { log("findCGI: currentLoc=" + currentLocation); } String nextElement = (String) dirWalker.nextElement(); currentLocation = new File(currentLocation, nextElement); cginame = cginame + "/" + nextElement; } if (!currentLocation.isFile()) { return new String[] { null, null, null, null }; } else { if (debug >= 2) { log("findCGI: FOUND cgi at " + currentLocation); } path = currentLocation.getAbsolutePath(); name = currentLocation.getName(); if (".".equals(contextPath)) { scriptname = servletPath; } else { scriptname = contextPath + servletPath; } if (!servletPath.equals(cginame)) { scriptname = scriptname + cginame; } } if (debug >= 1) { log("findCGI calc: name=" + name + ", path=" + path + ", scriptname=" + scriptname + ", cginame=" + cginame); } return new String[] { path, scriptname, cginame, name }; } /** * Constructs the CGI environment to be supplied to the invoked CGI * script; relies heavliy on Servlet API methods and findCGI * * @param req request associated with the CGI * invokation * * @return true if environment was set OK, false if there * was a problem and no environment was set */ protected boolean setCGIEnvironment(HttpServletRequest req) throws IOException { /* * This method is slightly ugly; c'est la vie. * "You cannot stop [ugliness], you can only hope to contain [it]" * (apologies to Marv Albert regarding MJ) */ Hashtable<String,String> envp = new Hashtable | "); sb.append("CGIEnvironment Info</th> | ||
---|---|---|---|
Debug Level | "); sb.append(debug); sb.append("</td> | ||
Validity: | "); sb.append(isValid()); sb.append("</td> | ||
"); sb.append(s); sb.append("</td> | "); sb.append(blanksToString((String) env.get(s), "[will be set to blank]")); sb.append("</td> | ||
Derived Command | "); sb.append(nullsToBlanks(command)); sb.append("</td> | ||
Working Directory | "); if (workingDirectory != null) { sb.append(workingDirectory.toString()); } sb.append("</td> | ||
Command Line Params | "); for (int i=0; i < cmdLineParameters.size(); i++) { String param = (String) cmdLineParameters.get(i); sb.append("<p>"); sb.append(param); sb.append("</p>"); } sb.append("</td> |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.