Skip to content

Commit 704f8d8

Browse files
committed
Gjort det coderabbit bätt mig om
1 parent f5b2f44 commit 704f8d8

File tree

4 files changed

+119
-42
lines changed

4 files changed

+119
-42
lines changed

src/main/java/org/example/CacheFilter.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
package org.example;
32

43
import java.io.IOException;
@@ -7,6 +6,8 @@
76
import java.util.concurrent.atomic.AtomicLong;
87
import java.util.logging.Logger;
98
import java.util.logging.Level;
9+
import java.net.URLDecoder;
10+
import java.nio.charset.StandardCharsets;
1011

1112
/**
1213
* Thread-safe in-memory cache filter using ConcurrentHashMap
@@ -55,7 +56,7 @@ public byte[] getOrFetch(String uri, FileProvider provider) throws IOException {
5556
CacheEntry entry = cache.get(uri);
5657
if (entry != null) {
5758
entry.recordAccess();
58-
LOGGER.log(Level.FINE, " Cache hit for: " + uri);
59+
LOGGER.log(Level.FINE, " Cache hit for: " + uri);
5960
return entry.data;
6061
}
6162

@@ -65,14 +66,14 @@ public byte[] getOrFetch(String uri, FileProvider provider) throws IOException {
6566
entry = cache.get(uri);
6667
if (entry != null) {
6768
entry.recordAccess();
68-
LOGGER.log(Level.FINE, "Cache hit for: " + uri + " (from concurrent fetch)");
69+
LOGGER.log(Level.FINE, "Cache hit for: " + uri + " (from concurrent fetch)");
6970
return entry.data;
7071
}
7172

7273

7374

7475
// Fetch och cachelagra
75-
LOGGER.log(Level.FINE, "Cache miss for: " + uri);
76+
LOGGER.log(Level.FINE, "Cache miss for: " + uri);
7677
byte[] fileBytes = provider.fetch(uri);
7778

7879
if (fileBytes != null) {
@@ -86,7 +87,7 @@ public byte[] getOrFetch(String uri, FileProvider provider) throws IOException {
8687
private void addToCacheUnsafe(String uri, byte[] data) {
8788
// Guard mot oversized entries som kan blockera eviction
8889
if (data.length > MAX_CACHE_BYTES) {
89-
LOGGER.log(Level.WARNING, "⚠️ Skipping cache for oversized file: " + uri +
90+
LOGGER.log(Level.WARNING, "Skipping cache for oversized file: " + uri +
9091
" (" + (data.length / 1024 / 1024) + "MB > " +
9192
(MAX_CACHE_BYTES / 1024 / 1024) + "MB)");
9293
return;
@@ -99,7 +100,7 @@ private void addToCacheUnsafe(String uri, byte[] data) {
99100

100101
// Om cache fortfarande är full efter eviction, hoppa över
101102
if (shouldEvict(data)) {
102-
LOGGER.log(Level.WARNING, "⚠️ Cache full, skipping: " + uri);
103+
LOGGER.log(Level.WARNING, " Cache full, skipping: " + uri);
103104
return;
104105
}
105106

@@ -137,7 +138,7 @@ private void evictLeastRecentlyUsedUnsafe() {
137138
CacheEntry removed = cache.remove(keyToRemove);
138139
if (removed != null) {
139140
currentBytes.addAndGet(-removed.data.length);
140-
LOGGER.log(Level.FINE, " Evicted from cache: " + keyToRemove +
141+
LOGGER.log(Level.FINE, " Evicted from cache: " + keyToRemove +
141142
" (accesses: " + removed.accessCount.get() + ")");
142143
}
143144
}
@@ -153,19 +154,79 @@ public void clearCache() {
153154
currentBytes.set(0);
154155
}
155156
}
156-
157+
/**
158+
* Cache statistics record.
159+
*/
157160
@Override
158-
public CacheStats getStats() {
159-
long totalAccesses = cache.values().stream()
160-
.mapToLong(e -> e.accessCount.get())
161-
.sum();
162-
163-
return new CacheStats(
164-
cache.size(),
165-
currentBytes.get(),
166-
MAX_CACHE_ENTRIES,
167-
MAX_CACHE_BYTES,
168-
totalAccesses
161+
public FileCache.CacheStats getStats() {
162+
return null;
163+
}
164+
165+
166+
class CacheStats {
167+
public final int entries;
168+
public final long bytes;
169+
public final int maxEntries;
170+
public final long maxBytes;
171+
public final long totalAccesses;
172+
173+
public CacheStats(int entries, long bytes, int maxEntries, long maxBytes, long totalAccesses) {
174+
this.entries = entries;
175+
this.bytes = bytes;
176+
this.maxEntries = maxEntries;
177+
this.maxBytes = maxBytes;
178+
this.totalAccesses = totalAccesses;
179+
}
180+
181+
@Override
182+
public String toString() {
183+
String bytesFormatted = formatBytes(bytes);
184+
String maxBytesFormatted = formatBytes(maxBytes);
185+
186+
return String.format(
187+
"CacheStats{entries=%d/%d, bytes=%s/%s, utilization=%.1f%%, accesses=%d}",
188+
entries, maxEntries, bytesFormatted, maxBytesFormatted,
189+
(double) bytes / maxBytes * 100, totalAccesses
190+
);
191+
}
192+
193+
private static String formatBytes(long bytes) {
194+
if (bytes <= 0) return "0 B";
195+
final String[] units = new String[]{"B", "KB", "MB", "GB"};
196+
int digitGroups = (int) (Math.log10(bytes) / Math.log10(1024));
197+
return String.format("%.1f %s", bytes / Math.pow(1024, digitGroups), units[digitGroups]);
198+
}
199+
}
200+
201+
/**
202+
* Sanitizes URI by removing query strings, fragments, null bytes, and leading slashes.
203+
* Also performs URL-decoding to normalize percent-encoded sequences.
204+
*/
205+
private String sanitizeUri(String uri) {
206+
if (uri == null || uri.isEmpty()) {
207+
return "index.html";
208+
}
209+
210+
// Ta bort query string och fragment
211+
int queryIndex = uri.indexOf('?');
212+
int fragmentIndex = uri.indexOf('#');
213+
int endIndex = Math.min(
214+
queryIndex > 0 ? queryIndex : uri.length(),
215+
fragmentIndex > 0 ? fragmentIndex : uri.length()
169216
);
217+
218+
uri = uri.substring(0, endIndex)
219+
.replace("\0", "")
220+
.replaceAll("^/+", ""); // Bort med leading slashes
221+
222+
// URL-decode för att normalisera percent-encoded sequences (t.ex. %2e%2e -> ..)
223+
try {
224+
uri = URLDecoder.decode(uri, StandardCharsets.UTF_8);
225+
} catch (IllegalArgumentException e) {
226+
LOGGER.log(Level.WARNING, "Ogiltig URL-kodning i URI: " + uri);
227+
// Returna som den är om avkodning misslyckas; isPathTraversal kommer hantera det
228+
}
229+
230+
return uri.isEmpty() ? "index.html" : uri;
170231
}
171232
}

src/main/java/org/example/FileCache.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,21 @@ public CacheStats(int entries, long bytes, int maxEntries, long maxBytes, long t
5656

5757
@Override
5858
public String toString() {
59+
String bytesFormatted = formatBytes(bytes);
60+
String maxBytesFormatted = formatBytes(maxBytes);
61+
5962
return String.format(
60-
"CacheStats{entries=%d/%d, bytes=%d/%d, utilization=%.1f%%, accesses=%d}",
61-
entries, maxEntries, bytes, maxBytes,
63+
"CacheStats{entries=%d/%d, bytes=%s/%s, utilization=%.1f%%, accesses=%d}",
64+
entries, maxEntries, bytesFormatted, maxBytesFormatted,
6265
(double) bytes / maxBytes * 100, totalAccesses
6366
);
6467
}
68+
69+
private static String formatBytes(long bytes) {
70+
if (bytes <= 0) return "0 B";
71+
final String[] units = new String[]{"B", "KB", "MB", "GB"};
72+
int digitGroups = (int) (Math.log10(bytes) / Math.log10(1024));
73+
return String.format("%.1f %s", bytes / Math.pow(1024, digitGroups), units[digitGroups]);
74+
}
6575
}
6676
}

