summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.http.server/src
diff options
context:
space:
mode:
authorShawn O. Pearce <spearce@spearce.org>2010-01-06 12:26:54 -0800
committerShawn O. Pearce <spearce@spearce.org>2010-01-12 12:01:24 -0800
commit5e33a1de831fcbac4ff53cadacfdfc8e7b204ffe (patch)
tree264a978001fbae0913eb0cd11da7cc71c2daec11 /org.eclipse.jgit.http.server/src
parent71b34847299f0c8f6923fe37fdd509f57fd35830 (diff)
downloadjgit-5e33a1de831fcbac4ff53cadacfdfc8e7b204ffe.tar.gz
jgit-5e33a1de831fcbac4ff53cadacfdfc8e7b204ffe.zip
Simple dumb HTTP server for Git
This is a simple HTTP server that provides the minimum server side support required for dumb (non-git aware) transport clients. We produce the info/refs and objects/info/packs file on the fly from the local repository state, but otherwise serve data as raw files from the on-disk structure. In the future we could better optimize the FileSender class and the servlets that use it to take advantage of direct file to network APIs in more advanced servlet containers like Jetty. Our glue package borrows the idea of a micro embedded DSL from Google Guice and uses it to configure a collection of Filters and HttpServlets, all of which are matched against requests using regular expressions. If a subgroup exists in the pattern, it is extracted and used for the path info component of the request. Change-Id: Ia0f1a425d07d035e344ae54faf8aeb04763e7487 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Diffstat (limited to 'org.eclipse.jgit.http.server/src')
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java93
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java225
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java236
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java81
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java106
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java88
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java82
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java175
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java150
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java216
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java85
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java74
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java210
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java97
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java158
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java63
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java92
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java108
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java264
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java87
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java126
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java164
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/RepositoryResolver.java77
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotAuthorizedException.java54
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotEnabledException.java54
25 files changed, 3165 insertions, 0 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java
new file mode 100644
index 0000000000..810f694202
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/AsIsFileFilter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.http.server.resolver.AsIsFileService;
+import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.lib.Repository;
+
+class AsIsFileFilter implements Filter {
+ private final AsIsFileService asIs;
+
+ AsIsFileFilter(final AsIsFileService getAnyFile) {
+ this.asIs = getAnyFile;
+ }
+
+ public void init(FilterConfig config) throws ServletException {
+ // Do nothing.
+ }
+
+ public void destroy() {
+ // Do nothing.
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ try {
+ final Repository db = getRepository(request);
+ asIs.access((HttpServletRequest) request, db);
+ chain.doFilter(request, response);
+ } catch (ServiceNotAuthorizedException e) {
+ ((HttpServletResponse) response).sendError(SC_UNAUTHORIZED);
+ } catch (ServiceNotEnabledException e) {
+ ((HttpServletResponse) response).sendError(SC_FORBIDDEN);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
new file mode 100644
index 0000000000..280450d01a
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/FileSender.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_PARTIAL_CONTENT;
+import static javax.servlet.http.HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_RANGES;
+import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
+import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_RANGE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_IF_RANGE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_RANGE;
+
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.util.IO;
+
+/**
+ * Dumps a file over HTTP GET (or its information via HEAD).
+ * <p>
+ * Supports a single byte range requested via {@code Range} HTTP header. This
+ * feature supports a dumb client to resume download of a larger object file.
+ */
+final class FileSender {
+ private final File path;
+
+ private final RandomAccessFile source;
+
+ private final long lastModified;
+
+ private final long fileLen;
+
+ private long pos;
+
+ private long end;
+
+ FileSender(final File path) throws FileNotFoundException {
+ this.path = path;
+ this.source = new RandomAccessFile(path, "r");
+
+ try {
+ this.lastModified = path.lastModified();
+ this.fileLen = source.getChannel().size();
+ this.end = fileLen;
+ } catch (IOException e) {
+ try {
+ source.close();
+ } catch (IOException closeError) {
+ // Ignore any error closing the stream.
+ }
+
+ final FileNotFoundException r;
+ r = new FileNotFoundException("Cannot get length of " + path);
+ r.initCause(e);
+ throw r;
+ }
+ }
+
+ void close() {
+ try {
+ source.close();
+ } catch (IOException e) {
+ // Ignore close errors on a read-only stream.
+ }
+ }
+
+ long getLastModified() {
+ return lastModified;
+ }
+
+ String getTailChecksum() throws IOException {
+ final int n = 20;
+ final byte[] buf = new byte[n];
+ IO.readFully(source.getChannel(), fileLen - n, buf, 0, n);
+ return ObjectId.fromRaw(buf).getName();
+ }
+
+ void serve(final HttpServletRequest req, final HttpServletResponse rsp,
+ final boolean sendBody) throws IOException {
+ if (!initRangeRequest(req, rsp)) {
+ rsp.sendError(SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+
+ rsp.setHeader(HDR_ACCEPT_RANGES, "bytes");
+ rsp.setHeader(HDR_CONTENT_LENGTH, Long.toString(end - pos));
+
+ if (sendBody) {
+ final OutputStream out = rsp.getOutputStream();
+ try {
+ final byte[] buf = new byte[4096];
+ while (pos < end) {
+ final int r = (int) Math.min(buf.length, end - pos);
+ final int n = source.read(buf, 0, r);
+ if (n < 0) {
+ throw new EOFException("Unexpected EOF on " + path);
+ }
+ out.write(buf, 0, n);
+ pos += n;
+ }
+ out.flush();
+ } finally {
+ out.close();
+ }
+ }
+ }
+
+ private boolean initRangeRequest(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ final Enumeration<String> rangeHeaders = getRange(req);
+ if (!rangeHeaders.hasMoreElements()) {
+ // No range headers, the request is fine.
+ return true;
+ }
+
+ final String range = rangeHeaders.nextElement();
+ if (rangeHeaders.hasMoreElements()) {
+ // To simplify the code we support only one range.
+ return false;
+ }
+
+ final int eq = range.indexOf('=');
+ final int dash = range.indexOf('-');
+ if (eq < 0 || dash < 0 || !range.startsWith("bytes=")) {
+ return false;
+ }
+
+ final String ifRange = req.getHeader(HDR_IF_RANGE);
+ if (ifRange != null && !getTailChecksum().equals(ifRange)) {
+ // If the client asked us to verify the ETag and its not
+ // what they expected we need to send the entire content.
+ return true;
+ }
+
+ try {
+ if (eq + 1 == dash) {
+ // "bytes=-500" means last 500 bytes
+ pos = Long.parseLong(range.substring(dash + 1));
+ pos = fileLen - pos;
+ } else {
+ // "bytes=500-" (position 500 to end)
+ // "bytes=500-1000" (position 500 to 1000)
+ pos = Long.parseLong(range.substring(eq + 1, dash));
+ if (dash < range.length() - 1) {
+ end = Long.parseLong(range.substring(dash + 1));
+ end++; // range was inclusive, want exclusive
+ }
+ }
+ } catch (NumberFormatException e) {
+ // We probably hit here because of a non-digit such as
+ // "," appearing at the end of the first range telling
+ // us there is a second range following. To simplify
+ // the code we support only one range.
+ return false;
+ }
+
+ if (end > fileLen) {
+ end = fileLen;
+ }
+ if (pos >= end) {
+ return false;
+ }
+
+ rsp.setStatus(SC_PARTIAL_CONTENT);
+ rsp.setHeader(HDR_CONTENT_RANGE, "bytes " + pos + "-" + (end - 1) + "/"
+ + fileLen);
+ source.seek(pos);
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Enumeration<String> getRange(final HttpServletRequest req) {
+ return req.getHeaders(HDR_RANGE);
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
new file mode 100644
index 0000000000..1ce2776e4d
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/GitServlet.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import java.io.File;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+import org.eclipse.jgit.http.server.glue.MetaServlet;
+import org.eclipse.jgit.http.server.glue.RegexGroupFilter;
+import org.eclipse.jgit.http.server.glue.ServletBinder;
+import org.eclipse.jgit.http.server.resolver.FileResolver;
+import org.eclipse.jgit.http.server.resolver.AsIsFileService;
+import org.eclipse.jgit.http.server.resolver.RepositoryResolver;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * Handles Git repository access over HTTP.
+ * <p>
+ * Applications embedding this servlet should map a directory path within the
+ * application to this servlet, for example:
+ *
+ * <pre>
+ * &lt;servlet&gt;
+ * &lt;servlet-name&gt;GitServlet&lt;/servlet-name&gt;
+ * &lt;servlet-class&gt;org.eclipse.jgit.http.server.GitServlet&lt;/servlet-class&gt;
+ * &lt;init-param&gt;
+ * &lt;param-name&gt;base-path&lt;/param-name&gt;
+ * &lt;param-value&gt;/var/srv/git&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * &lt;init-param&gt;
+ * &lt;param-name&gt;export-all&lt;/param-name&gt;
+ * &lt;param-value&gt;0&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * &lt;/servlet&gt;
+ * &lt;servlet-mapping&gt;
+ * &lt;servlet-name&gt;GitServlet&lt;/servlet-name&gt;
+ * &lt;url-pattern&gt;/git/*&lt;/url-pattern&gt;
+ * &lt;/servlet-mapping&gt;
+ * </pre>
+ *
+ * <p>
+ * Applications may wish to add additional repository action URLs to this
+ * servlet by taking advantage of its extension from {@link MetaServlet}.
+ * Callers may register their own URL suffix translations through
+ * {@link #serve(String)}, or their regex translations through
+ * {@link #serveRegex(String)}. Each translation should contain a complete
+ * filter pipeline which ends with the HttpServlet that should handle the
+ * requested action.
+ */
+public class GitServlet extends MetaServlet {
+ private static final long serialVersionUID = 1L;
+
+ private volatile boolean initialized;
+
+ private RepositoryResolver resolver;
+
+ private AsIsFileService asIs = new AsIsFileService();
+
+ /**
+ * New servlet that will load its base directory from {@code web.xml}.
+ * <p>
+ * The required parameter {@code base-path} must be configured to point to
+ * the local filesystem directory where all served Git repositories reside.
+ */
+ public GitServlet() {
+ // Initialized above by field declarations.
+ }
+
+ /**
+ * New servlet configured with a specific resolver.
+ *
+ * @param resolver
+ * the resolver to use when matching URL to Git repository. If
+ * null the {@code base-path} parameter will be looked for in the
+ * parameter table during init, which usually comes from the
+ * {@code web.xml} file of the web application.
+ */
+ public void setRepositoryResolver(RepositoryResolver resolver) {
+ assertNotInitialized();
+ this.resolver = resolver;
+ }
+
+ /**
+ * @param f
+ * the filter to validate direct access to repository files
+ * through a dumb client. If {@code null} then dumb client
+ * support is completely disabled.
+ */
+ public void setAsIsFileService(AsIsFileService f) {
+ assertNotInitialized();
+ this.asIs = f != null ? f : AsIsFileService.DISABLED;
+ }
+
+ private void assertNotInitialized() {
+ if (initialized)
+ throw new IllegalStateException("Already initialized by container");
+ }
+
+ @Override
+ public void init(final ServletConfig config) throws ServletException {
+ super.init(config);
+
+ if (resolver == null) {
+ final File root = getFile("base-path");
+ final boolean exportAll = getBoolean("export-all");
+ setRepositoryResolver(new FileResolver(root, exportAll));
+ }
+
+ initialized = true;
+
+ if (asIs != AsIsFileService.DISABLED) {
+ final IsLocalFilter mustBeLocal = new IsLocalFilter();
+ final AsIsFileFilter enabled = new AsIsFileFilter(asIs);
+
+ serve("*/" + Constants.INFO_REFS)//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .with(new InfoRefsServlet());
+
+ serve("*/" + Constants.HEAD)//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .with(new TextFileServlet(Constants.HEAD));
+
+ final String info_alternates = "objects/info/alternates";
+ serve("*/" + info_alternates)//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .with(new TextFileServlet(info_alternates));
+
+ final String http_alternates = "objects/info/http-alternates";
+ serve("*/" + http_alternates)//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .with(new TextFileServlet(http_alternates));
+
+ serve("*/objects/info/packs")//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .with(new InfoPacksServlet());
+
+ serveRegex("^/(.*)/objects/([0-9a-f]{2}/[0-9a-f]{38})$")//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .through(new RegexGroupFilter(2))//
+ .with(new ObjectFileServlet.Loose());
+
+ serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.pack)$")//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .through(new RegexGroupFilter(2))//
+ .with(new ObjectFileServlet.Pack());
+
+ serveRegex("^/(.*)/objects/(pack/pack-[0-9a-f]{40}\\.idx)$")//
+ .through(mustBeLocal)//
+ .through(enabled)//
+ .through(new RegexGroupFilter(2))//
+ .with(new ObjectFileServlet.PackIdx());
+ }
+ }
+
+ private File getFile(final String param) throws ServletException {
+ String n = getInitParameter(param);
+ if (n == null || "".equals(n))
+ throw new ServletException("Parameter " + param + " not set");
+
+ File path = new File(n);
+ if (!path.exists())
+ throw new ServletException(path + " (for " + param + ") not found");
+ return path;
+ }
+
+ private boolean getBoolean(String param) throws ServletException {
+ String n = getInitParameter(param);
+ if (n == null)
+ return false;
+ try {
+ return StringUtils.toBoolean(n);
+ } catch (IllegalArgumentException err) {
+ throw new ServletException("Invalid boolean " + param + " = " + n);
+ }
+ }
+
+ @Override
+ protected ServletBinder register(ServletBinder binder) {
+ if (resolver == null)
+ throw new IllegalStateException("No resolver available");
+ binder = binder.through(new NoCacheFilter());
+ binder = binder.through(new RepositoryFilter(resolver));
+ return binder;
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
new file mode 100644
index 0000000000..dff1e8252e
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoPacksServlet.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.http.server.ServletUtils.sendPlainText;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.ObjectDatabase;
+import org.eclipse.jgit.lib.ObjectDirectory;
+import org.eclipse.jgit.lib.PackFile;
+
+/** Sends the current list of pack files, sorted most recent first. */
+class InfoPacksServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ public void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ sendPlainText(packList(req), req, rsp);
+ }
+
+ private static String packList(final HttpServletRequest req) {
+ final StringBuilder out = new StringBuilder();
+ final ObjectDatabase db = getRepository(req).getObjectDatabase();
+ if (db instanceof ObjectDirectory) {
+ for (PackFile pack : ((ObjectDirectory) db).getPacks()) {
+ out.append("P ");
+ out.append(pack.getPackFile().getName());
+ out.append('\n');
+ }
+ }
+ out.append('\n');
+ return out.toString();
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
new file mode 100644
index 0000000000..7bad517170
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/InfoRefsServlet.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.http.server.ServletUtils.send;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefAdvertiser;
+import org.eclipse.jgit.util.HttpSupport;
+
+/** Send a complete list of current refs, including peeled values for tags. */
+class InfoRefsServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ public void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ // Assume a dumb client and send back the dumb client
+ // version of the info/refs file.
+ final byte[] raw = dumbHttp(req);
+ rsp.setContentType(HttpSupport.TEXT_PLAIN);
+ rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+ send(raw, req, rsp);
+ }
+
+ private byte[] dumbHttp(final HttpServletRequest req) throws IOException {
+ final Repository db = getRepository(req);
+ final RevWalk walk = new RevWalk(db);
+ final RevFlag ADVERTISED = walk.newFlag("ADVERTISED");
+ final StringBuilder out = new StringBuilder();
+ final RefAdvertiser adv = new RefAdvertiser() {
+ @Override
+ protected void writeOne(final CharSequence line) {
+ // Whoever decided that info/refs should use a different
+ // delimiter than the native git:// protocol shouldn't
+ // be allowed to design this sort of stuff. :-(
+ out.append(line.toString().replace(' ', '\t'));
+ }
+
+ @Override
+ protected void end() {
+ // No end marker required for info/refs format.
+ }
+ };
+ adv.init(walk, ADVERTISED);
+ adv.setDerefTags(true);
+
+ Map<String, Ref> refs = new HashMap<String, Ref>(db.getAllRefs());
+ refs.remove(Constants.HEAD);
+ adv.send(refs.values());
+ return out.toString().getBytes(Constants.CHARACTER_ENCODING);
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
new file mode 100644
index 0000000000..34edf82792
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/IsLocalFilter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.ObjectDirectory;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Requires the target {@link Repository} to be available via local filesystem.
+ * <p>
+ * The target {@link Repository} must be using a {@link ObjectDirectory}, so the
+ * downstream servlet can directly access its contents on disk.
+ */
+class IsLocalFilter implements Filter {
+ public void init(FilterConfig config) throws ServletException {
+ // Do nothing.
+ }
+
+ public void destroy() {
+ // Do nothing.
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (isLocal(getRepository(request)))
+ chain.doFilter(request, response);
+ else
+ ((HttpServletResponse) response).sendError(SC_FORBIDDEN);
+ }
+
+ private static boolean isLocal(final Repository db) {
+ return db.getObjectDatabase() instanceof ObjectDirectory;
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java
new file mode 100644
index 0000000000..6a23cb95f5
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/NoCacheFilter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static org.eclipse.jgit.util.HttpSupport.HDR_CACHE_CONTROL;
+import static org.eclipse.jgit.util.HttpSupport.HDR_EXPIRES;
+import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+/** Adds HTTP response headers to prevent caching by proxies/browsers. */
+class NoCacheFilter implements Filter {
+ public void init(FilterConfig config) throws ServletException {
+ // Do nothing.
+ }
+
+ public void destroy() {
+ // Do nothing.
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpServletResponse rsp = (HttpServletResponse) response;
+
+ rsp.setHeader(HDR_EXPIRES, "Fri, 01 Jan 1980 00:00:00 GMT");
+ rsp.setHeader(HDR_PRAGMA, "no-cache");
+
+ final String nocache = "no-cache, max-age=0, must-revalidate";
+ rsp.setHeader(HDR_CACHE_CONTROL, nocache);
+
+ chain.doFilter(request, response);
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
new file mode 100644
index 0000000000..5d774a8248
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ObjectFileServlet.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG;
+import static org.eclipse.jgit.util.HttpSupport.HDR_IF_MODIFIED_SINCE;
+import static org.eclipse.jgit.util.HttpSupport.HDR_IF_NONE_MATCH;
+import static org.eclipse.jgit.util.HttpSupport.HDR_LAST_MODIFIED;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.ObjectDirectory;
+import org.eclipse.jgit.lib.Repository;
+
+/** Sends any object from {@code GIT_DIR/objects/??/0 38}, or any pack file. */
+abstract class ObjectFileServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ static class Loose extends ObjectFileServlet {
+ private static final long serialVersionUID = 1L;
+
+ Loose() {
+ super("application/x-git-loose-object");
+ }
+
+ @Override
+ String etag(final FileSender sender) throws IOException {
+ return Long.toHexString(sender.getLastModified());
+ }
+ }
+
+ private static abstract class PackData extends ObjectFileServlet {
+ private static final long serialVersionUID = 1L;
+
+ PackData(String contentType) {
+ super(contentType);
+ }
+
+ @Override
+ String etag(final FileSender sender) throws IOException {
+ return sender.getTailChecksum();
+ }
+ }
+
+ static class Pack extends PackData {
+ private static final long serialVersionUID = 1L;
+
+ Pack() {
+ super("application/x-git-packed-objects");
+ }
+ }
+
+ static class PackIdx extends PackData {
+ private static final long serialVersionUID = 1L;
+
+ PackIdx() {
+ super("application/x-git-packed-objects-toc");
+ }
+ }
+
+ private final String contentType;
+
+ ObjectFileServlet(final String contentType) {
+ this.contentType = contentType;
+ }
+
+ abstract String etag(FileSender sender) throws IOException;
+
+ @Override
+ public void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ serve(req, rsp, true);
+ }
+
+ @Override
+ protected void doHead(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws ServletException, IOException {
+ serve(req, rsp, false);
+ }
+
+ private void serve(final HttpServletRequest req,
+ final HttpServletResponse rsp, final boolean sendBody)
+ throws IOException {
+ final File obj = new File(objects(req), req.getPathInfo());
+ final FileSender sender;
+ try {
+ sender = new FileSender(obj);
+ } catch (FileNotFoundException e) {
+ rsp.sendError(SC_NOT_FOUND);
+ return;
+ }
+
+ try {
+ final String etag = etag(sender);
+ final long lastModified = (sender.getLastModified() / 1000) * 1000;
+
+ String ifNoneMatch = req.getHeader(HDR_IF_NONE_MATCH);
+ if (etag != null && etag.equals(ifNoneMatch)) {
+ rsp.sendError(SC_NOT_MODIFIED);
+ return;
+ }
+
+ long ifModifiedSince = req.getDateHeader(HDR_IF_MODIFIED_SINCE);
+ if (0 < lastModified && lastModified < ifModifiedSince) {
+ rsp.sendError(SC_NOT_MODIFIED);
+ return;
+ }
+
+ if (etag != null)
+ rsp.setHeader(HDR_ETAG, etag);
+ if (0 < lastModified)
+ rsp.setDateHeader(HDR_LAST_MODIFIED, lastModified);
+ rsp.setContentType(contentType);
+ sender.serve(req, rsp, sendBody);
+ } finally {
+ sender.close();
+ }
+ }
+
+ private static File objects(final HttpServletRequest req) {
+ final Repository db = getRepository(req);
+ return ((ObjectDirectory) db.getObjectDatabase()).getDirectory();
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
new file mode 100644
index 0000000000..a212f0d7ba
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/RepositoryFilter.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
+import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_REPOSITORY;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.http.server.resolver.RepositoryResolver;
+import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.lib.Repository;
+
+/**
+ * Opens a repository named by the path info through {@link RepositoryResolver}.
+ * <p>
+ * This filter assumes it is invoked by {@link GitServlet} and is likely to not
+ * work as expected if called from any other class. This filter assumes the path
+ * info of the current request is a repository name which can be used by the
+ * configured {@link RepositoryResolver} to open a {@link Repository} and attach
+ * it to the current request.
+ * <p>
+ * This filter sets request attribute {@link ServletUtils#ATTRIBUTE_REPOSITORY}
+ * when it discovers the repository, and automatically closes and removes the
+ * attribute when the request is complete.
+ */
+public class RepositoryFilter implements Filter {
+ private final RepositoryResolver resolver;
+
+ private ServletContext context;
+
+ /**
+ * Create a new filter.
+ *
+ * @param resolver
+ * the resolver which will be used to translate the URL name
+ * component to the actual {@link Repository} instance for the
+ * current web request.
+ */
+ public RepositoryFilter(final RepositoryResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ public void init(final FilterConfig config) throws ServletException {
+ context = config.getServletContext();
+ }
+
+ public void destroy() {
+ context = null;
+ }
+
+ public void doFilter(final ServletRequest request,
+ final ServletResponse rsp, final FilterChain chain)
+ throws IOException, ServletException {
+ if (request.getAttribute(ATTRIBUTE_REPOSITORY) != null) {
+ context.log("Internal server error, request attribute "
+ + ATTRIBUTE_REPOSITORY + " was already set when "
+ + getClass().getName() + " was invoked.");
+ ((HttpServletResponse) rsp).sendError(SC_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ final HttpServletRequest req = (HttpServletRequest) request;
+
+ String name = req.getPathInfo();
+ if (name == null || name.length() == 0) {
+ ((HttpServletResponse) rsp).sendError(SC_NOT_FOUND);
+ return;
+ }
+ if (name.startsWith("/"))
+ name = name.substring(1);
+
+ final Repository db;
+ try {
+ db = resolver.open(req, name);
+ } catch (RepositoryNotFoundException e) {
+ ((HttpServletResponse) rsp).sendError(SC_NOT_FOUND);
+ return;
+ } catch (ServiceNotAuthorizedException e) {
+ ((HttpServletResponse) rsp).sendError(SC_UNAUTHORIZED);
+ return;
+ } catch (ServiceNotEnabledException e) {
+ ((HttpServletResponse) rsp).sendError(SC_FORBIDDEN);
+ return;
+ }
+ try {
+ request.setAttribute(ATTRIBUTE_REPOSITORY, db);
+ chain.doFilter(request, rsp);
+ } finally {
+ request.removeAttribute(ATTRIBUTE_REPOSITORY);
+ db.close();
+ }
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
new file mode 100644
index 0000000000..d6b039246f
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ServletUtils.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
+import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ETAG;
+import static org.eclipse.jgit.util.HttpSupport.TEXT_PLAIN;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+
+/** Common utility functions for servlets. */
+public final class ServletUtils {
+ /** Request attribute which stores the {@link Repository} instance. */
+ public static final String ATTRIBUTE_REPOSITORY = "org.eclipse.jgit.Repository";
+
+ /**
+ * Get the selected repository from the request.
+ *
+ * @param req
+ * the current request.
+ * @return the repository; never null.
+ * @throws IllegalStateException
+ * the repository was not set by the filter, the servlet is
+ * being invoked incorrectly and the programmer should ensure
+ * the filter runs before the servlet.
+ * @see #ATTRIBUTE_REPOSITORY
+ */
+ public static Repository getRepository(final ServletRequest req) {
+ Repository db = (Repository) req.getAttribute(ATTRIBUTE_REPOSITORY);
+ if (db == null)
+ throw new IllegalStateException("Expected Repository attribute");
+ return db;
+ }
+
+ /**
+ * Open the request input stream, automatically inflating if necessary.
+ * <p>
+ * This method automatically inflates the input stream if the request
+ * {@code Content-Encoding} header was set to {@code gzip} or the legacy
+ * {@code x-gzip}.
+ *
+ * @param req
+ * the incoming request whose input stream needs to be opened.
+ * @return an input stream to read the raw, uncompressed request body.
+ * @throws IOException
+ * if an input or output exception occurred.
+ */
+ public static InputStream getInputStream(final HttpServletRequest req)
+ throws IOException {
+ InputStream in = req.getInputStream();
+ final String enc = req.getHeader(HDR_CONTENT_ENCODING);
+ if (ENCODING_GZIP.equals(enc) || "x-gzip".equals(enc)) //$NON-NLS-1$
+ in = new GZIPInputStream(in);
+ else if (enc != null)
+ throw new IOException(HDR_CONTENT_ENCODING + " \"" + enc + "\""
+ + ": not supported by this library.");
+ return in;
+ }
+
+ /**
+ * Send a plain text response to a {@code GET} or {@code HEAD} HTTP request.
+ * <p>
+ * The text response is encoded in the Git character encoding, UTF-8.
+ * <p>
+ * If the user agent supports a compressed transfer encoding and the content
+ * is large enough, the content may be compressed before sending.
+ * <p>
+ * The {@code ETag} and {@code Content-Length} headers are automatically set
+ * by this method. {@code Content-Encoding} is conditionally set if the user
+ * agent supports a compressed transfer. Callers are responsible for setting
+ * any cache control headers.
+ *
+ * @param content
+ * to return to the user agent as this entity's body.
+ * @param req
+ * the incoming request.
+ * @param rsp
+ * the outgoing response.
+ * @throws IOException
+ * the servlet API rejected sending the body.
+ */
+ public static void sendPlainText(final String content,
+ final HttpServletRequest req, final HttpServletResponse rsp)
+ throws IOException {
+ final byte[] raw = content.getBytes(Constants.CHARACTER_ENCODING);
+ rsp.setContentType(TEXT_PLAIN);
+ rsp.setCharacterEncoding(Constants.CHARACTER_ENCODING);
+ send(raw, req, rsp);
+ }
+
+ /**
+ * Send a response to a {@code GET} or {@code HEAD} HTTP request.
+ * <p>
+ * If the user agent supports a compressed transfer encoding and the content
+ * is large enough, the content may be compressed before sending.
+ * <p>
+ * The {@code ETag} and {@code Content-Length} headers are automatically set
+ * by this method. {@code Content-Encoding} is conditionally set if the user
+ * agent supports a compressed transfer. Callers are responsible for setting
+ * {@code Content-Type} and any cache control headers.
+ *
+ * @param content
+ * to return to the user agent as this entity's body.
+ * @param req
+ * the incoming request.
+ * @param rsp
+ * the outgoing response.
+ * @throws IOException
+ * the servlet API rejected sending the body.
+ */
+ public static void send(byte[] content, final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ content = sendInit(content, req, rsp);
+ final OutputStream out = rsp.getOutputStream();
+ try {
+ out.write(content);
+ out.flush();
+ } finally {
+ out.close();
+ }
+ }
+
+ private static byte[] sendInit(byte[] content,
+ final HttpServletRequest req, final HttpServletResponse rsp)
+ throws IOException {
+ rsp.setHeader(HDR_ETAG, etag(content));
+ if (256 < content.length && acceptsGzipEncoding(req)) {
+ content = compress(content);
+ rsp.setHeader(HDR_CONTENT_ENCODING, ENCODING_GZIP);
+ }
+ rsp.setContentLength(content.length);
+ return content;
+ }
+
+ private static boolean acceptsGzipEncoding(final HttpServletRequest req) {
+ final String accepts = req.getHeader(HDR_ACCEPT_ENCODING);
+ return accepts != null && 0 <= accepts.indexOf(ENCODING_GZIP);
+ }
+
+ private static byte[] compress(final byte[] raw) throws IOException {
+ final int maxLen = raw.length + 32;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(maxLen);
+ final GZIPOutputStream gz = new GZIPOutputStream(out);
+ gz.write(raw);
+ gz.finish();
+ gz.flush();
+ return out.toByteArray();
+ }
+
+ private static String etag(final byte[] content) {
+ final MessageDigest md = Constants.newMessageDigest();
+ md.update(content);
+ return ObjectId.fromRaw(md.digest()).getName();
+ }
+
+ private ServletUtils() {
+ // static utility class only
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
new file mode 100644
index 0000000000..5bf5546cf7
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/TextFileServlet.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
+import static org.eclipse.jgit.http.server.ServletUtils.send;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.util.HttpSupport;
+import org.eclipse.jgit.util.IO;
+
+/** Sends a small text meta file from the repository. */
+class TextFileServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ private final String fileName;
+
+ TextFileServlet(final String name) {
+ this.fileName = name;
+ }
+
+ public void doGet(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws IOException {
+ try {
+ rsp.setContentType(HttpSupport.TEXT_PLAIN);
+ send(read(req), req, rsp);
+ } catch (FileNotFoundException noFile) {
+ rsp.sendError(SC_NOT_FOUND);
+ }
+ }
+
+ private byte[] read(final HttpServletRequest req) throws IOException {
+ final File gitdir = getRepository(req).getDirectory();
+ return IO.readFully(new File(gitdir, fileName));
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java
new file mode 100644
index 0000000000..c0a9e0e608
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ErrorServlet.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** Sends a fixed status code to the client. */
+public class ErrorServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ private final int status;
+
+ /**
+ * Sends a specific status code.
+ *
+ * @param status
+ * the HTTP status code to always send.
+ */
+ public ErrorServlet(final int status) {
+ this.status = status;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ rsp.sendError(status);
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
new file mode 100644
index 0000000000..d289743ba4
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/MetaServlet.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+
+import java.io.IOException;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Generic container servlet to manage routing to different pipelines.
+ * <p>
+ * Callers can create and configure a new processing pipeline by using one of
+ * the {@link #serve(String)} or {@link #serveRegex(String)} methods to allocate
+ * a binder for a particular URL pattern.
+ * <p>
+ * Registered filters and servlets are initialized lazily, usually during the
+ * first request. Once initialized the bindings in this servlet cannot be
+ * modified without destroying the servlet and thereby destroying all registered
+ * filters and servlets.
+ */
+public class MetaServlet extends HttpServlet {
+ private static final long serialVersionUID = 1L;
+
+ static final String REGEX_GROUPS = "org.eclipse.jgit.http.server.glue.MetaServlet.serveRegex";
+
+ private final List<ServletBinderImpl> bindings;
+
+ private volatile UrlPipeline[] pipelines;
+
+ /** Empty servlet with no bindings. */
+ public MetaServlet() {
+ this.bindings = new ArrayList<ServletBinderImpl>();
+ }
+
+ /**
+ * Construct a binding for a specific path.
+ *
+ * @param path
+ * pattern to match.
+ * @return binder for the passed path.
+ */
+ public ServletBinder serve(String path) {
+ if (path.startsWith("*"))
+ return register(new SuffixPipeline.Binder(path.substring(1)));
+ throw new IllegalArgumentException("\"" + path + "\" not supported");
+ }
+
+ /**
+ * Construct a binding for a regular expression.
+ *
+ * @param expression
+ * the regular expression to pattern match the URL against.
+ * @return binder for the passed expression.
+ */
+ public ServletBinder serveRegex(String expression) {
+ return register(new RegexPipeline.Binder(expression));
+ }
+
+ public void destroy() {
+ if (pipelines != null) {
+ Set<Object> destroyed = newIdentitySet();
+ for (UrlPipeline p : pipelines)
+ p.destroy(destroyed);
+ pipelines = null;
+ }
+ }
+
+ private static Set<Object> newIdentitySet() {
+ final Map<Object, Object> m = new IdentityHashMap<Object, Object>();
+ return new AbstractSet<Object>() {
+ @Override
+ public boolean add(Object o) {
+ return m.put(o, o) == null;
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return m.keySet().contains(o);
+ }
+
+ @Override
+ public Iterator<Object> iterator() {
+ return m.keySet().iterator();
+ }
+
+ @Override
+ public int size() {
+ return m.size();
+ }
+ };
+ }
+
+ @Override
+ protected void service(final HttpServletRequest req,
+ final HttpServletResponse rsp) throws ServletException, IOException {
+ final UrlPipeline p = find(req);
+ if (p != null)
+ p.service(req, rsp);
+ else
+ rsp.sendError(SC_NOT_FOUND);
+ }
+
+ private UrlPipeline find(final HttpServletRequest req)
+ throws ServletException {
+ for (UrlPipeline p : getPipelines())
+ if (p.match(req))
+ return p;
+ return null;
+ }
+
+ private ServletBinder register(ServletBinderImpl b) {
+ synchronized (bindings) {
+ if (pipelines != null)
+ throw new IllegalStateException("Servlet already initialized");
+ bindings.add(b);
+ }
+ return register((ServletBinder) b);
+ }
+
+ /**
+ * Configure a newly created binder.
+ *
+ * @param b
+ * the newly created binder.
+ * @return binder for the caller, potentially after adding one or more
+ * filters into the pipeline.
+ */
+ protected ServletBinder register(ServletBinder b) {
+ return b;
+ }
+
+ private UrlPipeline[] getPipelines() throws ServletException {
+ UrlPipeline[] r = pipelines;
+ if (r == null) {
+ synchronized (bindings) {
+ r = pipelines;
+ if (r == null) {
+ r = createPipelines();
+ pipelines = r;
+ }
+ }
+ }
+ return r;
+ }
+
+ private UrlPipeline[] createPipelines() throws ServletException {
+ UrlPipeline[] array = new UrlPipeline[bindings.size()];
+
+ for (int i = 0; i < bindings.size(); i++)
+ array[i] = bindings.get(i).create();
+
+ Set<Object> inited = newIdentitySet();
+ for (UrlPipeline p : array)
+ p.init(getServletContext(), inited);
+ return array;
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
new file mode 100644
index 0000000000..ed7b1cf694
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexGroupFilter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+/**
+ * Switch servlet path and path info to use another regex match group.
+ * <p>
+ * This filter is meant to be installed in the middle of a pipeline created by
+ * {@link MetaServlet#serveRegex(String)}. The passed request's servlet path is
+ * updated to be all text up to the start of the designated capture group, and
+ * the path info is changed to the contents of the capture group.
+ **/
+public class RegexGroupFilter implements Filter {
+ private final int groupIdx;
+
+ /**
+ * @param groupIdx
+ * capture group number, 1 through the number of groups.
+ */
+ public RegexGroupFilter(final int groupIdx) {
+ if (groupIdx < 1)
+ throw new IllegalArgumentException("Invalid index: " + groupIdx);
+ this.groupIdx = groupIdx - 1;
+ }
+
+ public void init(FilterConfig config) throws ServletException {
+ // Do nothing.
+ }
+
+ public void destroy() {
+ // Do nothing.
+ }
+
+ public void doFilter(final ServletRequest request,
+ final ServletResponse rsp, final FilterChain chain)
+ throws IOException, ServletException {
+ final WrappedRequest[] g = groupsFor(request);
+ if (groupIdx < g.length)
+ chain.doFilter(g[groupIdx], rsp);
+ else
+ throw new ServletException("Invalid regex group " + (groupIdx + 1));
+ }
+
+ private static WrappedRequest[] groupsFor(final ServletRequest r) {
+ return (WrappedRequest[]) r.getAttribute(MetaServlet.REGEX_GROUPS);
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
new file mode 100644
index 0000000000..635ff5493f
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/RegexPipeline.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static org.eclipse.jgit.http.server.glue.MetaServlet.REGEX_GROUPS;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Selects requests by matching the URI against a regular expression.
+ * <p>
+ * The pattern is bound and matched against the path info of the servlet
+ * request, as this class assumes it is invoked by {@link MetaServlet}.
+ * <p>
+ * If there are capture groups in the regular expression, the matched ranges of
+ * the capture groups are stored as an array of modified HttpServetRequests,
+ * into the request attribute {@link MetaServlet#REGEX_GROUPS}.
+ * <p>
+ * Each servlet request has been altered to have its {@code getPathInfo()}
+ * method return the matched text of the corresponding capture group. A
+ * {@link RegexGroupFilter} can be applied in the pipeline to switch the current
+ * HttpServletRequest to reference a different capture group before running
+ * additional filters, or the final servlet.
+ * <p>
+ * This class dispatches the remainder of the pipeline using the first capture
+ * group as the current request, making {@code RegexGroupFilter} required only
+ * to access capture groups beyond the first.
+ */
+class RegexPipeline extends UrlPipeline {
+ static class Binder extends ServletBinderImpl {
+ private final Pattern pattern;
+
+ Binder(final String p) {
+ pattern = Pattern.compile(p);
+ }
+
+ UrlPipeline create() {
+ return new RegexPipeline(pattern, getFilters(), getServlet());
+ }
+ }
+
+ private final Pattern pattern;
+
+ RegexPipeline(final Pattern pattern, final Filter[] filters,
+ final HttpServlet servlet) {
+ super(filters, servlet);
+ this.pattern = pattern;
+ }
+
+ boolean match(final HttpServletRequest req) {
+ final String pathInfo = req.getPathInfo();
+ return pathInfo != null && pattern.matcher(pathInfo).matches();
+ }
+
+ @Override
+ void service(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ final String reqInfo = req.getPathInfo();
+ if (reqInfo == null) {
+ rsp.sendError(SC_NOT_FOUND);
+ return;
+ }
+
+ final Matcher cur = pattern.matcher(reqInfo);
+ if (!cur.matches()) {
+ rsp.sendError(SC_NOT_FOUND);
+ return;
+ }
+
+ final String reqPath = req.getServletPath();
+ final Object old = req.getAttribute(REGEX_GROUPS);
+ try {
+ if (1 <= cur.groupCount()) {
+ // If there are groups extract every capture group and
+ // build a request for them so RegexGroupFilter can pick
+ // a different capture group later. Continue using the
+ // first capture group as the path info.
+ WrappedRequest groups[] = new WrappedRequest[cur.groupCount()];
+ for (int groupId = 1; groupId <= cur.groupCount(); groupId++) {
+ final int s = cur.start(groupId);
+ final String path, info;
+
+ path = reqPath + reqInfo.substring(0, s);
+ info = cur.group(groupId);
+ groups[groupId - 1] = new WrappedRequest(req, path, info);
+ }
+ req.setAttribute(REGEX_GROUPS, groups);
+ super.service(groups[0], rsp);
+
+ } else {
+ // No capture groups were present, service the whole request.
+ final String path = reqPath + reqInfo;
+ final String info = null;
+ super.service(new WrappedRequest(req, path, info), rsp);
+ }
+ } finally {
+ if (old != null)
+ req.setAttribute(REGEX_GROUPS, old);
+ else
+ req.removeAttribute(REGEX_GROUPS);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Pipeline[regex: " + pattern + " ]";
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
new file mode 100644
index 0000000000..9c3ed50d08
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinder.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServlet;
+
+/** Binds a servlet to a URL. */
+public interface ServletBinder {
+ /**
+ * @param filter
+ * the filter to trigger while processing the path.
+ * @return {@code this}.
+ */
+ public ServletBinder through(Filter filter);
+
+ /**
+ * @param servlet
+ * the servlet to execute on this path.
+ */
+ public void with(HttpServlet servlet);
+} \ No newline at end of file
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
new file mode 100644
index 0000000000..d4cd445a13
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/ServletBinderImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletResponse;
+
+abstract class ServletBinderImpl implements ServletBinder {
+ private final List<Filter> filters;
+
+ private HttpServlet httpServlet;
+
+ ServletBinderImpl() {
+ this.filters = new ArrayList<Filter>();
+ }
+
+ public ServletBinder through(Filter filter) {
+ if (filter == null)
+ throw new NullPointerException("filter must not be null");
+ filters.add(filter);
+ return this;
+ }
+
+ public void with(HttpServlet servlet) {
+ if (servlet == null)
+ throw new NullPointerException("servlet must not be null");
+ if (httpServlet != null)
+ throw new IllegalStateException("servlet was already bound");
+ httpServlet = servlet;
+ }
+
+ /** @return the configured servlet, or singleton returning 404 if none. */
+ protected HttpServlet getServlet() {
+ if (httpServlet != null)
+ return httpServlet;
+ else
+ return new ErrorServlet(HttpServletResponse.SC_NOT_FOUND);
+ }
+
+ /** @return the configured filters; zero-length array if none. */
+ protected Filter[] getFilters() {
+ return filters.toArray(new Filter[filters.size()]);
+ }
+
+ /** @return the pipeline that matches and executes this chain. */
+ abstract UrlPipeline create();
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java
new file mode 100644
index 0000000000..b942016259
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/SuffixPipeline.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Selects requests by matching the suffix of the URI.
+ * <p>
+ * The suffix string is literally matched against the path info of the servlet
+ * request, as this class assumes it is invoked by {@link MetaServlet}. Suffix
+ * strings may include path components. Examples include {@code /info/refs}, or
+ * just simple extension matches like {@code .txt}.
+ * <p>
+ * When dispatching to the rest of the pipeline the HttpServletRequest is
+ * modified so that {@code getPathInfo()} does not contain the suffix that
+ * caused this pipeline to be selected.
+ */
+class SuffixPipeline extends UrlPipeline {
+ static class Binder extends ServletBinderImpl {
+ private final String suffix;
+
+ Binder(final String suffix) {
+ this.suffix = suffix;
+ }
+
+ UrlPipeline create() {
+ return new SuffixPipeline(suffix, getFilters(), getServlet());
+ }
+ }
+
+ private final String suffix;
+
+ private final int suffixLen;
+
+ SuffixPipeline(final String suffix, final Filter[] filters,
+ final HttpServlet servlet) {
+ super(filters, servlet);
+ this.suffix = suffix;
+ this.suffixLen = suffix.length();
+ }
+
+ boolean match(final HttpServletRequest req) {
+ final String pathInfo = req.getPathInfo();
+ return pathInfo != null && pathInfo.endsWith(suffix);
+ }
+
+ @Override
+ void service(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ String curInfo = req.getPathInfo();
+ String newPath = req.getServletPath() + curInfo;
+ String newInfo = curInfo.substring(0, curInfo.length() - suffixLen);
+ super.service(new WrappedRequest(req, newPath, newInfo), rsp);
+ }
+
+ @Override
+ public String toString() {
+ return "Pipeline[ *" + suffix + " ]";
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java
new file mode 100644
index 0000000000..2257966d69
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/UrlPipeline.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Encapsulates the entire serving stack for a single URL.
+ * <p>
+ * Subclasses provide the implementation of {@link #match(HttpServletRequest)},
+ * which is called by {@link MetaServlet} in registration order to determine the
+ * pipeline that will be used to handle a request.
+ * <p>
+ * The very bottom of each pipeline is a single {@link HttpServlet} that will
+ * handle producing the response for this pipeline's URL. {@link Filter}s may
+ * also be registered and applied around the servlet's processing, to manage
+ * request attributes, set standard response headers, or completely override the
+ * response generation.
+ */
+abstract class UrlPipeline {
+ /** Filters to apply around {@link #servlet}; may be empty but never null. */
+ private final Filter[] filters;
+
+ /** Instance that must generate the response; never null. */
+ private final HttpServlet servlet;
+
+ UrlPipeline(final Filter[] filters, final HttpServlet servlet) {
+ this.filters = filters;
+ this.servlet = servlet;
+ }
+
+ /**
+ * Initialize all contained filters and servlets.
+ *
+ * @param context
+ * the servlet container context our {@link MetaServlet} is
+ * running within.
+ * @param inited
+ * <i>(input/output)</i> the set of filters and servlets which
+ * have already been initialized within the container context. If
+ * those same instances appear in this pipeline they are not
+ * initialized a second time. Filters and servlets that are first
+ * initialized by this pipeline will be added to this set.
+ * @throws ServletException
+ * a filter or servlet is unable to initialize.
+ */
+ void init(final ServletContext context, final Set<Object> inited)
+ throws ServletException {
+ for (Filter ref : filters)
+ initFilter(ref, context, inited);
+ initServlet(servlet, context, inited);
+ }
+
+ private static void initFilter(final Filter ref,
+ final ServletContext context, final Set<Object> inited)
+ throws ServletException {
+ if (!inited.contains(ref)) {
+ ref.init(new FilterConfig() {
+ public String getInitParameter(String name) {
+ return null;
+ }
+
+ public Enumeration getInitParameterNames() {
+ return new Enumeration<String>() {
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ public String nextElement() {
+ throw new NoSuchElementException();
+ }
+ };
+ }
+
+ public ServletContext getServletContext() {
+ return context;
+ }
+
+ public String getFilterName() {
+ return ref.getClass().getName();
+ }
+ });
+ inited.add(ref);
+ }
+ }
+
+ private static void initServlet(final HttpServlet ref,
+ final ServletContext context, final Set<Object> inited)
+ throws ServletException {
+ if (!inited.contains(ref)) {
+ ref.init(new ServletConfig() {
+ public String getInitParameter(String name) {
+ return null;
+ }
+
+ public Enumeration getInitParameterNames() {
+ return new Enumeration<String>() {
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ public String nextElement() {
+ throw new NoSuchElementException();
+ }
+ };
+ }
+
+ public ServletContext getServletContext() {
+ return context;
+ }
+
+ public String getServletName() {
+ return ref.getClass().getName();
+ }
+ });
+ inited.add(ref);
+ }
+ }
+
+ /**
+ * Destroy all contained filters and servlets.
+ *
+ * @param destroyed
+ * <i>(input/output)</i> the set of filters and servlets which
+ * have already been destroyed within the container context. If
+ * those same instances appear in this pipeline they are not
+ * destroyed a second time. Filters and servlets that are first
+ * destroyed by this pipeline will be added to this set.
+ */
+ void destroy(final Set<Object> destroyed) {
+ for (Filter ref : filters)
+ destroyFilter(ref, destroyed);
+ destroyServlet(servlet, destroyed);
+ }
+
+ private static void destroyFilter(Filter ref, Set<Object> destroyed) {
+ if (!destroyed.contains(ref)) {
+ ref.destroy();
+ destroyed.add(ref);
+ }
+ }
+
+ private static void destroyServlet(HttpServlet ref, Set<Object> destroyed) {
+ if (!destroyed.contains(ref)) {
+ ref.destroy();
+ destroyed.add(ref);
+ }
+ }
+
+ /**
+ * Determine if this pipeline handles the request's URL.
+ * <p>
+ * This method should match on the request's {@code getPathInfo()} method,
+ * as {@link MetaServlet} passes the request along as-is to each pipeline's
+ * match method.
+ *
+ * @param req
+ * current HTTP request being considered by {@link MetaServlet}.
+ * @return {@code true} if this pipeline is configured to handle the
+ * request; {@code false} otherwise.
+ */
+ abstract boolean match(HttpServletRequest req);
+
+ /**
+ * Execute the filters and the servlet on the request.
+ * <p>
+ * Invoked by {@link MetaServlet} once {@link #match(HttpServletRequest)}
+ * has determined this pipeline is the correct pipeline to handle the
+ * current request.
+ *
+ * @param req
+ * current HTTP request.
+ * @param rsp
+ * current HTTP response.
+ * @throws ServletException
+ * request cannot be completed.
+ * @throws IOException
+ * IO error prevents the request from being completed.
+ */
+ void service(HttpServletRequest req, HttpServletResponse rsp)
+ throws ServletException, IOException {
+ if (0 < filters.length)
+ new Chain(filters, servlet).doFilter(req, rsp);
+ else
+ servlet.service(req, rsp);
+ }
+
+ private static class Chain implements FilterChain {
+ private final Filter[] filters;
+
+ private final HttpServlet servlet;
+
+ private int filterIdx;
+
+ Chain(final Filter[] filters, final HttpServlet servlet) {
+ this.filters = filters;
+ this.servlet = servlet;
+ }
+
+ public void doFilter(ServletRequest req, ServletResponse rsp)
+ throws IOException, ServletException {
+ if (filterIdx < filters.length)
+ filters[filterIdx++].doFilter(req, rsp, this);
+ else
+ servlet.service(req, rsp);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java
new file mode 100644
index 0000000000..7f8da7c440
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/glue/WrappedRequest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.glue;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/** Overrides the path and path info. */
+public class WrappedRequest extends HttpServletRequestWrapper {
+ private final String path;
+
+ private final String pathInfo;
+
+ /**
+ * Create a new request with different path and path info properties.
+ *
+ * @param originalRequest
+ * the original HTTP request.
+ * @param path
+ * new servlet path to report to callers.
+ * @param pathInfo
+ * new path info to report to callers.
+ */
+ public WrappedRequest(final HttpServletRequest originalRequest,
+ final String path, final String pathInfo) {
+ super(originalRequest);
+ this.path = path;
+ this.pathInfo = pathInfo;
+ }
+
+ @Override
+ public String getPathTranslated() {
+ final String p = getPathInfo();
+ return p != null ? getRealPath(p) : null;
+ }
+
+ @Override
+ public String getPathInfo() {
+ return pathInfo;
+ }
+
+ @Override
+ public String getServletPath() {
+ return path;
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
new file mode 100644
index 0000000000..b9545f9101
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/AsIsFileService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.resolver;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.http.server.GitServlet;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Config.SectionParser;
+
+/**
+ * Controls access to bare files in a repository.
+ * <p>
+ * Older HTTP clients which do not speak the smart HTTP variant of the Git
+ * protocol fetch from a repository by directly getting its objects and pack
+ * files. This class, along with the {@code http.getanyfile} per-repository
+ * configuration setting, can be used by {@link GitServlet} to control whether
+ * or not these older clients are permitted to read these direct files.
+ */
+public class AsIsFileService {
+ /** Always throws {@link ServiceNotEnabledException}. */
+ public static final AsIsFileService DISABLED = new AsIsFileService() {
+ @Override
+ public void access(HttpServletRequest req, Repository db)
+ throws ServiceNotEnabledException {
+ throw new ServiceNotEnabledException();
+ }
+ };
+
+ private static final SectionParser<ServiceConfig> CONFIG = new SectionParser<ServiceConfig>() {
+ public ServiceConfig parse(final Config cfg) {
+ return new ServiceConfig(cfg);
+ }
+ };
+
+ private static class ServiceConfig {
+ final boolean enabled;
+
+ ServiceConfig(final Config cfg) {
+ enabled = cfg.getBoolean("http", "getanyfile", true);
+ }
+ }
+
+ /**
+ * Determine if {@code http.getanyfile} is enabled in the configuration.
+ *
+ * @param db
+ * the repository to check.
+ * @return {@code false} if {@code http.getanyfile} was explicitly set to
+ * {@code false} in the repository's configuration file; otherwise
+ * {@code true}.
+ */
+ protected static boolean isEnabled(Repository db) {
+ return db.getConfig().get(CONFIG).enabled;
+ }
+
+ /**
+ * Determine if access to any bare file of the repository is allowed.
+ * <p>
+ * This method silently succeeds if the request is allowed, or fails by
+ * throwing a checked exception if access should be denied.
+ * <p>
+ * The default implementation of this method checks {@code http.getanyfile},
+ * throwing {@link ServiceNotEnabledException} if it was explicitly set to
+ * {@code false}, and otherwise succeeding silently.
+ *
+ * @param req
+ * current HTTP request, in case information from the request may
+ * help determine the access request.
+ * @param db
+ * the repository the request would obtain a bare file from.
+ * @throws ServiceNotEnabledException
+ * bare file access is not allowed on the target repository, by
+ * any user, for any reason.
+ * @throws ServiceNotAuthorizedException
+ * bare file access is not allowed for this HTTP request and
+ * repository, such as due to a permission error.
+ */
+ public void access(HttpServletRequest req, Repository db)
+ throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+ if (!isEnabled(db))
+ throw new ServiceNotEnabledException();
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java
new file mode 100644
index 0000000000..82a0ce84a0
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/FileResolver.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.resolver;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+
+/** Default resolver serving from a single root path in local filesystem. */
+public class FileResolver implements RepositoryResolver {
+ private final File basePath;
+
+ private final boolean exportAll;
+
+ /**
+ * Create a new resolver for the given path.
+ *
+ * @param basePath
+ * the base path all repositories are rooted under.
+ * @param exportAll
+ * if true, exports all repositories, ignoring the check for the
+ * {@code git-daemon-export-ok} files.
+ */
+ public FileResolver(final File basePath, final boolean exportAll) {
+ this.basePath = basePath;
+ this.exportAll = exportAll;
+ }
+
+ public Repository open(final HttpServletRequest req,
+ final String repositoryName) throws RepositoryNotFoundException,
+ ServiceNotEnabledException {
+ if (isUnreasonableName(repositoryName))
+ throw new RepositoryNotFoundException(repositoryName);
+
+ final Repository db;
+ try {
+ final File gitdir = new File(basePath, repositoryName);
+ db = RepositoryCache.open(FileKey.lenient(gitdir), true);
+ } catch (IOException e) {
+ throw new RepositoryNotFoundException(repositoryName, e);
+ }
+
+ try {
+ if (isExportOk(req, repositoryName, db)) {
+ // We have to leak the open count to the caller, they
+ // are responsible for closing the repository if we
+ // complete successfully.
+ return db;
+ } else
+ throw new ServiceNotEnabledException();
+
+ } catch (RuntimeException e) {
+ db.close();
+ throw new RepositoryNotFoundException(repositoryName, e);
+
+ } catch (IOException e) {
+ db.close();
+ throw new RepositoryNotFoundException(repositoryName, e);
+
+ } catch (ServiceNotEnabledException e) {
+ db.close();
+ throw e;
+ }
+ }
+
+ /** @return {@code true} if all repositories are to be exported. */
+ protected boolean isExportAll() {
+ return exportAll;
+ }
+
+ /**
+ * Check if this repository can be served over HTTP.
+ * <p>
+ * The default implementation of this method returns true only if either
+ * {@link #isExportAll()} is true, or the {@code git-daemon-export-ok} file
+ * is present in the repository's directory.
+ *
+ * @param req
+ * the current HTTP request.
+ * @param repositoryName
+ * name of the repository, as present in the URL.
+ * @param db
+ * the opened repository instance.
+ * @return true if the repository is accessible; false if not.
+ * @throws IOException
+ * the repository could not be accessed, the caller will claim
+ * the repository does not exist.
+ */
+ protected boolean isExportOk(HttpServletRequest req, String repositoryName,
+ Repository db) throws IOException {
+ if (isExportAll())
+ return true;
+ else
+ return new File(db.getDirectory(), "git-daemon-export-ok").exists();
+ }
+
+ private static boolean isUnreasonableName(final String name) {
+ if (name.length() == 0)
+ return true; // no empty paths
+
+ if (name.indexOf('\\') >= 0)
+ return true; // no windows/dos style paths
+ if (new File(name).isAbsolute())
+ return true; // no absolute paths
+
+ if (name.startsWith("../"))
+ return true; // no "l../etc/passwd"
+ if (name.contains("/../"))
+ return true; // no "foo/../etc/passwd"
+ if (name.contains("/./"))
+ return true; // "foo/./foo" is insane to ask
+ if (name.contains("//"))
+ return true; // double slashes is sloppy, don't use it
+
+ return false; // is a reasonable name
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/RepositoryResolver.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/RepositoryResolver.java
new file mode 100644
index 0000000000..ba17dac45b
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/RepositoryResolver.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.resolver;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+
+/** Locate a Git {@link Repository} by name from the URL. */
+public interface RepositoryResolver {
+ /**
+ * Locate and open a reference to a {@link Repository}.
+ * <p>
+ * The caller is responsible for closing the returned Repository.
+ *
+ * @param req
+ * the current HTTP request, may be used to inspect session state
+ * including cookies or user authentication.
+ * @param name
+ * name of the repository, as parsed out of the URL.
+ * @return the opened repository instance, never null.
+ * @throws RepositoryNotFoundException
+ * the repository does not exist or the name is incorrectly
+ * formatted as a repository name.
+ * @throws ServiceNotAuthorizedException
+ * the repository exists, but HTTP access is not allowed for the
+ * current user.
+ * @throws ServiceNotEnabledException
+ * the repository exists, but HTTP access is not allowed on the
+ * target repository, by any user.
+ */
+ Repository open(HttpServletRequest req, String name)
+ throws RepositoryNotFoundException, ServiceNotAuthorizedException,
+ ServiceNotEnabledException;
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotAuthorizedException.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotAuthorizedException.java
new file mode 100644
index 0000000000..fca044a10c
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotAuthorizedException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.resolver;
+
+/** Indicates the request service is not authorized for current user. */
+public class ServiceNotAuthorizedException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /** Indicates the request service is not available. */
+ public ServiceNotAuthorizedException() {
+ super("Service not permitted");
+ }
+}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotEnabledException.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotEnabledException.java
new file mode 100644
index 0000000000..dedc0d4825
--- /dev/null
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/resolver/ServiceNotEnabledException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009-2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - 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.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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.
+ */
+
+package org.eclipse.jgit.http.server.resolver;
+
+/** Indicates the request service is not enabled on a repository. */
+public class ServiceNotEnabledException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ /** Indicates the request service is not available. */
+ public ServiceNotEnabledException() {
+ super("Service not enabled");
+ }
+}