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