mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-10 15:35:49 +01:00
merge with branch 1.x
This commit is contained in:
@@ -58,6 +58,14 @@ public class HgConfig extends RepositoryConfig
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
// Don't change this without migrating SCM permission configuration!
|
||||
return PERMISSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -124,6 +132,14 @@ public class HgConfig extends RepositoryConfig
|
||||
return useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public boolean isDisableHookSSLValidation() {
|
||||
return disableHookSSLValidation;
|
||||
}
|
||||
|
||||
public boolean isEnableHttpPostArgs() {
|
||||
return enableHttpPostArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -194,6 +210,10 @@ public class HgConfig extends RepositoryConfig
|
||||
this.showRevisionInId = showRevisionInId;
|
||||
}
|
||||
|
||||
public void setEnableHttpPostArgs(boolean enableHttpPostArgs) {
|
||||
this.enableHttpPostArgs = enableHttpPostArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -205,6 +225,10 @@ public class HgConfig extends RepositoryConfig
|
||||
this.useOptimizedBytecode = useOptimizedBytecode;
|
||||
}
|
||||
|
||||
public void setDisableHookSSLValidation(boolean disableHookSSLValidation) {
|
||||
this.disableHookSSLValidation = disableHookSSLValidation;
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
@@ -225,10 +249,11 @@ public class HgConfig extends RepositoryConfig
|
||||
/** Field description */
|
||||
private boolean showRevisionInId = false;
|
||||
|
||||
@Override
|
||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||
public String getId() {
|
||||
// Don't change this without migrating SCM permission configuration!
|
||||
return PERMISSION;
|
||||
}
|
||||
private boolean enableHttpPostArgs = false;
|
||||
|
||||
/**
|
||||
* disable validation of ssl certificates for mercurial hook
|
||||
* @see <a href="https://goo.gl/zH5eY8">Issue 959</a>
|
||||
*/
|
||||
private boolean disableHookSSLValidation = false;
|
||||
}
|
||||
|
||||
@@ -56,15 +56,20 @@ import sonia.scm.web.cgi.CGIExecutor;
|
||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||
import sonia.scm.web.cgi.EnvList;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -74,6 +79,8 @@ import java.util.Enumeration;
|
||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
{
|
||||
|
||||
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||
|
||||
@@ -83,6 +90,8 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
/** Field description */
|
||||
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||
|
||||
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||
|
||||
/** Field description */
|
||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
||||
|
||||
@@ -268,11 +277,22 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||
directory.getAbsolutePath());
|
||||
|
||||
// add hook environment
|
||||
Map<String, String> environment = executor.getEnvironment().asMutableMap();
|
||||
if (handler.getConfig().isDisableHookSSLValidation()) {
|
||||
// disable ssl validation
|
||||
// Issue 959: https://goo.gl/zH5eY8
|
||||
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
||||
}
|
||||
|
||||
// enable experimental httppostargs protocol of mercurial
|
||||
// Issue 970: https://goo.gl/poascp
|
||||
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
||||
|
||||
//J-
|
||||
HgEnvironment.prepareEnvironment(
|
||||
executor.getEnvironment().asMutableMap(),
|
||||
environment,
|
||||
handler,
|
||||
hookManager,
|
||||
hookManager,
|
||||
request
|
||||
);
|
||||
//J+
|
||||
|
||||
@@ -33,13 +33,23 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||
import sonia.scm.web.filter.PermissionFilter;
|
||||
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Permission filter for mercurial repositories.
|
||||
@@ -51,14 +61,48 @@ public class HgPermissionFilter extends PermissionFilter
|
||||
|
||||
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
|
||||
|
||||
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate)
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
|
||||
public HgPermissionFilter(ScmConfiguration configuration, ScmProviderHttpServlet delegate, HgRepositoryHandler repositoryHandler)
|
||||
{
|
||||
super(configuration, delegate);
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void service(HttpServletRequest request, HttpServletResponse response, Repository repository) throws IOException, ServletException {
|
||||
super.service(wrapRequestIfRequired(request), response, repository);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
HttpServletRequest wrapRequestIfRequired(HttpServletRequest request) {
|
||||
if (isHttpPostArgsEnabled()) {
|
||||
return new HgServletRequest(request);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteRequest(HttpServletRequest request)
|
||||
{
|
||||
return !READ_METHODS.contains(request.getMethod());
|
||||
if (isHttpPostArgsEnabled()) {
|
||||
return isHttpPostArgsWriteRequest(request);
|
||||
}
|
||||
return isDefaultWriteRequest(request);
|
||||
}
|
||||
|
||||
private boolean isHttpPostArgsEnabled() {
|
||||
return repositoryHandler.getConfig().isEnableHttpPostArgs();
|
||||
}
|
||||
|
||||
private boolean isHttpPostArgsWriteRequest(HttpServletRequest request) {
|
||||
return WireProtocol.isWriteRequest(request);
|
||||
}
|
||||
|
||||
private boolean isDefaultWriteRequest(HttpServletRequest request) {
|
||||
if (READ_METHODS.contains(request.getMethod())) {
|
||||
return WireProtocol.isWriteRequest(request);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ import javax.inject.Inject;
|
||||
public class HgPermissionFilterFactory implements ScmProviderHttpServletDecoratorFactory {
|
||||
|
||||
private final ScmConfiguration configuration;
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
|
||||
@Inject
|
||||
public HgPermissionFilterFactory(ScmConfiguration configuration) {
|
||||
public HgPermissionFilterFactory(ScmConfiguration configuration, HgRepositoryHandler repositoryHandler) {
|
||||
this.configuration = configuration;
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -25,6 +27,6 @@ public class HgPermissionFilterFactory implements ScmProviderHttpServletDecorato
|
||||
|
||||
@Override
|
||||
public ScmProviderHttpServlet createDecorator(ScmProviderHttpServlet delegate) {
|
||||
return new HgPermissionFilter(configuration, delegate);
|
||||
return new HgPermissionFilter(configuration, delegate,repositoryHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* HgServletInputStream is a wrapper around the original {@link ServletInputStream} and provides some extra
|
||||
* functionality to support the mercurial client.
|
||||
*/
|
||||
public class HgServletInputStream extends ServletInputStream {
|
||||
|
||||
private final ServletInputStream original;
|
||||
private ByteArrayInputStream captured;
|
||||
|
||||
HgServletInputStream(ServletInputStream original) {
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given amount of bytes from the stream and captures them, if the {@link #read()} methods is called the
|
||||
* captured bytes are returned before the rest of the stream.
|
||||
*
|
||||
* @param size amount of bytes to read
|
||||
*
|
||||
* @return byte array
|
||||
*
|
||||
* @throws IOException if the method is called twice
|
||||
*/
|
||||
public byte[] readAndCapture(int size) throws IOException {
|
||||
Preconditions.checkState(captured == null, "readAndCapture can only be called once per request");
|
||||
|
||||
// TODO should we enforce a limit? to prevent OOM?
|
||||
byte[] bytes = new byte[size];
|
||||
original.read(bytes);
|
||||
captured = new ByteArrayInputStream(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (captured != null && captured.available() > 0) {
|
||||
return captured.read();
|
||||
}
|
||||
return original.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
original.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link HttpServletRequestWrapper} which adds some functionality in order to support the mercurial client.
|
||||
*/
|
||||
public final class HgServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
private HgServletInputStream hgServletInputStream;
|
||||
|
||||
/**
|
||||
* Constructs a request object wrapping the given request.
|
||||
*
|
||||
* @param request
|
||||
* @throws IllegalArgumentException if the request is null
|
||||
*/
|
||||
public HgServletRequest(HttpServletRequest request) {
|
||||
super(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HgServletInputStream getInputStream() throws IOException {
|
||||
if (hgServletInputStream == null) {
|
||||
hgServletInputStream = new HgServletInputStream(super.getInputStream());
|
||||
}
|
||||
return hgServletInputStream;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright (c) 2018, 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;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* WireProtocol provides methods for handling the mercurial wire protocol.
|
||||
*
|
||||
* @see <a href="https://goo.gl/WaVJzw">Mercurial Wire Protocol</a>
|
||||
*/
|
||||
public final class WireProtocol {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WireProtocol.class);
|
||||
|
||||
private static final Set<String> READ_COMMANDS = ImmutableSet.of(
|
||||
"batch", "between", "branchmap", "branches", "capabilities", "changegroup", "changegroupsubset", "clonebundles",
|
||||
"getbundle", "heads", "hello", "listkeys", "lookup", "known", "stream_out",
|
||||
// could not find lheads in the wireprotocol description but mercurial 4.5.2 uses it for clone
|
||||
"lheads"
|
||||
);
|
||||
|
||||
private static final Set<String> WRITE_COMMANDS = ImmutableSet.of(
|
||||
"pushkey", "unbundle"
|
||||
);
|
||||
|
||||
private WireProtocol() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the request is a write request. The method will always return {@code true}, expect for the
|
||||
* following cases:
|
||||
*
|
||||
* - no command was specified with the request (is required for the hgweb ui)
|
||||
* - the command in the query string was found in the list of read request
|
||||
* - if query string contains the batch command, then all commands specified in X-HgArg headers must be
|
||||
* in the list of read requests
|
||||
* - in case of enabled HttpPostArgs protocol and query string container the batch command, the header X-HgArgs-Post
|
||||
* is read and the commands which are specified in the body from 0 to the value of X-HgArgs-Post must be in the list
|
||||
* of read requests
|
||||
*
|
||||
* @param request http request
|
||||
*
|
||||
* @return {@code true} for write requests.
|
||||
*/
|
||||
public static boolean isWriteRequest(HttpServletRequest request) {
|
||||
List<String> commands = commandsOf(request);
|
||||
boolean write = isWriteRequest(commands);
|
||||
LOG.trace("mercurial request {} is write: {}", commands, write);
|
||||
return write;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static boolean isWriteRequest(List<String> commands) {
|
||||
return !READ_COMMANDS.containsAll(commands);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static List<String> commandsOf(HttpServletRequest request) {
|
||||
List<String> listOfCmds = Lists.newArrayList();
|
||||
|
||||
String cmd = getCommandFromQueryString(request);
|
||||
if (cmd != null) {
|
||||
listOfCmds.add(cmd);
|
||||
if (isBatchCommand(cmd)) {
|
||||
parseHgArgHeaders(request, listOfCmds);
|
||||
handleHttpPostArgs(request, listOfCmds);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(listOfCmds);
|
||||
}
|
||||
|
||||
private static void handleHttpPostArgs(HttpServletRequest request, List<String> listOfCmds) {
|
||||
int hgArgsPostSize = request.getIntHeader("X-HgArgs-Post");
|
||||
if (hgArgsPostSize > 0) {
|
||||
|
||||
if (request instanceof HgServletRequest) {
|
||||
HgServletRequest hgRequest = (HgServletRequest) request;
|
||||
|
||||
parseHttpPostArgs(listOfCmds, hgArgsPostSize, hgRequest);
|
||||
} else {
|
||||
throw new IllegalArgumentException("could not process the httppostargs protocol without HgServletRequest");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHttpPostArgs(List<String> listOfCmds, int hgArgsPostSize, HgServletRequest hgRequest) {
|
||||
try {
|
||||
byte[] bytes = hgRequest.getInputStream().readAndCapture(hgArgsPostSize);
|
||||
// we use iso-8859-1 for encoding, because the post args are normally http headers which are using iso-8859-1
|
||||
// see https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
String hgArgs = new String(bytes, Charsets.ISO_8859_1);
|
||||
String decoded = decodeValue(hgArgs);
|
||||
parseHgCommandHeader(listOfCmds, decoded);
|
||||
} catch (IOException ex) {
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgArgHeaders(HttpServletRequest request, List<String> listOfCmds) {
|
||||
Enumeration headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String header = (String) headerNames.nextElement();
|
||||
parseHgArgHeader(request, listOfCmds, header);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgArgHeader(HttpServletRequest request, List<String> listOfCmds, String header) {
|
||||
if (isHgArgHeader(header)) {
|
||||
String value = getHeaderDecoded(request, header);
|
||||
parseHgArgValue(listOfCmds, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgArgValue(List<String> listOfCmds, String value) {
|
||||
if (isHgArgCommandHeader(value)) {
|
||||
parseHgCommandHeader(listOfCmds, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgCommandHeader(List<String> listOfCmds, String value) {
|
||||
String[] cmds = value.substring(5).split(";");
|
||||
for (String cmd : cmds ) {
|
||||
String normalizedCmd = normalize(cmd);
|
||||
int index = normalizedCmd.indexOf(' ');
|
||||
if (index > 0) {
|
||||
listOfCmds.add(normalizedCmd.substring(0, index));
|
||||
} else {
|
||||
listOfCmds.add(normalizedCmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String normalize(String cmd) {
|
||||
return cmd.trim().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
private static boolean isHgArgCommandHeader(String value) {
|
||||
return value.startsWith("cmds=");
|
||||
}
|
||||
|
||||
private static String getHeaderDecoded(HttpServletRequest request, String header) {
|
||||
return decodeValue(request.getHeader(header));
|
||||
}
|
||||
|
||||
private static String decodeValue(String value) {
|
||||
return HttpUtil.decode(Strings.nullToEmpty(value));
|
||||
}
|
||||
|
||||
private static boolean isHgArgHeader(String header) {
|
||||
return header.toLowerCase(Locale.ENGLISH).startsWith("x-hgarg-");
|
||||
}
|
||||
|
||||
private static boolean isBatchCommand(String cmd) {
|
||||
return "batch".equalsIgnoreCase(cmd);
|
||||
}
|
||||
|
||||
private static String getCommandFromQueryString(HttpServletRequest request) {
|
||||
// we can't use getParameter, because this would inspect the body for form parameters as well
|
||||
Multimap<String, String> queryParameterMap = createQueryParameterMap(request);
|
||||
|
||||
Collection<String> cmd = queryParameterMap.get("cmd");
|
||||
Preconditions.checkArgument(cmd.size() <= 1, "found more than one cmd query parameter");
|
||||
Iterator<String> iterator = cmd.iterator();
|
||||
|
||||
String command = null;
|
||||
if (iterator.hasNext()) {
|
||||
command = iterator.next();
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
private static Multimap<String,String> createQueryParameterMap(HttpServletRequest request) {
|
||||
Multimap<String,String> parameterMap = HashMultimap.create();
|
||||
|
||||
String queryString = request.getQueryString();
|
||||
if (!Strings.isNullOrEmpty(queryString)) {
|
||||
|
||||
String[] parameters = queryString.split("&");
|
||||
for (String parameter : parameters) {
|
||||
int index = parameter.indexOf('=');
|
||||
if (index > 0) {
|
||||
parameterMap.put(parameter.substring(0, index), parameter.substring(index + 1));
|
||||
} else {
|
||||
parameterMap.put(parameter, "true");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return parameterMap;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,22 @@ from collections import defaultdict
|
||||
from mercurial import cmdutil,util
|
||||
|
||||
cmdtable = {}
|
||||
command = cmdutil.command(cmdtable)
|
||||
|
||||
try:
|
||||
from mercurial import registrar
|
||||
command = registrar.command(cmdtable)
|
||||
except (AttributeError, ImportError):
|
||||
# Fallback to hg < 4.3 support
|
||||
from mercurial import cmdutil
|
||||
command = cmdutil.command(cmdtable)
|
||||
|
||||
try:
|
||||
from mercurial.utils import dateutil
|
||||
_parsedate = dateutil.parsedate
|
||||
except ImportError:
|
||||
# compat with hg < 4.6
|
||||
from mercurial import util
|
||||
_parsedate = util.parsedate
|
||||
|
||||
FILE_MARKER = '<files>'
|
||||
|
||||
@@ -166,7 +181,7 @@ def collect_sub_repositories(revCtx):
|
||||
subrepos[parts[0].strip()] = subrepo
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
hgsubstate = revCtx.filectx('.hgsubstate').data().split('\n')
|
||||
for line in hgsubstate:
|
||||
@@ -201,7 +216,7 @@ class File_Printer:
|
||||
description = 'n/a'
|
||||
if not self.disableLastCommit:
|
||||
linkrev = self.repo[file.linkrev()]
|
||||
date = '%d %d' % util.parsedate(linkrev.date())
|
||||
date = '%d %d' % _parsedate(linkrev.date())
|
||||
description = linkrev.description()
|
||||
format = '%s %i %s %s\n'
|
||||
if self.transport:
|
||||
|
||||
@@ -36,7 +36,11 @@ from mercurial.hgweb import hgweb, wsgicgi
|
||||
|
||||
demandimport.enable()
|
||||
|
||||
u = uimod.ui()
|
||||
try:
|
||||
u = uimod.ui.load()
|
||||
except AttributeError:
|
||||
# For installations earlier than Mercurial 4.1
|
||||
u = uimod.ui()
|
||||
|
||||
u.setconfig('web', 'push_ssl', 'false')
|
||||
u.setconfig('web', 'allow_read', '*')
|
||||
@@ -45,7 +49,13 @@ u.setconfig('web', 'allow_push', '*')
|
||||
u.setconfig('hooks', 'changegroup.scm', 'python:scmhooks.callback')
|
||||
u.setconfig('hooks', 'pretxnchangegroup.scm', 'python:scmhooks.callback')
|
||||
|
||||
# pass SCM_HTTP_POST_ARGS to enable experimental httppostargs protocol of mercurial
|
||||
# SCM_HTTP_POST_ARGS is set by HgCGIServlet
|
||||
# Issue 970: https://goo.gl/poascp
|
||||
u.setconfig('experimental', 'httppostargs', os.environ['SCM_HTTP_POST_ARGS'])
|
||||
|
||||
# open repository
|
||||
# SCM_REPOSITORY_PATH contains the repository path and is set by HgCGIServlet
|
||||
r = hg.repository(u, os.environ['SCM_REPOSITORY_PATH'])
|
||||
application = hgweb(r)
|
||||
wsgicgi.launch(application)
|
||||
|
||||
@@ -47,7 +47,7 @@ def printMessages(ui, msgs):
|
||||
for line in msgs:
|
||||
if line.startswith("_e") or line.startswith("_n"):
|
||||
line = line[2:];
|
||||
ui.warn(line);
|
||||
ui.warn('%s\n' % line.rstrip())
|
||||
|
||||
def callHookUrl(ui, repo, hooktype, node):
|
||||
abort = True
|
||||
@@ -79,8 +79,10 @@ def callHookUrl(ui, repo, hooktype, node):
|
||||
printMessages(ui, msg.splitlines(True))
|
||||
else:
|
||||
ui.warn( "ERROR: scm-hook failed with an unknown error\n" )
|
||||
ui.traceback()
|
||||
except ValueError:
|
||||
ui.warn( "scm-hook failed with an exception\n" )
|
||||
ui.traceback()
|
||||
return abort
|
||||
|
||||
def callback(ui, repo, hooktype, node=None, source=None, pending=None, **kwargs):
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
header = "%{pattern}"
|
||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{join(extras,',')}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
||||
changeset = "{rev}:{node}{author}\n{date|hgdate}\n{branch}\n{parents}{extras}\n{tags}{file_adds}{file_mods}{file_dels}\n{desc}\0"
|
||||
tag = "t {tag}\n"
|
||||
file_add = "a {file_add}\n"
|
||||
file_mod = "m {file_mod}\n"
|
||||
file_del = "d {file_del}\n"
|
||||
extra = "{key}={value|stringescape},"
|
||||
footer = "%{pattern}"
|
||||
@@ -31,21 +31,32 @@
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import static org.mockito.Mockito.*;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.HgConfig;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.CMDS_HEADS_KNOWN_NODES;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.BOOKMARKS;
|
||||
import static sonia.scm.web.WireProtocolRequestMockFactory.Namespace.PHASES;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HgPermissionFilter}.
|
||||
*
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -56,13 +67,37 @@ public class HgPermissionFilterTest {
|
||||
|
||||
@Mock
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
|
||||
@Mock
|
||||
private RepositoryProvider repositoryProvider;
|
||||
|
||||
|
||||
@Mock
|
||||
private HgRepositoryHandler hgRepositoryHandler;
|
||||
|
||||
private WireProtocolRequestMockFactory wireProtocol = new WireProtocolRequestMockFactory("/scm/hg/repo");
|
||||
|
||||
@InjectMocks
|
||||
private HgPermissionFilter filter;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(hgRepositoryHandler.getConfig()).thenReturn(new HgConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#wrapRequestIfRequired(HttpServletRequest)}.
|
||||
*/
|
||||
@Test
|
||||
public void testWrapRequestIfRequired() {
|
||||
assertSame(request, filter.wrapRequestIfRequired(request));
|
||||
|
||||
HgConfig hgConfig = new HgConfig();
|
||||
hgConfig.setEnableHttpPostArgs(true);
|
||||
when(hgRepositoryHandler.getConfig()).thenReturn(hgConfig);
|
||||
|
||||
assertThat(filter.wrapRequestIfRequired(request), is(instanceOf(HgServletRequest.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)}.
|
||||
*/
|
||||
@@ -73,7 +108,7 @@ public class HgPermissionFilterTest {
|
||||
assertFalse(isWriteRequest("HEAD"));
|
||||
assertFalse(isWriteRequest("TRACE"));
|
||||
assertFalse(isWriteRequest("OPTIONS"));
|
||||
|
||||
|
||||
// write methods
|
||||
assertTrue(isWriteRequest("POST"));
|
||||
assertTrue(isWriteRequest("PUT"));
|
||||
@@ -81,8 +116,121 @@ public class HgPermissionFilterTest {
|
||||
assertTrue(isWriteRequest("KA"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with enabled httppostargs option.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithEnabledHttpPostArgs() {
|
||||
HgConfig config = new HgConfig();
|
||||
config.setEnableHttpPostArgs(true);
|
||||
when(hgRepositoryHandler.getConfig()).thenReturn(config);
|
||||
|
||||
assertFalse(isWriteRequest("POST"));
|
||||
assertFalse(isWriteRequest("POST", "heads"));
|
||||
assertTrue(isWriteRequest("POST", "unbundle"));
|
||||
}
|
||||
|
||||
private boolean isWriteRequest(String method) {
|
||||
return isWriteRequest(method, "capabilities");
|
||||
}
|
||||
|
||||
private boolean isWriteRequest(String method, String command) {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
when(request.getQueryString()).thenReturn("cmd=" + command);
|
||||
when(request.getMethod()).thenReturn(method);
|
||||
return filter.isWriteRequest(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||
* fresh clone of a repository.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithClone() {
|
||||
assertIsReadRequest(wireProtocol.capabilities());
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||
* push of a single changeset.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithSingleChangesetPush() {
|
||||
assertIsReadRequest(wireProtocol.capabilities());
|
||||
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2")));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsWriteRequest(wireProtocol.unbundle(261L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsWriteRequest(wireProtocol.pushkey("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2&namespace=phases&new=0&old=1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||
* push to a single changeset.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithMultipleChangesetsPush() {
|
||||
assertIsReadRequest(wireProtocol.capabilities());
|
||||
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsReadRequest(wireProtocol.branchmap());
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsWriteRequest(wireProtocol.unbundle(746L, "686173686564+95373ca7cd5371cb6c49bb755ee451d9ec585845"));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||
* push of multiple branches to a new repository.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithMutlipleBranchesToNewRepositoryPush() {
|
||||
assertIsReadRequest(wireProtocol.capabilities());
|
||||
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||
assertIsReadRequest(wireProtocol.known("c0ceccb3b2f0f5c977ff32b9337519e5f37942c2+187ddf37e237c370514487a0bb1a226f11a780b3+b5914611f84eae14543684b2721eec88b0edac12+8b63a323606f10c86b30465570c2574eb7a3a989"));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsWriteRequest(wireProtocol.unbundle(913L, "686173686564+6768033e216468247bd031a0a2d9876d79818f8f"));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsWriteRequest(wireProtocol.pushkey("ef5993bb4abb32a0565c347844c6d939fc4f4b98&namespace=phases&new=0&old=1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a set of requests, which are used for a
|
||||
* push of a bookmark.
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithBookmarkPush() {
|
||||
assertIsReadRequest(wireProtocol.capabilities());
|
||||
assertIsReadRequest(wireProtocol.batch(CMDS_HEADS_KNOWN_NODES.concat("ef5993bb4abb32a0565c347844c6d939fc4f4b98")));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsReadRequest(wireProtocol.listkeys(BOOKMARKS));
|
||||
assertIsReadRequest(wireProtocol.listkeys(PHASES));
|
||||
assertIsWriteRequest(wireProtocol.pushkey("markone&namespace=bookmarks&new=ef5993bb4abb32a0565c347844c6d939fc4f4b98&old="));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link HgPermissionFilter#isWriteRequest(HttpServletRequest)} with a write request hidden in a batch GET
|
||||
* request.
|
||||
*
|
||||
* @see <a href="https://goo.gl/poascp">Issue #970</a>
|
||||
*/
|
||||
@Test
|
||||
public void testIsWriteRequestWithBookmarkPushInABatch() {
|
||||
assertIsWriteRequest(wireProtocol.batch("pushkey key=markthree,namespace=bookmarks,new=187ddf37e237c370514487a0bb1a226f11a780b3,old="));
|
||||
}
|
||||
|
||||
private void assertIsReadRequest(HttpServletRequest request) {
|
||||
assertFalse(filter.isWriteRequest(request));
|
||||
}
|
||||
|
||||
private void assertIsWriteRequest(HttpServletRequest request) {
|
||||
assertTrue(filter.isWriteRequest(request));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class HgServletInputStreamTest {
|
||||
|
||||
@Test
|
||||
public void testReadAndCapture() throws IOException {
|
||||
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
|
||||
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
|
||||
|
||||
byte[] prefix = hgServletInputStream.readAndCapture(8);
|
||||
assertEquals("trillian", new String(prefix, Charsets.US_ASCII));
|
||||
|
||||
byte[] wholeBytes = ByteStreams.toByteArray(hgServletInputStream);
|
||||
assertEquals("trillian.mcmillian@hitchhiker.com", new String(wholeBytes, Charsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testReadAndCaptureCalledTwice() throws IOException {
|
||||
SampleServletInputStream original = new SampleServletInputStream("trillian.mcmillian@hitchhiker.com");
|
||||
HgServletInputStream hgServletInputStream = new HgServletInputStream(original);
|
||||
|
||||
hgServletInputStream.readAndCapture(1);
|
||||
hgServletInputStream.readAndCapture(1);
|
||||
}
|
||||
|
||||
private static class SampleServletInputStream extends ServletInputStream {
|
||||
|
||||
private ByteArrayInputStream input;
|
||||
|
||||
private SampleServletInputStream(String data) {
|
||||
input = new ByteArrayInputStream(data.getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return input.read();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package sonia.scm.web;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class WireProtocolRequestMockFactory {
|
||||
|
||||
public enum Namespace {
|
||||
PHASES, BOOKMARKS;
|
||||
}
|
||||
|
||||
public static final String CMDS_HEADS_KNOWN_NODES = "heads+%3Bknown+nodes%3D";
|
||||
|
||||
private String repositoryPath;
|
||||
|
||||
public WireProtocolRequestMockFactory(String repositoryPath) {
|
||||
this.repositoryPath = repositoryPath;
|
||||
}
|
||||
|
||||
public HttpServletRequest capabilities() {
|
||||
return base("GET", "cmd=capabilities");
|
||||
}
|
||||
|
||||
public HttpServletRequest listkeys(Namespace namespace) {
|
||||
HttpServletRequest request = base("GET", "cmd=capabilities");
|
||||
header(request, "vary", "X-HgArg-1");
|
||||
header(request, "x-hgarg-1", namespaceValue(namespace));
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpServletRequest branchmap() {
|
||||
return base("GET", "cmd=branchmap");
|
||||
}
|
||||
|
||||
public HttpServletRequest batch(String... args) {
|
||||
HttpServletRequest request = base("GET", "cmd=batch");
|
||||
args(request, "cmds", args);
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpServletRequest unbundle(long contentLength, String... heads) {
|
||||
HttpServletRequest request = base("POST", "cmd=unbundle");
|
||||
header(request, "Content-Length", String.valueOf(contentLength));
|
||||
args(request, "heads", heads);
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpServletRequest pushkey(String... keys) {
|
||||
HttpServletRequest request = base("POST", "cmd=pushkey");
|
||||
args(request, "key", keys);
|
||||
return request;
|
||||
}
|
||||
|
||||
public HttpServletRequest known(String... nodes) {
|
||||
HttpServletRequest request = base("GET", "cmd=known");
|
||||
args(request, "nodes", nodes);
|
||||
return request;
|
||||
}
|
||||
|
||||
private void args(HttpServletRequest request, String prefix, String[] values) {
|
||||
List<String> headers = Lists.newArrayList();
|
||||
|
||||
StringBuilder vary = new StringBuilder();
|
||||
for ( int i=0; i<values.length; i++ ) {
|
||||
String header = "X-HgArg-" + (i+1);
|
||||
|
||||
if (i>0) {
|
||||
vary.append(",");
|
||||
}
|
||||
|
||||
vary.append(header);
|
||||
headers.add(header);
|
||||
|
||||
header(request, header, prefix + "=" + values[i]);
|
||||
}
|
||||
header(request, "Vary", vary.toString());
|
||||
|
||||
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
|
||||
}
|
||||
|
||||
private HttpServletRequest base(String method, String queryStringValue) {
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
|
||||
when(request.getRequestURI()).thenReturn(repositoryPath);
|
||||
when(request.getMethod()).thenReturn(method);
|
||||
|
||||
queryString(request, queryStringValue);
|
||||
|
||||
header(request, "Accept", "application/mercurial-0.1");
|
||||
header(request, "Accept-Encoding", "identity");
|
||||
header(request, "User-Agent", "mercurial/proto-1.0 (Mercurial 4.3.1)");
|
||||
return request;
|
||||
}
|
||||
|
||||
private void queryString(HttpServletRequest request, String queryString) {
|
||||
when(request.getQueryString()).thenReturn(queryString);
|
||||
}
|
||||
|
||||
private void header(HttpServletRequest request, String header, String value) {
|
||||
when(request.getHeader(header)).thenReturn(value);
|
||||
}
|
||||
|
||||
private String namespaceValue(Namespace namespace) {
|
||||
return "namespace=" + namespace.toString().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright (c) 2018, 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;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WireProtocol}.
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class WireProtocolTest {
|
||||
|
||||
@Mock
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Test
|
||||
public void testIsWriteRequestOnPost() {
|
||||
assertIsWriteRequest("capabilities", "unbundle");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsWriteRequest() {
|
||||
assertIsWriteRequest("unbundle");
|
||||
assertIsWriteRequest("capabilities", "unbundle");
|
||||
assertIsWriteRequest("capabilities", "postkeys");
|
||||
assertIsReadRequest();
|
||||
assertIsReadRequest("capabilities");
|
||||
assertIsReadRequest("capabilities", "branches", "branchmap");
|
||||
}
|
||||
|
||||
private void assertIsWriteRequest(String... commands) {
|
||||
List<String> cmdList = Lists.newArrayList(commands);
|
||||
assertTrue(WireProtocol.isWriteRequest(cmdList));
|
||||
}
|
||||
|
||||
private void assertIsReadRequest(String... commands) {
|
||||
List<String> cmdList = Lists.newArrayList(commands);
|
||||
assertFalse(WireProtocol.isWriteRequest(cmdList));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOf() {
|
||||
expectQueryCommand("capabilities", "cmd=capabilities");
|
||||
expectQueryCommand("unbundle", "cmd=unbundle");
|
||||
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle");
|
||||
expectQueryCommand("unbundle", "cmd=unbundle&suffix=stuff");
|
||||
expectQueryCommand("unbundle", "prefix=stuff&cmd=unbundle&suffix=stuff");
|
||||
expectQueryCommand("unbundle", "bool=&cmd=unbundle");
|
||||
expectQueryCommand("unbundle", "bool&cmd=unbundle");
|
||||
expectQueryCommand("unbundle", "prefix=stu==ff&cmd=unbundle");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithHgArgsPost() throws IOException {
|
||||
when(request.getMethod()).thenReturn("POST");
|
||||
when(request.getQueryString()).thenReturn("cmd=batch");
|
||||
when(request.getIntHeader("X-HgArgs-Post")).thenReturn(29);
|
||||
when(request.getHeaderNames()).thenReturn(Collections.enumeration(Lists.newArrayList("X-HgArgs-Post")));
|
||||
when(request.getInputStream()).thenReturn(new BufferedServletInputStream("cmds=lheads+%3Bknown+nodes%3D"));
|
||||
|
||||
List<String> commands = WireProtocol.commandsOf(new HgServletRequest(request));
|
||||
assertThat(commands, contains("batch", "lheads", "known"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithBatch() {
|
||||
prepareBatch("cmds=heads ;known nodes,ef5993bb4abb32a0565c347844c6d939fc4f4b98");
|
||||
List<String> commands = WireProtocol.commandsOf(request);
|
||||
assertThat(commands, contains("batch", "heads", "known"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithBatchEncoded() {
|
||||
prepareBatch("cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98");
|
||||
List<String> commands = WireProtocol.commandsOf(request);
|
||||
assertThat(commands, contains("batch", "heads", "known"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithBatchAndMutlipleLines() {
|
||||
prepareBatch(
|
||||
"cmds=heads+%3Bknown+nodes%3Def5993bb4abb32a0565c347844c6d939fc4f4b98",
|
||||
"cmds=unbundle; postkeys",
|
||||
"cmds= branchmap p1=r2,p2=r4; listkeys"
|
||||
);
|
||||
List<String> commands = WireProtocol.commandsOf(request);
|
||||
assertThat(commands, contains("batch", "heads", "known", "unbundle", "postkeys", "branchmap", "listkeys"));
|
||||
}
|
||||
|
||||
private void prepareBatch(String... args) {
|
||||
when(request.getQueryString()).thenReturn("cmd=batch");
|
||||
List<String> headers = Lists.newArrayList();
|
||||
for (int i=0; i<args.length; i++) {
|
||||
String header = "X-HgArg-" + (i+1);
|
||||
headers.add(header);
|
||||
when(request.getHeader(header)).thenReturn(args[i]);
|
||||
}
|
||||
when(request.getHeaderNames()).thenReturn(Collections.enumeration(headers));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetCommandsOfWithMultipleCommandsInQueryString() {
|
||||
when(request.getQueryString()).thenReturn("cmd=abc&cmd=def");
|
||||
WireProtocol.commandsOf(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithoutCmdInQueryString() {
|
||||
when(request.getQueryString()).thenReturn("abc=def&123=456");
|
||||
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithEmptyQueryString() {
|
||||
when(request.getQueryString()).thenReturn("");
|
||||
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCommandsOfWithNullQueryString() {
|
||||
assertTrue(WireProtocol.commandsOf(request).isEmpty());
|
||||
}
|
||||
|
||||
private void expectQueryCommand(String expected, String queryString) {
|
||||
when(request.getQueryString()).thenReturn(queryString);
|
||||
List<String> commands = WireProtocol.commandsOf(request);
|
||||
assertEquals(1, commands.size());
|
||||
assertTrue(commands.contains(expected));
|
||||
}
|
||||
|
||||
private static class BufferedServletInputStream extends ServletInputStream {
|
||||
|
||||
private ByteArrayInputStream input;
|
||||
|
||||
BufferedServletInputStream(String content) {
|
||||
this.input = new ByteArrayInputStream(content.getBytes(Charsets.US_ASCII));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return input.read();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user