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 }