http-client-branch: HTTP Client file publishers, handlers, and subscribers as capability objects http-client-branch
authorchegar
Fri, 16 Feb 2018 15:06:29 +0000
branchhttp-client-branch
changeset 56138 4f92b988600e
parent 56137 dd867826d55b
child 56139 b3d6203051df
http-client-branch: HTTP Client file publishers, handlers, and subscribers as capability objects
src/java.net.http/share/classes/java/net/http/HttpRequest.java
src/java.net.http/share/classes/java/net/http/HttpResponse.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java
src/java.net.http/share/classes/jdk/internal/net/http/PushGroup.java
src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java
src/java.net.http/share/classes/jdk/internal/net/http/UntrustedBodyHandler.java
test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java
test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java
--- a/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpRequest.java	Fri Feb 16 15:06:29 2018 +0000
@@ -580,6 +580,10 @@
         /**
          * A request body publisher that takes data from the contents of a File.
          *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodyPublisher} is created. Care must be taken
+         * that the {@code BodyPublisher} is not shared with untrusted code.
+         *
          * @param path the path to the file containing the body
          * @return a BodyPublisher
          * @throws java.io.FileNotFoundException if the path is not found
--- a/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/java/net/http/HttpResponse.java	Fri Feb 16 15:06:29 2018 +0000
@@ -33,9 +33,9 @@
 import java.nio.charset.Charset;
 import java.nio.channels.FileChannel;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -54,6 +54,8 @@
 import jdk.internal.net.http.ResponseBodyHandlers.PathBodyHandler;
 import jdk.internal.net.http.ResponseBodyHandlers.PushPromisesHandlerWithMap;
 import jdk.internal.net.http.ResponseSubscribers;
+import jdk.internal.net.http.ResponseSubscribers.PathSubscriber;
+import static java.nio.file.StandardOpenOption.*;
 import static jdk.internal.net.http.common.Utils.charsetFrom;
 
 /**
@@ -449,49 +451,53 @@
          * been completely written to the file, and {@link #body()} returns a
          * reference to its {@link Path}.
          *
-         * @param file the filename to store the body in
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
+         * @param file the file to store the body in
          * @param openOptions any options to use when opening/creating the file
          * @return a response body handler
+         * @throws IllegalArgumentException if an invalid set of open options
+         *          are specified
          * @throws SecurityException If a security manager has been installed
          *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with
-         *          the {@code DELETE_ON_CLOSE} option.
+         *          write access} to the file.
          */
         public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) {
             Objects.requireNonNull(file);
             List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) {
+                // these options make no sense, since the FileChannel is not exposed
+                throw new IllegalArgumentException("invalid openOptions: " + opts);
+            }
+
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 String fn = pathForSecurityCheck(file);
                 sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
             }
-            return new PathBodyHandler(file, openOptions);
+            return new PathBodyHandler(file, opts);
         }
 
         /**
          * Returns a {@code BodyHandler<Path>} that returns a
-         * {@link BodySubscriber BodySubscriber}{@code <Path>} obtained from
-         * {@link BodySubscriber#asFile(Path) BodySubscriber.asFile(Path)}.
+         * {@link BodySubscriber BodySubscriber}{@code <Path>}.
+         *
+         * <p> Equivalent to: {@code asFile(file, CREATE, WRITE)}
          *
-         * <p> When the {@code HttpResponse} object is returned, the body has
-         * been completely written to the file, and {@link #body()} returns a
-         * reference to its {@link Path}.
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
          *
          * @param file the file to store the body in
          * @return a response body handler
-         * @throws SecurityException if a security manager has been installed
+         * @throws SecurityException If a security manager has been installed
          *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file
+         *          write access} to the file.
          */
         public static BodyHandler<Path> asFile(Path file) {
-            return BodyHandler.asFile(file, StandardOpenOption.CREATE,
-                                            StandardOpenOption.WRITE);
+            return BodyHandler.asFile(file, CREATE, WRITE);
         }
 
         /**
@@ -511,31 +517,44 @@
          * by the server. If the destination directory does not exist or cannot
          * be written to, then the response will fail with an {@link IOException}.
          *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodyHandler} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
          * @param directory the directory to store the file in
-         * @param openOptions open options
+         * @param openOptions open options used when opening the file
          * @return a response body handler
+         * @throws IllegalArgumentException if the given path does not exist,
+         *          is not a directory, is not writable, or if an invalid set
+         *          of open options are specified
          * @throws SecurityException If a security manager has been installed
-         *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with
-         *          the {@code DELETE_ON_CLOSE} option.
+         *          and it denies {@linkplain SecurityManager#checkRead(String)
+         *          read access} or {@linkplain SecurityManager#checkWrite(String)
+         *          write access} to the directory.
          */
