Skip to content

Commit 9a0d0f5

Browse files
committed
Router: domain predicate + trustProxy option fix #1292
1 parent e853e6a commit 9a0d0f5

9 files changed

Lines changed: 391 additions & 47 deletions

File tree

jooby/src/main/java/io/jooby/Jooby.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,32 @@ public Jooby use(@Nonnull Router router) {
267267
return router;
268268
}
269269

270+
@Override public boolean isTrustProxy() {
271+
return router.isTrustProxy();
272+
}
273+
274+
@Nonnull @Override public Jooby setTrustProxy(boolean trustProxy) {
275+
this.router.setTrustProxy(trustProxy);
276+
return this;
277+
}
278+
279+
@Nonnull @Override public Router domain(@Nonnull String domain, @Nonnull Router subrouter) {
280+
this.router.domain(domain, subrouter);
281+
return this;
282+
}
283+
284+
@Nonnull @Override public RouteSet domain(@Nonnull String domain, @Nonnull Runnable body) {
285+
return router.domain(domain, body);
286+
}
287+
288+
@Nonnull @Override
289+
public RouteSet use(@Nonnull Predicate<Context> predicate, @Nonnull Runnable body) {
290+
return router.use(predicate, body);
291+
}
292+
270293
@Nonnull @Override
271-
public Jooby use(@Nonnull Predicate<Context> predicate, @Nonnull Router router) {
272-
this.router.use(predicate, router);
294+
public Jooby use(@Nonnull Predicate<Context> predicate, @Nonnull Router subrouter) {
295+
this.router.use(predicate, subrouter);
273296
return this;
274297
}
275298

@@ -452,7 +475,8 @@ public Jooby errorCode(@Nonnull Class<? extends Throwable> type,
452475

453476
@Nonnull @Override public Path getTmpdir() {
454477
if (tmpdir == null) {
455-
tmpdir = Paths.get(getEnvironment().getConfig().getString("application.tmpdir")).toAbsolutePath();
478+
tmpdir = Paths.get(getEnvironment().getConfig().getString("application.tmpdir"))
479+
.toAbsolutePath();
456480
}
457481
return tmpdir;
458482
}

jooby/src/main/java/io/jooby/ProxyPeerAddressHandler.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby;
27

38
import io.jooby.internal.ProxyPeerAddress;
@@ -26,11 +31,7 @@
2631
public class ProxyPeerAddressHandler implements Route.Decorator {
2732
@Nonnull @Override public Route.Handler apply(@Nonnull Route.Handler next) {
2833
return ctx -> {
29-
ProxyPeerAddress result = ProxyPeerAddress.parse(ctx);
30-
ctx.setRemoteAddress(result.getRemoteAddress());
31-
ctx.setHost(result.getHost());
32-
ctx.setScheme(result.getScheme());
33-
ctx.setPort(result.getPort());
34+
ProxyPeerAddress.parse(ctx).set(ctx);
3435
return next.apply(ctx);
3536
};
3637
}

jooby/src/main/java/io/jooby/Router.java

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,92 @@ interface Match {
163163
*/
164164
@Nonnull String getContextPath();
165165

166+
/**
167+
* When true handles X-Forwarded-* headers by updating the values on the current context to
168+
* match what was sent in the header(s).
169+
*
170+
* This should only be installed behind a reverse proxy that has been configured to send the
171+
* <code>X-Forwarded-*</code> header, otherwise a remote user can spoof their address by
172+
* sending a header with bogus values.
173+
*
174+
* The headers that are read/set are:
175+
* <ul>
176+
* <li>X-Forwarded-For: Set/update the remote address {@link Context#setRemoteAddress(String)}.</li>
177+
* <li>X-Forwarded-Proto: Set/update request scheme {@link Context#setScheme(String)}.</li>
178+
* <li>X-Forwarded-Host: Set/update the request host {@link Context#setHost(String)}.</li>
179+
* <li>X-Forwarded-Port: Set/update the request port {@link Context#setPort(int)}.</li>
180+
* </ul>
181+
*
182+
* @return True when enabled. Default is false.
183+
*/
184+
boolean isTrustProxy();
185+
186+
/**
187+
* When true handles X-Forwarded-* headers by updating the values on the current context to
188+
* match what was sent in the header(s).
189+
*
190+
* This should only be installed behind a reverse proxy that has been configured to send the
191+
* <code>X-Forwarded-*</code> header, otherwise a remote user can spoof their address by
192+
* sending a header with bogus values.
193+
*
194+
* The headers that are read/set are:
195+
* <ul>
196+
* <li>X-Forwarded-For: Set/update the remote address {@link Context#setRemoteAddress(String)}.</li>
197+
* <li>X-Forwarded-Proto: Set/update request scheme {@link Context#setScheme(String)}.</li>
198+
* <li>X-Forwarded-Host: Set/update the request host {@link Context#setHost(String)}.</li>
199+
* <li>X-Forwarded-Port: Set/update the request port {@link Context#setPort(int)}.</li>
200+
* </ul>
201+
*
202+
* @param trustProxy True to enabled.
203+
* @return This router.
204+
*/
205+
@Nonnull Router setTrustProxy(boolean trustProxy);
206+
166207
/* ***********************************************************************************************
167208
* use(Router)
168209
* ***********************************************************************************************
169210
*/
170211

212+
/**
213+
* Enabled routes for specific domain. Domain matching is done using the <code>host</code> header.
214+
*
215+
* <pre>{@code
216+
* {
217+
* domain("foo.com", new FooApp());
218+
* domain("bar.com", new BarApp());
219+
* }
220+
* }</pre>
221+
*
222+
* NOTE: if you run behind a reverse proxy you might to enabled {@link #setTrustProxy(boolean)}.
223+
*
224+
* @param domain Predicate
225+
* @param subrouter Subrouter.
226+
* @return This router.
227+
*/
228+
@Nonnull Router domain(@Nonnull String domain, @Nonnull Router subrouter);
229+
230+
/**
231+
* Enabled routes for specific domain. Domain matching is done using the <code>host</code> header.
232+
*
233+
* <pre>{@code
234+
* {
235+
* domain("foo.com", () -> {
236+
* get("/", ctx -> "foo");
237+
* });
238+
* domain("bar.com", () -> {
239+
* get("/", ctx -> "bar");
240+
* });
241+
* }
242+
* }</pre>
243+
*
244+
* NOTE: if you run behind a reverse proxy you might to enabled {@link #setTrustProxy(boolean)}.
245+
*
246+
* @param domain Predicate
247+
* @param body Route action.
248+
* @return This router.
249+
*/
250+
@Nonnull RouteSet domain(@Nonnull String domain, @Nonnull Runnable body);
251+
171252
/**
172253
* Import routes from given router. Predicate works like a filter and only when predicate pass
173254
* the routes match against the current request.
@@ -176,19 +257,45 @@ interface Match {
176257
*
177258
* <pre>{@code
178259
* {
179-
*
180260
* use(ctx -> ctx.getHost().equals("foo.com"), new FooApp());
181261
* use(ctx -> ctx.getHost().equals("bar.com"), new BarApp());
182262
* }
183263
* }</pre>
184264
*
185265
* Imported routes are matched only when predicate pass.
186266
*
267+
* NOTE: if you run behind a reverse proxy you might to enabled {@link #setTrustProxy(boolean)}.
268+
*
187269
* @param predicate Context predicate.
188-
* @param router Router to import.
270+
* @param subrouter Router to import.
271+
* @return This router.
272+
*/
273+
@Nonnull Router use(@Nonnull Predicate<Context> predicate, @Nonnull Router subrouter);
274+
275+
/**
276+
* Import routes from given action. Predicate works like a filter and only when predicate pass
277+
* the routes match against the current request.
278+
*
279+
* Example of domain predicate filter:
280+
*
281+
* <pre>{@code
282+
* {
283+
* use(ctx -> ctx.getHost().equals("foo.com"), () -> {
284+
* get("/", ctx -> "foo");
285+
* });
286+
* use(ctx -> ctx.getHost().equals("bar.com"), () -> {
287+
* get("/", ctx -> "bar");
288+
* });
289+
* }
290+
* }</pre>
291+
*
292+
* NOTE: if you run behind a reverse proxy you might to enabled {@link #setTrustProxy(boolean)}.
293+
*
294+
* @param predicate Context predicate.
295+
* @param body Route action.
189296
* @return This router.
190297
*/
191-
@Nonnull Router use(@Nonnull Predicate<Context> predicate, @Nonnull Router router);
298+
@Nonnull RouteSet use(@Nonnull Predicate<Context> predicate, @Nonnull Runnable body);
192299

193300
/**
194301
* Import all routes from the given router and prefix them with the given path.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal;
7+
8+
import io.jooby.Context;
9+
10+
public interface ContextInitializer {
11+
ContextInitializer PROXY_PEER_ADDRESS = ctx -> ProxyPeerAddress.parse(ctx).set(ctx);
12+
13+
void apply(Context ctx);
14+
15+
default ContextInitializer add(ContextInitializer initializer) {
16+
return initializer;
17+
}
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal;
7+
8+
import io.jooby.Context;
9+
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
13+
public class ContextInitializerList implements ContextInitializer {
14+
private List<ContextInitializer> initializers = new ArrayList<>(5);
15+
16+
public ContextInitializerList(ContextInitializer initializer) {
17+
add(initializer);
18+
}
19+
20+
@Override public ContextInitializer add(ContextInitializer initializer) {
21+
if (!initializers.contains(initializer)) {
22+
initializers.add(initializer);
23+
}
24+
return this;
25+
}
26+
27+
@Override public void apply(Context ctx) {
28+
for (ContextInitializer initializer : initializers) {
29+
initializer.apply(ctx);
30+
}
31+
}
32+
33+
public void remove(ContextInitializer initializer) {
34+
initializers.remove(initializer);
35+
}
36+
}

jooby/src/main/java/io/jooby/internal/ProxyPeerAddress.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
16
package io.jooby.internal;
27

38
import io.jooby.Context;
@@ -65,6 +70,13 @@ public int getPort() {
6570
return port;
6671
}
6772

73+
public void set(Context ctx) {
74+
ctx.setRemoteAddress(getRemoteAddress());
75+
ctx.setHost(getHost());
76+
ctx.setScheme(getScheme());
77+
ctx.setPort(getPort());
78+
}
79+
6880
public static ProxyPeerAddress parse(Context ctx) {
6981
ProxyPeerAddress result = new ProxyPeerAddress();
7082

0 commit comments

Comments
 (0)