1 /*
2 * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43 package org.eclipse.jgit.lfs.server;
44
45 import static java.nio.charset.StandardCharsets.UTF_8;
46 import static org.apache.http.HttpStatus.SC_FORBIDDEN;
47 import static org.apache.http.HttpStatus.SC_INSUFFICIENT_STORAGE;
48 import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
49 import static org.apache.http.HttpStatus.SC_NOT_FOUND;
50 import static org.apache.http.HttpStatus.SC_OK;
51 import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
52 import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
53 import static org.apache.http.HttpStatus.SC_UNPROCESSABLE_ENTITY;
54 import static org.eclipse.jgit.lfs.lib.Constants.DOWNLOAD;
55 import static org.eclipse.jgit.lfs.lib.Constants.UPLOAD;
56 import static org.eclipse.jgit.lfs.lib.Constants.VERIFY;
57 import static org.eclipse.jgit.util.HttpSupport.HDR_AUTHORIZATION;
58
59 import java.io.BufferedReader;
60 import java.io.BufferedWriter;
61 import java.io.IOException;
62 import java.io.InputStreamReader;
63 import java.io.OutputStreamWriter;
64 import java.io.Reader;
65 import java.io.Writer;
66 import java.text.MessageFormat;
67 import java.util.List;
68
69 import javax.servlet.ServletException;
70 import javax.servlet.http.HttpServlet;
71 import javax.servlet.http.HttpServletRequest;
72 import javax.servlet.http.HttpServletResponse;
73
74 import org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded;
75 import org.eclipse.jgit.lfs.errors.LfsException;
76 import org.eclipse.jgit.lfs.errors.LfsInsufficientStorage;
77 import org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded;
78 import org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound;
79 import org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly;
80 import org.eclipse.jgit.lfs.errors.LfsUnauthorized;
81 import org.eclipse.jgit.lfs.errors.LfsUnavailable;
82 import org.eclipse.jgit.lfs.errors.LfsValidationError;
83 import org.eclipse.jgit.lfs.internal.LfsText;
84 import org.eclipse.jgit.lfs.server.internal.LfsGson;
85 import org.slf4j.Logger;
86 import org.slf4j.LoggerFactory;
87
88 /**
89 * LFS protocol handler implementing the LFS batch API [1]
90 *
91 * [1] https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md
92 *
93 * @since 4.3
94 */
95 public abstract class LfsProtocolServlet extends HttpServlet {
96 private static Logger LOG = LoggerFactory
97 .getLogger(LfsProtocolServlet.class);
98
99 private static final long serialVersionUID = 1L;
100
101 private static final String CONTENTTYPE_VND_GIT_LFS_JSON =
102 "application/vnd.git-lfs+json; charset=utf-8"; //$NON-NLS-1$
103
104 private static final int SC_RATE_LIMIT_EXCEEDED = 429;
105
106 private static final int SC_BANDWIDTH_LIMIT_EXCEEDED = 509;
107
108 /**
109 * Get the large file repository for the given request and path.
110 *
111 * @param request
112 * the request
113 * @param path
114 * the path
115 * @return the large file repository storing large files.
116 * @throws org.eclipse.jgit.lfs.errors.LfsException
117 * implementations should throw more specific exceptions to
118 * signal which type of error occurred:
119 * <dl>
120 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsValidationError}</dt>
121 * <dd>when there is a validation error with one or more of the
122 * objects in the request</dd>
123 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}</dt>
124 * <dd>when the repository does not exist for the user</dd>
125 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}</dt>
126 * <dd>when the user has read, but not write access. Only
127 * applicable when the operation in the request is "upload"</dd>
128 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}</dt>
129 * <dd>when the user has hit a rate limit with the server</dd>
130 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}</dt>
131 * <dd>when the bandwidth limit for the user or repository has
132 * been exceeded</dd>
133 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}</dt>
134 * <dd>when there is insufficient storage on the server</dd>
135 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsUnavailable}</dt>
136 * <dd>when LFS is not available</dd>
137 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsException}</dt>
138 * <dd>when an unexpected internal server error occurred</dd>
139 * </dl>
140 * @since 4.5
141 * @deprecated use
142 * {@link #getLargeFileRepository(LfsRequest, String, String)}
143 */
144 @Deprecated
145 protected LargeFileRepository getLargeFileRepository(LfsRequest request,
146 String path) throws LfsException {
147 return getLargeFileRepository(request, path, null);
148 }
149
150 /**
151 * Get the large file repository for the given request and path.
152 *
153 * @param request
154 * the request
155 * @param path
156 * the path
157 * @param auth
158 * the Authorization HTTP header
159 * @return the large file repository storing large files.
160 * @throws org.eclipse.jgit.lfs.errors.LfsException
161 * implementations should throw more specific exceptions to
162 * signal which type of error occurred:
163 * <dl>
164 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsValidationError}</dt>
165 * <dd>when there is a validation error with one or more of the
166 * objects in the request</dd>
167 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryNotFound}</dt>
168 * <dd>when the repository does not exist for the user</dd>
169 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRepositoryReadOnly}</dt>
170 * <dd>when the user has read, but not write access. Only
171 * applicable when the operation in the request is "upload"</dd>
172 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsRateLimitExceeded}</dt>
173 * <dd>when the user has hit a rate limit with the server</dd>
174 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsBandwidthLimitExceeded}</dt>
175 * <dd>when the bandwidth limit for the user or repository has
176 * been exceeded</dd>
177 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsInsufficientStorage}</dt>
178 * <dd>when there is insufficient storage on the server</dd>
179 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsUnavailable}</dt>
180 * <dd>when LFS is not available</dd>
181 * <dt>{@link org.eclipse.jgit.lfs.errors.LfsException}</dt>
182 * <dd>when an unexpected internal server error occurred</dd>
183 * </dl>
184 * @since 4.7
185 */
186 protected abstract LargeFileRepository getLargeFileRepository(
187 LfsRequest request, String path, String auth) throws LfsException;
188
189 /**
190 * LFS request.
191 *
192 * @since 4.5
193 */
194 protected static class LfsRequest {
195 private String operation;
196
197 private List<LfsObject> objects;
198
199 /**
200 * Get the LFS operation.
201 *
202 * @return the operation
203 */
204 public String getOperation() {
205 return operation;
206 }
207
208 /**
209 * Get the LFS objects.
210 *
211 * @return the objects
212 */
213 public List<LfsObject> getObjects() {
214 return objects;
215 }
216
217 /**
218 * @return true if the operation is upload.
219 * @since 4.7
220 */
221 public boolean isUpload() {
222 return operation.equals(UPLOAD);
223 }
224
225 /**
226 * @return true if the operation is download.
227 * @since 4.7
228 */
229 public boolean isDownload() {
230 return operation.equals(DOWNLOAD);
231 }
232
233 /**
234 * @return true if the operation is verify.
235 * @since 4.7
236 */
237 public boolean isVerify() {
238 return operation.equals(VERIFY);
239 }
240 }
241
242 /** {@inheritDoc} */
243 @Override
244 protected void doPost(HttpServletRequest req, HttpServletResponse res)
245 throws ServletException, IOException {
246 Writer w = new BufferedWriter(
247 new OutputStreamWriter(res.getOutputStream(), UTF_8));
248
249 Reader r = new BufferedReader(
250 new InputStreamReader(req.getInputStream(), UTF_8));
251 LfsRequest request = LfsGson.fromJson(r, LfsRequest.class);
252 String path = req.getPathInfo();
253
254 res.setContentType(CONTENTTYPE_VND_GIT_LFS_JSON);
255 LargeFileRepository repo = null;
256 try {
257 repo = getLargeFileRepository(request, path,
258 req.getHeader(HDR_AUTHORIZATION));
259 if (repo == null) {
260 String error = MessageFormat
261 .format(LfsText.get().lfsFailedToGetRepository, path);
262 LOG.error(error);
263 throw new LfsException(error);
264 }
265 res.setStatus(SC_OK);
266 TransferHandler handler = TransferHandler
267 .forOperation(request.operation, repo, request.objects);
268 LfsGson.toJson(handler.process(), w);
269 } catch (LfsValidationError e) {
270 sendError(res, w, SC_UNPROCESSABLE_ENTITY, e.getMessage());
271 } catch (LfsRepositoryNotFound e) {
272 sendError(res, w, SC_NOT_FOUND, e.getMessage());
273 } catch (LfsRepositoryReadOnly e) {
274 sendError(res, w, SC_FORBIDDEN, e.getMessage());
275 } catch (LfsRateLimitExceeded e) {
276 sendError(res, w, SC_RATE_LIMIT_EXCEEDED, e.getMessage());
277 } catch (LfsBandwidthLimitExceeded e) {
278 sendError(res, w, SC_BANDWIDTH_LIMIT_EXCEEDED, e.getMessage());
279 } catch (LfsInsufficientStorage e) {
280 sendError(res, w, SC_INSUFFICIENT_STORAGE, e.getMessage());
281 } catch (LfsUnavailable e) {
282 sendError(res, w, SC_SERVICE_UNAVAILABLE, e.getMessage());
283 } catch (LfsUnauthorized e) {
284 sendError(res, w, SC_UNAUTHORIZED, e.getMessage());
285 } catch (LfsException e) {
286 sendError(res, w, SC_INTERNAL_SERVER_ERROR, e.getMessage());
287 } finally {
288 w.flush();
289 }
290 }
291
292 private void sendError(HttpServletResponse rsp, Writer writer, int status,
293 String message) {
294 rsp.setStatus(status);
295 LfsGson.toJson(message, writer);
296 }
297 }