/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.esigate.http; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.message.BasicHttpResponse; import org.esigate.impl.DriverRequest; import org.esigate.impl.UrlRewriter; import org.esigate.util.FilterList; import org.esigate.util.UriUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * This class is responsible for copying headers from incoming requests to outgoing requests and from incoming responses * to outgoing responses. * * @author Francois-Xavier Bonnet * */ public class HeaderManager { private static final Logger LOG = LoggerFactory.getLogger(HeaderManager.class); private final FilterList requestHeadersFilterList = new FilterList(); private final FilterList responseHeadersFilterList = new FilterList(); private final UrlRewriter urlRewriter; /** * Builds a Header manager. * * @param urlRewriter * The {@link UrlRewriter} to be used to rewrite headers like "Location" */ public HeaderManager(UrlRewriter urlRewriter) { this.urlRewriter = urlRewriter; // Populate headers filter lists // By default all request headers are forwarded requestHeadersFilterList.add("*"); // Except hop-by-hop headers requestHeadersFilterList.remove("Connection"); requestHeadersFilterList.remove("Content-Length"); requestHeadersFilterList.remove("Cache-control"); requestHeadersFilterList.remove("Cookie"); requestHeadersFilterList.remove("Host"); requestHeadersFilterList.remove("Max-Forwards"); requestHeadersFilterList.remove("Pragma"); requestHeadersFilterList.remove("Proxy-Authorization"); requestHeadersFilterList.remove("TE"); requestHeadersFilterList.remove("Trailer"); requestHeadersFilterList.remove("Transfer-Encoding"); requestHeadersFilterList.remove("Upgrade"); // By default all response headers are forwarded responseHeadersFilterList.add("*"); // Except hop-by-hop headers responseHeadersFilterList.remove("Connection"); responseHeadersFilterList.remove("Content-Length"); responseHeadersFilterList.remove("Content-MD5"); responseHeadersFilterList.remove("Date"); responseHeadersFilterList.remove("Keep-Alive"); responseHeadersFilterList.remove("Proxy-Authenticate"); responseHeadersFilterList.remove("Set-Cookie"); responseHeadersFilterList.remove("Trailer"); responseHeadersFilterList.remove("Transfer-Encoding"); } protected boolean isForwardedRequestHeader(String headerName) { return requestHeadersFilterList.contains(headerName); } protected boolean isForwardedResponseHeader(String headerName) { return responseHeadersFilterList.contains(headerName); } /** * Copy header from originalRequest to httpRequest. * <p> * Referer is rewritten. X-Forwarded-* headers are updated. * * @param originalRequest * source request * @param httpRequest * destination request */ public void copyHeaders(DriverRequest originalRequest, HttpRequest httpRequest) { String baseUrl = originalRequest.getBaseUrl().toString(); String visibleBaseUrl = originalRequest.getVisibleBaseUrl(); for (Header header : originalRequest.getOriginalRequest().getAllHeaders()) { String name = header.getName(); // Special headers if (HttpHeaders.REFERER.equalsIgnoreCase(name) && isForwardedRequestHeader(HttpHeaders.REFERER)) { String value = header.getValue(); value = urlRewriter.rewriteReferer(value, baseUrl, visibleBaseUrl); httpRequest.addHeader(name, value); // All other headers are copied if allowed } else if (isForwardedRequestHeader(name)) { httpRequest.addHeader(header); } } // process X-Forwarded-For header String remoteAddr = originalRequest.getOriginalRequest().getRemoteAddr(); if (remoteAddr != null) { String forwardedFor = null; if (httpRequest.containsHeader("X-Forwarded-For")) { forwardedFor = httpRequest.getFirstHeader("X-Forwarded-For").getValue(); } if (forwardedFor == null) { forwardedFor = remoteAddr; } else { forwardedFor = forwardedFor + ", " + remoteAddr; } httpRequest.setHeader("X-Forwarded-For", forwardedFor); } // Process X-Forwarded-Proto header if (!httpRequest.containsHeader("X-Forwarded-Proto")) { httpRequest.addHeader("X-Forwarded-Proto", UriUtils.extractScheme(originalRequest.getOriginalRequest().getRequestLine().getUri())); } } /** * Copies end-to-end headers from a response received from the server to the response to be sent to the client. * * @param outgoingRequest * the request sent * @param incomingRequest * the original request received from the client * @param httpClientResponse * the response received from the provider application * @return the response to be sent to the client */ public CloseableHttpResponse copyHeaders(OutgoingRequest outgoingRequest, HttpEntityEnclosingRequest incomingRequest, HttpResponse httpClientResponse) { HttpResponse result = new BasicHttpResponse(httpClientResponse.getStatusLine()); result.setEntity(httpClientResponse.getEntity()); String originalUri = incomingRequest.getRequestLine().getUri(); String baseUrl = outgoingRequest.getBaseUrl().toString(); String visibleBaseUrl = outgoingRequest.getOriginalRequest().getVisibleBaseUrl(); for (Header header : httpClientResponse.getAllHeaders()) { String name = header.getName(); String value = header.getValue(); try { // Ignore Content-Encoding and Content-Type as these headers are // set in HttpEntity if (!HttpHeaders.CONTENT_ENCODING.equalsIgnoreCase(name)) { if (isForwardedResponseHeader(name)) { // Some headers containing an URI have to be rewritten if (HttpHeaders.LOCATION.equalsIgnoreCase(name) || HttpHeaders.CONTENT_LOCATION.equalsIgnoreCase(name)) { // Header contains only an url value = urlRewriter.rewriteUrl(value, originalUri, baseUrl, visibleBaseUrl, true); value = HttpResponseUtils.removeSessionId(value, httpClientResponse); result.addHeader(name, value); } else if ("Link".equalsIgnoreCase(name)) { // Header has the following format // Link: </feed>; rel="alternate" if (value.startsWith("<") && value.contains(">")) { String urlValue = value.substring(1, value.indexOf(">")); String targetUrlValue = urlRewriter.rewriteUrl(urlValue, originalUri, baseUrl, visibleBaseUrl, true); targetUrlValue = HttpResponseUtils.removeSessionId(targetUrlValue, httpClientResponse); value = value.replace("<" + urlValue + ">", "<" + targetUrlValue + ">"); } result.addHeader(name, value); } else if ("Refresh".equalsIgnoreCase(name)) { // Header has the following format // Refresh: 5; url=http://www.example.com int urlPosition = value.indexOf("url="); if (urlPosition >= 0) { value = urlRewriter.rewriteRefresh(value, originalUri, baseUrl, visibleBaseUrl); value = HttpResponseUtils.removeSessionId(value, httpClientResponse); } result.addHeader(name, value); } else if ("P3p".equalsIgnoreCase(name)) { // Do not translate url yet. // P3P is used with a default fixed url most of the // time. result.addHeader(name, value); } else { result.addHeader(header.getName(), header.getValue()); } } } } catch (Exception e1) { // It's important not to fail here. // An application can always send corrupted headers, and we // should not crash LOG.error("Error while copying headers", e1); result.addHeader("X-Esigate-Error", "Error processing header " + name + ": " + value); } } return BasicCloseableHttpResponse.adapt(result); } }