ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/servlets/CGIServlet.java

Path
ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/servlets/CGIServlet.java
Status
scanned
Type
file
Name
CGIServlet.java
Extension
.java
Programming language
Java
Mime type
text/plain
File type
ASCII text, with CRLF line terminators
Tag

      
    
Rootfs path

      
    
Size
71366 (69.7 KB)
MD5
ef2dd47c78da1af0ed48e6b9de3bd86b
SHA1
151ea22e45b2de355fe246be3c7f2ad5ef1d4333
SHA256
96b79d4998733a714318ba74a45e12d54cf6c11f68a0154c58293c28e5590a77
SHA512

      
    
SHA1_git
2822cb4b118c491bfa6f8a1a2cad6b8ee3824af7
Is binary

      
    
Is text
True
Is archive

      
    
Is media

      
    
Is legal

      
    
Is manifest

      
    
Is readme

      
    
Is top level

      
    
Is key file

      
    
CGIServlet.java | 69.7 KB |

/* * 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.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Serial; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.file.Files; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Pattern; import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletConfig; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.UnavailableException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import org.apache.catalina.Globals; import org.apache.catalina.WebResource; import org.apache.catalina.WebResourceRoot; import org.apache.catalina.util.IOTools; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.compat.JrePlatform; import org.apache.tomcat.util.http.Method; import org.apache.tomcat.util.res.StringManager; /** * 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>&lt;web-app&gt;/WEB-INF/web.xml</code>) to: * </p> * <p> * <code> * &lt;web-app&gt;/cgi-bin/* * </code> * </p> * <p> * then the following request: * </p> * <p> * <code> * http://localhost:8080/&lt;web-app&gt;/cgi-bin/dir1/script/pathinfo1 * </code> * </p> * <p> * would result in the execution of the script * </p> * <p> * <code> * &lt;web-app-root&gt;/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>&lt;webapp&gt;/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 &amp; 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> */ public final class CGIServlet extends HttpServlet { private static final Log log = LogFactory.getLog(CGIServlet.class); private static final StringManager sm = StringManager.getManager(CGIServlet.class); @Serial private static final long serialVersionUID = 1L; private static final Set<String> DEFAULT_SUPER_METHODS = new HashSet<>(); private static final Pattern DEFAULT_CMD_LINE_ARGUMENTS_DECODED_PATTERN; private static final String ALLOW_ANY_PATTERN = ".*"; static { DEFAULT_SUPER_METHODS.add(Method.HEAD); DEFAULT_SUPER_METHODS.add(Method.OPTIONS); DEFAULT_SUPER_METHODS.add(Method.TRACE); 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 final Set<String> cgiMethods = new HashSet<>(); private boolean cgiMethodsAll = false; private transient WebResourceRoot resources = null; /** * The time (in milliseconds) to wait for the reading of stderr to complete before terminating the CGI process. */ private long 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 */ private static final Object expandFileLock = new Object(); /** the shell environment variables to be passed to the CGI script */ private final 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 */ private boolean 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. * * @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 public void 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) { throw new 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("cgiMethods") != null) { String paramValue = getServletConfig().getInitParameter("cgiMethods"); paramValue = paramValue.trim(); if ("*".equals(paramValue)) { cgiMethodsAll = true; } else { String[] methods = paramValue.split(","); for (String method : methods) { String trimmedMethod = method.trim(); cgiMethods.add(trimmedMethod); } } } else { cgiMethods.add(Method.GET); cgiMethods.add(Method.POST); } 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; } else if (value != null) { cmdLineArgumentsDecodedPattern = Pattern.compile(value); } // Load the web resources resources = (WebResourceRoot) getServletContext().getAttribute(Globals.RESOURCES_ATTR); if (resources == null) { throw new UnavailableException(sm.getString("cgiServlet.noResources")); } } /** * Logs important Servlet API and container information. * * @param req HttpServletRequest object used as source of information */ private void printServletEnvironment(HttpServletRequest req) { // Document the properties from ServletRequest log.trace("ServletRequest Properties"); Enumeration<String> attrs = req.getAttributeNames(); while (attrs.hasMoreElements()) { String attr = attrs.nextElement(); log.trace("Request Attribute: " + attr + ": [ " + req.getAttribute(attr) + "]"); } log.trace("Character Encoding: [" + req.getCharacterEncoding() + "]"); log.trace("Content Length: [" + req.getContentLengthLong() + "]"); log.trace("Content Type: [" + req.getContentType() + "]"); Enumeration<Locale> locales = req.getLocales(); while (locales.hasMoreElements()) { Locale locale = locales.nextElement(); log.trace("Locale: [" + locale + "]"); } Enumeration<String> params; try { params = req.getParameterNames(); while (params.hasMoreElements()) { String param = params.nextElement(); for (String value : req.getParameterValues(param)) { log.trace("Request Parameter: " + param + ": [" + value + "]"); } } } catch (IllegalStateException ise) { if (log.isTraceEnabled()) { log.trace("Request Parameters: [Invalid]", ise); } } log.trace("Protocol: [" + req.getProtocol() + "]"); log.trace("Remote Address: [" + req.getRemoteAddr() + "]"); log.trace("Remote Host: [" + req.getRemoteHost() + "]"); log.trace("Scheme: [" + req.getScheme() + "]"); log.trace("Secure: [" + req.isSecure() + "]"); log.trace("Server Name: [" + req.getServerName() + "]"); log.trace("Server Port: [" + req.getServerPort() + "]"); // Document the properties from HttpServletRequest log.trace("HttpServletRequest Properties"); log.trace("Auth Type: [" + req.getAuthType() + "]"); log.trace("Context Path: [" + req.getContextPath() + "]"); Cookie[] cookies = req.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { log.trace("Cookie: " + cookie.getName() + ": [" + cookie.getValue() + "]"); } } Enumeration<String> headers = req.getHeaderNames(); while (headers.hasMoreElements()) { String header = headers.nextElement(); log.trace("HTTP Header: " + header + ": [" + req.getHeader(header) + "]"); } log.trace("Method: [" + req.getMethod() + "]"); log.trace("Path Info: [" + req.getPathInfo() + "]"); log.trace("Path Translated: [" + req.getPathTranslated() + "]"); log.trace("Query String: [" + req.getQueryString() + "]"); log.trace("Remote User: [" + req.getRemoteUser() + "]"); log.trace("Requested Session ID: [" + req.getRequestedSessionId() + "]"); log.trace("Requested Session ID From Cookie: [" + req.isRequestedSessionIdFromCookie() + "]"); log.trace("Requested Session ID From URL: [" + req.isRequestedSessionIdFromURL() + "]"); log.trace("Requested Session ID Valid: [" + req.isRequestedSessionIdValid() + "]"); log.trace("Request URI: [" + req.getRequestURI() + "]"); log.trace("Servlet Path: [" + req.getServletPath() + "]"); log.trace("User Principal: [" + req.getUserPrincipal() + "]"); // Process the current session (if there is one) HttpSession session = req.getSession(false); if (session != null) { // Document the session properties log.trace("HttpSession Properties"); log.trace("ID: [" + session.getId() + "]"); log.trace("Creation Time: [" + new Date(session.getCreationTime()) + "]"); log.trace("Last Accessed Time: [" + new Date(session.getLastAccessedTime()) + "]"); log.trace("Max Inactive Interval: [" + session.getMaxInactiveInterval() + "]"); // Document the session attributes attrs = session.getAttributeNames(); while (attrs.hasMoreElements()) { String attr = attrs.nextElement(); log.trace("Session Attribute: " + attr + ": [" + session.getAttribute(attr) + "]"); } } // Document the servlet configuration properties log.trace("ServletConfig Properties"); log.trace("Servlet Name: [" + getServletConfig().getServletName() + "]"); // Document the servlet configuration initialization parameters params = getServletConfig().getInitParameterNames(); while (params.hasMoreElements()) { String param = params.nextElement(); String value = getServletConfig().getInitParameter(param); log.trace("Servlet Init Param: " + param + ": [" + value + "]"); } // Document the servlet context properties log.trace("ServletContext Properties"); log.trace("Major Version: [" + getServletContext().getMajorVersion() + "]"); log.trace("Minor Version: [" + getServletContext().getMinorVersion() + "]"); log.trace("Real Path for '/': [" + getServletContext().getRealPath("/") + "]"); log.trace("Server Info: [" + getServletContext().getServerInfo() + "]"); // Document the servlet context initialization parameters log.trace("ServletContext Initialization Parameters"); params = getServletContext().getInitParameterNames(); while (params.hasMoreElements()) { String param = params.nextElement(); String value = getServletContext().getInitParameter(param); log.trace("Servlet Context Init Param: " + param + ": [" + value + "]"); } // Document the servlet context attributes log.trace("ServletContext Attributes"); attrs = getServletContext().getAttributeNames(); while (attrs.hasMoreElements()) { String attr = attrs.nextElement(); log.trace("Servlet Context Attribute: " + attr + ": [" + getServletContext().getAttribute(attr) + "]"); } } @Override protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String method = req.getMethod(); if (cgiMethodsAll || cgiMethods.contains(method)) { doGet(req, res); } else if (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 protected void 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 (Method.POST.equals(req.getMethod())) { cgi.setInput(req.getInputStream()); } cgi.setResponse(res); cgi.run(); } else { res.sendError(404); } if (log.isTraceEnabled()) { String[] cgiEnvLines = cgiEnv.toString().split(System.lineSeparator()); for (String cgiEnvLine : cgiEnvLines) { log.trace(cgiEnvLine); } printServletEnvironment(req); } } @Override protected void doOptions(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Note: This method will never be called if cgiMethods is "*" so that // case does not 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(','); } // Remove trailing comma headerValue.deleteCharAt(headerValue.length() - 1); res.setHeader("allow", headerValue.toString()); } /* * 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. */ private boolean setStatus(HttpServletResponse response, int status) throws IOException { if (status >= HttpServletResponse.SC_BAD_REQUEST) { response.sendError(status); return true; } else { response.setStatus(status); return false; } } /** * Encapsulates the CGI environment and rules to derive that environment from the servlet container and request * information. */ protected class CGIEnvironment { /** context of the enclosing servlet */ private ServletContext context = null; /** context path of enclosing servlet */ private String contextPath = null; /** servlet URI of the enclosing servlet */ private String servletPath = null; /** pathInfo for the current request */ private String pathInfo = null; /** tempdir for context - used to expand scripts in unexpanded wars */ private File tmpDir = null; /** derived cgi environment */ private Map<String,String> env = null; /** cgi command to be invoked */ private String command = null; /** cgi command's desired working directory */ private final File workingDirectory; /** cgi command's command line parameters */ private final ArrayList<String> cmdLineParameters = new ArrayList<>(); /** whether or not this object is valid or not */ private final boolean 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 */ protected void setupFromContext(ServletContext context) { this.context = context; 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 */ protected boolean setupFromRequest(HttpServletRequest req) throws UnsupportedEncodingException { boolean isIncluded = req.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null; // Look to see if this request is an include 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 && (Method.GET.equals(req.getMethod()) || Method.POST.equals(req.getMethod()) || Method.HEAD.equals(req.getMethod()))) { 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())); } return false; } String decodedArgument = URLDecoder.decode(encodedArgument, parameterEncoding); if (cmdLineArgumentsDecodedPattern != null && !cmdLineArgumentsDecodedPattern.matcher(decodedArgument).matches()) { if (log.isDebugEnabled()) { log.debug(sm.getString("cgiServlet.invalidArgumentDecoded", decodedArgument, cmdLineArgumentsDecodedPattern.toString())); } return false; } cmdLineParameters.add(decodedArgument); } } } return true; } /** * 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 &lt;my-webapp-root&gt; 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 contextPath String as from HttpServletRequest.getContextPath() * @param servletPath String as from HttpServletRequest.getServletPath() * @param pathInfo String from HttpServletRequest.getPathInfo() * @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 contextPath, String servletPath, String pathInfo, String cgiPathPrefix) { StringBuilder cgiPath = new StringBuilder(); StringBuilder urlPath = new StringBuilder(); WebResource cgiScript = null; if (cgiPathPrefix == null || cgiPathPrefix.isEmpty()) { cgiPath.append(servletPath); } else { cgiPath.append('/'); cgiPath.append(cgiPathPrefix); } urlPath.append(servletPath); StringTokenizer pathWalker = new StringTokenizer(pathInfo, "/"); while (pathWalker.hasMoreElements() && (cgiScript == null || !cgiScript.isFile())) { String urlSegment = pathWalker.nextToken(); cgiPath.append('/'); cgiPath.append(urlSegment); urlPath.append('/'); urlPath.append(urlSegment); if (log.isTraceEnabled()) { log.trace(sm.getString("cgiServlet.find.location", cgiPath.toString())); } cgiScript = resources.getResource(cgiPath.toString()); } // No script was found if (cgiScript == null || !cgiScript.isFile()) { return new String[] { null, null, null, null }; } // Set-up return values String path = null; String scriptName = null; String cgiName = null; String name = null; path = cgiScript.getCanonicalPath(); if (path == null) { /* * The script doesn't exist directly on the file system. It might be located in an archive or similar. * Such scripts are extracted to the web application's temporary file location. */ File tmpCgiFile = new File(tmpDir + cgiPath.toString()); if (!tmpCgiFile.exists()) { // Create directories File parent = tmpCgiFile.getParentFile(); if (!parent.mkdirs() && !parent.isDirectory()) { log.warn(sm.getString("cgiServlet.expandCreateDirFail", parent.getAbsolutePath())); return new String[] { null, null, null, null }; } try (InputStream is = cgiScript.getInputStream()) { synchronized (expandFileLock) { // Check if file was created by concurrent request if (!tmpCgiFile.exists()) { try { Files.copy(is, tmpCgiFile.toPath()); } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.expandFail", cgiScript.getURL(), tmpCgiFile.getAbsolutePath()), ioe); if (tmpCgiFile.exists()) { if (!tmpCgiFile.delete()) { log.warn(sm.getString("cgiServlet.expandDeleteFail", tmpCgiFile.getAbsolutePath())); } } return new String[] { null, null, null, null }; } if (log.isDebugEnabled()) { log.debug(sm.getString("cgiServlet.expandOk", cgiScript.getURL(), tmpCgiFile.getAbsolutePath())); } } } } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.expandCloseFail", cgiScript.getURL()), ioe); } } path = tmpCgiFile.getAbsolutePath(); } scriptName = urlPath.toString(); cgiName = scriptName.substring(servletPath.length()); name = scriptName.substring(scriptName.lastIndexOf('/') + 1); if (log.isTraceEnabled()) { log.trace(sm.getString("cgiServlet.find.found", name, path, scriptName, cgiName)); } return new 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 */ 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) */ // Add the shell environment variables (if any) Map<String,String> envp = new HashMap<>(shellEnv); // Add the CGI environment variables String sPathInfoOrig; String sPathInfoCGI; String sPathTranslatedCGI = null; String sCGIFullPath; String sCGIScriptName; String sCGIFullName; String sCGIName; String[] sCGINames; sPathInfoOrig = this.pathInfo; sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig; sCGINames = findCGI(contextPath, servletPath, sPathInfoOrig, cgiPathPrefix); sCGIFullPath = sCGINames[0]; sCGIScriptName = sCGINames[1]; sCGIFullName = sCGINames[2]; sCGIName = sCGINames[3]; if (sCGIFullPath == null || sCGIScriptName == null || sCGIFullName == null || sCGIName == null) { return false; } envp.put("SERVER_SOFTWARE", "TOMCAT"); envp.put("SERVER_NAME", nullsToBlanks(req.getServerName())); envp.put("GATEWAY_INTERFACE", "CGI/1.1"); envp.put("SERVER_PROTOCOL", nullsToBlanks(req.getProtocol())); int port = req.getServerPort(); Integer iPort = (port == 0 ? Integer.valueOf(-1) : Integer.valueOf(port)); envp.put("SERVER_PORT", iPort.toString()); envp.put("REQUEST_METHOD", nullsToBlanks(req.getMethod())); envp.put("REQUEST_URI", nullsToBlanks(req.getRequestURI())); /*- * 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()).isEmpty())) { 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 || sPathTranslatedCGI.isEmpty()) { // NOOP } else { envp.put("PATH_TRANSLATED", nullsToBlanks(sPathTranslatedCGI)); } envp.put("SCRIPT_NAME", nullsToBlanks(sCGIScriptName)); envp.put("QUERY_STRING", nullsToBlanks(req.getQueryString())); envp.put("REMOTE_HOST", nullsToBlanks(req.getRemoteHost())); envp.put("REMOTE_ADDR", nullsToBlanks(req.getRemoteAddr())); envp.put("AUTH_TYPE", nullsToBlanks(req.getAuthType())); envp.put("REMOTE_USER", nullsToBlanks(req.getRemoteUser())); envp.put("REMOTE_IDENT", ""); // not necessary for full compliance envp.put("CONTENT_TYPE", nullsToBlanks(req.getContentType())); /* * 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; while (headers.hasMoreElements()) { 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; return true; } /** * Returns important CGI environment information in a multi-line text format. * * @return CGI environment info */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("CGIEnvironment Info:"); sb.append(System.lineSeparator()); if (isValid()) { sb.append("Validity: [true]"); sb.append(System.lineSeparator()); sb.append("Environment values:"); sb.append(System.lineSeparator()); for (Entry<String,String> entry : env.entrySet()) { sb.append(" "); sb.append(entry.getKey()); sb.append(": ["); sb.append(blanksToString(entry.getValue(), "will be set to blank")); sb.append(']'); sb.append(System.lineSeparator()); } sb.append("Derived Command :["); sb.append(nullsToBlanks(command)); sb.append(']'); sb.append(System.lineSeparator()); sb.append("Working Directory: ["); if (workingDirectory != null) { sb.append(workingDirectory.toString()); } sb.append(']'); sb.append(System.lineSeparator()); 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()); } 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 Map<String,String> getEnvironment() { return env; } /** * Gets derived CGI query parameters * * @return CGI query parameters */ protected ArrayList<String> 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</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> */ protected class CGIRunner { /** script/command to be executed */ private final String command; /** environment used when invoking the cgi script */ private final Map<String,String> env; /** working directory used when invoking the cgi script */ private final File wd; /** command line parameters to be passed to the invoked script */ private final 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() */ 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</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(); } /** * Checks and sets ready status */ protected void updateReadyStatus() { readyToRun = command != null && env != null && wd != null && params != null && response != null; } /** * Gets ready status * * @return false if not ready (<code>run</code> 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 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 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 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(sm.getString("cgiServlet.notReady")); } if (log.isTraceEnabled()) { log.trace("envp: [" + env + "], command: [" + command + "]"); } if ((command.contains(File.separator + "." + File.separator)) || (command.contains(File.separator + "..")) || (command.contains(".." + File.separator))) { throw new IOException(sm.getString("cgiServlet.invalidCommand", command)); } Runtime rt; BufferedReader cgiHeaderReader = null; InputStream cgiOutput = null; BufferedReader commandsStdErr; Thread errReaderThread = null; BufferedOutputStream commandsStdIn; Process proc = null; int bufRead = -1; List<String> cmdAndArgs = new ArrayList<>(); if (!cgiExecutable.isEmpty()) { cmdAndArgs.add(cgiExecutable); } if (cgiExecutableArgs != null) { cmdAndArgs.addAll(cgiExecutableArgs); } cmdAndArgs.add(command); cmdAndArgs.addAll(params); try { rt = Runtime.getRuntime(); proc = rt.exec(cmdAndArgs.toArray(new String[0]), mapToStringArray(env), wd); String sContentLength = 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://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4223650 */ 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; 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 } } } proc.exitValue(); // Throws exception if alive isRunning = false; } catch (IllegalThreadStateException e) { try { Thread.sleep(500); } catch (InterruptedException ignored) { // Ignore } } } // replacement for Process.waitFor() } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.runFail"), ioe); throw ioe; } finally { // Close the header reader if (cgiHeaderReader != null) { try { cgiHeaderReader.close(); } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.runHeaderReaderFail"), ioe); } } // Close the output stream if used if (cgiOutput != null) { try { cgiOutput.close(); } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.runOutputStreamFail"), ioe); } } // Make sure the error stream reader has finished if (errReaderThread != null) { try { errReaderThread.join(stderrTimeout); } catch (InterruptedException e) { log.warn(sm.getString("cgiServlet.runReaderInterrupt"), e); } } if (proc != null) { proc.destroy(); } } } /** * 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.warn(sm.getString("cgiServlet.runInvalidStatus", 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.warn(sm.getString("cgiServlet.runInvalidStatus", 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.warn(sm.getString("cgiServlet.runInvalidStatus", 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.warn(sm.getString("cgiServlet.runInvalidStatus", status)); return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; } return statusCode; } private void sendToLog(BufferedReader rdr) { String line; int lineCount = 0; try { while ((line = rdr.readLine()) != null) { log.warn(sm.getString("cgiServlet.runStdErr", line)); lineCount++; } } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.runStdErrFail"), ioe); } finally { try { rdr.close(); } catch (IOException ioe) { log.warn(sm.getString("cgiServlet.runStdErrFail"), ioe); } } if (lineCount > 0) { log.warn(sm.getString("cgiServlet.runStdErrCount", Integer.valueOf(lineCount))); } } } // class CGIRunner /** * This is an input stream specifically for reading HTTP headers. It reads up to and including the two blank lines * terminating the headers. It allows the content to be read using bytes or characters as appropriate. */ protected static 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 final InputStream input; private int state; HTTPHeaderInputStream(InputStream theInput) { input = theInput; state = STATE_CHARACTER; } /** * @see java.io.InputStream#read() */ @Override public int read() throws IOException { if (state == STATE_HEADER_END) { return -1; } int i = input.read(); /* * Update the state * State machine looks like this * @formatter:off * * -------->-------- * | (CR) | * | | * CR1--->--- | * | | | * ^(CR) |(LF) | * | | | * CHAR--->--LF1--->--EOH * (LF) | (LF) | * |(CR) ^(LF) * | | * (CR2)-->--- * * @formatter:on */ 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
Detected license expression
apache-2.0
Detected license expression (SPDX)
Apache-2.0
Percentage of license text
1.88
Copyrights

      
    
Holders

      
    
Authors

      
    
License detections License expression License expression SPDX
apache_2_0-4bde3f57-78aa-4201-96bf-531cba09e7de apache-2.0 Apache-2.0
URL Start line End line
http://www.apache.org/licenses/LICENSE-2.0 9 9
http://cgi-spec.golux.com/ 117 117
https://tomcat.apache.org/lists.html 121 121
https://tools.ietf.org/html/rfc3875#section-4.4 272 272
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4223650 1431 1431