1
1
/*
2
- * Copyright 2002-2017 the original author or authors.
2
+ * Copyright 2002-2018 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
17
17
package org .springframework .web .servlet .resource ;
18
18
19
19
import java .io .IOException ;
20
+ import java .io .UnsupportedEncodingException ;
20
21
import java .net .URLDecoder ;
21
22
import java .nio .charset .Charset ;
22
23
import java .util .ArrayList ;
@@ -507,46 +508,75 @@ protected Resource getResource(HttpServletRequest request) throws IOException {
507
508
throw new IllegalStateException ("Required request attribute '" +
508
509
HandlerMapping .PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set" );
509
510
}
511
+
510
512
path = processPath (path );
511
513
if (!StringUtils .hasText (path ) || isInvalidPath (path )) {
512
514
if (logger .isTraceEnabled ()) {
513
515
logger .trace ("Ignoring invalid resource path [" + path + "]" );
514
516
}
515
517
return null ;
516
518
}
517
- if (path .contains ("%" )) {
518
- try {
519
- // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
520
- if (isInvalidPath (URLDecoder .decode (path , "UTF-8" ))) {
521
- if (logger .isTraceEnabled ()) {
522
- logger .trace ("Ignoring invalid resource path with escape sequences [" + path + "]." );
523
- }
524
- return null ;
525
- }
526
- }
527
- catch (IllegalArgumentException ex ) {
528
- // ignore
519
+ if (isInvalidEncodedPath (path )) {
520
+ if (logger .isTraceEnabled ()) {
521
+ logger .trace ("Ignoring invalid resource path with escape sequences [" + path + "]" );
529
522
}
523
+ return null ;
530
524
}
525
+
531
526
ResourceResolverChain resolveChain = new DefaultResourceResolverChain (getResourceResolvers ());
532
527
Resource resource = resolveChain .resolveResource (request , path , getLocations ());
533
528
if (resource == null || getResourceTransformers ().isEmpty ()) {
534
529
return resource ;
535
530
}
531
+
536
532
ResourceTransformerChain transformChain =
537
533
new DefaultResourceTransformerChain (resolveChain , getResourceTransformers ());
538
534
resource = transformChain .transform (request , resource );
539
535
return resource ;
540
536
}
541
537
542
538
/**
543
- * Process the given resource path to be used.
544
- * <p>The default implementation replaces any combination of leading '/' and
545
- * control characters (00-1F and 7F) with a single "/" or "". For example
546
- * {@code " // /// //// foo/bar"} becomes {@code "/foo/bar"}.
539
+ * Process the given resource path.
540
+ * <p>The default implementation replaces:
541
+ * <ul>
542
+ * <li>Backslash with forward slash.
543
+ * <li>Duplicate occurrences of slash with a single slash.
544
+ * <li>Any combination of leading slash and control characters (00-1F and 7F)
545
+ * with a single "/" or "". For example {@code " / // foo/bar"}
546
+ * becomes {@code "/foo/bar"}.
547
+ * </ul>
547
548
* @since 3.2.12
548
549
*/
549
550
protected String processPath (String path ) {
551
+ path = StringUtils .replace (path , "\\ " , "/" );
552
+ path = cleanDuplicateSlashes (path );
553
+ return cleanLeadingSlash (path );
554
+ }
555
+
556
+ private String cleanDuplicateSlashes (String path ) {
557
+ StringBuilder sb = null ;
558
+ char prev = 0 ;
559
+ for (int i = 0 ; i < path .length (); i ++) {
560
+ char curr = path .charAt (i );
561
+ try {
562
+ if ((curr == '/' ) && (prev == '/' )) {
563
+ if (sb == null ) {
564
+ sb = new StringBuilder (path .substring (0 , i ));
565
+ }
566
+ continue ;
567
+ }
568
+ if (sb != null ) {
569
+ sb .append (path .charAt (i ));
570
+ }
571
+ }
572
+ finally {
573
+ prev = curr ;
574
+ }
575
+ }
576
+ return sb != null ? sb .toString () : path ;
577
+ }
578
+
579
+ private String cleanLeadingSlash (String path ) {
550
580
boolean slash = false ;
551
581
for (int i = 0 ; i < path .length (); i ++) {
552
582
if (path .charAt (i ) == '/' ) {
@@ -556,16 +586,44 @@ else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
556
586
if (i == 0 || (i == 1 && slash )) {
557
587
return path ;
558
588
}
559
- path = slash ? "/" + path .substring (i ) : path .substring (i );
589
+ path = ( slash ? "/" + path .substring (i ) : path .substring (i ) );
560
590
if (logger .isTraceEnabled ()) {
561
- logger .trace ("Path after trimming leading '/' and control characters: " + path );
591
+ logger .trace ("Path after trimming leading '/' and control characters: [ " + path + "]" );
562
592
}
563
593
return path ;
564
594
}
565
595
}
566
596
return (slash ? "/" : "" );
567
597
}
568
598
599
+ /**
600
+ * Check whether the given path contains invalid escape sequences.
601
+ * @param path the path to validate
602
+ * @return {@code true} if the path is invalid, {@code false} otherwise
603
+ */
604
+ private boolean isInvalidEncodedPath (String path ) {
605
+ if (path .contains ("%" )) {
606
+ try {
607
+ // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars
608
+ String decodedPath = URLDecoder .decode (path , "UTF-8" );
609
+ if (isInvalidPath (decodedPath )) {
610
+ return true ;
611
+ }
612
+ decodedPath = processPath (decodedPath );
613
+ if (isInvalidPath (decodedPath )) {
614
+ return true ;
615
+ }
616
+ }
617
+ catch (IllegalArgumentException ex ) {
618
+ // Should never happen...
619
+ }
620
+ catch (UnsupportedEncodingException ex ) {
621
+ // Should never happen...
622
+ }
623
+ }
624
+ return false ;
625
+ }
626
+
569
627
/**
570
628
* Identifies invalid resource paths. By default rejects:
571
629
* <ul>
@@ -580,32 +638,24 @@ else if (path.charAt(i) > ' ' && path.charAt(i) != 127) {
580
638
* path starts predictably with a single '/' or does not have one.
581
639
* @param path the path to validate
582
640
* @return {@code true} if the path is invalid, {@code false} otherwise
641
+ * @since 3.0.6
583
642
*/
584
643
protected boolean isInvalidPath (String path ) {
585
- if (logger .isTraceEnabled ()) {
586
- logger .trace ("Applying \" invalid path\" checks to path: " + path );
587
- }
588
644
if (path .contains ("WEB-INF" ) || path .contains ("META-INF" )) {
589
- if (logger .isTraceEnabled ()) {
590
- logger .trace ("Path contains \" WEB-INF\" or \" META-INF\" ." );
591
- }
645
+ logger .trace ("Path contains \" WEB-INF\" or \" META-INF\" ." );
592
646
return true ;
593
647
}
594
648
if (path .contains (":/" )) {
595
649
String relativePath = (path .charAt (0 ) == '/' ? path .substring (1 ) : path );
596
650
if (ResourceUtils .isUrl (relativePath ) || relativePath .startsWith ("url:" )) {
597
- if (logger .isTraceEnabled ()) {
598
- logger .trace ("Path represents URL or has \" url:\" prefix." );
599
- }
651
+ logger .trace ("Path represents URL or has \" url:\" prefix." );
600
652
return true ;
601
653
}
602
654
}
603
655
if (path .contains (".." )) {
604
656
path = StringUtils .cleanPath (path );
605
657
if (path .contains ("../" )) {
606
- if (logger .isTraceEnabled ()) {
607
- logger .trace ("Path contains \" ../\" after call to StringUtils#cleanPath." );
608
- }
658
+ logger .trace ("Path contains \" ../\" after call to StringUtils#cleanPath." );
609
659
return true ;
610
660
}
611
661
}
0 commit comments