diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java index f66ee3245e..db3794c39f 100644 --- a/plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgCGIServlet.java @@ -44,8 +44,8 @@ import sonia.scm.repository.HgRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.util.AssertUtil; -import sonia.scm.web.cgi.AbstractCGIServlet; -import sonia.scm.web.cgi.EnvList; +import sonia.scm.web.cgi.CGIExecutor; +import sonia.scm.web.cgi.CGIExecutorFactory; //~--- JDK imports ------------------------------------------------------------ @@ -56,14 +56,16 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * * @author Sebastian Sdorra */ @Singleton -public class HgCGIServlet extends AbstractCGIServlet +public class HgCGIServlet extends HttpServlet { /** Field description */ @@ -90,16 +92,20 @@ public class HgCGIServlet extends AbstractCGIServlet * * * + * + * @param cgiExecutorFactory * @param configuration * @param repositoryManager * @param handler */ @Inject - public HgCGIServlet(ScmConfiguration configuration, + public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory, + ScmConfiguration configuration, RepositoryManager repositoryManager, HgRepositoryHandler handler) { - super(configuration); + this.cgiExecutorFactory = cgiExecutorFactory; + this.configuration = configuration; this.repositoryManager = repositoryManager; this.handler = handler; } @@ -124,18 +130,16 @@ public class HgCGIServlet extends AbstractCGIServlet * * * @param request - * @param baseEnvironment - * - * @return + * @param response * + * @throws IOException * @throws ServletException */ @Override - protected EnvList createRequestEnvironment(HttpServletRequest request, - EnvList baseEnvironment) - throws ServletException + protected void service(HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { - EnvList list = new EnvList(baseEnvironment); Repository repository = getRepository(request); if (repository == null) @@ -145,10 +149,6 @@ public class HgCGIServlet extends AbstractCGIServlet String name = repository.getName(); File directory = handler.getDirectory(repository); - - list.set(ENV_REPOSITORY_PATH, directory.getAbsolutePath()); - list.set(ENV_REPOSITORY_NAME, name); - String pythonPath = ""; HgConfig config = handler.getConfig(); @@ -162,9 +162,22 @@ public class HgCGIServlet extends AbstractCGIServlet } } - list.set(ENV_PYTHON_PATH, pythonPath); + CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration, + getServletContext(), request, response); - return list; + executor.getEnvironment().set(ENV_REPOSITORY_NAME, name); + executor.getEnvironment().set(ENV_REPOSITORY_PATH, + directory.getAbsolutePath()); + executor.getEnvironment().set(ENV_PYTHON_PATH, pythonPath); + + String interpreter = getInterpreter(); + + if (interpreter != null) + { + executor.setInterpreter(interpreter); + } + + executor.execute(command.getAbsolutePath()); } //~--- get methods ---------------------------------------------------------- @@ -175,8 +188,7 @@ public class HgCGIServlet extends AbstractCGIServlet * * @return */ - @Override - protected String getCmdPrefix() + private String getInterpreter() { HgConfig config = handler.getConfig(); @@ -192,24 +204,6 @@ public class HgCGIServlet extends AbstractCGIServlet return python; } - /** - * Method description - * - * - * @param req - * - * @return - * - * @throws IOException - * @throws ServletException - */ - @Override - protected File getCommand(HttpServletRequest req) - throws ServletException, IOException - { - return command; - } - /** * Method description * @@ -218,7 +212,7 @@ public class HgCGIServlet extends AbstractCGIServlet * * @return */ - protected Repository getRepository(HttpServletRequest request) + private Repository getRepository(HttpServletRequest request) { Repository repository = null; String uri = request.getRequestURI(); @@ -252,9 +246,15 @@ public class HgCGIServlet extends AbstractCGIServlet //~--- fields --------------------------------------------------------------- + /** Field description */ + private CGIExecutorFactory cgiExecutorFactory; + /** Field description */ private File command; + /** Field description */ + private ScmConfiguration configuration; + /** Field description */ private HgRepositoryHandler handler; diff --git a/scm-core/src/main/java/sonia/scm/util/IOUtil.java b/scm-core/src/main/java/sonia/scm/util/IOUtil.java index 9c9afa55bb..d7cca44d7c 100644 --- a/scm-core/src/main/java/sonia/scm/util/IOUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/IOUtil.java @@ -118,12 +118,14 @@ public class IOUtil * * @param reader * @param writer + * @param bufferSize * * @throws IOException */ - public static void copy(Reader reader, Writer writer) throws IOException + public static void copy(Reader reader, Writer writer, int bufferSize) + throws IOException { - char[] buffer = new char[0xFFFF]; + char[] buffer = new char[bufferSize]; for (int len; (len = reader.read(buffer)) != -1; ) { @@ -131,6 +133,20 @@ public class IOUtil } } + /** + * Method description + * + * + * @param reader + * @param writer + * + * @throws IOException + */ + public static void copy(Reader reader, Writer writer) throws IOException + { + copy(reader, writer, 0xFFFF); + } + /** * Method description * @@ -142,12 +158,30 @@ public class IOUtil */ public static void copy(InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[0xFFFF]; + copy(in, out, 0xFFFF); + } + + /** + * Method description + * + * + * @param in + * @param out + * @param bufferSize + * + * @throws IOException + */ + public static void copy(InputStream in, OutputStream out, int bufferSize) + throws IOException + { + byte[] buffer = new byte[bufferSize]; for (int len; (len = in.read(buffer)) != -1; ) { out.write(buffer, 0, len); } + + out.flush(); } /** diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java new file mode 100644 index 0000000000..10ef6981d6 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIExecutor.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.cgi; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class AbstractCGIExecutor implements CGIExecutor +{ + + /** + * Method description + * + * + * @return + */ + @Override + public int getBufferSize() + { + return bufferSize; + } + + /** + * Method description + * + * + * @return + */ + @Override + public EnvList getEnvironment() + { + return environment; + } + + /** + * Method description + * + * + * @return + */ + @Override + public String getInterpreter() + { + return interpreter; + } + + /** + * Method description + * + * + * @return + */ + @Override + public File getWorkDirectory() + { + return workDirectory; + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param bufferSize + */ + @Override + public void setBufferSize(int bufferSize) + { + this.bufferSize = bufferSize; + } + + /** + * Method description + * + * + * @param environment + */ + @Override + public void setEnvironment(EnvList environment) + { + this.environment = environment; + } + + /** + * Method description + * + * + * @param interpreter + */ + @Override + public void setInterpreter(String interpreter) + { + this.interpreter = interpreter; + } + + /** + * Method description + * + * + * @param workDirectory + */ + @Override + public void setWorkDirectory(File workDirectory) + { + this.workDirectory = workDirectory; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + protected int bufferSize; + + /** Field description */ + protected EnvList environment; + + /** Field description */ + protected String interpreter; + + /** Field description */ + protected File workDirectory; +} diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIServlet.java b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIServlet.java index 1693bde3eb..4db1645519 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIServlet.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/AbstractCGIServlet.java @@ -53,7 +53,9 @@ import javax.servlet.http.HttpServletResponse; /** * * @author Sebastian Sdorra + * @deprecated use {@link CGIExecutorFactory} */ +@Deprecated public abstract class AbstractCGIServlet extends HttpServlet { diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java new file mode 100644 index 0000000000..ee6802d54c --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutor.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.cgi; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletException; + +/** + * + * @author Sebastian Sdorra + */ +public interface CGIExecutor +{ + + /** Field description */ + public static final String ENV_AUTH_TYPE = "AUTH_TYPE"; + + /** Field description */ + public static final String ENV_CONTENT_LENGTH = "CONTENT_LENGTH"; + + /** Field description */ + public static final String ENV_CONTENT_TYPE = "CONTENT_TYPE"; + + /** Field description */ + public static final String ENV_GATEWAY_INTERFACE = "GATEWAY_INTERFACE"; + + /** Field description */ + public static final String ENV_HTTPS = "HTTPS"; + + /** Field description */ + public static final String ENV_HTTPS_VALUE_OFF = "OFF"; + + /** Field description */ + public static final String ENV_HTTPS_VALUE_ON = "ON"; + + /** Field description */ + public static final String ENV_HTTP_HEADER_PREFIX = "HTTP_"; + + /** Field description */ + public static final String ENV_PATH_INFO = "PATH_INFO"; + + /** Field description */ + public static final String ENV_PATH_TRANSLATED = "PATH_TRANSLATED"; + + /** Field description */ + public static final String ENV_QUERY_STRING = "QUERY_STRING"; + + /** Field description */ + public static final String ENV_REMOTE_ADDR = "REMOTE_ADDR"; + + /** Field description */ + public static final String ENV_REMOTE_HOST = "REMOTE_HOST"; + + /** Field description */ + public static final String ENV_REMOTE_USER = "REMOTE_USER"; + + /** Field description */ + public static final String ENV_REQUEST_METHOD = "REQUEST_METHOD"; + + /** Field description */ + public static final String ENV_SCRIPT_FILENAME = "SCRIPT_FILENAME"; + + /** Field description */ + public static final String ENV_SCRIPT_NAME = "SCRIPT_NAME"; + + /** Field description */ + public static final String ENV_SERVER_NAME = "SERVER_NAME"; + + /** Field description */ + public static final String ENV_SERVER_PORT = "SERVER_PORT"; + + /** Field description */ + public static final String ENV_SERVER_PROTOCOL = "SERVER_PROTOCOL"; + + /** Field description */ + public static final String ENV_SERVER_SOFTWARE = "SERVER_SOFTWARE"; + + /** Field description */ + public static final String RESPONSE_HEADER_HTTP_PREFIX = "HTTP"; + + /** Field description */ + public static final String RESPONSE_HEADER_LOCATION = "Location"; + + /** Field description */ + public static final String RESPONSE_HEADER_STATUS = "Status"; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param cmd + * + * @throws IOException + * @throws ServletException + */ + public void execute(String cmd) throws IOException, ServletException; + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public int getBufferSize(); + + /** + * Method description + * + * + * @return + */ + public EnvList getEnvironment(); + + /** + * Method description + * + * + * @return + */ + public String getInterpreter(); + + /** + * Method description + * + * + * @return + */ + public File getWorkDirectory(); + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param bufferSize + */ + public void setBufferSize(int bufferSize); + + /** + * Method description + * + * + * @param environment + */ + public void setEnvironment(EnvList environment); + + /** + * Method description + * + * + * @param interpreter + */ + public void setInterpreter(String interpreter); + + /** + * Method description + * + * + * @param workDirectory + */ + public void setWorkDirectory(File workDirectory); +} diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutorFactory.java b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutorFactory.java new file mode 100644 index 0000000000..0ef14a9db5 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/cgi/CGIExecutorFactory.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.cgi; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.config.ScmConfiguration; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public interface CGIExecutorFactory +{ + + /** + * Method description + * + * + * + * @param configuration + * @param context + * @param request + * @param response + * + * @return + */ + public CGIExecutor createExecutor(ScmConfiguration configuration, + ServletContext context, + HttpServletRequest request, + HttpServletResponse response); +} diff --git a/scm-core/src/main/java/sonia/scm/web/cgi/CGIRunner.java b/scm-core/src/main/java/sonia/scm/web/cgi/CGIRunner.java index 5623f9df7d..9554aab8e1 100644 --- a/scm-core/src/main/java/sonia/scm/web/cgi/CGIRunner.java +++ b/scm-core/src/main/java/sonia/scm/web/cgi/CGIRunner.java @@ -59,8 +59,9 @@ import javax.servlet.http.HttpServletResponse; * Based on org.eclipse.jetty.servlets.CGI * * @author Sebastian Sdorra - * + * @deprecated use {@link CGIExecutorFactory} */ +@Deprecated public class CGIRunner { @@ -335,11 +336,7 @@ public class CGIRunner } finally { - if (os != null) - { - IOUtil.close(os); - } - + IOUtil.close(os); os = null; p.destroy(); diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index b93f6918c8..d70f6ab7b1 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -278,7 +278,7 @@ 1.11 1.0-beta-7 3.0.3 - Tomcat60 + gfv3ee6 diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 75e8c9a164..92dbfa9a6a 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -70,6 +70,8 @@ import sonia.scm.template.TemplateServlet; import sonia.scm.user.UserManager; import sonia.scm.user.xml.XmlUserManager; import sonia.scm.util.DebugServlet; +import sonia.scm.web.cgi.CGIExecutorFactory; +import sonia.scm.web.cgi.DefaultCGIExecutorFactory; import sonia.scm.web.security.AuthenticationManager; import sonia.scm.web.security.BasicSecurityContext; import sonia.scm.web.security.ChainAuthenticatonManager; @@ -215,8 +217,9 @@ public class ScmServletModule extends ServletModule bind(RepositoryManager.class).to(XmlRepositoryManager.class); bind(UserManager.class).to(XmlUserManager.class); bind(GroupManager.class).to(XmlGroupManager.class); + bind(CGIExecutorFactory.class).to(DefaultCGIExecutorFactory.class); - // filter(PATTERN_RESTAPI).through(LoggingFilter.class); + // filter("/hg/*").through(LoggingFilter.class); /* * filter(PATTERN_PAGE, diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java new file mode 100644 index 0000000000..668c9349fb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutor.java @@ -0,0 +1,487 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.cgi; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.SCMContext; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.util.HttpUtil; +import sonia.scm.util.IOUtil; +import sonia.scm.util.Util; + +//~--- JDK imports ------------------------------------------------------------ + +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.util.Enumeration; + +import javax.servlet.ServletContext; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public class DefaultCGIExecutor extends AbstractCGIExecutor +{ + + /** Field description */ + public static final String CGI_VERSION = "CGI/1.1"; + + /** Field description */ + public static final int DEFAULT_BUFFER_SIZE = 16264; + + /** Field description */ + private static final String SERVER_SOFTWARE_PREFIX = "scm-manager/"; + + /** the logger for DefaultCGIExecutor */ + private static final Logger logger = + LoggerFactory.getLogger(DefaultCGIExecutor.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configuration + * @param context + * @param request + * @param response + */ + public DefaultCGIExecutor(ScmConfiguration configuration, + ServletContext context, HttpServletRequest request, + HttpServletResponse response) + { + this.configuration = configuration; + this.context = context; + this.request = request; + this.response = response; + + // set default values + this.bufferSize = DEFAULT_BUFFER_SIZE; + this.environment = createEnvironment(); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * + * + * @param cmd + * + * @throws IOException + */ + @Override + public void execute(String cmd) throws IOException + { + File command = new File(cmd); + EnvList env = new EnvList(environment); + + if (workDirectory == null) + { + workDirectory = command.getParentFile(); + } + + String path = command.getAbsolutePath(); + String pathTranslated = request.getPathTranslated(); + + if (Util.isEmpty(pathTranslated)) + { + pathTranslated = path; + } + + env.set(ENV_PATH_TRANSLATED, pathTranslated); + + String execCmd = path; + + if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0)) + { + execCmd = "\"" + execCmd + "\""; + } + + if (interpreter != null) + { + execCmd = interpreter + " " + execCmd; + } + + if (logger.isDebugEnabled()) + { + logger.debug("execute cgi: {}", execCmd); + + if (logger.isTraceEnabled()) + { + logger.trace(environment.toString()); + } + } + + Process p = Runtime.getRuntime().exec(execCmd, environment.getEnvArray(), + workDirectory); + + execute(p); + } + + /** + * Method description + * + * + * @return + */ + private EnvList createEnvironment() + { + String pathInfo = request.getPathInfo(); + int serverPort = HttpUtil.getServerPort(configuration, request); + String scriptName = request.getRequestURI().substring(0, + request.getRequestURI().length() - pathInfo.length()); + String scriptPath = context.getRealPath(scriptName); + int len = request.getContentLength(); + + if (len < 0) + { + len = 0; + } + + EnvList env = new EnvList(); + + env.set(ENV_AUTH_TYPE, request.getAuthType()); + env.set(ENV_CONTENT_LENGTH, Integer.toString(len)); + env.set(ENV_CONTENT_TYPE, request.getContentType()); + env.set(ENV_GATEWAY_INTERFACE, CGI_VERSION); + env.set(ENV_PATH_INFO, pathInfo); + env.set(ENV_QUERY_STRING, request.getQueryString()); + env.set(ENV_REMOTE_ADDR, request.getRemoteAddr()); + env.set(ENV_REMOTE_HOST, request.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(ENV_REMOTE_USER, request.getRemoteUser()); + env.set(ENV_REQUEST_METHOD, request.getMethod()); + env.set(ENV_SCRIPT_NAME, scriptName); + env.set(ENV_SCRIPT_FILENAME, scriptPath); + env.set(ENV_SERVER_NAME, request.getServerName()); + env.set(ENV_SERVER_PORT, Integer.toString(serverPort)); + env.set(ENV_SERVER_PROTOCOL, request.getProtocol()); + env.set( + ENV_SERVER_SOFTWARE, + SERVER_SOFTWARE_PREFIX.concat(SCMContext.getContext().getVersion())); + + Enumeration enm = request.getHeaderNames(); + + while (enm.hasMoreElements()) + { + String name = (String) enm.nextElement(); + String value = request.getHeader(name); + + env.set(ENV_HTTP_HEADER_PREFIX + name.toUpperCase().replace('-', '_'), + value); + } + + // these extra ones were from printenv on www.dev.nomura.co.uk + env.set(ENV_HTTPS, (request.isSecure() + ? ENV_HTTPS_VALUE_ON + : ENV_HTTPS_VALUE_OFF)); + + return env; + } + + /** + * Method description + * + * + * @param process + * + * @throws IOException + */ + private void execute(Process process) throws IOException + { + InputStream processIS = null; + InputStream processES = null; + + try + { + processIS = process.getInputStream(); + processES = process.getErrorStream(); + processServletInput(process); + processProcessInputStream(processIS); + processErrorStream(processES); + waitForFinish(process); + } + finally + { + IOUtil.close(processIS); + IOUtil.close(processES); + } + } + + /** + * Method description + * + * + * @param is + * + * + * @throws IOException + */ + private void parseHeaders(InputStream is) throws IOException + { + String line = null; + + while ((line = getTextLineFromStream(is)).length() > 0) + { + if (logger.isTraceEnabled()) + { + logger.trace(" ".concat(line)); + } + + if (!line.startsWith(RESPONSE_HEADER_HTTP_PREFIX)) + { + int k = line.indexOf(':'); + + if (k > 0) + { + String key = line.substring(0, k).trim(); + String value = line.substring(k + 1).trim(); + + if (RESPONSE_HEADER_LOCATION.equals(key)) + { + response.sendRedirect(response.encodeRedirectURL(value)); + } + else if (RESPONSE_HEADER_STATUS.equals(key)) + { + String[] token = value.split(" "); + int status = Integer.parseInt(token[0]); + + if (logger.isDebugEnabled()) + { + logger.debug("CGI returned with status {}", status); + } + + if (status < 304) + { + response.setStatus(status); + } + else + { + response.sendError(status); + } + } + else + { + + // add remaining header items to our response header + response.addHeader(key, value); + } + } + } + } + } + + /** + * Method description + * + * + * @param in + * + * @throws IOException + */ + private void processErrorStream(InputStream in) throws IOException + { + BufferedReader reader = null; + + try + { + reader = new BufferedReader(new InputStreamReader(in)); + + StringBuilder error = new StringBuilder(); + String s = System.getProperty("line.separator"); + String line = reader.readLine(); + + while (line != null) + { + error.append(line); + line = reader.readLine(); + + if (line != null) + { + error.append(s); + } + } + + if (logger.isWarnEnabled()) + { + logger.warn(error.toString()); + } + } + finally + { + IOUtil.close(reader); + } + } + + /** + * Method description + * + * + * @param is + * + * @throws IOException + */ + private void processProcessInputStream(InputStream is) throws IOException + { + parseHeaders(is); + + ServletOutputStream servletOS = null; + + try + { + servletOS = response.getOutputStream(); + IOUtil.copy(is, servletOS, bufferSize); + } + finally + { + IOUtil.close(servletOS); + } + } + + /** + * Method description + * + * + * @param process + */ + private void processServletInput(Process process) + { + OutputStream processOS = null; + ServletInputStream servletIS = null; + + try + { + processOS = process.getOutputStream(); + servletIS = request.getInputStream(); + IOUtil.copy(servletIS, processOS, bufferSize); + } + catch (IOException ex) + { + logger.error( + "could not read from ServletInputStream and write to ProcessOutputStream", + ex); + } + finally + { + IOUtil.close(processOS); + IOUtil.close(servletIS); + } + } + + /** + * Method description + * + * + * @param process + * + */ + private void waitForFinish(Process process) + { + try + { + int exitCode = process.waitFor(); + + if (exitCode != 0) + { + logger.warn("process ends with exit code {}", exitCode); + } + } + catch (InterruptedException ex) + { + logger.error("process interrupted", ex); + } + } + + //~--- 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 ScmConfiguration configuration; + + /** Field description */ + private ServletContext context; + + /** Field description */ + private HttpServletRequest request; + + /** Field description */ + private HttpServletResponse response; +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java new file mode 100644 index 0000000000..eaa89224e2 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/web/cgi/DefaultCGIExecutorFactory.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.cgi; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.config.ScmConfiguration; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public class DefaultCGIExecutorFactory implements CGIExecutorFactory +{ + + /** + * Method description + * + * + * @param configuration + * @param context + * @param request + * @param response + * + * @return + */ + @Override + public CGIExecutor createExecutor(ScmConfiguration configuration, + ServletContext context, + HttpServletRequest request, + HttpServletResponse response) + { + return new DefaultCGIExecutor(configuration, context, request, response); + } +}