-
Notifications
You must be signed in to change notification settings - Fork 532
/
Copy pathUtils.java
253 lines (216 loc) · 7.56 KB
/
Utils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*
* Copyright 2014 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.web.impl;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.regex.Pattern;
/**
* @author <a href="http://tfox.org">Tim Fox</a>
* @author <a href="http://pmlopes@gmail.com">Paulo Lopes</a>
*/
public class Utils {
public static ClassLoader getClassLoader() {
// try current thread
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
// try the current class
cl = Utils.class.getClassLoader();
if (cl == null) {
// fall back to a well known object that should alwways exist
cl = Object.class.getClassLoader();
}
}
return cl;
}
private static final ZoneId ZONE_GMT = ZoneId.of("GMT");
public static String formatRFC1123DateTime(final long time) {
return DateTimeFormatter.RFC_1123_DATE_TIME.format(Instant.ofEpochMilli(time).atZone(ZONE_GMT));
}
public static long parseRFC1123DateTime(final String header) {
try {
return header == null || header.isEmpty() ? -1 :
LocalDateTime.parse(header, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant(ZoneOffset.UTC).toEpochMilli();
} catch (DateTimeParseException ex) {
return -1;
}
}
public static String pathOffset(String path, RoutingContext context) {
final Route route = context.currentRoute();
// cannot make any assumptions
if (route == null) {
return path;
}
if (!route.isExactPath()) {
final String rest = context.pathParam("*");
if (rest != null) {
// normalize
if (rest.length() > 0) {
if (rest.charAt(0) == '/') {
return rest;
} else {
return "/" + rest;
}
} else {
return "/";
}
}
}
int prefixLen = 0;
String mountPoint = context.mountPoint();
if (mountPoint != null) {
prefixLen = mountPoint.length();
// special case we need to verify if a trailing slash is present and exclude
if (mountPoint.charAt(mountPoint.length() - 1) == '/') {
prefixLen--;
}
}
// we can only safely skip the route path if there are no variables or regex
if (!route.isRegexPath()) {
String routePath = route.getPath();
if (routePath != null) {
prefixLen += routePath.length();
// special case we need to verify if a trailing slash is present and exclude
if (routePath.charAt(routePath.length() - 1) == '/') {
prefixLen--;
}
}
}
return prefixLen != 0 ? path.substring(prefixLen) : path;
}
public static long secondsFactor(long millis) {
return millis - (millis % 1000);
}
public static boolean isJsonContentType(String contentType) {
return contentType.contains("application/json") || contentType.contains("+json");
}
public static boolean isXMLContentType(String contentType) {
return contentType.contains("application/xml") || contentType.contains("text/xml") || contentType.contains("+xml");
}
public static void addToMapIfAbsent(MultiMap map, CharSequence key, CharSequence value) {
if (!map.contains(key)) {
map.set(key, value);
}
}
public static void appendToMapIfAbsent(MultiMap map, CharSequence key, CharSequence sep, CharSequence value) {
if (!map.contains(key)) {
map.set(key, value);
} else {
String existing = map.get(key);
map.set(key, existing + sep + value);
}
}
/**
* RegExp to check for no-cache token in Cache-Control.
*/
private static final Pattern CACHE_CONTROL_NO_CACHE_REGEXP = Pattern.compile("(?:^|,)\\s*?no-cache\\s*?(?:,|$)");
public static boolean fresh(RoutingContext ctx) {
return fresh(ctx, -1);
}
public static boolean fresh(RoutingContext ctx, long lastModified) {
final HttpServerRequest req = ctx.request();
final HttpServerResponse res = ctx.response();
// fields
final String modifiedSince = req.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
final String noneMatch = req.getHeader(HttpHeaders.IF_NONE_MATCH);
// unconditional request
if (modifiedSince == null && noneMatch == null) {
return false;
}
// Always return stale when Cache-Control: no-cache
// to support end-to-end reload requests
// https://tools.ietf.org/html/rfc2616#section-14.9.4
final String cacheControl = req.getHeader(HttpHeaders.CACHE_CONTROL);
if (cacheControl != null && CACHE_CONTROL_NO_CACHE_REGEXP.matcher(cacheControl).find()) {
return false;
}
// if-none-match
if (noneMatch != null && !"*".equals(noneMatch)) {
final String etag = res.headers().get(HttpHeaders.ETAG);
if (etag == null) {
return false;
}
boolean etagStale = true;
// lookup etags
int end = 0;
int start = 0;
loop:
for (int i = 0; i < noneMatch.length(); i++) {
switch (noneMatch.charAt(i)) {
case ' ':
if (start == end) {
start = end = i + 1;
}
break;
case ',':
String match = noneMatch.substring(start, end);
if (match.equals(etag) || match.equals("W/" + etag) || ("W/" + match).equals(etag)) {
etagStale = false;
break loop;
}
start = end = i + 1;
break;
default:
end = i + 1;
break;
}
}
if (etagStale) {
// the parser run out of bytes, need to check if the match is valid
String match = noneMatch.substring(start, end);
if (!match.equals(etag) && !match.equals("W/" + etag) && !("W/" + match).equals(etag)) {
return false;
}
}
}
// if-modified-since
if (modifiedSince != null) {
if (lastModified == -1) {
// no custom last modified provided, will use the response headers if any
lastModified = parseRFC1123DateTime(res.headers().get(HttpHeaders.LAST_MODIFIED));
}
boolean modifiedStale = lastModified == -1 || !(lastModified <= parseRFC1123DateTime(modifiedSince));
return !modifiedStale;
}
return true;
}
public static boolean canUpgradeToWebsocket(HttpServerRequest req) {
// verify if we can upgrade
// 1. Connection header contains "Upgrade"
// 2. Upgrade header is "websocket"
final MultiMap headers = req.headers();
if (headers.contains(HttpHeaders.CONNECTION)) {
for (String connection : headers.getAll(HttpHeaders.CONNECTION)) {
if (connection.toLowerCase().contains(HttpHeaders.UPGRADE)) {
if (headers.contains(HttpHeaders.UPGRADE)) {
for (String upgrade : headers.getAll(HttpHeaders.UPGRADE)) {
if (upgrade.toLowerCase().contains(HttpHeaders.WEBSOCKET)) {
return true;
}
}
}
}
}
}
return false;
}
}