LCOV - code coverage report
Current view: top level - httpd - GitOverHttpServlet.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 239 277 86.3 %
Date: 2022-11-19 15:00:39 Functions: 40 46 87.0 %

          Line data    Source code
       1             : // Copyright (C) 2010 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.httpd;
      16             : 
      17             : import static org.eclipse.jgit.http.server.GitSmartHttpTools.sendError;
      18             : 
      19             : import com.google.common.annotations.VisibleForTesting;
      20             : import com.google.common.cache.Cache;
      21             : import com.google.common.collect.ImmutableListMultimap;
      22             : import com.google.common.collect.ListMultimap;
      23             : import com.google.common.collect.Lists;
      24             : import com.google.common.flogger.FluentLogger;
      25             : import com.google.gerrit.common.Nullable;
      26             : import com.google.gerrit.common.data.Capable;
      27             : import com.google.gerrit.entities.Project;
      28             : import com.google.gerrit.extensions.registration.DynamicSet;
      29             : import com.google.gerrit.extensions.restapi.AuthException;
      30             : import com.google.gerrit.server.AccessPath;
      31             : import com.google.gerrit.server.AnonymousUser;
      32             : import com.google.gerrit.server.CurrentUser;
      33             : import com.google.gerrit.server.RequestInfo;
      34             : import com.google.gerrit.server.RequestListener;
      35             : import com.google.gerrit.server.audit.HttpAuditEvent;
      36             : import com.google.gerrit.server.cache.CacheModule;
      37             : import com.google.gerrit.server.git.GitRepositoryManager;
      38             : import com.google.gerrit.server.git.PermissionAwareRepositoryManager;
      39             : import com.google.gerrit.server.git.TracingHook;
      40             : import com.google.gerrit.server.git.TransferConfig;
      41             : import com.google.gerrit.server.git.UploadPackInitializer;
      42             : import com.google.gerrit.server.git.UsersSelfAdvertiseRefsHook;
      43             : import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
      44             : import com.google.gerrit.server.git.validators.UploadValidators;
      45             : import com.google.gerrit.server.group.GroupAuditService;
      46             : import com.google.gerrit.server.logging.TraceContext;
      47             : import com.google.gerrit.server.permissions.PermissionBackend;
      48             : import com.google.gerrit.server.permissions.PermissionBackendException;
      49             : import com.google.gerrit.server.permissions.ProjectPermission;
      50             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      51             : import com.google.gerrit.server.project.ProjectCache;
      52             : import com.google.gerrit.server.project.ProjectState;
      53             : import com.google.gerrit.server.util.time.TimeUtil;
      54             : import com.google.inject.AbstractModule;
      55             : import com.google.inject.Inject;
      56             : import com.google.inject.Provider;
      57             : import com.google.inject.Singleton;
      58             : import com.google.inject.TypeLiteral;
      59             : import com.google.inject.name.Named;
      60             : import java.io.IOException;
      61             : import java.text.MessageFormat;
      62             : import java.time.Duration;
      63             : import java.util.Arrays;
      64             : import java.util.Collections;
      65             : import java.util.HashSet;
      66             : import java.util.Map;
      67             : import java.util.Set;
      68             : import java.util.concurrent.atomic.AtomicLong;
      69             : import javax.servlet.Filter;
      70             : import javax.servlet.FilterChain;
      71             : import javax.servlet.FilterConfig;
      72             : import javax.servlet.ServletException;
      73             : import javax.servlet.ServletRequest;
      74             : import javax.servlet.ServletResponse;
      75             : import javax.servlet.http.HttpServletRequest;
      76             : import javax.servlet.http.HttpServletResponse;
      77             : import javax.servlet.http.HttpServletResponseWrapper;
      78             : import org.eclipse.jgit.errors.PackProtocolException;
      79             : import org.eclipse.jgit.errors.RepositoryNotFoundException;
      80             : import org.eclipse.jgit.http.server.GitServlet;
      81             : import org.eclipse.jgit.http.server.GitSmartHttpTools;
      82             : import org.eclipse.jgit.http.server.HttpServerText;
      83             : import org.eclipse.jgit.http.server.ServletUtils;
      84             : import org.eclipse.jgit.http.server.UploadPackErrorHandler;
      85             : import org.eclipse.jgit.http.server.resolver.AsIsFileService;
      86             : import org.eclipse.jgit.lib.ObjectId;
      87             : import org.eclipse.jgit.lib.Repository;
      88             : import org.eclipse.jgit.transport.PostUploadHook;
      89             : import org.eclipse.jgit.transport.PostUploadHookChain;
      90             : import org.eclipse.jgit.transport.PreUploadHook;
      91             : import org.eclipse.jgit.transport.PreUploadHookChain;
      92             : import org.eclipse.jgit.transport.ReceivePack;
      93             : import org.eclipse.jgit.transport.ServiceMayNotContinueException;
      94             : import org.eclipse.jgit.transport.UploadPack;
      95             : import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
      96             : import org.eclipse.jgit.transport.resolver.RepositoryResolver;
      97             : import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
      98             : import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
      99             : import org.eclipse.jgit.transport.resolver.UploadPackFactory;
     100             : 
     101             : /** Serves Git repositories over HTTP. */
     102             : @Singleton
     103             : public class GitOverHttpServlet extends GitServlet {
     104             :   private static final long serialVersionUID = 1L;
     105             : 
     106          99 :   private static final String ATT_STATE = ProjectState.class.getName();
     107          99 :   private static final String ATT_ARC = AsyncReceiveCommits.class.getName();
     108             :   private static final String ID_CACHE = "adv_bases";
     109             : 
     110             :   public static final String URL_REGEX;
     111             :   public static final String GIT_COMMAND_STATUS_HEADER = "X-git-command-status";
     112             : 
     113           8 :   private enum GIT_COMMAND_STATUS {
     114           8 :     OK(0),
     115           8 :     FAIL(-1);
     116             : 
     117             :     private final int exitStatus;
     118             : 
     119           8 :     GIT_COMMAND_STATUS(int exitStatus) {
     120           8 :       this.exitStatus = exitStatus;
     121           8 :     }
     122             : 
     123             :     @Override
     124             :     public String toString() {
     125           8 :       return Integer.toString(exitStatus);
     126             :     }
     127             :   }
     128             : 
     129             :   static {
     130          99 :     StringBuilder url = new StringBuilder();
     131          99 :     url.append("^(?:/a)?(?:/p/|/)(.*/(?:info/refs");
     132          99 :     for (String name : GitSmartHttpTools.VALID_SERVICES) {
     133          99 :       url.append('|').append(name);
     134          99 :     }
     135          99 :     url.append("))$");
     136          99 :     URL_REGEX = url.toString();
     137          99 :   }
     138             : 
     139             :   static class GitOverHttpServletModule extends AbstractModule {
     140             : 
     141             :     private final boolean enableReceive;
     142             : 
     143          99 :     GitOverHttpServletModule(boolean enableReceive) {
     144          99 :       this.enableReceive = enableReceive;
     145          99 :     }
     146             : 
     147             :     @Override
     148             :     protected void configure() {
     149          99 :       bind(Resolver.class);
     150          99 :       bind(UploadFactory.class);
     151          99 :       bind(UploadFilter.class);
     152          99 :       bind(new TypeLiteral<ReceivePackFactory<HttpServletRequest>>() {})
     153          99 :           .to(enableReceive ? ReceiveFactory.class : DisabledReceiveFactory.class);
     154          99 :       bind(ReceiveFilter.class);
     155          99 :       install(
     156          99 :           new CacheModule() {
     157             :             @Override
     158             :             protected void configure() {
     159          99 :               cache(ID_CACHE, AdvertisedObjectsCacheKey.class, new TypeLiteral<Set<ObjectId>>() {})
     160          99 :                   .maximumWeight(4096)
     161          99 :                   .expireAfterWrite(Duration.ofMinutes(10));
     162          99 :             }
     163             :           });
     164             : 
     165             :       // Don't bind Metrics, which is bound in a parent injector in tests.
     166          99 :     }
     167             :   }
     168             : 
     169             :   @VisibleForTesting
     170             :   @Singleton
     171         138 :   public static class Metrics {
     172             :     // Recording requests separately in this class is only necessary because of a bug in the
     173             :     // implementation of the generic RequestMetricsFilter; see
     174             :     // https://gerrit-review.googlesource.com/c/gerrit/+/211692
     175         138 :     private final AtomicLong requestsStarted = new AtomicLong();
     176             : 
     177             :     void requestStarted() {
     178           8 :       requestsStarted.incrementAndGet();
     179           8 :     }
     180             : 
     181             :     public long getRequestsStarted() {
     182           2 :       return requestsStarted.get();
     183             :     }
     184             :   }
     185             : 
     186             :   static class HttpServletResponseWithStatusWrapper extends HttpServletResponseWrapper {
     187             :     private int responseStatus;
     188             : 
     189             :     HttpServletResponseWithStatusWrapper(HttpServletResponse response) {
     190           8 :       super(response);
     191             :       /* Even if we could read the status from response, we assume that it is all
     192             :        * fine because we entered the filter without any prior issues.
     193             :        * When Google will have upgraded to Servlet 3.0, we could actually
     194             :        * call response.getStatus() and the code will be clearer.
     195             :        */
     196           8 :       responseStatus = HttpServletResponse.SC_OK;
     197           8 :     }
     198             : 
     199             :     @Override
     200             :     public void setStatus(int sc) {
     201           3 :       responseStatus = sc;
     202           3 :       super.setStatus(sc);
     203           3 :     }
     204             : 
     205             :     @SuppressWarnings("deprecation")
     206             :     @Override
     207             :     public void setStatus(int sc, String sm) {
     208           0 :       responseStatus = sc;
     209           0 :       super.setStatus(sc, sm);
     210           0 :     }
     211             : 
     212             :     @Override
     213             :     public void sendError(int sc) throws IOException {
     214           0 :       this.responseStatus = sc;
     215           0 :       super.sendError(sc);
     216           0 :     }
     217             : 
     218             :     @Override
     219             :     public void sendError(int sc, String msg) throws IOException {
     220           0 :       this.responseStatus = sc;
     221           0 :       super.sendError(sc, msg);
     222           0 :     }
     223             : 
     224             :     @Override
     225             :     public void sendRedirect(String location) throws IOException {
     226           0 :       this.responseStatus = HttpServletResponse.SC_MOVED_TEMPORARILY;
     227           0 :       super.sendRedirect(location);
     228           0 :     }
     229             : 
     230             :     public int getResponseStatus() {
     231           8 :       return responseStatus;
     232             :     }
     233             :   }
     234             : 
     235             :   @Inject
     236             :   GitOverHttpServlet(
     237             :       Resolver resolver,
     238             :       UploadFactory upload,
     239             :       UploadFilter uploadFilter,
     240             :       GerritUploadPackErrorHandler uploadPackErrorHandler,
     241             :       ReceivePackFactory<HttpServletRequest> receive,
     242          99 :       ReceiveFilter receiveFilter) {
     243          99 :     setRepositoryResolver(resolver);
     244          99 :     setAsIsFileService(AsIsFileService.DISABLED);
     245             : 
     246          99 :     setUploadPackFactory(upload);
     247          99 :     setUploadPackErrorHandler(uploadPackErrorHandler);
     248          99 :     addUploadPackFilter(uploadFilter);
     249             : 
     250          99 :     setReceivePackFactory(receive);
     251          99 :     addReceivePackFilter(receiveFilter);
     252          99 :   }
     253             : 
     254             :   private static String extractWhat(HttpServletRequest request) {
     255           8 :     StringBuilder commandName = new StringBuilder(request.getRequestURL());
     256           8 :     if (request.getQueryString() != null) {
     257           8 :       commandName.append("?").append(request.getQueryString());
     258             :     }
     259           8 :     return commandName.toString();
     260             :   }
     261             : 
     262             :   private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
     263           8 :     if (request.getQueryString() == null) {
     264           8 :       return ImmutableListMultimap.of();
     265             :     }
     266             :     // Explicit cast is required to compile under Servlet API 2.5, where the return type is raw Map.
     267             :     @SuppressWarnings("cast")
     268           8 :     Map<String, String[]> parameterMap = (Map<String, String[]>) request.getParameterMap();
     269           8 :     ImmutableListMultimap.Builder<String, String> b = ImmutableListMultimap.builder();
     270           8 :     parameterMap.forEach(b::putAll);
     271           8 :     return b.build();
     272             :   }
     273             : 
     274             :   static class Resolver implements RepositoryResolver<HttpServletRequest> {
     275             :     private final GitRepositoryManager manager;
     276             :     private final PermissionBackend permissionBackend;
     277             :     private final Provider<CurrentUser> userProvider;
     278             :     private final ProjectCache projectCache;
     279             : 
     280             :     @Inject
     281             :     Resolver(
     282             :         GitRepositoryManager manager,
     283             :         PermissionBackend permissionBackend,
     284             :         Provider<CurrentUser> userProvider,
     285          99 :         ProjectCache projectCache) {
     286          99 :       this.manager = manager;
     287          99 :       this.permissionBackend = permissionBackend;
     288          99 :       this.userProvider = userProvider;
     289          99 :       this.projectCache = projectCache;
     290          99 :     }
     291             : 
     292             :     @Override
     293             :     public Repository open(HttpServletRequest req, String projectName)
     294             :         throws RepositoryNotFoundException, ServiceNotAuthorizedException,
     295             :             ServiceNotEnabledException, ServiceMayNotContinueException {
     296           8 :       while (projectName.endsWith("/")) {
     297           0 :         projectName = projectName.substring(0, projectName.length() - 1);
     298             :       }
     299             : 
     300           8 :       if (projectName.endsWith(".git")) {
     301             :         // Be nice and drop the trailing ".git" suffix, which we never keep
     302             :         // in our database, but clients might mistakenly provide anyway.
     303             :         //
     304           0 :         projectName = projectName.substring(0, projectName.length() - 4);
     305           0 :         while (projectName.endsWith("/")) {
     306           0 :           projectName = projectName.substring(0, projectName.length() - 1);
     307             :         }
     308             :       }
     309             : 
     310           8 :       CurrentUser user = userProvider.get();
     311           8 :       user.setAccessPath(AccessPath.GIT);
     312             : 
     313             :       try {
     314           8 :         Project.NameKey nameKey = Project.nameKey(projectName);
     315           8 :         ProjectState state =
     316             :             projectCache
     317           8 :                 .get(nameKey)
     318           8 :                 .orElseThrow(() -> new RepositoryNotFoundException(nameKey.get()));
     319           8 :         if (!state.statePermitsRead()) {
     320           0 :           throw new RepositoryNotFoundException(nameKey.get());
     321             :         }
     322           8 :         req.setAttribute(ATT_STATE, state);
     323             : 
     324             :         try {
     325           8 :           permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
     326           2 :         } catch (AuthException e) {
     327           2 :           if (user instanceof AnonymousUser) {
     328           1 :             throw new ServiceNotAuthorizedException();
     329             :           }
     330           1 :           throw new RepositoryNotFoundException(nameKey.get(), e);
     331           8 :         }
     332             : 
     333           8 :         return manager.openRepository(nameKey);
     334           1 :       } catch (IOException | PermissionBackendException err) {
     335           1 :         throw new ServiceMayNotContinueException(projectName + " unavailable", err);
     336             :       }
     337             :     }
     338             :   }
     339             : 
     340             :   static class UploadFactory implements UploadPackFactory<HttpServletRequest> {
     341             :     private final TransferConfig config;
     342             :     private final DynamicSet<PreUploadHook> preUploadHooks;
     343             :     private final DynamicSet<PostUploadHook> postUploadHooks;
     344             :     private final PluginSetContext<UploadPackInitializer> uploadPackInitializers;
     345             :     private final PermissionBackend permissionBackend;
     346             : 
     347             :     @Inject
     348             :     UploadFactory(
     349             :         TransferConfig tc,
     350             :         DynamicSet<PreUploadHook> preUploadHooks,
     351             :         DynamicSet<PostUploadHook> postUploadHooks,
     352             :         PluginSetContext<UploadPackInitializer> uploadPackInitializers,
     353          99 :         PermissionBackend permissionBackend) {
     354          99 :       this.config = tc;
     355          99 :       this.preUploadHooks = preUploadHooks;
     356          99 :       this.postUploadHooks = postUploadHooks;
     357          99 :       this.uploadPackInitializers = uploadPackInitializers;
     358          99 :       this.permissionBackend = permissionBackend;
     359          99 :     }
     360             : 
     361             :     @Override
     362             :     public UploadPack create(HttpServletRequest req, Repository repo) {
     363           8 :       ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
     364           8 :       UploadPack up =
     365             :           new UploadPack(
     366           8 :               PermissionAwareRepositoryManager.wrap(
     367           8 :                   repo, permissionBackend.currentUser().project(state.getNameKey())));
     368           8 :       up.setPackConfig(config.getPackConfig());
     369           8 :       up.setTimeout(config.getTimeout());
     370           8 :       up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks)));
     371           8 :       up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
     372           8 :       String header = req.getHeader("Git-Protocol");
     373           8 :       if (header != null) {
     374           8 :         String[] params = header.split(":");
     375           8 :         up.setExtraParameters(Arrays.asList(params));
     376             :       }
     377           8 :       uploadPackInitializers.runEach(initializer -> initializer.init(state.getNameKey(), up));
     378           8 :       return up;
     379             :     }
     380             :   }
     381             : 
     382             :   static class UploadFilter implements Filter {
     383             :     private final UploadValidators.Factory uploadValidatorsFactory;
     384             :     private final PermissionBackend permissionBackend;
     385             :     private final Provider<CurrentUser> userProvider;
     386             :     private final GroupAuditService groupAuditService;
     387             :     private final Metrics metrics;
     388             :     private final PluginSetContext<RequestListener> requestListeners;
     389             :     private final UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook;
     390             :     private final Provider<WebSession> sessionProvider;
     391             : 
     392             :     @Inject
     393             :     UploadFilter(
     394             :         UploadValidators.Factory uploadValidatorsFactory,
     395             :         PermissionBackend permissionBackend,
     396             :         Provider<CurrentUser> userProvider,
     397             :         GroupAuditService groupAuditService,
     398             :         Metrics metrics,
     399             :         PluginSetContext<RequestListener> requestListeners,
     400             :         UsersSelfAdvertiseRefsHook usersSelfAdvertiseRefsHook,
     401          99 :         Provider<WebSession> sessionProvider) {
     402          99 :       this.uploadValidatorsFactory = uploadValidatorsFactory;
     403          99 :       this.permissionBackend = permissionBackend;
     404          99 :       this.userProvider = userProvider;
     405          99 :       this.groupAuditService = groupAuditService;
     406          99 :       this.metrics = metrics;
     407          99 :       this.requestListeners = requestListeners;
     408          99 :       this.usersSelfAdvertiseRefsHook = usersSelfAdvertiseRefsHook;
     409          99 :       this.sessionProvider = sessionProvider;
     410          99 :     }
     411             : 
     412             :     @Override
     413             :     public void doFilter(ServletRequest request, ServletResponse response, FilterChain next)
     414             :         throws IOException, ServletException {
     415           8 :       metrics.requestStarted();
     416             :       // The Resolver above already checked READ access for us.
     417           8 :       Repository repo = ServletUtils.getRepository(request);
     418           8 :       ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
     419           8 :       UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER);
     420           8 :       PermissionBackend.ForProject perm =
     421           8 :           permissionBackend.currentUser().project(state.getNameKey());
     422           8 :       HttpServletResponseWithStatusWrapper responseWrapper =
     423             :           new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
     424           8 :       HttpServletRequest httpRequest = (HttpServletRequest) request;
     425           8 :       String sessionId = getSessionIdOrNull(sessionProvider);
     426             : 
     427           8 :       try (TraceContext traceContext = TraceContext.open()) {
     428           8 :         RequestInfo requestInfo =
     429           8 :             RequestInfo.builder(
     430           8 :                     RequestInfo.RequestType.GIT_UPLOAD, userProvider.get(), traceContext)
     431           8 :                 .project(state.getNameKey())
     432           8 :                 .build();
     433           8 :         requestListeners.runEach(l -> l.onRequest(requestInfo));
     434             : 
     435             :         try {
     436           8 :           if (!perm.test(ProjectPermission.RUN_UPLOAD_PACK)) {
     437           0 :             GitSmartHttpTools.sendError(
     438             :                 (HttpServletRequest) request,
     439             :                 responseWrapper,
     440             :                 HttpServletResponse.SC_FORBIDDEN,
     441             :                 "upload-pack not permitted on this server");
     442           0 :             return;
     443             :           }
     444           0 :         } catch (PermissionBackendException e) {
     445           0 :           responseWrapper.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
     446           0 :           throw new ServletException(e);
     447           8 :         }
     448             : 
     449             :         // We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
     450             :         // may have been overridden by a proxy server -- we'll try to avoid this.
     451           8 :         UploadValidators uploadValidators =
     452           8 :             uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
     453           8 :         up.setPreUploadHook(
     454           8 :             PreUploadHookChain.newChain(
     455           8 :                 Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
     456           8 :         if (state.isAllUsers()) {
     457           1 :           up.setAdvertiseRefsHook(usersSelfAdvertiseRefsHook);
     458             :         }
     459             : 
     460           8 :         try (TracingHook tracingHook = new TracingHook()) {
     461           8 :           up.setProtocolV2Hook(tracingHook);
     462           8 :           next.doFilter(httpRequest, responseWrapper);
     463             :         }
     464           0 :       } finally {
     465           8 :         groupAuditService.dispatch(
     466             :             new HttpAuditEvent(
     467             :                 sessionId,
     468           8 :                 userProvider.get(),
     469           8 :                 extractWhat(httpRequest),
     470           8 :                 TimeUtil.nowMs(),
     471           8 :                 extractParameters(httpRequest),
     472           8 :                 httpRequest.getMethod(),
     473             :                 httpRequest,
     474           8 :                 responseWrapper.getResponseStatus(),
     475             :                 responseWrapper));
     476             :       }
     477           8 :     }
     478             : 
     479             :     @Override
     480           8 :     public void init(FilterConfig config) {}
     481             : 
     482             :     @Override
     483           8 :     public void destroy() {}
     484             :   }
     485             : 
     486          99 :   static class GerritUploadPackErrorHandler implements UploadPackErrorHandler {
     487          99 :     private static final FluentLogger logger = FluentLogger.forEnclosingClass();
     488             : 
     489             :     @Override
     490             :     public void upload(HttpServletRequest req, HttpServletResponse rsp, UploadPackRunnable r)
     491             :         throws IOException {
     492           8 :       rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.FAIL.toString());
     493             :       try {
     494           8 :         r.upload();
     495           8 :         rsp.setHeader(GIT_COMMAND_STATUS_HEADER, GIT_COMMAND_STATUS.OK.toString());
     496           0 :       } catch (ServiceMayNotContinueException e) {
     497           0 :         if (!e.isOutput() && !rsp.isCommitted()) {
     498           0 :           rsp.reset();
     499           0 :           sendError(req, rsp, e.getStatusCode(), e.getMessage());
     500             :         }
     501           3 :       } catch (Throwable e) {
     502           3 :         logger.atSevere().withCause(e).log(
     503             :             "%s",
     504           3 :             MessageFormat.format(
     505           3 :                 HttpServerText.get().internalErrorDuringUploadPack,
     506           3 :                 ServletUtils.getRepository(req)));
     507           3 :         if (!rsp.isCommitted()) {
     508           3 :           rsp.reset();
     509           3 :           String msg = e instanceof PackProtocolException ? e.getMessage() : null;
     510           3 :           sendError(req, rsp, UploadPackErrorHandler.statusCodeForThrowable(e), msg);
     511             :         }
     512           8 :       }
     513           8 :     }
     514             :   }
     515             : 
     516             :   static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
     517             :     private final AsyncReceiveCommits.Factory factory;
     518             :     private final Provider<CurrentUser> userProvider;
     519             : 
     520             :     @Inject
     521          99 :     ReceiveFactory(AsyncReceiveCommits.Factory factory, Provider<CurrentUser> userProvider) {
     522          99 :       this.factory = factory;
     523          99 :       this.userProvider = userProvider;
     524          99 :     }
     525             : 
     526             :     @Override
     527             :     public ReceivePack create(HttpServletRequest req, Repository db)
     528             :         throws ServiceNotAuthorizedException {
     529           7 :       final ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
     530             : 
     531           7 :       if (!userProvider.get().isIdentifiedUser()) {
     532             :         // Anonymous users are not permitted to push.
     533           0 :         throw new ServiceNotAuthorizedException();
     534             :       }
     535             : 
     536           7 :       AsyncReceiveCommits arc =
     537           7 :           factory.create(state, userProvider.get().asIdentifiedUser(), db, null);
     538           7 :       ReceivePack rp = arc.getReceivePack();
     539           7 :       req.setAttribute(ATT_ARC, arc);
     540           7 :       return rp;
     541             :     }
     542             :   }
     543             : 
     544           1 :   static class DisabledReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
     545             :     @Override
     546             :     public ReceivePack create(HttpServletRequest req, Repository db)
     547             :         throws ServiceNotEnabledException {
     548           0 :       throw new ServiceNotEnabledException();
     549             :     }
     550             :   }
     551             : 
     552             :   static class ReceiveFilter implements Filter {
     553             :     private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
     554             :     private final PermissionBackend permissionBackend;
     555             :     private final Provider<CurrentUser> userProvider;
     556             :     private final GroupAuditService groupAuditService;
     557             :     private final Metrics metrics;
     558             :     private final Provider<WebSession> sessionProvider;
     559             : 
     560             :     @Inject
     561             :     ReceiveFilter(
     562             :         @Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
     563             :         PermissionBackend permissionBackend,
     564             :         Provider<CurrentUser> userProvider,
     565             :         GroupAuditService groupAuditService,
     566             :         Metrics metrics,
     567          99 :         Provider<WebSession> sessionProvider) {
     568          99 :       this.cache = cache;
     569          99 :       this.permissionBackend = permissionBackend;
     570          99 :       this.userProvider = userProvider;
     571          99 :       this.groupAuditService = groupAuditService;
     572          99 :       this.metrics = metrics;
     573          99 :       this.sessionProvider = sessionProvider;
     574          99 :     }
     575             : 
     576             :     @Override
     577             :     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
     578             :         throws IOException, ServletException {
     579           7 :       metrics.requestStarted();
     580           7 :       boolean isGet = "GET".equalsIgnoreCase(((HttpServletRequest) request).getMethod());
     581             : 
     582           7 :       AsyncReceiveCommits arc = (AsyncReceiveCommits) request.getAttribute(ATT_ARC);
     583             : 
     584             :       // Send refs down the wire.
     585           7 :       ReceivePack rp = arc.getReceivePack();
     586           7 :       rp.getAdvertiseRefsHook().advertiseRefs(rp);
     587             : 
     588           7 :       ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
     589           7 :       HttpServletResponseWithStatusWrapper responseWrapper =
     590             :           new HttpServletResponseWithStatusWrapper((HttpServletResponse) response);
     591           7 :       HttpServletRequest httpRequest = (HttpServletRequest) request;
     592             :       Capable canUpload;
     593             :       try {
     594             :         try {
     595           7 :           if (!permissionBackend
     596           7 :               .currentUser()
     597           7 :               .project(state.getNameKey())
     598           7 :               .test(ProjectPermission.RUN_RECEIVE_PACK)) {
     599           0 :             GitSmartHttpTools.sendError(
     600             :                 httpRequest,
     601             :                 responseWrapper,
     602             :                 HttpServletResponse.SC_FORBIDDEN,
     603             :                 "receive-pack not permitted on this server");
     604           0 :             return;
     605             :           }
     606           7 :           canUpload = arc.canUpload();
     607           0 :         } catch (PermissionBackendException e) {
     608           0 :           throw new RuntimeException(e);
     609           7 :         }
     610             :       } finally {
     611           7 :         groupAuditService.dispatch(
     612             :             new HttpAuditEvent(
     613           7 :                 getSessionIdOrNull(sessionProvider),
     614           7 :                 userProvider.get(),
     615           7 :                 extractWhat(httpRequest),
     616           7 :                 TimeUtil.nowMs(),
     617           7 :                 extractParameters(httpRequest),
     618           7 :                 httpRequest.getMethod(),
     619             :                 httpRequest,
     620           7 :                 responseWrapper.getResponseStatus(),
     621             :                 responseWrapper));
     622             :       }
     623             : 
     624           7 :       if (canUpload != Capable.OK) {
     625           0 :         GitSmartHttpTools.sendError(
     626             :             httpRequest,
     627             :             responseWrapper,
     628             :             HttpServletResponse.SC_FORBIDDEN,
     629           0 :             "\n" + canUpload.getMessage());
     630           0 :         return;
     631             :       }
     632             : 
     633           7 :       if (!rp.isCheckReferencedObjectsAreReachable()) {
     634           6 :         chain.doFilter(request, responseWrapper);
     635           6 :         return;
     636             :       }
     637             : 
     638           5 :       if (!userProvider.get().isIdentifiedUser()) {
     639           0 :         chain.doFilter(request, responseWrapper);
     640           0 :         return;
     641             :       }
     642             : 
     643           5 :       AdvertisedObjectsCacheKey cacheKey =
     644           5 :           AdvertisedObjectsCacheKey.create(userProvider.get().getAccountId(), state.getNameKey());
     645             : 
     646           5 :       if (isGet) {
     647           5 :         cache.invalidate(cacheKey);
     648             :       } else {
     649           5 :         Set<ObjectId> ids = cache.getIfPresent(cacheKey);
     650           5 :         if (ids != null) {
     651           5 :           rp.getAdvertisedObjects().addAll(ids);
     652           5 :           cache.invalidate(cacheKey);
     653             :         }
     654             :       }
     655             : 
     656           5 :       chain.doFilter(request, responseWrapper);
     657             : 
     658           5 :       if (isGet) {
     659           5 :         cache.put(cacheKey, Collections.unmodifiableSet(new HashSet<>(rp.getAdvertisedObjects())));
     660             :       }
     661           5 :     }
     662             : 
     663             :     @Override
     664           8 :     public void init(FilterConfig arg0) {}
     665             : 
     666             :     @Override
     667           8 :     public void destroy() {}
     668             :   }
     669             : 
     670             :   @Nullable
     671             :   private static String getSessionIdOrNull(Provider<WebSession> sessionProvider) {
     672           8 :     WebSession session = sessionProvider.get();
     673           8 :     if (session.isSignedIn()) {
     674           8 :       return session.getSessionId();
     675             :     }
     676           3 :     return null;
     677             :   }
     678             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750