34
34
import java .io .Serializable ;
35
35
import java .net .URL ;
36
36
import java .nio .charset .StandardCharsets ;
37
+ import java .nio .file .LinkOption ;
38
+ import java .nio .file .OpenOption ;
37
39
import java .text .Collator ;
38
40
import java .util .ArrayList ;
39
41
import java .util .Arrays ;
49
51
import java .util .StringTokenizer ;
50
52
import java .util .logging .Level ;
51
53
import java .util .logging .Logger ;
54
+ import java .util .regex .Pattern ;
52
55
import java .util .stream .Collectors ;
53
56
import java .util .stream .Stream ;
54
57
import javax .servlet .ServletException ;
@@ -83,6 +86,11 @@ public final class DirectoryBrowserSupport implements HttpResponse {
83
86
@ SuppressFBWarnings (value = "MS_SHOULD_BE_FINAL" , justification = "Accessible via System Groovy Scripts" )
84
87
public static boolean ALLOW_SYMLINK_ESCAPE = SystemProperties .getBoolean (DirectoryBrowserSupport .class .getName () + ".allowSymlinkEscape" );
85
88
89
+ @ SuppressFBWarnings (value = "MS_SHOULD_BE_FINAL" , justification = "Accessible via System Groovy Scripts" )
90
+ public static boolean ALLOW_TMP_DISPLAY = SystemProperties .getBoolean (DirectoryBrowserSupport .class .getName () + ".allowTmpEscape" );
91
+
92
+ private static final Pattern TMPDIR_PATTERN = Pattern .compile (".+@tmp/.*" );
93
+
86
94
/**
87
95
* Escape hatch for the protection against SECURITY-2481. If enabled, the absolute paths on Windows will be allowed.
88
96
*/
@@ -264,7 +272,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
264
272
baseFile = root .child (base );
265
273
}
266
274
267
- if (baseFile .hasSymlink (getNoFollowLinks ())) {
275
+ if (baseFile .hasSymlink (getOpenOptions ()) || hasTmpDir ( baseFile , base , getOpenOptions ())) {
268
276
rsp .sendError (HttpServletResponse .SC_NOT_FOUND );
269
277
return ;
270
278
}
@@ -280,13 +288,13 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
280
288
includes = rest ;
281
289
prefix = "" ;
282
290
}
283
- baseFile .zip (rsp .getOutputStream (), includes , null , true , getNoFollowLinks (), prefix );
291
+ baseFile .zip (rsp .getOutputStream (), includes , null , true , prefix , getOpenOptions () );
284
292
return ;
285
293
}
286
294
if (plain ) {
287
295
rsp .setContentType ("text/plain;charset=UTF-8" );
288
296
try (OutputStream os = rsp .getOutputStream ()) {
289
- for (VirtualFile kid : baseFile .list (getNoFollowLinks ())) {
297
+ for (VirtualFile kid : baseFile .list (getOpenOptions ())) {
290
298
os .write (kid .getName ().getBytes (StandardCharsets .UTF_8 ));
291
299
if (kid .isDirectory ()) {
292
300
os .write ('/' );
@@ -310,14 +318,16 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
310
318
List <List <Path >> glob = null ;
311
319
boolean patternUsed = rest .length () > 0 ;
312
320
boolean containsSymlink = false ;
313
- if (patternUsed ) {
321
+ boolean containsTmpDir = false ;
322
+ if (patternUsed ) {
314
323
// the rest is Ant glob pattern
315
324
glob = patternScan (baseFile , rest , createBackRef (restSize ));
316
325
} else
317
326
if (serveDirIndex ) {
318
327
// serve directory index
319
328
glob = baseFile .run (new BuildChildPaths (root , baseFile , req .getLocale ()));
320
- containsSymlink = baseFile .containsSymLinkChild (getNoFollowLinks ());
329
+ containsSymlink = baseFile .containsSymLinkChild (getOpenOptions ());
330
+ containsTmpDir = baseFile .containsTmpDirChild (getOpenOptions ());
321
331
}
322
332
323
333
if (glob != null ) {
@@ -333,6 +343,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
333
343
req .setAttribute ("pattern" , rest );
334
344
req .setAttribute ("dir" , baseFile );
335
345
req .setAttribute ("showSymlinkWarning" , containsSymlink );
346
+ req .setAttribute ("showTmpDirWarning" , containsTmpDir );
336
347
if (ResourceDomainConfiguration .isResourceRequest (req )) {
337
348
req .getView (this , "plaindir.jelly" ).forward (req , rsp );
338
349
} else {
@@ -378,7 +389,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
378
389
if (view ) {
379
390
InputStream in ;
380
391
try {
381
- in = baseFile .open (getNoFollowLinks ());
392
+ in = baseFile .open (getOpenOptions ());
382
393
} catch (IOException ioe ) {
383
394
rsp .sendError (HttpServletResponse .SC_NOT_FOUND );
384
395
return ;
@@ -406,7 +417,7 @@ private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root
406
417
}
407
418
InputStream in ;
408
419
try {
409
- in = baseFile .open (getNoFollowLinks ());
420
+ in = baseFile .open (getOpenOptions ());
410
421
} catch (IOException ioe ) {
411
422
rsp .sendError (HttpServletResponse .SC_NOT_FOUND );
412
423
return ;
@@ -429,6 +440,13 @@ public Boolean call() throws IOException {
429
440
}
430
441
}
431
442
443
+ private boolean hasTmpDir (VirtualFile baseFile , String base , OpenOption [] openOptions ) {
444
+ if (FilePath .isTmpDir (baseFile .getName (), openOptions )) {
445
+ return true ;
446
+ }
447
+ return FilePath .isIgnoreTmpDirs (openOptions ) && TMPDIR_PATTERN .matcher (base ).matches ();
448
+ }
449
+
432
450
private List <List <Path >> keepReadabilityOnlyOnDescendants (VirtualFile root , boolean patternUsed , List <List <Path >> pathFragmentsList ) {
433
451
Stream <List <Path >> pathFragmentsStream = pathFragmentsList .stream ().map ((List <Path > pathFragments ) -> {
434
452
List <Path > mappedFragments = new ArrayList <>(pathFragments .size ());
@@ -754,7 +772,7 @@ private static final class BuildChildPaths extends MasterToSlaveCallable<List<Li
754
772
private static List <List <Path >> buildChildPaths (VirtualFile cur , Locale locale ) throws IOException {
755
773
List <List <Path >> r = new ArrayList <>();
756
774
757
- VirtualFile [] files = cur .list (getNoFollowLinks ());
775
+ VirtualFile [] files = cur .list (getOpenOptions ());
758
776
Arrays .sort (files , new FileComparator (locale ));
759
777
760
778
for (VirtualFile f : files ) {
@@ -769,7 +787,7 @@ private static List<List<Path>> buildChildPaths(VirtualFile cur, Locale locale)
769
787
while (true ) {
770
788
// files that don't start with '.' qualify for 'meaningful files', nor SCM related files
771
789
List <VirtualFile > sub = new ArrayList <>();
772
- for (VirtualFile vf : f .list (getNoFollowLinks ())) {
790
+ for (VirtualFile vf : f .list (getOpenOptions ())) {
773
791
String name = vf .getName ();
774
792
if (!name .startsWith ("." ) && !name .equals ("CVS" ) && !name .equals (".svn" )) {
775
793
sub .add (vf );
@@ -794,7 +812,7 @@ private static List<List<Path>> buildChildPaths(VirtualFile cur, Locale locale)
794
812
* @param baseRef String like "../../../" that cancels the 'rest' portion. Can be "./"
795
813
*/
796
814
private static List <List <Path >> patternScan (VirtualFile baseDir , String pattern , String baseRef ) throws IOException {
797
- Collection <String > files = baseDir .list (pattern , null , /* TODO what is the user expectation? */ true , getNoFollowLinks ());
815
+ Collection <String > files = baseDir .list (pattern , null , /* TODO what is the user expectation? */ true , getOpenOptions ());
798
816
799
817
if (!files .isEmpty ()) {
800
818
List <List <Path >> r = new ArrayList <>(files .size ());
@@ -837,8 +855,15 @@ private static void buildPathList(VirtualFile baseDir, VirtualFile filePath, Lis
837
855
pathList .add (path );
838
856
}
839
857
840
- private static boolean getNoFollowLinks () {
841
- return !ALLOW_SYMLINK_ESCAPE ;
858
+ private static OpenOption [] getOpenOptions () {
859
+ List <OpenOption > options = new ArrayList <>();
860
+ if (!ALLOW_SYMLINK_ESCAPE ) {
861
+ options .add (LinkOption .NOFOLLOW_LINKS );
862
+ }
863
+ if (!ALLOW_TMP_DISPLAY ) {
864
+ options .add (FilePath .DisplayOption .IGNORE_TMP_DIRS );
865
+ }
866
+ return options .toArray (new OpenOption [0 ]);
842
867
}
843
868
844
869
private static final Logger LOGGER = Logger .getLogger (DirectoryBrowserSupport .class .getName ());
0 commit comments