1
18
19 package io.undertow.server.handlers.resource;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.net.JarURLConnection;
25 import java.net.URISyntaxException;
26 import java.net.URL;
27 import java.net.URLConnection;
28 import java.nio.ByteBuffer;
29 import java.nio.file.DirectoryStream;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.util.Date;
34 import java.util.LinkedList;
35 import java.util.List;
36
37 import org.xnio.IoUtils;
38 import io.undertow.UndertowLogger;
39 import io.undertow.io.IoCallback;
40 import io.undertow.io.Sender;
41 import io.undertow.server.HttpServerExchange;
42 import io.undertow.util.DateUtils;
43 import io.undertow.util.ETag;
44 import io.undertow.util.MimeMappings;
45 import io.undertow.util.StatusCodes;
46
47
50 public class URLResource implements Resource, RangeAwareResource {
51
52 private final URL url;
53 private final String path;
54
55 private boolean connectionOpened = false;
56 private Date lastModified;
57 private Long contentLength;
58
59 @Deprecated
60 public URLResource(final URL url, URLConnection connection, String path) {
61 this(url, path);
62 }
63
64 public URLResource(final URL url, String path) {
65 this.url = url;
66 this.path = path;
67 }
68
69 @Override
70 public String getPath() {
71 return path;
72 }
73
74 @Override
75 public Date getLastModified() {
76 openConnection();
77 return lastModified;
78 }
79
80 private void openConnection() {
81 if (!connectionOpened) {
82 connectionOpened = true;
83 URLConnection connection = null;
84 try {
85 try {
86 connection = url.openConnection();
87 } catch (IOException e) {
88 lastModified = null;
89 contentLength = null;
90 return;
91 }
92 if (url.getProtocol().equals("jar")) {
93 connection.setUseCaches(false);
94 URL jar = ((JarURLConnection) connection).getJarFileURL();
95 lastModified = new Date(new File(jar.getFile()).lastModified());
96 } else {
97 lastModified = new Date(connection.getLastModified());
98 }
99 contentLength = connection.getContentLengthLong();
100 } finally {
101 if (connection != null) {
102 try {
103 IoUtils.safeClose(connection.getInputStream());
104 } catch (IOException e) {
105
106 }
107 }
108 }
109 }
110 }
111
112 @Override
113 public String getLastModifiedString() {
114 return DateUtils.toDateString(getLastModified());
115 }
116
117 @Override
118 public ETag getETag() {
119 return null;
120 }
121
122 @Override
123 public String getName() {
124 String path = url.getPath();
125 if (path.endsWith("/")) {
126 path = path.substring(0, path.length() - 1);
127 }
128 int sepIndex = path.lastIndexOf("/");
129 if (sepIndex != -1) {
130 path = path.substring(sepIndex + 1);
131 }
132 return path;
133 }
134
135 @Override
136 public boolean isDirectory() {
137 Path file = getFilePath();
138 if (file != null) {
139 return Files.isDirectory(file);
140 } else if (url.getPath().endsWith("/")) {
141 return true;
142 }
143 return false;
144 }
145
146 @Override
147 public List<Resource> list() {
148 List<Resource> result = new LinkedList<>();
149 Path file = getFilePath();
150 try {
151 if (file != null) {
152 try (DirectoryStream<Path> stream = Files.newDirectoryStream(file)) {
153 for (Path child : stream) {
154 result.add(new URLResource(child.toUri().toURL(), child.toString()));
155 }
156 }
157 }
158 } catch (IOException e) {
159 throw new RuntimeException(e);
160 }
161 return result;
162 }
163
164 @Override
165 public String getContentType(final MimeMappings mimeMappings) {
166 final String fileName = getName();
167 int index = fileName.lastIndexOf('.');
168 if (index != -1 && index != fileName.length() - 1) {
169 return mimeMappings.getMimeType(fileName.substring(index + 1));
170 }
171 return null;
172 }
173
174 @Override
175 public void serve(Sender sender, HttpServerExchange exchange, IoCallback completionCallback) {
176 serveImpl(sender, exchange, -1, -1, false, completionCallback);
177 }
178
179 public void serveImpl(final Sender sender, final HttpServerExchange exchange, final long start, final long end, final boolean range, final IoCallback completionCallback) {
180
181 class ServerTask implements Runnable, IoCallback {
182
183 private InputStream inputStream;
184 private byte[] buffer;
185
186 long toSkip = start;
187 long remaining = end - start + 1;
188
189 @Override
190 public void run() {
191 if (range && remaining == 0) {
192
193 IoUtils.safeClose(inputStream);
194 completionCallback.onComplete(exchange, sender);
195 return;
196 }
197 if (inputStream == null) {
198 try {
199 inputStream = url.openStream();
200 } catch (IOException e) {
201 exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
202 return;
203 }
204 buffer = new byte[1024];
205 }
206 try {
207 int res = inputStream.read(buffer);
208 if (res == -1) {
209
210 IoUtils.safeClose(inputStream);
211 completionCallback.onComplete(exchange, sender);
212 return;
213 }
214 int bufferStart = 0;
215 int length = res;
216 if (range && toSkip > 0) {
217
218
219 while (toSkip > res) {
220 toSkip -= res;
221 res = inputStream.read(buffer);
222 if (res == -1) {
223
224 IoUtils.safeClose(inputStream);
225 completionCallback.onComplete(exchange, sender);
226 return;
227 }
228 }
229 bufferStart = (int) toSkip;
230 length -= toSkip;
231 toSkip = 0;
232 }
233 if (range && length > remaining) {
234 length = (int) remaining;
235 }
236 sender.send(ByteBuffer.wrap(buffer, bufferStart, length), this);
237 } catch (IOException e) {
238 onException(exchange, sender, e);
239 }
240
241 }
242
243 @Override
244 public void onComplete(final HttpServerExchange exchange, final Sender sender) {
245 if (exchange.isInIoThread()) {
246 exchange.dispatch(this);
247 } else {
248 run();
249 }
250 }
251
252 @Override
253 public void onException(final HttpServerExchange exchange, final Sender sender, final IOException exception) {
254 UndertowLogger.REQUEST_IO_LOGGER.ioException(exception);
255 IoUtils.safeClose(inputStream);
256 if (!exchange.isResponseStarted()) {
257 exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
258 }
259 completionCallback.onException(exchange, sender, exception);
260 }
261 }
262
263 ServerTask serveTask = new ServerTask();
264 if (exchange.isInIoThread()) {
265 exchange.dispatch(serveTask);
266 } else {
267 serveTask.run();
268 }
269 }
270
271 @Override
272 public Long getContentLength() {
273 openConnection();
274 return contentLength;
275 }
276
277 @Override
278 public String getCacheKey() {
279 return url.toString();
280 }
281
282 @Override
283 public File getFile() {
284 Path path = getFilePath();
285 return path != null ? path.toFile() : null;
286 }
287
288 @Override
289 public Path getFilePath() {
290 if (url.getProtocol().equals("file")) {
291 try {
292 return Paths.get(url.toURI());
293 } catch (URISyntaxException e) {
294 return null;
295 }
296 }
297 return null;
298 }
299
300 @Override
301 public File getResourceManagerRoot() {
302 return null;
303 }
304
305 @Override
306 public Path getResourceManagerRootPath() {
307 return null;
308 }
309
310 @Override
311 public URL getUrl() {
312 return url;
313 }
314
315 @Override
316 public void serveRange(Sender sender, HttpServerExchange exchange, long start, long end, IoCallback completionCallback) {
317 serveImpl(sender, exchange, start, end, true, completionCallback);
318 }
319
320 @Override
321 public boolean isRangeSupported() {
322 return true;
323 }
324 }
325