12e521446SShawn O. Pearce /* 2*5c5f7c6bSMatthias Sohn * Copyright (C) 2009-2010, Google Inc. and others 32e521446SShawn O. Pearce * 4*5c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 5*5c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 6*5c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 72e521446SShawn O. Pearce * 8*5c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 92e521446SShawn O. Pearce */ 102e521446SShawn O. Pearce 112e521446SShawn O. Pearce package org.eclipse.jgit.http.server; 122e521446SShawn O. Pearce 132e521446SShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; 142e521446SShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; 152e521446SShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; 162e521446SShawn O. Pearce import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; 17867087bbSShawn O. Pearce import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK; 18867087bbSShawn O. Pearce import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_REQUEST_TYPE; 19867087bbSShawn O. Pearce import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE; 20867087bbSShawn O. Pearce import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError; 21af3562f7SShawn O. Pearce import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; 22db00632dSShawn O. Pearce import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody; 232e521446SShawn O. Pearce import static org.eclipse.jgit.http.server.ServletUtils.getInputStream; 242e521446SShawn O. Pearce import static org.eclipse.jgit.http.server.ServletUtils.getRepository; 251c6c73c5SShawn O. Pearce import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; 262e521446SShawn O. Pearce 272e521446SShawn O. Pearce import java.io.IOException; 28ae592cc6SShawn Pearce import java.text.MessageFormat; 29af3562f7SShawn O. Pearce import java.util.List; 302e521446SShawn O. Pearce 31af3562f7SShawn O. Pearce import javax.servlet.Filter; 32af3562f7SShawn O. Pearce import javax.servlet.FilterChain; 33af3562f7SShawn O. Pearce import javax.servlet.FilterConfig; 34af3562f7SShawn O. Pearce import javax.servlet.ServletException; 35af3562f7SShawn O. Pearce import javax.servlet.ServletRequest; 36af3562f7SShawn O. Pearce import javax.servlet.ServletResponse; 372e521446SShawn O. Pearce import javax.servlet.http.HttpServlet; 382e521446SShawn O. Pearce import javax.servlet.http.HttpServletRequest; 392e521446SShawn O. Pearce import javax.servlet.http.HttpServletResponse; 40d72d6a6bSMasaya Suzuki 41abedaf0dSMasaya Suzuki import org.eclipse.jgit.annotations.Nullable; 42d72d6a6bSMasaya Suzuki import org.eclipse.jgit.errors.PackProtocolException; 43abedaf0dSMasaya Suzuki import org.eclipse.jgit.http.server.UploadPackErrorHandler.UploadPackRunnable; 442e521446SShawn O. Pearce import org.eclipse.jgit.lib.Repository; 454a984e20SShawn Pearce import org.eclipse.jgit.transport.InternalHttpServerGlue; 46f516c1dfSJonathan Tan import org.eclipse.jgit.transport.PacketLineOut; 472e521446SShawn O. Pearce import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; 48ae592cc6SShawn Pearce import org.eclipse.jgit.transport.ServiceMayNotContinueException; 49af3562f7SShawn O. Pearce import org.eclipse.jgit.transport.UploadPack; 507ff6eb58SShawn O. Pearce import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; 511b7a5a29SShawn O. Pearce import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; 521b7a5a29SShawn O. Pearce import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; 531b7a5a29SShawn O. Pearce import org.eclipse.jgit.transport.resolver.UploadPackFactory; 542e521446SShawn O. Pearce 552e521446SShawn O. Pearce /** Server side implementation of smart fetch over HTTP. */ 562e521446SShawn O. Pearce class UploadPackServlet extends HttpServlet { 572e521446SShawn O. Pearce private static final long serialVersionUID = 1L; 582e521446SShawn O. Pearce 592e521446SShawn O. Pearce static class InfoRefs extends SmartServiceInfoRefs { 60af3562f7SShawn O. Pearce private final UploadPackFactory<HttpServletRequest> uploadPackFactory; 612e521446SShawn O. Pearce InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory, List<Filter> filters)62af3562f7SShawn O. Pearce InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory, 63af3562f7SShawn O. Pearce List<Filter> filters) { 64867087bbSShawn O. Pearce super(UPLOAD_PACK, filters); 652e521446SShawn O. Pearce this.uploadPackFactory = uploadPackFactory; 662e521446SShawn O. Pearce } 672e521446SShawn O. Pearce 682e521446SShawn O. Pearce @Override begin(HttpServletRequest req, Repository db)69af3562f7SShawn O. Pearce protected void begin(HttpServletRequest req, Repository db) 70af3562f7SShawn O. Pearce throws IOException, ServiceNotEnabledException, 71af3562f7SShawn O. Pearce ServiceNotAuthorizedException { 72af3562f7SShawn O. Pearce UploadPack up = uploadPackFactory.create(req, db); 734a984e20SShawn Pearce InternalHttpServerGlue.setPeerUserAgent( 744a984e20SShawn Pearce up, 754a984e20SShawn Pearce req.getHeader(HDR_USER_AGENT)); 76af3562f7SShawn O. Pearce req.setAttribute(ATTRIBUTE_HANDLER, up); 77af3562f7SShawn O. Pearce } 78af3562f7SShawn O. Pearce 79af3562f7SShawn O. Pearce @Override advertise(HttpServletRequest req, PacketLineOutRefAdvertiser pck)80af3562f7SShawn O. Pearce protected void advertise(HttpServletRequest req, 812e521446SShawn O. Pearce PacketLineOutRefAdvertiser pck) throws IOException, 822e521446SShawn O. Pearce ServiceNotEnabledException, ServiceNotAuthorizedException { 83af3562f7SShawn O. Pearce UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); 84515deaf7SShawn O. Pearce try { 85b209671dSShawn O. Pearce up.setBiDirectionalPipe(false); 86515deaf7SShawn O. Pearce up.sendAdvertisedRefs(pck); 87515deaf7SShawn O. Pearce } finally { 88f516c1dfSJonathan Tan // TODO(jonathantanmy): Move responsibility for closing the 89f516c1dfSJonathan Tan // RevWalk to UploadPack, either by making it AutoCloseable 90f516c1dfSJonathan Tan // or by making sendAdvertisedRefs clean up after itself. 91f516c1dfSJonathan Tan up.getRevWalk().close(); 92f516c1dfSJonathan Tan } 93f516c1dfSJonathan Tan } 94f516c1dfSJonathan Tan 95f516c1dfSJonathan Tan @Override respond(HttpServletRequest req, PacketLineOut pckOut, String serviceName)96f516c1dfSJonathan Tan protected void respond(HttpServletRequest req, 97f516c1dfSJonathan Tan PacketLineOut pckOut, String serviceName) throws IOException, 98f516c1dfSJonathan Tan ServiceNotEnabledException, ServiceNotAuthorizedException { 99f516c1dfSJonathan Tan UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); 100f516c1dfSJonathan Tan try { 101f516c1dfSJonathan Tan up.setBiDirectionalPipe(false); 102f516c1dfSJonathan Tan up.sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut), serviceName); 103f516c1dfSJonathan Tan } finally { 104f516c1dfSJonathan Tan // TODO(jonathantanmy): Move responsibility for closing the 105f516c1dfSJonathan Tan // RevWalk to UploadPack, either by making it AutoCloseable 106f516c1dfSJonathan Tan // or by making sendAdvertisedRefs clean up after itself. 107686124beSMatthias Sohn up.getRevWalk().close(); 108515deaf7SShawn O. Pearce } 1092e521446SShawn O. Pearce } 1102e521446SShawn O. Pearce } 1112e521446SShawn O. Pearce 112af3562f7SShawn O. Pearce static class Factory implements Filter { 113af3562f7SShawn O. Pearce private final UploadPackFactory<HttpServletRequest> uploadPackFactory; 1142e521446SShawn O. Pearce Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory)115af3562f7SShawn O. Pearce Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory) { 1162e521446SShawn O. Pearce this.uploadPackFactory = uploadPackFactory; 1172e521446SShawn O. Pearce } 1182e521446SShawn O. Pearce 1197ac182f4SDavid Pursehouse @Override doFilter(ServletRequest request, ServletResponse response, FilterChain chain)120af3562f7SShawn O. Pearce public void doFilter(ServletRequest request, ServletResponse response, 121af3562f7SShawn O. Pearce FilterChain chain) throws IOException, ServletException { 122af3562f7SShawn O. Pearce HttpServletRequest req = (HttpServletRequest) request; 123af3562f7SShawn O. Pearce HttpServletResponse rsp = (HttpServletResponse) response; 124af3562f7SShawn O. Pearce UploadPack rp; 125af3562f7SShawn O. Pearce try { 126af3562f7SShawn O. Pearce rp = uploadPackFactory.create(req, getRepository(req)); 127af3562f7SShawn O. Pearce } catch (ServiceNotAuthorizedException e) { 128cc4f4f2fSJonathan Nieder rsp.sendError(SC_UNAUTHORIZED, e.getMessage()); 129af3562f7SShawn O. Pearce return; 130af3562f7SShawn O. Pearce } catch (ServiceNotEnabledException e) { 131cc4f4f2fSJonathan Nieder sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); 132af3562f7SShawn O. Pearce return; 133af3562f7SShawn O. Pearce } 134af3562f7SShawn O. Pearce 135af3562f7SShawn O. Pearce try { 136af3562f7SShawn O. Pearce req.setAttribute(ATTRIBUTE_HANDLER, rp); 137af3562f7SShawn O. Pearce chain.doFilter(req, rsp); 138af3562f7SShawn O. Pearce } finally { 139af3562f7SShawn O. Pearce req.removeAttribute(ATTRIBUTE_HANDLER); 140af3562f7SShawn O. Pearce } 141af3562f7SShawn O. Pearce } 142af3562f7SShawn O. Pearce 1437ac182f4SDavid Pursehouse @Override init(FilterConfig filterConfig)144af3562f7SShawn O. Pearce public void init(FilterConfig filterConfig) throws ServletException { 145af3562f7SShawn O. Pearce // Nothing. 146af3562f7SShawn O. Pearce } 147af3562f7SShawn O. Pearce 1487ac182f4SDavid Pursehouse @Override destroy()149af3562f7SShawn O. Pearce public void destroy() { 150af3562f7SShawn O. Pearce // Nothing. 151af3562f7SShawn O. Pearce } 152af3562f7SShawn O. Pearce } 153af3562f7SShawn O. Pearce 154abedaf0dSMasaya Suzuki private final UploadPackErrorHandler handler; 155abedaf0dSMasaya Suzuki UploadPackServlet(@ullable UploadPackErrorHandler handler)156abedaf0dSMasaya Suzuki UploadPackServlet(@Nullable UploadPackErrorHandler handler) { 157abedaf0dSMasaya Suzuki this.handler = handler != null ? handler 158abedaf0dSMasaya Suzuki : this::defaultUploadPackHandler; 159abedaf0dSMasaya Suzuki } 160abedaf0dSMasaya Suzuki 1613b00041cSMatthias Sohn /** {@inheritDoc} */ 1622e521446SShawn O. Pearce @Override doPost(HttpServletRequest req, HttpServletResponse rsp)163abedaf0dSMasaya Suzuki public void doPost(HttpServletRequest req, HttpServletResponse rsp) 164abedaf0dSMasaya Suzuki throws IOException { 165867087bbSShawn O. Pearce if (!UPLOAD_PACK_REQUEST_TYPE.equals(req.getContentType())) { 1662e521446SShawn O. Pearce rsp.sendError(SC_UNSUPPORTED_MEDIA_TYPE); 1672e521446SShawn O. Pearce return; 1682e521446SShawn O. Pearce } 1692e521446SShawn O. Pearce 170abedaf0dSMasaya Suzuki UploadPackRunnable r = () -> { 171abedaf0dSMasaya Suzuki UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); 172abedaf0dSMasaya Suzuki @SuppressWarnings("resource") 173f93a6a72SShawn O. Pearce SmartOutputStream out = new SmartOutputStream(req, rsp, false) { 174ccd0c0c9SShawn O. Pearce @Override 175ccd0c0c9SShawn O. Pearce public void flush() throws IOException { 176ccd0c0c9SShawn O. Pearce doFlush(); 177ccd0c0c9SShawn O. Pearce } 178ccd0c0c9SShawn O. Pearce }; 179ac6cda95SShawn O. Pearce 180ac6cda95SShawn O. Pearce up.setBiDirectionalPipe(false); 181ac6cda95SShawn O. Pearce rsp.setContentType(UPLOAD_PACK_RESULT_TYPE); 182ac6cda95SShawn O. Pearce 183abedaf0dSMasaya Suzuki try { 184d72d6a6bSMasaya Suzuki up.uploadWithExceptionPropagation(getInputStream(req), out, 185d72d6a6bSMasaya Suzuki null); 186c0f09389SShawn O. Pearce out.close(); 1871f2022e3SDave Borowitz } catch (ServiceMayNotContinueException e) { 188ac6cda95SShawn O. Pearce if (e.isOutput()) { 189db00632dSShawn O. Pearce consumeRequestBody(req); 190ac6cda95SShawn O. Pearce out.close(); 1917ff6eb58SShawn O. Pearce } 192abedaf0dSMasaya Suzuki throw e; 1937ff6eb58SShawn O. Pearce } catch (UploadPackInternalServerErrorException e) { 194867087bbSShawn O. Pearce // Special case exception, error message was sent to client. 195ae592cc6SShawn Pearce log(up.getRepository(), e.getCause()); 196db00632dSShawn O. Pearce consumeRequestBody(req); 197ac6cda95SShawn O. Pearce out.close(); 198abedaf0dSMasaya Suzuki } 199abedaf0dSMasaya Suzuki }; 2007ff6eb58SShawn O. Pearce 201abedaf0dSMasaya Suzuki handler.upload(req, rsp, r); 202abedaf0dSMasaya Suzuki } 203abedaf0dSMasaya Suzuki defaultUploadPackHandler(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r)204abedaf0dSMasaya Suzuki private void defaultUploadPackHandler(HttpServletRequest req, 205abedaf0dSMasaya Suzuki HttpServletResponse rsp, UploadPackRunnable r) throws IOException { 206abedaf0dSMasaya Suzuki try { 207abedaf0dSMasaya Suzuki r.upload(); 208abedaf0dSMasaya Suzuki } catch (ServiceMayNotContinueException e) { 209abedaf0dSMasaya Suzuki if (!e.isOutput() && !rsp.isCommitted()) { 210abedaf0dSMasaya Suzuki rsp.reset(); 211abedaf0dSMasaya Suzuki sendError(req, rsp, e.getStatusCode(), e.getMessage()); 212abedaf0dSMasaya Suzuki } 213ac6cda95SShawn O. Pearce } catch (Throwable e) { 214abedaf0dSMasaya Suzuki UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); 215ae592cc6SShawn Pearce log(up.getRepository(), e); 2167ff6eb58SShawn O. Pearce if (!rsp.isCommitted()) { 2172e521446SShawn O. Pearce rsp.reset(); 218d72d6a6bSMasaya Suzuki String msg = e instanceof PackProtocolException ? e.getMessage() 219d72d6a6bSMasaya Suzuki : null; 220d72d6a6bSMasaya Suzuki sendError(req, rsp, SC_INTERNAL_SERVER_ERROR, msg); 2217ff6eb58SShawn O. Pearce } 2222e521446SShawn O. Pearce } 2232e521446SShawn O. Pearce } 224ae592cc6SShawn Pearce log(Repository git, Throwable e)225ae592cc6SShawn Pearce private void log(Repository git, Throwable e) { 226ae592cc6SShawn Pearce getServletContext().log(MessageFormat.format( 227ae592cc6SShawn Pearce HttpServerText.get().internalErrorDuringUploadPack, 228ae592cc6SShawn Pearce ServletUtils.identify(git)), e); 229ae592cc6SShawn Pearce } 2302e521446SShawn O. Pearce } 231