Rework shell stores, other various small changes

This commit is contained in:
Christopher Schnick committed 2022-11-16 23:40:59 +01:00
1 parent 02b622fcf9
commit 85389d26f9
40 files changed
+626 -553

No files matched your search

@@ -1,6 +1,7 @@
package io.xpipe.core.charsetter;
import io.xpipe.core.store.FileStore;
import io.xpipe.core.store.MachineStore;
import io.xpipe.core.store.StreamDataStore;
import lombok.Value;
@@ -107,10 +108,11 @@ public abstract class Charsetter {
}
}
if (store instanceof FileStore fileStore) {
var newline = fileStore.getMachine().getNewLine();
if (store instanceof FileStore fileStore && fileStore.getFileSystem() instanceof MachineStore m) {
if (result.getNewLine() == null) {
result = new Result(result.getCharset(), newline);
try (var pc = m.create().start()) {
result = new Result(result.getCharset(), pc.getShellType().getNewLine());
}
}
}
@@ -7,12 +7,14 @@ import io.xpipe.core.source.RawWriteConnection;
import io.xpipe.core.source.WriteMode;
import io.xpipe.core.store.StreamDataStore;
import lombok.experimental.SuperBuilder;
import lombok.extern.jackson.Jacksonized;
import java.io.InputStream;
import java.io.OutputStream;
@JsonTypeName("binary")
@SuperBuilder
@Jacksonized
public class BinarySource extends RawDataSource<StreamDataStore> {
@Override
@@ -46,8 +46,14 @@ public abstract class DataSource<DS extends DataStore> extends JacksonizedValue
throw new AssertionError(ex);
}
}
public void test() throws Exception {
store.validate();
public boolean isComplete() {
try {
checkComplete();
return true;
} catch (Exception ignored) {
return false;
}
}
public void checkComplete() throws Exception {
@@ -0,0 +1,122 @@
package io.xpipe.core.store;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
public interface CommandProcessControl extends ProcessControl {
default InputStream startExternalStdout() throws Exception {
start();
discardErr();
return new FilterInputStream(getStdout()) {
@Override
public void close() throws IOException {
getStdout().close();
CommandProcessControl.this.close();
}
};
}
default OutputStream startExternalStdin() throws Exception {
try (CommandProcessControl pc = start()) {
pc.discardOut();
pc.discardErr();
return new FilterOutputStream(getStdin()) {
@Override
public void close() throws IOException {
pc.getStdin().close();
pc.close();
}
};
} catch (Exception e) {
throw e;
}
}
CommandProcessControl customCharset(Charset charset);
int getExitCode();
CommandProcessControl elevated();
@Override
CommandProcessControl start() throws Exception;
@Override
CommandProcessControl exitTimeout(int timeout);
String readOnlyStdout() throws Exception;
public default void discardOrThrow() throws Exception {
readOrThrow();
}
public default boolean startAndCheckExit() {
try (var pc = start()) {
return pc.discardAndCheckExit();
} catch (Exception e) {
return false;
}
}
public default boolean discardAndCheckExit() {
try {
discardOrThrow();
return true;
} catch (Exception ex) {
return false;
}
}
public default Optional<String> readStderrIfPresent() throws Exception {
discardOut();
var bytes = getStderr().readAllBytes();
var string = new String(bytes, getCharset());
var ec = waitFor();
return ec ? Optional.of(string) : Optional.empty();
}
public default String readOrThrow() throws Exception {
AtomicReference<String> readError = new AtomicReference<>("");
var errorThread = new Thread(() -> {
try {
readError.set(new String(getStderr().readAllBytes(), getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
errorThread.setDaemon(true);
errorThread.start();
AtomicReference<String> read = new AtomicReference<>("");
var t = new Thread(() -> {
try {
read.set(readLine());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
var ec = waitFor();
if (!ec) {
throw new ProcessOutputException("Command timed out");
}
var exitCode = getExitCode();
if (exitCode == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
return read.get().trim();
} else {
throw new ProcessOutputException(
"Command returned with " + ec + ": " + readError.get().trim());
}
}
Thread discardOut();
Thread discardErr();
}
@@ -0,0 +1,6 @@
package io.xpipe.core.store;
public interface CommandsStore extends DataStore {
CommandProcessControl create() throws Exception;
}
@@ -11,7 +11,7 @@ import java.io.OutputStream;
import java.nio.file.Path;
/**
* Represents a file located on a certain machine.
* Represents a file located on a file system.
*/
@JsonTypeName("file")
@SuperBuilder
@@ -19,16 +19,16 @@ import java.nio.file.Path;
@Getter
public class FileStore extends JacksonizedValue implements FilenameStore, StreamDataStore {
MachineFileStore machine;
FileSystemStore fileSystem;
String file;
public FileStore(MachineFileStore machine, String file) {
this.machine = machine;
public FileStore(FileSystemStore fileSystem, String file) {
this.fileSystem = fileSystem;
this.file = file;
}
public final boolean isLocal() {
return machine instanceof LocalStore;
return fileSystem instanceof LocalStore;
}
public static FileStore local(Path p) {
@@ -44,7 +44,7 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override
public void checkComplete() throws Exception {
if (machine == null) {
if (fileSystem == null) {
throw new IllegalStateException("Machine is missing");
}
if (file == null) {
@@ -54,17 +54,17 @@ public class FileStore extends JacksonizedValue implements FilenameStore, Stream
@Override
public InputStream openInput() throws Exception {
return machine.openInput(file);
return fileSystem.openInput(file);
}
@Override
public OutputStream openOutput() throws Exception {
return machine.openOutput(file);
return fileSystem.openOutput(file);
}
@Override
public boolean canOpen() throws Exception {
return machine.exists(file);
return fileSystem.exists(file);
}
@Override
@@ -1,11 +1,9 @@
package io.xpipe.core.store;
import io.xpipe.core.charsetter.NewLine;
import java.io.InputStream;
import java.io.OutputStream;
public interface MachineFileStore extends DataStore {
public interface FileSystemStore extends DataStore {
InputStream openInput(String file) throws Exception;
@@ -13,7 +11,5 @@ public interface MachineFileStore extends DataStore {
public boolean exists(String file) throws Exception;
void mkdirs(String file) throws Exception;
NewLine getNewLine() throws Exception;
boolean mkdirs(String file) throws Exception;
}
@@ -1,21 +1,16 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.JacksonizedValue;
import io.xpipe.core.util.SecretValue;
import lombok.Getter;
import java.io.*;
import java.nio.charset.Charset;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.ServiceLoader;
@JsonTypeName("local")
public class LocalStore extends JacksonizedValue implements MachineFileStore, StandardShellStore {
public class LocalStore extends JacksonizedValue implements FileSystemStore, MachineStore {
@Override
public boolean isLocal() {
@@ -28,13 +23,13 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
}
@Override
public void mkdirs(String file) throws Exception {
Files.createDirectories(Path.of(file).getParent());
}
@Override
public NewLine getNewLine() {
return ShellTypes.getPlatformDefault().getNewLine();
public boolean mkdirs(String file) throws Exception {
try {
Files.createDirectories(Path.of(file).getParent());
return true;
} catch (Exception ex) {
return false;
}
}
@Override
@@ -49,98 +44,23 @@ public class LocalStore extends JacksonizedValue implements MachineFileStore, St
var p = Path.of(file);
return Files.newOutputStream(p);
}
@Override
public ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeout), charset);
public ShellProcessControl create() {
return LocalProcessControlProvider.create();
}
@Override
public ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeOut, Charset charset)
throws Exception {
return new LocalProcessControl(input, cmd, getEffectiveTimeOut(timeOut), charset);
}
public static abstract class LocalProcessControlProvider {
@Override
public ShellType determineType() throws Exception {
return ShellTypes.getPlatformDefault();
}
private static LocalProcessControlProvider INSTANCE;
class LocalProcessControl extends ProcessControl {
private final List<SecretValue> input;
private final Integer timeout;
@Getter
private final List<String> command;
private final Charset charset;
private Process process;
LocalProcessControl(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset) {
this.input = input;
this.timeout = timeout;
this.command = cmd;
this.charset = charset;
public static void init(ModuleLayer layer) {
INSTANCE = ServiceLoader.load(layer, LocalProcessControlProvider.class).findFirst().orElseThrow();
}
private InputStream createInputStream() {
var string =
input.stream().map(secret -> secret.getSecretValue()).collect(Collectors.joining("\n")) + "\r\n";
return new ByteArrayInputStream(string.getBytes(charset));
public static ShellProcessControl create() {
return INSTANCE.createProcessControl();
}
@Override
public void start() throws Exception {
var type = LocalStore.this.determineType();
var l = type.switchTo(command);
var builder = new ProcessBuilder(l);
process = builder.start();
var t = new Thread(() -> {
try (var inputStream = createInputStream()) {
process.getOutputStream().flush();
inputStream.transferTo(process.getOutputStream());
process.getOutputStream().close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
//t.start();
}
@Override
public int waitFor() throws Exception {
if (timeout != null) {
return process.waitFor(timeout, TimeUnit.SECONDS) ? 0 : -1;
} else {
return process.waitFor();
}
}
@Override
public InputStream getStdout() {
return process.getInputStream();
}
@Override
public OutputStream getStdin() {
return process.getOutputStream();
}
@Override
public InputStream getStderr() {
return process.getErrorStream();
}
@Override
public Charset getCharset() {
return charset;
}
public Integer getTimeout() {
return timeout;
}
public abstract ShellProcessControl createProcessControl();
}
}
@@ -0,0 +1,47 @@
package io.xpipe.core.store;
import java.io.InputStream;
import java.io.OutputStream;
public interface MachineStore extends FileSystemStore, ShellStore {
public default boolean isLocal() {
return false;
}
public default String queryMachineName() throws Exception {
try (CommandProcessControl pc = create().commandListFunction(shellProcessControl ->
shellProcessControl.getShellType().getOperatingSystemNameCommand())
.start()) {
return pc.readOrThrow().trim();
}
}
@Override
public default InputStream openInput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileReadCommand(file))
.startExternalStdout();
}
@Override
public default OutputStream openOutput(String file) throws Exception {
return create().commandListFunction(proc -> proc.getShellType().createFileWriteCommand(file))
.startExternalStdin();
}
@Override
public default boolean exists(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createFileExistsCommand(file))
.start()
.discardAndCheckExit();
return r;
}
@Override
public default boolean mkdirs(String file) throws Exception {
var r = create().commandListFunction(proc -> proc.getShellType().createMkdirsCommand(file))
.start()
.discardAndCheckExit();
return r;
}
}
@@ -3,129 +3,60 @@ package io.xpipe.core.store;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.UUID;
public abstract class ProcessControl {
public interface ProcessControl extends AutoCloseable {
public String executeAndReadStdout() throws Exception {
var pc = this;
pc.start();
pc.discardErr();
var bytes = pc.getStdout().readAllBytes();
var string = new String(bytes, pc.getCharset());
pc.waitFor();
return string;
}
boolean isRunning();
public void executeOrThrow() throws Exception {
var pc = this;
pc.start();
pc.discardOut();
pc.discardErr();
pc.waitFor();
}
ShellType getShellType();
public boolean executeAndCheckStatus() {
try {
executeOrThrow();
return true;
} catch (Exception ex) {
return false;
String readResultLine(String input, boolean captureOutput) throws IOException;
void writeLine(String line) throws IOException;
void writeLine(String line, boolean captureOutput) throws IOException;
void typeLine(String line);
public default String readOutput() throws IOException {
var id = UUID.randomUUID();
writeLine("echo " + id, false);
String lines = "";
while (true) {
var newLine = readLine();
if (newLine.contains(id.toString())) {
if (getShellType().echoesInput()) {
readLine();
}
break;
}
lines = lines + newLine + "\n";
}
return lines;
}
public Optional<String> executeAndReadStderrIfPresent() throws Exception {
var pc = this;
pc.start();
pc.discardOut();
var bytes = pc.getStderr().readAllBytes();
var string = new String(bytes, pc.getCharset());
var ec = pc.waitFor();
return ec != 0 ? Optional.of(string) : Optional.empty();
}
@Override
void close() throws IOException;
public String executeAndReadStdoutOrThrow()
throws Exception {
var pc = this;
pc.start();
String readLine() throws IOException;
AtomicReference<String> readError = new AtomicReference<>("");
var errorThread = new Thread(() -> {
try {
void kill() throws IOException;
readError.set(new String(pc.getStderr().readAllBytes(), pc.getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
errorThread.setDaemon(true);
errorThread.start();
ProcessControl exitTimeout(int timeout);
AtomicReference<String> read = new AtomicReference<>("");
var t = new Thread(() -> {
try {
read.set(new String(pc.getStdout().readAllBytes(), pc.getCharset()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
ProcessControl start() throws Exception;
var ec = pc.waitFor();
if (ec == -1) {
throw new ProcessOutputException("Command timed out");
}
boolean waitFor() throws Exception;
if (ec == 0 && !(read.get().isEmpty() && !readError.get().isEmpty())) {
return read.get().trim();
} else {
throw new ProcessOutputException(
"Command returned with " + ec + ": " + readError.get().trim());
}
}
InputStream getStdout();
public Thread discardOut() {
var t = new Thread(() -> {
try {
getStdout().transferTo(OutputStream.nullOutputStream());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
return t;
}
OutputStream getStdin();
public Thread discardErr() {
var t = new Thread(() -> {
try {
getStderr().transferTo(OutputStream.nullOutputStream());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
t.setDaemon(true);
t.start();
return t;
}
InputStream getStderr();
public abstract void start() throws Exception;
public abstract int waitFor() throws Exception;
public abstract InputStream getStdout();
public abstract OutputStream getStdin();
public abstract InputStream getStderr();
public abstract Charset getCharset();
public abstract List<String> getCommand();
Charset getCharset();
}
@@ -0,0 +1,6 @@
package io.xpipe.core.store;
public abstract class ProcessControlProvider {
public abstract ProcessControl local();
}
@@ -0,0 +1,58 @@
package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue;
import lombok.NonNull;
import java.io.IOException;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public interface ShellProcessControl extends ProcessControl {
ShellProcessControl elevation(SecretValue value);
SecretValue getElevationPassword();
default ShellProcessControl shell(@NonNull ShellType type){
return shell(type.openCommand());
}
default ShellProcessControl shell(@NonNull List<String> command) {
return shell(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
default ShellProcessControl shell(@NonNull String command){
return shell(processControl -> command);
}
ShellProcessControl shell(@NonNull Function<ProcessControl, String> command);
void executeCommand(String command) throws IOException;
default void executeCommand(List<String> command) throws IOException {
executeCommand(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
@Override
ShellProcessControl start() throws Exception;
default CommandProcessControl commandListFunction(Function<ShellProcessControl, List<String>> command) {
return commandFunction(shellProcessControl ->
command.apply(shellProcessControl).stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
CommandProcessControl commandFunction(Function<ShellProcessControl, String> command);
CommandProcessControl command(String command);
default CommandProcessControl command(List<String> command) {
return command(
command.stream().map(s -> s.contains(" ") ? "\"" + s + "\"" : s).collect(Collectors.joining(" ")));
}
void exit() throws IOException;
}
@@ -1,43 +1,20 @@
package io.xpipe.core.store;
import io.xpipe.core.util.SecretValue;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public interface ShellStore extends DataStore {
public default Integer getTimeout() {
return null;
public static MachineStore local() {
return new LocalStore();
}
public default List<SecretValue> getInput() {
return List.of();
}
ShellProcessControl create();
public default Integer getEffectiveTimeOut(Integer timeout) {
if (this.getTimeout() == null) {
return timeout;
public default ShellType determineType() throws Exception {
AtomicReference<ShellType> type = new AtomicReference<>();
try (var pc = create().start()) {
type.set(pc.getShellType());
}
if (timeout == null) {
return getTimeout();
}
return Math.min(getTimeout(), timeout);
}
public default ProcessControl prepareCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return prepareCommand(List.of(), cmd, timeout, charset);
}
public abstract ProcessControl prepareCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset)
throws Exception;
public default ProcessControl preparePrivilegedCommand(List<String> cmd, Integer timeout, Charset charset) throws Exception {
return preparePrivilegedCommand(List.of(), cmd, timeout, charset);
}
public default ProcessControl preparePrivilegedCommand(List<SecretValue> input, List<String> cmd, Integer timeout, Charset charset)
throws Exception {
throw new UnsupportedOperationException();
return type.get();
}
}
@@ -0,0 +1,55 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.charsetter.NewLine;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type"
)
public interface ShellType {
void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException;
default void init(ProcessControl proc) throws IOException {
}
default String getExitCommand() {
return "exit";
}
String getExitCodeVariable();
default String getConcatenationOperator() {
return ";";
}
String getEchoCommand(String s, boolean newLine);
List<String> openCommand();
String switchTo(String cmd);
List<String> createMkdirsCommand(String dirs);
List<String> createFileReadCommand(String file);
List<String> createFileWriteCommand(String file);
List<String> createFileExistsCommand(String file);
Charset determineCharset(ProcessControl control) throws Exception;
NewLine getNewLine();
String getName();
String getDisplayName();
List<String> getOperatingSystemNameCommand();
boolean echoesInput();
}
@@ -2,54 +2,57 @@ package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.SecretValue;
import lombok.Value;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class ShellTypes {
public static final StandardShellStore.ShellType POWERSHELL = new PowerShell();
public static final StandardShellStore.ShellType CMD = new Cmd();
public static final StandardShellStore.ShellType SH = new Sh();
public static final ShellType POWERSHELL = new PowerShell();
public static final ShellType CMD = new Cmd();
public static final ShellType SH = new Sh();
public static StandardShellStore.ShellType determine(ShellStore store) throws Exception {
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII)
.executeAndReadStdoutOrThrow()
.strip();
if (!o.equals("$0")) {
return SH;
public static ShellType determine(ProcessControl proc) throws Exception {
proc.writeLine("echo -NoEnumerate \"a\"", false);
String line;
while (true) {
line = proc.readLine();
if (line.equals("-NoEnumerate a")) {
return SH;
}
if (line.contains("echo -NoEnumerate \"a\"")) {
break;
}
}
var o = proc.readLine();
if (o.equals("a")) {
return POWERSHELL;
} else if (o.equals("-NoEnumerate \"a\"")) {
return CMD;
} else {
o = store.prepareCommand(
List.of(),
List.of("(dir 2>&1 *`|echo CMD);&<# rem #>echo PowerShell"),
null,
StandardCharsets.UTF_16LE)
.executeAndReadStdoutOrThrow()
.trim();
if (o.equals("PowerShell")) {
return POWERSHELL;
return SH;
}
}
public static ShellType[] getAvailable(ShellStore store) throws Exception {
try (ProcessControl proc = store.create().start()) {
var type = determine(proc);
if (type == SH) {
return getLinuxShells();
} else {
return CMD;
return getWindowsShells();
}
}
}
public static StandardShellStore.ShellType[] getAvailable(ShellStore store) throws Exception {
var o = store.prepareCommand(List.of(), List.of("echo", "$0"), null, StandardCharsets.US_ASCII)
.executeAndReadStdoutOrThrow();
if (o.trim().length() > 0 && !o.trim().equals("$0")) {
return getLinuxShells();
} else {
return getWindowsShells();
}
}
public static StandardShellStore.ShellType getRecommendedDefault() {
public static ShellType getRecommendedDefault() {
if (System.getProperty("os.name").startsWith("Windows")) {
return POWERSHELL;
} else {
@@ -57,7 +60,7 @@ public class ShellTypes {
}
}
public static StandardShellStore.ShellType getPlatformDefault() {
public static ShellType getPlatformDefault() {
if (System.getProperty("os.name").startsWith("Windows")) {
return CMD;
} else {
@@ -65,17 +68,51 @@ public class ShellTypes {
}
}
public static StandardShellStore.ShellType[] getWindowsShells() {
return new StandardShellStore.ShellType[] {CMD, POWERSHELL};
public static ShellType[] getWindowsShells() {
return new ShellType[] {CMD, POWERSHELL};
}
public static StandardShellStore.ShellType[] getLinuxShells() {
return new StandardShellStore.ShellType[] {SH};
public static ShellType[] getLinuxShells() {
return new ShellType[] {SH};
}
@JsonTypeName("cmd")
@Value
public static class Cmd implements StandardShellStore.ShellType {
public static class Cmd implements ShellType {
@Override
public String getEchoCommand(String s, boolean newLine) {
return newLine ? "echo " + s : "echo | set /p dummyName=" + s;
}
@Override
public String getConcatenationOperator() {
return "&";
}
@Override
public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
control.executeCommand("net session >NUL 2>NUL");
control.executeCommand("echo %errorLevel%");
var exitCode = Integer.parseInt(control.readLine());
if (exitCode != 0) {
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
}
control.executeCommand(command);
}
@Override
public void init(ProcessControl proc) throws IOException {
proc.readLine();
proc.readLine();
proc.readLine();
}
@Override
public String getExitCodeVariable() {
return "%errorlevel%";
}
@Override
public NewLine getNewLine() {
@@ -83,24 +120,18 @@ public class ShellTypes {
}
@Override
public List<String> switchTo(List<String> cmd) {
var l = new ArrayList<>(cmd);
l.add(0, "cmd.exe");
l.add(1, "/c");
l.add(2, "@chcp");
l.add(3, "65001");
l.add(4, ">");
l.add(5, "nul");
l.add(6, "&&");
return l;
public List<String> openCommand() {
return List.of("cmd");
}
@Override
public ProcessControl prepareElevatedCommand(
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
throws Exception {
var l = List.of("net", "session", ";", "if", "%errorLevel%", "!=", "0");
return st.prepareCommand(List.of(), l, timeout, charset);
public String switchTo(String cmd) {
return "cmd.exe /c " + cmd;
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("lmkdir", dirs);
}
@Override
@@ -119,8 +150,11 @@ public class ShellTypes {
}
@Override
public Charset determineCharset(ShellStore store) throws Exception {
return StandardCharsets.UTF_8;
public Charset determineCharset(ProcessControl control) throws Exception {
var output = control.readResultLine("chcp", true);
var matcher = Pattern.compile("\\d+").matcher(output);
matcher.find();
return Charset.forName("ibm" + matcher.group());
}
@Override
@@ -130,24 +164,63 @@ public class ShellTypes {
@Override
public String getDisplayName() {
return "cmd.exe";
return "cmd";
}
@Override
public List<String> getOperatingSystemNameCommand() {
return List.of("Get-ComputerInfo");
}
@Override
public boolean echoesInput() {
return true;
}
}
@JsonTypeName("powershell")
@Value
public static class PowerShell implements StandardShellStore.ShellType {
public static class PowerShell implements ShellType {
@Override
public List<String> switchTo(List<String> cmd) {
var l = new ArrayList<>(cmd);
l.add(0, "powershell.exe");
return l;
public boolean echoesInput() {
return true;
}
@Override
public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
control.executeCommand("([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)");
var exitCode = Integer.parseInt(control.readLine());
if (exitCode != 0) {
throw new IllegalStateException("The command \"" + displayCommand + "\" requires elevation.");
}
control.executeCommand(command);
}
@Override
public String getExitCodeVariable() {
return "$LASTEXITCODE";
}
@Override
public String getEchoCommand(String s, boolean newLine) {
return newLine ? "echo " + s : String.format("Write-Host \"%s\" -NoNewLine", s);
}
@Override
public List<String> openCommand() {
return List.of("powershell", "/nologo");
}
@Override
public String switchTo(String cmd) {
return "powershell.exe -Command " + cmd;
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("New-Item", "-Path", "D:\\temp\\Test Folder", "-ItemType", "Directory");
}
@Override
@@ -166,8 +239,11 @@ public class ShellTypes {
}
@Override
public Charset determineCharset(ShellStore store) throws Exception {
return StandardCharsets.UTF_16LE;
public Charset determineCharset(ProcessControl control) throws Exception {
var output = control.readResultLine("chcp", true);
var matcher = Pattern.compile("\\d+").matcher(output);
matcher.find();
return Charset.forName("ibm" + matcher.group());
}
@Override
@@ -193,22 +269,41 @@ public class ShellTypes {
@JsonTypeName("sh")
@Value
public static class Sh implements StandardShellStore.ShellType {
public static class Sh implements ShellType {
@Override
public List<String> switchTo(List<String> cmd) {
return cmd;
public void elevate(ShellProcessControl control, String command, String displayCommand) throws IOException {
if (control.getElevationPassword().getSecretValue() == null) {
throw new IllegalStateException("No password for sudo has been set");
}
control.executeCommand("sudo -S " + switchTo(command));
control.writeLine(control.getElevationPassword().getSecretValue());
}
@Override
public ProcessControl prepareElevatedCommand(
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
throws Exception {
var l = new ArrayList<>(cmd);
l.add(0, "sudo");
l.add(1, "-S");
var pws = new ByteArrayInputStream(pw.getBytes(determineCharset(st)));
return st.prepareCommand(List.of(SecretValue.createForSecretValue(pw)), l, timeout, charset);
public String getExitCodeVariable() {
return "$?";
}
@Override
public String getEchoCommand(String s, boolean newLine) {
return newLine ? "echo " + s : "echo -n " + s;
}
@Override
public List<String> openCommand() {
return List.of("sh");
}
@Override
public String switchTo(String cmd) {
return "sh -c \"" + cmd + "\"";
}
@Override
public List<String> createMkdirsCommand(String dirs) {
return List.of("mkdir", "-p", dirs);
}
@Override
@@ -227,7 +322,7 @@ public class ShellTypes {
}
@Override
public Charset determineCharset(ShellStore st) throws Exception {
public Charset determineCharset(ProcessControl st) throws Exception {
return StandardCharsets.UTF_8;
}
@@ -250,5 +345,10 @@ public class ShellTypes {
public List<String> getOperatingSystemNameCommand() {
return List.of("uname", "-o");
}
@Override
public boolean echoesInput() {
return false;
}
}
}
@@ -1,97 +0,0 @@
package io.xpipe.core.store;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.xpipe.core.charsetter.NewLine;
import io.xpipe.core.util.SecretValue;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.List;
public interface StandardShellStore extends MachineFileStore, ShellStore {
public default ProcessControl prepareLocalCommand(List<SecretValue> input, List<String> cmd, Integer timeout)
throws Exception {
return prepareCommand(input, cmd, timeout, determineType().determineCharset(this));
}
public default boolean isLocal() {
return false;
}
public default NewLine getNewLine() throws Exception {
return determineType().getNewLine();
}
ShellType determineType() throws Exception;
public default String querySystemName() throws Exception {
var result = prepareCommand(
List.of(),
determineType().getOperatingSystemNameCommand(),
getTimeout(),
determineType().determineCharset(this))
.executeAndReadStdoutOrThrow();
return result.strip();
}
@Override
public default InputStream openInput(String file) throws Exception {
var type = determineType();
var cmd = type.createFileReadCommand(file);
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start();
return p.getStdout();
}
@Override
public default OutputStream openOutput(String file) throws Exception {
return null;
// var type = determineType();
// var cmd = type.createFileWriteCommand(file);
// var p = prepare(cmd).redirectErrorStream(true);
// var proc = p.start();
// return proc.getOutputStream();
}
@Override
public default boolean exists(String file) throws Exception {
var type = determineType();
var cmd = type.createFileExistsCommand(file);
var p = prepareCommand(List.of(), cmd, null, type.determineCharset(this));
p.start();
return p.waitFor() == 0;
}
@Override
public default void mkdirs(String file) throws Exception {}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
public static interface ShellType {
List<String> switchTo(List<String> cmd);
default ProcessControl prepareElevatedCommand(
ShellStore st, List<SecretValue> in, List<String> cmd, Integer timeout, String pw, Charset charset)
throws Exception {
return st.prepareCommand(in, cmd, timeout, charset);
}
List<String> createFileReadCommand(String file);
List<String> createFileWriteCommand(String file);
List<String> createFileExistsCommand(String file);
Charset determineCharset(ShellStore store) throws Exception;
NewLine getNewLine();
String getName();
String getDisplayName();
List<String> getOperatingSystemNameCommand();
}
}
+1
View File
@@ -22,6 +22,7 @@ open module io.xpipe.core {
uses com.fasterxml.jackson.databind.Module;
uses io.xpipe.core.source.WriteMode;
uses io.xpipe.core.store.LocalStore.LocalProcessControlProvider;
provides WriteMode with WriteMode.Replace, WriteMode.Append, WriteMode.Prepend;
provides com.fasterxml.jackson.databind.Module with