src/main/java/org/example/StaticFileHandler.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ private String generateCacheKey(String sanitizedUri) {
7474
* Also performs URL-decoding to normalize percent-encoded sequences.
7575
*/
7676
private String sanitizeUri(String uri) {
77-
// Entydlig: ta bort query string och fragment
77+
if (uri == null || uri.isEmpty()) {
78+
return "index.html";
79+
}
80+
81+
// Ta bort query string och fragment
7882
int queryIndex = uri.indexOf('?');
7983
int fragmentIndex = uri.indexOf('#');
8084
int endIndex = Math.min(
@@ -86,18 +90,17 @@ private String sanitizeUri(String uri) {
8690
.replace("\0", "")
8791
.replaceAll("^/+", ""); // Bort med leading slashes
8892

89-
// URL-decode to normalize percent-encoded sequences (e.g., %2e%2e -> ..)
93+
// URL-decode för att normalisera percent-encoded sequences (t.ex. %2e%2e -> ..)
9094
try {
9195
uri = URLDecoder.decode(uri, StandardCharsets.UTF_8);
9296
} catch (IllegalArgumentException e) {
93-
LOGGER.log(Level.WARNING, "Invalid URL encoding in URI: " + uri);
94-
// Return as-is if decoding fails; isPathTraversal will handle it
97+
LOGGER.log(Level.WARNING, "Ogiltig URL-kodning i URI: " + uri);
98+
// Returna som den är om avkodning misslyckas; isPathTraversal kommer hantera det
9599
}
96100

97-
return uri;
101+
return uri.isEmpty() ? "index.html" : uri;
98102
}
99103

100-
/**
101104
/**
102105
* Kontrollerar om den begärda sökvägen försöker traversera utanför webroten.
103106
* Använder sökvägsnormalisering efter avkodning för att fånga traversalförsök.

src/test/java/org/example/StaticFileHandlerTest.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,30 @@ void testConcurrent_MultipleReads() throws InterruptedException, IOException {
8585
Files.writeString(tempDir.resolve("shared.html"), "Data");
8686
StaticFileHandler handler = new StaticFileHandler(tempDir.toString());
8787

88-
// Förvärmning
88+
// Förvärmning - ladda filen i cache
8989
handler.sendGetRequest(new ByteArrayOutputStream(), "shared.html");
9090

91-
// Act - 10 trådar läser samma fil 50 gånger varje
91+
// Act - 10 trådar läser samma fil 50 gånger varje = 500 totala läsningar
9292
Thread[] threads = new Thread[10];
93-
final AssertionError[] assertionErrors = new AssertionError[1];
93+
final Exception[] threadError = new Exception[1];
9494

9595
for (int i = 0; i < 10; i++) {
9696
threads[i] = new Thread(() -> {
9797
try {
9898
for (int j = 0; j < 50; j++) {
9999
ByteArrayOutputStream out = new ByteArrayOutputStream();
100100
handler.sendGetRequest(out, "shared.html");
101-
assertThat(out.toString()).contains("HTTP/1.1 200");
101+
String response = out.toString();
102+
103+
// Validera att svaret är korrekt
104+
if (!response.contains("HTTP/1.1 200") || !response.contains("Data")) {
105+
throw new AssertionError("Oväntad response: " + response.substring(0, Math.min(100, response.length())));
106+
}
102107
}
103-
} catch (IOException e) {
104-
throw new RuntimeException(e);
105-
} catch (AssertionError e) {
106-
// Capture assertion errors from child thread
108+
} catch (Exception e) {
107109
synchronized (threads) {
108-
assertionErrors[0] = e;
110+
threadError[0] = e;
109111
}
110-
throw e;
111112
}
112113
});
113114
threads[i].start();
@@ -118,13 +119,15 @@ void testConcurrent_MultipleReads() throws InterruptedException, IOException {
118119
t.join();
119120
}
120121

121-
// Assert - Check if any child thread had assertion failures
122-
if (assertionErrors[0] != null) {
123-
throw assertionErrors[0];
122+
// Assert - Kontrollera om någon tråd hade fel
123+
if (threadError[0] != null) {
124+
throw new AssertionError("Tråd-fel: " + threadError[0].getMessage(), threadError[0]);
124125
}
125126

126-
// Assert - Cache ska bara ha EN entry
127-
assertThat(StaticFileHandler.getCacheStats().entries).isEqualTo(1);
127+
// Assert - Cache ska bara ha EN entry för shared.html
128+
FileCache.CacheStats stats = StaticFileHandler.getCacheStats();
129+
assertThat(stats.entries).isEqualTo(1);
130+
assertThat(stats.totalAccesses).isGreaterThanOrEqualTo(500);
128131
}
129132

130133
@Test

0 commit comments

Comments
 (0)