xref: /JGit/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java (revision 5c5f7c6b146b24f2bd4afae1902df85ad6e57ea3)
1 /*
2  * Copyright (C) 2009-2010, Google Inc. 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 
11 package org.eclipse.jgit.http.server;
12 
13 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
14 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
15 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
16 import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
17 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK;
18 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_REQUEST_TYPE;
19 import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE;
20 import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
21 import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER;
22 import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
23 import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
24 import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
25 import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT;
26 
27 import java.io.IOException;
28 import java.text.MessageFormat;
29 import java.util.List;
30 
31 import javax.servlet.Filter;
32 import javax.servlet.FilterChain;
33 import javax.servlet.FilterConfig;
34 import javax.servlet.ServletException;
35 import javax.servlet.ServletRequest;
36 import javax.servlet.ServletResponse;
37 import javax.servlet.http.HttpServlet;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40 
41 import org.eclipse.jgit.annotations.Nullable;
42 import org.eclipse.jgit.errors.PackProtocolException;
43 import org.eclipse.jgit.http.server.UploadPackErrorHandler.UploadPackRunnable;
44 import org.eclipse.jgit.lib.Repository;
45 import org.eclipse.jgit.transport.InternalHttpServerGlue;
46 import org.eclipse.jgit.transport.PacketLineOut;
47 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
48 import org.eclipse.jgit.transport.ServiceMayNotContinueException;
49 import org.eclipse.jgit.transport.UploadPack;
50 import org.eclipse.jgit.transport.UploadPackInternalServerErrorException;
51 import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
52 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
53 import org.eclipse.jgit.transport.resolver.UploadPackFactory;
54 
55 /** Server side implementation of smart fetch over HTTP. */
56 class UploadPackServlet extends HttpServlet {
57 	private static final long serialVersionUID = 1L;
58 
59 	static class InfoRefs extends SmartServiceInfoRefs {
60 		private final UploadPackFactory<HttpServletRequest> uploadPackFactory;
61 
InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory, List<Filter> filters)62 		InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory,
63 				List<Filter> filters) {
64 			super(UPLOAD_PACK, filters);
65 			this.uploadPackFactory = uploadPackFactory;
66 		}
67 
68 		@Override
begin(HttpServletRequest req, Repository db)69 		protected void begin(HttpServletRequest req, Repository db)
70 				throws IOException, ServiceNotEnabledException,
71 				ServiceNotAuthorizedException {
72 			UploadPack up = uploadPackFactory.create(req, db);
73 			InternalHttpServerGlue.setPeerUserAgent(
74 					up,
75 					req.getHeader(HDR_USER_AGENT));
76 			req.setAttribute(ATTRIBUTE_HANDLER, up);
77 		}
78 
79 		@Override
advertise(HttpServletRequest req, PacketLineOutRefAdvertiser pck)80 		protected void advertise(HttpServletRequest req,
81 				PacketLineOutRefAdvertiser pck) throws IOException,
82 				ServiceNotEnabledException, ServiceNotAuthorizedException {
83 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
84 			try {
85 				up.setBiDirectionalPipe(false);
86 				up.sendAdvertisedRefs(pck);
87 			} finally {
88 				// TODO(jonathantanmy): Move responsibility for closing the
89 				// RevWalk to UploadPack, either by making it AutoCloseable
90 				// or by making sendAdvertisedRefs clean up after itself.
91 				up.getRevWalk().close();
92 			}
93 		}
94 
95 		@Override
respond(HttpServletRequest req, PacketLineOut pckOut, String serviceName)96 		protected void respond(HttpServletRequest req,
97 				PacketLineOut pckOut, String serviceName) throws IOException,
98 				ServiceNotEnabledException, ServiceNotAuthorizedException {
99 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
100 			try {
101 				up.setBiDirectionalPipe(false);
102 				up.sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut), serviceName);
103 			} finally {
104 				// TODO(jonathantanmy): Move responsibility for closing the
105 				// RevWalk to UploadPack, either by making it AutoCloseable
106 				// or by making sendAdvertisedRefs clean up after itself.
107 				up.getRevWalk().close();
108 			}
109 		}
110 	}
111 
112 	static class Factory implements Filter {
113 		private final UploadPackFactory<HttpServletRequest> uploadPackFactory;
114 
Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory)115 		Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory) {
116 			this.uploadPackFactory = uploadPackFactory;
117 		}
118 
119 		@Override
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)120 		public void doFilter(ServletRequest request, ServletResponse response,
121 				FilterChain chain) throws IOException, ServletException {
122 			HttpServletRequest req = (HttpServletRequest) request;
123 			HttpServletResponse rsp = (HttpServletResponse) response;
124 			UploadPack rp;
125 			try {
126 				rp = uploadPackFactory.create(req, getRepository(req));
127 			} catch (ServiceNotAuthorizedException e) {
128 				rsp.sendError(SC_UNAUTHORIZED, e.getMessage());
129 				return;
130 			} catch (ServiceNotEnabledException e) {
131 				sendError(req, rsp, SC_FORBIDDEN, e.getMessage());
132 				return;
133 			}
134 
135 			try {
136 				req.setAttribute(ATTRIBUTE_HANDLER, rp);
137 				chain.doFilter(req, rsp);
138 			} finally {
139 				req.removeAttribute(ATTRIBUTE_HANDLER);
140 			}
141 		}
142 
143 		@Override
init(FilterConfig filterConfig)144 		public void init(FilterConfig filterConfig) throws ServletException {
145 			// Nothing.
146 		}
147 
148 		@Override
destroy()149 		public void destroy() {
150 			// Nothing.
151 		}
152 	}
153 
154 	private final UploadPackErrorHandler handler;
155 
UploadPackServlet(@ullable UploadPackErrorHandler handler)156 	UploadPackServlet(@Nullable UploadPackErrorHandler handler) {
157 		this.handler = handler != null ? handler
158 				: this::defaultUploadPackHandler;
159 	}
160 
161 	/** {@inheritDoc} */
162 	@Override
doPost(HttpServletRequest req, HttpServletResponse rsp)163 	public void doPost(HttpServletRequest req, HttpServletResponse rsp)
164 			throws IOException {
165 		if (!UPLOAD_PACK_REQUEST_TYPE.equals(req.getContentType())) {
166 			rsp.sendError(SC_UNSUPPORTED_MEDIA_TYPE);
167 			return;
168 		}
169 
170 		UploadPackRunnable r = () -> {
171 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
172 			@SuppressWarnings("resource")
173 			SmartOutputStream out = new SmartOutputStream(req, rsp, false) {
174 				@Override
175 				public void flush() throws IOException {
176 					doFlush();
177 				}
178 			};
179 
180 			up.setBiDirectionalPipe(false);
181 			rsp.setContentType(UPLOAD_PACK_RESULT_TYPE);
182 
183 			try {
184 				up.uploadWithExceptionPropagation(getInputStream(req), out,
185 						null);
186 				out.close();
187 			} catch (ServiceMayNotContinueException e) {
188 				if (e.isOutput()) {
189 					consumeRequestBody(req);
190 					out.close();
191 				}
192 				throw e;
193 			} catch (UploadPackInternalServerErrorException e) {
194 				// Special case exception, error message was sent to client.
195 				log(up.getRepository(), e.getCause());
196 				consumeRequestBody(req);
197 				out.close();
198 			}
199 		};
200 
201 		handler.upload(req, rsp, r);
202 	}
203 
defaultUploadPackHandler(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r)204 	private void defaultUploadPackHandler(HttpServletRequest req,
205 			HttpServletResponse rsp, UploadPackRunnable r) throws IOException {
206 		try {
207 			r.upload();
208 		} catch (ServiceMayNotContinueException e) {
209 			if (!e.isOutput() && !rsp.isCommitted()) {
210 				rsp.reset();
211 				sendError(req, rsp, e.getStatusCode(), e.getMessage());
212 			}
213 		} catch (Throwable e) {
214 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
215 			log(up.getRepository(), e);
216 			if (!rsp.isCommitted()) {
217 				rsp.reset();
218 				String msg = e instanceof PackProtocolException ? e.getMessage()
219 						: null;
220 				sendError(req, rsp, SC_INTERNAL_SERVER_ERROR, msg);
221 			}
222 		}
223 	}
224 
log(Repository git, Throwable e)225 	private void log(Repository git, Throwable e) {
226 		getServletContext().log(MessageFormat.format(
227 				HttpServerText.get().internalErrorDuringUploadPack,
228 				ServletUtils.identify(git)), e);
229 	}
230 }
231