mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-13 17:05:43 +01:00
490 lines
11 KiB
Java
490 lines
11 KiB
Java
/*
|
|
* To change this template, choose Tools | Templates
|
|
* and open the template in the editor.
|
|
*/
|
|
|
|
|
|
|
|
package sonia.scm.web.cgi;
|
|
|
|
//~--- non-JDK imports --------------------------------------------------------
|
|
|
|
import sonia.scm.util.IOUtil;
|
|
|
|
//~--- JDK imports ------------------------------------------------------------
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.InputStreamReader;
|
|
import java.io.OutputStream;
|
|
import java.io.OutputStreamWriter;
|
|
|
|
import java.util.Enumeration;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.servlet.ServletContext;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
/**
|
|
* Based on org.eclipse.jetty.servlets.CGI
|
|
*
|
|
* @author Sebastian Sdorra
|
|
*
|
|
*/
|
|
public class CGIRunner
|
|
{
|
|
|
|
/** Field description */
|
|
public static final int BUFFERSIZE = 2 * 8192;
|
|
|
|
/** Field description */
|
|
private static final Logger logger =
|
|
Logger.getLogger(CGIRunner.class.getName());
|
|
|
|
//~--- constructors ---------------------------------------------------------
|
|
|
|
/**
|
|
* Constructs ...
|
|
*
|
|
*
|
|
* @param context
|
|
* @param environment
|
|
* @param cmdPrefix
|
|
* @param ignoreExitState
|
|
*/
|
|
public CGIRunner(ServletContext context, EnvList environment,
|
|
String cmdPrefix, boolean ignoreExitState)
|
|
{
|
|
this.context = context;
|
|
this.environment = environment;
|
|
this.cmdPrefix = cmdPrefix;
|
|
this.ignoreExitState = ignoreExitState;
|
|
}
|
|
|
|
//~--- methods --------------------------------------------------------------
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param command
|
|
* @param pathInfo
|
|
* @param req
|
|
* @param res
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public void exec(File command, String pathInfo, HttpServletRequest req,
|
|
HttpServletResponse res)
|
|
throws IOException
|
|
{
|
|
String path = command.getAbsolutePath();
|
|
File dir = command.getParentFile();
|
|
String scriptName = req.getRequestURI().substring(0,
|
|
req.getRequestURI().length() - pathInfo.length());
|
|
String scriptPath = context.getRealPath(scriptName);
|
|
String pathTranslated = req.getPathTranslated();
|
|
int len = req.getContentLength();
|
|
|
|
if (len < 0)
|
|
{
|
|
len = 0;
|
|
}
|
|
|
|
if ((pathTranslated == null) || (pathTranslated.length() == 0))
|
|
{
|
|
pathTranslated = path;
|
|
}
|
|
|
|
EnvList env = new EnvList(environment);
|
|
|
|
// these ones are from "The WWW Common Gateway Interface Version 1.1"
|
|
// look at :
|
|
// http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
|
|
env.set("AUTH_TYPE", req.getAuthType());
|
|
env.set("CONTENT_LENGTH", Integer.toString(len));
|
|
env.set("CONTENT_TYPE", req.getContentType());
|
|
env.set("GATEWAY_INTERFACE", "CGI/1.1");
|
|
|
|
if ((pathInfo != null) && (pathInfo.length() > 0))
|
|
{
|
|
env.set("PATH_INFO", pathInfo);
|
|
}
|
|
|
|
env.set("PATH_TRANSLATED", pathTranslated);
|
|
env.set("QUERY_STRING", req.getQueryString());
|
|
env.set("REMOTE_ADDR", req.getRemoteAddr());
|
|
env.set("REMOTE_HOST", req.getRemoteHost());
|
|
|
|
// The identity information reported about the connection by a
|
|
// RFC 1413 [11] request to the remote agent, if
|
|
// available. Servers MAY choose not to support this feature, or
|
|
// not to request the data for efficiency reasons.
|
|
// "REMOTE_IDENT" => "NYI"
|
|
env.set("REMOTE_USER", req.getRemoteUser());
|
|
env.set("REQUEST_METHOD", req.getMethod());
|
|
env.set("SCRIPT_NAME", scriptName);
|
|
env.set("SCRIPT_FILENAME", scriptPath);
|
|
env.set("SERVER_NAME", req.getServerName());
|
|
env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
|
|
env.set("SERVER_PROTOCOL", req.getProtocol());
|
|
env.set("SERVER_SOFTWARE", context.getServerInfo());
|
|
|
|
Enumeration enm = req.getHeaderNames();
|
|
|
|
while (enm.hasMoreElements())
|
|
{
|
|
String name = (String) enm.nextElement();
|
|
String value = req.getHeader(name);
|
|
|
|
env.set("HTTP_" + name.toUpperCase().replace('-', '_'), value);
|
|
}
|
|
|
|
// these extra ones were from printenv on www.dev.nomura.co.uk
|
|
env.set("HTTPS", (req.isSecure()
|
|
? "ON"
|
|
: "OFF"));
|
|
|
|
// "DOCUMENT_ROOT" => root + "/docs",
|
|
// "SERVER_URL" => "NYI - http://us0245",
|
|
// "TZ" => System.getProperty("user.timezone"),
|
|
// are we meant to decode args here ? or does the script get them
|
|
// via PATH_INFO ? if we are, they should be decoded and passed
|
|
// into exec here...
|
|
String execCmd = path;
|
|
|
|
if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
|
|
{
|
|
execCmd = "\"" + execCmd + "\"";
|
|
}
|
|
|
|
if (cmdPrefix != null)
|
|
{
|
|
execCmd = cmdPrefix + " " + execCmd;
|
|
}
|
|
|
|
if (logger.isLoggable(Level.FINE))
|
|
{
|
|
logger.fine("execute cgi: ".concat(execCmd));
|
|
}
|
|
|
|
Process p = (dir == null)
|
|
? Runtime.getRuntime().exec(execCmd, env.getEnvArray())
|
|
: Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
|
|
|
|
// hook processes input to browser's output (async)
|
|
final InputStream inFromReq = req.getInputStream();
|
|
final OutputStream outToCgi = p.getOutputStream();
|
|
final int inLength = len;
|
|
|
|
// TODO: print or log error stream
|
|
IOUtil.copyThread(new InputStreamReader(p.getErrorStream()),
|
|
new OutputStreamWriter(System.err));
|
|
new Thread(new Runnable()
|
|
{
|
|
@Override
|
|
public void run()
|
|
{
|
|
try
|
|
{
|
|
if (inLength > 0)
|
|
{
|
|
copy(inFromReq, outToCgi, inLength);
|
|
}
|
|
|
|
outToCgi.close();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
logger.log(Level.FINEST, null, e);
|
|
}
|
|
finally
|
|
{
|
|
IOUtil.close(inFromReq);
|
|
IOUtil.close(outToCgi);
|
|
}
|
|
}
|
|
}).start();
|
|
|
|
// hook processes output to browser's input (sync)
|
|
// if browser closes stream, we should detect it and kill process...
|
|
OutputStream os = null;
|
|
|
|
try
|
|
{
|
|
|
|
// read any headers off the top of our input stream
|
|
// NOTE: Multiline header items not supported!
|
|
String line = null;
|
|
InputStream inFromCgi = p.getInputStream();
|
|
|
|
// br=new BufferedReader(new InputStreamReader(inFromCgi));
|
|
// while ((line=br.readLine())!=null)
|
|
while ((line = getTextLineFromStream(inFromCgi)).length() > 0)
|
|
{
|
|
if (!line.startsWith("HTTP"))
|
|
{
|
|
int k = line.indexOf(':');
|
|
|
|
if (k > 0)
|
|
{
|
|
String key = line.substring(0, k).trim();
|
|
String value = line.substring(k + 1).trim();
|
|
|
|
if ("Location".equals(key))
|
|
{
|
|
res.sendRedirect(res.encodeRedirectURL(value));
|
|
}
|
|
else if ("Status".equals(key))
|
|
{
|
|
String[] token = value.split(" ");
|
|
int status = Integer.parseInt(token[0]);
|
|
|
|
res.setStatus(status);
|
|
}
|
|
else
|
|
{
|
|
|
|
// add remaining header items to our response header
|
|
res.addHeader(key, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy cgi content to response stream...
|
|
os = res.getOutputStream();
|
|
IOUtil.copy(inFromCgi, os);
|
|
p.waitFor();
|
|
|
|
if (!ignoreExitState)
|
|
{
|
|
int exitValue = p.exitValue();
|
|
|
|
if (0 != exitValue)
|
|
{
|
|
StringBuilder msg = new StringBuilder("Non-zero exit status (");
|
|
|
|
msg.append(exitValue).append(") from CGI program: ").append(path);
|
|
logger.warning(msg.toString());
|
|
|
|
if (!res.isCommitted())
|
|
{
|
|
res.sendError(500, "Failed to exec CGI");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
|
|
// browser has probably closed its input stream - we
|
|
// terminate and clean up...
|
|
logger.finest("CGI: Client closed connection!");
|
|
}
|
|
catch (InterruptedException ie)
|
|
{
|
|
logger.finest("CGI: interrupted!");
|
|
}
|
|
finally
|
|
{
|
|
if (os != null)
|
|
{
|
|
IOUtil.close(os);
|
|
}
|
|
|
|
os = null;
|
|
p.destroy();
|
|
|
|
// Log.debug("CGI: terminated!");
|
|
}
|
|
}
|
|
|
|
//~--- get methods ----------------------------------------------------------
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @return
|
|
*/
|
|
public String getCmdPrefix()
|
|
{
|
|
return cmdPrefix;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @return
|
|
*/
|
|
public ServletContext getContext()
|
|
{
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @return
|
|
*/
|
|
public EnvList getEnvironment()
|
|
{
|
|
return environment;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isIgnoreExitState()
|
|
{
|
|
return ignoreExitState;
|
|
}
|
|
|
|
//~--- set methods ----------------------------------------------------------
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param cmdPrefix
|
|
*/
|
|
public void setCmdPrefix(String cmdPrefix)
|
|
{
|
|
this.cmdPrefix = cmdPrefix;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param context
|
|
*/
|
|
public void setContext(ServletContext context)
|
|
{
|
|
this.context = context;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param environment
|
|
*/
|
|
public void setEnvironment(EnvList environment)
|
|
{
|
|
this.environment = environment;
|
|
}
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param ignoreExitState
|
|
*/
|
|
public void setIgnoreExitState(boolean ignoreExitState)
|
|
{
|
|
this.ignoreExitState = ignoreExitState;
|
|
}
|
|
|
|
//~--- methods --------------------------------------------------------------
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param in
|
|
* @param out
|
|
* @param byteCount
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
private void copy(InputStream in, OutputStream out, long byteCount)
|
|
throws IOException
|
|
{
|
|
byte buffer[] = new byte[BUFFERSIZE];
|
|
int len = BUFFERSIZE;
|
|
|
|
if (byteCount >= 0)
|
|
{
|
|
while (byteCount > 0)
|
|
{
|
|
int max = (byteCount < BUFFERSIZE)
|
|
? (int) byteCount
|
|
: BUFFERSIZE;
|
|
|
|
len = in.read(buffer, 0, max);
|
|
|
|
if (len == -1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
byteCount -= len;
|
|
out.write(buffer, 0, len);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
len = in.read(buffer, 0, BUFFERSIZE);
|
|
|
|
if (len < 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
out.write(buffer, 0, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
//~--- get methods ----------------------------------------------------------
|
|
|
|
/**
|
|
* Method description
|
|
*
|
|
*
|
|
* @param is
|
|
*
|
|
* @return
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
private String getTextLineFromStream(InputStream is) throws IOException
|
|
{
|
|
StringBuilder buffer = new StringBuilder();
|
|
int b;
|
|
|
|
while ((b = is.read()) != -1 && (b != (int) '\n'))
|
|
{
|
|
buffer.append((char) b);
|
|
}
|
|
|
|
return buffer.toString().trim();
|
|
}
|
|
|
|
//~--- fields ---------------------------------------------------------------
|
|
|
|
/** Field description */
|
|
private String cmdPrefix;
|
|
|
|
/** Field description */
|
|
private ServletContext context;
|
|
|
|
/** Field description */
|
|
private EnvList environment;
|
|
|
|
/** Field description */
|
|
private boolean ignoreExitState;
|
|
}
|