1 /* 2 * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com> and others 3 * 4 * This program and the accompanying materials are made available under the 5 * terms of the Eclipse Distribution License v. 1.0 which is available at 6 * https://www.eclipse.org/org/documents/edl-v10.php. 7 * 8 * SPDX-License-Identifier: BSD-3-Clause 9 */ 10 package org.eclipse.jgit.lfs.server; 11 12 import static java.nio.charset.StandardCharsets.UTF_8; 13 import static org.apache.http.HttpStatus.SC_FORBIDDEN; 14 import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE; 15 import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR; 16 import static org.apache.http.HttpStatus.SC_NOT_FOUND; 17 import static org.apache.http.HttpStatus.SC_OK; 18 import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE; 19 import static org.apache.http.HttpStatus.SC_UNAUTHORIZED; 20 import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY; 21 import static org.eclipse.jgit.lfs.lib.Constants.DOWNLOAD; 22 import static org.eclipse.jgit.lfs.lib.Constants.UPLOAD; 23 import static org.eclipse.jgit.lfs.lib.Constants.VERIFY; 24 import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION; 25 26 import java.io.BufferedReader; 27 import java.io.BufferedWriter; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.io.OutputStreamWriter; 31 import java.io.Reader; 32 import java.io.Writer; 33 import java.text.MessageFormat; 34 import java.util.List; 35 36 import javax.servlet.ServletException; 37 import javax.servlet.http.HttpServlet; 38 import javax.servlet.http.HttpServletRequest; 39 import javax.servlet.http.HttpServletResponse; 40 41 import org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded; 42 import org.eclipse.jgit.lfs.errors.LfsException; 43 import org.eclipse.jgit.lfs.errors.LfsInsufficientStorage; 44 import org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded; 45 import org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound; 46 import org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly; 47 import org.eclipse.jgit.lfs.errors.LfsUnauthorized; 48 import org.eclipse.jgit.lfs.errors.LfsUnavailable; 49 import org.eclipse.jgit.lfs.errors.LfsValidationError; 50 import org.eclipse.jgit.lfs.internal.LfsText; 51 import org.eclipse.jgit.lfs.server.internal.LfsGson; 52 import org.slf4j.Logger; 53 import org.slf4j.LoggerFactory; 54 55 /** 56 * LFS protocol handler implementing the LFS batch API [1] 57 * 58 * [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md 59 * 60 * @since 4.3 61 */ 62 public abstract class LfsProtocolServlet extends HttpServlet { 63 private static final Logger LOG = LoggerFactory 64 .getLogger(LfsProtocolServlet.class); 65 66 private static final long serialVersionUID = 1L; 67 68 private static final String CONTENTTYPE_VND_GIT_LFS_JSON = 69 "application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$ 70 71 private static final int SC_RATE_LIMIT_EXCEEDED = 429; 72 73 private static final int SC_BANDWIDTH_LIMIT_EXCEEDED = 509; 74 75 /** 76 * Get the large file repository for the given request and path. 77 * 78 * @param request 79 * the request 80 * @param path 81 * the path 82 * @param auth 83 * the Authorization HTTP header 84 * @return the large file repository storing large files. 85 * @throws org.eclipse.jgit.lfs.errors.LfsException 86 * implementations should throw more specific exceptions to 87 * signal which type of error occurred: 88 * <dl> 89 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsValidationError}</dt> 90 * <dd>when there is a validation error with one or more of the 91 * objects in the request</dd> 92 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}</dt> 93 * <dd>when the repository does not exist for the user</dd> 94 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}</dt> 95 * <dd>when the user has read, but not write access. Only 96 * applicable when the operation in the request is "upload"</dd> 97 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}</dt> 98 * <dd>when the user has hit a rate limit with the server</dd> 99 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}</dt> 100 * <dd>when the bandwidth limit for the user or repository has 101 * been exceeded</dd> 102 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}</dt> 103 * <dd>when there is insufficient storage on the server</dd> 104 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsUnavailable}</dt> 105 * <dd>when LFS is not available</dd> 106 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsException}</dt> 107 * <dd>when an unexpected internal server error occurred</dd> 108 * </dl> 109 * @since 4.7 110 */ getLargeFileRepository( LfsRequest request, String path, String auth)111 protected abstract LargeFileRepository getLargeFileRepository( 112 LfsRequest request, String path, String auth) throws LfsException; 113 114 /** 115 * LFS request. 116 * 117 * @since 4.5 118 */ 119 protected static class LfsRequest { 120 private String operation; 121 122 private List<LfsObject> objects; 123 124 /** 125 * Get the LFS operation. 126 * 127 * @return the operation 128 */ getOperation()129 public String getOperation() { 130 return operation; 131 } 132 133 /** 134 * Get the LFS objects. 135 * 136 * @return the objects 137 */ getObjects()138 public List<LfsObject> getObjects() { 139 return objects; 140 } 141 142 /** 143 * @return true if the operation is upload. 144 * @since 4.7 145 */ isUpload()146 public boolean isUpload() { 147 return operation.equals(UPLOAD); 148 } 149 150 /** 151 * @return true if the operation is download. 152 * @since 4.7 153 */ isDownload()154 public boolean isDownload() { 155 return operation.equals(DOWNLOAD); 156 } 157 158 /** 159 * @return true if the operation is verify. 160 * @since 4.7 161 */ isVerify()162 public boolean isVerify() { 163 return operation.equals(VERIFY); 164 } 165 } 166 167 /** {@inheritDoc} */ 168 @Override doPost(HttpServletRequest req, HttpServletResponse res)169 protected void doPost(HttpServletRequest req, HttpServletResponse res) 170 throws ServletException, IOException { 171 Writer w = new BufferedWriter( 172 new OutputStreamWriter(res.getOutputStream(), UTF_8)); 173 174 Reader r = new BufferedReader( 175 new InputStreamReader(req.getInputStream(), UTF_8)); 176 LfsRequest request = LfsGson.fromJson(r, LfsRequest.class); 177 String path = req.getPathInfo(); 178 179 res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON); 180 LargeFileRepository repo = null; 181 try { 182 repo = getLargeFileRepository(request, path, 183 req.getHeader(HDR_AUTHORIZATION)); 184 if (repo == null) { 185 String error = MessageFormat 186 .format(LfsText.get().lfsFailedToGetRepository, path); 187 LOG.error(error); 188 throw new LfsException(error); 189 } 190 res.setStatus(SC_OK); 191 TransferHandler handler = TransferHandler 192 .forOperation(request.operation, repo, request.objects); 193 LfsGson.toJson(handler.process(), w); 194 } catch (LfsValidationError e) { 195 sendError(res, w, SC_UNPROCESSABLE_ENTITY, e.getMessage()); 196 } catch (LfsRepositoryNotFound e) { 197 sendError(res, w, SC_NOT_FOUND, e.getMessage()); 198 } catch (LfsRepositoryReadOnly e) { 199 sendError(res, w, SC_FORBIDDEN, e.getMessage()); 200 } catch (LfsRateLimitExceeded e) { 201 sendError(res, w, SC_RATE_LIMIT_EXCEEDED, e.getMessage()); 202 } catch (LfsBandwidthLimitExceeded e) { 203 sendError(res, w, SC_BANDWIDTH_LIMIT_EXCEEDED, e.getMessage()); 204 } catch (LfsInsufficientStorage e) { 205 sendError(res, w, SC_INSUFFICIENT_STORAGE, e.getMessage()); 206 } catch (LfsUnavailable e) { 207 sendError(res, w, SC_SERVICE_UNAVAILABLE, e.getMessage()); 208 } catch (LfsUnauthorized e) { 209 sendError(res, w, SC_UNAUTHORIZED, e.getMessage()); 210 } catch (LfsException e) { 211 sendError(res, w, SC_INTERNAL_SERVER_ERROR, e.getMessage()); 212 } finally { 213 w.flush(); 214 } 215 } 216 sendError(HttpServletResponse rsp, Writer writer, int status, String message)217 private void sendError(HttpServletResponse rsp, Writer writer, int status, 218 String message) { 219 rsp.setStatus(status); 220 LfsGson.toJson(message, writer); 221 } 222 } 223