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