mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
#970 initial support of mercurials httppostargs protocol
This commit is contained in:
@@ -38,16 +38,17 @@ package sonia.scm.web;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.HgRepositoryHandler;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.web.filter.ProviderPermissionFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Permission filter for mercurial repositories.
|
||||
@@ -60,6 +61,8 @@ public class HgPermissionFilter extends ProviderPermissionFilter
|
||||
|
||||
private static final Set<String> READ_METHODS = ImmutableSet.of("GET", "HEAD", "OPTIONS", "TRACE");
|
||||
|
||||
private final HgRepositoryHandler repositoryHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
@@ -67,17 +70,36 @@ public class HgPermissionFilter extends ProviderPermissionFilter
|
||||
* @param repositoryProvider repository provider
|
||||
*/
|
||||
@Inject
|
||||
public HgPermissionFilter(ScmConfiguration configuration,
|
||||
RepositoryProvider repositoryProvider)
|
||||
public HgPermissionFilter(ScmConfiguration configuration, RepositoryProvider repositoryProvider, HgRepositoryHandler repositoryHandler)
|
||||
{
|
||||
super(configuration, repositoryProvider);
|
||||
this.repositoryHandler = repositoryHandler;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HgServletRequest hgRequest = new HgServletRequest(request);
|
||||
super.doFilter(hgRequest, response, chain);
|
||||
// TODO closing stream in case of fire?
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isWriteRequest(HttpServletRequest request)
|
||||
{
|
||||
if (repositoryHandler.getConfig().isEnableHttpPostArgs()) {
|
||||
return isHttpPostArgsWriteRequest(request);
|
||||
}
|
||||
return isDefaultWriteRequest(request);
|
||||
}
|
||||
|
||||
private boolean isHttpPostArgsWriteRequest(HttpServletRequest request) {
|
||||
return WireProtocol.isWriteRequest(request);
|
||||
}
|
||||
|
||||
private boolean isDefaultWriteRequest(HttpServletRequest request) {
|
||||
if (READ_METHODS.contains(request.getMethod())) {
|
||||
return WireProtocol.isWriteRequest(request);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,17 @@
|
||||
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.*;
|
||||
|
||||
/**
|
||||
@@ -73,7 +76,10 @@ public final class WireProtocol {
|
||||
* - 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 request
|
||||
* 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
|
||||
*
|
||||
@@ -94,16 +100,40 @@ public final class WireProtocol {
|
||||
@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;
|
||||
|
||||
try {
|
||||
byte[] bytes = hgRequest.getInputStream().readAndCapture(hgArgsPostSize);
|
||||
String hgArgs = new String(bytes, Charsets.US_ASCII);
|
||||
String decoded = decodeValue(hgArgs);
|
||||
parseHgCommandHeader(listOfCmds, decoded);
|
||||
} catch (IOException ex) {
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("could not process the httppostargs protocol without HgServletRequest");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgArgHeaders(HttpServletRequest request, List<String> listOfCmds) {
|
||||
Enumeration headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
@@ -115,9 +145,13 @@ public final class WireProtocol {
|
||||
private static void parseHgArgHeader(HttpServletRequest request, List<String> listOfCmds, String header) {
|
||||
if (isHgArgHeader(header)) {
|
||||
String value = getHeaderDecoded(request, header);
|
||||
if (isHgArgCommandHeader(value)) {
|
||||
parseHgCommandHeader(listOfCmds, value);
|
||||
}
|
||||
parseHgArgValue(listOfCmds, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseHgArgValue(List<String> listOfCmds, String value) {
|
||||
if (isHgArgCommandHeader(value)) {
|
||||
parseHgCommandHeader(listOfCmds, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +177,11 @@ public final class WireProtocol {
|
||||
}
|
||||
|
||||
private static String getHeaderDecoded(HttpServletRequest request, String header) {
|
||||
return HttpUtil.decode(Strings.nullToEmpty(request.getHeader(header)));
|
||||
return decodeValue(request.getHeader(header));
|
||||
}
|
||||
|
||||
private static String decodeValue(String value) {
|
||||
return HttpUtil.decode(Strings.nullToEmpty(value));
|
||||
}
|
||||
|
||||
private static boolean isHgArgHeader(String header) {
|
||||
|
||||
Reference in New Issue
Block a user