Line 0
Link Here
|
|
|
1 |
/* |
2 |
* Licensed to the Apache Software Foundation (ASF) under one or more |
3 |
* contributor license agreements. See the NOTICE file distributed with |
4 |
* this work for additional information regarding copyright ownership. |
5 |
* The ASF licenses this file to You under the Apache License, Version 2.0 |
6 |
* (the "License"); you may not use this file except in compliance with |
7 |
* the License. You may obtain a copy of the License at |
8 |
* |
9 |
* http://www.apache.org/licenses/LICENSE-2.0 |
10 |
* |
11 |
* Unless required by applicable law or agreed to in writing, software |
12 |
* distributed under the License is distributed on an "AS IS" BASIS, |
13 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
* See the License for the specific language governing permissions and |
15 |
* limitations under the License. |
16 |
*/ |
17 |
|
18 |
package org.apache.catalina.valves; |
19 |
|
20 |
import java.io.IOException; |
21 |
import java.util.ArrayList; |
22 |
import java.util.Iterator; |
23 |
import java.util.LinkedList; |
24 |
import java.util.List; |
25 |
import java.util.regex.Pattern; |
26 |
import java.util.regex.PatternSyntaxException; |
27 |
|
28 |
import javax.servlet.ServletException; |
29 |
|
30 |
import org.apache.tomcat.util.res.StringManager; |
31 |
import org.apache.catalina.connector.Request; |
32 |
import org.apache.catalina.connector.Response; |
33 |
import org.apache.catalina.valves.Constants; |
34 |
import org.apache.catalina.valves.RequestFilterValve; |
35 |
import org.apache.catalina.valves.ValveBase; |
36 |
import org.apache.juli.logging.Log; |
37 |
import org.apache.juli.logging.LogFactory; |
38 |
|
39 |
/** |
40 |
* <p> |
41 |
* Tomcat port of <a href="http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html">mod_remoteip</a>, this valve replaces the apparent |
42 |
* client remote IP address and hostname for the request with the IP address list presented by a proxy or a load balancer via a request |
43 |
* headers (e.g. "X-Forwarded-For"). |
44 |
* </p> |
45 |
* <p> |
46 |
* Another feature of this valve is to replace the apparent scheme (http/https) and server port with the scheme presented by a proxy or a |
47 |
* load balancer via a request header (e.g. "X-Forwarded-Proto"). |
48 |
* </p> |
49 |
* <p> |
50 |
* This valve proceeds as follows: |
51 |
* </p> |
52 |
* <p> |
53 |
* If the incoming <code>request.getRemoteAddr()</code> matches the valve's list of internal proxies : |
54 |
* <ul> |
55 |
* <li>Loop on the comma delimited list of IPs and hostnames passed by the preceding load balancer or proxy in the given request's Http |
56 |
* header named <code>$remoteIPHeader</code> (default value <code>x-forwarded-for</code>). Values are processed in right-to-left order.</li> |
57 |
* <li>For each ip/host of the list: |
58 |
* <ul> |
59 |
* <li>if it matches the internal proxies list, the ip/host is swallowed</li> |
60 |
* <li>if it matches the trusted proxies list, the ip/host is added to the created proxies header</li> |
61 |
* <li>otherwise, the ip/host is declared to be the remote ip and looping is stopped.</li> |
62 |
* </ul> |
63 |
* </li> |
64 |
* <li>If the request http header named <code>$protocolHeader</code> (e.g. <code>x-forwarded-for</code>) equals to the value of |
65 |
* <code>protocolHeaderHttpsValue</code> configuration parameter (default <code>https</code>) then <code>request.isSecure = true</code>, |
66 |
* <code>request.scheme = https</code> and <code>request.serverPort = 443</code>. Note that 443 can be overwritten with the |
67 |
* <code>$httpsServerPort</code> configuration parameter.</li> |
68 |
* </ul> |
69 |
* </p> |
70 |
* <p> |
71 |
* <strong>Configuration parameters:</strong> |
72 |
* <table border="1"> |
73 |
* <tr> |
74 |
* <th>RemoteIpValve property</th> |
75 |
* <th>Description</th> |
76 |
* <th>Equivalent mod_remoteip directive</th> |
77 |
* <th>Format</th> |
78 |
* <th>Default Value</th> |
79 |
* </tr> |
80 |
* <tr> |
81 |
* <td>remoteIPHeader</td> |
82 |
* <td>Name of the Http Header read by this valve that holds the list of traversed IP addresses starting from the requesting client</td> |
83 |
* <td>RemoteIPHeader</td> |
84 |
* <td>Compliant http header name</td> |
85 |
* <td>x-forwarded-for</td> |
86 |
* </tr> |
87 |
* <tr> |
88 |
* <td>internalProxies</td> |
89 |
* <td>List of internal proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, they will be trusted and will not appear |
90 |
* in the <code>proxiesHeader</code> value</td> |
91 |
* <td>RemoteIPInternalProxy</td> |
92 |
* <td>Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library)</td> |
93 |
* <td>10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 169\.254\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3} <br/> |
94 |
* By default, 10/8, 192.168/16, 169.254/16 and 127/8 are allowed ; 172.16/12 has not been enabled by default because it is complex to |
95 |
* describe with regular expressions</td> |
96 |
* </tr> |
97 |
* </tr> |
98 |
* <tr> |
99 |
* <td>proxiesHeader</td> |
100 |
* <td>Name of the http header created by this valve to hold the list of proxies that have been processed in the incoming |
101 |
* <code>remoteIPHeader</code></td> |
102 |
* <td>RemoteIPProxiesHeader</td> |
103 |
* <td>Compliant http header name</td> |
104 |
* <td>x-forwarded-by</td> |
105 |
* </tr> |
106 |
* <tr> |
107 |
* <td>trustedProxies</td> |
108 |
* <td>List of trusted proxies ip adress. If they appear in the <code>remoteIpHeader</code> value, they will be trusted and will appear |
109 |
* in the <code>proxiesHeader</code> value</td> |
110 |
* <td>RemoteIPTrustedProxy</td> |
111 |
* <td>Comma delimited list of regular expressions (in the syntax supported by the {@link java.util.regex.Pattern} library)</td> |
112 |
* <td> </td> |
113 |
* </tr> |
114 |
* <tr> |
115 |
* <td>protocolHeader</td> |
116 |
* <td>Name of the http header read by this valve that holds the flag that this request </td> |
117 |
* <td>N/A</td> |
118 |
* <td>Compliant http header name like <code>X-Forwarded-Proto</code>, <code>X-Forwarded-Ssl</code> 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 |
* </table> |
130 |
* </p> |
131 |
* <p> |
132 |
* <p> |
133 |
* This Valve may be attached to any Container, depending on the granularity of the filtering you wish to perform. |
134 |
* </p> |
135 |
* <p> |
136 |
* <strong>Regular expression vs. IP address blocks:</strong> <code>mod_remoteip</code> allows to use address blocks (e.g. |
137 |
* <code>192.168/16</code>) to configure <code>RemoteIPInternalProxy</code> and <code>RemoteIPTrustedProxy</code> ; as Tomcat doesn't have a |
138 |
* library similar to <a |
139 |
* href="http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#gb74d21b8898b7c40bf7fd07ad3eb993d">apr_ipsubnet_test</a>, |
140 |
* <code>RemoteIpValve</code> uses regular expression to configure <code>internalProxies</code> and <code>trustedProxies</code> in the same |
141 |
* fashion as {@link RequestFilterValve} does. |
142 |
* </p> |
143 |
* <hr/> |
144 |
* <p> |
145 |
* <strong>Sample with internal proxies</strong> |
146 |
* </p> |
147 |
* <p> |
148 |
* RemoteIpValve configuration: |
149 |
* </p> |
150 |
* <code><pre> |
151 |
* <Valve |
152 |
* className="org.apache.catalina.connector.RemoteIpValve" |
153 |
* internalProxies="192\.168\.0\.10, 192\.168\.0\.11" |
154 |
* remoteIPHeader="x-forwarded-for" |
155 |
* remoteIPProxiesHeader="x-forwarded-by" |
156 |
* protocolHeader="x-forwarded-proto" |
157 |
* /></pre></code> |
158 |
* <p> |
159 |
* Request values: |
160 |
* <table border="1"> |
161 |
* <tr> |
162 |
* <th>property</th> |
163 |
* <th>Value Before RemoteIpValve</th> |
164 |
* <th>Value After RemoteIpValve</th> |
165 |
* </tr> |
166 |
* <tr> |
167 |
* <td>request.remoteAddr</td> |
168 |
* <td>192.168.0.10</td> |
169 |
* <td>140.211.11.130</td> |
170 |
* </tr> |
171 |
* <tr> |
172 |
* <td>request.header['x-forwarded-for']</td> |
173 |
* <td>140.211.11.130, 192.168.0.10</td> |
174 |
* <td>null</td> |
175 |
* </tr> |
176 |
* <tr> |
177 |
* <td>request.header['x-forwarded-by']</td> |
178 |
* <td>null</td> |
179 |
* <td>null</td> |
180 |
* </tr> |
181 |
* <tr> |
182 |
* <td>request.header['x-forwarded-proto']</td> |
183 |
* <td>https</td> |
184 |
* <td>https</td> |
185 |
* </tr> |
186 |
* <tr> |
187 |
* <td>request.scheme</td> |
188 |
* <td>http</td> |
189 |
* <td>https</td> |
190 |
* </tr> |
191 |
* <tr> |
192 |
* <td>request.secure</td> |
193 |
* <td>false</td> |
194 |
* <td>true</td> |
195 |
* </tr> |
196 |
* <tr> |
197 |
* <td>request.serverPort</td> |
198 |
* <td>80</td> |
199 |
* <td>443</td> |
200 |
* </tr> |
201 |
* </table> |
202 |
* Note : <code>x-forwarded-by</code> header is null because only internal proxies as been traversed by the request. |
203 |
* <code>x-forwarded-by</code> is null because all the proxies are trusted or internal. |
204 |
* </p> |
205 |
* <hr/> |
206 |
* <p> |
207 |
* <strong>Sample with trusted proxies</strong> |
208 |
* </p> |
209 |
* <p> |
210 |
* RemoteIpValve configuration: |
211 |
* </p> |
212 |
* <code><pre> |
213 |
* <Valve |
214 |
* className="org.apache.catalina.connector.RemoteIpValve" |
215 |
* internalProxies="192\.168\.0\.10, 192\.168\.0\.11" |
216 |
* remoteIPHeader="x-forwarded-for" |
217 |
* remoteIPProxiesHeader="x-forwarded-by" |
218 |
* trustedProxies="proxy1, proxy2" |
219 |
* /></pre></code> |
220 |
* <p> |
221 |
* Request values: |
222 |
* <table border="1"> |
223 |
* <tr> |
224 |
* <th>property</th> |
225 |
* <th>Value Before RemoteIpValve</th> |
226 |
* <th>Value After RemoteIpValve</th> |
227 |
* </tr> |
228 |
* <tr> |
229 |
* <td>request.remoteAddr</td> |
230 |
* <td>192.168.0.10</td> |
231 |
* <td>140.211.11.130</td> |
232 |
* </tr> |
233 |
* <tr> |
234 |
* <td>request.header['x-forwarded-for']</td> |
235 |
* <td>140.211.11.130, proxy1, proxy2</td> |
236 |
* <td>null</td> |
237 |
* </tr> |
238 |
* <tr> |
239 |
* <td>request.header['x-forwarded-by']</td> |
240 |
* <td>null</td> |
241 |
* <td>proxy1, proxy2</td> |
242 |
* </tr> |
243 |
* </table> |
244 |
* Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both |
245 |
* are migrated in <code>x-forwarded-by</code> header. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal. |
246 |
* </p> |
247 |
* <hr/> |
248 |
* <p> |
249 |
* <strong>Sample with internal and trusted proxies</strong> |
250 |
* </p> |
251 |
* <p> |
252 |
* RemoteIpValve configuration: |
253 |
* </p> |
254 |
* <code><pre> |
255 |
* <Valve |
256 |
* className="org.apache.catalina.connector.RemoteIpValve" |
257 |
* internalProxies="192\.168\.0\.10, 192\.168\.0\.11" |
258 |
* remoteIPHeader="x-forwarded-for" |
259 |
* remoteIPProxiesHeader="x-forwarded-by" |
260 |
* trustedProxies="proxy1, proxy2" |
261 |
* /></pre></code> |
262 |
* <p> |
263 |
* Request values: |
264 |
* <table border="1"> |
265 |
* <tr> |
266 |
* <th>property</th> |
267 |
* <th>Value Before RemoteIpValve</th> |
268 |
* <th>Value After RemoteIpValve</th> |
269 |
* </tr> |
270 |
* <tr> |
271 |
* <td>request.remoteAddr</td> |
272 |
* <td>192.168.0.10</td> |
273 |
* <td>140.211.11.130</td> |
274 |
* </tr> |
275 |
* <tr> |
276 |
* <td>request.header['x-forwarded-for']</td> |
277 |
* <td>140.211.11.130, proxy1, proxy2, 192.168.0.10</td> |
278 |
* <td>null</td> |
279 |
* </tr> |
280 |
* <tr> |
281 |
* <td>request.header['x-forwarded-by']</td> |
282 |
* <td>null</td> |
283 |
* <td>proxy1, proxy2</td> |
284 |
* </tr> |
285 |
* </table> |
286 |
* Note : <code>proxy1</code> and <code>proxy2</code> are both trusted proxies that come in <code>x-forwarded-for</code> header, they both |
287 |
* are migrated in <code>x-forwarded-by</code> header. As <code>192.168.0.10</code> is an internal proxy, it does not appear in |
288 |
* <code>x-forwarded-by</code>. <code>x-forwarded-by</code> is null because all the proxies are trusted or internal. |
289 |
* </p> |
290 |
* <hr/> |
291 |
* <p> |
292 |
* <strong>Sample with an untrusted proxy</strong> |
293 |
* </p> |
294 |
* <p> |
295 |
* RemoteIpValve configuration: |
296 |
* </p> |
297 |
* <code><pre> |
298 |
* <Valve |
299 |
* className="org.apache.catalina.connector.RemoteIpValve" |
300 |
* internalProxies="192\.168\.0\.10, 192\.168\.0\.11" |
301 |
* remoteIPHeader="x-forwarded-for" |
302 |
* remoteIPProxiesHeader="x-forwarded-by" |
303 |
* trustedProxies="proxy1, proxy2" |
304 |
* /></pre></code> |
305 |
* <p> |
306 |
* Request values: |
307 |
* <table border="1"> |
308 |
* <tr> |
309 |
* <th>property</th> |
310 |
* <th>Value Before RemoteIpValve</th> |
311 |
* <th>Value After RemoteIpValve</th> |
312 |
* </tr> |
313 |
* <tr> |
314 |
* <td>request.remoteAddr</td> |
315 |
* <td>192.168.0.10</td> |
316 |
* <td>untrusted-proxy</td> |
317 |
* </tr> |
318 |
* <tr> |
319 |
* <td>request.header['x-forwarded-for']</td> |
320 |
* <td>140.211.11.130, untrusted-proxy, proxy1</td> |
321 |
* <td>140.211.11.130</td> |
322 |
* </tr> |
323 |
* <tr> |
324 |
* <td>request.header['x-forwarded-by']</td> |
325 |
* <td>null</td> |
326 |
* <td>proxy1</td> |
327 |
* </tr> |
328 |
* </table> |
329 |
* Note : <code>x-forwarded-by</code> holds the trusted proxy <code>proxy1</code>. <code>x-forwarded-by</code> holds |
330 |
* <code>140.211.11.130</code> because <code>untrusted-proxy</code> is not trusted and thus, we can not trust that |
331 |
* <code>untrusted-proxy</code> is the actual remote ip. <code>request.remoteAddr</code> is <code>untrusted-proxy</code> that is an IP |
332 |
* verified by <code>proxy1</code>. |
333 |
* </p> |
334 |
*/ |
335 |
public class RemoteIpValve extends ValveBase { |
336 |
|
337 |
/** |
338 |
* {@link Pattern} for a comma delimited string that support whitespace characters |
339 |
*/ |
340 |
private static final Pattern commaSeparatedValuesPattern = Pattern.compile("\\s*,\\s*"); |
341 |
|
342 |
/** |
343 |
* The descriptive information related to this implementation. |
344 |
*/ |
345 |
private static final String info = "org.apache.catalina.connector.RemoteIpValve/1.0"; |
346 |
|
347 |
/** |
348 |
* Logger |
349 |
*/ |
350 |
private static Log log = LogFactory.getLog(RemoteIpValve.class); |
351 |
|
352 |
/** |
353 |
* The StringManager for this package. |
354 |
*/ |
355 |
protected static StringManager sm = StringManager.getManager(Constants.Package); |
356 |
|
357 |
/** |
358 |
* Convert a given comma delimited list of regular expressions into an array of compiled {@link Pattern} |
359 |
* |
360 |
* @return array of patterns (not <code>null</code>) |
361 |
*/ |
362 |
protected static Pattern[] commaDelimitedListToPatternArray(String commaDelimitedPatterns) { |
363 |
String[] patterns = commaDelimitedListToStringArray(commaDelimitedPatterns); |
364 |
List<Pattern> patternsList = new ArrayList<Pattern>(); |
365 |
for (String pattern : patterns) { |
366 |
try { |
367 |
patternsList.add(Pattern.compile(pattern)); |
368 |
} catch (PatternSyntaxException e) { |
369 |
throw new IllegalArgumentException(sm.getString("remoteIpValve.syntax", pattern), e); |
370 |
} |
371 |
} |
372 |
return patternsList.toArray(new Pattern[0]); |
373 |
} |
374 |
|
375 |
/** |
376 |
* Convert a given comma delimited list of regular expressions into an array of String |
377 |
* |
378 |
* @return array of patterns (non <code>null</code>) |
379 |
*/ |
380 |
protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) { |
381 |
return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern |
382 |
.split(commaDelimitedStrings); |
383 |
} |
384 |
|
385 |
/** |
386 |
* Convert an array of strings in a comma delimited string |
387 |
*/ |
388 |
protected static String listToCommaDelimitedString(List<String> stringList) { |
389 |
if (stringList == null) { |
390 |
return ""; |
391 |
} |
392 |
StringBuilder result = new StringBuilder(); |
393 |
for (Iterator<String> it = stringList.iterator(); it.hasNext();) { |
394 |
Object element = it.next(); |
395 |
if (element != null) { |
396 |
result.append(element); |
397 |
if (it.hasNext()) { |
398 |
result.append(", "); |
399 |
} |
400 |
} |
401 |
} |
402 |
return result.toString(); |
403 |
} |
404 |
|
405 |
/** |
406 |
* Return <code>true</code> if the given <code>str</code> matches at least one of the given <code>patterns</code>. |
407 |
*/ |
408 |
protected static boolean matchesOne(String str, Pattern... patterns) { |
409 |
for (Pattern pattern : patterns) { |
410 |
if (pattern.matcher(str).matches()) { |
411 |
return true; |
412 |
} |
413 |
} |
414 |
return false; |
415 |
} |
416 |
|
417 |
/** |
418 |
* @see #setHttpsServerPort(int) |
419 |
*/ |
420 |
private int httpsServerPort = 443; |
421 |
|
422 |
/** |
423 |
* @see #setInternalProxies(String) |
424 |
*/ |
425 |
private Pattern[] internalProxies = new Pattern[] { |
426 |
Pattern.compile("10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("192\\.168\\.\\d{1,3}\\.\\d{1,3}"), |
427 |
Pattern.compile("169\\.254\\.\\d{1,3}\\.\\d{1,3}"), Pattern.compile("127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}") |
428 |
}; |
429 |
|
430 |
/** |
431 |
* @see #setProtocolHeader(String) |
432 |
*/ |
433 |
private String protocolHeader = null; |
434 |
|
435 |
/** |
436 |
* @see #setProtocolHeaderHttpsValue(String) |
437 |
*/ |
438 |
private String protocolHeaderHttpsValue = "https"; |
439 |
|
440 |
/** |
441 |
* @see #setProxiesHeader(String) |
442 |
*/ |
443 |
private String proxiesHeader = "X-Forwarded-By"; |
444 |
|
445 |
/** |
446 |
* @see #setRemoteIpHeader(String) |
447 |
*/ |
448 |
private String remoteIpHeader = "X-Forwarded-For"; |
449 |
|
450 |
/** |
451 |
* @see RemoteIpValve#setTrustedProxies(String) |
452 |
*/ |
453 |
private Pattern[] trustedProxies = new Pattern[0]; |
454 |
|
455 |
public int getHttpsServerPort() { |
456 |
return httpsServerPort; |
457 |
} |
458 |
|
459 |
/** |
460 |
* Return descriptive information about this Valve implementation. |
461 |
*/ |
462 |
public String getInfo() { |
463 |
return info; |
464 |
} |
465 |
|
466 |
/** |
467 |
* @see #setInternalProxies(String) |
468 |
* @return comma delimited list of internal proxies |
469 |
*/ |
470 |
public String getInternalProxies() { |
471 |
List<String> internalProxiesAsStringList = new ArrayList<String>(); |
472 |
for (Pattern internalProxyPattern : internalProxies) { |
473 |
internalProxiesAsStringList.add(String.valueOf(internalProxyPattern)); |
474 |
} |
475 |
return listToCommaDelimitedString(internalProxiesAsStringList); |
476 |
} |
477 |
|
478 |
/** |
479 |
* @see #setProtocolHeader(String) |
480 |
* @return the protocol header (e.g. "X-Forwarded-Proto") |
481 |
*/ |
482 |
public String getProtocolHeader() { |
483 |
return protocolHeader; |
484 |
} |
485 |
|
486 |
/** |
487 |
* @see RemoteIpValve#setProtocolHeaderHttpsValue(String) |
488 |
* @return the value of the protocol header for incoming https request (e.g. "https") |
489 |
*/ |
490 |
public String getProtocolHeaderHttpsValue() { |
491 |
return protocolHeaderHttpsValue; |
492 |
} |
493 |
|
494 |
/** |
495 |
* @see #setProxiesHeader(String) |
496 |
* @return the proxies header name (e.g. "X-Forwarded-By") |
497 |
*/ |
498 |
public String getProxiesHeader() { |
499 |
return proxiesHeader; |
500 |
} |
501 |
|
502 |
/** |
503 |
* @see #setRemoteIpHeader(String) |
504 |
* @return the remote IP header name (e.g. "X-Forwarded-For") |
505 |
*/ |
506 |
public String getRemoteIpHeader() { |
507 |
return remoteIpHeader; |
508 |
} |
509 |
|
510 |
/** |
511 |
* @see #setTrustedProxies(String) |
512 |
* @return comma delimited list of trusted proxies |
513 |
*/ |
514 |
public String getTrustedProxies() { |
515 |
List<String> trustedProxiesAsStringList = new ArrayList<String>(); |
516 |
for (Pattern trustedProxy : trustedProxies) { |
517 |
trustedProxiesAsStringList.add(String.valueOf(trustedProxy)); |
518 |
} |
519 |
return listToCommaDelimitedString(trustedProxiesAsStringList); |
520 |
} |
521 |
|
522 |
/** |
523 |
* {@inheritDoc} |
524 |
*/ |
525 |
@Override |
526 |
public void invoke(Request request, Response response) throws IOException, ServletException { |
527 |
final String originalRemoteAddr = request.getRemoteAddr(); |
528 |
final String originalRemoteHost = request.getRemoteHost(); |
529 |
final String originalScheme = request.getScheme(); |
530 |
final boolean originalSecure = request.isSecure(); |
531 |
final int originalServerPort = request.getServerPort(); |
532 |
|
533 |
if (matchesOne(originalRemoteAddr, internalProxies)) { |
534 |
String remoteIp = null; |
535 |
// In java 6, proxiesHeaderValue should be declared as a java.util.Deque |
536 |
LinkedList<String> proxiesHeaderValue = new LinkedList<String>(); |
537 |
|
538 |
String[] remoteIPHeaderValue = commaDelimitedListToStringArray(request.getHeader(remoteIpHeader)); |
539 |
int idx; |
540 |
// loop on remoteIPHeaderValue to find the first trusted remote ip and to build the proxies chain |
541 |
for (idx = remoteIPHeaderValue.length - 1; idx >= 0; idx--) { |
542 |
String currentRemoteIp = remoteIPHeaderValue[idx]; |
543 |
remoteIp = currentRemoteIp; |
544 |
if (matchesOne(currentRemoteIp, internalProxies)) { |
545 |
// do nothing, internalProxies IPs are not appended to the |
546 |
} else if (matchesOne(currentRemoteIp, trustedProxies)) { |
547 |
proxiesHeaderValue.addFirst(currentRemoteIp); |
548 |
} else { |
549 |
idx--; // decrement idx because break statement doesn't do it |
550 |
break; |
551 |
} |
552 |
} |
553 |
// continue to loop on remoteIPHeaderValue to build the new value of the remoteIPHeader |
554 |
LinkedList<String> newRemoteIpHeaderValue = new LinkedList<String>(); |
555 |
for (; idx >= 0; idx--) { |
556 |
String currentRemoteIp = remoteIPHeaderValue[idx]; |
557 |
newRemoteIpHeaderValue.addFirst(currentRemoteIp); |
558 |
} |
559 |
if (remoteIp != null) { |
560 |
|
561 |
request.setRemoteAddr(remoteIp); |
562 |
request.setRemoteHost(remoteIp); |
563 |
|
564 |
// use request.coyoteRequest.mimeHeaders.setValue(str).setString(str) because request.addHeader(str, str) is no-op in Tomcat |
565 |
// 6.0 |
566 |
if (proxiesHeaderValue.size() == 0) { |
567 |
request.getCoyoteRequest().getMimeHeaders().removeHeader(proxiesHeader); |
568 |
} else { |
569 |
String commaDelimitedListOfProxies = listToCommaDelimitedString(proxiesHeaderValue); |
570 |
request.getCoyoteRequest().getMimeHeaders().setValue(proxiesHeader).setString(commaDelimitedListOfProxies); |
571 |
} |
572 |
if (newRemoteIpHeaderValue.size() == 0) { |
573 |
request.getCoyoteRequest().getMimeHeaders().removeHeader(remoteIpHeader); |
574 |
} else { |
575 |
String commaDelimitedRemoteIpHeaderValue = listToCommaDelimitedString(newRemoteIpHeaderValue); |
576 |
request.getCoyoteRequest().getMimeHeaders().setValue(remoteIpHeader).setString(commaDelimitedRemoteIpHeaderValue); |
577 |
} |
578 |
} |
579 |
|
580 |
if (protocolHeader != null) { |
581 |
String protocolHeaderValue = request.getHeader(protocolHeader); |
582 |
if (protocolHeaderValue != null && protocolHeaderHttpsValue.equalsIgnoreCase(protocolHeaderValue)) { |
583 |
request.setSecure(true); |
584 |
// use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 |
585 |
request.getCoyoteRequest().scheme().setString("https"); |
586 |
|
587 |
request.setServerPort(httpsServerPort); |
588 |
} |
589 |
} |
590 |
|
591 |
if (log.isDebugEnabled()) { |
592 |
log.debug("Incoming request " + request.getRequestURI() + " with originalRemoteAddr '" + originalRemoteAddr |
593 |
+ "', originalRemoteHost='" + originalRemoteHost + "', originalSecure='" + originalSecure + "', originalScheme='" |
594 |
+ originalScheme + "' will be seen as newRemoteAddr='" + request.getRemoteAddr() + "', newRemoteHost='" |
595 |
+ request.getRemoteHost() + "', newScheme='" + request.getScheme() + "', newSecure='" + request.isSecure() + "'"); |
596 |
} |
597 |
} |
598 |
try { |
599 |
getNext().invoke(request, response); |
600 |
} finally { |
601 |
request.setRemoteAddr(originalRemoteAddr); |
602 |
request.setRemoteHost(originalRemoteHost); |
603 |
|
604 |
request.setSecure(originalSecure); |
605 |
|
606 |
// use request.coyoteRequest.scheme instead of request.setScheme() because request.setScheme() is no-op in Tomcat 6.0 |
607 |
request.getCoyoteRequest().scheme().setString(originalScheme); |
608 |
|
609 |
request.setServerPort(originalServerPort); |
610 |
} |
611 |
} |
612 |
|
613 |
/** |
614 |
* <p> |
615 |
* Server Port value if the {@link #protocolHeader} indicates HTTPS |
616 |
* </p> |
617 |
* <p> |
618 |
* Default value : 443 |
619 |
* </p> |
620 |
*/ |
621 |
public void setHttpsServerPort(int httpsServerPort) { |
622 |
this.httpsServerPort = httpsServerPort; |
623 |
} |
624 |
|
625 |
/** |
626 |
* <p> |
627 |
* Comma delimited list of internal proxies. Can be expressed with regular expressions. |
628 |
* </p> |
629 |
* <p> |
630 |
* Default value : 10\.\d{1,3}\.\d{1,3}\.\d{1,3}, 192\.168\.\d{1,3}\.\d{1,3}, 127\.\d{1,3}\.\d{1,3}\.\d{1,3} |
631 |
* </p> |
632 |
*/ |
633 |
public void setInternalProxies(String commaDelimitedInternalProxies) { |
634 |
this.internalProxies = commaDelimitedListToPatternArray(commaDelimitedInternalProxies); |
635 |
} |
636 |
|
637 |
/** |
638 |
* <p> |
639 |
* Header that holds the incoming protocol, usally named <code>X-Forwarded-Proto</code>. If <code>null</code>, request.scheme and |
640 |
* request.secure will not be modified. |
641 |
* </p> |
642 |
* <p> |
643 |
* Default value : <code>null</code> |
644 |
* </p> |
645 |
*/ |
646 |
public void setProtocolHeader(String protocolHeader) { |
647 |
this.protocolHeader = protocolHeader; |
648 |
} |
649 |
|
650 |
/** |
651 |
* <p> |
652 |
* Case insensitive value of the protocol header to indicate that the incoming http request uses SSL. |
653 |
* </p> |
654 |
* <p> |
655 |
* Default value : <code>https</code> |
656 |
* </p> |
657 |
*/ |
658 |
public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) { |
659 |
this.protocolHeaderHttpsValue = protocolHeaderHttpsValue; |
660 |
} |
661 |
|
662 |
/** |
663 |
* <p> |
664 |
* The proxiesHeader directive specifies a header into which mod_remoteip will collect a list of all of the intermediate client IP |
665 |
* addresses trusted to resolve the actual remote IP. Note that intermediate RemoteIPTrustedProxy addresses are recorded in this header, |
666 |
* while any intermediate RemoteIPInternalProxy addresses are discarded. |
667 |
* </p> |
668 |
* <p> |
669 |
* Name of the http header that holds the list of trusted proxies that has been traversed by the http request. |
670 |
* </p> |
671 |
* <p> |
672 |
* The value of this header can be comma delimited. |
673 |
* </p> |
674 |
* <p> |
675 |
* Default value : <code>X-Forwarded-By</code> |
676 |
* </p> |
677 |
*/ |
678 |
public void setProxiesHeader(String proxiesHeader) { |
679 |
this.proxiesHeader = proxiesHeader; |
680 |
} |
681 |
|
682 |
/** |
683 |
* <p> |
684 |
* Name of the http header from which the remote ip is extracted. |
685 |
* </p> |
686 |
* <p> |
687 |
* The value of this header can be comma delimited. |
688 |
* </p> |
689 |
* <p> |
690 |
* Default value : <code>X-Forwarded-For</code> |
691 |
* </p> |
692 |
* |
693 |
* @param remoteIPHeader |
694 |
*/ |
695 |
public void setRemoteIpHeader(String remoteIpHeader) { |
696 |
this.remoteIpHeader = remoteIpHeader; |
697 |
} |
698 |
|
699 |
/** |
700 |
* <p> |
701 |
* Comma delimited list of proxies that are trusted when they appear in the {@link #remoteIPHeader} header. Can be expressed as a |
702 |
* regular expression. |
703 |
* </p> |
704 |
* <p> |
705 |
* Default value : empty list, no external proxy is trusted. |
706 |
* </p> |
707 |
*/ |
708 |
public void setTrustedProxies(String commaDelimitedTrustedProxies) { |
709 |
this.trustedProxies = commaDelimitedListToPatternArray(commaDelimitedTrustedProxies); |
710 |
} |
711 |
} |