From 3adb3a67e9a3eec4540b7968ee6119dbd5c44193 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 30 Jul 2025 18:19:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86=E4=BB=A3=E7=A0=81=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=20URL=20=E6=9B=BF=E6=8D=A2=E4=B8=BA=20URI=20(#4131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/game/LauncherHelper.java | 24 +-- .../jackhuang/hmcl/game/TexturesLoader.java | 4 +- .../hmcl/setting/DownloadProviders.java | 20 +-- .../jackhuang/hmcl/setting/ProxyManager.java | 40 ++++- .../java/org/jackhuang/hmcl/ui/FXUtils.java | 6 +- .../account/AddAuthlibInjectorServerPane.java | 4 +- .../ui/construct/TaskExecutorDialogPane.java | 8 +- .../ui/decorator/DecoratorController.java | 4 +- .../hmcl/ui/download/DownloadPage.java | 4 +- .../ui/download/ModpackSelectionPage.java | 10 +- .../UpdateInstallerWizardProvider.java | 18 +-- .../hmcl/ui/main/JavaDownloadDialog.java | 2 +- .../hmcl/ui/versions/DownloadPage.java | 4 +- .../hmcl/ui/versions/ModUpdatesPage.java | 8 +- .../jackhuang/hmcl/ui/versions/Versions.java | 16 +- .../hmcl/upgrade/HMCLDownloadTask.java | 19 ++- .../jackhuang/hmcl/upgrade/RemoteVersion.java | 3 +- .../jackhuang/hmcl/util/CrashReporter.java | 3 +- .../AuthlibInjectorDownloader.java | 8 +- .../AuthlibInjectorProvider.java | 28 ++-- .../AuthlibInjectorServer.java | 35 +++-- .../hmcl/auth/microsoft/MicrosoftService.java | 21 +-- .../org/jackhuang/hmcl/auth/offline/Skin.java | 21 ++- .../auth/yggdrasil/YggdrasilProvider.java | 14 +- .../hmcl/auth/yggdrasil/YggdrasilService.java | 10 +- .../download/AdaptedDownloadProvider.java | 8 +- .../hmcl/download/AutoDownloadProvider.java | 8 +- .../hmcl/download/DownloadProvider.java | 15 +- .../download/fabric/FabricAPIInstallTask.java | 6 +- .../hmcl/download/forge/ForgeInstallTask.java | 2 +- .../download/forge/ForgeNewInstallTask.java | 11 +- .../download/game/GameAssetDownloadTask.java | 6 +- .../game/GameAssetIndexDownloadTask.java | 4 +- .../hmcl/download/game/GameDownloadTask.java | 9 +- .../download/game/LibraryDownloadTask.java | 6 +- .../java/mojang/MojangJavaDownloadTask.java | 4 +- .../neoforge/NeoForgeInstallTask.java | 2 +- .../neoforge/NeoForgeOldInstallTask.java | 13 +- .../optifine/OptiFineInstallTask.java | 4 +- .../download/quilt/QuiltAPIInstallTask.java | 6 +- .../hmcl/mod/curse/CurseCompletionTask.java | 2 +- .../hmcl/mod/curse/CurseManifestFile.java | 8 +- .../mod/mcbbs/McbbsModpackCompletionTask.java | 14 +- .../hmcl/mod/mcbbs/McbbsModpackManifest.java | 8 +- .../mod/modrinth/ModrinthCompletionTask.java | 2 +- .../hmcl/mod/modrinth/ModrinthManifest.java | 8 +- .../hmcl/mod/multimc/MultiMCComponents.java | 7 +- .../server/ServerModpackCompletionTask.java | 8 +- .../hmcl/task/DownloadException.java | 15 +- .../org/jackhuang/hmcl/task/FetchTask.java | 54 ++++--- .../jackhuang/hmcl/task/FileDownloadTask.java | 112 ++++++------- .../java/org/jackhuang/hmcl/task/GetTask.java | 26 +-- .../jackhuang/hmcl/util/CacheRepository.java | 39 +++-- .../hmcl/util/io/ContentEncoding.java | 56 +++++++ .../jackhuang/hmcl/util/io/HttpRequest.java | 30 ++-- .../jackhuang/hmcl/util/io/NetworkUtils.java | 148 +++++++++--------- .../hmcl/util/io/ResponseCodeException.java | 26 +-- .../hmcl/util/io/NetworkUtilsTest.java | 38 +++++ 58 files changed, 577 insertions(+), 462 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ContentEncoding.java create mode 100644 HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/NetworkUtilsTest.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index b247a77e0..4852ae469 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -53,7 +53,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.SocketTimeoutException; -import java.net.URL; +import java.net.URI; import java.nio.file.AccessDeniedException; import java.util.*; import java.util.concurrent.*; @@ -267,27 +267,27 @@ public final class LauncherHelper { if (ex.getCause() instanceof ResponseCodeException) { ResponseCodeException rce = (ResponseCodeException) ex.getCause(); int responseCode = rce.getResponseCode(); - URL url = rce.getUrl(); + URI uri = rce.getUri(); if (responseCode == 404) - message += i18n("download.code.404", url); + message += i18n("download.code.404", uri); else - message += i18n("download.failed", url, responseCode); + message += i18n("download.failed", uri, responseCode); } else { message += StringUtils.getStackTrace(ex.getCause()); } } else if (ex instanceof DownloadException) { - URL url = ((DownloadException) ex).getUrl(); + URI uri = ((DownloadException) ex).getUri(); if (ex.getCause() instanceof SocketTimeoutException) { - message = i18n("install.failed.downloading.timeout", url); + message = i18n("install.failed.downloading.timeout", uri); } else if (ex.getCause() instanceof ResponseCodeException) { ResponseCodeException responseCodeException = (ResponseCodeException) ex.getCause(); if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) { - message = i18n("download.code." + responseCodeException.getResponseCode(), url); + message = i18n("download.code." + responseCodeException.getResponseCode(), uri); } else { - message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause()); + message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause()); } } else { - message = i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(ex.getCause()); + message = i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(ex.getCause()); } } else if (ex instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) { message = i18n("assets.index.malformed"); @@ -298,11 +298,11 @@ public final class LauncherHelper { } else if (ex instanceof ResponseCodeException) { ResponseCodeException rce = (ResponseCodeException) ex; int responseCode = rce.getResponseCode(); - URL url = rce.getUrl(); + URI uri = rce.getUri(); if (responseCode == 404) - message = i18n("download.code.404", url); + message = i18n("download.code.404", uri); else - message = i18n("download.failed", url, responseCode); + message = i18n("download.failed", uri, responseCode); } else if (ex instanceof CommandTooLongException) { message = i18n("launch.failed.command_too_long"); } else if (ex instanceof ExecutionPolicyLimitException) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java index ca3398a19..d0cfc951d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -110,7 +110,7 @@ public final class TexturesLoader { if (!Files.isRegularFile(file)) { // download it try { - new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run(); + new FileDownloadTask(URI.create(texture.getUrl()), file).run(); LOG.info("Texture downloaded: " + texture.getUrl()); } catch (Exception e) { if (Files.isRegularFile(file)) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index f23159f16..df53a1f10 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException; import javax.net.ssl.SSLHandshakeException; import java.io.FileNotFoundException; import java.net.SocketTimeoutException; -import java.net.URL; +import java.net.URI; import java.nio.file.AccessDeniedException; import java.util.Arrays; import java.util.Map; @@ -137,26 +137,26 @@ public final class DownloadProviders { public static String localizeErrorMessage(Throwable exception) { if (exception instanceof DownloadException) { - URL url = ((DownloadException) exception).getUrl(); + URI uri = ((DownloadException) exception).getUri(); if (exception.getCause() instanceof SocketTimeoutException) { - return i18n("install.failed.downloading.timeout", url); + return i18n("install.failed.downloading.timeout", uri); } else if (exception.getCause() instanceof ResponseCodeException) { ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause(); if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) { - return i18n("download.code." + responseCodeException.getResponseCode(), url); + return i18n("download.code." + responseCodeException.getResponseCode(), uri); } else { - return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()); + return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()); } } else if (exception.getCause() instanceof FileNotFoundException) { - return i18n("download.code.404", url); + return i18n("download.code.404", uri); } else if (exception.getCause() instanceof AccessDeniedException) { - return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile()); + return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.access_denied", ((AccessDeniedException) exception.getCause()).getFile()); } else if (exception.getCause() instanceof ArtifactMalformedException) { - return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.artifact_malformed"); + return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.artifact_malformed"); } else if (exception.getCause() instanceof SSLHandshakeException) { - return i18n("install.failed.downloading.detail", url) + "\n" + i18n("exception.ssl_handshake"); + return i18n("install.failed.downloading.detail", uri) + "\n" + i18n("exception.ssl_handshake"); } else { - return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()); + return i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()); } } else if (exception instanceof ArtifactMalformedException) { return i18n("exception.artifact_malformed"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java index 3fbe7466c..79da3a93b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ProxyManager.java @@ -18,13 +18,15 @@ package org.jackhuang.hmcl.setting; import javafx.beans.InvalidationListener; -import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.net.*; import java.util.Collections; import java.util.List; +import java.util.Objects; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -32,7 +34,10 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class ProxyManager { private static final ProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY); - private static final ProxySelector SYSTEM_DEFAULT = Lang.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY); + private static final ProxySelector SYSTEM_DEFAULT = Objects.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY); + + private static volatile @NotNull ProxySelector defaultProxySelector = SYSTEM_DEFAULT; + private static volatile @Nullable SimpleAuthenticator defaultAuthenticator = null; private static ProxySelector getProxySelector() { if (config().hasProxy()) { @@ -53,7 +58,7 @@ public final class ProxyManager { } } - private static Authenticator getAuthenticator() { + private static SimpleAuthenticator getAuthenticator() { if (config().hasProxy() && config().hasProxyAuth()) { String username = config().getProxyUser(); String password = config().getProxyPass(); @@ -67,15 +72,34 @@ public final class ProxyManager { } static void init() { - ProxySelector.setDefault(getProxySelector()); - InvalidationListener updateProxySelector = observable -> ProxySelector.setDefault(getProxySelector()); + ProxySelector.setDefault(new ProxySelector() { + @Override + public List select(URI uri) { + return defaultProxySelector.select(uri); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + defaultProxySelector.connectFailed(uri, sa, ioe); + } + }); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + var defaultAuthenticator = ProxyManager.defaultAuthenticator; + return defaultAuthenticator != null ? defaultAuthenticator.getPasswordAuthentication() : null; + } + }); + + defaultProxySelector = getProxySelector(); + InvalidationListener updateProxySelector = observable -> defaultProxySelector = getProxySelector(); config().proxyTypeProperty().addListener(updateProxySelector); config().proxyHostProperty().addListener(updateProxySelector); config().proxyPortProperty().addListener(updateProxySelector); config().hasProxyProperty().addListener(updateProxySelector); - Authenticator.setDefault(getAuthenticator()); - InvalidationListener updateAuthenticator = observable -> Authenticator.setDefault(getAuthenticator()); + defaultAuthenticator = getAuthenticator(); + InvalidationListener updateAuthenticator = observable -> defaultAuthenticator = getAuthenticator(); config().hasProxyProperty().addListener(updateAuthenticator); config().hasProxyAuthProperty().addListener(updateAuthenticator); config().proxyUserProperty().addListener(updateAuthenticator); @@ -124,7 +148,7 @@ public final class ProxyManager { } @Override - protected PasswordAuthentication getPasswordAuthentication() { + public PasswordAuthentication getPasswordAuthentication() { return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index c350cd1bf..2d12de7d4 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -844,14 +844,14 @@ public final class FXUtils { } } - public static Image loadImage(URL url) throws Exception { - URLConnection connection = NetworkUtils.createConnection(url); + public static Image loadImage(URI uri) throws Exception { + URLConnection connection = NetworkUtils.createConnection(uri); if (connection instanceof HttpURLConnection) { connection = NetworkUtils.resolveConnection((HttpURLConnection) connection); } try (InputStream input = connection.getInputStream()) { - String path = url.getPath(); + String path = uri.getPath(); if (path != null && "webp".equalsIgnoreCase(StringUtils.substringAfterLast(path, '.'))) return loadWebPImage(input); else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java index c19c80131..4b3c0b5da 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AddAuthlibInjectorServerPane.java @@ -31,10 +31,10 @@ import org.jackhuang.hmcl.ui.construct.DialogAware; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.io.NetworkUtils; import javax.net.ssl.SSLException; import java.io.IOException; +import java.net.URI; import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; @@ -196,7 +196,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement lblServerName.setText(serverBeingAdded.getName()); lblServerUrl.setText(serverBeingAdded.getUrl()); - lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol())); + lblServerWarning.setVisible("http".equals(URI.create(serverBeingAdded.getUrl()).getScheme())); this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT); } else { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java index 36191ba05..0d74ba7d5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/TaskExecutorDialogPane.java @@ -26,9 +26,7 @@ import javafx.scene.control.ScrollPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.TaskExecutor; -import org.jackhuang.hmcl.task.TaskListener; +import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jetbrains.annotations.NotNull; @@ -43,7 +41,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public class TaskExecutorDialogPane extends BorderPane { private TaskExecutor executor; private TaskCancellationAction onCancel; - private final Consumer speedEventHandler; + private final Consumer speedEventHandler; private final Label lblTitle; private final Label lblProgress; @@ -108,7 +106,7 @@ public class TaskExecutorDialogPane extends BorderPane { String finalUnit = unit; Platform.runLater(() -> lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit))); }; - FileDownloadTask.speedEvent.channel(FileDownloadTask.SpeedEvent.class).registerWeak(speedEventHandler); + FileDownloadTask.speedEvent.channel(FetchTask.SpeedEvent.class).registerWeak(speedEventHandler); onEscPressed(this, btnCancel::fire); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java index b0393598d..1ff75656f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/decorator/DecoratorController.java @@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -214,7 +214,7 @@ public class DecoratorController { String backgroundImageUrl = config().getBackgroundImageUrl(); if (backgroundImageUrl != null) { try { - image = FXUtils.loadImage(new URL(backgroundImageUrl)); + image = FXUtils.loadImage(URI.create(backgroundImageUrl)); } catch (Exception e) { LOG.warning("Couldn't load background image", e); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index dcb4ed4fe..ead89c673 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -52,10 +52,10 @@ import org.jackhuang.hmcl.ui.wizard.Navigation; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardProvider; import org.jackhuang.hmcl.util.TaskCancellationAction; -import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jetbrains.annotations.Nullable; +import java.net.URI; import java.nio.file.Path; import java.util.HashMap; import java.util.Locale; @@ -156,7 +156,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage Path dest = runDirectory.resolve(subdirectoryName).resolve(result); Controllers.taskDialog(Task.composeAsync(() -> { - FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile()); + var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest); task.setName(file.getName()); return task; }).whenComplete(Schedulers.javafx(), exception -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java index 8f83156b8..3d63f094d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/ModpackSelectionPage.java @@ -29,9 +29,7 @@ import javafx.scene.shape.SVGPath; import javafx.stage.FileChooser; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.server.ServerModpackManifest; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.GetTask; -import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.SVG; @@ -43,7 +41,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.File; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -128,7 +126,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage { private void onChooseRemoteFile() { Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (urlString, resolve, reject) -> { try { - URL url = new URL(urlString); + URI url = URI.create(urlString); if (urlString.endsWith("server-manifest.json")) { // if urlString ends with .json, we assume that the url is server-manifest.json Controllers.taskDialog(new GetTask(url).whenComplete(Schedulers.javafx(), (result, e) -> { @@ -150,7 +148,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage { resolve.run(); Controllers.taskDialog( - new FileDownloadTask(url, modpack.toFile(), null) + new FileDownloadTask(url, modpack, null) .whenComplete(Schedulers.javafx(), e -> { if (e == null) { resolve.run(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java index fec2b275d..9ec804519 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/UpdateInstallerWizardProvider.java @@ -37,7 +37,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.net.SocketTimeoutException; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -133,28 +133,28 @@ public final class UpdateInstallerWizardProvider implements WizardProvider { if (exception.getCause() instanceof ResponseCodeException) { ResponseCodeException rce = (ResponseCodeException) exception.getCause(); int responseCode = rce.getResponseCode(); - URL url = rce.getUrl(); + URI uri = rce.getUri(); if (responseCode == 404) - message += i18n("download.code.404", url); + message += i18n("download.code.404", uri); else - message += i18n("download.failed", url, responseCode); + message += i18n("download.failed", uri, responseCode); } else { message += StringUtils.getStackTrace(exception.getCause()); } Controllers.dialog(message, i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); } else if (exception instanceof DownloadException) { - URL url = ((DownloadException) exception).getUrl(); + URI uri = ((DownloadException) exception).getUri(); if (exception.getCause() instanceof SocketTimeoutException) { - Controllers.dialog(i18n("install.failed.downloading.timeout", url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); + Controllers.dialog(i18n("install.failed.downloading.timeout", uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); } else if (exception.getCause() instanceof ResponseCodeException) { ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause(); if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) { - Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), url), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); + Controllers.dialog(i18n("download.code." + responseCodeException.getResponseCode(), uri), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); } else { - Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); + Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); } } else { - Controllers.dialog(i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); + Controllers.dialog(i18n("install.failed.downloading.detail", uri) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR, next); } } else if (exception instanceof UnsupportedInstallationException) { switch (((UnsupportedInstallationException) exception).getReason()) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java index bff8c9bfc..adb3d611b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -363,7 +363,7 @@ public final class JavaDownloadDialog extends StackPane { return getIntegrityCheck .thenComposeAsync(integrityCheck -> new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()), - targetFile, integrityCheck).setName(fileInfo.getFileName())) + targetFile.toPath(), integrityCheck).setName(fileInfo.getFileName())) .thenSupplyAsync(targetFile::toPath); }) .whenComplete(Schedulers.javafx(), ((result, exception) -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index b852f72bb..6de0971de 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -49,12 +49,12 @@ import org.jackhuang.hmcl.ui.construct.*; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.util.*; import org.jackhuang.hmcl.util.i18n.I18n; -import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.net.URI; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -180,7 +180,7 @@ public class DownloadPage extends Control implements DecoratorPage { Controllers.taskDialog( Task.composeAsync(() -> { - FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck()); + var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest.toPath(), file.getFile().getIntegrityCheck()); task.setName(file.getName()); return task; }), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java index 05209706f..07c2587eb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModUpdatesPage.java @@ -45,7 +45,7 @@ import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CSVTable; -import java.net.URL; +import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; @@ -293,9 +293,9 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage { if (isDisabled) fileName += ModManager.DISABLED_EXTENSION; - FileDownloadTask task = new FileDownloadTask( - new URL(remote.getFile().getUrl()), - modManager.getModsDirectory().resolve(fileName).toFile()); + var task = new FileDownloadTask( + URI.create(remote.getFile().getUrl()), + modManager.getModsDirectory().resolve(fileName)); task.setName(remote.getName()); return task; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java index a9ff73ec6..750b4aa1a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java @@ -28,10 +28,7 @@ import org.jackhuang.hmcl.game.GameRepository; import org.jackhuang.hmcl.game.LauncherHelper; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.setting.*; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.task.TaskExecutor; +import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.account.CreateAccountPane; @@ -47,7 +44,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.File; import java.io.IOException; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CancellationException; @@ -75,18 +73,18 @@ public final class Versions { public static void downloadModpackImpl(Profile profile, String version, RemoteMod.Version file) { Path modpack; - URL downloadURL; + URI downloadURL; try { modpack = Files.createTempFile("modpack", ".zip"); - downloadURL = new URL(file.getFile().getUrl()); - } catch (IOException e) { + downloadURL = new URI(file.getFile().getUrl()); + } catch (IOException | URISyntaxException e) { Controllers.dialog( i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e), i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR); return; } Controllers.taskDialog( - new FileDownloadTask(downloadURL, modpack.toFile()) + new FileDownloadTask(downloadURL, modpack) .whenComplete(Schedulers.javafx(), e -> { if (e == null) { if (version != null) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java index 8b6fdc06c..2b6a91a3c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java @@ -19,21 +19,21 @@ package org.jackhuang.hmcl.upgrade; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.util.Pack200Utils; -import org.jackhuang.hmcl.util.io.NetworkUtils; import org.tukaani.xz.XZInputStream; import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarOutputStream; -class HMCLDownloadTask extends FileDownloadTask { +final class HMCLDownloadTask extends FileDownloadTask { - private RemoteVersion.Type archiveFormat; + private final RemoteVersion.Type archiveFormat; public HMCLDownloadTask(RemoteVersion version, Path target) { - super(NetworkUtils.toURL(version.getUrl()), target.toFile(), version.getIntegrityCheck()); + super(URI.create(version.getUrl()), target, version.getIntegrityCheck()); archiveFormat = version.getType(); } @@ -42,8 +42,7 @@ class HMCLDownloadTask extends FileDownloadTask { super.execute(); try { - Path target = getFile().toPath(); - + Path target = getPath(); switch (archiveFormat) { case JAR: break; @@ -51,7 +50,7 @@ class HMCLDownloadTask extends FileDownloadTask { case PACK_XZ: byte[] raw = Files.readAllBytes(target); try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw)); - JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) { + JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) { Pack200Utils.unpack(in, out); } break; @@ -60,7 +59,11 @@ class HMCLDownloadTask extends FileDownloadTask { throw new IllegalArgumentException("Unknown format: " + archiveFormat); } } catch (Throwable e) { - getFile().delete(); + try { + Files.deleteIfExists(getPath()); + } catch (Throwable e2) { + e.addSuppressed(e2); + } throw e; } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java index 81947320e..5c4d8d056 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java @@ -26,13 +26,14 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; +import java.net.URI; import java.util.Optional; public class RemoteVersion { public static RemoteVersion fetch(UpdateChannel channel, String url) throws IOException { try { - JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class); + JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(URI.create(url)), JsonObject.class); String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing")); String jarUrl = Optional.ofNullable(response.get("jar")).map(JsonElement::getAsString).orElse(null); String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java index 3eb8d38cd..e4ed74b51 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -28,6 +28,7 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; +import java.net.URI; import java.util.HashMap; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -122,7 +123,7 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler { map.put("version", Metadata.VERSION); map.put("log", LOG.getLogs()); try { - String response = NetworkUtils.doPost(NetworkUtils.toURL(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map); + String response = NetworkUtils.doPost(URI.create(Metadata.PUBLISH_URL + "/hmcl/crash.php"), map); if (StringUtils.isNotBlank(response)) LOG.error("Crash server response: " + response); } catch (IOException ex) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloader.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloader.java index 3af74b280..28ace5320 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloader.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorDownloader.java @@ -25,7 +25,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.util.io.HttpRequest; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; @@ -96,7 +96,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide } try { - new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation.toFile(), + new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation, Optional.ofNullable(latest.checksums.get("sha256")) .map(checksum -> new IntegrityCheck("SHA-256", checksum)) .orElse(null)) @@ -110,9 +110,9 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException { IOException exception = null; - for (URL url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) { + for (URI url : downloadProvider.get().injectURLWithCandidates(LATEST_BUILD_URL)) { try { - return HttpRequest.GET(url.toExternalForm()).getJson(AuthlibInjectorVersionInfo.class); + return HttpRequest.GET(url.toString()).getJson(AuthlibInjectorVersionInfo.class); } catch (IOException | JsonParseException e) { if (exception == null) { exception = new IOException("Failed to fetch authlib-injector artifact info"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorProvider.java index 871e667b9..ee504fb7e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorProvider.java @@ -17,13 +17,11 @@ */ package org.jackhuang.hmcl.auth.authlibinjector; -import static org.jackhuang.hmcl.util.io.NetworkUtils.toURL; - import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; -import java.net.URL; +import java.net.URI; import java.util.UUID; public class AuthlibInjectorProvider implements YggdrasilProvider { @@ -35,33 +33,33 @@ public class AuthlibInjectorProvider implements YggdrasilProvider { } @Override - public URL getAuthenticationURL() throws AuthenticationException { - return toURL(apiRoot + "authserver/authenticate"); + public URI getAuthenticationURL() throws AuthenticationException { + return URI.create(apiRoot + "authserver/authenticate"); } @Override - public URL getRefreshmentURL() throws AuthenticationException { - return toURL(apiRoot + "authserver/refresh"); + public URI getRefreshmentURL() throws AuthenticationException { + return URI.create(apiRoot + "authserver/refresh"); } @Override - public URL getValidationURL() throws AuthenticationException { - return toURL(apiRoot + "authserver/validate"); + public URI getValidationURL() throws AuthenticationException { + return URI.create(apiRoot + "authserver/validate"); } @Override - public URL getInvalidationURL() throws AuthenticationException { - return toURL(apiRoot + "authserver/invalidate"); + public URI getInvalidationURL() throws AuthenticationException { + return URI.create(apiRoot + "authserver/invalidate"); } @Override - public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException { - return toURL(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin"); + public URI getSkinUploadURL(UUID uuid) throws UnsupportedOperationException { + return URI.create(apiRoot + "api/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin"); } @Override - public URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException { - return toURL(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)); + public URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException { + return URI.create(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java index 0b8e76965..ebe8fb6f5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/authlibinjector/AuthlibInjectorServer.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.auth.authlibinjector; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyMap; import static org.jackhuang.hmcl.util.Lang.tryCast; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -25,14 +24,15 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; import java.io.IOException; import java.lang.reflect.Type; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.util.LinkedHashMap; -import java.util.Locale; import java.util.Map; import java.util.Optional; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService; import org.jackhuang.hmcl.util.io.HttpRequest; +import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.ObservableHelper; import org.jetbrains.annotations.Nullable; @@ -58,17 +58,14 @@ public class AuthlibInjectorServer implements Observable { public static AuthlibInjectorServer locateServer(String url) throws IOException { try { url = addHttpsIfMissing(url); - HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); - conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag()); - + HttpURLConnection conn = NetworkUtils.createHttpConnection(URI.create(url)); String ali = conn.getHeaderField("x-authlib-injector-api-location"); if (ali != null) { - URL absoluteAli = new URL(conn.getURL(), ali); + URI absoluteAli = conn.getURL().toURI().resolve(ali); if (!urlEqualsIgnoreSlash(url, absoluteAli.toString())) { conn.disconnect(); url = absoluteAli.toString(); - conn = (HttpURLConnection) absoluteAli.openConnection(); - conn.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag()); + conn = NetworkUtils.createHttpConnection(absoluteAli); } } @@ -77,22 +74,26 @@ public class AuthlibInjectorServer implements Observable { try { AuthlibInjectorServer server = new AuthlibInjectorServer(url); - server.refreshMetadata(new String(conn.getInputStream().readAllBytes(), UTF_8)); + server.refreshMetadata(NetworkUtils.readFullyAsString(conn)); return server; } finally { conn.disconnect(); } - } catch (IllegalArgumentException e) { + } catch (IllegalArgumentException | URISyntaxException e) { throw new IOException(e); } } - private static String addHttpsIfMissing(String url) { - String lowercased = url.toLowerCase(Locale.ROOT); - if (!lowercased.startsWith("http://") && !lowercased.startsWith("https://")) { - url = "https://" + url; + private static String addHttpsIfMissing(String url) throws IOException { + URI uri = URI.create(url); + + if (uri.getScheme() == null) { + return "https://" + url; + } else if (!NetworkUtils.isHttpUri(uri)) { + throw new IOException("Yggdrasil server should be an HTTP or HTTPS URI, but got: " + url); + } else { + return url; } - return url; } private static boolean urlEqualsIgnoreSlash(String a, String b) { @@ -103,7 +104,7 @@ public class AuthlibInjectorServer implements Observable { return a.equals(b); } - private String url; + private final String url; @Nullable private String metadataResponse; private long metadataTimestamp; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index b5e81f7e0..ea95e9766 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -34,7 +34,8 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -164,7 +165,7 @@ public class MicrosoftService { .accept("application/json").createConnection(); if (request.getResponseCode() != 200) { - throw new ResponseCodeException(new URL("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode()); + throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode()); } // Get Minecraft Account UUID @@ -247,22 +248,22 @@ public class MicrosoftService { if (responseCode == HTTP_NOT_FOUND) { throw new NoMinecraftJavaEditionProfileException(); } else if (responseCode != 200) { - throw new ResponseCodeException(new URL("https://api.minecraftservices.com/minecraft/profile"), responseCode); + throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/minecraft/profile"), responseCode); } - String result = NetworkUtils.readData(conn); + String result = NetworkUtils.readFullyAsString(conn); return JsonUtils.fromNonNullJson(result, MinecraftProfileResponse.class); } public Optional getCompleteGameProfile(UUID uuid) throws AuthenticationException { Objects.requireNonNull(uuid); - return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class)); + return Optional.ofNullable(GSON.fromJson(request(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class)); } public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException { try { - HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins")); + HttpURLConnection con = NetworkUtils.createHttpConnection(URI.create("https://api.minecraftservices.com/minecraft/profile/skins")); con.setRequestMethod("POST"); con.setRequestProperty("Authorization", "Bearer " + accessToken); con.setDoOutput(true); @@ -273,21 +274,21 @@ public class MicrosoftService { } } - String response = NetworkUtils.readData(con); + String response = NetworkUtils.readFullyAsString(con); if (StringUtils.isBlank(response)) { if (con.getResponseCode() / 100 != 2) - throw new ResponseCodeException(con.getURL(), con.getResponseCode()); + throw new ResponseCodeException(con.getURL().toURI(), con.getResponseCode()); } else { MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class); if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2) throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response); } - } catch (IOException | JsonParseException e) { + } catch (IOException | JsonParseException | URISyntaxException e) { throw new AuthenticationException(e); } } - private static String request(URL url, Object payload) throws AuthenticationException { + private static String request(URI url, Object payload) throws AuthenticationException { try { if (payload == null) return NetworkUtils.doGet(url); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java index cfe9b6870..1678b186c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java @@ -33,14 +33,11 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.URL; +import java.net.URI; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Lang.tryCast; @@ -169,7 +166,7 @@ public class Skin { String realCslApi = type == Type.LITTLE_SKIN ? "https://littleskin.cn/csl" : StringUtils.removeSuffix(Lang.requireNonNullElse(cslApi, ""), "/"); - return Task.composeAsync(() -> new GetTask(new URL(String.format("%s/%s.json", realCslApi, username)))) + return Task.composeAsync(() -> new GetTask(URI.create(String.format("%s/%s.json", realCslApi, username)))) .thenComposeAsync(json -> { SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class); @@ -179,8 +176,8 @@ public class Skin { return Task.allOf( Task.supplyAsync(result::getModel), - result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getHash())), 3), - result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(new URL(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3) + result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getHash())), 3), + result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(URI.create(String.format("%s/textures/%s", realCslApi, result.getCapeHash())), 3) ); }).thenApplyAsync(result -> { if (result == null) { @@ -232,8 +229,8 @@ public class Skin { private static class FetchBytesTask extends FetchTask { - public FetchBytesTask(URL url, int retry) { - super(Collections.singletonList(url), retry); + public FetchBytesTask(URI uri, int retry) { + super(List.of(uri), retry); } @Override @@ -247,7 +244,7 @@ public class Skin { } @Override - protected Context getContext(URLConnection conn, boolean checkETag) throws IOException { + protected Context getContext(URLConnection connection, boolean checkETag) throws IOException { return new Context() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -263,7 +260,7 @@ public class Skin { setResult(new ByteArrayInputStream(baos.toByteArray())); if (checkETag) { - repository.cacheBytes(baos.toByteArray(), conn); + repository.cacheBytes(connection, baos.toByteArray()); } } }; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilProvider.java index 00c8f6681..db90e38f1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilProvider.java @@ -19,7 +19,7 @@ package org.jackhuang.hmcl.auth.yggdrasil; import org.jackhuang.hmcl.auth.AuthenticationException; -import java.net.URL; +import java.net.URI; import java.util.UUID; /** @@ -27,13 +27,13 @@ import java.util.UUID; */ public interface YggdrasilProvider { - URL getAuthenticationURL() throws AuthenticationException; + URI getAuthenticationURL() throws AuthenticationException; - URL getRefreshmentURL() throws AuthenticationException; + URI getRefreshmentURL() throws AuthenticationException; - URL getValidationURL() throws AuthenticationException; + URI getValidationURL() throws AuthenticationException; - URL getInvalidationURL() throws AuthenticationException; + URI getInvalidationURL() throws AuthenticationException; /** * URL to upload skin. @@ -51,8 +51,8 @@ public interface YggdrasilProvider { * @throws AuthenticationException if url cannot be generated. e.g. some parameter or query is malformed. * @throws UnsupportedOperationException if the Yggdrasil provider does not support third-party skin uploading. */ - URL getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException; + URI getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException; - URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException; + URI getProfilePropertiesURL(UUID uuid) throws AuthenticationException; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java index 72e0ded57..5e73f748c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -160,7 +160,7 @@ public class YggdrasilService { request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis); } } - requireEmpty(NetworkUtils.readData(con)); + requireEmpty(NetworkUtils.readFullyAsString(con)); } catch (IOException e) { throw new AuthenticationException(e); } @@ -227,12 +227,12 @@ public class YggdrasilService { } } - private static String request(URL url, Object payload) throws AuthenticationException { + private static String request(URI uri, Object payload) throws AuthenticationException { try { if (payload == null) - return NetworkUtils.doGet(url); + return NetworkUtils.doGet(uri); else - return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json"); + return NetworkUtils.doPost(uri, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json"); } catch (IOException e) { throw new ServerDisconnectException(e); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java index dc729b1e1..11527db1c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AdaptedDownloadProvider.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.download; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -59,21 +59,21 @@ public class AdaptedDownloadProvider implements DownloadProvider { } @Override - public List getAssetObjectCandidates(String assetObjectLocation) { + public List getAssetObjectCandidates(String assetObjectLocation) { return downloadProviderCandidates.stream() .flatMap(d -> d.getAssetObjectCandidates(assetObjectLocation).stream()) .collect(Collectors.toList()); } @Override - public List injectURLWithCandidates(String baseURL) { + public List injectURLWithCandidates(String baseURL) { return downloadProviderCandidates.stream() .flatMap(d -> d.injectURLWithCandidates(baseURL).stream()) .collect(Collectors.toList()); } @Override - public List injectURLsWithCandidates(List urls) { + public List injectURLsWithCandidates(List urls) { return downloadProviderCandidates.stream() .flatMap(d -> d.injectURLsWithCandidates(urls).stream()) .collect(Collectors.toList()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java index f2db0d6dd..d886dec4f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/AutoDownloadProvider.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hmcl.download; -import java.net.URL; +import java.net.URI; import java.util.List; /** @@ -51,17 +51,17 @@ public class AutoDownloadProvider implements DownloadProvider { } @Override - public List getAssetObjectCandidates(String assetObjectLocation) { + public List getAssetObjectCandidates(String assetObjectLocation) { return fileProvider.getAssetObjectCandidates(assetObjectLocation); } @Override - public List injectURLWithCandidates(String baseURL) { + public List injectURLWithCandidates(String baseURL) { return fileProvider.injectURLWithCandidates(baseURL); } @Override - public List injectURLsWithCandidates(List urls) { + public List injectURLsWithCandidates(List urls) { return fileProvider.injectURLsWithCandidates(urls); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java index a1f6fd45d..28a12539c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/DownloadProvider.java @@ -17,10 +17,7 @@ */ package org.jackhuang.hmcl.download; -import org.jackhuang.hmcl.util.io.NetworkUtils; - -import java.net.URL; -import java.util.Collections; +import java.net.URI; import java.util.List; import java.util.stream.Collectors; @@ -35,8 +32,8 @@ public interface DownloadProvider { String getAssetBaseURL(); - default List getAssetObjectCandidates(String assetObjectLocation) { - return Collections.singletonList(NetworkUtils.toURL(getAssetBaseURL() + assetObjectLocation)); + default List getAssetObjectCandidates(String assetObjectLocation) { + return List.of(URI.create(getAssetBaseURL() + assetObjectLocation)); } /** @@ -59,11 +56,11 @@ public interface DownloadProvider { * @param baseURL original URL provided by Mojang and Forge. * @return the URL that is equivalent to [baseURL], but belongs to your own service provider. */ - default List injectURLWithCandidates(String baseURL) { - return Collections.singletonList(NetworkUtils.toURL(injectURL(baseURL))); + default List injectURLWithCandidates(String baseURL) { + return List.of(URI.create(injectURL(baseURL))); } - default List injectURLsWithCandidates(List urls) { + default List injectURLsWithCandidates(List urls) { return urls.stream().flatMap(url -> injectURLWithCandidates(url).stream()).collect(Collectors.toList()); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIInstallTask.java index 8dfadafae..b5f19dda6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIInstallTask.java @@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -59,8 +59,8 @@ public final class FabricAPIInstallTask extends Task { @Override public void execute() throws IOException { dependencies.add(new FileDownloadTask( - new URL(remote.getVersion().getFile().getUrl()), - dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar").toFile(), + URI.create(remote.getVersion().getFile().getUrl()), + dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"), remote.getVersion().getFile().getIntegrityCheck()) ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index f8e6c4da1..11b410069 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -69,7 +69,7 @@ public final class ForgeInstallTask extends Task { dependent = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), - installer.toFile(), null); + installer, null); dependent.setCacheRepository(dependencyManager.getCacheRepository()); dependent.setCaching(true); dependent.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java index a8f3a6484..b9b04dcfd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeNewInstallTask.java @@ -31,7 +31,6 @@ import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.function.ExceptionalFunction; @@ -48,7 +47,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.URL; +import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -346,12 +345,12 @@ public class ForgeNewInstallTask extends Task { throw new Exception("client_mappings download info not found"); } - List mappingsUrl = dependencyManager.getDownloadProvider() + List mappingsUrl = dependencyManager.getDownloadProvider() .injectURLWithCandidates(mappings.getUrl()); - FileDownloadTask mappingsTask = new FileDownloadTask( + var mappingsTask = new FileDownloadTask( mappingsUrl, - new File(output), - IntegrityCheck.of("SHA-1", mappings.getSha1())); + Path.of(output), + FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1())); mappingsTask.setCaching(true); mappingsTask.setCacheRepository(dependencyManager.getCacheRepository()); return mappingsTask; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java index 208a82244..0f644a520 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetDownloadTask.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.util.CacheRepository; import org.jackhuang.hmcl.util.gson.JsonUtils; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -102,9 +102,9 @@ public final class GameAssetDownloadTask extends Task { LOG.warning("Unable to calc hash value of file " + file, e); } if (download) { - List urls = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation()); + List uris = dependencyManager.getDownloadProvider().getAssetObjectCandidates(assetObject.getLocation()); - FileDownloadTask task = new FileDownloadTask(urls, file.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); + var task = new FileDownloadTask(uris, file, new FileDownloadTask.IntegrityCheck("SHA-1", assetObject.getHash())); task.setName(assetObject.getHash()); task.setCandidate(dependencyManager.getCacheRepository().getCommonDirectory() .resolve("assets").resolve("objects").resolve(assetObject.getLocation())); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java index a9c9c9a18..88beb8b69 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameAssetIndexDownloadTask.java @@ -94,9 +94,9 @@ public final class GameAssetIndexDownloadTask extends Task { // We should not check the hash code of asset index file since this file is not consistent // And Mojang will modify this file anytime. So assetIndex.hash might be outdated. - FileDownloadTask task = new FileDownloadTask( + var task = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLWithCandidates(assetIndexInfo.getUrl()), - assetIndexFile.toFile(), + assetIndexFile, verifyHashCode ? new FileDownloadTask.IntegrityCheck("SHA-1", assetIndexInfo.getSha1()) : null ); task.setCacheRepository(dependencyManager.getCacheRepository()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java index 999e30ae2..1123aecd6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameDownloadTask.java @@ -20,11 +20,10 @@ package org.jackhuang.hmcl.download.game; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.CacheRepository; -import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -54,12 +53,12 @@ public final class GameDownloadTask extends Task { @Override public void execute() { - File jar = dependencyManager.getGameRepository().getVersionJar(version); + Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath(); - FileDownloadTask task = new FileDownloadTask( + var task = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()), jar, - IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1())); + FileDownloadTask.IntegrityCheck.of(CacheRepository.SHA1, version.getDownloadInfo().getSha1())); task.setCaching(true); task.setCacheRepository(dependencyManager.getCacheRepository()); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java index 8970d6bbd..4d851c0af 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/LibraryDownloadTask.java @@ -30,7 +30,7 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -116,8 +116,8 @@ public class LibraryDownloadTask extends Task { } - List urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url); - task = new FileDownloadTask(urls, jar, + List uris = dependencyManager.getDownloadProvider().injectURLWithCandidates(url); + task = new FileDownloadTask(uris, jar.toPath(), library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null); task.setCacheRepository(cacheRepository); task.setCaching(true); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java index a37ad844d..e7471f42f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/java/mojang/MojangJavaDownloadTask.java @@ -103,7 +103,7 @@ public final class MojangJavaDownloadTask extends Task { Path decompressed = target.resolve(entry.getKey() + ".tmp"); @@ -121,7 +121,7 @@ public final class MojangJavaDownloadTask extends Task dest.toFile().setExecutable(true))); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java index 1ad25a477..da7aae2ab 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeInstallTask.java @@ -49,7 +49,7 @@ public final class NeoForgeInstallTask extends Task { dependent = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLsWithCandidates(remoteVersion.getUrls()), - installer.toFile(), null + installer, null ); dependent.setCacheRepository(dependencyManager.getCacheRepository()); dependent.setCaching(true); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java index fbad0c2a2..d47672486 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/neoforge/NeoForgeOldInstallTask.java @@ -26,7 +26,6 @@ import org.jackhuang.hmcl.download.game.GameLibrariesTask; import org.jackhuang.hmcl.download.game.VersionJsonDownloadTask; import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.DigestUtils; import org.jackhuang.hmcl.util.StringUtils; @@ -44,7 +43,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.net.URL; +import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -342,12 +341,12 @@ public class NeoForgeOldInstallTask extends Task { throw new Exception("client_mappings download info not found"); } - List mappingsUrl = dependencyManager.getDownloadProvider() + List mappingsUri = dependencyManager.getDownloadProvider() .injectURLWithCandidates(mappings.getUrl()); - FileDownloadTask mappingsTask = new FileDownloadTask( - mappingsUrl, - new File(output), - IntegrityCheck.of("SHA-1", mappings.getSha1())); + var mappingsTask = new FileDownloadTask( + mappingsUri, + Path.of(output), + FileDownloadTask.IntegrityCheck.of("SHA-1", mappings.getSha1())); mappingsTask.setCaching(true); mappingsTask.setCacheRepository(dependencyManager.getCacheRepository()); return mappingsTask; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index 411a746a1..d65d93dca 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -96,9 +96,9 @@ public final class OptiFineInstallTask extends Task { dest = Files.createTempFile("optifine-installer", ".jar"); if (installer == null) { - FileDownloadTask task = new FileDownloadTask( + var task = new FileDownloadTask( dependencyManager.getDownloadProvider().injectURLsWithCandidates(remote.getUrls()), - dest.toFile(), null); + dest, null); task.setCacheRepository(dependencyManager.getCacheRepository()); task.setCaching(true); dependents.add(task); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIInstallTask.java index 4d3f88790..89fdfb85c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIInstallTask.java @@ -23,7 +23,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Task; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -59,8 +59,8 @@ public final class QuiltAPIInstallTask extends Task { @Override public void execute() throws IOException { dependencies.add(new FileDownloadTask( - new URL(remote.getVersion().getFile().getUrl()), - dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar").toFile(), + URI.create(remote.getVersion().getFile().getUrl()), + dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"), remote.getVersion().getFile().getIntegrityCheck()) ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java index bb93dee76..3358a06d1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java @@ -152,7 +152,7 @@ public final class CurseCompletionTask extends Task { return Stream.empty(); } - FileDownloadTask task = new FileDownloadTask(f.getUrl(), path); + var task = new FileDownloadTask(f.getUrl(), path.toPath()); task.setCacheRepository(dependency.getCacheRepository()); task.setCaching(true); return Stream.of(task.withCounter("hmcl.modpack.download")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestFile.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestFile.java index 150bf608d..bb1013372 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestFile.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseManifestFile.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.Validation; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jetbrains.annotations.Nullable; -import java.net.URL; +import java.net.URI; import java.util.Objects; /** @@ -84,15 +84,15 @@ public final class CurseManifestFile implements Validation { } @Nullable - public URL getUrl() { + public URI getUrl() { if (url == null) { if (fileName != null) { - return NetworkUtils.toURL(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName))); + return URI.create(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName))); } else { return null; } } else { - return NetworkUtils.toURL(NetworkUtils.encodeLocation(url)); + return URI.create(NetworkUtils.encodeLocation(url)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java index b7cc37c81..a11697797 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackCompletionTask.java @@ -36,7 +36,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -102,7 +102,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { throw new CustomException(); } })).thenComposeAsync(wrap(unused1 -> { - return executor.one(new GetTask(new URL(manifest.getFileApi() + "/manifest.json"))); + return executor.one(new GetTask(URI.create(manifest.getFileApi() + "/manifest.json"))); })).thenComposeAsync(wrap(remoteManifestJson -> { McbbsModpackManifest remoteManifest; // We needs to update modpack from online server. @@ -204,7 +204,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { return file.withFileName(NetworkUtils.detectFileName(file.getUrl())); } catch (IOException e) { try { - String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID()))); + String result = NetworkUtils.doGet(URI.create(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID()))); CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class); return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL()); } catch (FileNotFoundException fof) { @@ -213,7 +213,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { return file; } catch (IOException | JsonParseException e2) { try { - String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID()))); + String result = NetworkUtils.doGet(URI.create(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID()))); CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class); return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL()); } catch (FileNotFoundException fof) { @@ -247,7 +247,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { McbbsModpackManifest.CurseFile curseFile = (McbbsModpackManifest.CurseFile) file; if (StringUtils.isNotBlank(curseFile.getFileName())) { if (!modManager.hasSimpleMod(curseFile.getFileName())) { - FileDownloadTask task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName()).toFile()); + var task = new FileDownloadTask(curseFile.getUrl(), modManager.getSimpleModPath(curseFile.getFileName())); task.setCacheRepository(dependency.getCacheRepository()); task.setCaching(true); dependencies.add(task.withCounter("hmcl.modpack.download")); @@ -297,8 +297,8 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask { if (file instanceof McbbsModpackManifest.AddonFile) { McbbsModpackManifest.AddonFile addonFile = (McbbsModpackManifest.AddonFile) file; return new FileDownloadTask( - new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())), - modManager.getSimpleModPath(addonFile.getPath()).toFile(), + URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())), + modManager.getSimpleModPath(addonFile.getPath()), addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck("SHA-1", addonFile.getHash()) : null); } else if (file instanceof McbbsModpackManifest.CurseFile) { // we download it later. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackManifest.java index 7e5984f10..7deffb716 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackManifest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/mcbbs/McbbsModpackManifest.java @@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.charset.Charset; import java.util.Collections; import java.util.List; @@ -329,9 +329,9 @@ public class McbbsModpackManifest implements ModpackManifest, Validation { return fileName; } - public URL getUrl() { - return url == null ? NetworkUtils.toURL("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file") - : NetworkUtils.toURL(NetworkUtils.encodeLocation(url)); + public URI getUrl() { + return url == null ? URI.create("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file") + : URI.create(NetworkUtils.encodeLocation(url)); } public CurseFile withFileName(String fileName) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java index 7999fdec6..cf05a9b79 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthCompletionTask.java @@ -121,7 +121,7 @@ public class ModrinthCompletionTask extends Task { if (modsDirectory.equals(filePath.getParent()) && this.modManager.hasSimpleMod(FileUtils.getName(filePath))) continue; - FileDownloadTask task = new FileDownloadTask(file.getDownloads(), filePath.toFile()); + var task = new FileDownloadTask(file.getDownloads(), filePath); task.setCacheRepository(dependency.getCacheRepository()); task.setCaching(true); dependencies.add(task.withCounter("hmcl.modpack.download")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthManifest.java index 19b3d19fc..ea717b206 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthManifest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthManifest.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.TolerableValidationException; import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; -import java.net.URL; +import java.net.URI; import java.util.List; import java.util.Map; import java.util.Objects; @@ -98,10 +98,10 @@ public class ModrinthManifest implements ModpackManifest, Validation { private final String path; private final Map hashes; private final Map env; - private final List downloads; + private final List downloads; private final int fileSize; - public File(String path, Map hashes, Map env, List downloads, int fileSize) { + public File(String path, Map hashes, Map env, List downloads, int fileSize) { this.path = path; this.hashes = hashes; this.env = env; @@ -121,7 +121,7 @@ public class ModrinthManifest implements ModpackManifest, Validation { return env; } - public List getDownloads() { + public List getDownloads() { return downloads; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java index dafd01dd7..39fca4719 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/multimc/MultiMCComponents.java @@ -1,9 +1,8 @@ package org.jackhuang.hmcl.mod.multimc; import org.jackhuang.hmcl.download.LibraryAnalyzer; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import java.net.URL; +import java.net.URI; import java.util.*; import java.util.stream.Collectors; @@ -46,7 +45,7 @@ public final class MultiMCComponents { return PAIRS; } - public static URL getMetaURL(String componentID, String version) { - return NetworkUtils.toURL(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version)); + public static URI getMetaURL(String componentID, String version) { + return URI.create(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java index 6cbb19c67..7c26d2b2c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/server/ServerModpackCompletionTask.java @@ -33,7 +33,7 @@ import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.File; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -85,7 +85,7 @@ public class ServerModpackCompletionTask extends Task { @Override public void preExecute() throws Exception { if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; - dependent = new GetTask(new URL(manifest.getManifest().getFileApi() + "/server-manifest.json")); + dependent = new GetTask(URI.create(manifest.getManifest().getFileApi() + "/server-manifest.json")); } @Override @@ -153,8 +153,8 @@ public class ServerModpackCompletionTask extends Task { if (download) { total++; dependencies.add(new FileDownloadTask( - new URL(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())), - actualPath.toFile(), + URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())), + actualPath, new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash())) .withCounter("hmcl.modpack.download")); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/DownloadException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/DownloadException.java index 55d7b1cc7..dab0e48cd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/DownloadException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/DownloadException.java @@ -20,21 +20,20 @@ package org.jackhuang.hmcl.task; import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.net.URL; +import java.net.URI; import static java.util.Objects.requireNonNull; public class DownloadException extends IOException { - private final URL url; + private final URI uri; - public DownloadException(URL url, @NotNull Throwable cause) { - super("Unable to download " + url + ", " + cause.getMessage(), requireNonNull(cause)); - - this.url = url; + public DownloadException(URI uri, @NotNull Throwable cause) { + super("Unable to download " + uri + ", " + cause.getMessage(), requireNonNull(cause)); + this.uri = uri; } - public URL getUrl() { - return url; + public URI getUri() { + return uri; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java index a8c95a4cc..b3a0ff1e8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FetchTask.java @@ -24,36 +24,38 @@ import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.io.IOUtils; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.io.ResponseCodeException; +import org.jetbrains.annotations.NotNull; import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; import java.net.URLConnection; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import static org.jackhuang.hmcl.util.Lang.threadPool; import static org.jackhuang.hmcl.util.logging.Logger.LOG; public abstract class FetchTask extends Task { - protected final List urls; + protected final List uris; protected final int retry; protected boolean caching; protected CacheRepository repository = CacheRepository.getInstance(); - public FetchTask(List urls, int retry) { - Objects.requireNonNull(urls); + public FetchTask(@NotNull List<@NotNull URI> uris, int retry) { + Objects.requireNonNull(uris); - this.urls = urls.stream().filter(Objects::nonNull).collect(Collectors.toList()); + this.uris = List.copyOf(uris); this.retry = retry; - if (this.urls.isEmpty()) + if (this.uris.isEmpty()) throw new IllegalArgumentException("At least one URL is required"); setExecutor(download()); @@ -67,18 +69,19 @@ public abstract class FetchTask extends Task { this.repository = repository; } - protected void beforeDownload(URL url) throws IOException {} + protected void beforeDownload(URI uri) throws IOException { + } protected abstract void useCachedResult(Path cachedFile) throws IOException; protected abstract EnumCheckETag shouldCheckETag(); - protected abstract Context getContext(URLConnection conn, boolean checkETag) throws IOException; + protected abstract Context getContext(URLConnection connection, boolean checkETag) throws IOException; @Override public void execute() throws Exception { Exception exception = null; - URL failedURL = null; + URI failedURI = null; boolean checkETag; switch (shouldCheckETag()) { case CHECK_E_TAG: checkETag = true; break; @@ -87,7 +90,7 @@ public abstract class FetchTask extends Task { } int repeat = 0; - download: for (URL url : urls) { + download: for (URI uri : uris) { for (int retryTime = 0; retryTime < retry; retryTime++) { if (isCancelled()) { break download; @@ -95,11 +98,11 @@ public abstract class FetchTask extends Task { List redirects = null; try { - beforeDownload(url); + beforeDownload(uri); updateProgress(0); - URLConnection conn = NetworkUtils.createConnection(url); + URLConnection conn = NetworkUtils.createConnection(uri); if (checkETag) repository.injectConnection(conn); if (conn instanceof HttpURLConnection) { @@ -111,21 +114,21 @@ public abstract class FetchTask extends Task { if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) { // Handle cache try { - Path cache = repository.getCachedRemoteFile(conn); + Path cache = repository.getCachedRemoteFile(conn.getURL().toURI()); useCachedResult(cache); return; } catch (IOException e) { - LOG.warning("Unable to use cached file, redownload " + url, e); - repository.removeRemoteEntry(conn); + LOG.warning("Unable to use cached file, redownload " + uri, e); + repository.removeRemoteEntry(conn.getURL().toURI()); // Now we must reconnect the server since 304 may result in empty content, // if we want to redownload the file, we must reconnect the server without etag settings. retryTime--; continue; } } else if (responseCode / 100 == 4) { - throw new FileNotFoundException(url.toString()); + throw new FileNotFoundException(uri.toString()); } else if (responseCode / 100 != 2) { - throw new ResponseCodeException(url, responseCode); + throw new ResponseCodeException(uri, responseCode); } } @@ -164,21 +167,21 @@ public abstract class FetchTask extends Task { return; } catch (FileNotFoundException ex) { - failedURL = url; + failedURI = uri; exception = ex; - LOG.warning("Failed to download " + url + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex); + LOG.warning("Failed to download " + uri + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex); break; // we will not try this URL again } catch (IOException ex) { - failedURL = url; + failedURI = uri; exception = ex; - LOG.warning("Failed to download " + url + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex); + LOG.warning("Failed to download " + uri + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex); } } } if (exception != null) - throw new DownloadException(failedURL, exception); + throw new DownloadException(failedURI, exception); } private static final Timer timer = new Timer("DownloadSpeedRecorder", true); @@ -209,6 +212,7 @@ public abstract class FetchTask extends Task { /** * Download speed in byte/sec. + * * @return download speed */ public int getSpeed() { @@ -240,7 +244,7 @@ public abstract class FetchTask extends Task { NOT_CHECK_E_TAG, CACHED } - + protected static final class DownloadState { private final int startPosition; private final int endPosition; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index 622daf110..ee6224685 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -22,16 +22,20 @@ import org.jackhuang.hmcl.util.io.ChecksumMismatchException; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import java.io.File; import java.io.IOException; -import java.io.RandomAccessFile; -import java.net.URL; +import java.io.OutputStream; +import java.net.URI; import java.net.URLConnection; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.security.MessageDigest; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; import static java.util.Objects.requireNonNull; import static org.jackhuang.hmcl.util.DigestUtils.getDigest; @@ -78,73 +82,76 @@ public class FileDownloadTask extends FetchTask { } } - private final File file; + private final Path file; private final IntegrityCheck integrityCheck; private Path candidate; private final ArrayList integrityCheckHandlers = new ArrayList<>(); /** - * @param url the URL of remote file. - * @param file the location that download to. + * @param uri the URI of remote file. + * @param path the location that download to. */ - public FileDownloadTask(URL url, File file) { - this(url, file, null); + public FileDownloadTask(URI uri, Path path) { + this(uri, path, null); } /** - * @param url the URL of remote file. - * @param file the location that download to. + * @param uri the URI of remote file. + * @param path the location that download to. * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed */ - public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) { - this(Collections.singletonList(url), file, integrityCheck); + public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck) { + this(List.of(uri), path, integrityCheck); } /** - * @param url the URL of remote file. - * @param file the location that download to. + * @param uri the URI of remote file. + * @param path the location that download to. * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed - * @param retry the times for retrying if downloading fails. + * @param retry the times for retrying if downloading fails. */ - public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) { - this(Collections.singletonList(url), file, integrityCheck, retry); + public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck, int retry) { + this(List.of(uri), path, integrityCheck, retry); } /** * Constructor. - * @param urls urls of remote file, will be attempted in order. + * + * @param uris uris of remote file, will be attempted in order. * @param file the location that download to. */ - public FileDownloadTask(List urls, File file) { - this(urls, file, null); + public FileDownloadTask(List uris, Path file) { + this(uris, file, null); } /** * Constructor. - * @param urls urls of remote file, will be attempted in order. - * @param file the location that download to. + * + * @param uris uris of remote file, will be attempted in order. + * @param path the location that download to. * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed */ - public FileDownloadTask(List urls, File file, IntegrityCheck integrityCheck) { - this(urls, file, integrityCheck, 3); + public FileDownloadTask(List uris, Path path, IntegrityCheck integrityCheck) { + this(uris, path, integrityCheck, 3); } /** * Constructor. - * @param urls urls of remote file, will be attempted in order. - * @param file the location that download to. + * + * @param uris uris of remote file, will be attempted in order. + * @param path the location that download to. * @param integrityCheck the integrity check to perform, null if no integrity check is to be performed - * @param retry the times for retrying if downloading fails. + * @param retry the times for retrying if downloading fails. */ - public FileDownloadTask(List urls, File file, IntegrityCheck integrityCheck, int retry) { - super(urls, retry); - this.file = file; + public FileDownloadTask(List uris, Path path, IntegrityCheck integrityCheck, int retry) { + super(uris, retry); + this.file = path; this.integrityCheck = integrityCheck; - setName(file.getName()); + setName(path.getFileName().toString()); } - public File getFile() { + public Path getPath() { return file; } @@ -164,8 +171,8 @@ public class FileDownloadTask extends FetchTask { Optional cache = repository.checkExistentFile(candidate, integrityCheck.getAlgorithm(), integrityCheck.getChecksum()); if (cache.isPresent()) { try { - FileUtils.copyFile(cache.get().toFile(), file); - LOG.trace("Successfully verified file " + file + " from " + urls.get(0)); + FileUtils.copyFile(cache.get(), file); + LOG.trace("Successfully verified file " + file + " from " + uris.get(0)); return EnumCheckETag.CACHED; } catch (IOException e) { LOG.warning("Failed to copy cache files", e); @@ -178,20 +185,20 @@ public class FileDownloadTask extends FetchTask { } @Override - protected void beforeDownload(URL url) { - LOG.trace("Downloading " + url + " to " + file); + protected void beforeDownload(URI uri) { + LOG.trace("Downloading " + uri + " to " + file); } @Override protected void useCachedResult(Path cache) throws IOException { - FileUtils.copyFile(cache.toFile(), file); + FileUtils.copyFile(cache, file); } @Override - protected Context getContext(URLConnection conn, boolean checkETag) throws IOException { + protected Context getContext(URLConnection connection, boolean checkETag) throws IOException { Path temp = Files.createTempFile(null, null); - RandomAccessFile rFile = new RandomAccessFile(temp.toFile(), "rw"); MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest(); + OutputStream fileOutput = Files.newOutputStream(temp); return new Context() { @Override @@ -200,36 +207,34 @@ public class FileDownloadTask extends FetchTask { digest.update(buffer, offset, len); } - rFile.write(buffer, offset, len); + fileOutput.write(buffer, offset, len); } @Override public void close() throws IOException { try { - rFile.close(); + fileOutput.close(); } catch (IOException e) { - LOG.warning("Failed to close file: " + rFile, e); + LOG.warning("Failed to close file: " + temp, e); } if (!isSuccess()) { try { - Files.delete(temp); + Files.deleteIfExists(temp); } catch (IOException e) { - LOG.warning("Failed to delete file: " + rFile, e); + LOG.warning("Failed to delete file: " + temp, e); } return; } for (IntegrityCheckHandler handler : integrityCheckHandlers) { - handler.checkIntegrity(temp, file.toPath()); + handler.checkIntegrity(temp, file); } - Files.deleteIfExists(file.toPath()); - if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile())) - throw new IOException("Unable to make parent directory " + file); + Files.createDirectories(file.toAbsolutePath().getParent()); try { - FileUtils.moveFile(temp.toFile(), file); + Files.move(temp, file, StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { throw new IOException("Unable to move temp file from " + temp + " to " + file, e); } @@ -241,14 +246,14 @@ public class FileDownloadTask extends FetchTask { if (caching && integrityCheck != null) { try { - repository.cacheFile(file.toPath(), integrityCheck.getAlgorithm(), integrityCheck.getChecksum()); + repository.cacheFile(file, integrityCheck.getAlgorithm(), integrityCheck.getChecksum()); } catch (IOException e) { LOG.warning("Failed to cache file", e); } } if (checkETag) { - repository.cacheRemoteFile(file.toPath(), conn); + repository.cacheRemoteFile(connection, file); } } }; @@ -257,7 +262,8 @@ public class FileDownloadTask extends FetchTask { public interface IntegrityCheckHandler { /** * Check whether the file is corrupted or not. - * @param filePath the file locates in (maybe in temp directory) + * + * @param filePath the file locates in (maybe in temp directory) * @param destinationPath for real file name * @throws IOException if the file is corrupted */ diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java index 524a4807f..d5505420f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/GetTask.java @@ -19,12 +19,12 @@ package org.jackhuang.hmcl.task; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.net.URL; +import java.net.URI; import java.net.URLConnection; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.List; import static java.nio.charset.StandardCharsets.UTF_8; @@ -34,25 +34,27 @@ import static java.nio.charset.StandardCharsets.UTF_8; */ public final class GetTask extends FetchTask { + private static final int DEFAULT_RETRY = 3; + private final Charset charset; - public GetTask(URL url) { + public GetTask(URI url) { this(url, UTF_8); } - public GetTask(URL url, Charset charset) { - this(url, charset, 3); + public GetTask(URI url, Charset charset) { + this(url, charset, DEFAULT_RETRY); } - public GetTask(URL url, Charset charset, int retry) { - this(Collections.singletonList(url), charset, retry); + public GetTask(URI url, Charset charset, int retry) { + this(List.of(url), charset, retry); } - public GetTask(List url) { - this(url, UTF_8, 3); + public GetTask(List url) { + this(url, UTF_8, DEFAULT_RETRY); } - public GetTask(List urls, Charset charset, int retry) { + public GetTask(List urls, Charset charset, int retry) { super(urls, retry); this.charset = charset; @@ -70,7 +72,7 @@ public final class GetTask extends FetchTask { } @Override - protected Context getContext(URLConnection conn, boolean checkETag) { + protected Context getContext(URLConnection connection, boolean checkETag) { return new Context() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -87,7 +89,7 @@ public final class GetTask extends FetchTask { setResult(result); if (checkETag) { - repository.cacheText(result, conn); + repository.cacheText(connection, result); } } }; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java index 2fe0b9878..75a1fad2c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java @@ -24,6 +24,7 @@ import org.jackhuang.hmcl.util.io.FileUtils; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URI; import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.channels.Channels; @@ -157,12 +158,11 @@ public class CacheRepository { return cache; } - public Path getCachedRemoteFile(URLConnection conn) throws IOException { - String url = conn.getURL().toString(); + public Path getCachedRemoteFile(URI uri) throws IOException { lock.readLock().lock(); ETagItem eTagItem; try { - eTagItem = index.get(url); + eTagItem = index.get(uri.toString()); } finally { lock.readLock().unlock(); } @@ -177,11 +177,10 @@ public class CacheRepository { return file; } - public void removeRemoteEntry(URLConnection conn) { - String url = conn.getURL().toString(); + public void removeRemoteEntry(URI uri) { lock.readLock().lock(); try { - index.remove(url); + index.remove(uri.toString()); } finally { lock.readLock().unlock(); } @@ -203,34 +202,34 @@ public class CacheRepository { // conn.setRequestProperty("If-Modified-Since", eTagItem.getRemoteLastModified()); } - public void cacheRemoteFile(Path downloaded, URLConnection conn) throws IOException { - cacheData(() -> { + public void cacheRemoteFile(URLConnection connection, Path downloaded) throws IOException { + cacheData(connection, () -> { String hash = DigestUtils.digestToString(SHA1, downloaded); Path cached = cacheFile(downloaded, SHA1, hash); return new CacheResult(hash, cached); - }, conn); + }); } - public void cacheText(String text, URLConnection conn) throws IOException { - cacheBytes(text.getBytes(UTF_8), conn); + public void cacheText(URLConnection connection, String text) throws IOException { + cacheBytes(connection, text.getBytes(UTF_8)); } - public void cacheBytes(byte[] bytes, URLConnection conn) throws IOException { - cacheData(() -> { + public void cacheBytes(URLConnection connection, byte[] bytes) throws IOException { + cacheData(connection, () -> { String hash = DigestUtils.digestToString(SHA1, bytes); Path cached = getFile(SHA1, hash); FileUtils.writeBytes(cached, bytes); return new CacheResult(hash, cached); - }, conn); + }); } - public synchronized void cacheData(ExceptionalSupplier cacheSupplier, URLConnection conn) throws IOException { - String eTag = conn.getHeaderField("ETag"); - if (eTag == null) return; - String url = conn.getURL().toString(); - String lastModified = conn.getHeaderField("Last-Modified"); + private void cacheData(URLConnection connection, ExceptionalSupplier cacheSupplier) throws IOException { + String eTag = connection.getHeaderField("ETag"); + if (eTag == null || eTag.isEmpty()) return; + String uri = connection.getURL().toString(); + String lastModified = connection.getHeaderField("Last-Modified"); CacheResult cacheResult = cacheSupplier.get(); - ETagItem eTagItem = new ETagItem(url, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified); + ETagItem eTagItem = new ETagItem(uri, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified); Lock writeLock = lock.writeLock(); writeLock.lock(); try { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ContentEncoding.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ContentEncoding.java new file mode 100644 index 000000000..f257b0065 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ContentEncoding.java @@ -0,0 +1,56 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and 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 org.jackhuang.hmcl.util.io; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLConnection; +import java.util.zip.GZIPInputStream; + +/** + * @author Glavo + */ +public enum ContentEncoding { + NONE { + @Override + public InputStream wrap(InputStream inputStream) { + return inputStream; + } + }, + GZIP { + @Override + public InputStream wrap(InputStream inputStream) throws IOException { + return new GZIPInputStream(inputStream); + } + }; + + public static @NotNull ContentEncoding fromConnection(URLConnection connection) throws IOException { + String encoding = connection.getContentEncoding(); + if (encoding == null || encoding.isEmpty()) { + return NONE; + } else if ("gzip".equalsIgnoreCase(encoding)) { + return GZIP; + } else { + throw new IOException("Unsupported content encoding: " + encoding); + } + } + + public abstract InputStream wrap(InputStream inputStream) throws IOException; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java index 4fb0fead5..3f8535858 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/HttpRequest.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.HashSet; @@ -113,18 +114,13 @@ public abstract class HttpRequest { return getStringAsync().thenApplyAsync(jsonString -> JsonUtils.fromNonNullJson(jsonString, type)); } - public HttpRequest filter(ExceptionalBiConsumer responseCodeTester) { - this.responseCodeTester = responseCodeTester; - return this; - } - public HttpRequest ignoreHttpErrorCode(int code) { toleratedHttpCodes.add(code); return this; } public HttpURLConnection createConnection() throws IOException { - HttpURLConnection con = createHttpConnection(new URL(url)); + HttpURLConnection con = createHttpConnection(URI.create(url)); con.setRequestMethod(method); for (Map.Entry entry : headers.entrySet()) { con.setRequestProperty(entry.getKey(), entry.getValue()); @@ -133,7 +129,7 @@ public abstract class HttpRequest { } public static class HttpGetRequest extends HttpRequest { - public HttpGetRequest(String url) { + protected HttpGetRequest(String url) { super(url, "GET"); } @@ -149,7 +145,7 @@ public abstract class HttpRequest { public static final class HttpPostRequest extends HttpRequest { private byte[] bytes; - public HttpPostRequest(String url) { + private HttpPostRequest(String url) { super(url, "POST"); } @@ -189,21 +185,17 @@ public abstract class HttpRequest { URL url = new URL(this.url); - if (responseCodeTester != null) { - responseCodeTester.accept(url, con.getResponseCode()); - } else { - if (con.getResponseCode() / 100 != 2) { - if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { - try { - throw new ResponseCodeException(url, con.getResponseCode(), NetworkUtils.readData(con)); - } catch (IOException e) { - throw new ResponseCodeException(url, con.getResponseCode(), e); - } + if (con.getResponseCode() / 100 != 2) { + if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) { + try { + throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), NetworkUtils.readFullyAsString(con)); + } catch (IOException e) { + throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), e); } } } - return NetworkUtils.readData(con); + return NetworkUtils.readFullyAsString(con); }, retryTimes); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java index 706dde773..d2c82bd19 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/NetworkUtils.java @@ -21,12 +21,16 @@ import org.jackhuang.hmcl.util.Pair; import java.io.*; import java.net.*; +import java.nio.charset.Charset; import java.util.*; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static java.nio.charset.StandardCharsets.UTF_8; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.StringUtils.*; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; /** * @author huangyuhui @@ -39,6 +43,10 @@ public final class NetworkUtils { private NetworkUtils() { } + public static boolean isHttpUri(URI uri) { + return "http".equals(uri.getScheme()) || "https".equals(uri.getScheme()); + } + public static String withQuery(String baseUrl, Map params) { StringBuilder sb = new StringBuilder(baseUrl); boolean first = true; @@ -73,7 +81,7 @@ public final class NetworkUtils { scanner.useDelimiter("&"); while (scanner.hasNext()) { String[] nameValue = scanner.next().split(NAME_VALUE_SEPARATOR); - if (nameValue.length <= 0 || nameValue.length > 2) { + if (nameValue.length == 0 || nameValue.length > 2) { throw new IllegalArgumentException("bad query string"); } @@ -85,8 +93,8 @@ public final class NetworkUtils { return result; } - public static URLConnection createConnection(URL url) throws IOException { - URLConnection connection = url.openConnection(); + public static URLConnection createConnection(URI uri) throws IOException { + URLConnection connection = uri.toURL().openConnection(); connection.setUseCaches(false); connection.setConnectTimeout(TIME_OUT); connection.setReadTimeout(TIME_OUT); @@ -94,15 +102,15 @@ public final class NetworkUtils { return connection; } - public static HttpURLConnection createHttpConnection(URL url) throws IOException { + public static HttpURLConnection createHttpConnection(URI url) throws IOException { return (HttpURLConnection) createConnection(url); } /** - * @see Curl * @param location the url to be URL encoded * @return encoded URL + * @see Curl */ public static String encodeLocation(String location) { StringBuilder sb = new StringBuilder(); @@ -138,10 +146,10 @@ public final class NetworkUtils { * This method is a work-around that aims to solve problem when "Location" in * stupid server's response is not encoded. * - * @see Issue with libcurl * @param conn the stupid http connection. * @return manually redirected http connection. * @throws IOException if an I/O error occurs. + * @see Issue with libcurl */ public static HttpURLConnection resolveConnection(HttpURLConnection conn, List redirects) throws IOException { int redirect = 0; @@ -153,7 +161,7 @@ public final class NetworkUtils { Map> properties = conn.getRequestProperties(); String method = conn.getRequestMethod(); int code = conn.getResponseCode(); - if (code >= 300 && code <= 307 && code != 306 && code != 304) { + if (code >= 300 && code <= 308 && code != 306 && code != 304) { String newURL = conn.getHeaderField("Location"); conn.disconnect(); @@ -178,19 +186,15 @@ public final class NetworkUtils { return conn; } - public static String doGet(URL url) throws IOException { - HttpURLConnection con = createHttpConnection(url); - con = resolveConnection(con); - return IOUtils.readFullyAsString(con.getInputStream()); + public static String doGet(URI uri) throws IOException { + return readFullyAsString(resolveConnection(createHttpConnection(uri))); } - public static String doGet(List urls) throws IOException { + public static String doGet(List uris) throws IOException { List exceptions = null; - for (URL url : urls) { + for (URI uri : uris) { try { - HttpURLConnection con = createHttpConnection(url); - con = resolveConnection(con); - return IOUtils.readFullyAsString(con.getInputStream()); + return doGet(uri); } catch (IOException e) { if (exceptions == null) { exceptions = new ArrayList<>(1); @@ -212,7 +216,11 @@ public final class NetworkUtils { } } - public static String doPost(URL u, Map params) throws IOException { + public static String doPost(URI uri, String post) throws IOException { + return doPost(uri, post, "application/x-www-form-urlencoded"); + } + + public static String doPost(URI u, Map params) throws IOException { StringBuilder sb = new StringBuilder(); if (params != null) { for (Map.Entry e : params.entrySet()) @@ -222,50 +230,72 @@ public final class NetworkUtils { return doPost(u, sb.toString()); } - public static String doPost(URL u, String post) throws IOException { - return doPost(u, post, "application/x-www-form-urlencoded"); - } - - public static String doPost(URL url, String post, String contentType) throws IOException { + public static String doPost(URI uri, String post, String contentType) throws IOException { byte[] bytes = post.getBytes(UTF_8); - HttpURLConnection con = createHttpConnection(url); + HttpURLConnection con = createHttpConnection(uri); con.setRequestMethod("POST"); con.setDoOutput(true); con.setRequestProperty("Content-Type", contentType + "; charset=utf-8"); - con.setRequestProperty("Content-Length", "" + bytes.length); + con.setRequestProperty("Content-Length", String.valueOf(bytes.length)); try (OutputStream os = con.getOutputStream()) { os.write(bytes); } - return readData(con); + return readFullyAsString(con); } - public static String readData(HttpURLConnection con) throws IOException { - try { - try (InputStream stdout = con.getInputStream()) { - return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stdout) : stdout); + static final Pattern CHARSET_REGEX = Pattern.compile("\\s*(charset)\\s*=\\s*['|\"]?(?[^\"^';,]+)['|\"]?"); + + static Charset getCharsetFromContentType(String contentType) { + if (contentType == null || contentType.isBlank()) + return UTF_8; + + Matcher matcher = CHARSET_REGEX.matcher(contentType); + if (matcher.find()) { + String charsetName = matcher.group("charset"); + try { + return Charset.forName(charsetName); + } catch (Throwable e) { + // Ignore invalid charset + LOG.warning("Bad charset name: " + charsetName + ", using UTF-8 instead", e); } - } catch (IOException e) { - try (InputStream stderr = con.getErrorStream()) { - if (stderr == null) + } + return UTF_8; + } + + public static String readFullyAsString(URLConnection con) throws IOException { + try { + var contentEncoding = ContentEncoding.fromConnection(con); + Charset charset = getCharsetFromContentType(con.getHeaderField("Content-Type")); + + try (InputStream stdout = con.getInputStream()) { + return IOUtils.readFullyAsString(contentEncoding.wrap(stdout), charset); + } catch (IOException e) { + if (con instanceof HttpURLConnection) { + try (InputStream stderr = ((HttpURLConnection) con).getErrorStream()) { + if (stderr == null) + throw e; + return IOUtils.readFullyAsString(contentEncoding.wrap(stderr), charset); + } + } else { throw e; - return IOUtils.readFullyAsString("gzip".equals(con.getContentEncoding()) ? IOUtils.wrapFromGZip(stderr) : stderr); + } + } + } finally { + if (con instanceof HttpURLConnection) { + ((HttpURLConnection) con).disconnect(); } } } - public static String detectFileName(URL url) throws IOException { - HttpURLConnection conn = resolveConnection(createHttpConnection(url)); + public static String detectFileName(URI uri) throws IOException { + HttpURLConnection conn = resolveConnection(createHttpConnection(uri)); int code = conn.getResponseCode(); if (code / 100 == 4) throw new FileNotFoundException(); if (code / 100 != 2) - throw new IOException(url + ": response code " + conn.getResponseCode()); + throw new ResponseCodeException(uri, conn.getResponseCode()); - return detectFileName(conn); - } - - public static String detectFileName(HttpURLConnection conn) { String disposition = conn.getHeaderField("Content-Disposition"); if (disposition == null || !disposition.contains("filename=")) { String u = conn.getURL().toString(); @@ -275,46 +305,22 @@ public final class NetworkUtils { } } - public static URL toURL(String str) { + public static URI toURI(URL url) { try { - return new URL(str); - } catch (MalformedURLException e) { + return url.toURI(); + } catch (URISyntaxException e) { throw new IllegalArgumentException(e); } } - public static boolean isURL(String str) { - try { - new URL(str); - return true; - } catch (MalformedURLException e) { - return false; - } - } - - public static boolean urlExists(URL url) throws IOException { - HttpURLConnection con = createHttpConnection(url); - con = resolveConnection(con); - int responseCode = con.getResponseCode(); - con.disconnect(); - return responseCode / 100 == 2; - } - // ==== Shortcut methods for encoding/decoding URLs in UTF-8 ==== public static String encodeURL(String toEncode) { - try { - return URLEncoder.encode(toEncode, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new Error(); - } + return URLEncoder.encode(toEncode, UTF_8); } public static String decodeURL(String toDecode) { - try { - return URLDecoder.decode(toDecode, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new Error(); - } + return URLDecoder.decode(toDecode, UTF_8); } // ==== + } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java index b38649764..5206c8cb8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/ResponseCodeException.java @@ -18,37 +18,37 @@ package org.jackhuang.hmcl.util.io; import java.io.IOException; -import java.net.URL; +import java.net.URI; public final class ResponseCodeException extends IOException { - private final URL url; + private final URI uri; private final int responseCode; private final String data; - public ResponseCodeException(URL url, int responseCode) { - super("Unable to request url " + url + ", response code: " + responseCode); - this.url = url; + public ResponseCodeException(URI uri, int responseCode) { + super("Unable to request url " + uri + ", response code: " + responseCode); + this.uri = uri; this.responseCode = responseCode; this.data = null; } - public ResponseCodeException(URL url, int responseCode, Throwable cause) { - super("Unable to request url " + url + ", response code: " + responseCode, cause); - this.url = url; + public ResponseCodeException(URI uri, int responseCode, Throwable cause) { + super("Unable to request url " + uri + ", response code: " + responseCode, cause); + this.uri = uri; this.responseCode = responseCode; this.data = null; } - public ResponseCodeException(URL url, int responseCode, String data) { - super("Unable to request url " + url + ", response code: " + responseCode + ", data: " + data); - this.url = url; + public ResponseCodeException(URI uri, int responseCode, String data) { + super("Unable to request url " + uri + ", response code: " + responseCode + ", data: " + data); + this.uri = uri; this.responseCode = responseCode; this.data = data; } - public URL getUrl() { - return url; + public URI getUri() { + return uri; } public int getResponseCode() { diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/NetworkUtilsTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/NetworkUtilsTest.java new file mode 100644 index 000000000..0423ff59c --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/io/NetworkUtilsTest.java @@ -0,0 +1,38 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and 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 org.jackhuang.hmcl.util.io; + +import org.junit.jupiter.api.Test; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Glavo + */ +public class NetworkUtilsTest { + @Test + public void testGetEncodingFromUrl() { + assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType(null)); + assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType("")); + assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType("text/html")); + assertEquals(UTF_8, NetworkUtils.getCharsetFromContentType("text/html; charset=utf-8")); + assertEquals(US_ASCII, NetworkUtils.getCharsetFromContentType("text/html; charset=ascii")); + } +}