-         //####: check if the dir exists and is writable??
         public static BodyHandler<Path> asFileDownload(Path directory,
                                                        OpenOption... openOptions) {
             Objects.requireNonNull(directory);
             List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE)) {
+                throw new IllegalArgumentException("invalid option: " + DELETE_ON_CLOSE);
+            }
+
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 String fn = pathForSecurityCheck(directory);
                 sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
+                sm.checkRead(fn);
             }
-            return new FileDownloadBodyHandler(directory, openOptions);
+
+            if (Files.notExists(directory))
+                throw new IllegalArgumentException("non-existent directory: " + directory);
+            if (!Files.isDirectory(directory))
+                throw new IllegalArgumentException("not a directory: " + directory);
+            if (!Files.isWritable(directory))
+                throw new IllegalArgumentException("non-writable directory: " + directory);
+
+            return new FileDownloadBodyHandler(directory, opts);
         }
 
         /**
@@ -934,11 +953,6 @@
             );
         }
 
-        // no security check
-        private static BodySubscriber<Path> asFileImpl(Path file, OpenOption... openOptions) {
-            return new ResponseSubscribers.PathSubscriber(file, openOptions);
-        }
-
         /**
          * Returns a {@code BodySubscriber} which stores the response body in a
          * file opened with the given options and name. The file will be opened
@@ -951,39 +965,44 @@
          * <p> The {@link HttpResponse} using this subscriber is available after
          * the entire response has been read.
          *
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodySubscriber} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
+         *
          * @param file the file to store the body in
          * @param openOptions the list of options to open the file with
          * @return a body subscriber
-         * @throws SecurityException If a security manager has been installed
+         * @throws IllegalArgumentException if an invalid set of open options
+         *          are specified
+         * @throws SecurityException if a security manager has been installed
          *          and it denies {@link SecurityManager#checkWrite(String)
-         *          write access} to the file. The {@link
-         *          SecurityManager#checkDelete(String) checkDelete} method is
-         *          invoked to check delete access if the file is opened with the
-         *          {@code DELETE_ON_CLOSE} option.
+         *          write access} to the file
          */
         public static BodySubscriber<Path> asFile(Path file, OpenOption... openOptions) {
             Objects.requireNonNull(file);
             List<OpenOption> opts = List.of(openOptions);
+            if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) {
+                // these options make no sense, since the FileChannel is not exposed
+                throw new IllegalArgumentException("invalid openOptions: " + opts);
+            }
+
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 String fn = pathForSecurityCheck(file);
                 sm.checkWrite(fn);
-                if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE))
-                    sm.checkDelete(fn);
-                if (opts.contains(StandardOpenOption.READ))
-                    sm.checkRead(fn);
             }
