Squash merge branch 14-release into master

This commit is contained in:
crschnick
2025-01-16 07:29:55 +00:00
parent 48c9f96c03
commit 45f6545fc8
2688 changed files with 41312 additions and 19924 deletions

View File

@@ -10,9 +10,10 @@ public interface ElevationHandler {
return new ElevationHandler() {
@Override
public boolean handleRequest(UUID requestId, CountDown countDown, boolean confirmIfNeeded) {
var r = ElevationHandler.this.handleRequest(requestId, countDown, confirmIfNeeded);
return r || other.handleRequest(requestId, countDown, confirmIfNeeded);
public boolean handleRequest(
ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded) {
var r = ElevationHandler.this.handleRequest(parent, requestId, countDown, confirmIfNeeded);
return r || other.handleRequest(parent, requestId, countDown, confirmIfNeeded);
}
@Override
@@ -23,7 +24,7 @@ public interface ElevationHandler {
};
}
boolean handleRequest(UUID requestId, CountDown countDown, boolean confirmIfNeeded);
boolean handleRequest(ShellControl parent, UUID requestId, CountDown countDown, boolean confirmIfNeeded);
SecretReference getSecretRef();
}

View File

@@ -0,0 +1,15 @@
package io.xpipe.core.process;
import java.io.FilterInputStream;
import java.io.InputStream;
public abstract class LocalProcessInputStream extends FilterInputStream {
protected LocalProcessInputStream(InputStream in) {
super(in);
}
public abstract boolean bufferedAvailable();
public abstract boolean isClosed();
}

View File

@@ -0,0 +1,13 @@
package io.xpipe.core.process;
import java.io.FilterOutputStream;
import java.io.OutputStream;
public abstract class LocalProcessOutputStream extends FilterOutputStream {
protected LocalProcessOutputStream(OutputStream out) {
super(out);
}
public abstract boolean isClosed();
}

View File

@@ -113,7 +113,22 @@ public interface OsType {
@Override
public String getUserHomeDirectory(ShellControl pc) throws Exception {
return pc.executeSimpleStringCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("HOME"));
var r = pc.executeSimpleStringCommand(pc.getShellDialect().getPrintEnvironmentVariableCommand("HOME"));
if (r.isBlank()) {
var user = pc.view().user();
var eval = pc.command("eval echo ~" + user).readStdoutIfPossible();
if (eval.isPresent() && !eval.get().isBlank()) {
return eval.get();
}
if (user.equals("root")) {
return "/root";
} else {
return "/home/" + user;
}
} else {
return r;
}
}
@Override

View File

@@ -33,6 +33,11 @@ public interface ParentSystemAccess {
public String translateToLocalSystemPath(String path) {
throw new UnsupportedOperationException();
}
@Override
public boolean isIdentity() {
return false;
}
};
}
@@ -67,6 +72,11 @@ public interface ParentSystemAccess {
public String translateToLocalSystemPath(String path) {
return path;
}
@Override
public boolean isIdentity() {
return true;
}
};
}
@@ -101,6 +111,11 @@ public interface ParentSystemAccess {
public String translateToLocalSystemPath(String path) throws Exception {
return a1.translateToLocalSystemPath(a2.translateToLocalSystemPath(path));
}
@Override
public boolean isIdentity() {
return a1.isIdentity() && a2.isIdentity();
}
};
}
@@ -119,4 +134,6 @@ public interface ParentSystemAccess {
String translateFromLocalSystemPath(String path) throws Exception;
String translateToLocalSystemPath(String path) throws Exception;
boolean isIdentity();
}

View File

