|
1 | 1 | package org.example; |
2 | 2 |
|
3 | 3 | import org.example.http.HttpResponseBuilder; |
4 | | -import org.example.httpparser.HttpRequest; |
5 | 4 |
|
6 | 5 | import java.io.File; |
7 | 6 | import java.io.IOException; |
| 7 | +import java.io.OutputStream; |
8 | 8 | import java.nio.file.Files; |
9 | 9 |
|
10 | 10 | import static org.example.http.HttpResponseBuilder.*; |
11 | 11 |
|
12 | | -public class StaticFileHandler implements org.example.server.TerminalHandler { |
13 | | - private final String webRoot; |
| 12 | +public class StaticFileHandler { |
| 13 | + |
| 14 | + private final String WEB_ROOT; |
| 15 | + private byte[] fileBytes; |
| 16 | + private int statusCode; |
| 17 | + |
14 | 18 | public static final String TEXT_PLAIN_CHARSET_UTF_8 = "text/plain; charset=utf-8"; |
15 | 19 |
|
16 | | - // Constructor for production |
17 | 20 | public StaticFileHandler() { |
18 | | - webRoot = "www"; |
| 21 | + WEB_ROOT = "www"; |
19 | 22 | } |
20 | 23 |
|
21 | | - // Constructor for tests, otherwise the www folder won't be seen |
22 | 24 | public StaticFileHandler(String webRoot) { |
23 | | - this.webRoot = webRoot; |
| 25 | + this.WEB_ROOT = webRoot; |
24 | 26 | } |
25 | 27 |
|
26 | | - private void handleGetRequest(HttpRequest request, HttpResponseBuilder response) { |
27 | | - byte[] fileBytes; |
28 | | - if (!"GET".equals(request.getMethod())) { |
29 | | - fileBytes = "405 Method Not Allowed".getBytes(java.nio.charset.StandardCharsets.UTF_8); |
30 | | - response.setContentType(TEXT_PLAIN_CHARSET_UTF_8); |
31 | | - response.setStatusCode(SC_METHOD_NOT_ALLOWED); |
32 | | - response.setHeader("Allow", "GET"); |
33 | | - response.setBody(fileBytes); |
34 | | - return; |
35 | | - } |
36 | | - |
37 | | - String uri = request.getPath(); |
38 | | - if (uri == null) { |
39 | | - uri = ""; |
40 | | - } |
41 | | - |
42 | | - // Sanitize URI |
| 28 | + private void handleGetRequest(String uri) throws IOException { |
| 29 | + // Clean URI |
43 | 30 | int q = uri.indexOf('?'); |
44 | 31 | if (q >= 0) uri = uri.substring(0, q); |
45 | 32 | int h = uri.indexOf('#'); |
46 | 33 | if (h >= 0) uri = uri.substring(0, h); |
47 | 34 | uri = uri.replace("\0", ""); |
| 35 | + if (uri.startsWith("/")) uri = uri.substring(1); |
| 36 | + if (uri.isEmpty()) uri = "index.html"; |
48 | 37 |
|
49 | | - uri = defaultFile(uri); |
| 38 | + File root = new File(WEB_ROOT).getCanonicalFile(); |
| 39 | + File file = new File(root, uri).getCanonicalFile(); |
50 | 40 |
|
51 | | - // Path traversal check |
52 | | - File root; |
53 | | - File file; |
54 | | - try { |
55 | | - root = new File(webRoot).getCanonicalFile(); |
56 | | - file = new File(root, uri).getCanonicalFile(); |
57 | | - } catch (IOException e) { |
58 | | - throw new RuntimeException(e); |
| 41 | + if (!file.toPath().startsWith(root.toPath())) { |
| 42 | + fileBytes = "403 Forbidden".getBytes(); |
| 43 | + statusCode = SC_FORBIDDEN; |
| 44 | + return; |
59 | 45 | } |
60 | 46 |
|
61 | | - if (!file.toPath().startsWith(root.toPath())) { |
62 | | - fileBytes = "403 Forbidden".getBytes(java.nio.charset.StandardCharsets.UTF_8); |
63 | | - response.setContentType(TEXT_PLAIN_CHARSET_UTF_8); |
64 | | - response.setStatusCode(SC_FORBIDDEN); |
65 | | - } else if (file.isFile()) { |
66 | | - try { |
67 | | - fileBytes = Files.readAllBytes(file.toPath()); |
68 | | - } catch (IOException e) { |
69 | | - throw new RuntimeException(e); |
70 | | - } |
71 | | - response.setContentTypeFromFilename(file.toString()); |
72 | | - response.setStatusCode(SC_OK); |
| 47 | + if (file.isFile()) { |
| 48 | + fileBytes = Files.readAllBytes(file.toPath()); |
| 49 | + statusCode = SC_OK; |
73 | 50 | } else { |
74 | | - File errorFile = new File(webRoot, "pageNotFound.html"); |
| 51 | + File errorFile = new File(WEB_ROOT, "pageNotFound.html"); |
75 | 52 | if (errorFile.isFile()) { |
76 | | - try { |
77 | | - fileBytes = Files.readAllBytes(errorFile.toPath()); |
78 | | - } catch (IOException e) { |
79 | | - throw new RuntimeException(e); |
80 | | - } |
81 | | - response.setContentTypeFromFilename(errorFile.toString()); |
| 53 | + fileBytes = Files.readAllBytes(errorFile.toPath()); |
82 | 54 | } else { |
83 | | - fileBytes = "404 Not Found".getBytes(java.nio.charset.StandardCharsets.UTF_8); |
84 | | - response.setContentType(TEXT_PLAIN_CHARSET_UTF_8); |
| 55 | + fileBytes = "404 Not Found".getBytes(); |
85 | 56 | } |
86 | | - response.setStatusCode(SC_NOT_FOUND); |
| 57 | + statusCode = SC_NOT_FOUND; |
87 | 58 | } |
88 | | - response.setBody(fileBytes); |
89 | 59 | } |
90 | 60 |
|
91 | | - @Override |
92 | | - public void handle(HttpRequest request, HttpResponseBuilder response) { |
93 | | - handleGetRequest(request, response); |
| 61 | + public void sendGetRequest(OutputStream outputStream, String uri) throws IOException { |
| 62 | + handleGetRequest(uri); |
| 63 | + HttpResponseBuilder response = new HttpResponseBuilder(); |
| 64 | + response.setStatusCode(statusCode); |
| 65 | + response.setContentTypeFromFilename(uri); |
| 66 | + response.setBody(fileBytes); |
| 67 | + outputStream.write(response.build()); |
| 68 | + outputStream.flush(); |
94 | 69 | } |
95 | 70 |
|
96 | | - private String defaultFile(String uri) { |
97 | | - if (uri == null || uri.isBlank()) { |
98 | | - return "index.html"; |
99 | | - } |
100 | | - String normalized = uri; |
101 | | - while (normalized.startsWith("/")) { |
102 | | - normalized = normalized.substring(1); |
103 | | - } |
104 | | - if (normalized.isEmpty()) { |
105 | | - return "index.html"; |
106 | | - } |
107 | | - if (normalized.endsWith("/")) { |
108 | | - normalized = normalized + "index.html"; |
109 | | - } |
110 | | - return normalized; |
| 71 | + public void sendHeadRequest(OutputStream outputStream, String uri) throws IOException { |
| 72 | + handleGetRequest(uri); |
| 73 | + HttpResponseBuilder response = new HttpResponseBuilder(); |
| 74 | + response.setStatusCode(statusCode); |
| 75 | + response.setContentTypeFromFilename(uri); |
| 76 | + response.setHeader("Content-Length", String.valueOf(fileBytes.length)); |
| 77 | + response.setBody(new byte[0]); |
| 78 | + outputStream.write(response.build()); |
| 79 | + outputStream.flush(); |
111 | 80 | } |
112 | 81 | } |
0 commit comments