Skip to content

Commit 5681b3f

Browse files
committed
Fix support for non-number ETag
1 parent f25f013 commit 5681b3f

6 files changed

Lines changed: 106 additions & 55 deletions

File tree

Plan/common/src/main/java/com/djrapitops/plan/PlanSystem.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import javax.inject.Inject;
4242
import javax.inject.Singleton;
4343
import java.io.IOException;
44+
import java.util.concurrent.atomic.AtomicLong;
4445

4546
/**
4647
* PlanSystem contains everything Plan needs to run.
@@ -52,10 +53,8 @@
5253
@Singleton
5354
public class PlanSystem implements SubSystem {
5455

56+
public static final AtomicLong LAST_RELOAD = new AtomicLong(0L);
5557
private static final long SERVER_ENABLE_TIME = System.currentTimeMillis();
56-
57-
private boolean enabled = false;
58-
5958
private final PlanFiles files;
6059
private final ConfigSystem configSystem;
6160
private final VersionChecker versionChecker;
@@ -66,9 +65,7 @@ public class PlanSystem implements SubSystem {
6665
private final TaskSystem taskSystem;
6766
private final ServerInfo serverInfo;
6867
private final WebServerSystem webServerSystem;
69-
7068
private final Processing processing;
71-
7269
private final ImportSystem importSystem;
7370
private final ExportSystem exportSystem;
7471
private final DeliveryUtilities deliveryUtilities;
@@ -77,6 +74,7 @@ public class PlanSystem implements SubSystem {
7774
private final PluginLogger logger;
7875
private final ErrorLogger errorLogger;
7976
private final ApplicationDependencyManager applicationDependencyManager;
77+
private boolean enabled = false;
8078

8179
@Inject
8280
public PlanSystem(
@@ -128,12 +126,17 @@ public PlanSystem(
128126
logger.info("§2");
129127
}
130128

129+
public static long getServerEnableTime() {
130+
return SERVER_ENABLE_TIME;
131+
}
132+
131133
/**
132134
* Enables only the systems that are required for {@link com.djrapitops.plan.commands.PlanCommand}.
133135
*
134136
* @see #enableOtherThanCommands()
135137
*/
136138
public void enableForCommands() {
139+
LAST_RELOAD.set(System.currentTimeMillis());
137140
enableSystems(configSystem);
138141
}
139142

@@ -220,6 +223,8 @@ public void disable() {
220223
);
221224
}
222225

