001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.client.classic.remote;
018
019/**
020 * Marks the current thread as executing an SSRF-guard-active {@code @Remote} call, for the duration of one
021 * blocking {@code RestRequest.run()} (including any internal retries/redirect hops it triggers on the same
022 * thread).
023 *
024 * <p>
025 * The classic engine shares a single {@code CloseableHttpClient} across every request the client issues, whether
026 * {@code @Remote}-originated or an ordinary {@code client.get(url)} call. Since neither Apache HttpClient's
027 * {@code HttpClientConnectionManager.requestConnection} SPI nor its {@code RedirectStrategy} SPI receives enough
028 * information to distinguish which caller triggered a given connection lease or redirect decision, this thread-local
029 * flag is set for the duration of exactly one {@code @Remote} invocation (which runs synchronously, blocking the
030 * calling thread, per Juneau's classic-engine execution model).  While it is set, the client's connection manager
031 * leases connections from a dedicated pool whose connections are all opened through the
032 * {@code org.apache.juneau.http.remote.RemoteUrlPolicy} pin, and its redirect strategy defers {@code 3xx} handling
033 * to Juneau's revalidating redirect loop &mdash; leaving ordinary (non-{@code @Remote}) requests on the same client,
034 * and their separate pool, completely unaffected.
035 *
036 * <p>
037 * The guard of the {@code @Remote} request itself also travels on the request's {@code HttpContext}, so it holds even
038 * when the request is executed on another thread; this flag additionally guards any other request made on the calling
039 * thread while the call runs.
040 *
041 * @since 10.0.0
042 */
043public final class RemoteUrlPolicyState {
044
045   private RemoteUrlPolicyState() {}
046
047   // Nesting depth of the guarded calls on this thread (a guarded call can make another one, for example from a
048   // request interceptor); absent when none is running.
049   private static final ThreadLocal<int[]> DEPTH = new ThreadLocal<>();
050
051   /**
052    * Marks the calling thread as executing an SSRF-guard-active {@code @Remote} call.
053    *
054    * <p>
055    * Callers must invoke {@link #deactivate()} in a {@code finally} block once the call (including any retries)
056    * has completed.  Calls nest: the thread stays marked until every {@code activate()} has been matched by a
057    * {@code deactivate()}, so a guarded call made while another one is running does not end the outer call's guard.
058    */
059   public static void activate() {
060      var depth = DEPTH.get();
061      if (depth == null)
062         DEPTH.set(new int[] { 1 });
063      else
064         depth[0]++;
065   }
066
067   /** Ends one {@link #activate()}; the calling thread's marker is cleared when the outermost guarded call ends. */
068   public static void deactivate() {
069      var depth = DEPTH.get();
070      if (depth != null && --depth[0] <= 0)
071         DEPTH.remove();
072   }
073
074   /**
075    * Returns {@code true} if the calling thread is currently executing an SSRF-guard-active {@code @Remote} call.
076    *
077    * @return {@code true} if the guard is active on this thread.
078    */
079   public static boolean isActive() {
080      return DEPTH.get() != null;
081   }
082}