diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java index e1595b52..7819d74a 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java @@ -121,11 +121,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV { + Request original = chain.request(); + return chain.proceed( + original.newBuilder() + .removeHeader("User-Agent") + .addHeader("User-Agent", "DiscordSRV/" + version()) + .build() + ); + }) + .callTimeout(1, TimeUnit.MINUTES) + .build(); + private final ObjectMapper objectMapper = new ObjectMapper(); + // Internal private final ReentrantLock lifecycleLock = new ReentrantLock(); @@ -129,6 +157,38 @@ public abstract class AbstractDiscordSRV invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) { diff --git a/common/src/main/java/com/discordsrv/common/DiscordSRV.java b/common/src/main/java/com/discordsrv/common/DiscordSRV.java index bca84add..2da50afe 100644 --- a/common/src/main/java/com/discordsrv/common/DiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/DiscordSRV.java @@ -41,8 +41,10 @@ import com.discordsrv.common.plugin.PluginManager; import com.discordsrv.common.profile.ProfileManager; import com.discordsrv.common.scheduler.Scheduler; import com.discordsrv.common.storage.Storage; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.benmanes.caffeine.cache.Caffeine; import dev.vankka.dependencydownload.classpath.ClasspathAppender; +import okhttp3.OkHttpClient; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -59,7 +61,6 @@ public interface DiscordSRV extends DiscordSRVApi { Path dataDirectory(); Scheduler scheduler(); Console console(); - String version(); PluginManager pluginManager(); OnlineMode onlineMode(); ClasspathAppender classpathAppender(); @@ -92,6 +93,14 @@ public interface DiscordSRV extends DiscordSRVApi { // Link Provider LinkProvider linkProvider(); + // Version + @NotNull + String version(); + @Nullable + String gitRevision(); + @Nullable + String gitBranch(); + // Config ConnectionConfigManager connectionConfigManager(); ConnectionConfig connectionConfig(); @@ -124,6 +133,8 @@ public interface DiscordSRV extends DiscordSRVApi { return (Caffeine) Caffeine.newBuilder() .executor(scheduler().forkJoinPool()); } + OkHttpClient httpClient(); + ObjectMapper json(); // Lifecycle CompletableFuture invokeEnable(); diff --git a/common/src/main/java/com/discordsrv/common/paste/Paste.java b/common/src/main/java/com/discordsrv/common/paste/Paste.java new file mode 100644 index 00000000..2f011711 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/paste/Paste.java @@ -0,0 +1,48 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.paste; + +public class Paste { + + private final String id; + private final String url; + private final byte[] decryptionKey; + + public Paste(String id, String url, byte[] decryptionKey) { + this.id = id; + this.url = url; + this.decryptionKey = decryptionKey; + } + + public String id() { + return id; + } + + public String url() { + return url; + } + + public byte[] decryptionKey() { + return decryptionKey; + } + + public Paste withDecryptionKey(byte[] decryptionKey) { + return new Paste(id, url, decryptionKey); + } +} diff --git a/common/src/main/java/com/discordsrv/common/paste/PasteService.java b/common/src/main/java/com/discordsrv/common/paste/PasteService.java new file mode 100644 index 00000000..668c9a6b --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/paste/PasteService.java @@ -0,0 +1,25 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.paste; + +public interface PasteService { + + Paste uploadFile(byte[] fileContent) throws Throwable; + +} diff --git a/common/src/main/java/com/discordsrv/common/paste/service/AESEncryptedPasteService.java b/common/src/main/java/com/discordsrv/common/paste/service/AESEncryptedPasteService.java new file mode 100644 index 00000000..d516f788 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/paste/service/AESEncryptedPasteService.java @@ -0,0 +1,69 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.paste.service; + +import com.discordsrv.common.paste.Paste; +import com.discordsrv.common.paste.PasteService; +import org.apache.commons.lang3.ArrayUtils; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public class AESEncryptedPasteService implements PasteService { + + private final PasteService service; + private final int keySize; + private final SecureRandom RANDOM = new SecureRandom(); + + public AESEncryptedPasteService(PasteService service, int keySize) { + this.service = service; + this.keySize = keySize; + } + + @Override + public Paste uploadFile(byte[] fileContent) throws Throwable { + byte[] iv = new byte[16]; + RANDOM.nextBytes(iv); + + SecretKey secretKey = generateKey(); + byte[] encrypted = encrypt(secretKey, fileContent, iv); + + Paste paste = service.uploadFile(Base64.getEncoder().encode(ArrayUtils.addAll(iv, encrypted))); + return paste.withDecryptionKey(secretKey.getEncoded()); + } + + private SecretKey generateKey() throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(keySize); + + return keyGenerator.generateKey(); + } + + private byte[] encrypt(SecretKey key, byte[] content, byte[] iv) throws GeneralSecurityException { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); + return cipher.doFinal(content); + } +} diff --git a/common/src/main/java/com/discordsrv/common/paste/service/BinPasteService.java b/common/src/main/java/com/discordsrv/common/paste/service/BinPasteService.java new file mode 100644 index 00000000..557e4f00 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/paste/service/BinPasteService.java @@ -0,0 +1,70 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.paste.service; + +import com.discordsrv.common.DiscordSRV; +import com.discordsrv.common.paste.Paste; +import com.discordsrv.common.paste.PasteService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class BinPasteService implements PasteService { + + private final DiscordSRV discordSRV; + private final String binUrl; + + public BinPasteService(DiscordSRV discordSRV, String binUrl) { + this.discordSRV = discordSRV; + this.binUrl = binUrl; + } + + @Override + public Paste uploadFile(byte[] fileContent) throws IOException { + ObjectNode json = discordSRV.json().createObjectNode(); + ArrayNode files = json.putArray("files"); + ObjectNode file = files.addObject(); + + // "File name must be divisible by 16" not that I care about the file name... + file.put("name", new String(Base64.getEncoder().encode(new byte[16]))); + file.put("content", new String(fileContent, StandardCharsets.UTF_8)); + + Request request = new Request.Builder() + .url(binUrl + "/v1/post") + .post(RequestBody.create(MediaType.get("application/json"), json.toString())) + .build(); + + try (Response response = discordSRV.httpClient().newCall(request).execute()) { + ResponseBody responseBody = response.body(); + if (responseBody == null) { + return null; + } + + JsonNode responseNode = discordSRV.json().readTree(responseBody.string()); + String binId = responseNode.get("bin").asText(); + return new Paste(binId, binUrl + "/v1/" + binId + ".json", null); + } + } + +} diff --git a/common/src/main/java/com/discordsrv/common/paste/service/BytebinPasteService.java b/common/src/main/java/com/discordsrv/common/paste/service/BytebinPasteService.java new file mode 100644 index 00000000..bf7b95db --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/paste/service/BytebinPasteService.java @@ -0,0 +1,56 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.paste.service; + +import com.discordsrv.common.DiscordSRV; +import com.discordsrv.common.paste.Paste; +import com.discordsrv.common.paste.PasteService; +import com.fasterxml.jackson.databind.JsonNode; +import okhttp3.*; + +public class BytebinPasteService implements PasteService { + + private final DiscordSRV discordSRV; + private final String bytebinUrl; + + public BytebinPasteService(DiscordSRV discordSRV, String bytebinUrl) { + this.discordSRV = discordSRV; + this.bytebinUrl = bytebinUrl; + } + + @Override + public Paste uploadFile(byte[] fileContent) throws Throwable { + Request request = new Request.Builder() + .url(bytebinUrl + "/post") + .header("Content-Encoding", "gzip") + .post(RequestBody.create(MediaType.get("application/octet-stream"), fileContent)) + .build(); + + try (Response response = discordSRV.httpClient().newCall(request).execute()) { + ResponseBody responseBody = response.body(); + if (responseBody == null) { + return null; + } + + JsonNode responseNode = discordSRV.json().readTree(responseBody.string()); + String key = responseNode.get("key").asText(); + return new Paste(key, bytebinUrl + "/" + key, null); + } + } +} diff --git a/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java b/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java index 9fa7b2ce..c758eed0 100644 --- a/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java +++ b/common/src/test/java/com/discordsrv/common/MockDiscordSRV.java @@ -85,7 +85,7 @@ public class MockDiscordSRV extends AbstractDiscordSRV { @@ -71,6 +74,16 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV new IllegalStateException("No version")); - } - @Override public PluginManager pluginManager() { return pluginManager;