xref: /JGit/org.eclipse.jgit.lfs.server/src/org/eclipse/jgit/lfs/server/LfsProtocolServlet.java (revision 2161c1e5e4a53b3c56256389b3fc388b3c51d82d)
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