-            return asFileImpl(file, openOptions);
+            return new PathSubscriber(file, opts);
         }
 
         /**
          * Returns a {@code BodySubscriber} which stores the response body in a
-         * file opened with the given name. Has the same effect as calling
-         * {@link #asFile(Path, OpenOption...) asFile} with the standard open
-         * options {@code CREATE} and {@code WRITE}
+         * file opened with the given name.
+         *
+         * <p> Equivalent to: {@code asFile(file, CREATE, WRITE)}
          *
-         * <p> The {@link HttpResponse} using this subscriber is available after
-         * the entire response has been read.
+         * <p> Security manager permission checks are performed in this factory
+         * method, when a {@code BodySubscriber} is created. Care must be taken
+         * that the {@code BodyHandler} is not shared with untrusted code.
          *
          * @param file the file to store the body in
          * @return a body subscriber
@@ -992,7 +1011,7 @@
          *          write access} to the file
          */
         public static BodySubscriber<Path> asFile(Path file) {
-            return asFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
+            return asFile(file, CREATE, WRITE);
         }
 
         /**
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java	Fri Feb 16 15:06:29 2018 +0000
@@ -116,10 +116,6 @@
         this.expectContinue = request.expectContinue();
         this.secure = uri.getScheme().toLowerCase(Locale.US).equals("https");
         this.requestPublisher = request.bodyPublisher().orElse(null);
-        if (acc != null && requestPublisher instanceof RequestPublishers.FilePublisher) {
-            // Restricts the file publisher with the senders ACC, if any
-            ((RequestPublishers.FilePublisher)requestPublisher).setAccessControlContext(acc);
-        }
         this.timeout = request.timeout().orElse(null);
         this.version = request.version();
         this.authority = null;
--- a/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java	Fri Feb 16 15:06:29 2018 +0000
@@ -42,7 +42,6 @@
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.PushPromiseHandler;
 import java.net.http.HttpTimeoutException;
-import jdk.internal.net.http.UntrustedBodyHandler;
 import jdk.internal.net.http.common.Log;
 import jdk.internal.net.http.common.MinimalFuture;
 import jdk.internal.net.http.common.ConnectionExpiredException;
@@ -119,11 +118,6 @@
         this.acc = acc;
         this.executor = client.theExecutor();
         this.responseHandler = responseHandler;
-        if (acc != null) {
-            // Restricts the file publisher with the senders ACC, if any
-            if (responseHandler instanceof UntrustedBodyHandler)
-                ((UntrustedBodyHandler)this.responseHandler).setAccessControlContext(acc);
-        }
 
         if (pushPromiseHandler != null) {
             this.pushGroup = new PushGroup<>(pushPromiseHandler, request, acc);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/PushGroup.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PushGroup.java	Fri Feb 16 15:06:29 2018 +0000
@@ -26,7 +26,6 @@
 package jdk.internal.net.http;
 
 import java.security.AccessControlContext;
-import java.security.AccessController;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.net.http.HttpRequest;
@@ -70,9 +69,6 @@
         this.noMorePushesCF = new MinimalFuture<>();
         this.pushPromiseHandler = pushPromiseHandler;
         this.initiatingRequest = initiatingRequest;
-        // Restricts the file publisher with the senders ACC, if any
-        if (pushPromiseHandler instanceof UntrustedBodyHandler)
-            ((UntrustedBodyHandler)this.pushPromiseHandler).setAccessControlContext(acc);
         this.acc = acc;
     }
 
@@ -109,9 +105,6 @@
 
         synchronized (this) {
             if (acceptor.accepted()) {
-                if (acceptor.bodyHandler instanceof UntrustedBodyHandler) {
-                    ((UntrustedBodyHandler) acceptor.bodyHandler).setAccessControlContext(acc);
-                }
                 numberOfPushes++;
                 remainingPushes++;
             }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/RequestPublishers.java	Fri Feb 16 15:06:29 2018 +0000
@@ -219,27 +219,18 @@
 
     public static class FilePublisher implements BodyPublisher  {
         private final File file;
-        private volatile AccessControlContext acc;
 
         public FilePublisher(Path name) {
             file = name.toFile();
         }
 
-        void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
         @Override
         public void subscribe(Flow.Subscriber<? super ByteBuffer> subscriber) {
-            if (System.getSecurityManager() != null && acc == null)
-                throw new InternalError(
-                        "Unexpected null acc when security manager has been installed");
-
             InputStream is;
             try {
                 PrivilegedExceptionAction<FileInputStream> pa =
                         () -> new FileInputStream(file);
-                is = AccessController.doPrivileged(pa, acc);
+                is = AccessController.doPrivileged(pa);
             } catch (PrivilegedActionException pae) {
                 throw new UncheckedIOException((IOException)pae.getCause());
             }
@@ -250,9 +241,8 @@
 
         @Override
         public long contentLength() {
-            assert System.getSecurityManager() != null ? acc != null: true;
             PrivilegedAction<Long> pa = () -> file.length();
-            return AccessController.doPrivileged(pa, acc);
+            return AccessController.doPrivileged(pa);
         }
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java	Fri Feb 16 15:06:29 2018 +0000
@@ -31,7 +31,7 @@
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.security.AccessControlContext;
+import java.nio.file.StandardOpenOption;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentMap;
@@ -52,30 +52,19 @@
 
     /**
      * A Path body handler.
-     *
-     * Note: Exists mainly too allow setting of the senders ACC post creation of
-     * the handler.
      */
