mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-04-21 23:20:27 -04:00
Merge branch 'vpn' into 23-release
This commit is contained in:
@@ -10,6 +10,7 @@ import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.core.OsType;
|
||||
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
@@ -70,6 +71,11 @@ public class ModalOverlayComp extends RegionBuilder<Region> {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Timeline createCloseBlockedAnimation() {
|
||||
return new Timeline();
|
||||
}
|
||||
});
|
||||
modal.setInTransitionFactory(
|
||||
OsType.ofLocal() == OsType.LINUX ? null : node -> Animations.fadeIn(node, Duration.millis(150)));
|
||||
|
||||
@@ -104,8 +104,7 @@ public class InPlaceKeyStrategy implements SshIdentityStrategy {
|
||||
}),
|
||||
key)
|
||||
.nonNull()
|
||||
.name("keyPassword")
|
||||
.description("sshConfigHost.identityPassphraseDescription")
|
||||
.nameAndDescription("keyPassphrase")
|
||||
.sub(passwordChoice, keyPasswordProperty)
|
||||
.nonNull()
|
||||
.nameAndDescription("inPlacePublicKey")
|
||||
|
||||
@@ -167,8 +167,7 @@ public class KeyFileStrategy implements SshIdentityStrategy {
|
||||
false),
|
||||
keyPath)
|
||||
.nonNull()
|
||||
.name("keyPassword")
|
||||
.description("sshConfigHost.identityPassphraseDescription")
|
||||
.nameAndDescription("keyPassphrase")
|
||||
.sub(passwordChoice, keyPasswordProperty)
|
||||
.nonNull()
|
||||
.nameAndDescription("inPlacePublicKey")
|
||||
|
||||
@@ -21,7 +21,8 @@ public enum DataStoreCreationCategory {
|
||||
SERIAL(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID),
|
||||
MACRO(DataStorage.ALL_MACROS_CATEGORY_UUID),
|
||||
FILE_SYSTEM(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID),
|
||||
IDENTITY(DataStorage.ALL_IDENTITIES_CATEGORY_UUID);
|
||||
IDENTITY(DataStorage.ALL_IDENTITIES_CATEGORY_UUID),
|
||||
NETWORK(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID);
|
||||
|
||||
private final UUID category;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import io.xpipe.app.process.CommandControl;
|
||||
import io.xpipe.app.process.ShellControl;
|
||||
import io.xpipe.app.process.ShellDialect;
|
||||
import io.xpipe.app.secret.SecretRetrievalStrategy;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.RemoteDesktopDockContentEntry;
|
||||
import io.xpipe.app.util.HttpProxy;
|
||||
import io.xpipe.app.vnc.VncBaseStore;
|
||||
import io.xpipe.core.SecretValue;
|
||||
|
||||
@@ -19,6 +21,7 @@ import javafx.beans.property.Property;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
public abstract class ProcessControlProvider {
|
||||
@@ -82,4 +85,6 @@ public abstract class ProcessControlProvider {
|
||||
public abstract void cloneRepository(String url, Path target) throws Exception;
|
||||
|
||||
public abstract void pullRepository(Path target) throws Exception;
|
||||
|
||||
public abstract Optional<HttpProxy> getHttpProxy(DataStoreEntryRef<?> store) throws Exception;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class StoreCreationMenu {
|
||||
menu.getItems()
|
||||
.add(categoryMenu(
|
||||
"addOther",
|
||||
"mdi2f-folder-plus-outline", null, DataStoreCreationCategory.CLUSTER, DataStoreCreationCategory.FILE_SYSTEM, DataStoreCreationCategory.SERIAL));
|
||||
"mdi2f-folder-plus-outline", null, DataStoreCreationCategory.NETWORK, DataStoreCreationCategory.CLUSTER, DataStoreCreationCategory.FILE_SYSTEM, DataStoreCreationCategory.SERIAL));
|
||||
|
||||
menu.getItems().add(new SeparatorMenuItem());
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static atlantafx.base.theme.Styles.ACCENT;
|
||||
import static atlantafx.base.theme.Styles.BUTTON_OUTLINED;
|
||||
|
||||
@@ -94,12 +96,16 @@ public class ErrorHandlerComp extends SimpleRegionBuilder {
|
||||
private String getEventDescription() {
|
||||
var desc = event.getDescription();
|
||||
|
||||
String lastLine = desc;
|
||||
Throwable t = event.getThrowable();
|
||||
while (t != null) {
|
||||
var toAppend = t.getMessage() != null
|
||||
? t.getMessage()
|
||||
: AppI18n.get("errorTypeOccured", t.getClass().getSimpleName());
|
||||
desc = desc != null ? desc + "\n\n" + toAppend : toAppend;
|
||||
if (!Objects.equals(toAppend, lastLine)) {
|
||||
desc = desc != null ? desc + "\n\n" + toAppend : toAppend;
|
||||
lastLine = toAppend;
|
||||
}
|
||||
t = t.getCause() != t && !(t instanceof ProcessOutputException) ? t.getCause() : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,12 @@ public final class AppPrefs {
|
||||
.valueClass(Boolean.class)
|
||||
.requiresRestart(false)
|
||||
.build());
|
||||
final BooleanProperty useExternalNetcatForProxies = map(Mapping.builder()
|
||||
.property(new GlobalBooleanProperty(false))
|
||||
.key("useExternalNetcatForProxies")
|
||||
.valueClass(Boolean.class)
|
||||
.requiresRestart(false)
|
||||
.build());
|
||||
final BooleanProperty pinLocalMachineOnStartup = map(Mapping.builder()
|
||||
.property(new GlobalBooleanProperty(false))
|
||||
.key("pinLocalMachineOnStartup")
|
||||
@@ -275,6 +281,12 @@ public final class AppPrefs {
|
||||
.valueClass(ShellScript.class)
|
||||
.log(false)
|
||||
.build());
|
||||
final Property<UUID> httpProxy = map(Mapping.builder()
|
||||
.property(new GlobalObjectProperty<>())
|
||||
.key("httpProxy")
|
||||
.valueClass(UUID.class)
|
||||
.requiresRestart(false)
|
||||
.build());
|
||||
final Property<UUID> terminalProxy = map(Mapping.builder()
|
||||
.property(new GlobalObjectProperty<>())
|
||||
.key("terminalProxy")
|
||||
@@ -443,6 +455,7 @@ public final class AppPrefs {
|
||||
new ApiCategory(),
|
||||
new McpCategory(),
|
||||
new UpdatesCategory(),
|
||||
new HttpProxyCategory(),
|
||||
new SecurityCategory(),
|
||||
new WorkspacesCategory(),
|
||||
new DeveloperCategory(),
|
||||
@@ -465,6 +478,10 @@ public final class AppPrefs {
|
||||
return globalStorageHandler.isInitialized();
|
||||
}
|
||||
|
||||
public ObservableBooleanValue useExternalNetcatForProxies() {
|
||||
return useExternalNetcatForProxies;
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> disableHttpsTlsCheck() {
|
||||
return disableHttpsTlsCheck;
|
||||
}
|
||||
@@ -585,6 +602,10 @@ public final class AppPrefs {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public ObservableValue<UUID> httpProxy() {
|
||||
return httpProxy;
|
||||
}
|
||||
|
||||
public ObservableBooleanValue disableApiAuthentication() {
|
||||
return disableApiAuthentication;
|
||||
}
|
||||
|
||||
110
app/src/main/java/io/xpipe/app/prefs/HttpProxyCategory.java
Normal file
110
app/src/main/java/io/xpipe/app/prefs/HttpProxyCategory.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.comp.BaseRegionBuilder;
|
||||
import io.xpipe.app.comp.RegionBuilder;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStore;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.hub.comp.StoreChoiceComp;
|
||||
import io.xpipe.app.hub.comp.StoreCreationDialog;
|
||||
import io.xpipe.app.hub.comp.StoreCreationModel;
|
||||
import io.xpipe.app.hub.comp.StoreViewState;
|
||||
import io.xpipe.app.platform.LabelGraphic;
|
||||
import io.xpipe.app.platform.OptionsBuilder;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.*;
|
||||
import io.xpipe.app.util.DocumentationLink;
|
||||
import io.xpipe.app.util.HttpProxy;
|
||||
import io.xpipe.core.OsType;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
public class HttpProxyCategory extends AppPrefsCategory {
|
||||
|
||||
@Override
|
||||
protected String getId() {
|
||||
return "httpProxy";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LabelGraphic getIcon() {
|
||||
return new LabelGraphic.IconGraphic("mdi2s-server-network-outline");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BaseRegionBuilder<?, ?> create() {
|
||||
var prefs = AppPrefs.get();
|
||||
return new OptionsBuilder()
|
||||
.title("httpProxyConfiguration")
|
||||
.sub(proxy())
|
||||
.sub(new OptionsBuilder()
|
||||
.pref(prefs.disableHttpsTlsCheck)
|
||||
.addToggle(prefs.disableHttpsTlsCheck)
|
||||
)
|
||||
.buildComp();
|
||||
}
|
||||
|
||||
private OptionsBuilder proxy() {
|
||||
var prefs = AppPrefs.get();
|
||||
var initial = prefs.httpProxy.getValue();
|
||||
var initialRef = initial != null ? DataStorage.get().getStoreEntryIfPresent(initial)
|
||||
.map(e -> e.ref())
|
||||
.filter(e -> HttpProxy.canUseAsProxy(e))
|
||||
.orElse(null) : null;
|
||||
var ref = new SimpleObjectProperty<>(initialRef);
|
||||
ref.addListener((observable, oldValue, newValue) -> {
|
||||
prefs.httpProxy.setValue(
|
||||
newValue != null
|
||||
? newValue.get().getUuid()
|
||||
: null);
|
||||
});
|
||||
var proxyChoice = new DelayedInitComp(
|
||||
RegionBuilder.of(() -> {
|
||||
var comp = new StoreChoiceComp<>(
|
||||
null,
|
||||
ref,
|
||||
DataStore.class,
|
||||
r -> HttpProxy.canUseAsProxy(r),
|
||||
StoreViewState.get().getAllConnectionsCategory()) {
|
||||
@Override
|
||||
protected String toName(DataStoreEntry entry) {
|
||||
if (entry == null) {
|
||||
return AppI18n.get("systemDefault");
|
||||
}
|
||||
|
||||
return super.toName(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toGraphic(DataStoreEntry entry) {
|
||||
if (entry == null) {
|
||||
return "proc:networkProxy_icon.svg";
|
||||
}
|
||||
|
||||
return super.toGraphic(entry);
|
||||
}
|
||||
};
|
||||
return comp.build();
|
||||
}),
|
||||
() -> StoreViewState.get() != null && StoreViewState.get().isInitialized());
|
||||
proxyChoice.maxWidth(getCompWidth());
|
||||
|
||||
var addButton = new ButtonComp(AppI18n.observable("addProxy"), () -> {
|
||||
var selected = DataStoreProviders.byId("networkProxy").orElseThrow();
|
||||
StoreCreationDialog.showCreation(
|
||||
null, selected.defaultStore(DataStorage.get().getSelectedCategory()),
|
||||
DataStoreCreationCategory.NETWORK,
|
||||
ignored -> {},
|
||||
false);
|
||||
});
|
||||
|
||||
return new OptionsBuilder()
|
||||
.nameAndDescription("httpProxy")
|
||||
.addComp(proxyChoice, ref)
|
||||
.addComp(addButton);
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class McpCategory extends AppPrefsCategory {
|
||||
.pref(prefs.enableMcpServer)
|
||||
.addToggle(prefs.enableMcpServer)
|
||||
.nameAndDescription("mcpClientConfigurationDetails")
|
||||
.addComp(tabComp)
|
||||
.addComp(tabComp.maxWidth(getCompWidth()))
|
||||
.pref(prefs.enableMcpMutationTools)
|
||||
.addToggle(prefs.enableMcpMutationTools)
|
||||
.hide(prefs.enableMcpServer.not())
|
||||
@@ -150,7 +150,7 @@ public class McpCategory extends AppPrefsCategory {
|
||||
.getTextArea()
|
||||
.promptTextProperty()
|
||||
.bind(AppI18n.observable("mcpAdditionalContextSample"));
|
||||
}))
|
||||
}).maxWidth(getCompWidth()))
|
||||
.hide(prefs.enableMcpServer.not()))
|
||||
.buildComp();
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ public class SecurityCategory extends AppPrefsCategory {
|
||||
.addToggle(prefs.dontAutomaticallyStartVmSshServer)
|
||||
.pref(prefs.disableTerminalRemotePasswordPreparation)
|
||||
.addToggle(prefs.disableTerminalRemotePasswordPreparation)
|
||||
.pref(prefs.disableHttpsTlsCheck)
|
||||
.addToggle(prefs.disableHttpsTlsCheck));
|
||||
);
|
||||
return builder.buildComp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@ public class SshCategory extends AppPrefsCategory {
|
||||
options.addComp(prefs.getCustomOptions("x11WslInstance").buildComp());
|
||||
}
|
||||
|
||||
options.sub(new OptionsBuilder()
|
||||
.pref(prefs.useExternalNetcatForProxies)
|
||||
.addToggle(prefs.useExternalNetcatForProxies)
|
||||
);
|
||||
|
||||
var agentTest = new SshAgentTestComp(
|
||||
() -> {},
|
||||
new SimpleObjectProperty<>(CustomAgentStrategy.builder().build()));
|
||||
|
||||
@@ -190,9 +190,7 @@ public class HashicorpVaultPasswordManager implements PasswordManager {
|
||||
}
|
||||
|
||||
var res = HttpHelper.client().send(req.build(), HttpResponse.BodyHandlers.ofString());
|
||||
if (res.statusCode() >= 400) {
|
||||
throw new IOException(res.body());
|
||||
}
|
||||
HttpHelper.checkOrThrow(res);
|
||||
|
||||
var resJson = JacksonMapper.getDefault().readTree(res.body());
|
||||
if (!resJson.isObject()) {
|
||||
|
||||
@@ -265,9 +265,7 @@ public interface DataStorageGroupStrategy {
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.noBody())
|
||||
.build();
|
||||
var result = HttpHelper.client().send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (result.statusCode() >= 400) {
|
||||
throw ErrorEventFactory.expected(new IOException(result.body()));
|
||||
}
|
||||
HttpHelper.checkOrThrow(result);
|
||||
var body = result.body();
|
||||
if (body.length() == 0) {
|
||||
throw ErrorEventFactory.expected(new IllegalArgumentException("Http response body is empty"));
|
||||
|
||||
@@ -51,7 +51,7 @@ public class MobaXTermTerminalType implements ExternalApplicationType.WindowsTyp
|
||||
var rawCommand = command.buildSimple();
|
||||
var script = AppLocalTemp.getLocalTempDataDirectory().resolve("mobaxpipe.sh");
|
||||
Files.writeString(Path.of(script.toString()), "#!/usr/bin/env bash\n" + rawCommand);
|
||||
var fixedFile = script.toString().replaceAll("\\\\", "/").replaceAll("\\s", "\\$0");
|
||||
var fixedFile = script.toString().replaceAll("\\\\", "/").replaceAll("\\s", "\\\\$0");
|
||||
launch(CommandBuilder.of().add("-newtab").add(fixedFile));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,7 @@ public class AppDownloads {
|
||||
var httpRequest = builder.uri(URI.create(release.getUrl())).GET().build();
|
||||
var client = HttpHelper.client();
|
||||
var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (response.statusCode() >= 400) {
|
||||
throw new IOException(new String(response.body(), StandardCharsets.UTF_8));
|
||||
}
|
||||
HttpHelper.checkOrThrow(response);
|
||||
|
||||
var downloadFile = AppCache.getBasePath().resolve(release.getFile());
|
||||
Files.write(downloadFile, response.body());
|
||||
@@ -64,10 +62,7 @@ public class AppDownloads {
|
||||
var httpRequest = builder.uri(uri).GET().build();
|
||||
var client = HttpHelper.client();
|
||||
var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() >= 400) {
|
||||
var s = response.body();
|
||||
throw new IOException("Changelog not found" + (s != null && !s.isEmpty() ? ": " + s : ""));
|
||||
}
|
||||
HttpHelper.checkOrThrow(response);
|
||||
var json = JacksonMapper.getDefault().readTree(response.body());
|
||||
var changelog = json.required("changelog").asText();
|
||||
return changelog;
|
||||
@@ -104,9 +99,7 @@ public class AppDownloads {
|
||||
.build();
|
||||
var client = HttpHelper.client();
|
||||
var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() >= 400) {
|
||||
throw new IOException(response.body());
|
||||
}
|
||||
HttpHelper.checkOrThrow(response);
|
||||
|
||||
var dateEntry = response.headers().firstValue("Date");
|
||||
if (dateEntry.isPresent()) {
|
||||
|
||||
@@ -28,9 +28,7 @@ public class GithubReleaseDownloader {
|
||||
.uri(URI.create(getDownloadUrl(repository, filter)))
|
||||
.build();
|
||||
var r = HttpHelper.client().send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (r.statusCode() >= 400) {
|
||||
throw new IOException(new String(r.body(), StandardCharsets.UTF_8));
|
||||
}
|
||||
HttpHelper.checkOrThrow(r);
|
||||
|
||||
Files.createDirectories(tempDir);
|
||||
Files.write(temp, r.body());
|
||||
@@ -55,9 +53,7 @@ public class GithubReleaseDownloader {
|
||||
.uri(URI.create("https://api.github.com/repos/" + repository + "/releases"))
|
||||
.build();
|
||||
var r = HttpHelper.client().send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (r.statusCode() >= 400) {
|
||||
throw new IOException(r.body());
|
||||
}
|
||||
HttpHelper.checkOrThrow(r);
|
||||
|
||||
var json = JacksonMapper.getDefault().readTree(r.body());
|
||||
var latest = json.get(0);
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorAction;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.SSLContext;
|
||||
@@ -15,10 +23,17 @@ public class HttpHelper {
|
||||
|
||||
@SneakyThrows
|
||||
public static HttpClient client() {
|
||||
var proxy = HttpProxy.getActiveProxy();
|
||||
return client(proxy.orElse(null), AppPrefs.get() != null && AppPrefs.get().disableHttpsTlsCheck().getValue());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static HttpClient client(HttpProxy proxy, boolean checkTls) {
|
||||
var builder = HttpClient.newBuilder();
|
||||
builder.version(HttpClient.Version.HTTP_1_1);
|
||||
builder.followRedirects(HttpClient.Redirect.NORMAL);
|
||||
if (AppPrefs.get() != null && AppPrefs.get().disableHttpsTlsCheck().getValue()) {
|
||||
|
||||
if (!checkTls) {
|
||||
var sslContext = SSLContext.getInstance("TLS");
|
||||
var trustManager = new X509TrustManager() {
|
||||
@Override
|
||||
@@ -35,6 +50,62 @@ public class HttpHelper {
|
||||
sslContext.init(null, new TrustManager[] {trustManager}, new SecureRandom());
|
||||
builder.sslContext(sslContext);
|
||||
}
|
||||
|
||||
if (proxy != null) {
|
||||
builder.proxy(ProxySelector.of(new InetSocketAddress(proxy.getHost(), proxy.getPort())));
|
||||
builder.authenticator(new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (proxy.getUser() != null && proxy.getPassword() != null) {
|
||||
return new PasswordAuthentication(proxy.getUser(), proxy.getPassword().getSecret());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static void checkOrThrow(HttpResponse<?> res) throws IOException {
|
||||
if (res.statusCode() == 407) {
|
||||
var ex = new IOException("HTTP proxy authentication required");
|
||||
ErrorEventFactory.preconfigure(ErrorEventFactory.fromThrowable(ex)
|
||||
.expected()
|
||||
.customAction(new ErrorAction() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return AppI18n.get("httpProxyError");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return AppI18n.get("httpProxyErrorDescription");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(ErrorEvent event) throws Exception {
|
||||
AppPrefs.get().selectCategory("httpProxy");
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (res.statusCode() >= 400) {
|
||||
if (res.body() instanceof String s) {
|
||||
var msg = !s.isEmpty() ?
|
||||
s :
|
||||
"Received HTTP " + res.statusCode() + " without further details";
|
||||
throw new IOException(msg);
|
||||
} else if (res.body() instanceof byte[] b) {
|
||||
var msg = b.length > 0 ?
|
||||
new String(b, StandardCharsets.UTF_8) :
|
||||
"Received HTTP " + res.statusCode() + " without further details";
|
||||
throw new IOException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
app/src/main/java/io/xpipe/app/util/HttpProxy.java
Normal file
59
app/src/main/java/io/xpipe/app/util/HttpProxy.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.ext.DataStore;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEventFactory;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.core.SecretValue;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Value
|
||||
public class HttpProxy {
|
||||
|
||||
public static Optional<HttpProxy> getActiveProxy() {
|
||||
if (AppPrefs.get() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var current = AppPrefs.get().httpProxy().getValue();
|
||||
if (current == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
var found = DataStorage.get().getStoreEntryIfPresent(current);
|
||||
if (found.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
var proxy = ProcessControlProvider.get().getHttpProxy(found.get().ref());
|
||||
return proxy;
|
||||
} catch (Exception e) {
|
||||
ErrorEventFactory.fromThrowable(e).handle();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean canUseAsProxy(DataStoreEntryRef<DataStore> ref) {
|
||||
if (!ref.get().getValidity().isUsable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return ProcessControlProvider.get().getHttpProxy(ref).isPresent();
|
||||
} catch (Exception e) {
|
||||
ErrorEventFactory.fromThrowable(e).handle();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String host;
|
||||
int port;
|
||||
String user;
|
||||
SecretValue password;
|
||||
}
|
||||
@@ -168,8 +168,13 @@ def getJvmArgs() {
|
||||
|
||||
// Why is this not on by default? ...
|
||||
// https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/net/doc-files/net-properties.html
|
||||
// https://stackoverflow.com/questions/53333556/proxy-authentication-with-jdk-11-httpclient
|
||||
// https://stackoverflow.com/questions/75150081/ioexception-too-many-authentication-attempts-limit-3-when-using-jdk-httpcli
|
||||
jvmRunArgs += [
|
||||
'-Djava.net.useSystemProxies=true'
|
||||
'-Djava.net.useSystemProxies=true',
|
||||
'-Djdk.http.auth.proxying.disabledSchemes=""',
|
||||
'-Djdk.http.auth.tunneling.disabledSchemes=""',
|
||||
'-Djdk.httpclient.auth.retrylimit=1'
|
||||
]
|
||||
|
||||
// Fix platform theme detection on macOS
|
||||
|
||||
@@ -177,9 +177,7 @@ public interface ScriptTextSource {
|
||||
|
||||
var req = HttpRequest.newBuilder().GET().uri(URI.create(url)).build();
|
||||
var r = HttpHelper.client().send(req, HttpResponse.BodyHandlers.ofString());
|
||||
if (r.statusCode() >= 400) {
|
||||
throw ErrorEventFactory.expected(new IOException(r.body()));
|
||||
}
|
||||
HttpHelper.checkOrThrow(r);
|
||||
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, r.body());
|
||||
|
||||
55
img/proc/networkProxy_icon-dark.svg
Normal file
55
img/proc/networkProxy_icon-dark.svg
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 204.59 204.59"
|
||||
version="1.1"
|
||||
sodipodi:docname="networkProxy_icon-dark.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.3945823"
|
||||
inkscape:cx="136.24151"
|
||||
inkscape:cy="99.312891"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs856"><style
|
||||
id="style854">.cls-1{fill:#ac55ff;}</style><style
|
||||
id="style2411">.cls-1{fill:#606161;}</style></defs><title
|
||||
id="title858">identity</title><path
|
||||
class="cls-1"
|
||||
d="M332.32,153.7H179.68a26,26,0,0,0-26,26V332.32a26,26,0,0,0,26,26H332.32a26,26,0,0,0,26-26V179.68A26,26,0,0,0,332.32,153.7Zm14,178.62a14,14,0,0,1-14,14H179.68a14,14,0,0,1-14-14V179.68a14,14,0,0,1,14-14H332.32a14,14,0,0,1,14,14Z"
|
||||
id="path860"
|
||||
style="fill:#dfdfdf;fill-opacity:1"
|
||||
transform="translate(-153.7 -153.7)" /><metadata
|
||||
id="metadata932"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:title>identity</dc:title></cc:Work></rdf:RDF></metadata><path
|
||||
d="M 50.618789,177.19298 H 154.7908 a 10.417202,10.417202 0 0 0 10.41722,-10.41719 V 135.52418 A 10.417202,10.417202 0 0 0 154.7908,125.10696 H 50.618789 a 10.417202,10.417202 0 0 0 -10.417198,10.41722 v 31.25161 a 10.417202,10.417202 0 0 0 10.417198,10.41719 z m 0,-41.6688 H 154.7908 v 31.25161 H 50.618789 Z"
|
||||
id="path1"
|
||||
style="stroke-width:5.2086;fill:#147dff;fill-opacity:1" /><path
|
||||
d="m 154.7908,31.352142 -7.34411,7.344134 13.43818,13.490275 H 135.59711 A 36.374266,36.374266 0 0 0 66.661278,73.020945 H 44.524724 L 57.962907,59.53067 50.618789,52.186551 24.575781,78.229559 50.618789,104.27257 57.962907,96.928436 44.524724,83.438162 H 69.812481 A 36.374266,36.374266 0 0 0 138.74831,62.603748 h 22.13656 l -13.43818,13.490276 7.34411,7.344138 26.04301,-26.043012 z M 128.74781,67.812346 A 25.95446,25.95446 0 0 1 82.003214,83.438162 H 102.7048 V 73.020945 H 77.188388 A 25.96592,25.96592 0 0 1 123.40638,52.186551 H 102.7048 v 10.417197 h 25.51641 a 26.052903,26.052903 0 0 1 0.5266,5.208598 z"
|
||||
id="path2"
|
||||
style="stroke-width:5.2086;fill:#3cbb39;fill-opacity:1" /><circle
|
||||
cx="68.352188"
|
||||
cy="150.98668"
|
||||
r="9.7888479"
|
||||
id="circle1"
|
||||
style="stroke-width:9.78885;fill:#f38523;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
55
img/proc/networkProxy_icon.svg
Normal file
55
img/proc/networkProxy_icon.svg
Normal file
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
viewBox="0 0 204.59 204.59"
|
||||
version="1.1"
|
||||
sodipodi:docname="networkProxy_icon.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.3945823"
|
||||
inkscape:cx="136.24151"
|
||||
inkscape:cy="99.312891"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1009"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><defs
|
||||
id="defs856"><style
|
||||
id="style854">.cls-1{fill:#ac55ff;}</style><style
|
||||
id="style2411">.cls-1{fill:#606161;}</style></defs><title
|
||||
id="title858">identity</title><path
|
||||
class="cls-1"
|
||||
d="M332.32,153.7H179.68a26,26,0,0,0-26,26V332.32a26,26,0,0,0,26,26H332.32a26,26,0,0,0,26-26V179.68A26,26,0,0,0,332.32,153.7Zm14,178.62a14,14,0,0,1-14,14H179.68a14,14,0,0,1-14-14V179.68a14,14,0,0,1,14-14H332.32a14,14,0,0,1,14,14Z"
|
||||
id="path860"
|
||||
style="fill:#212121;fill-opacity:1"
|
||||
transform="translate(-153.7 -153.7)" /><metadata
|
||||
id="metadata932"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:title>identity</dc:title></cc:Work></rdf:RDF></metadata><path
|
||||
d="M 50.618789,177.19298 H 154.7908 a 10.417202,10.417202 0 0 0 10.41722,-10.41719 V 135.52418 A 10.417202,10.417202 0 0 0 154.7908,125.10696 H 50.618789 a 10.417202,10.417202 0 0 0 -10.417198,10.41722 v 31.25161 a 10.417202,10.417202 0 0 0 10.417198,10.41719 z m 0,-41.6688 H 154.7908 v 31.25161 H 50.618789 Z"
|
||||
id="path1"
|
||||
style="stroke-width:5.2086;fill:#0066e3;fill-opacity:1" /><path
|
||||
d="m 154.7908,31.352142 -7.34411,7.344134 13.43818,13.490275 H 135.59711 A 36.374266,36.374266 0 0 0 66.661278,73.020945 H 44.524724 L 57.962907,59.53067 50.618789,52.186551 24.575781,78.229559 50.618789,104.27257 57.962907,96.928436 44.524724,83.438162 H 69.812481 A 36.374266,36.374266 0 0 0 138.74831,62.603748 h 22.13656 l -13.43818,13.490276 7.34411,7.344138 26.04301,-26.043012 z M 128.74781,67.812346 A 25.95446,25.95446 0 0 1 82.003214,83.438162 H 102.7048 V 73.020945 H 77.188388 A 25.96592,25.96592 0 0 1 123.40638,52.186551 H 102.7048 v 10.417197 h 25.51641 a 26.052903,26.052903 0 0 1 0.5266,5.208598 z"
|
||||
id="path2"
|
||||
style="stroke-width:5.2086;fill:#30962e;fill-opacity:1" /><circle
|
||||
cx="68.352188"
|
||||
cy="150.98668"
|
||||
r="9.7888479"
|
||||
id="circle1"
|
||||
style="stroke-width:9.78885;fill:#f27b10;fill-opacity:1" /></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
1
lang/strings/fixed_en.properties
generated
1
lang/strings/fixed_en.properties
generated
@@ -168,4 +168,5 @@ protonPassPasswordPlaceholder=Vault Name/Item name
|
||||
protonPass=Proton Pass
|
||||
passwork=Passwork
|
||||
passworkPlaceholder=Item ID
|
||||
socks5=SOCKS5
|
||||
|
||||
|
||||
23
lang/strings/translations_en.properties
generated
23
lang/strings/translations_en.properties
generated
@@ -1237,6 +1237,8 @@ customIpDescription=Override the default local VM IP detection if you use advanc
|
||||
automaticallyDetect=Automatically detect
|
||||
userAddDialogTitle=User creation
|
||||
groupAddDialogTitle=Group creation
|
||||
keyPassphrase=Key passphrase
|
||||
keyPassphraseDescription=The optional passphrase for your key
|
||||
passphrase=Passphrase
|
||||
repeatPassphrase=Repeat passphrase
|
||||
groupSecret=Group secret
|
||||
@@ -2077,3 +2079,24 @@ rdpWindowsSecurityWarningDialogTitle=RDP security warnings
|
||||
rdpWindowsSecurityWarningDialogContent=Since Windows build 26200, Windows will now always show of warning when an unsigned RDP file is launched, regardless of its contents.\n\nYou can disable this warning in the settings menu.
|
||||
openSettings=Open settings
|
||||
toggleSizeLock=Toggle size lock
|
||||
useExternalNetcatForProxies=Use externally installed netcat executable for proxies
|
||||
useExternalNetcatForProxiesDescription=Use locally installed netcat executable to proxy SSH connections instead of the built-in proxy functionality if possible. This requires ncat to be installed on Windows and nc on other operating systems.
|
||||
networkProxy.displayName=Network proxy
|
||||
networkProxy.displayDescription=Configure a proxy server to use as a gateway for connections
|
||||
networkProxyType=Proxy type
|
||||
networkProxyTypeDescription=The protocol of the proxy server
|
||||
networkProxyHost=Host
|
||||
networkProxyHostDescription=The host the proxy server is running on
|
||||
networkProxyPort=Port
|
||||
networkProxyPortDescription=The port the proxy server is listening on
|
||||
networkProxyUsername=Username
|
||||
networkProxyUsernameDescription=The username to log into the proxy if authentication is required
|
||||
networkProxyPassword=Password
|
||||
networkProxyPasswordDescription=The password to log into the proxy if authentication is required
|
||||
httpProxy=HTTP proxy
|
||||
httpProxyDescription=The proxy to use for any kind of HTTP requests sent by XPipe, e.g. update checks and license check requests.
|
||||
httpProxyConfiguration=HTTP proxy configuration
|
||||
addProxy=Add proxy ...
|
||||
httpProxyError=Configure HTTP proxy settings
|
||||
httpProxyErrorDescription=Add HTTP proxy authentication details in the settings menu
|
||||
systemDefault=System default
|
||||
|
||||
Reference in New Issue
Block a user