View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.transport.apache;
20  
21  import java.util.Collection;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import org.apache.http.HttpHost;
26  import org.apache.http.HttpRequest;
27  import org.apache.http.HttpRequestInterceptor;
28  import org.apache.http.protocol.HttpContext;
29  import org.apache.http.protocol.HttpCoreContext;
30  
31  /**
32   * Scopes operator-configured request headers ({@code aether.transport.http.headers}) to the repository origin.
33   * <p>
34   * Configured headers are stamped on every request the transport creates and frequently carry credentials
35   * ({@code Authorization}, cookies, private token headers). Apache HttpClient's redirect execution re-sends the
36   * original request headers on each redirect hop, including hops that leave the repository origin, which would
37   * replay those credentials to the redirect target. This interceptor runs in the protocol layer - which HttpClient
38   * re-enters for every redirect hop - and removes the configured headers from any request whose target host is not
39   * the repository origin (same scheme, host and effective port). Challenge- and preemptive-authentication headers
40   * are attached below the protocol layer and are host-scoped by the credentials provider already; they are not
41   * affected by this interceptor.
42   *
43   * @since 2.0.23
44   */
45  final class OriginScopedHeadersInterceptor implements HttpRequestInterceptor {
46      private final HttpHost origin;
47  
48      private final Set<String> headerNames;
49  
50      OriginScopedHeadersInterceptor(HttpHost origin, Collection<?> headerNames) {
51          this.origin = origin;
52          this.headerNames = new HashSet<>();
53          for (Object headerName : headerNames) {
54              if (headerName != null) {
55                  this.headerNames.add(String.valueOf(headerName));
56              }
57          }
58      }
59  
60      @Override
61      public void process(HttpRequest request, HttpContext context) {
62          if (headerNames.isEmpty()) {
63              return;
64          }
65          Object attribute = context != null ? context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST) : null;
66          // fail closed: when the target host cannot be determined, do not attach configured headers
67          if (!(attribute instanceof HttpHost) || !isSameOrigin(origin, (HttpHost) attribute)) {
68              for (String headerName : headerNames) {
69                  request.removeHeaders(headerName);
70              }
71          }
72      }
73  
74      static boolean isSameOrigin(HttpHost origin, HttpHost target) {
75          return origin.getSchemeName().equalsIgnoreCase(target.getSchemeName())
76                  && origin.getHostName().equalsIgnoreCase(target.getHostName())
77                  && schemeDefaultPort(origin) == schemeDefaultPort(target);
78      }
79  
80      /**
81       * Determines the effective port of the given host: the explicit port if present, otherwise the default port
82       * implied by the scheme.
83       */
84      static int schemeDefaultPort(HttpHost host) {
85          if (host.getPort() >= 0) {
86              return host.getPort();
87          }
88          return "https".equalsIgnoreCase(host.getSchemeName()) ? 443 : 80;
89      }
90  }