-    public static class PathBodyHandler implements UntrustedBodyHandler<Path> {
+    public static class PathBodyHandler implements BodyHandler<Path>{
         private final Path file;
-        private final OpenOption[]openOptions;
-        private volatile AccessControlContext acc;
+        private final List<OpenOption> openOptions;
 
-        public PathBodyHandler(Path file, OpenOption... openOptions) {
+        public PathBodyHandler(Path file, List<OpenOption> openOptions) {
             this.file = file;
             this.openOptions = openOptions;
         }
 
         @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
-        @Override
         public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) {
-            PathSubscriber bs = (PathSubscriber) asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
+            return new PathSubscriber(file, openOptions);
         }
     }
 
@@ -125,22 +114,16 @@
         }
     }
 
-    // Similar to Path body handler, but for file download. Supports setting ACC.
-    public static class FileDownloadBodyHandler implements UntrustedBodyHandler<Path> {
+    // Similar to Path body handler, but for file download.
+    public static class FileDownloadBodyHandler implements BodyHandler<Path> {
         private final Path directory;
-        private final OpenOption[] openOptions;
-        private volatile AccessControlContext acc;
+        private final List<OpenOption> openOptions;
 
-        public FileDownloadBodyHandler(Path directory, OpenOption... openOptions) {
+        public FileDownloadBodyHandler(Path directory, List<OpenOption> openOptions) {
             this.directory = directory;
             this.openOptions = openOptions;
         }
 
-        @Override
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
-        }
-
         /** The "attachment" disposition-type and separator. */
         static final String DISPOSITION_TYPE = "attachment;";
 
@@ -221,14 +204,7 @@
                         "Resulting file, " + file.toString() + ", outside of given directory");
             }
 
-            PathSubscriber bs = (PathSubscriber)asFileImpl(file, openOptions);
-            bs.setAccessControlContext(acc);
-            return bs;
+            return new PathSubscriber(file, openOptions);
         }
     }
-
-    // no security check
-    private static BodySubscriber<Path> asFileImpl(Path file, OpenOption... openOptions) {
-        return new ResponseSubscribers.PathSubscriber(file, openOptions);
-    }
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseSubscribers.java	Fri Feb 16 15:06:29 2018 +0000
@@ -35,7 +35,6 @@
 import java.nio.charset.Charset;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
-import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
@@ -114,32 +113,23 @@
 
         private final Path file;
         private final CompletableFuture<Path> result = new MinimalFuture<>();
+        private final OpenOption[] options;
 
         private volatile Flow.Subscription subscription;
         private volatile FileChannel out;
-        private volatile AccessControlContext acc;
-        private final OpenOption[] options;
 
