// Copyright (C) 2017 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.googlesource.gerrit.plugins.quota;

import com.google.common.cache.LoadingCache;
import com.google.gerrit.httpd.AllRequestFilter;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.googlesource.gerrit.plugins.quota.Module.Holder;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class RestApiRateLimiter extends AllRequestFilter {
  private static final Logger log = LoggerFactory.getLogger(RestApiRateLimiter.class);
  private static final int SECONDS_PER_HOUR = 3600;

  static final int SC_TOO_MANY_REQUESTS = 429;

  private final Provider<CurrentUser> user;
  private final LoadingCache<Account.Id, Holder> limitsPerAccount;
  private final LoadingCache<String, Holder> limitsPerRemoteHost;

  private final Pattern resturi =
      Pattern.compile(
          "^/(?:a/)?"
              + "(access|accounts|changes|config|groups|plugins|projects|Documentation|tools)/(.*)$");

  private final String limitExceededMsg;

  @Inject
  RestApiRateLimiter(
      Provider<CurrentUser> user,
      @Named(HttpModule.CACHE_NAME_RESTAPI_ACCOUNTID)
          LoadingCache<Account.Id, Holder> limitsPerAccount,
      @Named(HttpModule.CACHE_NAME_RESTAPI_REMOTEHOST)
          LoadingCache<String, Holder> limitsPerRemoteHost,
      @Named(RateMsgHelper.RESTAPI_CONFIGURABLE_MSG_ANNOTATION) String limitExceededMsg) {
    this.user = user;
    this.limitsPerAccount = limitsPerAccount;
    this.limitsPerRemoteHost = limitsPerRemoteHost;
    this.limitExceededMsg = limitExceededMsg;
  }

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain)
      throws IOException, ServletException {
    if (isRest(req)) {
      Holder rateLimiterHolder;
      CurrentUser u = user.get();
      if (u.isIdentifiedUser()) {
        Account.Id accountId = u.asIdentifiedUser().getAccountId();
        try {
          rateLimiterHolder = limitsPerAccount.get(accountId);
        } catch (ExecutionException e) {
          rateLimiterHolder = Holder.EMPTY;
          log.warn("Cannot get rate limits for account ''{}''", accountId, e);
        }
      } else {
        try {
          rateLimiterHolder = limitsPerRemoteHost.get(req.getRemoteHost());
        } catch (ExecutionException e) {
          rateLimiterHolder = Holder.EMPTY;
          log.warn(
              "Cannot get rate limits for anonymous access from remote host ''{}''",
              req.getRemoteHost(),
              e);
        }
      }
      if (!rateLimiterHolder.hasGracePermits()
          && rateLimiterHolder.get() != null
          && !rateLimiterHolder.get().tryAcquire()) {
        String msg =
            MessageFormat.format(
                limitExceededMsg,
                rateLimiterHolder.get().getRate() * SECONDS_PER_HOUR,
                rateLimiterHolder.getBurstPermits());
        ((HttpServletResponse) res).sendError(SC_TOO_MANY_REQUESTS, msg);
        return;
      }
    }
    chain.doFilter(req, res);
  }

  boolean isRest(ServletRequest req) {
    return req instanceof HttpServletRequest
        && resturi.matcher(((HttpServletRequest) req).getRequestURI()).matches();
  }
}

@@ -1939,23 +1939,27 @@
 Pattern 
-resturi
+servletPath
  =%0A     
@@ -4253,15 +4253,19 @@
  && 
-resturi
+servletPath
 .mat
@@ -4303,18 +4303,19 @@
 .get
-RequestURI
+ServletPath
 ()).

Fix Quota rest rate limiter support