View Javadoc
1   /* 
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   *
14   */
15  
16  package org.esigate.http;
17  
18  import org.apache.http.Header;
19  import org.apache.http.HttpEntityEnclosingRequest;
20  import org.apache.http.HttpHeaders;
21  import org.apache.http.HttpRequest;
22  import org.apache.http.HttpResponse;
23  import org.apache.http.client.methods.CloseableHttpResponse;
24  import org.apache.http.message.BasicHttpResponse;
25  import org.esigate.impl.DriverRequest;
26  import org.esigate.impl.UrlRewriter;
27  import org.esigate.util.FilterList;
28  import org.esigate.util.UriUtils;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  /**
33   * 
34   * This class is responsible for copying headers from incoming requests to outgoing requests and from incoming responses
35   * to outgoing responses.
36   * 
37   * @author Francois-Xavier Bonnet
38   * 
39   */
40  public class HeaderManager {
41      private static final Logger LOG = LoggerFactory.getLogger(HeaderManager.class);
42  
43      private final FilterList requestHeadersFilterList = new FilterList();
44      private final FilterList responseHeadersFilterList = new FilterList();
45  
46      private final UrlRewriter urlRewriter;
47  
48      /**
49       * Builds a Header manager.
50       * 
51       * @param urlRewriter
52       *            The {@link UrlRewriter} to be used to rewrite headers like "Location"
53       */
54      public HeaderManager(UrlRewriter urlRewriter) {
55  
56          this.urlRewriter = urlRewriter;
57  
58          // Populate headers filter lists
59  
60          // By default all request headers are forwarded
61          requestHeadersFilterList.add("*");
62          // Except hop-by-hop headers
63          requestHeadersFilterList.remove("Connection");
64          requestHeadersFilterList.remove("Content-Length");
65          requestHeadersFilterList.remove("Cache-control");
66          requestHeadersFilterList.remove("Cookie");
67          requestHeadersFilterList.remove("Host");
68          requestHeadersFilterList.remove("Max-Forwards");
69          requestHeadersFilterList.remove("Pragma");
70          requestHeadersFilterList.remove("Proxy-Authorization");
71          requestHeadersFilterList.remove("TE");
72          requestHeadersFilterList.remove("Trailer");
73          requestHeadersFilterList.remove("Transfer-Encoding");
74          requestHeadersFilterList.remove("Upgrade");
75  
76          // By default all response headers are forwarded
77          responseHeadersFilterList.add("*");
78          // Except hop-by-hop headers
79          responseHeadersFilterList.remove("Connection");
80          responseHeadersFilterList.remove("Content-Length");
81          responseHeadersFilterList.remove("Content-MD5");
82          responseHeadersFilterList.remove("Date");
83          responseHeadersFilterList.remove("Keep-Alive");
84          responseHeadersFilterList.remove("Proxy-Authenticate");
85          responseHeadersFilterList.remove("Set-Cookie");
86          responseHeadersFilterList.remove("Trailer");
87          responseHeadersFilterList.remove("Transfer-Encoding");
88      }
89  
90      protected boolean isForwardedRequestHeader(String headerName) {
91          return requestHeadersFilterList.contains(headerName);
92      }
93  
94      protected boolean isForwardedResponseHeader(String headerName) {
95          return responseHeadersFilterList.contains(headerName);
96      }
97  
98      /**
99       * Copy header from originalRequest to httpRequest.
100      * <p>
101      * Referer is rewritten. X-Forwarded-* headers are updated.
102      * 
103      * @param originalRequest
104      *            source request
105      * @param httpRequest
106      *            destination request
107      */
108     public void copyHeaders(DriverRequest originalRequest, HttpRequest httpRequest) {
109         String baseUrl = originalRequest.getBaseUrl().toString();
110         String visibleBaseUrl = originalRequest.getVisibleBaseUrl();
111         for (Header header : originalRequest.getOriginalRequest().getAllHeaders()) {
112             String name = header.getName();
113             // Special headers
114             if (HttpHeaders.REFERER.equalsIgnoreCase(name) && isForwardedRequestHeader(HttpHeaders.REFERER)) {
115                 String value = header.getValue();
116                 value = urlRewriter.rewriteReferer(value, baseUrl, visibleBaseUrl);
117                 httpRequest.addHeader(name, value);
118                 // All other headers are copied if allowed
119             } else if (isForwardedRequestHeader(name)) {
120                 httpRequest.addHeader(header);
121             }
122         }
123         // process X-Forwarded-For header
124         String remoteAddr = originalRequest.getOriginalRequest().getRemoteAddr();
125 
126         if (remoteAddr != null) {
127             String forwardedFor = null;
128             if (httpRequest.containsHeader("X-Forwarded-For")) {
129                 forwardedFor = httpRequest.getFirstHeader("X-Forwarded-For").getValue();
130             }
131 
132             if (forwardedFor == null) {
133                 forwardedFor = remoteAddr;
134             } else {
135                 forwardedFor = forwardedFor + ", " + remoteAddr;
136             }
137 
138             httpRequest.setHeader("X-Forwarded-For", forwardedFor);
139         }
140 
141         // Process X-Forwarded-Proto header
142         if (!httpRequest.containsHeader("X-Forwarded-Proto")) {
143             httpRequest.addHeader("X-Forwarded-Proto",
144                     UriUtils.extractScheme(originalRequest.getOriginalRequest().getRequestLine().getUri()));
145         }
146     }
147 
148     /**
149      * Copies end-to-end headers from a response received from the server to the response to be sent to the client.
150      * 
151      * @param outgoingRequest
152      *            the request sent
153      * @param incomingRequest
154      *            the original request received from the client
155      * @param httpClientResponse
156      *            the response received from the provider application
157      * @return the response to be sent to the client
158      */
159     public CloseableHttpResponse copyHeaders(OutgoingRequest outgoingRequest,
160             HttpEntityEnclosingRequest incomingRequest, HttpResponse httpClientResponse) {
161         HttpResponse result = new BasicHttpResponse(httpClientResponse.getStatusLine());
162         result.setEntity(httpClientResponse.getEntity());
163         String originalUri = incomingRequest.getRequestLine().getUri();
164         String baseUrl = outgoingRequest.getBaseUrl().toString();
165         String visibleBaseUrl = outgoingRequest.getOriginalRequest().getVisibleBaseUrl();
166         for (Header header : httpClientResponse.getAllHeaders()) {
167             String name = header.getName();
168             String value = header.getValue();
169             try {
170                 // Ignore Content-Encoding and Content-Type as these headers are
171                 // set in HttpEntity
172                 if (!HttpHeaders.CONTENT_ENCODING.equalsIgnoreCase(name)) {
173                     if (isForwardedResponseHeader(name)) {
174                         // Some headers containing an URI have to be rewritten
175                         if (HttpHeaders.LOCATION.equalsIgnoreCase(name)
176                                 || HttpHeaders.CONTENT_LOCATION.equalsIgnoreCase(name)) {
177                             // Header contains only an url
178                             value = urlRewriter.rewriteUrl(value, originalUri, baseUrl, visibleBaseUrl, true);
179                             value = HttpResponseUtils.removeSessionId(value, httpClientResponse);
180                             result.addHeader(name, value);
181                         } else if ("Link".equalsIgnoreCase(name)) {
182                             // Header has the following format
183                             // Link: </feed>; rel="alternate"
184 
185                             if (value.startsWith("<") && value.contains(">")) {
186                                 String urlValue = value.substring(1, value.indexOf(">"));
187 
188                                 String targetUrlValue =
189                                         urlRewriter.rewriteUrl(urlValue, originalUri, baseUrl, visibleBaseUrl, true);
190                                 targetUrlValue = HttpResponseUtils.removeSessionId(targetUrlValue, httpClientResponse);
191 
192                                 value = value.replace("<" + urlValue + ">", "<" + targetUrlValue + ">");
193                             }
194 
195                             result.addHeader(name, value);
196 
197                         } else if ("Refresh".equalsIgnoreCase(name)) {
198                             // Header has the following format
199                             // Refresh: 5; url=http://www.example.com
200                             int urlPosition = value.indexOf("url=");
201                             if (urlPosition >= 0) {
202                                 value = urlRewriter.rewriteRefresh(value, originalUri, baseUrl, visibleBaseUrl);
203                                 value = HttpResponseUtils.removeSessionId(value, httpClientResponse);
204                             }
205                             result.addHeader(name, value);
206                         } else if ("P3p".equalsIgnoreCase(name)) {
207                             // Do not translate url yet.
208                             // P3P is used with a default fixed url most of the
209                             // time.
210                             result.addHeader(name, value);
211                         } else {
212                             result.addHeader(header.getName(), header.getValue());
213                         }
214                     }
215                 }
216             } catch (Exception e1) {
217                 // It's important not to fail here.
218                 // An application can always send corrupted headers, and we
219                 // should not crash
220                 LOG.error("Error while copying headers", e1);
221                 result.addHeader("X-Esigate-Error", "Error processing header " + name + ": " + value);
222             }
223         }
224         return BasicCloseableHttpResponse.adapt(result);
225     }
226 }