-        public PathSubscriber(Path file, OpenOption... options) {
+        public PathSubscriber(Path file, List<OpenOption> options) {
             this.file = file;
-            this.options = options;
-        }
-
-        public void setAccessControlContext(AccessControlContext acc) {
-            this.acc = acc;
+            this.options = options.stream().toArray(OpenOption[]::new);
         }
 
         @Override
         public void onSubscribe(Flow.Subscription subscription) {
-            if (System.getSecurityManager() != null && acc == null)
-                throw new InternalError(
-                        "Unexpected null acc when security manager has been installed");
-
             this.subscription = subscription;
             try {
                 PrivilegedExceptionAction<FileChannel> pa =
                         () -> FileChannel.open(file, options);
-                out = AccessController.doPrivileged(pa, acc);
+                out = AccessController.doPrivileged(pa);
             } catch (PrivilegedActionException pae) {
                 Throwable t = pae.getCause() != null ? pae.getCause() : pae;
                 result.completeExceptionally(t);
--- a/src/java.net.http/share/classes/jdk/internal/net/http/UntrustedBodyHandler.java	Fri Feb 16 10:34:17 2018 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.net.http;
-
-import java.security.AccessControlContext;
-import java.net.http.HttpResponse;
-
-/** A body handler that is further restricted by a given ACC. */
-public interface UntrustedBodyHandler<T> extends HttpResponse.BodyHandler<T> {
-    void setAccessControlContext(AccessControlContext acc);
-}
--- a/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/test/jdk/java/net/httpclient/SubscriberPublisherAPIExceptions.java	Fri Feb 16 15:06:29 2018 +0000
@@ -39,7 +39,9 @@
 import org.testng.annotations.Test;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
 import static java.nio.file.StandardOpenOption.WRITE;
+import static java.nio.file.StandardOpenOption.READ;
 import static org.testng.Assert.assertThrows;
 
 /*
@@ -92,8 +94,13 @@
     }
 
     @Test
-    public void handlerAPIExceptions() {
+    public void handlerAPIExceptions() throws Exception {
         Path path = Paths.get(".").resolve("tt");
+        Path file = Files.createFile(Paths.get(".").resolve("aFile"));
+        Path doesNotExist = Paths.get(".").resolve("doneNotExist");
+        if (Files.exists(doesNotExist))
+            throw new AssertionError("Unexpected " + doesNotExist);
+
         assertThrows(NPE, () -> BodyHandler.asByteArrayConsumer(null));
         assertThrows(NPE, () -> BodyHandler.asFile(null));
         assertThrows(NPE, () -> BodyHandler.asFile(null, CREATE, WRITE));
@@ -108,11 +115,19 @@
         assertThrows(NPE, () -> BodyHandler.asFileDownload(path, new OpenOption[] {CREATE, null}));
         assertThrows(NPE, () -> BodyHandler.asFileDownload(path, new OpenOption[] {null, CREATE}));
         assertThrows(NPE, () -> BodyHandler.asFileDownload(null, (OpenOption[])null));
+        assertThrows(IAE, () -> BodyHandler.asFileDownload(file, CREATE, WRITE));
+        assertThrows(IAE, () -> BodyHandler.asFileDownload(doesNotExist, CREATE, WRITE));
         assertThrows(NPE, () -> BodyHandler.asString(null));
         assertThrows(NPE, () -> BodyHandler.buffering(null, 1));
         assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), 0));
         assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), -1));
         assertThrows(IAE, () -> BodyHandler.buffering(new NoOpHandler(), Integer.MIN_VALUE));
+
+        // implementation specific exceptions
+        assertThrows(IAE, () -> BodyHandler.asFile(path, READ));
+        assertThrows(IAE, () -> BodyHandler.asFile(path, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodyHandler.asFile(path, READ, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodyHandler.asFileDownload(path, DELETE_ON_CLOSE));
     }
 
     @Test
@@ -134,6 +149,11 @@
         assertThrows(NPE, () -> BodySubscriber.mapping(null, Function.identity()));
         assertThrows(NPE, () -> BodySubscriber.mapping(BodySubscriber.asByteArray(), null));
         assertThrows(NPE, () -> BodySubscriber.mapping(null, null));
+
+        // implementation specific exceptions
+        assertThrows(IAE, () -> BodySubscriber.asFile(path, READ));
+        assertThrows(IAE, () -> BodySubscriber.asFile(path, DELETE_ON_CLOSE));
+        assertThrows(IAE, () -> BodySubscriber.asFile(path, READ, DELETE_ON_CLOSE));
     }
 
     static class NoOpHandler implements BodyHandler<Void> {
--- a/test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java	Fri Feb 16 10:34:17 2018 +0000
+++ b/test/jdk/java/net/httpclient/security/filePerms/FileProcessorPermissionTest.java	Fri Feb 16 15:06:29 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -73,18 +73,10 @@
                 () -> HttpResponse.BodyHandler.asFile(asFilePath),
                 () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE),
                 () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE, READ),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, CREATE, WRITE, READ, DELETE_ON_CLOSE),
 
                 () -> HttpResponse.BodyHandler.asFileDownload(CWD),
                 () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE, READ),
-                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE, READ, DELETE_ON_CLOSE),
-
-                // TODO: what do these even mean by themselves, maybe ok means nothing?
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, DELETE_ON_CLOSE),
-                () -> HttpResponse.BodyHandler.asFile(asFilePath, READ)
+                () -> HttpResponse.BodyHandler.asFileDownload(CWD, CREATE, WRITE)
         );
 
         // sanity, just run http ( no security manager )