001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.http.servlet;
018
019import java.util.LinkedList;
020import java.util.regex.Pattern;
021
022import javax.servlet.FilterConfig;
023import javax.servlet.ServletRequest;
024import javax.servlet.http.HttpServletRequest;
025
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Request wrapper factory to integrate "X-Forwarded-For" and "X-Forwarded-Proto" HTTP headers.
031 * <p>
032 * Most of the design of this Servlet Filter is a port of <a
033 * href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this servlet
034 * filter replaces the apparent client remote IP address and hostname for the request with the IP
035 * address list presented by a proxy or a load balancer via a request headers (e.g.
036 * "X-Forwarded-For").
037 * <p>
038 * Another feature of this servlet filter is to replace the apparent scheme (http/https) and server
039 * port with the scheme presented by a proxy or a load balancer via a request header (e.g.
040 * "X-Forwarded-Proto").
041 * <p>
042 * This wrapper proceeds as follows:
043 * <p>
044 * If the incoming <code>request.getRemoteAddr()</code> matches the servlet filter's list of
045 * internal proxies :
046 * <ul>
047 * <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer
048 * or proxy in the given request's Http header named <code>$remoteIPHeader</code> (default value
049 * <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li>
050 * <li>For each ip/host of the list:
051 * <ul>
052 * <li>if it matches the internal proxies list, the ip/host is swallowed</li>
053 * <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li>
054 * <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li>
055 * </ul>
056 * </li>
057 * <li>If the request http header named <code>$protocolHeader</code> (e.g.
058 * <code>x-forwarded-for</code>) equals to the value of <code>protocolHeaderHttpsValue</code>
059 * configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>,
060 * <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can
061 * be overwritten with the <code>$httpsServerPort</code> configuration parameter.</li>
062 * </ul>
063 * </p>
064 * <p>
065 * <strong>Configuration parameters:</strong>
066 * <table border="1">
067 * <caption>Configuration parameters</caption>
068 * <tr>
069 * <th>XForwardedFilter property</th>
070 * <th>Description</th>
071 * <th>Equivalent mod_remoteip directive</th>
072 * <th>Format</th>
073 * <th>Default Value</th>
074 * </tr>
075 * <tr>
076 * <td>remoteIPHeader</td>
077 * <td>Name of the Http Header read by this servlet filter that holds the list of traversed IP
078 * addresses starting from the requesting client</td>
079 * <td>RemoteIPHeader</td>
080 * <td>Compliant http header name</td>
081 * <td>x-forwarded-for</td>
082 * </tr>
083 * <tr>
084 * <td>allowedInternalProxies</td>
085 * <td>List of internal proxies ip adress. If they appear in the <code>remoteIpHeader</code> value,
086 * they will be trusted and will not appear in the <code>proxiesHeader</code> value</td>
087 * <td>RemoteIPInternalProxy</td>
088 * <td>Comma delimited list of regular expressions (in the syntax supported by the
089 * {@link java.util.regex.Pattern} library)</td>
090 * <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3},
091 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3},
092 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3} <br/>
093 * By default, 10/8, 192.168/16, 172.16/12, 169.254/16 and 127/8 are allowed</td>
094 * </tr>
095 * </tr>
096 * <tr>
097 * <td>proxiesHeader</td>
098 * <td>Name of the http header created by this servlet filter to hold the list of proxies that have
099 * been processed in the incoming <code>remoteIPHeader</code></td>
100 * <td>RemoteIPProxiesHeader</td>
101 * <td>Compliant http header name</td>
102 * <td>x-forwarded-by</td>
103 * </tr>
104 * <tr>
105 * <td>trustedProxies</td>
106 * <td>List of trusted proxies ip adress. If they appear in the <code>remoteIpHeader</code> value,
107 * they will be trusted and will appear in the <code>proxiesHeader</code> value</td>
108 * <td>RemoteIPTrustedProxy</td>
109 * <td>Comma delimited list of regular expressions (in the syntax supported by the
110 * {@link java.util.regex.Pattern} library)</td>
111 * <td>&nbsp;</td>
112 * </tr>
113 * <tr>
114 * <td>protocolHeader</td>
115 * <td>Name of the http header read by this servlet filter that holds the flag that this request</td>
116 * <td>N/A</td>
117 * <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code>
118 * or <code>Front-End-Https</code></td>
119 * <td><code>null</code></td>
120 * </tr>
121 * <tr>
122 * <td>protocolHeaderHttpsValue</td>
123 * <td>Value of the <code>protocolHeader</code> to indicate that it is an Https request</td>
124 * <td>N/A</td>
125 * <td>String like <code>https</code> or <code>ON</code></td>
126 * <td><code>https</code></td>
127 * </tr>
128 * <tr>
129 * <tr>
130 * <td>httpServerPort</td>
131 * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code>
132 * indicates <code>http</code> protocol</td>
133 * <td>N/A</td>
134 * <td>integer</td>
135 * <td>80</td>
136 * </tr>
137 * <tr>
138 * <td>httpsServerPort</td>
139 * <td>Value returned by {@link ServletRequest#getServerPort()} when the <code>protocolHeader</code>
140 * indicates <code>https</code> protocol</td>
141 * <td>N/A</td>
142 * <td>integer</td>
143 * <td>443</td>
144 * </tr>
145 * </table>
146 * </p>
147 * <p>
148 * <p>
149 * <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to
150 * use address blocks (e.g. <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code>
151 * and <code>RemoteIPTrustedProxy</code> ; as the JVM doesnt have a library similar to <a href=
152 * "http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d"
153 * >apr_ipsubnet_test</a>.
154 * </p>
155 * <hr/>
156 * <p>
157 * <strong>Sample with internal proxies</strong>
158 * </p>
159 * <p>
160 * XForwardedFilter configuration:
161 * </p>
162 * <code><pre>
163 * &lt;filter&gt;
164 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
165 *    &lt;filter-class&gt;fr.xebia.servlet.filter.XForwardedFilter&lt;/filter-class&gt;
166 *    &lt;init-param&gt;
167 *       &lt;param-name&gt;allowedInternalProxies&lt;/param-name&gt;&lt;param-value&gt;192\.168\.0\.10, 192\.168\.0\.11&lt;/param-value&gt;
168 *    &lt;/init-param&gt;
169 *    &lt;init-param&gt;
170 *       &lt;param-name&gt;remoteIPHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
171 *    &lt;/init-param&gt;
172 *    &lt;init-param&gt;
173 *       &lt;param-name&gt;remoteIPProxiesHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
174 *    &lt;/init-param&gt;
175 *    &lt;init-param&gt;
176 *       &lt;param-name&gt;protocolHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-proto&lt;/param-value&gt;
177 *    &lt;/init-param&gt;
178 * &lt;/filter&gt;
179 * 
180 * &lt;filter-mapping&gt;
181 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
182 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
183 *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
184 * &lt;/filter-mapping&gt;</pre></code>
185 * <p>
186 * Request values:
187 * <table border="1">
188 * <caption>Request values</caption>
189 * <tr>
190 * <th>property</th>
191 * <th>Value Before XForwardedFilter</th>
192 * <th>Value After XForwardedFilter</th>
193 * </tr>
194 * <tr>
195 * <td>request.remoteAddr</td>
196 * <td>192.168.0.10</td>
197 * <td>140.211.11.130</td>
198 * </tr>
199 * <tr>
200 * <td>request.header['x-forwarded-for']</td>
201 * <td>140.211.11.130, 192.168.0.10</td>
202 * <td>null</td>
203 * </tr>
204 * <tr>
205 * <td>request.header['x-forwarded-by']</td>
206 * <td>null</td>
207 * <td>null</td>
208 * </tr>
209 * <tr>
210 * <td>request.header['x-forwarded-proto']</td>
211 * <td>https</td>
212 * <td>https</td>
213 * </tr>
214 * <tr>
215 * <td>request.scheme</td>
216 * <td>http</td>
217 * <td>https</td>
218 * </tr>
219 * <tr>
220 * <td>request.secure</td>
221 * <td>false</td>
222 * <td>true</td>
223 * </tr>
224 * <tr>
225 * <td>request.serverPort</td>
226 * <td>80</td>
227 * <td>443</td>
228 * </tr>
229 * </table>
230 * Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed
231 * by the request. <code>x-forwarded-by</code> is null because all the proxies are trusted or
232 * internal.
233 * </p>
234 * <hr/>
235 * <p>
236 * <strong>Sample with trusted proxies</strong>
237 * </p>
238 * <p>
239 * XForwardedFilter configuration:
240 * </p>
241 * <code><pre>
242 * &lt;filter&gt;
243 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
244 *    &lt;filter-class&gt;fr.xebia.servlet.filter.XForwardedFilter&lt;/filter-class&gt;
245 *    &lt;init-param&gt;
246 *       &lt;param-name&gt;allowedInternalProxies&lt;/param-name&gt;&lt;param-value&gt;192\.168\.0\.10, 192\.168\.0\.11&lt;/param-value&gt;
247 *    &lt;/init-param&gt;
248 *    &lt;init-param&gt;
249 *       &lt;param-name&gt;remoteIPHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
250 *    &lt;/init-param&gt;
251 *    &lt;init-param&gt;
252 *       &lt;param-name&gt;remoteIPProxiesHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
253 *    &lt;/init-param&gt;
254 *    &lt;init-param&gt;
255 *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;&lt;param-value&gt;proxy1, proxy2&lt;/param-value&gt;
256 *    &lt;/init-param&gt;
257 * &lt;/filter&gt;
258 * 
259 * &lt;filter-mapping&gt;
260 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
261 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
262 *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
263 * &lt;/filter-mapping&gt;</pre></code>
264 * <p>
265 * Request values:
266 * <table border="1">
267 * <caption>Request values</caption>
268 * <tr>
269 * <th>property</th>
270 * <th>Value Before XForwardedFilter</th>
271 * <th>Value After XForwardedFilter</th>
272 * </tr>
273 * <tr>
274 * <td>request.remoteAddr</td>
275 * <td>192.168.0.10</td>
276 * <td>140.211.11.130</td>
277 * </tr>
278 * <tr>
279 * <td>request.header['x-forwarded-for']</td>
280 * <td>140.211.11.130, proxy1, proxy2</td>
281 * <td>null</td>
282 * </tr>
283 * <tr>
284 * <td>request.header['x-forwarded-by']</td>
285 * <td>null</td>
286 * <td>proxy1, proxy2</td>
287 * </tr>
288 * </table>
289 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in
290 * <code>x-forwarded-for</code> header, they both are migrated in <code>x-forwarded-by</code>
291 * header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal.
292 * </p>
293 * <hr/>
294 * <p>
295 * <strong>Sample with internal and trusted proxies</strong>
296 * </p>
297 * <p>
298 * XForwardedFilter configuration:
299 * </p>
300 * <code><pre>
301 * &lt;filter&gt;
302 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
303 *    &lt;filter-class&gt;fr.xebia.servlet.filter.XForwardedFilter&lt;/filter-class&gt;
304 *    &lt;init-param&gt;
305 *       &lt;param-name&gt;allowedInternalProxies&lt;/param-name&gt;&lt;param-value&gt;192\.168\.0\.10, 192\.168\.0\.11&lt;/param-value&gt;
306 *    &lt;/init-param&gt;
307 *    &lt;init-param&gt;
308 *       &lt;param-name&gt;remoteIPHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
309 *    &lt;/init-param&gt;
310 *    &lt;init-param&gt;
311 *       &lt;param-name&gt;remoteIPProxiesHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
312 *    &lt;/init-param&gt;
313 *    &lt;init-param&gt;
314 *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;&lt;param-value&gt;proxy1, proxy2&lt;/param-value&gt;
315 *    &lt;/init-param&gt;
316 * &lt;/filter&gt;
317 * 
318 * &lt;filter-mapping&gt;
319 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
320 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
321 *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
322 * &lt;/filter-mapping&gt;</pre></code>
323 * <p>
324 * Request values:
325 * <table border="1">
326 * <caption>Request values</caption>
327 * <tr>
328 * <th>property</th>
329 * <th>Value Before XForwardedFilter</th>
330 * <th>Value After XForwardedFilter</th>
331 * </tr>
332 * <tr>
333 * <td>request.remoteAddr</td>
334 * <td>192.168.0.10</td>
335 * <td>140.211.11.130</td>
336 * </tr>
337 * <tr>
338 * <td>request.header['x-forwarded-for']</td>
339 * <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td>
340 * <td>null</td>
341 * </tr>
342 * <tr>
343 * <td>request.header['x-forwarded-by']</td>
344 * <td>null</td>
345 * <td>proxy1, proxy2</td>
346 * </tr>
347 * </table>
348 * Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in
349 * <code>x-forwarded-for</code> header, they both are migrated in <code>x-forwarded-by</code>
350 * header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in
351 * <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are
352 * trusted or internal.
353 * </p>
354 * <hr/>
355 * <p>
356 * <strong>Sample with an untrusted proxy</strong>
357 * </p>
358 * <p>
359 * XForwardedFilter configuration:
360 * </p>
361 * <code><pre>
362 * &lt;filter&gt;
363 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
364 *    &lt;filter-class&gt;fr.xebia.servlet.filter.XForwardedFilter&lt;/filter-class&gt;
365 *    &lt;init-param&gt;
366 *       &lt;param-name&gt;allowedInternalProxies&lt;/param-name&gt;&lt;param-value&gt;192\.168\.0\.10, 192\.168\.0\.11&lt;/param-value&gt;
367 *    &lt;/init-param&gt;
368 *    &lt;init-param&gt;
369 *       &lt;param-name&gt;remoteIPHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-for&lt;/param-value&gt;
370 *    &lt;/init-param&gt;
371 *    &lt;init-param&gt;
372 *       &lt;param-name&gt;remoteIPProxiesHeader&lt;/param-name&gt;&lt;param-value&gt;x-forwarded-by&lt;/param-value&gt;
373 *    &lt;/init-param&gt;
374 *    &lt;init-param&gt;
375 *       &lt;param-name&gt;trustedProxies&lt;/param-name&gt;&lt;param-value&gt;proxy1, proxy2&lt;/param-value&gt;
376 *    &lt;/init-param&gt;
377 * &lt;/filter&gt;
378 * 
379 * &lt;filter-mapping&gt;
380 *    &lt;filter-name&gt;XForwardedFilter&lt;/filter-name&gt;
381 *    &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
382 *    &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
383 * &lt;/filter-mapping&gt;</pre></code>
384 * <p>
385 * Request values:
386 * <table border="1">
387 * <caption>Request values</caption>
388 * <tr>
389 * <th>property</th>
390 * <th>Value Before XForwardedFilter</th>
391 * <th>Value After XForwardedFilter</th>
392 * </tr>
393 * <tr>
394 * <td>request.remoteAddr</td>
395 * <td>192.168.0.10</td>
396 * <td>untrusted-proxy</td>
397 * </tr>
398 * <tr>
399 * <td>request.header['x-forwarded-for']</td>
400 * <td>140.211.11.130, untrusted-proxy, proxy1</td>
401 * <td>140.211.11.130</td>
402 * </tr>
403 * <tr>
404 * <td>request.header['x-forwarded-by']</td>
405 * <td>null</td>
406 * <td>proxy1</td>
407 * </tr>
408 * </table>
409 * Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>.
410 * <code>x-forwarded-by</code> holds <code>140.211.11.130</code> because
411 * <code>untrusted-proxy</code> is not trusted and thus, we can not trust that
412 * <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is
413 * <code>untrusted-proxy</code> that is an IP verified by <code>proxy1</code>.
414 * </p>
415 * <hr/>
416 * 
417 * @author <a href="mailto:cyrille@cyrilleleclerc.com">Cyrille Le Clerc</a>
418 * @author Juergen Donnerstag
419 */
420public class XForwardedRequestWrapperFactory extends AbstractRequestWrapperFactory
421{
422        /** Logger */
423        private static final Logger log = LoggerFactory.getLogger(XForwardedRequestWrapperFactory.class);
424
425        protected static final String HTTP_SERVER_PORT_PARAMETER = "httpServerPort";
426
427        protected static final String HTTPS_SERVER_PORT_PARAMETER = "httpsServerPort";
428
429        protected static final String INTERNAL_PROXIES_PARAMETER = "allowedInternalProxies";
430
431        protected static final String PROTOCOL_HEADER_PARAMETER = "protocolHeader";
432
433        protected static final String PROTOCOL_HEADER_SSL_VALUE_PARAMETER = "protocolHeaderSslValue";
434
435        protected static final String PROXIES_HEADER_PARAMETER = "proxiesHeader";
436
437        protected static final String REMOTE_IP_HEADER_PARAMETER = "remoteIPHeader";
438
439        protected static final String TRUSTED_PROXIES_PARAMETER = "trustedProxies";
440
441        /**
442         * Filter Config
443         */
444        public static class Config
445        {
446                // Enable / disable xforwarded functionality
447                private boolean enabled = true;
448
449                /** @see #setHttpServerPort(int) */
450                private int httpServerPort = 80;
451
452                /** @see #setHttpsServerPort(int) */
453                private int httpsServerPort = 443;
454
455                /** @see #setProtocolHeader(String) */
456                private String protocolHeader = null;
457
458                /** @see #setProtocolHeaderSslValue(String) */
459                private String protocolHeaderSslValue = "https";
460
461                /** @see #setProxiesHeader(String) */
462                private String proxiesHeader = "X-Forwarded-By";
463
464                /** @see #setRemoteIPHeader(String) */
465                private String remoteIPHeader = "X-Forwarded-For";
466
467                /** @see #setTrustedProxies(String) */
468                private Pattern[] trustedProxies = new Pattern[0];
469
470                /** @see #setTrustedProxies(String) */
471                private Pattern[] allowedInternalProxies = new Pattern[] {
472                                Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"),
473                                Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"),
474                                Pattern.compile("172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}"),
475                                Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"),
476                                Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") };
477
478                /**
479                 * Comma delimited list of internal proxies. Expressed with regular expressions.
480                 * <p>
481                 * Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3},
482                 * 172\\.(?:1[6-9]|2\\d|3[0-1]).\\d{1,3}.\\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3},
483                 * 127\.\d{1,3}\.\d{1,3}\.\d{1,3}
484                 * 
485                 * @param allowedInternalProxies
486                 */
487                public void setAllowedInternalProxies(final String allowedInternalProxies)
488                {
489                        this.allowedInternalProxies = commaDelimitedListToPatternArray(allowedInternalProxies);
490                }
491
492                /**
493                 * Server Port value if the {@link #protocolHeader} does not indicate HTTPS
494                 * <p>
495                 * Default value : 80
496                 * 
497                 * @param httpServerPort
498                 */
499                public void setHttpServerPort(final int httpServerPort)
500                {
501                        this.httpServerPort = httpServerPort;
502                }
503
504                /**
505                 * Server Port value if the {@link #protocolHeader} indicates HTTPS
506                 * <p>
507                 * Default value : 443
508                 * 
509                 * @param httpsServerPort
510                 */
511                public void setHttpsServerPort(final int httpsServerPort)
512                {
513                        this.httpsServerPort = httpsServerPort;
514                }
515
516                /**
517                 * Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If
518                 * <code>null</code>, request.scheme and request.secure will not be modified.
519                 * <p>
520                 * Default value : <code>null</code>
521                 * 
522                 * @param protocolHeader
523                 */
524                public void setProtocolHeader(final String protocolHeader)
525                {
526                        this.protocolHeader = protocolHeader;
527                }
528
529                /**
530                 * Case insensitive value of the protocol header to indicate that the incoming http request
531                 * uses SSL.
532                 * <p>
533                 * Default value : <code>HTTPS</code>
534                 * 
535                 * @param protocolHeaderSslValue
536                 */
537                public void setProtocolHeaderSslValue(final String protocolHeaderSslValue)
538                {
539                        this.protocolHeaderSslValue = protocolHeaderSslValue;
540                }
541
542                /**
543                 * The proxiesHeader directive specifies a header into which mod_remoteip will collect a
544                 * list of all of the intermediate client IP addresses trusted to resolve the actual remote
545                 * IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header,
546                 * while any intermediate RemoteIPInternalProxy addresses are discarded.
547                 * <p>
548                 * Name of the http header that holds the list of trusted proxies that has been traversed by
549                 * the http request.
550                 * <p>
551                 * The value of this header can be comma delimited.
552                 * <p>
553                 * Default value : <code>X-Forwarded-By</code>
554                 * 
555                 * @param proxiesHeader
556                 */
557                public void setProxiesHeader(final String proxiesHeader)
558                {
559                        this.proxiesHeader = proxiesHeader;
560                }
561
562                /**
563                 * Name of the http header from which the remote ip is extracted.
564                 * <p>
565                 * The value of this header can be comma delimited.
566                 * <p>
567                 * Default value : <code>X-Forwarded-For</code>
568                 * 
569                 * @param remoteIPHeader
570                 */
571                public void setRemoteIPHeader(final String remoteIPHeader)
572                {
573                        this.remoteIPHeader = remoteIPHeader;
574                }
575
576                /**
577                 * Comma delimited list of proxies that are trusted when they appear in the
578                 * {@link #remoteIPHeader} header. Can be expressed as a regular expression.
579                 * <p>
580                 * Default value : empty list, no external proxy is trusted.
581                 * 
582                 * @param trustedProxies
583                 */
584                public void setTrustedProxies(final String trustedProxies)
585                {
586                        this.trustedProxies = commaDelimitedListToPatternArray(trustedProxies);
587                }
588
589                /**
590                 * Enable / disable XForwarded related processing
591                 * 
592                 * @param enable
593                 */
594                public void setEnabled(boolean enable)
595                {
596                        enabled = enable;
597                }
598
599                /**
600                 * @return True, if filter is active
601                 */
602                public boolean isEnabled()
603                {
604                        return enabled;
605                }
606        }
607
608        // Filter Config
609        private Config config = new Config();
610
611        /**
612         * Construct.
613         */
614        public XForwardedRequestWrapperFactory()
615        {
616        }
617
618        /**
619         * @return XForwarded filter specific config
620         */
621        public final Config getConfig()
622        {
623                return config;
624        }
625
626        /**
627         * The Wicket application might want to provide its own config
628         * 
629         * @param config
630         */
631        public final void setConfig(final Config config)
632        {
633                this.config = config;
634        }
635
636        /**
637         * {@inheritDoc}
638         */
639        @Override
640        public boolean needsWrapper(final HttpServletRequest request)
641        {
642                boolean rtn = matchesOne(request.getRemoteAddr(), config.allowedInternalProxies);
643                if (rtn == false)
644                {
645                        if (log.isDebugEnabled())
646                        {
647                                log.debug("Skip XForwardedFilter for request " + request.getRequestURI() +
648                                        " with remote address " + request.getRemoteAddr());
649                        }
650                }
651                return rtn;
652        }
653
654        /**
655         * 
656         * @param request
657         * @return Either the original request or the wrapper
658         */
659        @Override
660        public HttpServletRequest newRequestWrapper(final HttpServletRequest request)
661        {
662                String remoteIp = null;
663
664                // In java 6, proxiesHeaderValue should be declared as a java.util.Deque
665                LinkedList<String> proxiesHeaderValue = new LinkedList<String>();
666
667                String[] remoteIPHeaderValue = commaDelimitedListToStringArray(request.getHeader(config.remoteIPHeader));
668
669                // loop on remoteIPHeaderValue to find the first trusted remote ip and to build the
670                // proxies chain
671                int idx;
672                for (idx = remoteIPHeaderValue.length - 1; idx >= 0; idx--)
673                {
674                        String currentRemoteIp = remoteIPHeaderValue[idx];
675                        remoteIp = currentRemoteIp;
676                        if (matchesOne(currentRemoteIp, config.allowedInternalProxies))
677                        {
678                                // do nothing, allowedInternalProxies IPs are not appended to the
679                        }
680                        else if (matchesOne(currentRemoteIp, config.trustedProxies))
681                        {
682                                proxiesHeaderValue.addFirst(currentRemoteIp);
683                        }
684                        else
685                        {
686                                idx--; // decrement idx because break statement doesn't do it
687                                break;
688                        }
689                }
690
691                // continue to loop on remoteIPHeaderValue to build the new value of the remoteIPHeader
692                LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>();
693                for (; idx >= 0; idx--)
694                {
695                        String currentRemoteIp = remoteIPHeaderValue[idx];
696                        newRemoteIpHeaderValue.addFirst(currentRemoteIp);
697                }
698
699                XForwardedRequestWrapper xRequest = new XForwardedRequestWrapper(request);
700                if (remoteIp != null)
701                {
702                        xRequest.setRemoteAddr(remoteIp);
703                        xRequest.setRemoteHost(remoteIp);
704
705                        if (proxiesHeaderValue.size() == 0)
706                        {
707                                xRequest.removeHeader(config.proxiesHeader);
708                        }
709                        else
710                        {
711                                String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue);
712                                xRequest.setHeader(config.proxiesHeader, commaDelimitedListOfProxies);
713                        }
714                        if (newRemoteIpHeaderValue.size() == 0)
715                        {
716                                xRequest.removeHeader(config.remoteIPHeader);
717                        }
718                        else
719                        {
720                                String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue);
721                                xRequest.setHeader(config.remoteIPHeader, commaDelimitedRemoteIpHeaderValue);
722                        }
723                }
724
725                if (config.protocolHeader != null)
726                {
727                        String protocolHeaderValue = request.getHeader(config.protocolHeader);
728                        if (protocolHeaderValue == null)
729                        {
730                                // don't modify the secure,scheme and serverPort attributes of the request
731                        }
732                        else if (config.protocolHeaderSslValue.equalsIgnoreCase(protocolHeaderValue))
733                        {
734                                xRequest.setSecure(true);
735                                xRequest.setScheme("https");
736                                xRequest.setServerPort(config.httpsServerPort);
737                        }
738                        else
739                        {
740                                xRequest.setSecure(false);
741                                xRequest.setScheme("http");
742                                xRequest.setServerPort(config.httpServerPort);
743                        }
744                }
745
746                if (log.isDebugEnabled())
747                {
748                        log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" +
749                                request.getRemoteAddr() + "', originalRemoteHost='" + request.getRemoteHost() +
750                                "', originalSecure='" + request.isSecure() + "', originalScheme='" +
751                                request.getScheme() + "', original[" + config.remoteIPHeader + "]='" +
752                                request.getHeader(config.remoteIPHeader) + ", original[" + config.protocolHeader +
753                                "]='" +
754                                (config.protocolHeader == null ? null : request.getHeader(config.protocolHeader)) +
755                                "' will be seen as newRemoteAddr='" + xRequest.getRemoteAddr() +
756                                "', newRemoteHost='" + xRequest.getRemoteHost() + "', newScheme='" +
757                                xRequest.getScheme() + "', newSecure='" + xRequest.isSecure() + "', new[" +
758                                config.remoteIPHeader + "]='" + xRequest.getHeader(config.remoteIPHeader) +
759                                ", new[" + config.proxiesHeader + "]='" + xRequest.getHeader(config.proxiesHeader) +
760                                "'");
761                }
762                return xRequest;
763        }
764
765        /**
766         * 
767         * @param filterConfig
768         */
769        public void init(final FilterConfig filterConfig)
770        {
771                if (filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER) != null)
772                {
773                        config.setAllowedInternalProxies(filterConfig.getInitParameter(INTERNAL_PROXIES_PARAMETER));
774                }
775
776                if (filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER) != null)
777                {
778                        config.setProtocolHeader(filterConfig.getInitParameter(PROTOCOL_HEADER_PARAMETER));
779                }
780
781                if (filterConfig.getInitParameter(PROTOCOL_HEADER_SSL_VALUE_PARAMETER) != null)
782                {
783                        config.setProtocolHeaderSslValue(filterConfig.getInitParameter(PROTOCOL_HEADER_SSL_VALUE_PARAMETER));
784                }
785
786                if (filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER) != null)
787                {
788                        config.setProxiesHeader(filterConfig.getInitParameter(PROXIES_HEADER_PARAMETER));
789                }
790
791                if (filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER) != null)
792                {
793                        config.setRemoteIPHeader(filterConfig.getInitParameter(REMOTE_IP_HEADER_PARAMETER));
794                }
795
796                if (filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER) != null)
797                {
798                        config.setTrustedProxies(filterConfig.getInitParameter(TRUSTED_PROXIES_PARAMETER));
799                }
800
801                if (filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER) != null)
802                {
803                        try
804                        {
805                                config.setHttpServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTP_SERVER_PORT_PARAMETER)));
806                        }
807                        catch (NumberFormatException e)
808                        {
809                                throw new NumberFormatException("Illegal " + HTTP_SERVER_PORT_PARAMETER + " : " +
810                                        e.getMessage());
811                        }
812                }
813
814                if (filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER) != null)
815                {
816                        try
817                        {
818                                config.setHttpsServerPort(Integer.parseInt(filterConfig.getInitParameter(HTTPS_SERVER_PORT_PARAMETER)));
819                        }
820                        catch (NumberFormatException e)
821                        {
822                                throw new NumberFormatException("Illegal " + HTTPS_SERVER_PORT_PARAMETER + " : " +
823                                        e.getMessage());
824                        }
825                }
826        }
827}