| /* |
| * Copyright (C) 2009-2010, Google Inc. and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.eclipse.jgit.http.server; |
| |
| import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN; |
| import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; |
| import static jakarta.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; |
| import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK; |
| import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_REQUEST_TYPE; |
| import static org.eclipse.jgit.http.server.GitSmartHttpTools.UPLOAD_PACK_RESULT_TYPE; |
| import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError; |
| import static org.eclipse.jgit.http.server.ServletUtils.ATTRIBUTE_HANDLER; |
| import static org.eclipse.jgit.http.server.ServletUtils.consumeRequestBody; |
| import static org.eclipse.jgit.http.server.ServletUtils.getInputStream; |
| import static org.eclipse.jgit.http.server.ServletUtils.getRepository; |
| import static org.eclipse.jgit.http.server.UploadPackErrorHandler.statusCodeForThrowable; |
| import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; |
| |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| import java.util.List; |
| |
| import jakarta.servlet.Filter; |
| import jakarta.servlet.FilterChain; |
| import jakarta.servlet.FilterConfig; |
| import jakarta.servlet.ServletException; |
| import jakarta.servlet.ServletRequest; |
| import jakarta.servlet.ServletResponse; |
| import jakarta.servlet.http.HttpServlet; |
| import jakarta.servlet.http.HttpServletRequest; |
| import jakarta.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jgit.annotations.Nullable; |
| import org.eclipse.jgit.errors.PackProtocolException; |
| import org.eclipse.jgit.http.server.UploadPackErrorHandler.UploadPackRunnable; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.transport.InternalHttpServerGlue; |
| import org.eclipse.jgit.transport.PacketLineOut; |
| import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; |
| import org.eclipse.jgit.transport.ServiceMayNotContinueException; |
| import org.eclipse.jgit.transport.UploadPack; |
| import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; |
| import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; |
| import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; |
| import org.eclipse.jgit.transport.resolver.UploadPackFactory; |
| |
| /** Server side implementation of smart fetch over HTTP. */ |
| class UploadPackServlet extends HttpServlet { |
| private static final long serialVersionUID = 1L; |
| |
| static class InfoRefs extends SmartServiceInfoRefs { |
| private final UploadPackFactory<HttpServletRequest> uploadPackFactory; |
| |
| InfoRefs(UploadPackFactory<HttpServletRequest> uploadPackFactory, |
| List<Filter> filters) { |
| super(UPLOAD_PACK, filters); |
| this.uploadPackFactory = uploadPackFactory; |
| } |
| |
| @Override |
| protected void begin(HttpServletRequest req, Repository db) |
| throws IOException, ServiceNotEnabledException, |
| ServiceNotAuthorizedException { |
| UploadPack up = uploadPackFactory.create(req, db); |
| InternalHttpServerGlue.setPeerUserAgent( |
| up, |
| req.getHeader(HDR_USER_AGENT)); |
| req.setAttribute(ATTRIBUTE_HANDLER, up); |
| } |
| |
| @Override |
| protected void advertise(HttpServletRequest req, |
| PacketLineOutRefAdvertiser pck) throws IOException, |
| ServiceNotEnabledException, ServiceNotAuthorizedException { |
| UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); |
| try { |
| up.setBiDirectionalPipe(false); |
| up.sendAdvertisedRefs(pck); |
| } finally { |
| // TODO(jonathantanmy): Move responsibility for closing the |
| // RevWalk to UploadPack, either by making it AutoCloseable |
| // or by making sendAdvertisedRefs clean up after itself. |
| up.getRevWalk().close(); |
| } |
| } |
| |
| @Override |
| protected void respond(HttpServletRequest req, |
| PacketLineOut pckOut, String serviceName) throws IOException, |
| ServiceNotEnabledException, ServiceNotAuthorizedException { |
| UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); |
| try { |
| up.setBiDirectionalPipe(false); |
| up.sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut), serviceName); |
| } finally { |
| // TODO(jonathantanmy): Move responsibility for closing the |
| // RevWalk to UploadPack, either by making it AutoCloseable |
| // or by making sendAdvertisedRefs clean up after itself. |
| up.getRevWalk().close(); |
| } |
| } |
| } |
| |
| static class Factory implements Filter { |
| private final UploadPackFactory<HttpServletRequest> uploadPackFactory; |
| |
| Factory(UploadPackFactory<HttpServletRequest> uploadPackFactory) { |
| this.uploadPackFactory = uploadPackFactory; |
| } |
| |
| @Override |
| public void doFilter(ServletRequest request, ServletResponse response, |
| FilterChain chain) throws IOException, ServletException { |
| HttpServletRequest req = (HttpServletRequest) request; |
| HttpServletResponse rsp = (HttpServletResponse) response; |
| UploadPack rp; |
| try { |
| rp = uploadPackFactory.create(req, getRepository(req)); |
| } catch (ServiceNotAuthorizedException e) { |
| rsp.sendError(SC_UNAUTHORIZED, e.getMessage()); |
| return; |
| } catch (ServiceNotEnabledException e) { |
| sendError(req, rsp, SC_FORBIDDEN, e.getMessage()); |
| return; |
| } |
| |
| try { |
| req.setAttribute(ATTRIBUTE_HANDLER, rp); |
| chain.doFilter(req, rsp); |
| } finally { |
| req.removeAttribute(ATTRIBUTE_HANDLER); |
| } |
| } |
| |
| @Override |
| public void init(FilterConfig filterConfig) throws ServletException { |
| // Nothing. |
| } |
| |
| @Override |
| public void destroy() { |
| // Nothing. |
| } |
| } |
| |
| private final UploadPackErrorHandler handler; |
| |
| UploadPackServlet(@Nullable UploadPackErrorHandler handler) { |
| this.handler = handler != null ? handler |
| : this::defaultUploadPackHandler; |
| } |
| |
| @Override |
| public void doPost(HttpServletRequest req, HttpServletResponse rsp) |
| throws IOException { |
| if (!UPLOAD_PACK_REQUEST_TYPE.equals(req.getContentType())) { |
| rsp.sendError(SC_UNSUPPORTED_MEDIA_TYPE); |
| return; |
| } |
| |
| UploadPackRunnable r = () -> { |
| upload(req, rsp); |
| }; |
| |
| handler.upload(req, rsp, r); |
| } |
| |
| private void upload(HttpServletRequest req, HttpServletResponse rsp) |
| throws IOException, ServiceMayNotContinueException { |
| // to be explicitly closed by caller |
| @SuppressWarnings("resource") |
| SmartOutputStream out = new SmartOutputStream(req, rsp, false) { |
| @Override |
| public void flush() throws IOException { |
| doFlush(); |
| } |
| }; |
| Repository repo = null; |
| try (UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER)) { |
| up.setBiDirectionalPipe(false); |
| rsp.setContentType(UPLOAD_PACK_RESULT_TYPE); |
| repo = up.getRepository(); |
| up.uploadWithExceptionPropagation(getInputStream(req), out, null); |
| out.close(); |
| } catch (ServiceMayNotContinueException e) { |
| if (e.isOutput()) { |
| consumeRequestBody(req); |
| out.close(); |
| } |
| throw e; |
| } catch (UploadPackInternalServerErrorException e) { |
| // Special case exception, error message was sent to client. |
| log(repo, e.getCause()); |
| consumeRequestBody(req); |
| out.close(); |
| } |
| } |
| |
| private void defaultUploadPackHandler(HttpServletRequest req, |
| HttpServletResponse rsp, UploadPackRunnable r) throws IOException { |
| try { |
| r.upload(); |
| } catch (ServiceMayNotContinueException e) { |
| if (!e.isOutput() && !rsp.isCommitted()) { |
| rsp.reset(); |
| sendError(req, rsp, e.getStatusCode(), e.getMessage()); |
| } |
| } catch (Throwable e) { |
| UploadPack up = (UploadPack) req.getAttribute(ATTRIBUTE_HANDLER); |
| log(up.getRepository(), e); |
| if (!rsp.isCommitted()) { |
| rsp.reset(); |
| String msg = null; |
| if (e instanceof PackProtocolException |
| || e instanceof ServiceNotEnabledException) { |
| msg = e.getMessage(); |
| } |
| sendError(req, rsp, statusCodeForThrowable(e), msg); |
| } |
| } |
| } |
| |
| private void log(Repository git, Throwable e) { |
| getServletContext().log(MessageFormat.format( |
| HttpServerText.get().internalErrorDuringUploadPack, |
| ServletUtils.identify(git)), e); |
| } |
| } |