Skip to content

Commit 6e08477

Browse files
committed
Remove URL open events from Extension components
1 parent a1c8d24 commit 6e08477

4 files changed

Lines changed: 158 additions & 1 deletion

File tree

Plan/common/src/main/java/com/djrapitops/plan/extension/implementation/providers/gathering/DataValueGatherer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.djrapitops.plan.storage.database.Database;
4848
import com.djrapitops.plan.utilities.logging.ErrorContext;
4949
import com.djrapitops.plan.utilities.logging.ErrorLogger;
50+
import org.apache.commons.lang3.Strings;
5051

5152
import java.util.HashSet;
5253
import java.util.Optional;
@@ -459,6 +460,9 @@ private String getComponentAsJson(Component component) {
459460
if (json.length() > ComponentDataValue.MAX_LENGTH) {
460461
json = "{\"text\":\"<Component too long>\"}";
461462
}
463+
if (Strings.CI.containsAny(json, "javascript", "clickEvent", "hoverEvent", "open_url", "copy_to_clipboard", "\"action\"", "&#", "\0", "\\")) {
464+
json = "{\"text\":\"<Component contained disallowed words or characters>\"}";
465+
}
462466
return json;
463467
}
464468

Plan/common/src/main/java/com/djrapitops/plan/storage/database/Patches.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ public static Patch[] getAll(PluginLogger logger, PlanConfig config) {
8989
new CookieTableIpAddressPatch(),
9090
new TPSTableMSPTPatch(),
9191
new AllowlistIncorrectUniqueConstraintPatch(),
92-
new TPSTableIdPatch()
92+
new TPSTableIdPatch(),
93+
new DeleteUrlOpenEventsFromExtensionComponentsPatch()
9394
};
9495
}
9596
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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.storage.database.transactions.patches;
18+
19+
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionPlayerValueTable;
20+
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable;
21+
22+
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
23+
24+
/**
25+
* @author AuroraLS3
26+
*/
27+
public class DeleteUrlOpenEventsFromExtensionComponentsPatch extends Patch {
28+
29+
String[] invalidStrings = new String[]{
30+
"javascript", "clickEvent", "hoverEvent", "open_url", "copy_to_clipboard", "\"action\"", "&#", "\\"
31+
};
32+
33+
@Override
34+
public boolean hasBeenApplied() {
35+
for (String invalidString : invalidStrings) {
36+
String playerSql = SELECT + ExtensionPlayerValueTable.COMPONENT_VALUE +
37+
FROM + ExtensionPlayerValueTable.TABLE_NAME +
38+
WHERE + ExtensionPlayerValueTable.COMPONENT_VALUE + " LIKE '%" + invalidString + "%'";
39+
if (query(db -> db.queryOptional(playerSql, row -> row.getString(1)))
40+
.isPresent()) {
41+
return false;
42+
}
43+
String serverSql = SELECT + ExtensionServerValueTable.COMPONENT_VALUE +
44+
FROM + ExtensionServerValueTable.TABLE_NAME +
45+
WHERE + ExtensionServerValueTable.COMPONENT_VALUE + " LIKE '%" + invalidString + "%'";
46+
if (query(db -> db.queryOptional(serverSql, row -> row.getString(1)))
47+
.isPresent()) {
48+
return false;
49+
}
50+
String nullCharSql = SELECT + ExtensionPlayerValueTable.COMPONENT_VALUE +
51+
FROM + ExtensionPlayerValueTable.TABLE_NAME +
52+
WHERE + "INSTR(" + ExtensionPlayerValueTable.COMPONENT_VALUE + ", CHAR(0))";
53+
if (query(db -> db.queryOptional(nullCharSql, row -> row.getString(1)))
54+
.isPresent()) {
55+
return false;
56+
}
57+
String nullCharServerSql = SELECT + ExtensionServerValueTable.COMPONENT_VALUE +
58+
FROM + ExtensionPlayerValueTable.TABLE_NAME +
59+
WHERE + "INSTR(" + ExtensionServerValueTable.COMPONENT_VALUE + ", CHAR(0))";
60+
if (query(db -> db.queryOptional(nullCharServerSql, row -> row.getString(1)))
61+
.isPresent()) {
62+
return false;
63+
}
64+
}
65+
66+
return true;
67+
}
68+
69+
@Override
70+
protected void applyPatch() {
71+
for (String invalidString : invalidStrings) {
72+
execute("DELETE FROM " + ExtensionPlayerValueTable.TABLE_NAME +
73+
WHERE + ExtensionPlayerValueTable.COMPONENT_VALUE + " LIKE '%" + invalidString + "%'" +
74+
OR + "INSTR(" + ExtensionPlayerValueTable.COMPONENT_VALUE + ", CHAR(0))");
75+
execute("DELETE FROM " + ExtensionServerValueTable.TABLE_NAME +
76+
WHERE + ExtensionServerValueTable.COMPONENT_VALUE + " LIKE '%" + invalidString + "%'" +
77+
OR + "INSTR(" + ExtensionServerValueTable.COMPONENT_VALUE + ", CHAR(0))");
78+
}
79+
}
80+
}

