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 context 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 context) throws HttpException, IOException { 115 Args.notNull(request, "HTTP request"); 116 Args.notNull(context, "HTTP context"); 117 118 final ProtocolVersion ver = context.getProtocolVersion() != null ? context.getProtocolVersion() : HttpVersion.HTTP_1_1; 119 120 final URIAuthority authority = request.getAuthority(); 121 if (authority == null) { 122 throw new ProtocolException("Request authority not specified"); 123 } 124 125 final int port = authority.getPort(); 126 final StringBuilder valueBuilder = new StringBuilder(); 127 128 final Header forwardedHeader = request.getFirstHeader(FORWARDED_HEADER_NAME); 129 final boolean forwardedHeaderExists = forwardedHeader != null; 130 if (forwardedHeaderExists) { 131 // Forwarded header already exists, add current hop details to the end of the existing value 132 valueBuilder.append(forwardedHeader.getValue()); 133 valueBuilder.append(", "); 134 } 135 136 // Add the current hop details 137 final EndpointDetails endpointDetails = (EndpointDetails) context.getAttribute(HttpCoreContext.CONNECTION_ENDPOINT); 138 139 // Add the "by" parameter 140 if (endpointDetails != null) { 141 final SocketAddress remoteAddress = endpointDetails.getRemoteAddress(); 142 if (remoteAddress instanceof InetSocketAddress) { 143 final InetSocketAddress inetAddress = (InetSocketAddress) remoteAddress; 144 final String byValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); 145 if (inetAddress.getAddress() instanceof Inet6Address) { 146 valueBuilder.append("by=\"").append(byValue).append("\""); 147 } else { 148 valueBuilder.append("by=").append(byValue); 149 } 150 } 151 // Add the "for" parameter 152 final SocketAddress localAddress = endpointDetails.getLocalAddress(); 153 if (localAddress instanceof InetSocketAddress) { 154 final InetSocketAddress inetAddress = (InetSocketAddress) localAddress; 155 final String forValue = inetAddress.getHostString() + ":" + inetAddress.getPort(); 156 if (inetAddress.getAddress() instanceof Inet6Address) { 157 valueBuilder.append(";for=\"").append(forValue).append("\""); 158 } else { 159 valueBuilder.append(";for=").append(forValue); 160 } 161 } 162 163 } 164 // Add the "host" parameter 165 if (valueBuilder.length() > 0 && !forwardedHeaderExists) { 166 valueBuilder.append(";"); 167 } 168 valueBuilder.append("host=\"").append(authority.getHostName()).append("\""); 169 170 // Add the "port" parameter 171 if (port != -1) { 172 valueBuilder.append(";port=").append(port); 173 } 174 175 // Add the "proto" parameter 176 final String protoValue = ver.getProtocol(); 177 if (protoValue != null) { 178 valueBuilder.append(";proto=").append(protoValue); 179 } 180 181 final String value = valueBuilder.toString(); 182 request.setHeader(FORWARDED_HEADER_NAME, value); 183 } 184 185 }