/* * 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;
/** * 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</b>:<br> * If an instance of this servlet was mapped (using <code><web-app>/WEB-INF/web.xml</code>) to: * </p> * <p> * <code> * <web-app>/cgi-bin/* * </code> * </p> * <p> * then the following request: * </p> * <p> * <code> * http://localhost:8080/<web-app>/cgi-bin/dir1/script/pathinfo1 * </code> * </p> * <p> * would result in the execution of the script * </p> * <p> * <code> * <web-app-root>/WEB-INF/cgi/dir1/script * </code> * </p> * <p> * with the script's <code>PATH_INFO</code> set to <code>/pathinfo1</code>. * </p> * <p> * Recommendation: House all your CGI scripts under <code><webapp>/WEB-INF/cgi</code>. This will ensure that you * do not accidentally expose your cgi scripts' code to the outside world and that your cgis will be cleanly ensconced * underneath the WEB-INF (i.e., non-content) area. * </p> * <p> * The default CGI location is mentioned above. You have the flexibility to put CGIs wherever you want, however: * </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> * <p> * <B>CGI Specification</B>:<br> * derived from <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>. A work-in-progress & expired * Internet Draft. Note no actual RFC describing the CGI specification exists. Where the behavior of this servlet * differs from the specification cited above, it is either documented here, a bug, or an instance where the * specification cited differs from Best Community Practice (BCP). Such instances should be well-documented here. Please * email the <a href="https://tomcat.apache.org/lists.html">Tomcat group</a> with amendments. * </p> * <p> * <b>Canonical metavariables</b>:<br> * The CGI specification defines the following canonical metavariables: <br> * [excerpt from CGI specification] * * <PRE> * AUTH_TYPE * CONTENT_LENGTH * CONTENT_TYPE * GATEWAY_INTERFACE * PATH_INFO * PATH_TRANSLATED * QUERY_STRING * REMOTE_ADDR * REMOTE_HOST * REMOTE_IDENT * REMOTE_USER * REQUEST_METHOD * SCRIPT_NAME * SERVER_NAME * SERVER_PORT * SERVER_PROTOCOL * SERVER_SOFTWARE * </PRE> * <p> * Metavariables with names beginning with the protocol name (<EM>e.g.</EM>, "HTTP_ACCEPT") are also canonical in their * description of request header fields. The number and meaning of these fields may change independently of this * specification. (See also section 6.1.5 [of the CGI specification].) * </p> * [end excerpt] * <h2>Implementation notes</h2> * <p> * <b>standard input handling</b>: If your script accepts standard input, then the client must start sending input * within a certain timeout period, otherwise the servlet will assume no input is coming and carry on running the * script. The script's the standard input will be closed and handling of any further input from the client is * undefined. Most likely it will be ignored. If this behavior becomes undesirable, then this servlet needs to be * enhanced to handle threading of the spawned process' stdin, stdout, and stderr (which should not be too hard). <br> * If you find your cgi scripts are timing out receiving input, you can set the init parameter * <code>stderrTimeout</code> of your webapps' cgi-handling servlet. * </p> * <p> * <b>Metavariable Values</b>: According to the CGI specification, implementations may choose to represent both null or * missing values in an implementation-specific manner, but must define that manner. This implementation chooses to * always define all required metavariables, but set the value to "" for all metavariables whose value is either null or * undefined. PATH_TRANSLATED is the sole exception to this rule, as per the CGI Specification. * </p> * <p> * <b>NPH -- Non-parsed-header implementation</b>: This implementation does not support the CGI NPH concept, whereby * server ensures that the data supplied to the script are precisely as supplied by the client and unaltered by the * server. * </p> * <p> * The function of a servlet container (including Tomcat) is specifically designed to parse and possible alter * CGI-specific variables, and as such makes NPH functionality difficult to support. * </p> * <p> * The CGI specification states that compliant servers MAY support NPH output. It does not state servers MUST support * NPH output to be unconditionally compliant. Thus, this implementation maintains unconditional compliance with the * specification though NPH support is not present. * </p> * <p> * The CGI specification is located at <a href="http://cgi-spec.golux.com">http://cgi-spec.golux.com</a>. * </p> * <h3>TODO:</h3> * <ul> * <li>Support for setting headers (for example, Location headers don't work) * <li>Support for collapsing multiple header lines (per RFC 2616) * <li>Ensure handling of POST method does not interfere with 2.3 Filters * <li>Refactor some debug code out of core * <li>Ensure header handling preserves encoding * <li>Possibly rewrite CGIRunner.run()? * <li>Possibly refactor CGIRunner and CGIEnvironment as non-inner classes? * <li>Document handling of cgi stdin when there is no stdin * <li>Revisit IOException handling in CGIRunner.run() * <li>Better documentation * <li>Confirm use of ServletInputStream.available() in CGIRunner.run() is not needed * <li>[add more to this TODO list] * </ul> * * @author Martin T Dengler [root@martindengler.com] * @author Amy Roh
*/ publicfinalclass CGIServlet extends HttpServlet {
privatestaticfinal Log log = LogFactory.getLog(CGIServlet.class); privatestaticfinal StringManager sm = StringManager.getManager(CGIServlet.class);
/* some vars below copied from Craig R. McClanahan's InvokerServlet */
if (JrePlatform.IS_WINDOWS) {
DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN = Pattern.compile("[\\w\\Q-.\\/:\\E]+");
} else { // No restrictions
DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN = null;
}
}
/** * The CGI search path will start at webAppRootDir + File.separator + cgiPathPrefix (or webAppRootDir alone if * cgiPathPrefix is null)
*/ private String cgiPathPrefix = null;
/** the executable to use with the script */ private String cgiExecutable = "perl";
/** additional arguments for the executable */ private List<String> cgiExecutableArgs = null;
/** the encoding to use for parameters */ private String parameterEncoding = System.getProperty("file.encoding", "UTF-8");
/* The HTTP methods this Servlet will pass to the CGI script */ private Set<String> cgiMethods = new HashSet<>(); privateboolean cgiMethodsAll = false;
/** * The time (in milliseconds) to wait for the reading of stderr to complete before terminating the CGI process.
*/ privatelong stderrTimeout = 2000;
/** * The regular expression used to select HTTP headers to be passed to the CGI process as environment variables. The * name of the environment variable will be the name of the HTTP header converter to upper case, prefixed with * <code>HTTP_</code> and with all <code>-</code> characters converted to <code>_</code>.
*/ private Pattern envHttpHeadersPattern =
Pattern.compile("ACCEPT[-0-9A-Z]*|CACHE-CONTROL|COOKIE|HOST|IF-[-0-9A-Z]*|REFERER|USER-AGENT");
/** object used to ensure multiple threads don't try to expand same file */ privatestaticfinal Object expandFileLock = new Object();
/** the shell environment variables to be passed to the CGI script */ privatefinal Map<String,String> shellEnv = new HashMap<>();
/** * Enable creation of script command line arguments from query-string. See * https://tools.ietf.org/html/rfc3875#section-4.4 4.4. The Script Command Line
*/ privateboolean enableCmdLineArguments = false;
/** * Limits the encoded form of individual command line arguments. By default values are limited to those allowed by * the RFC. See https://tools.ietf.org/html/rfc3875#section-4.4 Uses \Q...\E to avoid individual quoting.
*/ private Pattern cmdLineArgumentsEncodedPattern = Pattern.compile("[\\w\\Q%;/?:@&,$-.!~*'()\\E]+">);
/** * Limits the decoded form of individual command line arguments. Default varies by platform.
*/ private Pattern cmdLineArgumentsDecodedPattern = DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN;
/** * Sets instance variables. * <P> * Modified from Craig R. McClanahan's InvokerServlet * </P> * * @param config a <code>ServletConfig</code> object containing the servlet's configuration and initialization * parameters * * @exception ServletException if an exception has occurred that interferes with the servlet's normal operation
*/
@Override publicvoid init(ServletConfig config) throws ServletException {
super.init(config);
// Set our properties from the initialization parameters
cgiPathPrefix = getServletConfig().getInitParameter("cgiPathPrefix"); boolean passShellEnvironment = Boolean.parseBoolean(getServletConfig().getInitParameter("passShellEnvironment"));
if (passShellEnvironment) {
shellEnv.putAll(System.getenv());
}
Enumeration<String> e = config.getInitParameterNames(); while (e.hasMoreElements()) {
String initParamName = e.nextElement(); if (initParamName.startsWith("environment-variable-")) { if (initParamName.length() == 21) { thrownew ServletException(sm.getString("cgiServlet.emptyEnvVarName"));
}
shellEnv.put(initParamName.substring(21), config.getInitParameter(initParamName));
}
}
if (getServletConfig().getInitParameter("executable") != null) {
cgiExecutable = getServletConfig().getInitParameter("executable");
}
if (getServletConfig().getInitParameter("executable-arg-1") != null) {
List<String> args = new ArrayList<>(); for (int i = 1;; i++) {
String arg = getServletConfig().getInitParameter("executable-arg-" + i); if (arg == null) { break;
}
args.add(arg);
}
cgiExecutableArgs = args;
}
if (getServletConfig().getInitParameter("parameterEncoding") != null) {
parameterEncoding = getServletConfig().getInitParameter("parameterEncoding");
}
if (getServletConfig().getInitParameter("stderrTimeout") != null) {
stderrTimeout = Long.parseLong(getServletConfig().getInitParameter("stderrTimeout"));
}
if (getServletConfig().getInitParameter("envHttpHeaders") != null) {
envHttpHeadersPattern = Pattern.compile(getServletConfig().getInitParameter("envHttpHeaders"));
}
if (getServletConfig().getInitParameter("enableCmdLineArguments") != null) {
enableCmdLineArguments = Boolean.parseBoolean(config.getInitParameter("enableCmdLineArguments"));
}
if (getServletConfig().getInitParameter("cmdLineArgumentsEncoded") != null) {
cmdLineArgumentsEncodedPattern =
Pattern.compile(getServletConfig().getInitParameter("cmdLineArgumentsEncoded"));
}
String value = getServletConfig().getInitParameter("cmdLineArgumentsDecoded"); if (ALLOW_ANY_PATTERN.equals(value)) { // Optimisation for case where anything is allowed
cmdLineArgumentsDecodedPattern = null;
} elseif (value != null) {
cmdLineArgumentsDecodedPattern = Pattern.compile(value);
}
}
/** * Logs important Servlet API and container information. * <p> * Based on SnoopAllServlet by Craig R. McClanahan * </p> * * @param req HttpServletRequest object used as source of information * * @exception IOException if a write operation exception occurs
*/ privatevoid printServletEnvironment(HttpServletRequest req) throws IOException {
String method = req.getMethod(); if (cgiMethodsAll || cgiMethods.contains(method)) {
doGet(req, res);
} elseif (DEFAULT_SUPER_METHODS.contains(method)) { // If the CGI servlet is explicitly configured to handle one of // these methods it will be handled in the previous condition super.service(req, res);
} else { // Unsupported method
res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
}
/** * Provides CGI Gateway service. * * @param req HttpServletRequest passed in by servlet container * @param res HttpServletResponse passed in by servlet container * * @exception ServletException if a servlet-specific exception occurs * @exception IOException if a read/write exception occurs
*/
@Override protectedvoid doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
CGIEnvironment cgiEnv = new CGIEnvironment(req, getServletContext());
if (cgiEnv.isValid()) {
CGIRunner cgi = new CGIRunner(cgiEnv.getCommand(), cgiEnv.getEnvironment(), cgiEnv.getWorkingDirectory(),
cgiEnv.getParameters());
if (log.isTraceEnabled()) {
String[] cgiEnvLines = cgiEnv.toString().split(System.lineSeparator()); for (String cgiEnvLine : cgiEnvLines) {
log.trace(cgiEnvLine);
}
printServletEnvironment(req);
}
}
@Override protectedvoid doOptions(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Note: This method will never be called if cgiMethods is "*" so that // case does nto need to be handled here.
Set<String> allowedMethods = new HashSet<>();
allowedMethods.addAll(cgiMethods);
allowedMethods.addAll(DEFAULT_SUPER_METHODS);
StringBuilder headerValue = new StringBuilder();
for (String method : allowedMethods) {
headerValue.append(method);
headerValue.append(',');
}
/* * Behaviour depends on the status code. * * Status < 400 - Calls setStatus. Returns false. CGI servlet will provide the response body. * * Status >= 400 - Calls sendError(status), returns true. Standard error page mechanism will provide the response * body.
*/ privateboolean setStatus(HttpServletResponse response, int status) throws IOException { if (status >= HttpServletResponse.SC_BAD_REQUEST) {
response.sendError(status); returntrue;
} else {
response.setStatus(status); returnfalse;
}
}
/** * Encapsulates the CGI environment and rules to derive that environment from the servlet container and request * information.
*/ protectedclass CGIEnvironment {
/** context of the enclosing servlet */ private ServletContext context = null;
/** cgi command to be invoked */ private String command = null;
/** cgi command's desired working directory */ privatefinal File workingDirectory;
/** cgi command's command line parameters */ privatefinal ArrayList<String> cmdLineParameters = new ArrayList<>();
/** whether or not this object is valid or not */ privatefinalboolean valid;
/** * Creates a CGIEnvironment and derives the necessary environment, query parameters, working directory, cgi * command, etc. * * @param req HttpServletRequest for information provided by the Servlet API * @param context ServletContext for information provided by the Servlet API * * @throws IOException an IO error occurred
*/ protected CGIEnvironment(HttpServletRequest req, ServletContext context) throws IOException {
setupFromContext(context); boolean valid = setupFromRequest(req);
if (valid) {
valid = setCGIEnvironment(req);
}
if (valid) {
workingDirectory = new File(command.substring(0, command.lastIndexOf(File.separator)));
} else {
workingDirectory = null;
}
this.valid = valid;
}
/** * Uses the ServletContext to set some CGI variables * * @param context ServletContext for information provided by the Servlet API
*/ protectedvoid setupFromContext(ServletContext context) { this.context = context; this.webAppRootDir = context.getRealPath("/"); this.tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR);
}
/** * Uses the HttpServletRequest to set most CGI variables * * @param req HttpServletRequest for information provided by the Servlet API * * @return true if the request was parsed without error, false if there was a problem * * @throws UnsupportedEncodingException Unknown encoding
*/ protectedboolean setupFromRequest(HttpServletRequest req) throws UnsupportedEncodingException {
boolean isIncluded = false;
// Look to see if this request is an include if (req.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null) {
isIncluded = true;
} if (isIncluded) { this.contextPath = (String) req.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH); this.servletPath = (String) req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); this.pathInfo = (String) req.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
} else { this.contextPath = req.getContextPath(); this.servletPath = req.getServletPath(); this.pathInfo = req.getPathInfo();
} // If getPathInfo() returns null, must be using extension mapping // In this case, pathInfo should be same as servletPath if (this.pathInfo == null) { this.pathInfo = this.servletPath;
}
// If the request method is GET, POST or HEAD and the query string // does not contain an unencoded "=" this is an indexed query. // The parsed query string becomes the command line parameters // for the cgi command. if (enableCmdLineArguments && (req.getMethod().equals("GET") || req.getMethod().equals("POST") ||
req.getMethod().equals("HEAD"))) {
String qs; if (isIncluded) {
qs = (String) req.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
} else {
qs = req.getQueryString();
} if (qs != null && qs.indexOf('=') == -1) {
StringTokenizer qsTokens = new StringTokenizer(qs, "+"); while (qsTokens.hasMoreTokens()) {
String encodedArgument = qsTokens.nextToken(); if (!cmdLineArgumentsEncodedPattern.matcher(encodedArgument).matches()) { if (log.isDebugEnabled()) {
log.debug(sm.getString("cgiServlet.invalidArgumentEncoded", encodedArgument,
cmdLineArgumentsEncodedPattern.toString()));
} returnfalse;
}
/** * Resolves core information about the cgi script. * <p> * Example URI: * </p> * * <PRE> * /servlet/cgigateway/dir1/realCGIscript/pathinfo1 * </PRE> * <ul> * <LI><b>path</b> = $CATALINA_HOME/mywebapp/dir1/realCGIscript * <LI><b>scriptName</b> = /servlet/cgigateway/dir1/realCGIscript * <LI><b>cgiName</b> = /dir1/realCGIscript * <LI><b>name</b> = realCGIscript * </ul> * <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</code> - full file-system path to valid cgi script, or null if no cgi was found * <li><code>scriptName</code> - CGI variable SCRIPT_NAME; the full URL path to valid cgi script or * null if no cgi was found * <li><code>cgiName</code> - servlet pathInfo fragment corresponding to the cgi script itself, or * null if not found * <li><code>name</code> - simple name (no directories) of the cgi script, or null if no cgi was * found * </ul>
*/ protected String[] findCGI(String pathInfo, String webAppRootDir, String contextPath, String servletPath,
String cgiPathPrefix) {
String path = null;
String name = null;
String scriptname = null;
if (webAppRootDir.lastIndexOf(File.separator) == (webAppRootDir.length() - 1)) { // strip the trailing "/" from the webAppRootDir
webAppRootDir = webAppRootDir.substring(0, (webAppRootDir.length() - 1));
}
if (log.isDebugEnabled()) {
log.debug(sm.getString("cgiServlet.find.found", name, path, scriptname, cginame));
} returnnew String[] { path, scriptname, cginame, name };
}
/** * Constructs the CGI environment to be supplied to the invoked CGI script; relies heavily on Servlet API * methods and findCGI * * @param req request associated with the CGI Invocation * * @return true if environment was set OK, false if there was a problem and no environment was set * * @throws IOException an IO error occurred
*/ protectedboolean 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)
*/
// Add the shell environment variables (if any)
Map<String,String> envp = new HashMap<>(shellEnv);
/*- * PATH_INFO should be determined by using sCGIFullName: * 1) Let sCGIFullName not end in a "/" (see method findCGI) * 2) Let sCGIFullName equal the pathInfo fragment which * corresponds to the actual cgi script. * 3) Thus, PATH_INFO = request.getPathInfo().substring( * sCGIFullName.length()) * * (see method findCGI, where the real work is done) *
*/ if (pathInfo == null || (pathInfo.substring(sCGIFullName.length()).length() <= 0)) {
sPathInfoCGI = "";
} else {
sPathInfoCGI = pathInfo.substring(sCGIFullName.length());
}
envp.put("PATH_INFO", sPathInfoCGI);
/*- * PATH_TRANSLATED must be determined after PATH_INFO (and the * implied real cgi-script) has been taken into account. * * The following example demonstrates: * * servlet info = /servlet/cgigw/dir1/dir2/cgi1/trans1/trans2 * cgifullpath = /servlet/cgigw/dir1/dir2/cgi1 * path_info = /trans1/trans2 * webAppRootDir = servletContext.getRealPath("/") * * path_translated = servletContext.getRealPath("/trans1/trans2") * * That is, PATH_TRANSLATED = webAppRootDir + sPathInfoCGI * (unless sPathInfoCGI is null or blank, then the CGI * specification dictates that the PATH_TRANSLATED metavariable * SHOULD NOT be defined. *
*/ if (!sPathInfoCGI.isEmpty()) {
sPathTranslatedCGI = context.getRealPath(sPathInfoCGI);
} if (sPathTranslatedCGI == null || "".equals(sPathTranslatedCGI)) { // NOOP
} else {
envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI));
}
/* * Note CGI spec says CONTENT_LENGTH must be NULL ("") or undefined if there is no content, so we cannot put * 0 or -1 in as per the Servlet API spec.
*/ long contentLength = req.getContentLengthLong();
String sContentLength = (contentLength <= 0 ? "" : Long.toString(contentLength));
envp.put("CONTENT_LENGTH", sContentLength);
Enumeration<String> headers = req.getHeaderNames();
String header = null; while (headers.hasMoreElements()) {
header = null;
header = headers.nextElement().toUpperCase(Locale.ENGLISH); // REMIND: rewrite multiple headers as if received as single // REMIND: change character set // REMIND: I forgot what the previous REMIND means if (envHttpHeadersPattern.matcher(header).matches()) {
envp.put("HTTP_" + header.replace('-', '_'), req.getHeader(header));
}
}
File fCGIFullPath = new File(sCGIFullPath);
command = fCGIFullPath.getCanonicalPath();
envp.put("X_TOMCAT_SCRIPT_PATH", command); // for kicks
envp.put("SCRIPT_FILENAME", command); // for PHP
this.env = envp;
returntrue;
}
/** * Extracts requested resource from web app archive to context work directory to enable CGI script to be * executed.
*/ protectedvoid expandCGIScript() {
StringBuilder srcPath = new StringBuilder();
StringBuilder destPath = new StringBuilder();
InputStream is = null;
// paths depend on mapping if (cgiPathPrefix == null) {
srcPath.append(pathInfo);
is = context.getResourceAsStream(srcPath.toString());
destPath.append(tmpDir);
destPath.append(pathInfo);
} else { // essentially same search algorithm as findCGI()
srcPath.append(cgiPathPrefix);
StringTokenizer pathWalker = new StringTokenizer(pathInfo, "/"); // start with first element while (pathWalker.hasMoreElements() && (is == null)) {
srcPath.append('/');
srcPath.append(pathWalker.nextElement());
is = context.getResourceAsStream(srcPath.toString());
}
destPath.append(tmpDir);
destPath.append('/');
destPath.append(srcPath);
}
if (is == null) { // didn't find anything, give up now
log.warn(sm.getString("cgiServlet.expandNotFound", srcPath)); return;
}
try {
File f = new File(destPath.toString()); if (f.exists()) { // Don't need to expand if it already exists return;
}
// create directories
File dir = f.getParentFile(); if (!dir.mkdirs() && !dir.isDirectory()) {
log.warn(sm.getString("cgiServlet.expandCreateDirFail", dir.getAbsolutePath())); return;
}
try { synchronized (expandFileLock) { // make sure file doesn't exist if (f.exists()) { return;
}
// create file if (!f.createNewFile()) { return;
}
Files.copy(is, f.toPath());
if (log.isDebugEnabled()) {
log.debug(sm.getString("cgiServlet.expandOk", srcPath, destPath));
}
}
} catch (IOException ioe) {
log.warn(sm.getString("cgiServlet.expandFail", srcPath, destPath), ioe); // delete in case file is corrupted if (f.exists()) { if (!f.delete()) {
log.warn(sm.getString("cgiServlet.expandDeleteFail", f.getAbsolutePath()));
}
}
}
} finally { try {
is.close();
} catch (IOException e) {
log.warn(sm.getString("cgiServlet.expandCloseFail", srcPath), e);
}
}
}
/** * Returns important CGI environment information in a multi-line text format. * * @return CGI environment info
*/
@Override public String toString() {
sb.append("Command Line Params:");
sb.append(System.lineSeparator()); for (String param : cmdLineParameters) {
sb.append(" [");
sb.append(param);
sb.append(']');
sb.append(System.lineSeparator());
}
} else {
sb.append("Validity: [false]");
sb.append(System.lineSeparator());
sb.append("CGI script not found or not specified.");
sb.append(System.lineSeparator());
sb.append("Check the HttpServletRequest pathInfo property to see if it is what ");
sb.append(System.lineSeparator());
sb.append("you meant it to be. You must specify an existent and executable file ");
sb.append(System.lineSeparator());
sb.append("as part of the path-info.");
sb.append(System.lineSeparator());
}
/** * Gets validity status * * @return true if this environment is valid, false otherwise
*/ protectedboolean 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</code>
*/ 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</code>
*/ 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</code> * or empty ("")
*/ protected String blanksToString(String couldBeBlank, String subForBlanks) { return (couldBeBlank == null || couldBeBlank.isEmpty()) ? 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</code> method used to actually invoke the CGI. * </p> * <p> * The CGI environment and settings are derived from the information passed to the constructor. * </p> * <p> * The input and output streams can be set by the <code>setInput</code> and <code>setResponse</code> methods, * respectively. * </p>
*/ protectedclass CGIRunner {
/** script/command to be executed */ privatefinal String command;
/** environment used when invoking the cgi script */ privatefinal Map<String,String> env;
/** working directory used when invoking the cgi script */ privatefinal File wd;
/** command line parameters to be passed to the invoked script */ privatefinal ArrayList<String> params;
/** 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() */ privateboolean 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</code> and <code>setResponse</code> methods, * respectively. * * @param command string full path to command to be executed * @param env Map 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 parameters as strings
*/ protected CGIRunner(String command, Map<String,String> env, File wd, ArrayList<String> params) { this.command = command; this.env = env; this.wd = wd; this.params = params;
updateReadyStatus();
}
/** * Gets ready status * * @return false if not ready (<code>run</code> will throw an exception), true if ready
*/ protectedboolean isReady() { return readyToRun;
}
/** * Sets HttpServletResponse object used to set headers and send output to * * @param response HttpServletResponse to be used
*/ protectedvoid 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
*/ protectedvoid setInput(InputStream stdin) { this.stdin = stdin;
updateReadyStatus();
}
/** * Converts a Map to a String array by converting each key/value pair in the Map to a String in the form * "key=value" (key + "=" + map.get(key).toString()) * * @param map Map to convert * * @return converted string array * * @exception NullPointerException if a hash key has a null value
*/ protected String[] mapToStringArray(Map<String,?> map) throws NullPointerException {
List<String> list = new ArrayList<>(map.size()); for (Entry<String,?> entry : map.entrySet()) {
list.add(entry.getKey() + "=" + entry.getValue().toString());
} return list.toArray(new String[0]);
}
/** * Executes a CGI script with the desired environment, current working directory, and input/output streams * <p> * This implements the following CGI specification recommendations: * </p> * <UL> * <LI>Servers SHOULD provide the "<code>query</code>" 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</code>" token of the * "<code>Authorization</code>" if it was supplied as part of the request header. See * <code>getCGIEnvironment</code> 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><u>Allowed characters in pathInfo</u>: This implementation does not allow ASCII NUL nor any character * which cannot be URL-encoded according to internet standards; * <LI><u>Allowed characters in path segments</u>: This implementation does not allow non-terminal NULL segments * in the the path -- IOExceptions may be thrown; * <LI><u>"<code>.</code>" and "<code>..</code>" path segments</u>: This implementation does not allow * "<code>.</code>" and "<code>..</code>" in the the path, and such characters will result in an IOException * being thrown (this should never happen since Tomcat normalises the requestURI before determining the * contextPath, servletPath and pathInfo); * <LI><u>Implementation limitations</u>: 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> * * @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 ((command.contains(File.separator + "." + File.separator)) || (command.contains(File.separator + "..")) || (command.contains(".." + File.separator))) { 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://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4216884 with major modifications by Martin Dengler */ Runtime rt = null; BufferedReader cgiHeaderReader = null; InputStream cgiOutput = null; BufferedReader commandsStdErr = null; Thread errReaderThread = null; BufferedOutputStream commandsStdIn = null; Process proc = null; int bufRead = -1;
List<String> cmdAndArgs = new ArrayList<>(); if (cgiExecutable.length() != 0) { cmdAndArgs.add(cgiExecutable); } if (cgiExecutableArgs != null) { cmdAndArgs.addAll(cgiExecutableArgs); } cmdAndArgs.add(command); cmdAndArgs.addAll(params);
boolean isRunning = true; commandsStdErr = new BufferedReader(new InputStreamReader(proc.getErrorStream())); final BufferedReader stdErrRdr = commandsStdErr;
errReaderThread = new Thread(() -> sendToLog(stdErrRdr)); errReaderThread.start();
InputStream cgiHeaderStream = new HTTPHeaderInputStream(proc.getInputStream()); cgiHeaderReader = new BufferedReader(new InputStreamReader(cgiHeaderStream));
// Need to be careful here. If sendError() is called the // response body should be provided by the standard error page // process. But, if the output of the CGI process isn't read // then that process can hang. boolean skipBody = false;
while (isRunning) { try { // set headers String line = null; while (((line = cgiHeaderReader.readLine()) != null) && !line.isEmpty()) { if (log.isTraceEnabled()) { log.trace("addHeader(\"" + line + "\")"); } if (line.startsWith("HTTP")) { skipBody = setStatus(response, 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")) { skipBody = setStatus(response, getSCFromCGIStatusHeader(value)); } else { response.addHeader(header, value); } } else { log.info(sm.getString("cgiServlet.runBadHeader", line)); } }
// write output byte[] bBuf = new byte[2048];
OutputStream out = response.getOutputStream(); cgiOutput = proc.getInputStream();
try { while (!skipBody && (bufRead = cgiOutput.read(bBuf)) != -1) { if (log.isTraceEnabled()) { log.trace("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) { // NOOP - just read the data } } }
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.