Plan/common/src/test/java/com/djrapitops/plan/storage/database/queries/ExtensionsDatabaseTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.djrapitops.plan.extension.annotation.*;
2626
import com.djrapitops.plan.extension.builder.ExtensionDataBuilder;
2727
import com.djrapitops.plan.extension.icon.Color;
28+
import com.djrapitops.plan.extension.icon.Family;
2829
import com.djrapitops.plan.extension.icon.Icon;
2930
import com.djrapitops.plan.extension.implementation.results.*;
3031
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionPlayerDataQuery;
@@ -73,6 +74,7 @@ default void unregisterExtensions() {
7374
extensionService.unregister(new ConditionalExtension());
7475
extensionService.unregister(new TableExtension());
7576
extensionService.unregister(new ThrowingExtension());
77+
extensionService.unregister(new ClickEventTestExtension());
7678
}
7779

7880
@Test
@@ -432,6 +434,43 @@ default void extensionExceptionsAreCaught() {
432434
assertEquals(5, TestErrorLogger.getCaught().size(), () -> "Not all exceptions got logged, logged exceptions: " + TestErrorLogger.getCaught().toString());
433435
}
434436

437+
@Test
438+
default void clickEventsNotAllowed() {
439+
ExtensionSvc extensionService = extensionService();
440+
extensionService.register(new ClickEventTestExtension());
441+
442+
extensionService.updateServerValues(CallEvents.MANUAL);
443+
444+
String expected = "{\"text\":\"<Component contained disallowed words or characters>\"}";
445+
String result = db().query(new ExtensionServerDataQuery(serverUUID()))
446+
.get(0)
447+
.getTabs()
448+
.get(0)
449+
.getComponent("clickEventComponent")
450+
.orElseThrow(AssertionError::new)
451+
.getFormattedValue();
452+
assertEquals(expected, result);
453+
}
454+
455+
@Test
456+
default void clickEventsNotAllowedForPlayer() {
457+
ExtensionSvc extensionService = extensionService();
458+
extensionService.register(new ClickEventTestExtension());
459+
460+
extensionService.updatePlayerValues(TestConstants.PLAYER_ONE_UUID, TestConstants.PLAYER_ONE_NAME, CallEvents.MANUAL);
461+
462+
String expected = "{\"text\":\"<Component contained disallowed words or characters>\"}";
463+
String result = db().query(new ExtensionPlayerDataQuery(TestConstants.PLAYER_ONE_UUID))
464+
.get(serverUUID())
465+
.get(0)
466+
.getTabs()
467+
.get(0)
468+
.getComponent("clickEventPlayerComponent")
469+
.orElseThrow(AssertionError::new)
470+
.getFormattedValue();
471+
assertEquals(expected, result);
472+
}
473+
435474

436475
@PluginInfo(name = "ConditionalExtension")
437476
class ConditionalExtension implements DataExtension {
@@ -654,4 +693,37 @@ public ExtensionDataBuilder builder3() {
654693
throw new NoSuchMethodError();
655694
}
656695
}
696+
697+
@PluginInfo(
698+
name = "Component with click event",
699+
iconName = "bug",
700+
iconFamily = Family.SOLID,
701+
color = Color.RED
702+
)
703+
public class ClickEventTestExtension implements DataExtension {
704+
@ComponentProvider(
705+
text = "Component with click event",
706+
description = "",
707+
iconName = "bug",
708+
iconFamily = Family.SOLID,
709+
iconColor = Color.RED
710+
)
711+
public Component clickEventComponent() {
712+
String json = "{\"text\":\"Click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"javascript:alert(document.cookie)\"}}";
713+
return ComponentService.getInstance().fromJson(json);
714+
}
715+
716+
717+
@ComponentProvider(
718+
text = "Component with click event",
719+
description = "",
720+
iconName = "bug",
721+
iconFamily = Family.SOLID,
722+
iconColor = Color.RED
723+
)
724+
public Component clickEventPlayerComponent(UUID playerUUID) {
725+
String json = "{\"text\":\"Click here\",\"color\":\"red\",\"underlined\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"javascript:alert(document.cookie)\"}}";
726+
return ComponentService.getInstance().fromJson(json);
727+
}
728+
}
657729
}

0 commit comments

Comments
 (0)