View Javadoc
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_UNAUTHORIZED;
15  import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE;
16  import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK;
17  import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_REQUEST_TYPE;
18  import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE;
19  import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
20  import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER;
21  import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody;
22  import static org.eclipse.jgit.http.server.ServletUtils.getInputStream;
23  import static org.eclipse.jgit.http.server.ServletUtils.getRepository;
24  import static org.eclipse.jgit.http.server.UploadPackErrorHandler.statusCodeForThrowable;
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  
62  		InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory,
63  				List<Filter> filters) {
64  			super(UPLOAD_PACK, filters);
65  			this.uploadPackFactory = uploadPackFactory;
66  		}
67  
68  		@Override
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
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
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 
115 		Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory) {
116 			this.uploadPackFactory = uploadPackFactory;
117 		}
118 
119 		@Override
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
144 		public void init(FilterConfig filterConfig) throws ServletException {
145 			// Nothing.
146 		}
147 
148 		@Override
149 		public void destroy() {
150 			// Nothing.
151 		}
152 	}
153 
154 	private final UploadPackErrorHandler handler;
155 
156 	UploadPackServlet(@Nullable UploadPackErrorHandler handler) {
157 		this.handler = handler != null ? handler
158 				: this::defaultUploadPackHandler;
159 	}
160 
161 	/** {@inheritDoc} */
162 	@Override
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 			upload(req, rsp);
172 		};
173 
174 		handler.upload(req, rsp, r);
175 	}
176 
177 	private void upload(HttpServletRequest req, HttpServletResponse rsp)
178 			throws IOException, ServiceMayNotContinueException {
179 		// to be explicitly closed by caller
180 		@SuppressWarnings("resource")
181 		SmartOutputStream out = new SmartOutputStream(req, rsp, false) {
182 			@Override
183 			public void flush() throws IOException {
184 				doFlush();
185 			}
186 		};
187 		Repository repo = null;
188 		try (UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER)) {
189 			up.setBiDirectionalPipe(false);
190 			rsp.setContentType(UPLOAD_PACK_RESULT_TYPE);
191 			repo = up.getRepository();
192 			up.uploadWithExceptionPropagation(getInputStream(req), out, null);
193 			out.close();
194 		} catch (ServiceMayNotContinueException e) {
195 			if (e.isOutput()) {
196 				consumeRequestBody(req);
197 				out.close();
198 			}
199 			throw e;
200 		} catch (UploadPackInternalServerErrorException e) {
201 			// Special case exception, error message was sent to client.
202 			log(repo, e.getCause());
203 			consumeRequestBody(req);
204 			out.close();
205 		}
206 	}
207 
208 	private void defaultUploadPackHandler(HttpServletRequest req,
209 			HttpServletResponse rsp, UploadPackRunnable r) throws IOException {
210 		try {
211 			r.upload();
212 		} catch (ServiceMayNotContinueException e) {
213 			if (!e.isOutput() && !rsp.isCommitted()) {
214 				rsp.reset();
215 				sendError(req, rsp, e.getStatusCode(), e.getMessage());
216 			}
217 		} catch (Throwable e) {
218 			UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER);
219 			log(up.getRepository(), e);
220 			if (!rsp.isCommitted()) {
221 				rsp.reset();
222 				String msg = null;
223 				if (e instanceof PackProtocolException
224 						|| e instanceof ServiceNotEnabledException) {
225 					msg = e.getMessage();
226 				}
227 				sendError(req, rsp, statusCodeForThrowable(e), msg);
228 			}
229 		}
230 	}
231 
232 	private void log(Repository git, Throwable e) {
233 		getServletContext().log(MessageFormat.format(
234 				HttpServerText.get().internalErrorDuringUploadPack,
235 				ServletUtils.identify(git)), e);
236 	}
237 }