LCOV - code coverage report
Current view: top level - httpd - AllRequestFilter.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 56 58 96.6 %
Date: 2022-11-19 15:00:39 Functions: 15 15 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2012 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 com.google.gerrit.extensions.registration.DynamicSet;
      18             : import com.google.gerrit.server.plugins.Plugin;
      19             : import com.google.gerrit.server.plugins.StopPluginListener;
      20             : import com.google.inject.Inject;
      21             : import com.google.inject.Module;
      22             : import com.google.inject.Scopes;
      23             : import com.google.inject.Singleton;
      24             : import com.google.inject.internal.UniqueAnnotations;
      25             : import com.google.inject.servlet.ServletModule;
      26             : import java.io.IOException;
      27             : import java.util.Iterator;
      28             : import javax.servlet.Filter;
      29             : import javax.servlet.FilterChain;
      30             : import javax.servlet.FilterConfig;
      31             : import javax.servlet.ServletException;
      32             : import javax.servlet.ServletRequest;
      33             : import javax.servlet.ServletResponse;
      34             : 
      35             : /** Filters all HTTP requests passing through the server. */
      36         100 : public abstract class AllRequestFilter implements Filter {
      37             :   public static Module module() {
      38          99 :     return new ServletModule() {
      39             :       @Override
      40             :       protected void configureServlets() {
      41          99 :         DynamicSet.setOf(binder(), AllRequestFilter.class);
      42          99 :         DynamicSet.bind(binder(), AllRequestFilter.class)
      43          99 :             .to(AllowRenderInFrameFilter.class)
      44          99 :             .in(Scopes.SINGLETON);
      45             : 
      46          99 :         filter("/*").through(FilterProxy.class);
      47             : 
      48          99 :         bind(StopPluginListener.class)
      49          99 :             .annotatedWith(UniqueAnnotations.create())
      50          99 :             .to(FilterProxy.class);
      51          99 :       }
      52             :     };
      53             :   }
      54             : 
      55             :   @Singleton
      56             :   static class FilterProxy implements Filter, StopPluginListener {
      57             :     private final DynamicSet<AllRequestFilter> filters;
      58             : 
      59             :     private DynamicSet<AllRequestFilter> initializedFilters;
      60             :     private FilterConfig filterConfig;
      61             : 
      62             :     @Inject
      63         100 :     FilterProxy(DynamicSet<AllRequestFilter> filters) {
      64         100 :       this.filters = filters;
      65         100 :       this.initializedFilters = new DynamicSet<>();
      66         100 :       this.filterConfig = null;
      67         100 :     }
      68             : 
      69             :     /**
      70             :      * Initializes a filter if needed
      71             :      *
      72             :      * @param filter The filter that should get initialized
      73             :      * @return {@code true} iff filter is now initialized
      74             :      * @throws ServletException if filter itself fails to init
      75             :      */
      76             :     private synchronized boolean initFilterIfNeeded(AllRequestFilter filter)
      77             :         throws ServletException {
      78         100 :       boolean ret = true;
      79         100 :       if (filters.contains(filter)) {
      80             :         // Regardless of whether or not the caller checked filter's
      81             :         // containment in initializedFilters, we better re-check as we're now
      82             :         // synchronized.
      83         100 :         if (!initializedFilters.contains(filter)) {
      84         100 :           filter.init(filterConfig);
      85         100 :           initializedFilters.add("gerrit", filter);
      86             :         }
      87             :       } else {
      88           0 :         ret = false;
      89             :       }
      90         100 :       return ret;
      91             :     }
      92             : 
      93             :     private synchronized void cleanUpInitializedFilters() {
      94          10 :       Iterable<AllRequestFilter> filtersToCleanUp = initializedFilters;
      95          10 :       initializedFilters = new DynamicSet<>();
      96          10 :       for (AllRequestFilter filter : filtersToCleanUp) {
      97          10 :         if (filters.contains(filter)) {
      98          10 :           initializedFilters.add("gerrit", filter);
      99             :         } else {
     100           1 :           filter.destroy();
     101             :         }
     102          10 :       }
     103          10 :     }
     104             : 
     105             :     @Override
     106             :     public void doFilter(ServletRequest req, ServletResponse res, FilterChain last)
     107             :         throws IOException, ServletException {
     108          39 :       final Iterator<AllRequestFilter> itr = filters.iterator();
     109          39 :       new FilterChain() {
     110             :         @Override
     111             :         public void doFilter(ServletRequest req, ServletResponse res)
     112             :             throws IOException, ServletException {
     113          39 :           while (itr.hasNext()) {
     114          39 :             AllRequestFilter filter = itr.next();
     115             :             // To avoid {@code synchronized} on the whole filtering (and
     116             :             // thereby killing concurrency), we start the below disjunction
     117             :             // with an unsynchronized check for containment. This
     118             :             // unsynchronized check is always correct if no filters got
     119             :             // initialized/cleaned concurrently behind our back.
     120             :             // The case of concurrently initialized filters is saved by the
     121             :             // call to initFilterIfNeeded. So that's fine too.
     122             :             // The case of concurrently cleaned filters between the {@code if}
     123             :             // condition and the call to {@code doFilter} is not saved by
     124             :             // anything. If a filter is getting removed concurrently while
     125             :             // another thread is in those two lines, doFilter might (but need
     126             :             // not) fail.
     127             :             //
     128             :             // Since this failure only occurs if a filter is deleted
     129             :             // (e.g.: a plugin reloaded) exactly when a thread is in those
     130             :             // two lines, and it only breaks a single request, we're ok with
     131             :             // it, given that this is really both really improbable and also
     132             :             // the "proper" fix for it would basically kill concurrency of
     133             :             // webrequests.
     134          39 :             if (initializedFilters.contains(filter) || initFilterIfNeeded(filter)) {
     135          39 :               filter.doFilter(req, res, this);
     136          39 :               return;
     137             :             }
     138           0 :           }
     139          39 :           last.doFilter(req, res);
     140          39 :         }
     141          39 :       }.doFilter(req, res);
     142          39 :     }
     143             : 
     144             :     @Override
     145             :     public void init(FilterConfig config) throws ServletException {
     146             :       // Plugins that provide AllRequestFilters might get loaded later at
     147             :       // runtime, long after this init method had been called. To allow to
     148             :       // correctly init such plugins' AllRequestFilters, we keep the
     149             :       // FilterConfig around, and reuse it to lazy init the AllRequestFilters.
     150         100 :       filterConfig = config;
     151             : 
     152         100 :       for (AllRequestFilter f : filters) {
     153         100 :         initFilterIfNeeded(f);
     154         100 :       }
     155         100 :     }
     156             : 
     157             :     @Override
     158             :     public synchronized void destroy() {
     159         100 :       Iterable<AllRequestFilter> filtersToDestroy = initializedFilters;
     160         100 :       initializedFilters = new DynamicSet<>();
     161         100 :       for (AllRequestFilter filter : filtersToDestroy) {
     162         100 :         filter.destroy();
     163         100 :       }
     164         100 :     }
     165             : 
     166             :     @Override
     167             :     public void onStopPlugin(Plugin plugin) {
     168             :       // In order to allow properly garbage collection, we need to scrub
     169             :       // initializedFilters clean of filters stemming from plugins as they
     170             :       // get unloaded.
     171          10 :       cleanUpInitializedFilters();
     172          10 :     }
     173             :   }
     174             : 
     175             :   @Override
     176          99 :   public void init(FilterConfig config) throws ServletException {}
     177             : 
     178             :   @Override
     179          99 :   public void destroy() {}
     180             : }

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