统一转义 URI 中的特殊字符 (#4181)
This commit is contained in:
@@ -267,7 +267,7 @@ public final class LauncherHelper {
|
||||
if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URI uri = rce.getUri();
|
||||
String uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
@@ -298,7 +298,7 @@ public final class LauncherHelper {
|
||||
} else if (ex instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex;
|
||||
int responseCode = rce.getResponseCode();
|
||||
URI uri = rce.getUri();
|
||||
String uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message = i18n("download.code.404", uri);
|
||||
else
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -110,7 +109,7 @@ public final class TexturesLoader {
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
try {
|
||||
new FileDownloadTask(URI.create(texture.getUrl()), file).run();
|
||||
new FileDownloadTask(texture.getUrl(), file).run();
|
||||
LOG.info("Texture downloaded: " + texture.getUrl());
|
||||
} catch (Exception e) {
|
||||
if (Files.isRegularFile(file)) {
|
||||
|
||||
@@ -836,7 +836,8 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Image loadImage(URI uri) throws Exception {
|
||||
public static Image loadImage(String url) throws Exception {
|
||||
URI uri = NetworkUtils.toURI(url);
|
||||
URLConnection connection = NetworkUtils.createConnection(uri);
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
@@ -896,7 +897,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static Task<Image> getRemoteImageTask(String url, double requestedWidth, double requestedHeight, boolean preserveRatio, boolean smooth) {
|
||||
return new CacheFileTask(URI.create(url))
|
||||
return new CacheFileTask(url)
|
||||
.thenApplyAsync(file -> {
|
||||
try (var channel = FileChannel.open(file, StandardOpenOption.READ)) {
|
||||
var header = new byte[12];
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
|
||||
@@ -159,18 +160,9 @@ public final class HTMLRenderer {
|
||||
|
||||
private void appendImage(Node node) {
|
||||
String src = node.absUrl("src");
|
||||
URI imageUri = null;
|
||||
try {
|
||||
if (!src.isEmpty())
|
||||
imageUri = URI.create(src);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
String alt = node.attr("alt");
|
||||
|
||||
if (imageUri != null) {
|
||||
URI uri = URI.create(src);
|
||||
|
||||
if (StringUtils.isNotBlank(src)) {
|
||||
String widthAttr = node.attr("width");
|
||||
String heightAttr = node.attr("height");
|
||||
|
||||
@@ -191,7 +183,7 @@ public final class HTMLRenderer {
|
||||
}
|
||||
|
||||
try {
|
||||
Image image = FXUtils.getRemoteImageTask(uri.toString(), width, height, true, true)
|
||||
Image image = FXUtils.getRemoteImageTask(src, width, height, true, true)
|
||||
.run();
|
||||
if (image == null)
|
||||
throw new AssertionError("Image loading task returned null");
|
||||
@@ -207,7 +199,7 @@ public final class HTMLRenderer {
|
||||
children.add(imageView);
|
||||
return;
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load image: " + uri, e);
|
||||
LOG.warning("Failed to load image: " + src, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import org.jackhuang.hmcl.util.platform.NativeUtils;
|
||||
@@ -48,7 +49,6 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
@@ -190,7 +190,7 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
item.titleProperty().bind(title);
|
||||
String host = "";
|
||||
try {
|
||||
host = URI.create(server.getUrl()).getHost();
|
||||
host = NetworkUtils.toURI(server.getUrl()).getHost();
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warning("Unparsable authlib-injector server url " + server.getUrl(), e);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
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 +195,8 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(URI.create(serverBeingAdded.getUrl()).getScheme()));
|
||||
//noinspection HttpUrlsUsage
|
||||
lblServerWarning.setVisible(serverBeingAdded.getUrl().startsWith("http://"));
|
||||
|
||||
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT);
|
||||
} else {
|
||||
|
||||
@@ -21,10 +21,7 @@ import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -58,9 +55,9 @@ public class URLValidator extends ValidatorBase {
|
||||
hasErrors.set(!nullable);
|
||||
else {
|
||||
try {
|
||||
new URL(textField.getText()).toURI();
|
||||
NetworkUtils.toURI(textField.getText());
|
||||
hasErrors.set(false);
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
hasErrors.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -214,7 +213,7 @@ public class DecoratorController {
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
image = FXUtils.loadImage(URI.create(backgroundImageUrl));
|
||||
image = FXUtils.loadImage(backgroundImageUrl);
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Couldn't load background image", e);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
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 +155,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
|
||||
|
||||
Controllers.taskDialog(Task.composeAsync(() -> {
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest);
|
||||
var task = new FileDownloadTask(file.getFile().getUrl(), dest);
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
|
||||
@@ -41,7 +41,6 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
@@ -124,10 +123,9 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
}
|
||||
|
||||
private void onChooseRemoteFile() {
|
||||
Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (urlString, resolve, reject) -> {
|
||||
Controllers.prompt(i18n("modpack.choose.remote.tooltip"), (url, resolve, reject) -> {
|
||||
try {
|
||||
URI url = URI.create(urlString);
|
||||
if (urlString.endsWith("server-manifest.json")) {
|
||||
if (url.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) -> {
|
||||
ServerModpackManifest manifest = JsonUtils.fromMaybeMalformedJson(result, ServerModpackManifest.class);
|
||||
@@ -148,7 +146,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
resolve.run();
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(url, modpack, null)
|
||||
new FileDownloadTask(url, modpack)
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
resolve.run();
|
||||
|
||||
@@ -133,7 +133,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) exception.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URI uri = rce.getUri();
|
||||
String uri = rce.getUri();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", uri);
|
||||
else
|
||||
|
||||
@@ -54,7 +54,6 @@ 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 +179,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
Controllers.taskDialog(
|
||||
Task.composeAsync(() -> {
|
||||
var task = new FileDownloadTask(URI.create(file.getFile().getUrl()), dest.toPath(), file.getFile().getIntegrityCheck());
|
||||
var task = new FileDownloadTask(file.getFile().getUrl(), dest.toPath(), file.getFile().getIntegrityCheck());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}),
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CSVTable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -294,7 +293,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
fileName += ModManager.DISABLED_EXTENSION;
|
||||
|
||||
var task = new FileDownloadTask(
|
||||
URI.create(remote.getFile().getUrl()),
|
||||
remote.getFile().getUrl(),
|
||||
modManager.getModsDirectory().resolve(fileName));
|
||||
|
||||
task.setName(remote.getName());
|
||||
|
||||
@@ -32,9 +32,6 @@ import javafx.scene.control.Toggle;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ProcessPriority;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.java.JavaManager;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
|
||||
@@ -40,12 +40,12 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -75,9 +75,9 @@ public final class Versions {
|
||||
Path modpack;
|
||||
URI downloadURL;
|
||||
try {
|
||||
downloadURL = NetworkUtils.toURI(file.getFile().getUrl());
|
||||
modpack = Files.createTempFile("modpack", ".zip");
|
||||
downloadURL = new URI(file.getFile().getUrl());
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Controllers.dialog(
|
||||
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
|
||||
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
|
||||
|
||||
@@ -23,7 +23,6 @@ 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;
|
||||
@@ -33,7 +32,7 @@ final class HMCLDownloadTask extends FileDownloadTask {
|
||||
private final RemoteVersion.Type archiveFormat;
|
||||
|
||||
public HMCLDownloadTask(RemoteVersion version, Path target) {
|
||||
super(URI.create(version.getUrl()), target, version.getIntegrityCheck());
|
||||
super(version.getUrl(), target, version.getIntegrityCheck());
|
||||
archiveFormat = version.getType();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,13 @@ 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(URI.create(url)), JsonObject.class);
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(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);
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.net.URISyntaxException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
@@ -58,10 +59,10 @@ public class AuthlibInjectorServer implements Observable {
|
||||
public static AuthlibInjectorServer locateServer(String url) throws IOException {
|
||||
try {
|
||||
url = addHttpsIfMissing(url);
|
||||
HttpURLConnection conn = NetworkUtils.createHttpConnection(URI.create(url));
|
||||
HttpURLConnection conn = NetworkUtils.createHttpConnection(url);
|
||||
String ali = conn.getHeaderField("x-authlib-injector-api-location");
|
||||
if (ali != null) {
|
||||
URI absoluteAli = conn.getURL().toURI().resolve(ali);
|
||||
URI absoluteAli = conn.getURL().toURI().resolve(NetworkUtils.toURI(ali));
|
||||
if (!urlEqualsIgnoreSlash(url, absoluteAli.toString())) {
|
||||
conn.disconnect();
|
||||
url = absoluteAli.toString();
|
||||
@@ -85,15 +86,15 @@ public class AuthlibInjectorServer implements Observable {
|
||||
}
|
||||
|
||||
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 {
|
||||
if (Pattern.compile("^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]*)://").matcher(url).find())
|
||||
return url;
|
||||
}
|
||||
|
||||
if (url.startsWith("//"))
|
||||
return "https:" + url;
|
||||
else if (url.startsWith("/"))
|
||||
return "https:/" + url;
|
||||
else
|
||||
return "https://" + url;
|
||||
}
|
||||
|
||||
private static boolean urlEqualsIgnoreSlash(String a, String b) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -165,7 +164,7 @@ public class MicrosoftService {
|
||||
.accept("application/json").createConnection();
|
||||
|
||||
if (request.getResponseCode() != 200) {
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/entitlements/mcstore"), request.getResponseCode());
|
||||
throw new ResponseCodeException("https://api.minecraftservices.com/entitlements/mcstore", request.getResponseCode());
|
||||
}
|
||||
|
||||
// Get Minecraft Account UUID
|
||||
@@ -248,7 +247,7 @@ public class MicrosoftService {
|
||||
if (responseCode == HTTP_NOT_FOUND) {
|
||||
throw new NoMinecraftJavaEditionProfileException();
|
||||
} else if (responseCode != 200) {
|
||||
throw new ResponseCodeException(URI.create("https://api.minecraftservices.com/minecraft/profile"), responseCode);
|
||||
throw new ResponseCodeException("https://api.minecraftservices.com/minecraft/profile", responseCode);
|
||||
}
|
||||
|
||||
String result = NetworkUtils.readFullyAsString(conn);
|
||||
@@ -258,12 +257,12 @@ public class MicrosoftService {
|
||||
public Optional<CompleteGameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
return Optional.ofNullable(GSON.fromJson(request(URI.create("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
return Optional.ofNullable(GSON.fromJson(request("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(URI.create("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection("https://api.minecraftservices.com/minecraft/profile/skins");
|
||||
con.setRequestMethod("POST");
|
||||
con.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
con.setDoOutput(true);
|
||||
@@ -288,12 +287,12 @@ public class MicrosoftService {
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URI url, Object payload) throws AuthenticationException {
|
||||
private static String request(String url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
return NetworkUtils.doPost(NetworkUtils.toURI(url), payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -166,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(URI.create(String.format("%s/%s.json", realCslApi, username))))
|
||||
return Task.composeAsync(() -> new GetTask(String.format("%s/%s.json", realCslApi, username)))
|
||||
.thenComposeAsync(json -> {
|
||||
SkinJson result = JsonUtils.GSON.fromJson(json, SkinJson.class);
|
||||
|
||||
@@ -176,8 +176,8 @@ public class Skin {
|
||||
|
||||
return Task.allOf(
|
||||
Task.supplyAsync(result::getModel),
|
||||
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)
|
||||
result.getHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(String.format("%s/textures/%s", realCslApi, result.getHash())),
|
||||
result.getCapeHash() == null ? Task.supplyAsync(() -> null) : new FetchBytesTask(String.format("%s/textures/%s", realCslApi, result.getCapeHash()))
|
||||
);
|
||||
}).thenApplyAsync(result -> {
|
||||
if (result == null) {
|
||||
@@ -229,8 +229,8 @@ public class Skin {
|
||||
|
||||
private static class FetchBytesTask extends FetchTask<InputStream> {
|
||||
|
||||
public FetchBytesTask(URI uri, int retry) {
|
||||
super(List.of(uri), retry);
|
||||
public FetchBytesTask(String uri) {
|
||||
super(List.of(NetworkUtils.toURI(uri)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -33,7 +35,7 @@ public interface DownloadProvider {
|
||||
String getAssetBaseURL();
|
||||
|
||||
default List<URI> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return List.of(URI.create(getAssetBaseURL() + assetObjectLocation));
|
||||
return List.of(NetworkUtils.toURI(getAssetBaseURL() + assetObjectLocation));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +59,7 @@ public interface DownloadProvider {
|
||||
* @return the URL that is equivalent to [baseURL], but belongs to your own service provider.
|
||||
*/
|
||||
default List<URI> injectURLWithCandidates(String baseURL) {
|
||||
return List.of(URI.create(injectURL(baseURL)));
|
||||
return List.of(NetworkUtils.toURI(injectURL(baseURL)));
|
||||
}
|
||||
|
||||
default List<URI> injectURLsWithCandidates(List<String> urls) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,7 +58,7 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
remote.getVersion().getFile().getUrl(),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
@@ -86,7 +85,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
String lookupVersion = toLookupVersion(gameVersion);
|
||||
|
||||
return new GetTask(URI.create(apiRoot + "/forge/minecraft/" + lookupVersion)).thenGetJsonAsync(listTypeOf(ForgeVersion.class))
|
||||
return new GetTask(apiRoot + "/forge/minecraft/" + lookupVersion).thenGetJsonAsync(listTypeOf(ForgeVersion.class))
|
||||
.thenAcceptAsync(forgeVersions -> {
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -54,7 +53,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return new GetTask(URI.create(downloadProvider.getVersionListURL())).thenGetJsonAsync(GameRemoteVersions.class)
|
||||
return new GetTask(downloadProvider.getVersionListURL()).thenGetJsonAsync(GameRemoteVersions.class)
|
||||
.thenAcceptAsync(root -> {
|
||||
GameRemoteVersions unlistedVersions = null;
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.jsoup.nodes.Document;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -53,7 +52,7 @@ public final class LiteLoaderVersionList extends VersionList<LiteLoaderRemoteVer
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return new GetTask(URI.create(downloadProvider.injectURL(LITELOADER_LIST)))
|
||||
return new GetTask(downloadProvider.injectURL(LITELOADER_LIST))
|
||||
.thenGetJsonAsync(LiteLoaderVersionsRoot.class)
|
||||
.thenAcceptAsync(root -> {
|
||||
LiteLoaderGameVersions versions = root.getVersions().get(gameVersion);
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -66,7 +65,7 @@ public final class NeoForgeBMCLVersionList extends VersionList<NeoForgeRemoteVer
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync(String gameVersion) {
|
||||
return new GetTask(URI.create(apiRoot + "/neoforge/list/" + gameVersion)).thenGetJsonAsync(listTypeOf(NeoForgeVersion.class))
|
||||
return new GetTask(apiRoot + "/neoforge/list/" + gameVersion).thenGetJsonAsync(listTypeOf(NeoForgeVersion.class))
|
||||
.thenAcceptAsync(neoForgeVersions -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -39,8 +38,8 @@ public final class NeoForgeOfficialVersionList extends VersionList<NeoForgeRemot
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return Task.allOf(
|
||||
new GetTask(URI.create(downloadProvider.injectURL(OLD_URL))).thenGetJsonAsync(OfficialAPIResult.class),
|
||||
new GetTask(URI.create(downloadProvider.injectURL(META_URL))).thenGetJsonAsync(OfficialAPIResult.class)
|
||||
new GetTask(downloadProvider.injectURL(OLD_URL)).thenGetJsonAsync(OfficialAPIResult.class),
|
||||
new GetTask(downloadProvider.injectURL(META_URL)).thenGetJsonAsync(OfficialAPIResult.class)
|
||||
).thenAcceptAsync(results -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -73,7 +72,7 @@ public final class OptiFineBMCLVersionList extends VersionList<OptiFineRemoteVer
|
||||
|
||||
@Override
|
||||
public Task<?> refreshAsync() {
|
||||
return new GetTask(URI.create(apiRoot + "/optifine/versionlist")).thenGetJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||
return new GetTask(apiRoot + "/optifine/versionlist").thenGetJsonAsync(listTypeOf(OptiFineVersion.class)).thenAcceptAsync(root -> {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
|
||||
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -59,7 +58,7 @@ public final class QuiltAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
URI.create(remote.getVersion().getFile().getUrl()),
|
||||
remote.getVersion().getFile().getUrl(),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
|
||||
@@ -28,9 +28,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.encodeLocation;
|
||||
|
||||
public class RemoteMod {
|
||||
public final class RemoteMod {
|
||||
|
||||
public static final RemoteMod BROKEN = new RemoteMod("", "", "RemoteMod.BROKEN", "", Collections.emptyList(), "", "", new RemoteMod.IMod() {
|
||||
@Override
|
||||
@@ -313,7 +311,7 @@ public class RemoteMod {
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return encodeLocation(url);
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
|
||||
@@ -21,10 +21,8 @@ import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -84,15 +82,13 @@ public final class CurseManifestFile implements Validation {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public URI getUrl() {
|
||||
public String getUrl() {
|
||||
if (url == null) {
|
||||
if (fileName != null) {
|
||||
return URI.create(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return fileName != null
|
||||
? String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)
|
||||
: null;
|
||||
} else {
|
||||
return URI.create(NetworkUtils.encodeLocation(url));
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -101,7 +100,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
throw new CustomException();
|
||||
}
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
return executor.one(new GetTask(URI.create(manifest.getFileApi() + "/manifest.json")));
|
||||
return executor.one(new GetTask(manifest.getFileApi() + "/manifest.json"));
|
||||
})).thenComposeAsync(wrap(remoteManifestJson -> {
|
||||
McbbsModpackManifest remoteManifest;
|
||||
// We needs to update modpack from online server.
|
||||
@@ -201,10 +200,10 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
McbbsModpackManifest.CurseFile file = (McbbsModpackManifest.CurseFile) rawFile;
|
||||
if (StringUtils.isBlank(file.getFileName())) {
|
||||
try {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
return file.withFileName(NetworkUtils.detectFileName(NetworkUtils.toURI(file.getUrl())));
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(URI.create(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
||||
String result = NetworkUtils.doGet(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 +212,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
return file;
|
||||
} catch (IOException | JsonParseException e2) {
|
||||
try {
|
||||
String result = NetworkUtils.doGet(URI.create(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
||||
String result = NetworkUtils.doGet(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) {
|
||||
@@ -297,7 +296,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
if (file instanceof McbbsModpackManifest.AddonFile) {
|
||||
McbbsModpackManifest.AddonFile addonFile = (McbbsModpackManifest.AddonFile) file;
|
||||
return new FileDownloadTask(
|
||||
URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(addonFile.getPath())),
|
||||
remoteManifest.getFileApi() + "/overrides/" + addonFile.getPath(),
|
||||
modManager.getSimpleModPath(addonFile.getPath()),
|
||||
addonFile.getHash() != null ? new FileDownloadTask.IntegrityCheck("SHA-1", addonFile.getHash()) : null);
|
||||
} else if (file instanceof McbbsModpackManifest.CurseFile) {
|
||||
|
||||
@@ -27,11 +27,9 @@ import org.jackhuang.hmcl.mod.ModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.ModpackProvider;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -329,9 +327,10 @@ public class McbbsModpackManifest implements ModpackManifest, Validation {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
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 String getUrl() {
|
||||
return url == null
|
||||
? "https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file"
|
||||
: url;
|
||||
}
|
||||
|
||||
public CurseFile withFileName(String fileName) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.jackhuang.hmcl.mod.multimc;
|
||||
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
@@ -46,6 +47,6 @@ public final class MultiMCComponents {
|
||||
}
|
||||
|
||||
public static URI getMetaURL(String componentID, String version) {
|
||||
return URI.create(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version));
|
||||
return NetworkUtils.toURI(String.format("https://meta.multimc.org/v1/%s/%s.json", componentID, version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,9 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -84,7 +82,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
|
||||
dependent = new GetTask(URI.create(manifest.getManifest().getFileApi() + "/server-manifest.json"));
|
||||
dependent = new GetTask(manifest.getManifest().getFileApi() + "/server-manifest.json");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +150,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
|
||||
if (download) {
|
||||
total++;
|
||||
dependencies.add(new FileDownloadTask(
|
||||
URI.create(remoteManifest.getFileApi() + "/overrides/" + NetworkUtils.encodeLocation(file.getPath())),
|
||||
remoteManifest.getFileApi() + "/overrides/" + file.getPath(),
|
||||
actualPath,
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", file.getHash()))
|
||||
.withCounter("hmcl.modpack.download"));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -36,12 +37,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
*/
|
||||
public final class CacheFileTask extends FetchTask<Path> {
|
||||
|
||||
public CacheFileTask(@NotNull URI uri) {
|
||||
super(List.of(uri), DEFAULT_RETRY);
|
||||
public CacheFileTask(@NotNull String uri) {
|
||||
super(List.of(NetworkUtils.toURI(uri)));
|
||||
}
|
||||
|
||||
public CacheFileTask(@NotNull URI uri, int retry) {
|
||||
super(List.of(uri), retry);
|
||||
public CacheFileTask(@NotNull URI uri) {
|
||||
super(List.of(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -49,14 +49,13 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
protected static final int DEFAULT_RETRY = 3;
|
||||
|
||||
protected final List<URI> uris;
|
||||
protected final int retry;
|
||||
protected int retry = DEFAULT_RETRY;
|
||||
protected CacheRepository repository = CacheRepository.getInstance();
|
||||
|
||||
public FetchTask(@NotNull List<@NotNull URI> uris, int retry) {
|
||||
public FetchTask(@NotNull List<@NotNull URI> uris) {
|
||||
Objects.requireNonNull(uris);
|
||||
|
||||
this.uris = List.copyOf(uris);
|
||||
this.retry = retry;
|
||||
|
||||
if (this.uris.isEmpty())
|
||||
throw new IllegalArgumentException("At least one URL is required");
|
||||
@@ -64,6 +63,13 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
setExecutor(download());
|
||||
}
|
||||
|
||||
public void setRetry(int retry) {
|
||||
if (retry <= 0)
|
||||
throw new IllegalArgumentException("Retry count must be greater than 0");
|
||||
|
||||
this.retry = retry;
|
||||
}
|
||||
|
||||
public void setCacheRepository(CacheRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
@@ -167,7 +173,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
try {
|
||||
Path cache = repository.getCachedRemoteFile(conn.getURL().toURI());
|
||||
Path cache = repository.getCachedRemoteFile(NetworkUtils.toURI(conn.getURL()));
|
||||
useCachedResult(cache);
|
||||
LOG.info("Using cached file for " + NetworkUtils.dropQuery(uri));
|
||||
return;
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.Hex;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -82,6 +83,23 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
private Path candidate;
|
||||
private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param uri the URI of remote file.
|
||||
* @param path the location that download to.
|
||||
*/
|
||||
public FileDownloadTask(String uri, Path path) {
|
||||
this(List.of(NetworkUtils.toURI(uri)), path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(String uri, Path path, IntegrityCheck integrityCheck) {
|
||||
this(List.of(NetworkUtils.toURI(uri)), path, integrityCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param uri the URI of remote file.
|
||||
* @param path the location that download to.
|
||||
@@ -99,16 +117,6 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
this(List.of(uri), path, integrityCheck);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
public FileDownloadTask(URI uri, Path path, IntegrityCheck integrityCheck, int retry) {
|
||||
this(List.of(uri), path, integrityCheck, retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@@ -127,19 +135,7 @@ public class FileDownloadTask extends FetchTask<Void> {
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(List<URI> uris, Path path, IntegrityCheck integrityCheck) {
|
||||
this(uris, path, integrityCheck, DEFAULT_RETRY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public FileDownloadTask(List<URI> uris, Path path, IntegrityCheck integrityCheck, int retry) {
|
||||
super(uris, retry);
|
||||
super(uris);
|
||||
this.file = path;
|
||||
this.integrityCheck = integrityCheck;
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ package org.jackhuang.hmcl.task;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
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.List;
|
||||
@@ -36,29 +36,18 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
*/
|
||||
public final class GetTask extends FetchTask<String> {
|
||||
|
||||
private final Charset charset;
|
||||
public GetTask(String uri) {
|
||||
this(NetworkUtils.toURI(uri));
|
||||
}
|
||||
|
||||
public GetTask(URI url) {
|
||||
this(url, UTF_8);
|
||||
}
|
||||
|
||||
public GetTask(URI url, Charset charset) {
|
||||
this(url, charset, DEFAULT_RETRY);
|
||||
}
|
||||
|
||||
public GetTask(URI url, Charset charset, int retry) {
|
||||
this(List.of(url), charset, retry);
|
||||
this(List.of(url));
|
||||
setName(url.toString());
|
||||
}
|
||||
|
||||
public GetTask(List<URI> url) {
|
||||
this(url, UTF_8, DEFAULT_RETRY);
|
||||
}
|
||||
|
||||
public GetTask(List<URI> urls, Charset charset, int retry) {
|
||||
super(urls, retry);
|
||||
this.charset = charset;
|
||||
|
||||
setName(urls.get(0).toString());
|
||||
super(url);
|
||||
setName(url.get(0).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,7 +74,7 @@ public final class GetTask extends FetchTask<String> {
|
||||
public void close() throws IOException {
|
||||
if (!isSuccess()) return;
|
||||
|
||||
String result = baos.toString(charset);
|
||||
String result = baos.toString(UTF_8);
|
||||
setResult(result);
|
||||
|
||||
if (checkETag) {
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -185,8 +184,8 @@ public class CacheRepository {
|
||||
|
||||
URI uri;
|
||||
try {
|
||||
uri = NetworkUtils.dropQuery(conn.getURL().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
uri = NetworkUtils.dropQuery(NetworkUtils.toURI(conn.getURL()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
ETagItem eTagItem;
|
||||
@@ -229,16 +228,16 @@ public class CacheRepository {
|
||||
if (StringUtils.isBlank(eTag)) return null;
|
||||
URI uri;
|
||||
try {
|
||||
uri = NetworkUtils.dropQuery(connection.getURL().toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
uri = NetworkUtils.dropQuery(NetworkUtils.toURI(connection.getURL()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
String lastModified = connection.getHeaderField("Last-Modified");
|
||||
CacheResult cacheResult = cacheSupplier.get();
|
||||
ETagItem eTagItem = new ETagItem(uri, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
ETagItem eTagItem = new ETagItem(uri.toString(), eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
index.compute(eTagItem.url, updateEntity(eTagItem, true));
|
||||
index.compute(uri, updateEntity(eTagItem, true));
|
||||
saveETagIndex();
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
@@ -282,7 +281,7 @@ public class CacheRepository {
|
||||
for (Collection<ETagItem> eTagItems : indexes) {
|
||||
if (eTagItems != null) {
|
||||
for (ETagItem eTag : eTagItems) {
|
||||
eTags.compute(eTag.url, updateEntity(eTag, false));
|
||||
eTags.compute(NetworkUtils.toURI(eTag.url), updateEntity(eTag, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +330,7 @@ public class CacheRepository {
|
||||
}
|
||||
|
||||
private static final class ETagItem {
|
||||
private final URI url;
|
||||
private final String url;
|
||||
private final String eTag;
|
||||
private final String hash;
|
||||
@SerializedName("local")
|
||||
@@ -346,7 +345,7 @@ public class CacheRepository {
|
||||
this(null, null, null, 0, null);
|
||||
}
|
||||
|
||||
public ETagItem(URI url, String eTag, String hash, long localLastModified, String remoteLastModified) {
|
||||
public ETagItem(String url, String eTag, String hash, long localLastModified, String remoteLastModified) {
|
||||
this.url = url;
|
||||
this.eTag = eTag;
|
||||
this.hash = hash;
|
||||
|
||||
@@ -29,7 +29,6 @@ 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;
|
||||
@@ -120,7 +119,7 @@ public abstract class HttpRequest {
|
||||
}
|
||||
|
||||
public HttpURLConnection createConnection() throws IOException {
|
||||
HttpURLConnection con = createHttpConnection(URI.create(url));
|
||||
HttpURLConnection con = createHttpConnection(url);
|
||||
con.setRequestMethod(method);
|
||||
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||
con.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
@@ -188,9 +187,9 @@ public abstract class HttpRequest {
|
||||
if (con.getResponseCode() / 100 != 2) {
|
||||
if (!ignoreHttpCode && !toleratedHttpCodes.contains(con.getResponseCode())) {
|
||||
try {
|
||||
throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), NetworkUtils.readFullyAsString(con));
|
||||
throw new ResponseCodeException(url.toString(), con.getResponseCode(), NetworkUtils.readFullyAsString(con));
|
||||
} catch (IOException e) {
|
||||
throw new ResponseCodeException(NetworkUtils.toURI(url), con.getResponseCode(), e);
|
||||
throw new ResponseCodeException(url.toString(), con.getResponseCode(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
@@ -122,10 +123,18 @@ public final class NetworkUtils {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public static HttpURLConnection createHttpConnection(String url) throws IOException {
|
||||
return (HttpURLConnection) createConnection(toURI(url));
|
||||
}
|
||||
|
||||
public static HttpURLConnection createHttpConnection(URI url) throws IOException {
|
||||
return (HttpURLConnection) createConnection(url);
|
||||
}
|
||||
|
||||
private static void encodeCodePoint(StringBuilder builder, int codePoint) {
|
||||
builder.append(encodeURL(Character.toString(codePoint)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param location the url to be URL encoded
|
||||
* @return encoded URL
|
||||
@@ -133,29 +142,59 @@ public final class NetworkUtils {
|
||||
* "https://github.com/curl/curl/blob/3f7b1bb89f92c13e69ee51b710ac54f775aab320/lib/transfer.c#L1427-L1461">Curl</a>
|
||||
*/
|
||||
public static String encodeLocation(String location) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int i = 0;
|
||||
boolean left = true;
|
||||
for (char ch : location.toCharArray()) {
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
if (left)
|
||||
sb.append("%20");
|
||||
else
|
||||
sb.append('+');
|
||||
break;
|
||||
case '?':
|
||||
left = false;
|
||||
// fallthrough
|
||||
default:
|
||||
if (ch >= 0x80)
|
||||
sb.append(encodeURL(Character.toString(ch)));
|
||||
else
|
||||
sb.append(ch);
|
||||
break;
|
||||
while (i < location.length()) {
|
||||
char ch = location.charAt(i);
|
||||
if (ch == ' ' || ch >= 0x80)
|
||||
break;
|
||||
else if (ch == '?')
|
||||
left = false;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i == location.length()) {
|
||||
// No need to encode
|
||||
return location;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder(location.length() + 10);
|
||||
builder.append(location, 0, i);
|
||||
|
||||
for (; i < location.length(); i++) {
|
||||
char ch = location.charAt(i);
|
||||
if (Character.isSurrogate(ch)) {
|
||||
if (Character.isHighSurrogate(ch) && i < location.length() - 1) {
|
||||
char ch2 = location.charAt(i + 1);
|
||||
if (Character.isLowSurrogate(ch2)) {
|
||||
int codePoint = Character.toCodePoint(ch, ch2);
|
||||
encodeCodePoint(builder, codePoint);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid surrogate pair, encode as '?'
|
||||
builder.append("%3F");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch == ' ') {
|
||||
if (left)
|
||||
builder.append("%20");
|
||||
else
|
||||
builder.append('+');
|
||||
} else if (ch == '?') {
|
||||
left = false;
|
||||
builder.append('?');
|
||||
} else if (ch >= 0x80) {
|
||||
encodeCodePoint(builder, ch);
|
||||
} else {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -200,6 +239,10 @@ public final class NetworkUtils {
|
||||
return conn;
|
||||
}
|
||||
|
||||
public static String doGet(String uri) throws IOException {
|
||||
return doGet(toURI(uri));
|
||||
}
|
||||
|
||||
public static String doGet(URI uri) throws IOException {
|
||||
return readFullyAsString(resolveConnection(createHttpConnection(uri)));
|
||||
}
|
||||
@@ -319,14 +362,6 @@ public final class NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static URI toURI(URL url) {
|
||||
try {
|
||||
return url.toURI();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==== Shortcut methods for encoding/decoding URLs in UTF-8 ====
|
||||
public static String encodeURL(String toEncode) {
|
||||
return URLEncoder.encode(toEncode, UTF_8);
|
||||
@@ -335,6 +370,20 @@ public final class NetworkUtils {
|
||||
public static String decodeURL(String toDecode) {
|
||||
return URLDecoder.decode(toDecode, UTF_8);
|
||||
}
|
||||
|
||||
/// @throws IllegalArgumentException if the string is not a valid URI
|
||||
public static @NotNull URI toURI(@NotNull String uri) {
|
||||
try {
|
||||
return new URI(encodeLocation(uri));
|
||||
} catch (URISyntaxException e) {
|
||||
// Possibly an Internationalized Domain Name (IDN)
|
||||
return URI.create(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull URI toURI(@NotNull URL url) {
|
||||
return toURI(url.toExternalForm());
|
||||
}
|
||||
// ====
|
||||
|
||||
}
|
||||
|
||||
@@ -22,32 +22,44 @@ import java.net.URI;
|
||||
|
||||
public final class ResponseCodeException extends IOException {
|
||||
|
||||
private final URI uri;
|
||||
private final String uri;
|
||||
private final int responseCode;
|
||||
private final String data;
|
||||
|
||||
public ResponseCodeException(URI uri, int responseCode) {
|
||||
this(uri.toString(), responseCode);
|
||||
}
|
||||
|
||||
public ResponseCodeException(URI uri, int responseCode, Throwable cause) {
|
||||
this(uri.toString(), responseCode, cause);
|
||||
}
|
||||
|
||||
public ResponseCodeException(URI uri, int responseCode, String data) {
|
||||
this(uri.toString(), responseCode, data);
|
||||
}
|
||||
|
||||
public ResponseCodeException(String uri, int responseCode) {
|
||||
super("Unable to request url " + uri + ", response code: " + responseCode);
|
||||
this.uri = uri;
|
||||
this.responseCode = responseCode;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public ResponseCodeException(URI uri, int responseCode, Throwable cause) {
|
||||
public ResponseCodeException(String 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(URI uri, int responseCode, String data) {
|
||||
public ResponseCodeException(String 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 URI getUri() {
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,18 +21,39 @@ 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.jackhuang.hmcl.util.io.NetworkUtils.encodeLocation;
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.getCharsetFromContentType;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public class NetworkUtilsTest {
|
||||
|
||||
@Test
|
||||
public void testEncodeLocation() {
|
||||
assertEquals("https://github.com", encodeLocation("https://github.com"));
|
||||
assertEquals("https://github.com/HMCL-dev/HMCL/commits?author=Glavo", encodeLocation("https://github.com/HMCL-dev/HMCL/commits?author=Glavo"));
|
||||
assertEquals("https://www.example.com/file%20with%20space", encodeLocation("https://www.example.com/file with space"));
|
||||
assertEquals("https://www.example.com/file%20with%20space", encodeLocation("https://www.example.com/file%20with%20space"));
|
||||
assertEquals("https://www.example.com/%E6%B5%8B%E8%AF%95", encodeLocation("https://www.example.com/测试"));
|
||||
assertEquals("https://www.example.com/%F0%9F%98%87", encodeLocation("https://www.example.com/\uD83D\uDE07"));
|
||||
assertEquals("https://www.example.com/test?a=10+20", encodeLocation("https://www.example.com/test?a=10 20"));
|
||||
assertEquals("https://www.example.com/%E6%B5%8B%E8%AF%95?a=10+20", encodeLocation("https://www.example.com/测试?a=10 20"));
|
||||
|
||||
// Invalid surrogate pair
|
||||
assertEquals("https://www.example.com/%3F", encodeLocation("https://www.example.com/\uD83D"));
|
||||
assertEquals("https://www.example.com/%3F", encodeLocation("https://www.example.com/\uDE07"));
|
||||
assertEquals("https://www.example.com/%3Ftest", encodeLocation("https://www.example.com/\uD83Dtest"));
|
||||
assertEquals("https://www.example.com/%3Ftest", encodeLocation("https://www.example.com/\uDE07test"));
|
||||
}
|
||||
|
||||
@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"));
|
||||
assertEquals(UTF_8, getCharsetFromContentType(null));
|
||||
assertEquals(UTF_8, getCharsetFromContentType(""));
|
||||
assertEquals(UTF_8, getCharsetFromContentType("text/html"));
|
||||
assertEquals(UTF_8, getCharsetFromContentType("text/html; charset=utf-8"));
|
||||
assertEquals(US_ASCII, getCharsetFromContentType("text/html; charset=ascii"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user