206206import com .google .common .base .Strings ;
207207import com .typesafe .config .ConfigFactory ;
208208import com .typesafe .config .ConfigValueFactory ;
209- import static java .util .Objects .requireNonNull ;
210209import org .jooby .Asset ;
211210import org .jooby .Err ;
212211import org .jooby .Jooby ;
229228import java .util .Date ;
230229import java .util .Map ;
231230
231+ import static java .util .Objects .requireNonNull ;
232+
232233/**
233234 * Serve static resources, via {@link Jooby#assets(String)} or variants.
234235 *
@@ -287,6 +288,12 @@ private interface Loader {
287288
288289 private int statusCode = 404 ;
289290
291+ private String location ;
292+
293+ private Path basedir ;
294+
295+ private ClassLoader classLoader ;
296+
290297 /**
291298 * <p>
292299 * Creates a new {@link AssetHandler}. The handler accepts a location pattern, that serve for
@@ -315,7 +322,9 @@ private interface Loader {
315322 * @param loader The one who load the static resources.
316323 */
317324 public AssetHandler (final String pattern , final ClassLoader loader ) {
318- init (Route .normalize (pattern ), Paths .get ("public" ), loader );
325+ this .location = Route .normalize (pattern );
326+ this .basedir = Paths .get ("public" );
327+ this .classLoader = loader ;
319328 }
320329
321330 /**
@@ -345,7 +354,9 @@ public AssetHandler(final String pattern, final ClassLoader loader) {
345354 * @param basedir Base directory.
346355 */
347356 public AssetHandler (final Path basedir ) {
348- init ("/{0}" , basedir , getClass ().getClassLoader ());
357+ this .location = "/{0}" ;
358+ this .basedir = basedir ;
359+ this .classLoader = getClass ().getClassLoader ();
349360 }
350361
351362 /**
@@ -375,7 +386,9 @@ public AssetHandler(final Path basedir) {
375386 * @param pattern Pattern to locate static resources.
376387 */
377388 public AssetHandler (final String pattern ) {
378- init (Route .normalize (pattern ), Paths .get ("public" ), getClass ().getClassLoader ());
389+ this .location = Route .normalize (pattern );
390+ this .basedir = Paths .get ("public" );
391+ this .classLoader = getClass ().getClassLoader ();
379392 }
380393
381394 /**
@@ -422,6 +435,45 @@ public AssetHandler maxAge(final long maxAge) {
422435 return this ;
423436 }
424437
438+ /**
439+ * Set the route definition and initialize the handler.
440+ *
441+ * @param route Route definition.
442+ * @return This handler.
443+ */
444+ public AssetHandler setRoute (final Route .AssetDefinition route ) {
445+ String prefix ;
446+ boolean rootLocation = location .equals ("/" ) || location .equals ("/{0}" );
447+ if (rootLocation ) {
448+ String pattern = route .pattern ();
449+ int i = pattern .indexOf ("/*" );
450+ if (i > 0 ) {
451+ prefix = pattern .substring (0 , i + 1 );
452+ } else {
453+ prefix = pattern ;
454+ }
455+ } else {
456+ int i = location .indexOf ("{" );
457+ if (i > 0 ) {
458+ prefix = location .substring (0 , i );
459+ } else {
460+ /// TODO: review what we have here
461+ prefix = location ;
462+ }
463+ }
464+ if (prefix .startsWith ("/" )) {
465+ prefix = prefix .substring (1 );
466+ }
467+ if (prefix .isEmpty () && rootLocation ) {
468+ throw new IllegalArgumentException (
469+ "For security reasons root classpath access is not allowed. Map your static resources "
470+ + "using a prefix like: assets(static/**); or use a location classpath prefix like: "
471+ + "assets(/, /static/{0})" );
472+ }
473+ init (prefix , location , basedir , classLoader );
474+ return this ;
475+ }
476+
425477 /**
426478 * Parse value as {@link Duration}. If the value is already a number then it uses as seconds.
427479 * Otherwise, it parse expressions like: 8m, 1h, 365d, etc...
@@ -485,7 +537,6 @@ public void handle(final Request req, final Response rsp) throws Throwable {
485537 }
486538
487539 private void doHandle (final Request req , final Response rsp , final Asset asset ) throws Throwable {
488-
489540 // handle etag
490541 if (this .etag ) {
491542 String etag = asset .etag ();
@@ -551,21 +602,22 @@ protected URL resolve(final String path) throws Exception {
551602 return loader .getResource (path );
552603 }
553604
554- private void init (final String pattern , final Path basedir , final ClassLoader loader ) {
605+ private void init (final String classPathPrefix , final String location , final Path basedir ,
606+ final ClassLoader loader ) {
555607 requireNonNull (loader , "Resource loader is required." );
556- this .fn = pattern .equals ("/" )
608+ this .fn = location .equals ("/" )
557609 ? (req , p ) -> prefix .apply (p )
558- : (req , p ) -> MessageFormat .format (prefix .apply (pattern ), vars (req ));
559- this .loader = loader (basedir , loader );
610+ : (req , p ) -> MessageFormat .format (prefix .apply (location ), vars (req ));
611+ this .loader = loader (basedir , classpathLoader ( classPathPrefix , classLoader ) );
560612 }
561613
562614 private static Object [] vars (final Request req ) {
563615 Map <Object , String > vars = req .route ().vars ();
564616 return vars .values ().toArray (new Object [vars .size ()]);
565617 }
566618
567- private static Loader loader (final Path basedir , final ClassLoader classloader ) {
568- if (Files .exists (basedir )) {
619+ private static Loader loader (final Path basedir , Loader classpath ) {
620+ if (basedir != null && Files .exists (basedir )) {
569621 return name -> {
570622 Path path = basedir .resolve (name ).normalize ();
571623 if (Files .exists (path ) && path .startsWith (basedir )) {
@@ -575,10 +627,45 @@ private static Loader loader(final Path basedir, final ClassLoader classloader)
575627 // shh
576628 }
577629 }
578- return classloader .getResource (name );
630+ return classpath .getResource (name );
579631 };
580632 }
581- return classloader ::getResource ;
633+ return classpath ;
634+ }
635+
636+ private static Loader classpathLoader (String prefix , ClassLoader classloader ) {
637+ return name -> {
638+ String safePath = safePath (name );
639+ if (safePath .startsWith (prefix )) {
640+ URL resource = classloader .getResource (safePath );
641+ return resource ;
642+ }
643+ return null ;
644+ };
645+ }
646+
647+ private static String safePath (String name ) {
648+ if (name .indexOf ("./" ) > 0 ) {
649+ Path path = toPath (name .split ("/" )).normalize ();
650+ return toStringPath (path );
651+ }
652+ return name ;
653+ }
654+
655+ private static String toStringPath (Path path ) {
656+ StringBuilder buffer = new StringBuilder ();
657+ for (Path segment : path ) {
658+ buffer .append ("/" ).append (segment );
659+ }
660+ return buffer .substring (1 );
661+ }
662+
663+ private static Path toPath (String [] segments ) {
664+ Path path = Paths .get (segments [0 ]);
665+ for (int i = 1 ; i < segments .length ; i ++) {
666+ path = path .resolve (segments [i ]);
667+ }
668+ return path ;
582669 }
583670
584671 private static Throwing .Function <String , String > prefix () {
0 commit comments