31 import java.net.URI; |
31 import java.net.URI; |
32 import java.nio.ByteBuffer; |
32 import java.nio.ByteBuffer; |
33 import java.nio.charset.Charset; |
33 import java.nio.charset.Charset; |
34 import java.nio.channels.FileChannel; |
34 import java.nio.channels.FileChannel; |
35 import java.nio.charset.StandardCharsets; |
35 import java.nio.charset.StandardCharsets; |
|
36 import java.nio.file.Files; |
36 import java.nio.file.OpenOption; |
37 import java.nio.file.OpenOption; |
37 import java.nio.file.Path; |
38 import java.nio.file.Path; |
38 import java.nio.file.StandardOpenOption; |
|
39 import java.util.List; |
39 import java.util.List; |
40 import java.util.Objects; |
40 import java.util.Objects; |
41 import java.util.Optional; |
41 import java.util.Optional; |
42 import java.util.concurrent.CompletableFuture; |
42 import java.util.concurrent.CompletableFuture; |
43 import java.util.concurrent.CompletionStage; |
43 import java.util.concurrent.CompletionStage; |
447 * |
449 * |
448 * <p> When the {@code HttpResponse} object is returned, the body has |
450 * <p> When the {@code HttpResponse} object is returned, the body has |
449 * been completely written to the file, and {@link #body()} returns a |
451 * been completely written to the file, and {@link #body()} returns a |
450 * reference to its {@link Path}. |
452 * reference to its {@link Path}. |
451 * |
453 * |
452 * @param file the filename to store the body in |
454 * <p> Security manager permission checks are performed in this factory |
|
455 * method, when a {@code BodyHandler} is created. Care must be taken |
|
456 * that the {@code BodyHandler} is not shared with untrusted code. |
|
457 * |
|
458 * @param file the file to store the body in |
453 * @param openOptions any options to use when opening/creating the file |
459 * @param openOptions any options to use when opening/creating the file |
454 * @return a response body handler |
460 * @return a response body handler |
|
461 * @throws IllegalArgumentException if an invalid set of open options |
|
462 * are specified |
455 * @throws SecurityException If a security manager has been installed |
463 * @throws SecurityException If a security manager has been installed |
456 * and it denies {@link SecurityManager#checkWrite(String) |
464 * and it denies {@link SecurityManager#checkWrite(String) |
457 * write access} to the file. The {@link |
465 * write access} to the file. |
458 * SecurityManager#checkDelete(String) checkDelete} method is |
|
459 * invoked to check delete access if the file is opened with |
|
460 * the {@code DELETE_ON_CLOSE} option. |
|
461 */ |
466 */ |
462 public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) { |
467 public static BodyHandler<Path> asFile(Path file, OpenOption... openOptions) { |
463 Objects.requireNonNull(file); |
468 Objects.requireNonNull(file); |
464 List<OpenOption> opts = List.of(openOptions); |
469 List<OpenOption> opts = List.of(openOptions); |
|
470 if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) { |
|
471 // these options make no sense, since the FileChannel is not exposed |
|
472 throw new IllegalArgumentException("invalid openOptions: " + opts); |
|
473 } |
|
474 |
465 SecurityManager sm = System.getSecurityManager(); |
475 SecurityManager sm = System.getSecurityManager(); |
466 if (sm != null) { |
476 if (sm != null) { |
467 String fn = pathForSecurityCheck(file); |
477 String fn = pathForSecurityCheck(file); |
468 sm.checkWrite(fn); |
478 sm.checkWrite(fn); |
469 if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE)) |
|
470 sm.checkDelete(fn); |
|
471 if (opts.contains(StandardOpenOption.READ)) |
|
472 sm.checkRead(fn); |
|
473 } |
479 } |
474 return new PathBodyHandler(file, openOptions); |
480 return new PathBodyHandler(file, opts); |
475 } |
481 } |
476 |
482 |
477 /** |
483 /** |
478 * Returns a {@code BodyHandler<Path>} that returns a |
484 * Returns a {@code BodyHandler<Path>} that returns a |
479 * {@link BodySubscriber BodySubscriber}{@code <Path>} obtained from |
485 * {@link BodySubscriber BodySubscriber}{@code <Path>}. |
480 * {@link BodySubscriber#asFile(Path) BodySubscriber.asFile(Path)}. |
486 * |
481 * |
487 * <p> Equivalent to: {@code asFile(file, CREATE, WRITE)} |
482 * <p> When the {@code HttpResponse} object is returned, the body has |
488 * |
483 * been completely written to the file, and {@link #body()} returns a |
489 * <p> Security manager permission checks are performed in this factory |
484 * reference to its {@link Path}. |
490 * method, when a {@code BodyHandler} is created. Care must be taken |
|
491 * that the {@code BodyHandler} is not shared with untrusted code. |
485 * |
492 * |
486 * @param file the file to store the body in |
493 * @param file the file to store the body in |
487 * @return a response body handler |
494 * @return a response body handler |
488 * @throws SecurityException if a security manager has been installed |
495 * @throws SecurityException If a security manager has been installed |
489 * and it denies {@link SecurityManager#checkWrite(String) |
496 * and it denies {@link SecurityManager#checkWrite(String) |
490 * write access} to the file |
497 * write access} to the file. |
491 */ |
498 */ |
492 public static BodyHandler<Path> asFile(Path file) { |
499 public static BodyHandler<Path> asFile(Path file) { |
493 return BodyHandler.asFile(file, StandardOpenOption.CREATE, |
500 return BodyHandler.asFile(file, CREATE, WRITE); |
494 StandardOpenOption.WRITE); |
|
495 } |
501 } |
496 |
502 |
497 /** |
503 /** |
498 * Returns a {@code BodyHandler<Path>} that returns a |
504 * Returns a {@code BodyHandler<Path>} that returns a |
499 * {@link BodySubscriber BodySubscriber}<{@link Path}> |
505 * {@link BodySubscriber BodySubscriber}<{@link Path}> |
509 * {@code Path} object for the file. The returned {@code Path} is the |
515 * {@code Path} object for the file. The returned {@code Path} is the |
510 * combination of the supplied directory name and the file name supplied |
516 * combination of the supplied directory name and the file name supplied |
511 * by the server. If the destination directory does not exist or cannot |
517 * by the server. If the destination directory does not exist or cannot |
512 * be written to, then the response will fail with an {@link IOException}. |
518 * be written to, then the response will fail with an {@link IOException}. |
513 * |
519 * |
|
520 * <p> Security manager permission checks are performed in this factory |
|
521 * method, when a {@code BodyHandler} is created. Care must be taken |
|
522 * that the {@code BodyHandler} is not shared with untrusted code. |
|
523 * |
514 * @param directory the directory to store the file in |
524 * @param directory the directory to store the file in |
515 * @param openOptions open options |
525 * @param openOptions open options used when opening the file |
516 * @return a response body handler |
526 * @return a response body handler |
|
527 * @throws IllegalArgumentException if the given path does not exist, |
|
528 * is not a directory, is not writable, or if an invalid set |
|
529 * of open options are specified |
517 * @throws SecurityException If a security manager has been installed |
530 * @throws SecurityException If a security manager has been installed |
518 * and it denies {@link SecurityManager#checkWrite(String) |
531 * and it denies {@linkplain SecurityManager#checkRead(String) |
519 * write access} to the file. The {@link |
532 * read access} or {@linkplain SecurityManager#checkWrite(String) |
520 * SecurityManager#checkDelete(String) checkDelete} method is |
533 * write access} to the directory. |
521 * invoked to check delete access if the file is opened with |
534 */ |
522 * the {@code DELETE_ON_CLOSE} option. |
|
523 */ |
|
524 //####: check if the dir exists and is writable?? |
|
525 public static BodyHandler<Path> asFileDownload(Path directory, |
535 public static BodyHandler<Path> asFileDownload(Path directory, |
526 OpenOption... openOptions) { |
536 OpenOption... openOptions) { |
527 Objects.requireNonNull(directory); |
537 Objects.requireNonNull(directory); |
528 List<OpenOption> opts = List.of(openOptions); |
538 List<OpenOption> opts = List.of(openOptions); |
|
539 if (opts.contains(DELETE_ON_CLOSE)) { |
|
540 throw new IllegalArgumentException("invalid option: " + DELETE_ON_CLOSE); |
|
541 } |
|
542 |
529 SecurityManager sm = System.getSecurityManager(); |
543 SecurityManager sm = System.getSecurityManager(); |
530 if (sm != null) { |
544 if (sm != null) { |
531 String fn = pathForSecurityCheck(directory); |
545 String fn = pathForSecurityCheck(directory); |
532 sm.checkWrite(fn); |
546 sm.checkWrite(fn); |
533 if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE)) |
547 sm.checkRead(fn); |
534 sm.checkDelete(fn); |
|
535 if (opts.contains(StandardOpenOption.READ)) |
|
536 sm.checkRead(fn); |
|
537 } |
548 } |
538 return new FileDownloadBodyHandler(directory, openOptions); |
549 |
|
550 if (Files.notExists(directory)) |
|
551 throw new IllegalArgumentException("non-existent directory: " + directory); |
|
552 if (!Files.isDirectory(directory)) |
|
553 throw new IllegalArgumentException("not a directory: " + directory); |
|
554 if (!Files.isWritable(directory)) |
|
555 throw new IllegalArgumentException("non-writable directory: " + directory); |
|
556 |
|
557 return new FileDownloadBodyHandler(directory, opts); |
539 } |
558 } |
540 |
559 |
541 /** |
560 /** |
542 * Returns a {@code BodyHandler<InputStream>} that returns a |
561 * Returns a {@code BodyHandler<InputStream>} that returns a |
543 * {@link BodySubscriber BodySubscriber}{@code <InputStream>} obtained |
562 * {@link BodySubscriber BodySubscriber}{@code <InputStream>} obtained |
932 return new ResponseSubscribers.ByteArraySubscriber<>( |
951 return new ResponseSubscribers.ByteArraySubscriber<>( |
933 Function.identity() // no conversion |
952 Function.identity() // no conversion |
934 ); |
953 ); |
935 } |
954 } |
936 |
955 |
937 // no security check |
|
938 private static BodySubscriber<Path> asFileImpl(Path file, OpenOption... openOptions) { |
|
939 return new ResponseSubscribers.PathSubscriber(file, openOptions); |
|
940 } |
|
941 |
|
942 /** |
956 /** |
943 * Returns a {@code BodySubscriber} which stores the response body in a |
957 * Returns a {@code BodySubscriber} which stores the response body in a |
944 * file opened with the given options and name. The file will be opened |
958 * file opened with the given options and name. The file will be opened |
945 * with the given options using {@link FileChannel#open(Path,OpenOption...) |
959 * with the given options using {@link FileChannel#open(Path,OpenOption...) |
946 * FileChannel.open} just before the body is read. Any exception thrown |
960 * FileChannel.open} just before the body is read. Any exception thrown |
949 * BodyHandler) HttpClient::sendAsync} as appropriate. |
963 * BodyHandler) HttpClient::sendAsync} as appropriate. |
950 * |
964 * |
951 * <p> The {@link HttpResponse} using this subscriber is available after |
965 * <p> The {@link HttpResponse} using this subscriber is available after |
952 * the entire response has been read. |
966 * the entire response has been read. |
953 * |
967 * |
|
968 * <p> Security manager permission checks are performed in this factory |
|
969 * method, when a {@code BodySubscriber} is created. Care must be taken |
|
970 * that the {@code BodyHandler} is not shared with untrusted code. |
|
971 * |
954 * @param file the file to store the body in |
972 * @param file the file to store the body in |
955 * @param openOptions the list of options to open the file with |
973 * @param openOptions the list of options to open the file with |
956 * @return a body subscriber |
974 * @return a body subscriber |
957 * @throws SecurityException If a security manager has been installed |
975 * @throws IllegalArgumentException if an invalid set of open options |
|
976 * are specified |
|
977 * @throws SecurityException if a security manager has been installed |
958 * and it denies {@link SecurityManager#checkWrite(String) |
978 * and it denies {@link SecurityManager#checkWrite(String) |
959 * write access} to the file. The {@link |
979 * write access} to the file |
960 * SecurityManager#checkDelete(String) checkDelete} method is |
|
961 * invoked to check delete access if the file is opened with the |
|
962 * {@code DELETE_ON_CLOSE} option. |
|
963 */ |
980 */ |
964 public static BodySubscriber<Path> asFile(Path file, OpenOption... openOptions) { |
981 public static BodySubscriber<Path> asFile(Path file, OpenOption... openOptions) { |
965 Objects.requireNonNull(file); |
982 Objects.requireNonNull(file); |
966 List<OpenOption> opts = List.of(openOptions); |
983 List<OpenOption> opts = List.of(openOptions); |
|
984 if (opts.contains(DELETE_ON_CLOSE) || opts.contains(READ)) { |
|
985 // these options make no sense, since the FileChannel is not exposed |
|
986 throw new IllegalArgumentException("invalid openOptions: " + opts); |
|
987 } |
|
988 |
967 SecurityManager sm = System.getSecurityManager(); |
989 SecurityManager sm = System.getSecurityManager(); |
968 if (sm != null) { |
990 if (sm != null) { |
969 String fn = pathForSecurityCheck(file); |
991 String fn = pathForSecurityCheck(file); |
970 sm.checkWrite(fn); |
992 sm.checkWrite(fn); |
971 if (opts.contains(StandardOpenOption.DELETE_ON_CLOSE)) |
|
972 sm.checkDelete(fn); |
|
973 if (opts.contains(StandardOpenOption.READ)) |
|
974 sm.checkRead(fn); |
|
975 } |
993 } |
976 return asFileImpl(file, openOptions); |
994 return new PathSubscriber(file, opts); |
977 } |
995 } |
978 |
996 |
979 /** |
997 /** |
980 * Returns a {@code BodySubscriber} which stores the response body in a |
998 * Returns a {@code BodySubscriber} which stores the response body in a |
981 * file opened with the given name. Has the same effect as calling |
999 * file opened with the given name. |
982 * {@link #asFile(Path, OpenOption...) asFile} with the standard open |
1000 * |
983 * options {@code CREATE} and {@code WRITE} |
1001 * <p> Equivalent to: {@code asFile(file, CREATE, WRITE)} |
984 * |
1002 * |
985 * <p> The {@link HttpResponse} using this subscriber is available after |
1003 * <p> Security manager permission checks are performed in this factory |
986 * the entire response has been read. |
1004 * method, when a {@code BodySubscriber} is created. Care must be taken |
|
1005 * that the {@code BodyHandler} is not shared with untrusted code. |
987 * |
1006 * |
988 * @param file the file to store the body in |
1007 * @param file the file to store the body in |
989 * @return a body subscriber |
1008 * @return a body subscriber |
990 * @throws SecurityException if a security manager has been installed |
1009 * @throws SecurityException if a security manager has been installed |
991 * and it denies {@link SecurityManager#checkWrite(String) |
1010 * and it denies {@link SecurityManager#checkWrite(String) |
992 * write access} to the file |
1011 * write access} to the file |
993 */ |
1012 */ |
994 public static BodySubscriber<Path> asFile(Path file) { |
1013 public static BodySubscriber<Path> asFile(Path file) { |
995 return asFile(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE); |
1014 return asFile(file, CREATE, WRITE); |
996 } |
1015 } |
997 |
1016 |
998 /** |
1017 /** |
999 * Returns a {@code BodySubscriber} which provides the incoming body |
1018 * Returns a {@code BodySubscriber} which provides the incoming body |
1000 * data to the provided Consumer of {@code Optional<byte[]>}. Each |
1019 * data to the provided Consumer of {@code Optional<byte[]>}. Each |