Support authlib-injector ALI

See yushijinhun/authlib-injector#18
This commit is contained in:
yushijinhun
2018-10-06 00:47:36 +08:00
parent 19ea27dece
commit 2a42ecda78
4 changed files with 95 additions and 74 deletions

View File

@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXTextField;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
@@ -39,7 +38,6 @@ import java.io.IOException;
import static org.jackhuang.hmcl.setting.ConfigHolder.config; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML; import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware { public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
@@ -70,24 +68,13 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
transitionHandler = new TransitionHandler(addServerContainer); transitionHandler = new TransitionHandler(addServerContainer);
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
btnAddNext.disableProperty().bind( btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
nextPane.hideSpinner(); nextPane.hideSpinner();
txtServerUrl.setText("https://");
} }
@Override @Override
public void onDialogShown() { public void onDialogShown() {
txtServerUrl.requestFocus(); txtServerUrl.requestFocus();
txtServerUrl.selectEnd();
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
} }
private String resolveFetchExceptionMessage(Throwable exception) { private String resolveFetchExceptionMessage(Throwable exception) {
@@ -110,13 +97,13 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
lblCreationWarning.setText(""); lblCreationWarning.setText("");
String url = fixInputUrl(txtServerUrl.getText()); String url = txtServerUrl.getText();
nextPane.showSpinner(); nextPane.showSpinner();
addServerPane.setDisable(true); addServerPane.setDisable(true);
Task.of(() -> { Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url); serverBeingAdded = AuthlibInjectorServer.locateServer(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { }).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
addServerPane.setDisable(false); addServerPane.setDisable(false);
nextPane.hideSpinner(); nextPane.hideSpinner();

View File

@@ -1,37 +0,0 @@
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TextInputControl;
import java.net.MalformedURLException;
import java.net.URL;
public class URLValidator extends ValidatorBase {
private final ObservableList<String> protocols = FXCollections.observableArrayList();
public URLValidator() {
super();
}
public ObservableList<String> getProtocols() {
return protocols;
}
@Override
protected void eval() {
if (srcControl.get() instanceof TextInputControl) {
try {
URL url = new URL(((TextInputControl) srcControl.get()).getText());
if (protocols.isEmpty())
hasErrors.set(false);
else
hasErrors.set(!protocols.contains(url.getProtocol()));
} catch (MalformedURLException e) {
hasErrors.set(true);
}
}
}
}

View File

@@ -4,7 +4,6 @@
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.*?> <?import com.jfoenix.controls.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?> <?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<fx:root xmlns="http://javafx.com/javafx" <fx:root xmlns="http://javafx.com/javafx"
@@ -16,16 +15,7 @@
<Label text="%account.injector.add" /> <Label text="%account.injector.add" />
</heading> </heading>
<body> <body>
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext"> <JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext" />
<validators>
<URLValidator message="%input.url">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
</protocols>
</URLValidator>
</validators>
</JFXTextField>
</body> </body>
<actions> <actions>
<Label fx:id="lblCreationWarning" /> <Label fx:id="lblCreationWarning" />

View File

@@ -17,23 +17,104 @@
*/ */
package org.jackhuang.hmcl.auth.authlibinjector; package org.jackhuang.hmcl.auth.authlibinjector;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.io.IOUtils.readFullyWithoutClosing;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Optional;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.Optional;
import static org.jackhuang.hmcl.util.Lang.tryCast;
public class AuthlibInjectorServer { public class AuthlibInjectorServer {
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException { private static final int MAX_REDIRECTS = 5;
public static AuthlibInjectorServer locateServer(String url) throws IOException {
url = parseInputUrl(url);
HttpURLConnection conn;
int redirectCount = 0;
for (;;) {
conn = (HttpURLConnection) new URL(url).openConnection();
Optional<String> ali = getApiLocationIndication(conn);
if (ali.isPresent()) {
conn.disconnect();
url = ali.get();
if (redirectCount >= MAX_REDIRECTS) {
throw new IOException("Exceeded maximum number of redirects (" + MAX_REDIRECTS + ")");
}
redirectCount++;
LOG.info("Redirect API root to: " + url);
} else {
break;
}
}
try { try {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class); return parseResponse(url, readFullyWithoutClosing(conn.getInputStream()));
} finally {
conn.disconnect();
}
}
private static Optional<String> getApiLocationIndication(URLConnection conn) {
return Optional.ofNullable(conn.getHeaderFields().get("X-Authlib-Injector-API-Location"))
.flatMap(list -> list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)))
.flatMap(indication -> {
String currentUrl = appendSuffixSlash(conn.getURL().toString());
String newUrl;
try {
newUrl = appendSuffixSlash(new URL(conn.getURL(), indication).toString());
} catch (MalformedURLException e) {
LOG.warning("Failed to resolve absolute ALI, the header is [" + indication + "]. Ignore it.");
return Optional.empty();
}
if (newUrl.equals(currentUrl)) {
return Optional.empty();
} else {
return Optional.of(newUrl);
}
});
}
private static String parseInputUrl(String url) {
String lowercased = url.toLowerCase();
if (!lowercased.startsWith("http://") && !lowercased.startsWith("https://")) {
url = "https://" + url;
}
url = appendSuffixSlash(url);
return url;
}
private static String appendSuffixSlash(String url) {
if (!url.endsWith("/")) {
return url + "/";
} else {
return url;
}
}
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
try (InputStream in = new URL(url).openStream()) {
return parseResponse(url, readFullyWithoutClosing(in));
}
}
private static AuthlibInjectorServer parseResponse(String url, byte[] rawResponse) throws IOException {
try {
JsonObject response = JsonUtils.fromNonNullJson(new String(rawResponse, UTF_8), JsonObject.class);
String name = extractServerName(response).orElse(url); String name = extractServerName(response).orElse(url);
return new AuthlibInjectorServer(url, name); return new AuthlibInjectorServer(url, name);
} catch (JsonParseException e) { } catch (JsonParseException e) {