@@ -21,18 +21,12 @@ public interface ProcessControl extends AutoCloseable {
void closeStdin() throws IOException;
boolean isStdinClosed();
boolean isAnyStreamClosed();
boolean isRunning();
boolean isRunning(boolean refresh);
ShellDialect getShellDialect();
void writeLine(String line) throws IOException;
void writeLine(String line, boolean log) throws IOException;
void write(byte[] b) throws IOException;
@Override
void close() throws Exception;

View File

@@ -0,0 +1,15 @@
package io.xpipe.core.process;
import lombok.Value;
@Value
public class ShellCapabilities {
public static ShellCapabilities all() {
return new ShellCapabilities(true, true, true);
}
boolean canReadFiles;
boolean canWriteFiles;
boolean canListFiles;
}

View File

@@ -18,6 +18,25 @@ import java.util.function.Function;
public interface ShellControl extends ProcessControl {
void writeLine(String line) throws IOException;
void writeLine(String line, boolean log) throws IOException;
void write(byte[] b) throws IOException;
@Override
LocalProcessInputStream getStdout();
@Override
LocalProcessOutputStream getStdin();
@Override
LocalProcessInputStream getStderr();
ShellView view();
ShellCapabilities getCapabilities();
Optional<ShellControl> getParentControl();
ShellTtyState getTtyState();
@@ -30,12 +49,16 @@ public interface ShellControl extends ProcessControl {
void setElevationHandler(ElevationHandler ref);
void closeStdout() throws IOException;
List<UUID> getExitUuids();
void setWorkingDirectory(WorkingDirectoryFunction workingDirectory);
Optional<DataStore> getSourceStore();
Optional<UUID> getSourceStoreId();
ShellControl withSourceStore(DataStore store);
List<ShellInitCommand> getInitCommands();
@@ -209,7 +232,7 @@ public interface ShellControl extends ProcessControl {
default <T> T enforceDialect(@NonNull ShellDialect type, FailableFunction<ShellControl, T, Exception> sc)
throws Exception {
if (isRunning() && getShellDialect().equals(type)) {
if (type.equals(getShellDialect())) {
return sc.apply(this);
} else {
try (var sub = subShell(type).start()) {

View File

@@ -16,6 +16,10 @@ import java.util.stream.Stream;
public interface ShellDialect {
String unsetEnvironmentVariableCommand(String var);
ShellCapabilities determineCapabilities();
CommandBuilder launchAsnyc(CommandBuilder cmd);
default String getLicenseFeatureId() {
@@ -64,7 +68,7 @@ public interface ShellDialect {
String addToPathVariableCommand(List<String> entries, boolean append);
default String applyInitFileCommand() {
default String applyInitFileCommand(ShellControl sc) throws Exception {
return null;
}

View File

@@ -0,0 +1,86 @@
package io.xpipe.core.process;
import io.xpipe.core.store.FilePath;
import java.io.InputStream;
import java.util.Optional;
public class ShellView {
protected final ShellControl shellControl;
public ShellView(ShellControl shellControl) {
this.shellControl = shellControl;
}
protected ShellDialect getDialect() {
return shellControl.getShellDialect();
}
public void writeTextFile(FilePath path, String text) throws Exception {
var cc = getDialect().createTextFileWriteCommand(shellControl, text, path.toString());
cc.execute();
}
public void writeScriptFile(FilePath path, String text) throws Exception {
var cc = getDialect().createScriptTextFileWriteCommand(shellControl, text, path.toString());
cc.execute();
}
public void writeStreamFile(FilePath path, InputStream inputStream, long size) throws Exception {
try (var out = getDialect()
.createStreamFileWriteCommand(shellControl, path.toString(), size)
.startExternalStdin()) {
inputStream.transferTo(out);
}
}
public FilePath userHome() throws Exception {
return new FilePath(shellControl.getOsType().getUserHomeDirectory(shellControl));
}
public boolean fileExists(FilePath path) throws Exception {
return getDialect()
.createFileExistsCommand(shellControl, path.toString())
.executeAndCheck();
}
public String user() throws Exception {
return getDialect().printUsernameCommand(shellControl).readStdoutOrThrow();
}
public String getPath() throws Exception {
var path = shellControl
.command(shellControl.getShellDialect().getPrintEnvironmentVariableCommand("PATH"))
.readStdoutOrThrow();
return path;
}
public String getLibraryPath() throws Exception {
var path = shellControl
.command(shellControl.getShellDialect().getPrintEnvironmentVariableCommand("LD_LIBRARY_PATH"))
.readStdoutOrThrow();
return path;
}
public boolean isRoot() throws Exception {
if (shellControl.getOsType() == OsType.WINDOWS) {
return false;
}
var isRoot = shellControl.executeSimpleBooleanCommand("test \"${EUID:-$(id -u)}\" -eq 0");
return isRoot;
}
public Optional<String> findProgram(String name) throws Exception {
var out = shellControl
.command(shellControl.getShellDialect().getWhichCommand(name))
.readStdoutIfPossible();
return out.flatMap(s -> s.lines().findFirst()).map(String::trim);
}
public boolean isInPath(String executable) throws Exception {
return shellControl.executeSimpleBooleanCommand(
shellControl.getShellDialect().getWhichCommand(executable));
}
}

View File

@@ -12,5 +12,5 @@ public class StubShellControl extends WrapperShellControl {
}
@Override
public void close() throws Exception {}
public void close() {}
}

View File

@@ -7,8 +7,6 @@ import io.xpipe.core.util.FailableConsumer;
import lombok.Getter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
@@ -26,6 +24,16 @@ public class WrapperShellControl implements ShellControl {
this.parent = parent;
}
@Override
public ShellView view() {
return parent.view();
}
@Override
public ShellCapabilities getCapabilities() {
return parent.getCapabilities();
}
@Override
public Optional<ShellControl> getParentControl() {
return parent.getParentControl();
@@ -56,6 +64,11 @@ public class WrapperShellControl implements ShellControl {
parent.setElevationHandler(ref);
}
@Override
public void closeStdout() throws IOException {
parent.closeStdout();
}
@Override
public List<UUID> getExitUuids() {
return parent.getExitUuids();
@@ -71,6 +84,11 @@ public class WrapperShellControl implements ShellControl {
return parent.getSourceStore();
}
@Override
public Optional<UUID> getSourceStoreId() {
return parent.getSourceStoreId();
}
@Override
public ShellControl withSourceStore(DataStore store) {
return parent.withSourceStore(store);
@@ -183,13 +201,13 @@ public class WrapperShellControl implements ShellControl {
}
@Override
public boolean isStdinClosed() {
return parent.isStdinClosed();
public boolean isAnyStreamClosed() {
return parent.isAnyStreamClosed();
}
@Override
public boolean isRunning() {
return parent.isRunning();
public boolean isRunning(boolean refresh) {
return parent.isRunning(true);
}
@Override
@@ -233,17 +251,17 @@ public class WrapperShellControl implements ShellControl {
}
@Override
public InputStream getStdout() {
public LocalProcessInputStream getStdout() {
return parent.getStdout();
}
@Override
public OutputStream getStdin() {
public LocalProcessOutputStream getStdin() {
return parent.getStdin();
}
@Override
public InputStream getStderr() {
public LocalProcessInputStream getStderr() {
return parent.getStderr();
}

View File

@@ -1,16 +1,10 @@
package io.xpipe.core.store;
import io.xpipe.core.util.DataStateProvider;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface DataStore {
default boolean isInStorage() {
return DataStateProvider.get().isInStorage(this);
}
default boolean isComplete() {
try {
checkComplete();

View File

@@ -0,0 +1,71 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.List;
public class NetworkTunnelSessionChain extends NetworkTunnelSession {
private final List<NetworkTunnelSession> sessions;
public NetworkTunnelSessionChain(SessionListener listener, List<NetworkTunnelSession> sessions) {
super(listener);
this.sessions = sessions;
}
public ShellControl getShellControl() {
return sessions.getLast().getShellControl();
}
public int getLocalPort() {
return sessions.getFirst().getLocalPort();
}
@Override
public int getRemotePort() {
return sessions.getLast().getRemotePort();
}
@Override
public boolean isRunning() {
return sessions.stream().allMatch(session -> session.isRunning());
}
@Override
public void start() throws Exception {
for (var i = 0; i < sessions.size(); i++) {
try {
sessions.get(i).start();
} catch (Exception e) {
for (var j = 0; j < i; j++) {
var started = sessions.get(j);
try {
started.stop();
} catch (Exception stopEx) {
e.addSuppressed(stopEx);
}
}
throw e;
}
}
}
@Override
public void stop() throws Exception {
Exception ex = null;
for (var i = sessions.size() - 1; i >= 0; i--) {
try {
sessions.get(i).stop();
} catch (Exception e) {
if (ex == null) {
ex = e;
} else {
ex.addSuppressed(e);
}
}
}
if (ex != null) {
throw ex;
}
}
}

View File

@@ -1,7 +1,5 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -15,41 +13,21 @@ public interface NetworkTunnelStore extends DataStore {
return p;
}
interface TunnelFunction {
NetworkTunnelSession create(int localPort, int remotePort, String address) throws Exception;
}
DataStore getNetworkParent();
default boolean requiresTunnel() {
NetworkTunnelStore current = this;
while (true) {
var func = current.tunnelSession();
if (func != null) {
return true;
}
if (current.getNetworkParent() == null) {
return false;
}
if (current.getNetworkParent() instanceof NetworkTunnelStore t) {
current = t;
} else {
return false;
}
}
return getNetworkParent() != null;
}
default boolean isLocallyTunnelable() {
NetworkTunnelStore current = this;
while (true) {
if (current.getNetworkParent() == null) {
var p = current.getNetworkParent();
if (p == null) {
return true;
}
if (current.getNetworkParent() instanceof NetworkTunnelStore t) {
if (p instanceof NetworkTunnelStore t) {
current = t;
} else {
return false;
@@ -63,22 +41,19 @@ public interface NetworkTunnelStore extends DataStore {
"Unable to create tunnel chain as one intermediate system does not support tunneling");
}
var counter = new AtomicInteger();
var sessions = new ArrayList<NetworkTunnelSession>();
NetworkTunnelStore current = this;
do {
var func = current.tunnelSession();
if (func == null) {
continue;
if (current.getNetworkParent() == null) {
break;
}
var currentLocalPort = isLast(current) ? local : randomPort();
var currentLocalPort = current.isLast() ? local : randomPort();
var currentRemotePort =
sessions.isEmpty() ? remotePort : sessions.getLast().getLocalPort();
var t = func.create(currentLocalPort, currentRemotePort, current == this ? address : "localhost");
t.start();
sessions.add(t);
counter.incrementAndGet();
var t = current.createTunnelSession(
currentLocalPort, currentRemotePort, current == this ? address : "localhost");
sessions.addFirst(t);
} while ((current = (NetworkTunnelStore) current.getNetworkParent()) != null);
if (sessions.size() == 1) {
@@ -86,51 +61,17 @@ public interface NetworkTunnelStore extends DataStore {
}
if (sessions.isEmpty()) {
return new NetworkTunnelSession(null) {
@Override
public boolean isRunning() {
return false;
}
@Override
public void start() {}
@Override
public void stop() {}
@Override
public int getLocalPort() {
return remotePort;
}
@Override
public int getRemotePort() {
return remotePort;
}
@Override
public ShellControl getShellControl() {
return null;
}
};
return null;
}
return new SessionChain(running1 -> {}, sessions);
return new NetworkTunnelSessionChain(running1 -> {}, sessions);
}
default boolean isLast(NetworkTunnelStore tunnelStore) {
NetworkTunnelStore current = tunnelStore;
while ((current = (NetworkTunnelStore) current.getNetworkParent()) != null) {
var func = current.tunnelSession();
if (func != null) {
return false;
}
}
return true;
default boolean isLast() {
return getNetworkParent() != null
&& getNetworkParent() instanceof NetworkTunnelStore n
&& n.getNetworkParent() == null;
}
default TunnelFunction tunnelSession() {
return null;
}
NetworkTunnelSession createTunnelSession(int localPort, int remotePort, String address) throws Exception;
}

View File

@@ -1,51 +0,0 @@
package io.xpipe.core.store;
import io.xpipe.core.process.ShellControl;
import java.util.List;
public class SessionChain extends NetworkTunnelSession {
private final List<NetworkTunnelSession> sessions;
private int runningCounter;
public SessionChain(SessionListener listener, List<NetworkTunnelSession> sessions) {
super(listener);
this.sessions = sessions;
sessions.forEach(session -> session.addListener(running -> {
runningCounter += running ? 1 : -1;
}));
}
public ShellControl getShellControl() {
return sessions.getLast().getShellControl();
}
public int getLocalPort() {
return sessions.getFirst().getLocalPort();
}
@Override
public int getRemotePort() {
return sessions.getLast().getRemotePort();
}
@Override
public boolean isRunning() {
return sessions.stream().allMatch(session -> session.isRunning());
}
@Override
public void start() throws Exception {
for (Session session : sessions) {
session.start();
}
}
@Override
public void stop() throws Exception {
for (Session session : sessions) {
session.stop();
}
}
}

View File

@@ -6,17 +6,11 @@ import lombok.experimental.SuperBuilder;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
@@ -25,16 +19,9 @@ public abstract class AesSecretValue extends EncryptedSecretValue {
private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private static final int IV_LENGTH_BYTE = 12;
private static final int AES_KEY_BIT = 128;
private static final int SALT_BIT = 16;
private static final SecretKeyFactory SECRET_FACTORY;
static {
try {
SECRET_FACTORY = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
public AesSecretValue(String encryptedValue) {
super(encryptedValue);
}
public AesSecretValue(char[] secret) {
@@ -45,29 +32,26 @@ public abstract class AesSecretValue extends EncryptedSecretValue {
super(b);
}
protected abstract int getIterationCount();
protected byte[] getNonce(int numBytes) {
byte[] nonce = new byte[numBytes];
new SecureRandom().nextBytes(nonce);
return nonce;
}
protected SecretKey getSecretKey(char[] chars) throws InvalidKeySpecException {
var salt = new byte[SALT_BIT];
new Random(AES_KEY_BIT).nextBytes(salt);
KeySpec spec = new PBEKeySpec(chars, salt, getIterationCount(), AES_KEY_BIT);
return new SecretKeySpec(SECRET_FACTORY.generateSecret(spec).getEncoded(), "AES");
protected String getAlgorithm() {
return "AES/GCM/NoPadding";
}
protected SecretKey getAESKey() throws InvalidKeySpecException {
throw new UnsupportedOperationException();
}
protected abstract SecretKey getSecretKey() throws InvalidKeySpecException;
@Override
@SneakyThrows
public byte[] encrypt(byte[] c) {
SecretKey secretKey = getAESKey();
SecretKey secretKey = getSecretKey();
if (secretKey == null) {
throw new IllegalStateException("Missing secret key");
}
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
var iv = getNonce(IV_LENGTH_BYTE);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
@@ -89,7 +73,11 @@ public abstract class AesSecretValue extends EncryptedSecretValue {
byte[] cipherText = new byte[bb.remaining()];
bb.get(cipherText);
SecretKey secretKey = getAESKey();
SecretKey secretKey = getSecretKey();
if (secretKey == null) {
throw new IllegalStateException("Missing secret key");
}
Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
return cipher.doFinal(cipherText);

View File

@@ -137,7 +137,7 @@ public class CoreJacksonModule extends SimpleModule {
public ShellDialect deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode tree = JacksonMapper.getDefault().readTree(p);
if (tree.isObject()) {
var t = (JsonNode) tree.get("type");
var t = tree.get("type");
if (t == null) {
return null;
}

View File

@@ -15,6 +15,10 @@ public abstract class EncryptedSecretValue implements SecretValue {
String encryptedValue;
public EncryptedSecretValue(String encryptedValue) {
this.encryptedValue = encryptedValue;
}
public EncryptedSecretValue(byte[] b) {
encryptedValue = SecretValue.toBase64e(encrypt(b));
}

View File

@@ -5,9 +5,14 @@ import lombok.EqualsAndHashCode;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Random;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
@JsonTypeName("default")
@SuperBuilder
@@ -15,6 +20,18 @@ import javax.crypto.SecretKey;
@EqualsAndHashCode(callSuper = true)
public class InPlaceSecretValue extends AesSecretValue {
private static final int AES_KEY_BIT = 128;
private static final int SALT_BIT = 16;
private static final SecretKeyFactory SECRET_FACTORY;
static {
try {
SECRET_FACTORY = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
public InPlaceSecretValue(byte[] b) {
super(b);
}
@@ -35,7 +52,6 @@ public class InPlaceSecretValue extends AesSecretValue {
return new InPlaceSecretValue(b);
}
@Override
protected int getIterationCount() {
return 2048;
}
@@ -46,8 +62,12 @@ public class InPlaceSecretValue extends AesSecretValue {
return nonce;
}
protected SecretKey getAESKey() throws InvalidKeySpecException {
return getSecretKey(new char[] {'X', 'P', 'E' << 1});
@Override
protected SecretKey getSecretKey() throws InvalidKeySpecException {
var salt = new byte[SALT_BIT];
new Random(AES_KEY_BIT).nextBytes(salt);
KeySpec spec = new PBEKeySpec(new char[] {'X', 'P', 'E' << 1}, salt, getIterationCount(), AES_KEY_BIT);
return new SecretKeySpec(SECRET_FACTORY.generateSecret(spec).getEncoded(), "AES");
}
@Override

View File

@@ -0,0 +1,8 @@
package io.xpipe.core.util;
import com.fasterxml.jackson.databind.*;
public interface JacksonExtension {
Class<?> getType();
}

View File

@@ -56,10 +56,31 @@ public class JacksonMapper {
@Override
public void init(ModuleLayer layer) {
List<Module> MODULES = findModules(layer);
INSTANCE.registerModules(MODULES);
List<Module> modules = findModules(layer);
INSTANCE.registerModules(modules);
var extensions = findExtensions(layer);
for (var extension : extensions) {
var mod = new SimpleModule();
if (extension instanceof JsonSerializer<?> s) {
add(mod, extension.getType(), s);
}
if (extension instanceof JsonDeserializer<?> d) {
add(mod, extension.getType(), d);
}
INSTANCE.registerModule(mod);
}
init = true;
}
@SuppressWarnings("unchecked")
private <T> void add(SimpleModule mod, Class<?> c, JsonSerializer<?> s) {
mod.addSerializer((Class<T>) c, (JsonSerializer<T>) s);
}
@SuppressWarnings("unchecked")
private <T> void add(SimpleModule mod, Class<?> c, JsonDeserializer<?> s) {
mod.addDeserializer((Class<T>) c, (JsonDeserializer<T>) s);
}
}
private static List<Module> findModules(ModuleLayer layer) {
@@ -72,6 +93,17 @@ public class JacksonMapper {
return modules;
}
private static List<JacksonExtension> findExtensions(ModuleLayer layer) {
ArrayList<JacksonExtension> exts = new ArrayList<>();
ServiceLoader<JacksonExtension> loader = layer != null
? ServiceLoader.load(layer, JacksonExtension.class)
: ServiceLoader.load(JacksonExtension.class);
for (JacksonExtension module : loader) {
exts.add(module);
}
return exts;
}
/**
* Constructs a new ObjectMapper that is able to map all required XPipe classes and also possible extensions.
*/

View File

@@ -1,38 +0,0 @@
package io.xpipe.core.util;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.SneakyThrows;
import lombok.experimental.SuperBuilder;
@SuperBuilder(toBuilder = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public class JacksonizedValue {
public JacksonizedValue() {}
@Override
public final int hashCode() {
var tree = JacksonMapper.getDefault().valueToTree(this);
return tree.hashCode();
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o != null && getClass() != o.getClass()) {
return false;
}
var tree = JacksonMapper.getDefault().valueToTree(this);
var otherTree = JacksonMapper.getDefault().valueToTree(o);
return tree.equals(otherTree);
}
@SneakyThrows
public String toString() {
var tree = JacksonMapper.getDefault().valueToTree(this);
return tree.toPrettyString();
}
}

View File

@@ -20,6 +20,4 @@ public interface ModuleLayerLoader {
}
default void init(ModuleLayer layer) {}
default void reset() {}
}

View File

@@ -12,16 +12,6 @@ public class SecretReference {
UUID secretId;
int subId;
public SecretReference(Object store) {
this.secretId = UuidHelper.generateFromObject(store);
this.subId = 0;
}
public SecretReference(Object store, int sub) {
this.secretId = UuidHelper.generateFromObject(store);
this.subId = sub;
}
public static SecretReference ofUuid(UUID secretId) {
return new SecretReference(secretId, 0);
}

View File

@@ -2,6 +2,7 @@ package io.xpipe.core.util;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Consumer;
@@ -10,6 +11,14 @@ import java.util.function.Function;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public interface SecretValue {
static char[] toBase64eChars(byte[] b) {
var base64 = Base64.getEncoder().encode(b);
var charBuf = ByteBuffer.wrap(base64).asCharBuffer();
char[] arr = new char[charBuf.remaining()];
charBuf.get(arr);
return arr;
}
static String toBase64e(byte[] b) {
var base64 = Base64.getEncoder().encodeToString(b);
return base64.replace("/", "-");

View File

@@ -184,6 +184,19 @@ public class StreamCharset {
return new InputStreamReader(stream, charset);
}
public byte[] toBytes(String s) {
var raw = s.getBytes(charset);
if (hasByteOrderMark()) {
var bom = getByteOrderMark();
var r = new byte[raw.length + bom.length];
System.arraycopy(bom, 0, r, 0, bom.length);
System.arraycopy(raw, 0, r, bom.length, raw.length);
return r;
} else {
return raw;
}
}
@Override
public int hashCode() {
int result = Objects.hash(charset);

View File

@@ -1,16 +1,10 @@
package io.xpipe.core.util;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
public class UuidHelper {
public static UUID generateFromObject(Object... o) {
return UUID.nameUUIDFromBytes(Arrays.toString(o).getBytes(StandardCharsets.UTF_8));
}
public static Optional<UUID> parse(String s) {
try {
return Optional.of(UUID.fromString(s));

View File

@@ -6,6 +6,7 @@ import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
@Getter
@@ -27,6 +28,17 @@ public enum XPipeDaemonMode {
this.nameAlternatives = nameAlternatives;
}
public static Optional<XPipeDaemonMode> getIfPresent(String name) {
if (name == null) {
return Optional.empty();
}
return Arrays.stream(XPipeDaemonMode.values())
.filter(xPipeDaemonMode ->
xPipeDaemonMode.getNameAlternatives().contains(name.toLowerCase(Locale.ROOT)))
.findAny();
}
public static XPipeDaemonMode get(String name) {
return Arrays.stream(XPipeDaemonMode.values())
.filter(xPipeDaemonMode ->

View File

@@ -1,6 +1,7 @@
import io.xpipe.core.process.ShellDialect;
import io.xpipe.core.process.ShellDialects;
import io.xpipe.core.util.CoreJacksonModule;
import io.xpipe.core.util.JacksonExtension;
import io.xpipe.core.util.JacksonMapper;
import io.xpipe.core.util.ModuleLayerLoader;
@@ -22,6 +23,7 @@ open module io.xpipe.core {
uses io.xpipe.core.util.DataStateProvider;
uses ModuleLayerLoader;
uses ShellDialect;
uses JacksonExtension;
provides ModuleLayerLoader with
JacksonMapper.Loader,