1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package org.apache.hc.core5.http.protocol; 29 30 import java.io.IOException; 31 import java.net.Inet6Address; 32 import java.net.InetSocketAddress; 33 import java.net.SocketAddress; 34 35 import org.apache.hc.core5.annotation.Contract; 36 import org.apache.hc.core5.annotation.ThreadingBehavior; 37 import org.apache.hc.core5.http.EndpointDetails; 38 import org.apache.hc.core5.http.EntityDetails; 39 import org.apache.hc.core5.http.Header; 40 import org.apache.hc.core5.http.HttpException; 41 import org.apache.hc.core5.http.HttpRequest; 42 import org.apache.hc.core5.http.HttpRequestInterceptor; 43 import org.apache.hc.core5.http.HttpVersion; 44 import org.apache.hc.core5.http.ProtocolException; 45 import org.apache.hc.core5.http.ProtocolVersion; 46 import org.apache.hc.core5.net.URIAuthority; 47 import org.apache.hc.core5.util.Args; 48 49 /** 50 * This request interceptor can be used by an HTTP proxy or an intermediary to add a Forwarded header 51 * to outgoing request messages. 52 * The Forwarded header is used to capture information about the intermediate nodes that a request 53 * has passed through. This information can be useful for security purposes or for debugging purposes. 54 * <p> 55 * The Forwarded header consists of a list of key-value pairs separated by semicolons. The keys that 56 * can be used in the Forwarded header include "host", "port", "proto", "for", and "by". The host 57 * key is used to specify the host name or IP address of the request authority. The port key is used 58 * to specify the port number of the request authority. The proto key is used to specify the 59 * protocol version of the request. The for key is used to specify the IP address of the client 60 * making the request. The by key is used to specify the IP address of the node adding the Forwarded 61 * header. 62 * <p> 63 * When multiple proxy servers are involved in forwarding a request, each proxy can add its own 64 * Forwarded header to the request. This allows for the capture of information about each 65 * intermediate node that the request passes through. 66 * <p> 67 * The ForwardedRequest class adds the Forwarded header to the request by implementing the process() 68 * method of the HttpRequestInterceptor interface. The method retrieves the ProtocolVersion and 69 * {@link URIAuthority} from the {@link HttpContext}. The ProtocolVersion is used to determine the 70 * proto key value and the {@link URIAuthority} is used to determine the host and port key values. 71 * The method also retrieves the {@link EndpointDetails} from the {@link HttpContext}, if it exists. 72 * The {@link EndpointDetails} is used to determine the by and for key values. If a Forwarded header 73 * already exists in the request, the existing header is not overwritten; instead, the new header 74 * value is appended to the existing header value, with a comma separator. 75 * <p> 76 * 77 * @since 5.3 78 */ 79 @Contract(threading = ThreadingBehavior.IMMUTABLE) 80 public class ForwardedRequest implements HttpRequestInterceptor { 81 82 /** 83 * The name of the header to set in the HTTP request. 84 */ 85 private static final String FORWARDED_HEADER_NAME = "Forwarded"; 86 87 /** 88 * Singleton instance. 89 */ 90 public static final HttpRequestInterceptor INSTANCE = new ForwardedRequest(); 91 92 93 /** 94 * The process method adds a Forwarded header to an HTTP request containing information about 95 * the intermediate nodes that the request has passed through. If a Forwarded header already 96 * exists in the request, the new header value is appended to the existing header value, with a 97 * comma separator. 98 * <p> 99 * The method retrieves the {@link ProtocolVersion} and {@link URIAuthority} from the 100 * HttpContext. The ProtocolVersion is used to determine the proto key value and the 101 * URIAuthority is used to determine the host and port key values. The method also retrieves the 102 * EndpointDetails from the {@link HttpContext}, if it exists. The {@link EndpointDetails} is 103 * used to determine the by key value. 104 * 105 * @param request the HTTP request to which the Forwarded header is to be added (not 106 * {@code null}) 107 * @param entity the entity details of the request (can be {@code null}) 108 * @param localContext the HTTP context in which the request is being executed (not {@code null}) 109 * @throws HttpException if there is an error processing the request 110 * @throws IOException if an IO error occurs while processing the request 111 * @throws ProtocolException if the request authority is not specified 112 */ 113 @Override 114 public void process(final HttpRequest request, final EntityDetails entity, final HttpContext localContext) throws HttpException, IOException { 115 Args.notNull(request, "HTTP request"); 116 Args.notNull(localContext, "HTTP context"); 117 118 final HttpCoreContext context = HttpCoreContext.cast(localContext); 119 final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1; 120 121 final URIAuthority authority = request.getAuthority(); 122 if (authority == null) { 123 throw new ProtocolException("Request authority not specified"); 124 } 125 126 final int port = authority.getPort(); 127 final StringBuilder valueBuilder = new StringBuilder(); 128 129 final Header forwardedHeader = request.getFirstHeader(FORWARDED_HEADER_NAME); 130 final boolean forwardedHeaderExists = forwardedHeader != null; 131 if (forwardedHeaderExists) { 132 // Forwarded header already exists, add current hop details to the end of the existing value 133 valueBuilder.append(forwardedHeader.getValue()); 134 valueBuilder.append(", "); 135 } 136 137 // Add the current hop details 138 final EndpointDetails endpointDetails = context.getEndpointDetails(); 139 140 // Add the "by" parameter 141 if (endpointDetails != null) { 142 final SocketAddress remoteAddress = endpointDetails.getRemoteAddress(); 143 if (remoteAddress instanceof InetSocketAddress) { 144 final InetSocketAddress inetAddress = (InetSocketAddress) remoteAddress; 145 final String byValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); 146 if (inetAddress.getAddress() instanceof Inet6Address) { 147 valueBuilder.append("by=\"").append(byValue).append("\""); 148 } else { 149 valueBuilder.append("by=").append(byValue); 150 } 151 } 152 // Add the "for" parameter 153 final SocketAddress localAddress = endpointDetails.getLocalAddress(); 154 if (localAddress instanceof InetSocketAddress) { 155 final InetSocketAddress inetAddress = (InetSocketAddress) localAddress; 156 final String forValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); 157 if (inetAddress.getAddress() instanceof Inet6Address) { 158 valueBuilder.append(";for=\"").append(forValue).append("\""); 159 } else { 160 valueBuilder.append(";for=").append(forValue); 161 } 162 } 163 164 } 165 // Add the "host" parameter 166 if (valueBuilder.length() > 0 && !forwardedHeaderExists) { 167 valueBuilder.append(";"); 168 } 169 valueBuilder.append("host=\"").append(authority.getHostName()).append("\""); 170 171 // Add the "port" parameter 172 if (port != -1) { 173 valueBuilder.append(";port=").append(port); 174 } 175 176 // Add the "proto" parameter 177 final String protoValue = ver.getProtocol(); 178 if (protoValue != null) { 179 valueBuilder.append(";proto=").append(protoValue); 180 } 181 182 final String value = valueBuilder.toString(); 183 request.setHeader(FORWARDED_HEADER_NAME, value); 184 } 185 186 }