xref: /JGit/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java (revision 2161c1e5e4a53b3c56256389b3fc388b3c51d82d)
13bae524fSMatthias Sohn /*
25c5f7c6bSMatthias Sohn  * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com> and others
33bae524fSMatthias Sohn  *
45c5f7c6bSMatthias Sohn  * This program and the accompanying materials are made available under the
55c5f7c6bSMatthias Sohn  * terms of the Eclipse Distribution License v. 1.0 which is available at
65c5f7c6bSMatthias Sohn  * https://www.eclipse.org/org/documents/edl-v10.php.
73bae524fSMatthias Sohn  *
85c5f7c6bSMatthias Sohn  * SPDX-License-Identifier: BSD-3-Clause
93bae524fSMatthias Sohn  */
103bae524fSMatthias Sohn package org.eclipse.jgit.lfs.server;
113bae524fSMatthias Sohn 
1230c6c754SDavid Pursehouse import static java.nio.charset.StandardCharsets.UTF_8;
13571c9f5fSDavid Pursehouse import static org.apache.http.HttpStatus.SC_FORBIDDEN;
14ffbe03aaSDavid Pursehouse import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE;
15a45cfee7SDavid Pursehouse import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
16571c9f5fSDavid Pursehouse import static org.apache.http.HttpStatus.SC_NOT_FOUND;
17571c9f5fSDavid Pursehouse import static org.apache.http.HttpStatus.SC_OK;
18571c9f5fSDavid Pursehouse import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
197245aa03SDavid Pursehouse import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
20571c9f5fSDavid Pursehouse import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
2155c629a9SDavid Pursehouse import static org.eclipse.jgit.lfs.lib.Constants.DOWNLOAD;
2255c629a9SDavid Pursehouse import static org.eclipse.jgit.lfs.lib.Constants.UPLOAD;
2355c629a9SDavid Pursehouse import static org.eclipse.jgit.lfs.lib.Constants.VERIFY;
245094c1a5SDavid Pursehouse import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
253bae524fSMatthias Sohn 
263bae524fSMatthias Sohn import java.io.BufferedReader;
273bae524fSMatthias Sohn import java.io.BufferedWriter;
283bae524fSMatthias Sohn import java.io.IOException;
293bae524fSMatthias Sohn import java.io.InputStreamReader;
303bae524fSMatthias Sohn import java.io.OutputStreamWriter;
313bae524fSMatthias Sohn import java.io.Reader;
323bae524fSMatthias Sohn import java.io.Writer;
3359014116SDavid Pursehouse import java.text.MessageFormat;
343bae524fSMatthias Sohn import java.util.List;
353bae524fSMatthias Sohn 
363bae524fSMatthias Sohn import javax.servlet.ServletException;
373bae524fSMatthias Sohn import javax.servlet.http.HttpServlet;
383bae524fSMatthias Sohn import javax.servlet.http.HttpServletRequest;
393bae524fSMatthias Sohn import javax.servlet.http.HttpServletResponse;
403bae524fSMatthias Sohn 
411096652eSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded;
42571c9f5fSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsException;
43ffbe03aaSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsInsufficientStorage;
441096652eSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded;
45571c9f5fSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound;
46571c9f5fSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly;
477245aa03SDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsUnauthorized;
48d52bf2eeSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsUnavailable;
49571c9f5fSDavid Pursehouse import org.eclipse.jgit.lfs.errors.LfsValidationError;
5059014116SDavid Pursehouse import org.eclipse.jgit.lfs.internal.LfsText;
51b433f272SDavid Pursehouse import org.eclipse.jgit.lfs.server.internal.LfsGson;
5259014116SDavid Pursehouse import org.slf4j.Logger;
5359014116SDavid Pursehouse import org.slf4j.LoggerFactory;
54571c9f5fSDavid Pursehouse 
553bae524fSMatthias Sohn /**
563bae524fSMatthias Sohn  * LFS protocol handler implementing the LFS batch API [1]
573bae524fSMatthias Sohn  *
58053684acSDavid Pursehouse  * [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md
593bae524fSMatthias Sohn  *
603bae524fSMatthias Sohn  * @since 4.3
613bae524fSMatthias Sohn  */
623bae524fSMatthias Sohn public abstract class LfsProtocolServlet extends HttpServlet {
63*14a157dfSDavid Pursehouse 	private static final Logger LOG = LoggerFactory
6459014116SDavid Pursehouse 			.getLogger(LfsProtocolServlet.class);
653bae524fSMatthias Sohn 
663bae524fSMatthias Sohn 	private static final long serialVersionUID = 1L;
673bae524fSMatthias Sohn 
680b4751e8SDavid Pursehouse 	private static final String CONTENTTYPE_VND_GIT_LFS_JSON =
690b4751e8SDavid Pursehouse 			"application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$
703bae524fSMatthias Sohn 
711096652eSDavid Pursehouse 	private static final int SC_RATE_LIMIT_EXCEEDED = 429;
721096652eSDavid Pursehouse 
731096652eSDavid Pursehouse 	private static final int SC_BANDWIDTH_LIMIT_EXCEEDED = 509;
741096652eSDavid Pursehouse 
753bae524fSMatthias Sohn 	/**
76571c9f5fSDavid Pursehouse 	 * Get the large file repository for the given request and path.
773bae524fSMatthias Sohn 	 *
78bb9988c2SDavid Pursehouse 	 * @param request
79bb9988c2SDavid Pursehouse 	 *            the request
80bb9988c2SDavid Pursehouse 	 * @param path
81bb9988c2SDavid Pursehouse 	 *            the path
825094c1a5SDavid Pursehouse 	 * @param auth
835094c1a5SDavid Pursehouse 	 *            the Authorization HTTP header
845094c1a5SDavid Pursehouse 	 * @return the large file repository storing large files.
855d64d3d8SMatthias Sohn 	 * @throws org.eclipse.jgit.lfs.errors.LfsException
865094c1a5SDavid Pursehouse 	 *             implementations should throw more specific exceptions to
875094c1a5SDavid Pursehouse 	 *             signal which type of error occurred:
885094c1a5SDavid Pursehouse 	 *             <dl>
895d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsValidationError}</dt>
905094c1a5SDavid Pursehouse 	 *             <dd>when there is a validation error with one or more of the
915094c1a5SDavid Pursehouse 	 *             objects in the request</dd>
925d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}</dt>
935094c1a5SDavid Pursehouse 	 *             <dd>when the repository does not exist for the user</dd>
945d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}</dt>
955094c1a5SDavid Pursehouse 	 *             <dd>when the user has read, but not write access. Only
965094c1a5SDavid Pursehouse 	 *             applicable when the operation in the request is "upload"</dd>
975d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}</dt>
985094c1a5SDavid Pursehouse 	 *             <dd>when the user has hit a rate limit with the server</dd>
995d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}</dt>
1005094c1a5SDavid Pursehouse 	 *             <dd>when the bandwidth limit for the user or repository has
1015094c1a5SDavid Pursehouse 	 *             been exceeded</dd>
1025d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}</dt>
1035094c1a5SDavid Pursehouse 	 *             <dd>when there is insufficient storage on the server</dd>
1045d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsUnavailable}</dt>
1055094c1a5SDavid Pursehouse 	 *             <dd>when LFS is not available</dd>
1065d64d3d8SMatthias Sohn 	 *             <dt>{@link org.eclipse.jgit.lfs.errors.LfsException}</dt>
1075094c1a5SDavid Pursehouse 	 *             <dd>when an unexpected internal server error occurred</dd>
1085094c1a5SDavid Pursehouse 	 *             </dl>
1095094c1a5SDavid Pursehouse 	 * @since 4.7
1103bae524fSMatthias Sohn 	 */
getLargeFileRepository( LfsRequest request, String path, String auth)111bb9988c2SDavid Pursehouse 	protected abstract LargeFileRepository getLargeFileRepository(
1125094c1a5SDavid Pursehouse 			LfsRequest request, String path, String auth) throws LfsException;
113bb9988c2SDavid Pursehouse 
114ae779f60SMatthias Sohn 	/**
115ae779f60SMatthias Sohn 	 * LFS request.
116ae779f60SMatthias Sohn 	 *
117ae779f60SMatthias Sohn 	 * @since 4.5
118ae779f60SMatthias Sohn 	 */
119bb9988c2SDavid Pursehouse 	protected static class LfsRequest {
120bb9988c2SDavid Pursehouse 		private String operation;
121bb9988c2SDavid Pursehouse 
122bb9988c2SDavid Pursehouse 		private List<LfsObject> objects;
123bb9988c2SDavid Pursehouse 
124bb9988c2SDavid Pursehouse 		/**
125bb9988c2SDavid Pursehouse 		 * Get the LFS operation.
126bb9988c2SDavid Pursehouse 		 *
127bb9988c2SDavid Pursehouse 		 * @return the operation
128bb9988c2SDavid Pursehouse 		 */
getOperation()129bb9988c2SDavid Pursehouse 		public String getOperation() {
130bb9988c2SDavid Pursehouse 			return operation;
131bb9988c2SDavid Pursehouse 		}
132584035a5SDavid Pursehouse 
133584035a5SDavid Pursehouse 		/**
134584035a5SDavid Pursehouse 		 * Get the LFS objects.
135584035a5SDavid Pursehouse 		 *
136584035a5SDavid Pursehouse 		 * @return the objects
137584035a5SDavid Pursehouse 		 */
getObjects()138584035a5SDavid Pursehouse 		public List<LfsObject> getObjects() {
139584035a5SDavid Pursehouse 			return objects;
140584035a5SDavid Pursehouse 		}
14155c629a9SDavid Pursehouse 
14255c629a9SDavid Pursehouse 		/**
14355c629a9SDavid Pursehouse 		 * @return true if the operation is upload.
14455c629a9SDavid Pursehouse 		 * @since 4.7
14555c629a9SDavid Pursehouse 		 */
isUpload()14655c629a9SDavid Pursehouse 		public boolean isUpload() {
14755c629a9SDavid Pursehouse 			return operation.equals(UPLOAD);
14855c629a9SDavid Pursehouse 		}
14955c629a9SDavid Pursehouse 
15055c629a9SDavid Pursehouse 		/**
15155c629a9SDavid Pursehouse 		 * @return true if the operation is download.
15255c629a9SDavid Pursehouse 		 * @since 4.7
15355c629a9SDavid Pursehouse 		 */
isDownload()15455c629a9SDavid Pursehouse 		public boolean isDownload() {
15555c629a9SDavid Pursehouse 			return operation.equals(DOWNLOAD);
15655c629a9SDavid Pursehouse 		}
15755c629a9SDavid Pursehouse 
15855c629a9SDavid Pursehouse 		/**
15955c629a9SDavid Pursehouse 		 * @return true if the operation is verify.
16055c629a9SDavid Pursehouse 		 * @since 4.7
16155c629a9SDavid Pursehouse 		 */
isVerify()16255c629a9SDavid Pursehouse 		public boolean isVerify() {
16355c629a9SDavid Pursehouse 			return operation.equals(VERIFY);
16455c629a9SDavid Pursehouse 		}
165bb9988c2SDavid Pursehouse 	}
1663bae524fSMatthias Sohn 
1675d64d3d8SMatthias Sohn 	/** {@inheritDoc} */
1683bae524fSMatthias Sohn 	@Override
doPost(HttpServletRequest req, HttpServletResponse res)1693bae524fSMatthias Sohn 	protected void doPost(HttpServletRequest req, HttpServletResponse res)
1703bae524fSMatthias Sohn 			throws ServletException, IOException {
1713bae524fSMatthias Sohn 		Writer w = new BufferedWriter(
17230c6c754SDavid Pursehouse 				new OutputStreamWriter(res.getOutputStream(), UTF_8));
1733bae524fSMatthias Sohn 
1744651d6e7SDavid Pursehouse 		Reader r = new BufferedReader(
17530c6c754SDavid Pursehouse 				new InputStreamReader(req.getInputStream(), UTF_8));
176b433f272SDavid Pursehouse 		LfsRequest request = LfsGson.fromJson(r, LfsRequest.class);
177bb9988c2SDavid Pursehouse 		String path = req.getPathInfo();
1783bae524fSMatthias Sohn 
1790b4751e8SDavid Pursehouse 		res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
180571c9f5fSDavid Pursehouse 		LargeFileRepository repo = null;
181571c9f5fSDavid Pursehouse 		try {
1825094c1a5SDavid Pursehouse 			repo = getLargeFileRepository(request, path,
1835094c1a5SDavid Pursehouse 					req.getHeader(HDR_AUTHORIZATION));
1843bae524fSMatthias Sohn 			if (repo == null) {
18559014116SDavid Pursehouse 				String error = MessageFormat
18659014116SDavid Pursehouse 						.format(LfsText.get().lfsFailedToGetRepository, path);
18759014116SDavid Pursehouse 				LOG.error(error);
18859014116SDavid Pursehouse 				throw new LfsException(error);
189d52bf2eeSDavid Pursehouse 			}
190bb9988c2SDavid Pursehouse 			res.setStatus(SC_OK);
1913bae524fSMatthias Sohn 			TransferHandler handler = TransferHandler
1923bae524fSMatthias Sohn 					.forOperation(request.operation, repo, request.objects);
193b433f272SDavid Pursehouse 			LfsGson.toJson(handler.process(), w);
1944651d6e7SDavid Pursehouse 		} catch (LfsValidationError e) {
1954651d6e7SDavid Pursehouse 			sendError(res, w, SC_UNPROCESSABLE_ENTITY, e.getMessage());
1964651d6e7SDavid Pursehouse 		} catch (LfsRepositoryNotFound e) {
1974651d6e7SDavid Pursehouse 			sendError(res, w, SC_NOT_FOUND, e.getMessage());
1984651d6e7SDavid Pursehouse 		} catch (LfsRepositoryReadOnly e) {
1994651d6e7SDavid Pursehouse 			sendError(res, w, SC_FORBIDDEN, e.getMessage());
2001096652eSDavid Pursehouse 		} catch (LfsRateLimitExceeded e) {
2011096652eSDavid Pursehouse 			sendError(res, w, SC_RATE_LIMIT_EXCEEDED, e.getMessage());
2021096652eSDavid Pursehouse 		} catch (LfsBandwidthLimitExceeded e) {
2031096652eSDavid Pursehouse 			sendError(res, w, SC_BANDWIDTH_LIMIT_EXCEEDED, e.getMessage());
204ffbe03aaSDavid Pursehouse 		} catch (LfsInsufficientStorage e) {
205ffbe03aaSDavid Pursehouse 			sendError(res, w, SC_INSUFFICIENT_STORAGE, e.getMessage());
206d52bf2eeSDavid Pursehouse 		} catch (LfsUnavailable e) {
2074651d6e7SDavid Pursehouse 			sendError(res, w, SC_SERVICE_UNAVAILABLE, e.getMessage());
2087245aa03SDavid Pursehouse 		} catch (LfsUnauthorized e) {
2097245aa03SDavid Pursehouse 			sendError(res, w, SC_UNAUTHORIZED, e.getMessage());
210d52bf2eeSDavid Pursehouse 		} catch (LfsException e) {
211d52bf2eeSDavid Pursehouse 			sendError(res, w, SC_INTERNAL_SERVER_ERROR, e.getMessage());
2124651d6e7SDavid Pursehouse 		} finally {
2133bae524fSMatthias Sohn 			w.flush();
2143bae524fSMatthias Sohn 		}
2154651d6e7SDavid Pursehouse 	}
2163bae524fSMatthias Sohn 
sendError(HttpServletResponse rsp, Writer writer, int status, String message)2174651d6e7SDavid Pursehouse 	private void sendError(HttpServletResponse rsp, Writer writer, int status,
2184651d6e7SDavid Pursehouse 			String message) {
219571c9f5fSDavid Pursehouse 		rsp.setStatus(status);
220b433f272SDavid Pursehouse 		LfsGson.toJson(message, writer);
2213bae524fSMatthias Sohn 	}
2223bae524fSMatthias Sohn }
223