Skip to content

Commit d882d52

Browse files
author
Julia Boes
committed
8235459: HttpRequest.BodyPublishers::ofFile assumes the default file system
Add support for non-default file systems to HttpRequest.BodyPublishers::ofFile Reviewed-by: chegar, dfuchs, amlu
1 parent f930d46 commit d882d52

9 files changed

Lines changed: 1488 additions & 47 deletions

File tree

src/java.net.http/share/classes/java/net/http/HttpRequest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,6 +31,8 @@
3131
import java.nio.ByteBuffer;
3232
import java.nio.charset.Charset;
3333
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Files;
35+
import java.nio.file.OpenOption;
3436
import java.nio.file.Path;
3537
import java.time.Duration;
3638
import java.util.Iterator;
@@ -614,12 +616,16 @@ public static BodyPublisher ofByteArray(byte[] buf, int offset, int length) {
614616
* method, when the {@code BodyPublisher} is created. Care must be taken
615617
* that the {@code BodyPublisher} is not shared with untrusted code.
616618
*
617-
* @param path the path to the file containing the body
619+
* @param path the path to the file containing the body
618620
* @return a BodyPublisher
619621
* @throws java.io.FileNotFoundException if the path is not found
620-
* @throws SecurityException if a security manager has been installed
621-
* and it denies {@link SecurityManager#checkRead(String)
622-
* read access} to the given file
622+
* @throws SecurityException if
623+
* {@linkplain Files#newInputStream(Path, OpenOption...)
624+
* opening the file for reading} is denied:
625+
* in the case of the system-default file system provider,
626+
* and a security manager is installed,
627+
* {@link SecurityManager#checkRead(String) checkRead}
628+
* is invoked to check read access to the given file
623629
*/
624630
public static BodyPublisher ofFile(Path path) throws FileNotFoundException {
625631
Objects.requireNonNull(path);

src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java

Lines changed: 106 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -25,20 +25,20 @@
2525

2626
package jdk.internal.net.http;
2727

28-
import java.io.File;
2928
import java.io.FileInputStream;
3029
import java.io.FileNotFoundException;
3130
import java.io.FilePermission;
3231
import java.io.IOException;
3332
import java.io.InputStream;
3433
import java.io.UncheckedIOException;
34+
import java.lang.reflect.UndeclaredThrowableException;
3535
import java.nio.ByteBuffer;
3636
import java.nio.charset.Charset;
3737
import java.nio.file.Files;
3838
import java.nio.file.Path;
3939
import java.security.AccessControlContext;
4040
import java.security.AccessController;
41-
import java.security.PrivilegedAction;
41+
import java.security.Permission;
4242
import java.security.PrivilegedActionException;
4343
import java.security.PrivilegedExceptionAction;
4444
import java.util.ArrayList;
@@ -50,6 +50,7 @@
5050
import java.util.concurrent.ConcurrentLinkedQueue;
5151
import java.util.concurrent.Flow;
5252
import java.util.concurrent.Flow.Publisher;
53+
import java.util.function.Function;
5354
import java.util.function.Supplier;
5455
import java.net.http.HttpRequest.BodyPublisher;
5556
import jdk.internal.net.http.common.Utils;
@@ -220,17 +221,17 @@ public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
220221

221222
/**
222223
* Publishes the content of a given file.
223-
*
224+
* <p>
224225
* Privileged actions are performed within a limited doPrivileged that only
225226
* asserts the specific, read, file permission that was checked during the
226-
* construction of this FilePublisher.
227+
* construction of this FilePublisher. This only applies if the file system
228+
* that created the file provides interoperability with {@code java.io.File}.
227229
*/
228-
public static class FilePublisher implements BodyPublisher {
230+
public static class FilePublisher implements BodyPublisher {
229231

230-
private static final FilePermission[] EMPTY_FILE_PERMISSIONS = new FilePermission[0];
231-
232-
private final File file;
233-
private final FilePermission[] filePermissions;
232+
private final Path path;
233+
private final long length;
234+
private final Function<Path, InputStream> inputStreamSupplier;
234235

235236
private static String pathForSecurityCheck(Path path) {
236237
return path.toFile().getPath();
@@ -243,48 +244,112 @@ private static String pathForSecurityCheck(Path path) {
243244
* FilePublisher. Permission checking and construction are deliberately
244245
* and tightly co-located.
245246
*/
246-
public static FilePublisher create(Path path) throws FileNotFoundException {
247-
FilePermission filePermission = null;
247+
public static FilePublisher create(Path path)
248+
throws FileNotFoundException {
248249
SecurityManager sm = System.getSecurityManager();
249-
if (sm != null) {
250+
FilePermission filePermission = null;
251+
boolean defaultFS = true;
252+
253+
try {
250254
String fn = pathForSecurityCheck(path);
251-
FilePermission readPermission = new FilePermission(fn, "read");
252-
sm.checkPermission(readPermission);
253-
filePermission = readPermission;
255+
if (sm != null) {
256+
FilePermission readPermission = new FilePermission(fn, "read");
257+
sm.checkPermission(readPermission);
258+
filePermission = readPermission;
259+
}
260+
} catch (UnsupportedOperationException uoe) {
261+
defaultFS = false;
262+
// Path not associated with the default file system
263+
// Test early if an input stream can still be obtained
264+
try {
265+
if (sm != null) {
266+
Files.newInputStream(path).close();
267+
}
268+
} catch (IOException ioe) {
269+
if (ioe instanceof FileNotFoundException) {
270+
throw (FileNotFoundException) ioe;
271+
} else {
272+
var ex = new FileNotFoundException(ioe.getMessage());
273+
ex.initCause(ioe);
274+
throw ex;
275+
}
276+
}
254277
}
255278

256279
// existence check must be after permission checks
257280
if (Files.notExists(path))
258281
throw new FileNotFoundException(path + " not found");
259282

260-
return new FilePublisher(path, filePermission);
283+
Permission perm = filePermission;
284+
assert perm == null || perm.getActions().equals("read");
285+
AccessControlContext acc = sm != null ?
286+
AccessController.getContext() : null;
287+
boolean finalDefaultFS = defaultFS;
288+
Function<Path, InputStream> inputStreamSupplier = (p) ->
289+
createInputStream(p, acc, perm, finalDefaultFS);
290+
291+
long length;
292+
try {
293+
length = Files.size(path);
294+
} catch (IOException ioe) {
295+
length = -1;
296+
}
297+
298+
return new FilePublisher(path, length, inputStreamSupplier);
299+
}
300+
301+
private static InputStream createInputStream(Path path,
302+
AccessControlContext acc,
303+
Permission perm,
304+
boolean defaultFS) {
305+
try {
306+
if (acc != null) {
307+
PrivilegedExceptionAction<InputStream> pa = defaultFS
308+
? () -> new FileInputStream(path.toFile())
309+
: () -> Files.newInputStream(path);
310+
return perm != null
311+
? AccessController.doPrivileged(pa, acc, perm)
312+
: AccessController.doPrivileged(pa, acc);
313+
} else {
314+
return defaultFS
315+
? new FileInputStream(path.toFile())
316+
: Files.newInputStream(path);
317+
}
318+
} catch (PrivilegedActionException pae) {
319+
throw toUncheckedException(pae.getCause());
320+
} catch (IOException io) {
321+
throw new UncheckedIOException(io);
322+
}
261323
}
262324

263-
private FilePublisher(Path name, FilePermission filePermission) {
264-
assert filePermission != null ? filePermission.getActions().equals("read") : true;
265-
file = name.toFile();
266-
this.filePermissions = filePermission == null ? EMPTY_FILE_PERMISSIONS
267-
: new FilePermission[] { filePermission };
325+
private static RuntimeException toUncheckedException(Throwable t) {
326+
if (t instanceof RuntimeException)
327+
throw (RuntimeException) t;
328+
if (t instanceof Error)
329+
throw (Error) t;
330+
if (t instanceof IOException)
331+
throw new UncheckedIOException((IOException) t);
332+
throw new UndeclaredThrowableException(t);
333+
}
334+
335+
private FilePublisher(Path name,
336+
long length,
337+
Function<Path, InputStream> inputStreamSupplier) {
338+
path = name;
339+
this.length = length;
340+
this.inputStreamSupplier = inputStreamSupplier;
268341
}
269342

270343
@Override
271344
public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
272345
InputStream is = null;
273346
Throwable t = null;
274-
if (System.getSecurityManager() == null) {
275-
try {
276-
is = new FileInputStream(file);
277-
} catch (IOException ioe) {
278-
t = ioe;
279-
}
280-
} else {
281-
try {
282-
PrivilegedExceptionAction<FileInputStream> pa =
283-
() -> new FileInputStream(file);
284-
is = AccessController.doPrivileged(pa, null, filePermissions);
285-
} catch (PrivilegedActionException pae) {
286-
t = pae.getCause();
287-
}
347+
try {
348+
is = inputStreamSupplier.apply(path);
349+
} catch (UncheckedIOException | UndeclaredThrowableException ue) {
350+
t = ue.getCause();
351+
} catch (Throwable th) {
352+
t = th;
288353
}
289354
final InputStream fis = is;
290355
PullPublisher<ByteBuffer> publisher;
@@ -298,12 +363,7 @@ public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
298363

299364
@Override
300365
public long contentLength() {
301-
if (System.getSecurityManager() == null) {
302-
return file.length();
303-
} else {
304-
PrivilegedAction<Long> pa = () -> file.length();
305-
return AccessController.doPrivileged(pa, null, filePermissions);
306-
}
366+
return length;
307367
}
308368
}
309369

@@ -313,6 +373,7 @@ public long contentLength() {
313373
public static class StreamIterator implements Iterator<ByteBuffer> {
314374
final InputStream is;
315375
final Supplier<? extends ByteBuffer> bufSupplier;
376+
private volatile boolean eof;
316377
volatile ByteBuffer nextBuffer;
317378
volatile boolean need2Read = true;
318379
volatile boolean haveNext;
@@ -331,6 +392,8 @@ public static class StreamIterator implements Iterator<ByteBuffer> {
331392
// }
332393

333394
private int read() {
395+
if (eof)
396+
return -1;
334397
nextBuffer = bufSupplier.get();
335398
nextBuffer.clear();
336399
byte[] buf = nextBuffer.array();
@@ -339,6 +402,7 @@ private int read() {
339402
try {
340403
int n = is.read(buf, offset, cap);
341404
if (n == -1) {
405+
eof = true;
342406
is.close();
343407
return -1;
344408
}

0 commit comments

Comments
 (0)