226+
// Accessor methods.
227+
223228
private void disableSystems(SubSystem... systems) {
224229
for (SubSystem system : systems) {
225230
try {
@@ -232,8 +237,6 @@ private void disableSystems(SubSystem... systems) {
232237
}
233238
}
234239

235-
// Accessor methods.
236-
237240
public VersionChecker getVersionChecker() {
238241
return versionChecker;
239242
}
@@ -301,8 +304,4 @@ public boolean isEnabled() {
301304
public ApiServices getApiServices() {
302305
return apiServices;
303306
}
304-
305-
public static long getServerEnableTime() {
306-
return SERVER_ENABLE_TIME;
307-
}
308307
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.djrapitops.plan.delivery.webserver;
1818

19+
import com.djrapitops.plan.PlanSystem;
1920
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
2021
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
2122
import com.djrapitops.plan.delivery.formatting.Formatter;
@@ -30,6 +31,7 @@
3031
import com.djrapitops.plan.delivery.web.resolver.ResponseBuilder;
3132
import com.djrapitops.plan.delivery.web.resolver.request.Request;
3233
import com.djrapitops.plan.delivery.web.resource.WebResource;
34+
import com.djrapitops.plan.delivery.webserver.resolver.ETag;
3335
import com.djrapitops.plan.identification.Identifiers;
3436
import com.djrapitops.plan.identification.ServerUUID;
3537
import com.djrapitops.plan.settings.config.PlanConfig;
@@ -47,7 +49,6 @@
4749
import com.djrapitops.plan.utilities.java.Maps;
4850
import com.djrapitops.plan.utilities.java.UnaryChain;
4951
import dagger.Lazy;
50-
import org.apache.commons.codec.digest.DigestUtils;
5152
import org.apache.commons.lang3.Strings;
5253
import org.apache.commons.text.StringEscapeUtils;
5354
import org.eclipse.jetty.http.HttpHeader;
@@ -124,9 +125,9 @@ private Response forPage(@Untrusted Request request, Page page) {
124125

125126
private Response forPage(@Untrusted Request request, Page page, int responseCode) {
126127
long modified = page.lastModified();
127-
Optional<Long> etag = Identifiers.getEtag(request);
128+
Optional<ETag> etag = Identifiers.getEtag(request);
128129

129-
if (etag.isPresent() && modified == etag.get()) {
130+
if (etag.isPresent() && etag.get().isOutdated(modified)) {
130131
return browserCachedNotChangedResponse();
131132
}
132133

@@ -171,10 +172,10 @@ private Response buildDBNotOpenResponse(Database.State dbState) throws IOExcepti
171172
.build();
172173
}
173174

174-
private Response getCachedOrNew(long modified, String fileName, Function<String, Response> newResponseFunction) {
175+
private Response getCachedOrNew(ETag etag, String fileName, Function<String, Response> newResponseFunction) {
175176
WebResource resource = getPublicOrJarResource(fileName);
176177
Optional<Long> lastModified = resource.getLastModified();
177-
if (lastModified.isPresent() && modified == lastModified.get()) {
178+
if (lastModified.isPresent() && etag.isOutdated(lastModified.get(), () -> "config-" + PlanSystem.LAST_RELOAD.get())) {
178179
return browserCachedNotChangedResponse();
179180
} else {
180181
return newResponseFunction.apply(fileName);
@@ -206,8 +207,8 @@ public Response rawPlayerPageResponse(UUID playerUUID) {
206207
.build();
207208
}
208209

209-
public Response javaScriptResponse(long modified, @Untrusted String fileName) {
210-
return getCachedOrNew(modified, fileName, this::javaScriptResponse);
210+
public Response javaScriptResponse(ETag etag, @Untrusted String fileName) {
211+
return getCachedOrNew(etag, fileName, this::javaScriptResponse);
211212
}
212213

213214
public Response javaScriptResponse(@Untrusted String fileName) {
@@ -235,7 +236,7 @@ public Response javaScriptResponse(@Untrusted String fileName) {
235236
resource.getLastModified().ifPresent(lastModified -> responseBuilder
236237
.setHeader(HttpHeader.CACHE_CONTROL.asString(), alwaysCheckRefetch ? CacheStrategy.CHECK_ETAG : CacheStrategy.CACHE_IN_BROWSER)
237238
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
238-
.setHeader(HttpHeader.ETAG.asString(), alwaysCheckRefetch ? DigestUtils.sha256Hex(content) : lastModified));
239+
.setHeader(HttpHeader.ETAG.asString(), alwaysCheckRefetch ? "config-" + PlanSystem.LAST_RELOAD.get() : lastModified));
239240
}
240241
return responseBuilder.build();
241242
} catch (UncheckedIOException e) {
@@ -249,8 +250,8 @@ private String replaceMainAddressPlaceholder(String resource) {
249250
return Strings.CS.replace(resource, "PLAN_BASE_ADDRESS", address);
250251
}
251252

252-
public Response cssResponse(long modified, @Untrusted String fileName) {
253-
return getCachedOrNew(modified, fileName, this::cssResponse);
253+
public Response cssResponse(ETag etag, @Untrusted String fileName) {
254+
return getCachedOrNew(etag, fileName, this::cssResponse);
254255
}
255256

256257
public Response cssResponse(@Untrusted String fileName) {
@@ -278,8 +279,8 @@ public Response cssResponse(@Untrusted String fileName) {
278279
}
279280
}
280281

281-
public Response imageResponse(long modified, @Untrusted String fileName) {
282-
return getCachedOrNew(modified, fileName, this::imageResponse);
282+
public Response imageResponse(ETag eTag, @Untrusted String fileName) {
283+
return getCachedOrNew(eTag, fileName, this::imageResponse);
283284
}
284285

285286
public Response imageResponse(@Untrusted String fileName) {
@@ -302,7 +303,7 @@ public Response imageResponse(@Untrusted String fileName) {
302303
}
303304
}
304305

305-
public Response fontResponse(long modified, @Untrusted String fileName) {
306+
public Response fontResponse(ETag modified, @Untrusted String fileName) {
306307
return getCachedOrNew(modified, fileName, this::fontResponse);
307308
}
308309

@@ -337,15 +338,15 @@ public Response fontResponse(@Untrusted String fileName) {
337338
}
338339
}
339340

340-
public Response publicHtmlResourceResponse(long modified, @Untrusted String fileName, String mimeType) {
341+
public Response publicHtmlResourceResponse(ETag etag, @Untrusted String fileName, String mimeType) {
341342
// Slightly different from getCachedOrNew
342343
WebResource resource = publicHtmlFiles.findPublicHtmlResource(fileName)
343344
.map(Resource::asWebResource)
344345
.orElse(null);
345346
if (resource == null) return null;
346347

347348
Optional<Long> lastModified = resource.getLastModified();
348-
if (lastModified.isPresent() && modified == lastModified.get()) {
349+
if (lastModified.isPresent() && etag.isOutdated(lastModified.get())) {
349350
return browserCachedNotChangedResponse();
350351
} else {
351352
return publicHtmlResourceResponse(fileName, mimeType);
@@ -573,8 +574,8 @@ public Response themeResponse(@Untrusted String themeName, Request request) {
573574
.asWebResource();
574575

575576
Optional<Long> lastModified = foundTheme.getLastModified();
576-
@Untrusted Optional<Long> tag = Identifiers.getEtag(request);
577-
if (tag.isPresent() && lastModified.isPresent() && tag.get() >= lastModified.get()) {
577+
@Untrusted Optional<ETag> tag = Identifiers.getEtag(request);
578+
if (tag.isPresent() && lastModified.isPresent() && tag.get().isOutdated(lastModified.get())) {
578579
return browserCachedNotChangedResponse();
579580
} else {
580581
long date = lastModified.orElseGet(System::currentTimeMillis);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* This file is part of Player Analytics (Plan).
3+
*
4+
* Plan is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License v3 as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Plan is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public License
15+
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
package com.djrapitops.plan.delivery.webserver.resolver;
18+
19+
import java.util.Optional;
20+
import java.util.function.Supplier;
21+
22+
/**
23+
* @author AuroraLS3
24+
*/
25+
public class ETag {
26+
27+
private final String etag;
28+
29+
public ETag(String etag) {
30+
this.etag = etag;
31+
}
32+
33+
public String getEtag() {
34+
return etag;
35+
}
36+
37+
public Optional<Long> parseAsLong() {
38+
try {
39+
return Optional.of(Long.parseLong(etag));
40+
} catch (NumberFormatException e) {
41+
return Optional.empty();
42+
}
43+
}
44+
45+
public boolean isOutdated(long timestamp) {
46+
return isOutdated(timestamp, () -> "");
47+
}
48+
49+
public boolean isOutdated(long timestamp, Supplier<String> hashSupplier) {
50+
Optional<Long> asLong = parseAsLong();
51+
if (asLong.isEmpty()) {
52+
return !etag.equals(hashSupplier.get());
53+
}
54+
return asLong.map(t -> t != timestamp).orElse(true);
55+
}
56+
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/PublicHtmlResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public Optional<Response> resolve(Request request) {
5151
@SuppressWarnings("OptionalIsPresent") // More readable
5252
private Response getResponse(Request request) {
5353
@Untrusted String resource = request.getPath().asString().substring(1);
54-
@Untrusted Optional<Long> etag = Identifiers.getEtag(request);
54+
@Untrusted Optional<ETag> etag = Identifiers.getEtag(request);
5555

5656
Optional<String> mimeType = getMimeType(resource);
5757
if (mimeType.isEmpty()) return null;

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/StaticResourceResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public Optional<Response> resolve(Request request) {
5454

5555
private Response getResponse(Request request) {
5656
@Untrusted String resource = getPath(request).asString().substring(1);
57-
@Untrusted Optional<Long> etag = Identifiers.getEtag(request);
57+
@Untrusted Optional<ETag> etag = Identifiers.getEtag(request);
5858
if (resource.endsWith(".css")) {
5959
return etag.map(tag -> responseFactory.cssResponse(tag, resource))
6060
.orElseGet(() -> responseFactory.cssResponse(resource));

Plan/common/src/main/java/com/djrapitops/plan/identification/Identifiers.java

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
2020
import com.djrapitops.plan.delivery.web.resolver.request.Request;
21+
import com.djrapitops.plan.delivery.webserver.resolver.ETag;
2122
import com.djrapitops.plan.storage.database.DBSystem;
2223
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
2324
import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries;
@@ -48,23 +49,6 @@ public Identifiers(DBSystem dbSystem, UUIDUtility uuidUtility) {
4849
this.uuidUtility = uuidUtility;
4950
}
5051

51-
/**
52-
* Obtain UUID of the server.
53-
*
54-
* @param request for Request, URIQuery needs a 'server' parameter.
55-
* @return UUID of the server.
56-
* @throws BadRequestException If server parameter is not defined or the server is not in the database.
57-
*/
58-
public ServerUUID getServerUUID(Request request) {
59-
String identifier = request.getQuery().get("server")
60-
.orElseThrow(() -> new BadRequestException("'server' parameter was not defined."));
61-
62-
Optional<ServerUUID> parsed = UUIDUtility.parseFromString(identifier).map(ServerUUID::from);
63-
return parsed.orElseGet(() -> getServerUUIDFromName(identifier).orElseThrow(
64-
() -> new BadRequestException("Given 'server' was not found in the database.")
65-
));
66-
}
67-
6852
public static Optional<Long> getTimestamp(@Untrusted Request request) {
6953
try {
7054
long currentTime = System.currentTimeMillis();
@@ -82,21 +66,32 @@ public static Optional<Long> getTimestamp(@Untrusted Request request) {
8266
}
8367
}
8468

85-
public static Optional<Long> getEtag(Request request) {
69+
public static Optional<ETag> getEtag(Request request) {
8670
return request.getHeader(HttpHeader.IF_NONE_MATCH.asString())
87-
.map(tag -> {
88-
try {
89-
return Long.parseLong(tag);
90-
} catch (NumberFormatException notANumber) {
91-
throw new BadRequestException("'" + HttpHeader.IF_NONE_MATCH.asString() + "'-header was not a number. Clear browser cache.");
92-
}
93-
});
71+
.map(ETag::new);
9472
}
9573

9674
public static Optional<String> getStringEtag(Request request) {
9775
return request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
9876
}
9977

78+
/**
79+
* Obtain UUID of the server.
80+
*
81+
* @param request for Request, URIQuery needs a 'server' parameter.
82+
* @return UUID of the server.
83+
* @throws BadRequestException If server parameter is not defined or the server is not in the database.
84+
*/
85+
public ServerUUID getServerUUID(Request request) {
86+
String identifier = request.getQuery().get("server")
87+
.orElseThrow(() -> new BadRequestException("'server' parameter was not defined."));
88+
89+
Optional<ServerUUID> parsed = UUIDUtility.parseFromString(identifier).map(ServerUUID::from);
90+
return parsed.orElseGet(() -> getServerUUIDFromName(identifier).orElseThrow(
91+
() -> new BadRequestException("Given 'server' was not found in the database.")
92+
));
93+
}
94+
10095
/**
10196
* Obtain UUID of the server.
10297
*

0 commit comments

Comments
 (0)