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