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