]> nmode's Git Repositories - signal-cli/blob - src/main/java/org/asamk/signal/util/IOUtils.java
Use console charset for reading/writing to stdin/out
[signal-cli] / src / main / java / org / asamk / signal / util / IOUtils.java
1 package org.asamk.signal.util;
2
3 import org.asamk.signal.commands.exceptions.IOErrorException;
4 import org.asamk.signal.commands.exceptions.UserErrorException;
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import java.io.BufferedReader;
9 import java.io.File;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.Reader;
13 import java.io.StringWriter;
14 import java.net.InetSocketAddress;
15 import java.net.SocketAddress;
16 import java.net.StandardProtocolFamily;
17 import java.net.UnixDomainSocketAddress;
18 import java.nio.channels.ServerSocketChannel;
19 import java.nio.channels.SocketChannel;
20 import java.nio.charset.Charset;
21 import java.nio.file.Files;
22 import java.nio.file.attribute.PosixFilePermission;
23 import java.nio.file.attribute.PosixFilePermissions;
24 import java.util.EnumSet;
25 import java.util.Set;
26 import java.util.function.Supplier;
27
28 import jdk.net.ExtendedSocketOptions;
29 import jdk.net.UnixDomainPrincipal;
30
31 import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
32 import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
33 import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
34
35 public class IOUtils {
36
37 private final static Logger logger = LoggerFactory.getLogger(IOUtils.class);
38
39 private IOUtils() {
40 }
41
42 public static Charset getConsoleCharset() {
43 final var console = System.console();
44 return console == null ? Charset.defaultCharset() : console.charset();
45 }
46
47 public static String readAll(InputStream in, Charset charset) throws IOException {
48 var output = new StringWriter();
49 var buffer = new byte[4096];
50 int n;
51 while (-1 != (n = in.read(buffer))) {
52 output.write(new String(buffer, 0, n, charset));
53 }
54 return output.toString();
55 }
56
57 public static void createPrivateDirectories(File file) throws IOException {
58 if (file.exists()) {
59 return;
60 }
61
62 final var path = file.toPath();
63 try {
64 Set<PosixFilePermission> perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE);
65 Files.createDirectories(path, PosixFilePermissions.asFileAttribute(perms));
66 } catch (UnsupportedOperationException e) {
67 Files.createDirectories(path);
68 }
69 }
70
71 public static File getDataHomeDir() {
72 var dataHome = System.getenv("XDG_DATA_HOME");
73 if (dataHome != null) {
74 return new File(dataHome);
75 }
76
77 logger.debug("XDG_DATA_HOME not set, falling back to home dir");
78 return new File(new File(System.getProperty("user.home"), ".local"), "share");
79 }
80
81 public static File getRuntimeDir() {
82 var runtimeDir = System.getenv("XDG_RUNTIME_DIR");
83 if (runtimeDir != null) {
84 return new File(runtimeDir);
85 }
86
87 logger.debug("XDG_RUNTIME_DIR not set, falling back to temp dir");
88 return new File(System.getProperty("java.io.tmpdir"));
89 }
90
91 public static Supplier<String> getLineSupplier(final Reader reader) {
92 final var bufferedReader = new BufferedReader(reader);
93 return () -> {
94 try {
95 return bufferedReader.readLine();
96 } catch (IOException e) {
97 logger.error("Error occurred while reading line", e);
98 return null;
99 }
100 };
101 }
102
103 public static InetSocketAddress parseInetSocketAddress(final String tcpAddress) throws UserErrorException {
104 final var colonIndex = tcpAddress.lastIndexOf(':');
105 if (colonIndex < 0) {
106 throw new UserErrorException("Invalid tcp bind address (expected host:port): " + tcpAddress);
107 }
108 final var host = tcpAddress.substring(0, colonIndex);
109 final var portString = tcpAddress.substring(colonIndex + 1);
110
111 final int port;
112 try {
113 port = Integer.parseInt(portString);
114 } catch (NumberFormatException e) {
115 throw new UserErrorException("Invalid tcp port: " + portString, e);
116 }
117 final var socketAddress = new InetSocketAddress(host, port);
118 if (socketAddress.isUnresolved()) {
119 throw new UserErrorException("Invalid tcp bind address, invalid host: " + host);
120 }
121 return socketAddress;
122 }
123
124 public static UnixDomainPrincipal getUnixDomainPrincipal(final SocketChannel channel) throws IOException {
125 UnixDomainPrincipal principal = null;
126 try {
127 principal = channel.getOption(ExtendedSocketOptions.SO_PEERCRED);
128 } catch (UnsupportedOperationException ignored) {
129 }
130 return principal;
131 }
132
133 public static ServerSocketChannel bindSocket(final SocketAddress address) throws IOErrorException {
134 final ServerSocketChannel serverChannel;
135 try {
136 preBind(address);
137 serverChannel = address instanceof UnixDomainSocketAddress
138 ? ServerSocketChannel.open(StandardProtocolFamily.UNIX)
139 : ServerSocketChannel.open();
140 serverChannel.bind(address);
141 logger.info("Listening on socket: " + address);
142 postBind(address);
143 } catch (IOException e) {
144 throw new IOErrorException("Failed to bind socket " + address + ": " + e.getMessage(), e);
145 }
146 return serverChannel;
147 }
148
149 private static void preBind(SocketAddress address) throws IOException {
150 if (address instanceof UnixDomainSocketAddress usa) {
151 createPrivateDirectories(usa.getPath().toFile().getParentFile());
152 }
153 }
154
155 private static void postBind(SocketAddress address) {
156 if (address instanceof UnixDomainSocketAddress usa) {
157 usa.getPath().toFile().deleteOnExit();
158 }
159 }
160 }