diff --git a/app/build.gradle b/app/build.gradle index 301049013..54b2645b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,9 +53,6 @@ dependencies { api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar") api 'org.bouncycastle:bcprov-jdk18on:1.80' api 'info.picocli:picocli:4.7.6' - api ('org.kohsuke:github-api:1.326') { - exclude group: 'org.apache.commons', module: 'commons-lang3' - } api 'org.apache.commons:commons-lang3:3.17.0' api 'io.sentry:sentry:7.20.0' api 'commons-io:commons-io:2.18.0' diff --git a/app/src/main/java/io/xpipe/app/ext/ConnectionFileSystem.java b/app/src/main/java/io/xpipe/app/ext/ConnectionFileSystem.java index 7ab21fdfd..da68c0b1e 100644 --- a/app/src/main/java/io/xpipe/app/ext/ConnectionFileSystem.java +++ b/app/src/main/java/io/xpipe/app/ext/ConnectionFileSystem.java @@ -55,7 +55,8 @@ public class ConnectionFileSystem implements FileSystem { if (!shellControl.getTtyState().isPreservesOutput() || !shellControl.getTtyState().isSupportsInput()) { throw ErrorEvent.expected(new UnsupportedOperationException( - "Shell has a PTY allocated and as a result does not support file system operations. For more information see " + Hyperlinks.DOCS_TTY)); + "Shell has a PTY allocated and as a result does not support file system operations. For more information see " + + Hyperlinks.DOCS_TTY)); } shellControl.checkLicenseOrThrow(); diff --git a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java index 6bf20807f..32901c6dd 100644 --- a/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java +++ b/app/src/main/java/io/xpipe/app/prefs/AppPrefs.java @@ -132,6 +132,8 @@ public class AppPrefs { mapLocal(new SimpleBooleanProperty(false), "enforceWindowModality", Boolean.class, false); final BooleanProperty checkForSecurityUpdates = mapLocal(new SimpleBooleanProperty(true), "checkForSecurityUpdates", Boolean.class, false); + final BooleanProperty disableApiHttpsTlsCheck = + mapLocal(new SimpleBooleanProperty(false), "disableApiHttpsTlsCheck", Boolean.class, false); final BooleanProperty condenseConnectionDisplay = mapLocal(new SimpleBooleanProperty(false), "condenseConnectionDisplay", Boolean.class, false); final BooleanProperty showChildCategoriesInParentCategory = diff --git a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationHelper.java b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationHelper.java index 745376d5d..bad0db5fb 100644 --- a/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationHelper.java +++ b/app/src/main/java/io/xpipe/app/prefs/ExternalApplicationHelper.java @@ -3,10 +3,10 @@ package io.xpipe.app.prefs; import io.xpipe.app.issue.TrackEvent; import io.xpipe.app.util.LocalShell; import io.xpipe.core.process.CommandBuilder; -import io.xpipe.core.process.ShellDialects; import java.util.Arrays; import java.util.Locale; +import java.util.stream.Collectors; public class ExternalApplicationHelper { @@ -21,17 +21,32 @@ public class ExternalApplicationHelper { } public static void startAsync(String raw) throws Exception { - try (var sc = LocalShell.getShell().start()) { - if (ShellDialects.isPowershell(sc)) { - // Do the best effort here - // This does not respect quoting rules, but otherwise powershell wouldn't work at all - var split = raw.split("\\s+"); - var splitBuilder = CommandBuilder.of().addAll(Arrays.asList(split)); - startAsync(splitBuilder); - } else { - startAsync(CommandBuilder.ofString(raw)); - } + if (raw == null) { + return; } + + raw = raw.trim(); + var split = Arrays.asList(raw.split("\\s+")); + if (split.size() == 0) { + return; + } + + String exec; + String args; + if (raw.startsWith("\"")) { + var end = raw.substring(1).indexOf("\""); + if (end == -1) { + return; + } + end++; + exec = raw.substring(1, end); + args = raw.substring(end + 1).trim(); + } else { + exec = split.getFirst(); + args = split.stream().skip(1).collect(Collectors.joining(" ")); + } + + startAsync(CommandBuilder.of().addFile(exec).add(args)); } public static void startAsync(CommandBuilder b) throws Exception { diff --git a/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java b/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java index 59ebd59ca..8e88f5a5e 100644 --- a/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java +++ b/app/src/main/java/io/xpipe/app/prefs/SecurityCategory.java @@ -30,7 +30,9 @@ public class SecurityCategory extends AppPrefsCategory { .pref(prefs.dontAutomaticallyStartVmSshServer) .addToggle(prefs.dontAutomaticallyStartVmSshServer) .pref(prefs.disableTerminalRemotePasswordPreparation) - .addToggle(prefs.disableTerminalRemotePasswordPreparation)); + .addToggle(prefs.disableTerminalRemotePasswordPreparation) + .pref(prefs.disableApiHttpsTlsCheck) + .addToggle(prefs.disableApiHttpsTlsCheck)); return builder.buildComp(); } } diff --git a/app/src/main/java/io/xpipe/app/terminal/GnomeConsoleType.java b/app/src/main/java/io/xpipe/app/terminal/GnomeConsoleType.java index 72346b981..482425fc7 100644 --- a/app/src/main/java/io/xpipe/app/terminal/GnomeConsoleType.java +++ b/app/src/main/java/io/xpipe/app/terminal/GnomeConsoleType.java @@ -1,9 +1,6 @@ package io.xpipe.app.terminal; -import io.xpipe.app.util.CommandSupport; -import io.xpipe.app.util.LocalShell; import io.xpipe.core.process.CommandBuilder; -import io.xpipe.core.process.ShellControl; public class GnomeConsoleType extends ExternalTerminalType.SimplePathType implements TrackableTerminalType { diff --git a/app/src/main/java/io/xpipe/app/terminal/MobaXTermTerminalType.java b/app/src/main/java/io/xpipe/app/terminal/MobaXTermTerminalType.java index c645f86b7..a933bce65 100644 --- a/app/src/main/java/io/xpipe/app/terminal/MobaXTermTerminalType.java +++ b/app/src/main/java/io/xpipe/app/terminal/MobaXTermTerminalType.java @@ -56,7 +56,9 @@ public class MobaXTermTerminalType extends ExternalTerminalType.WindowsType { SshLocalBridge.init(); var b = SshLocalBridge.get(); var abs = b.getIdentityKey().toAbsolutePath(); - var drivePath = "/drives/" + abs.getRoot().toString().substring(0, 1).toLowerCase() + "/" + abs.getRoot().relativize(abs).toString().replaceAll("\\\\", "/"); + var drivePath = + "/drives/" + abs.getRoot().toString().substring(0, 1).toLowerCase() + "/" + + abs.getRoot().relativize(abs).toString().replaceAll("\\\\", "/"); var winPath = b.getIdentityKey().toString().replaceAll("\\\\", "\\\\\\\\"); var command = CommandBuilder.of() .add("ssh") diff --git a/app/src/main/java/io/xpipe/app/update/AppDownloads.java b/app/src/main/java/io/xpipe/app/update/AppDownloads.java index e0426f9d6..98885bdc7 100644 --- a/app/src/main/java/io/xpipe/app/update/AppDownloads.java +++ b/app/src/main/java/io/xpipe/app/update/AppDownloads.java @@ -4,140 +4,61 @@ import io.xpipe.app.core.AppDistributionType; import io.xpipe.app.core.AppProperties; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.issue.TrackEvent; +import io.xpipe.app.util.HttpHelper; import io.xpipe.app.util.LicenseProvider; import io.xpipe.core.process.OsType; import io.xpipe.core.util.JacksonMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import lombok.Value; import org.apache.commons.io.FileUtils; -import org.kohsuke.github.GHRelease; -import org.kohsuke.github.GHRepository; -import org.kohsuke.github.GitHubBuilder; -import org.kohsuke.github.RateLimitHandler; -import org.kohsuke.github.authorization.AuthorizationProvider; import java.io.IOException; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Optional; public class AppDownloads { - private static GHRepository repository; - - @SuppressWarnings("deprecation") - private static GHRepository getRepository() throws IOException { - if (repository != null) { - return repository; + public static Path downloadInstaller(String version) throws Exception { + var release = Release.of(version); + var builder = HttpRequest.newBuilder(); + 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)); } - var github = new GitHubBuilder() - .withRateLimitHandler(RateLimitHandler.FAIL) - .withAuthorizationProvider(AuthorizationProvider.ANONYMOUS) - .build(); - repository = github.getRepository(AppProperties.get().isStaging() ? "xpipe-io/xpipe-ptb" : "xpipe-io/xpipe"); - return repository; + var downloadFile = FileUtils.getTempDirectory().toPath().resolve(release.getFile()); + Files.write(downloadFile, response.body()); + TrackEvent.withInfo("Downloaded asset") + .tag("version", version) + .tag("url", release.getUrl()) + .tag("size", FileUtils.byteCountToDisplaySize(response.body().length)) + .tag("target", downloadFile) + .handle(); + + return downloadFile; } - public static Optional downloadInstaller( - AppInstaller.InstallerAssetType iAsset, String version, boolean omitErrors) { - var release = AppDownloads.getRelease(version, omitErrors); - if (release.isEmpty()) { - return Optional.empty(); - } - - try { - var asset = release.orElseThrow().listAssets().toList().stream() - .filter(ghAsset -> iAsset.isCorrectAsset(ghAsset.getName())) - .findAny(); - if (asset.isEmpty()) { - ErrorEvent.fromMessage("No matching asset found for " + iAsset.getExtension()); - return Optional.empty(); - } - - var builder = HttpRequest.newBuilder(); - var httpRequest = builder.uri(URI.create(asset.get().getBrowserDownloadUrl())) - .GET() - .build(); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofByteArray()); - if (response.statusCode() != 200) { - throw new IOException(new String(response.body(), StandardCharsets.UTF_8)); - } - var downloadFile = - FileUtils.getTempDirectory().toPath().resolve(asset.get().getName()); - Files.write(downloadFile, response.body()); - TrackEvent.withInfo("Downloaded asset") - .tag("version", version) - .tag("url", asset.get().getBrowserDownloadUrl()) - .tag("size", FileUtils.byteCountToDisplaySize(response.body().length)) - .tag("target", downloadFile) - .handle(); - - return Optional.of(downloadFile); - } catch (Throwable t) { - ErrorEvent.fromThrowable(t).omitted(omitErrors).expected().handle(); - return Optional.empty(); - } - } - - public static Optional downloadChangelog(String version, boolean omitErrors) { - var release = AppDownloads.getRelease(version, omitErrors); - if (release.isEmpty()) { - return Optional.empty(); - } - - try { - var uri = URI.create("https://api.xpipe.io/changelog?from=" - + AppProperties.get().getVersion() + "&to=" + version + "&stage=" - + AppProperties.get().isStaging()); - var builder = HttpRequest.newBuilder(); - var httpRequest = builder.uri(uri).GET().build(); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { - throw new IOException(response.body()); - } - var json = JacksonMapper.getDefault().readTree(response.body()); - var changelog = json.required("changelog").asText(); - return Optional.of(changelog); - } catch (Throwable t) { - ErrorEvent.fromThrowable(t).omit().expected().handle(); - } - - try { - var asset = release.get().listAssets().toList().stream() - .filter(ghAsset -> ghAsset.getName().equals("changelog.md")) - .findAny(); - - if (asset.isEmpty()) { - return Optional.empty(); - } - - var uri = URI.create(asset.get().getBrowserDownloadUrl()); - var builder = HttpRequest.newBuilder(); - var httpRequest = builder.uri(uri).GET().build(); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); - var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { - throw new IOException(response.body()); - } - return Optional.of(response.body()); - } catch (Throwable t) { - ErrorEvent.fromThrowable(t).omitted(omitErrors).expected().handle(); - return Optional.empty(); + public static String downloadChangelog(String version) throws Exception { + var uri = URI.create("https://api.xpipe.io/changelog?from=" + + AppProperties.get().getVersion() + "&to=" + version + "&stage=" + + AppProperties.get().isStaging()); + var builder = HttpRequest.newBuilder(); + var httpRequest = builder.uri(uri).GET().build(); + var client = HttpHelper.client(); + var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() >= 400) { + throw new IOException(response.body()); } + var json = JacksonMapper.getDefault().readTree(response.body()); + var changelog = json.required("changelog").asText(); + return changelog; } private static String queryLatestVersion(boolean first, boolean securityOnly) throws Exception { @@ -158,11 +79,9 @@ public class AppDownloads { var httpRequest = builder.uri(url) .POST(HttpRequest.BodyPublishers.ofString(req.toPrettyString())) .build(); - var client = HttpClient.newBuilder() - .followRedirects(HttpClient.Redirect.NORMAL) - .build(); + var client = HttpHelper.client(); var response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { + if (response.statusCode() >= 400) { throw new IOException(response.body()); } @@ -176,24 +95,38 @@ public class AppDownloads { return ver; } - public static Optional queryLatestRelease(boolean first, boolean securityOnly) throws Exception { + public static Release queryLatestRelease(boolean first, boolean securityOnly) throws Exception { try { var ver = queryLatestVersion(first, securityOnly); - var repo = getRepository(); - var rel = repo.getReleaseByTagName(ver); - return Optional.ofNullable(rel); + return Release.of(ver); } catch (Exception e) { throw ErrorEvent.expected(e); } } - public static Optional getRelease(String version, boolean omitErrors) { - try { - var repo = getRepository(); - return Optional.ofNullable(repo.getReleaseByTagName(version)); - } catch (IOException e) { - ErrorEvent.fromThrowable(e).omitted(omitErrors).expected().handle(); - return Optional.empty(); + @Value + public static class Release { + + public static Release of(String tag) { + var type = AppInstaller.getSuitablePlatformAsset(); + var os = + switch (OsType.getLocal()) { + case OsType.Linux linux -> "linux"; + case OsType.MacOs macOs -> "macos"; + case OsType.Windows windows -> "windows"; + }; + var arch = AppProperties.get().getArch(); + var name = "xpipe-installer-%s-%s.%s".formatted(os, arch, type.getExtension()); + var url = "https://github.com/xpipe-io/%s/releases/download/%s/%s" + .formatted(AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe", tag, name); + var browser = "https://github.com/xpipe-io/%s/releases/%s" + .formatted(AppProperties.get().isStaging() ? "xpipe-ptb" : "xpipe", tag); + return new Release(tag, url, browser, name); } + + String tag; + String url; + String browserUrl; + String file; } } diff --git a/app/src/main/java/io/xpipe/app/update/AppInstaller.java b/app/src/main/java/io/xpipe/app/update/AppInstaller.java index bafcec451..3d7e41dc3 100644 --- a/app/src/main/java/io/xpipe/app/update/AppInstaller.java +++ b/app/src/main/java/io/xpipe/app/update/AppInstaller.java @@ -89,21 +89,17 @@ public class AppInstaller { var logFile = FileNames.join( logsDir, "installer_" + file.getFileName().toString() + ".log"); var systemWide = isSystemWide(); - var command = LocalShell.getShell().getShellDialect().equals(ShellDialects.CMD) - ? getCmdCommand(file.toString(), logFile, restartExec, systemWide) + var command = LocalShell.getShell().getShellDialect().equals(ShellDialects.CMD) && !systemWide + ? getCmdCommand(file.toString(), logFile, restartExec) : getPowershellCommand(file.toString(), logFile, restartExec, systemWide); String toRun; - if (ProcessControlProvider.get().getEffectiveLocalDialect() == ShellDialects.CMD) { - toRun = systemWide - ? "powershell -Command Start-Process -Verb runAs -WindowStyle Minimized -FilePath cmd -ArgumentList \"/c\", '\"" - + ScriptHelper.createLocalExecScript(command) + "\"'" - : "start \"XPipe Updater\" /min cmd /c \"" + ScriptHelper.createLocalExecScript(command) - + "\""; + if (ProcessControlProvider.get().getEffectiveLocalDialect() == ShellDialects.CMD && !systemWide) { + toRun = "start \"XPipe Updater\" /min cmd /c \"" + ScriptHelper.createLocalExecScript(command) + + "\""; } else { toRun = "Start-Process -WindowStyle Minimized -FilePath powershell -ArgumentList \"-ExecutionPolicy\", \"Bypass\", \"-File\", \"`\"" - + ScriptHelper.createLocalExecScript(command) + "`\"\"" - + (systemWide ? " -Verb runAs" : ""); + + ScriptHelper.createLocalExecScript(command) + "`\"\""; } runAndClose(() -> { LocalShell.getShell().executeSimpleCommand(toRun); @@ -112,7 +108,7 @@ public class AppInstaller { @Override public String getExtension() { - return ".msi"; + return "msi"; } private boolean isSystemWide() { @@ -120,8 +116,8 @@ public class AppInstaller { XPipeInstallation.getCurrentInstallationBasePath().resolve("system")); } - private String getCmdCommand(String file, String logFile, String exec, boolean systemWide) { - var args = "MSIFASTINSTALL=7 DISABLEROLLBACK=1" + (systemWide ? " ALLUSERS=1" : ""); + private String getCmdCommand(String file, String logFile, String exec) { + var args = "MSIFASTINSTALL=7 DISABLEROLLBACK=1"; return String.format( """ echo Installing %s ... @@ -138,17 +134,18 @@ public class AppInstaller { private String getPowershellCommand(String file, String logFile, String exec, boolean systemWide) { var property = "MSIFASTINSTALL=7 DISABLEROLLBACK=1" + (systemWide ? " ALLUSERS=1" : ""); var startProcessProperty = ", MSIFASTINSTALL=7, DISABLEROLLBACK=1" + (systemWide ? ", ALLUSERS=1" : ""); + var runas = systemWide ? "-Verb runAs" : ""; return String.format( """ echo Installing %s ... cd "$env:HOMEDRIVE\\$env:HOMEPATH" echo '+ msiexec /i "%s" /lv "%s" /qb%s' - Start-Process msiexec -Wait -ArgumentList "/i", "`"%s`"", "/lv", "`"%s`"", "/qb"%s + Start-Process %s -FilePath msiexec -Wait -ArgumentList "/i", "`"%s`"", "/lv", "`"%s`"", "/qb"%s echo 'Starting XPipe ...' echo '+ "%s"' Start-Process -FilePath "%s" """, - file, file, logFile, property, file, logFile, startProcessProperty, exec, exec); + file, file, logFile, property, runas, file, logFile, startProcessProperty, exec, exec); } } @@ -189,7 +186,7 @@ public class AppInstaller { @Override public String getExtension() { - return ".deb"; + return "deb"; } } @@ -230,7 +227,7 @@ public class AppInstaller { @Override public String getExtension() { - return ".rpm"; + return "rpm"; } } @@ -271,7 +268,7 @@ public class AppInstaller { @Override public String getExtension() { - return ".pkg"; + return "pkg"; } } } diff --git a/app/src/main/java/io/xpipe/app/update/GitHubUpdater.java b/app/src/main/java/io/xpipe/app/update/GitHubUpdater.java index dde5c3c16..092b6014a 100644 --- a/app/src/main/java/io/xpipe/app/update/GitHubUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/GitHubUpdater.java @@ -7,8 +7,6 @@ import io.xpipe.app.core.AppProperties; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.Hyperlinks; -import org.kohsuke.github.GHRelease; - import java.nio.file.Files; import java.time.Instant; import java.util.ArrayList; @@ -45,25 +43,18 @@ public class GitHubUpdater extends UpdateHandler { return list; } - public void prepareUpdateImpl() { - var downloadFile = AppDownloads.downloadInstaller( - lastUpdateCheckResult.getValue().getAssetType(), - lastUpdateCheckResult.getValue().getVersion(), - false); - if (downloadFile.isEmpty()) { - return; - } - + public void prepareUpdateImpl() throws Exception { + var downloadFile = + AppDownloads.downloadInstaller(lastUpdateCheckResult.getValue().getVersion()); var changelogString = - AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().getVersion(), false); - var changelog = changelogString.orElse(null); + AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().getVersion()); var rel = new PreparedUpdate( AppProperties.get().getVersion(), AppDistributionType.get().getId(), lastUpdateCheckResult.getValue().getVersion(), lastUpdateCheckResult.getValue().getReleaseUrl(), - downloadFile.get(), - changelog, + downloadFile, + changelogString, lastUpdateCheckResult.getValue().getAssetType(), lastUpdateCheckResult.getValue().isSecurityOnly()); preparedUpdate.setValue(rel); @@ -91,30 +82,16 @@ public class GitHubUpdater extends UpdateHandler { public synchronized AvailableRelease refreshUpdateCheckImpl(boolean first, boolean securityOnly) throws Exception { var rel = AppDownloads.queryLatestRelease(first, securityOnly); - event("Determined latest suitable release " - + rel.map(GHRelease::getName).orElse(null)); - - if (rel.isEmpty()) { - lastUpdateCheckResult.setValue(null); - return null; - } - - var isUpdate = isUpdate(rel.get().getTagName()); + event("Determined latest suitable release " + rel.getTag()); + var isUpdate = isUpdate(rel.getTag()); var assetType = AppInstaller.getSuitablePlatformAsset(); - var ghAsset = rel.orElseThrow().listAssets().toList().stream() - .filter(g -> assetType.isCorrectAsset(g.getName())) - .findAny(); - if (ghAsset.isEmpty()) { - return null; - } - - event("Selected asset " + ghAsset.get().getName()); + event("Selected asset " + rel.getFile()); lastUpdateCheckResult.setValue(new AvailableRelease( AppProperties.get().getVersion(), AppDistributionType.get().getId(), - rel.get().getTagName(), - rel.get().getHtmlUrl().toString(), - ghAsset.get().getBrowserDownloadUrl(), + rel.getTag(), + rel.getBrowserUrl(), + rel.getUrl(), assetType, Instant.now(), isUpdate, diff --git a/app/src/main/java/io/xpipe/app/update/PortableUpdater.java b/app/src/main/java/io/xpipe/app/update/PortableUpdater.java index c0615521c..21d2bc47f 100644 --- a/app/src/main/java/io/xpipe/app/update/PortableUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/PortableUpdater.java @@ -5,8 +5,6 @@ import io.xpipe.app.core.AppDistributionType; import io.xpipe.app.core.AppProperties; import io.xpipe.app.util.Hyperlinks; -import org.kohsuke.github.GHRelease; - import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -37,20 +35,13 @@ public class PortableUpdater extends UpdateHandler { public synchronized AvailableRelease refreshUpdateCheckImpl(boolean first, boolean securityOnly) throws Exception { var rel = AppDownloads.queryLatestRelease(first, securityOnly); - event("Determined latest suitable release " - + rel.map(GHRelease::getName).orElse(null)); - - if (rel.isEmpty()) { - lastUpdateCheckResult.setValue(null); - return null; - } - - var isUpdate = isUpdate(rel.get().getTagName()); + event("Determined latest suitable release " + rel.getTag()); + var isUpdate = isUpdate(rel.getTag()); lastUpdateCheckResult.setValue(new AvailableRelease( AppProperties.get().getVersion(), AppDistributionType.get().getId(), - rel.get().getTagName(), - rel.get().getHtmlUrl().toString(), + rel.getTag(), + rel.getBrowserUrl(), null, null, Instant.now(), diff --git a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java index dd6987436..fad6f55de 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateHandler.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateHandler.java @@ -181,23 +181,23 @@ public abstract class UpdateHandler { if (AppProperties.get().isStaging() && preparedUpdate.getValue() != null) { UpdateAvailableDialog.showIfNeeded(); } + } catch (Throwable t) { + ErrorEvent.fromThrowable(t).handle(); } } public abstract List createActions(); - public void prepareUpdateImpl() { + public void prepareUpdateImpl() throws Exception { var changelogString = - AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().getVersion(), false); - var changelog = changelogString.orElse(null); - + AppDownloads.downloadChangelog(lastUpdateCheckResult.getValue().getVersion()); var rel = new PreparedUpdate( AppProperties.get().getVersion(), AppDistributionType.get().getId(), lastUpdateCheckResult.getValue().getVersion(), lastUpdateCheckResult.getValue().getReleaseUrl(), null, - changelog, + changelogString, lastUpdateCheckResult.getValue().getAssetType(), lastUpdateCheckResult.getValue().isSecurityOnly()); preparedUpdate.setValue(rel); diff --git a/app/src/main/java/io/xpipe/app/util/HttpHelper.java b/app/src/main/java/io/xpipe/app/util/HttpHelper.java new file mode 100644 index 000000000..de393dfee --- /dev/null +++ b/app/src/main/java/io/xpipe/app/util/HttpHelper.java @@ -0,0 +1,39 @@ +package io.xpipe.app.util; + +import io.xpipe.app.prefs.AppPrefs; + +import lombok.SneakyThrows; + +import java.net.http.HttpClient; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class HttpHelper { + + @SneakyThrows + public static HttpClient client() { + var builder = HttpClient.newBuilder(); + builder.followRedirects(HttpClient.Redirect.NORMAL); + if (AppPrefs.get() != null && AppPrefs.get().disableApiAuthentication().get()) { + var sslContext = SSLContext.getInstance("TLS"); + var trustManager = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[] {}; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + }; + sslContext.init(null, new TrustManager[] {trustManager}, new SecureRandom()); + builder.sslContext(sslContext); + } + return builder.build(); + } +} diff --git a/app/src/main/java/module-info.java b/app/src/main/java/module-info.java index c000ae695..c352b591c 100644 --- a/app/src/main/java/module-info.java +++ b/app/src/main/java/module-info.java @@ -68,7 +68,6 @@ open module io.xpipe.app { requires org.kordamp.ikonli.material; requires io.sentry; requires io.xpipe.beacon; - requires org.kohsuke.github; requires info.picocli; requires java.instrument; requires java.management; diff --git a/dist/licenses/github-api.license b/dist/licenses/github-api.license deleted file mode 100644 index a12243f2f..000000000 --- a/dist/licenses/github-api.license +++ /dev/null @@ -1,22 +0,0 @@ - Copyright (c) 2011- Kohsuke Kawaguchi and other contributors - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. diff --git a/dist/licenses/github-api.properties b/dist/licenses/github-api.properties deleted file mode 100644 index 950dba9c8..000000000 --- a/dist/licenses/github-api.properties +++ /dev/null @@ -1,4 +0,0 @@ -name=Java API for GitHub -version=1.326 -license=MIT License -link=https://github.com/hub4j/github-api \ No newline at end of file diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index d42841269..f8ea8a998 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -1332,5 +1332,5 @@ iconSourceDeletionContent=Do you want to delete this icon source and all associa refreshIcons=Refresh icons refreshIconsDescription=Retrieving, rendering, and caching all available 1000+ icons to .png files. This may take a while ... vaultUserLegacy=Vault user (Limited legacy compatibility mode) -encryptVaultMultiUserTitle=Full old encryption for teams -encryptVaultMultiUserContent=It is not possible to enable this option for team vaults, as it would render all data unusable for other users. If you want +disableApiHttpsTlsCheck=Disable API HTTPS request certificate verification +disableApiHttpsTlsCheckDescription=If your organization is decrypting your HTTPS traffic in firewalls using SSL interception, any update checks or license checks will fail due to the certificates not matching up. You can fix this by enabling this option and disabling TLS certificate validation.