Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java
41171 views
1
/*
2
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package jdk.internal.net.http;
27
28
import java.io.File;
29
import java.io.FilePermission;
30
import java.io.IOException;
31
import java.io.UncheckedIOException;
32
import java.net.URI;
33
import java.nio.file.Files;
34
import java.nio.file.OpenOption;
35
import java.nio.file.Path;
36
import java.nio.file.Paths;
37
import java.security.AccessControlContext;
38
import java.security.AccessController;
39
import java.util.List;
40
import java.util.concurrent.CompletableFuture;
41
import java.util.concurrent.ConcurrentMap;
42
import java.util.function.Function;
43
import java.net.http.HttpRequest;
44
import java.net.http.HttpResponse;
45
import java.net.http.HttpResponse.BodyHandler;
46
import java.net.http.HttpResponse.ResponseInfo;
47
import java.net.http.HttpResponse.BodySubscriber;
48
import java.util.regex.Matcher;
49
import java.util.regex.Pattern;
50
import jdk.internal.net.http.ResponseSubscribers.PathSubscriber;
51
import static java.util.regex.Pattern.CASE_INSENSITIVE;
52
53
public final class ResponseBodyHandlers {
54
55
private ResponseBodyHandlers() { }
56
57
private static final String pathForSecurityCheck(Path path) {
58
return path.toFile().getPath();
59
}
60
61
/**
62
* A Path body handler.
63
*/
64
public static class PathBodyHandler implements BodyHandler<Path>{
65
private final Path file;
66
private final List<OpenOption> openOptions; // immutable list
67
@SuppressWarnings("removal")
68
private final AccessControlContext acc;
69
private final FilePermission filePermission;
70
71
/**
72
* Factory for creating PathBodyHandler.
73
*
74
* Permission checks are performed here before construction of the
75
* PathBodyHandler. Permission checking and construction are
76
* deliberately and tightly co-located.
77
*/
78
public static PathBodyHandler create(Path file,
79
List<OpenOption> openOptions) {
80
FilePermission filePermission = null;
81
@SuppressWarnings("removal")
82
SecurityManager sm = System.getSecurityManager();
83
if (sm != null) {
84
try {
85
String fn = pathForSecurityCheck(file);
86
FilePermission writePermission = new FilePermission(fn, "write");
87
sm.checkPermission(writePermission);
88
filePermission = writePermission;
89
} catch (UnsupportedOperationException ignored) {
90
// path not associated with the default file system provider
91
}
92
}
93
94
assert filePermission == null || filePermission.getActions().equals("write");
95
@SuppressWarnings("removal")
96
var acc = sm != null ? AccessController.getContext() : null;
97
return new PathBodyHandler(file, openOptions, acc, filePermission);
98
}
99
100
private PathBodyHandler(Path file,
101
List<OpenOption> openOptions,
102
@SuppressWarnings("removal") AccessControlContext acc,
103
FilePermission filePermission) {
104
this.file = file;
105
this.openOptions = openOptions;
106
this.acc = acc;
107
this.filePermission = filePermission;
108
}
109
110
@Override
111
public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
112
return new PathSubscriber(file, openOptions, acc, filePermission);
113
}
114
}
115
116
/** With push promise Map implementation */
117
public static class PushPromisesHandlerWithMap<T>
118
implements HttpResponse.PushPromiseHandler<T>
119
{
120
private final ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap;
121
private final Function<HttpRequest,BodyHandler<T>> pushPromiseHandler;
122
123
public PushPromisesHandlerWithMap(Function<HttpRequest,BodyHandler<T>> pushPromiseHandler,
124
ConcurrentMap<HttpRequest,CompletableFuture<HttpResponse<T>>> pushPromisesMap) {
125
this.pushPromiseHandler = pushPromiseHandler;
126
this.pushPromisesMap = pushPromisesMap;
127
}
128
129
@Override
130
public void applyPushPromise(
131
HttpRequest initiatingRequest, HttpRequest pushRequest,
132
Function<BodyHandler<T>,CompletableFuture<HttpResponse<T>>> acceptor)
133
{
134
URI initiatingURI = initiatingRequest.uri();
135
URI pushRequestURI = pushRequest.uri();
136
if (!initiatingURI.getHost().equalsIgnoreCase(pushRequestURI.getHost()))
137
return;
138
139
int initiatingPort = initiatingURI.getPort();
140
if (initiatingPort == -1 ) {
141
if ("https".equalsIgnoreCase(initiatingURI.getScheme()))
142
initiatingPort = 443;
143
else
144
initiatingPort = 80;
145
}
146
int pushPort = pushRequestURI.getPort();
147
if (pushPort == -1 ) {
148
if ("https".equalsIgnoreCase(pushRequestURI.getScheme()))
149
pushPort = 443;
150
else
151
pushPort = 80;
152
}
153
if (initiatingPort != pushPort)
154
return;
155
156
CompletableFuture<HttpResponse<T>> cf =
157
acceptor.apply(pushPromiseHandler.apply(pushRequest));
158
pushPromisesMap.put(pushRequest, cf);
159
}
160
}
161
162
// Similar to Path body handler, but for file download.
163
public static class FileDownloadBodyHandler implements BodyHandler<Path> {
164
private final Path directory;
165
private final List<OpenOption> openOptions;
166
@SuppressWarnings("removal")
167
private final AccessControlContext acc;
168
private final FilePermission[] filePermissions; // may be null
169
170
/**
171
* Factory for creating FileDownloadBodyHandler.
172
*
173
* Permission checks are performed here before construction of the
174
* FileDownloadBodyHandler. Permission checking and construction are
175
* deliberately and tightly co-located.
176
*/
177
public static FileDownloadBodyHandler create(Path directory,
178
List<OpenOption> openOptions) {
179
String fn;
180
try {
181
fn = pathForSecurityCheck(directory);
182
} catch (UnsupportedOperationException uoe) {
183
// directory not associated with the default file system provider
184
throw new IllegalArgumentException("invalid path: " + directory, uoe);
185
}
186
187
FilePermission filePermissions[] = null;
188
@SuppressWarnings("removal")
189
SecurityManager sm = System.getSecurityManager();
190
if (sm != null) {
191
FilePermission writePermission = new FilePermission(fn, "write");
192
String writePathPerm = fn + File.separatorChar + "*";
193
FilePermission writeInDirPermission = new FilePermission(writePathPerm, "write");
194
sm.checkPermission(writeInDirPermission);
195
FilePermission readPermission = new FilePermission(fn, "read");
196
sm.checkPermission(readPermission);
197
198
// read permission is only needed before determine the below checks
199
// only write permission is required when downloading to the file
200
filePermissions = new FilePermission[] { writePermission, writeInDirPermission };
201
}
202
203
// existence, etc, checks must be after permission checks
204
if (Files.notExists(directory))
205
throw new IllegalArgumentException("non-existent directory: " + directory);
206
if (!Files.isDirectory(directory))
207
throw new IllegalArgumentException("not a directory: " + directory);
208
if (!Files.isWritable(directory))
209
throw new IllegalArgumentException("non-writable directory: " + directory);
210
211
assert filePermissions == null || (filePermissions[0].getActions().equals("write")
212
&& filePermissions[1].getActions().equals("write"));
213
@SuppressWarnings("removal")
214
var acc = sm != null ? AccessController.getContext() : null;
215
return new FileDownloadBodyHandler(directory, openOptions, acc, filePermissions);
216
}
217
218
private FileDownloadBodyHandler(Path directory,
219
List<OpenOption> openOptions,
220
@SuppressWarnings("removal") AccessControlContext acc,
221
FilePermission... filePermissions) {
222
this.directory = directory;
223
this.openOptions = openOptions;
224
this.acc = acc;
225
this.filePermissions = filePermissions;
226
}
227
228
/** The "attachment" disposition-type and separator. */
229
static final String DISPOSITION_TYPE = "attachment;";
230
231
/** The "filename" parameter. */
232
static final Pattern FILENAME = Pattern.compile("filename\\s*=", CASE_INSENSITIVE);
233
234
static final List<String> PROHIBITED = List.of(".", "..", "", "~" , "|");
235
236
static final UncheckedIOException unchecked(ResponseInfo rinfo,
237
String msg) {
238
String s = String.format("%s in response [%d, %s]", msg, rinfo.statusCode(), rinfo.headers());
239
return new UncheckedIOException(new IOException(s));
240
}
241
242
@Override
243
public BodySubscriber<Path> apply(ResponseInfo responseInfo) {
244
String dispoHeader = responseInfo.headers().firstValue("Content-Disposition")
245
.orElseThrow(() -> unchecked(responseInfo, "No Content-Disposition header"));
246
247
if (!dispoHeader.regionMatches(true, // ignoreCase
248
0, DISPOSITION_TYPE,
249
0, DISPOSITION_TYPE.length())) {
250
throw unchecked(responseInfo, "Unknown Content-Disposition type");
251
}
252
253
Matcher matcher = FILENAME.matcher(dispoHeader);
254
if (!matcher.find()) {
255
throw unchecked(responseInfo, "Bad Content-Disposition filename parameter");
256
}
257
int n = matcher.end();
258
259
int semi = dispoHeader.substring(n).indexOf(";");
260
String filenameParam;
261
if (semi < 0) {
262
filenameParam = dispoHeader.substring(n);
263
} else {
264
filenameParam = dispoHeader.substring(n, n + semi);
265
}
266
267
// strip all but the last path segment
268
int x = filenameParam.lastIndexOf("/");
269
if (x != -1) {
270
filenameParam = filenameParam.substring(x+1);
271
}
272
x = filenameParam.lastIndexOf("\\");
273
if (x != -1) {
274
filenameParam = filenameParam.substring(x+1);
275
}
276
277
filenameParam = filenameParam.trim();
278
279
if (filenameParam.startsWith("\"")) { // quoted-string
280
if (!filenameParam.endsWith("\"") || filenameParam.length() == 1) {
281
throw unchecked(responseInfo,
282
"Badly quoted Content-Disposition filename parameter");
283
}
284
filenameParam = filenameParam.substring(1, filenameParam.length() -1 );
285
} else { // token,
286
if (filenameParam.contains(" ")) { // space disallowed
287
throw unchecked(responseInfo,
288
"unquoted space in Content-Disposition filename parameter");
289
}
290
}
291
292
if (PROHIBITED.contains(filenameParam)) {
293
throw unchecked(responseInfo,
294
"Prohibited Content-Disposition filename parameter:"
295
+ filenameParam);
296
}
297
298
Path file = Paths.get(directory.toString(), filenameParam);
299
300
if (!file.startsWith(directory)) {
301
throw unchecked(responseInfo,
302
"Resulting file, " + file.toString() + ", outside of given directory");
303
}
304
305
return new PathSubscriber(file, openOptions, acc, filePermissions);
306
}
307
}
308
}
309
310