23 * questions. |
23 * questions. |
24 */ |
24 */ |
25 |
25 |
26 package jdk.internal.net.http; |
26 package jdk.internal.net.http; |
27 |
27 |
|
28 import java.io.File; |
|
29 import java.io.FilePermission; |
28 import java.io.IOException; |
30 import java.io.IOException; |
29 import java.io.UncheckedIOException; |
31 import java.io.UncheckedIOException; |
30 import java.net.URI; |
32 import java.net.URI; |
|
33 import java.nio.file.Files; |
31 import java.nio.file.OpenOption; |
34 import java.nio.file.OpenOption; |
32 import java.nio.file.Path; |
35 import java.nio.file.Path; |
33 import java.nio.file.Paths; |
36 import java.nio.file.Paths; |
34 import java.nio.file.StandardOpenOption; |
|
35 import java.util.List; |
37 import java.util.List; |
36 import java.util.concurrent.CompletableFuture; |
38 import java.util.concurrent.CompletableFuture; |
37 import java.util.concurrent.ConcurrentMap; |
39 import java.util.concurrent.ConcurrentMap; |
38 import java.util.function.Function; |
40 import java.util.function.Function; |
39 import java.net.http.HttpHeaders; |
41 import java.net.http.HttpHeaders; |
48 |
50 |
49 public final class ResponseBodyHandlers { |
51 public final class ResponseBodyHandlers { |
50 |
52 |
51 private ResponseBodyHandlers() { } |
53 private ResponseBodyHandlers() { } |
52 |
54 |
|
55 private static final String pathForSecurityCheck(Path path) { |
|
56 return path.toFile().getPath(); |
|
57 } |
|
58 |
53 /** |
59 /** |
54 * A Path body handler. |
60 * A Path body handler. |
55 */ |
61 */ |
56 public static class PathBodyHandler implements BodyHandler<Path>{ |
62 public static class PathBodyHandler implements BodyHandler<Path>{ |
57 private final Path file; |
63 private final Path file; |
58 private final List<OpenOption> openOptions; |
64 private final List<OpenOption> openOptions; // immutable list |
59 |
65 private final FilePermission filePermission; |
60 public PathBodyHandler(Path file, List<OpenOption> openOptions) { |
66 |
|
67 /** |
|
68 * Factory for creating PathBodyHandler. |
|
69 * |
|
70 * Permission checks are performed here before construction of the |
|
71 * PathBodyHandler. Permission checking and construction are |
|
72 * deliberately and tightly co-located. |
|
73 */ |
|
74 public static PathBodyHandler create(Path file, |
|
75 List<OpenOption> openOptions) { |
|
76 FilePermission filePermission = null; |
|
77 SecurityManager sm = System.getSecurityManager(); |
|
78 if (sm != null) { |
|
79 String fn = pathForSecurityCheck(file); |
|
80 FilePermission writePermission = new FilePermission(fn, "write"); |
|
81 sm.checkPermission(writePermission); |
|
82 filePermission = writePermission; |
|
83 } |
|
84 return new PathBodyHandler(file, openOptions, filePermission); |
|
85 } |
|
86 |
|
87 private PathBodyHandler(Path file, |
|
88 List<OpenOption> openOptions, |
|
89 FilePermission filePermission) { |
61 this.file = file; |
90 this.file = file; |
62 this.openOptions = openOptions; |
91 this.openOptions = openOptions; |
|
92 this.filePermission = filePermission; |
63 } |
93 } |
64 |
94 |
65 @Override |
95 @Override |
66 public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) { |
96 public BodySubscriber<Path> apply(int statusCode, HttpHeaders headers) { |
67 return new PathSubscriber(file, openOptions); |
97 return new PathSubscriber(file, openOptions, filePermission); |
68 } |
98 } |
69 } |
99 } |
70 |
100 |
71 /** With push promise Map implementation */ |
101 /** With push promise Map implementation */ |
72 public static class PushPromisesHandlerWithMap<T> |
102 public static class PushPromisesHandlerWithMap<T> |
116 |
146 |
117 // Similar to Path body handler, but for file download. |
147 // Similar to Path body handler, but for file download. |
118 public static class FileDownloadBodyHandler implements BodyHandler<Path> { |
148 public static class FileDownloadBodyHandler implements BodyHandler<Path> { |
119 private final Path directory; |
149 private final Path directory; |
120 private final List<OpenOption> openOptions; |
150 private final List<OpenOption> openOptions; |
121 |
151 private final FilePermission[] filePermissions; // may be null |
122 public FileDownloadBodyHandler(Path directory, List<OpenOption> openOptions) { |
152 |
|
153 /** |
|
154 * Factory for creating FileDownloadBodyHandler. |
|
155 * |
|
156 * Permission checks are performed here before construction of the |
|
157 * FileDownloadBodyHandler. Permission checking and construction are |
|
158 * deliberately and tightly co-located. |
|
159 */ |
|
160 public static FileDownloadBodyHandler create(Path directory, |
|
161 List<OpenOption> openOptions) { |
|
162 FilePermission filePermissions[] = null; |
|
163 SecurityManager sm = System.getSecurityManager(); |
|
164 if (sm != null) { |
|
165 String fn = pathForSecurityCheck(directory); |
|
166 FilePermission writePermission = new FilePermission(fn, "write"); |
|
167 String writePathPerm = fn + File.separatorChar + "*"; |
|
168 FilePermission writeInDirPermission = new FilePermission(writePathPerm, "write"); |
|
169 sm.checkPermission(writeInDirPermission); |
|
170 FilePermission readPermission = new FilePermission(fn, "read"); |
|
171 sm.checkPermission(readPermission); |
|
172 |
|
173 // read permission is only needed before determine the below checks |
|
174 // only write permission is required when downloading to the file |
|
175 filePermissions = new FilePermission[] { writePermission, writeInDirPermission }; |
|
176 } |
|
177 |
|
178 // existence, etc, checks must be after permission checks |
|
179 if (Files.notExists(directory)) |
|
180 throw new IllegalArgumentException("non-existent directory: " + directory); |
|
181 if (!Files.isDirectory(directory)) |
|
182 throw new IllegalArgumentException("not a directory: " + directory); |
|
183 if (!Files.isWritable(directory)) |
|
184 throw new IllegalArgumentException("non-writable directory: " + directory); |
|
185 |
|
186 return new FileDownloadBodyHandler(directory, openOptions, filePermissions); |
|
187 |
|
188 } |
|
189 |
|
190 private FileDownloadBodyHandler(Path directory, |
|
191 List<OpenOption> openOptions, |
|
192 FilePermission... filePermissions) { |
123 this.directory = directory; |
193 this.directory = directory; |
124 this.openOptions = openOptions; |
194 this.openOptions = openOptions; |
|
195 this.filePermissions = filePermissions; |
125 } |
196 } |
126 |
197 |
127 /** The "attachment" disposition-type and separator. */ |
198 /** The "attachment" disposition-type and separator. */ |
128 static final String DISPOSITION_TYPE = "attachment;"; |
199 static final String DISPOSITION_TYPE = "attachment;"; |
129 |
200 |
202 if (!file.startsWith(directory)) { |
273 if (!file.startsWith(directory)) { |
203 throw unchecked(statusCode, headers, |
274 throw unchecked(statusCode, headers, |
204 "Resulting file, " + file.toString() + ", outside of given directory"); |
275 "Resulting file, " + file.toString() + ", outside of given directory"); |
205 } |
276 } |
206 |
277 |
207 return new PathSubscriber(file, openOptions); |
278 return new PathSubscriber(file, openOptions, filePermissions); |
208 } |
279 } |
209 } |
280 } |
210 } |
281 } |