1 package org
.asamk
.signal
.util
;
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
;
8 import java
.io
.BufferedReader
;
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
;
27 import java
.util
.function
.Supplier
;
29 import jdk
.net
.ExtendedSocketOptions
;
30 import jdk
.net
.UnixDomainPrincipal
;
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
;
36 public class IOUtils
{
38 private static final Logger logger
= LoggerFactory
.getLogger(IOUtils
.class);
43 public static Charset
getConsoleCharset() {
44 final var console
= System
.console();
45 return console
== null ? Charset
.defaultCharset() : console
.charset();
48 public static String
readAll(InputStream
in, Charset charset
) throws IOException
{
49 var output
= new StringWriter();
50 var buffer
= new byte[4096];
52 while (-1 != (n
= in.read(buffer
))) {
53 output
.write(new String(buffer
, 0, n
, charset
));
55 return output
.toString();
58 public static void createPrivateDirectories(File file
) throws IOException
{
63 final var path
= file
.toPath();
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
);
72 public static File
getDataHomeDir() {
73 var dataHome
= System
.getenv("XDG_DATA_HOME");
74 if (dataHome
!= null) {
75 return new File(dataHome
);
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");
82 public static File
getRuntimeDir() {
83 var runtimeDir
= System
.getenv("XDG_RUNTIME_DIR");
84 if (runtimeDir
!= null) {
85 return new File(runtimeDir
);
88 logger
.debug("XDG_RUNTIME_DIR not set, falling back to temp dir");
89 return new File(System
.getProperty("java.io.tmpdir"));
92 public static Supplier
<String
> getLineSupplier(final Reader reader
) {
93 final var bufferedReader
= new BufferedReader(reader
);
96 return bufferedReader
.readLine();
97 } catch (ClosedChannelException ignored
) {
98 logger
.trace("Line supplier has been interrupted.");
100 } catch (IOException e
) {
101 logger
.error("Error occurred while reading line", e
);
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
);
112 final var host
= tcpAddress
.substring(0, colonIndex
);
113 final var portString
= tcpAddress
.substring(colonIndex
+ 1);
117 port
= Integer
.parseInt(portString
);
118 } catch (NumberFormatException e
) {
119 throw new UserErrorException("Invalid tcp port: " + portString
, e
);
121 final var socketAddress
= new InetSocketAddress(host
, port
);
122 if (socketAddress
.isUnresolved()) {
123 throw new UserErrorException("Invalid tcp bind address, invalid host: " + host
);
125 return socketAddress
;
128 public static String
getUnixDomainPrincipal(final SocketChannel channel
) throws IOException
{
129 UnixDomainPrincipal principal
= null;
131 principal
= channel
.getOption(ExtendedSocketOptions
.SO_PEERCRED
);
132 } catch (UnsupportedOperationException
| NoClassDefFoundError ignored
) {
134 return principal
== null ?
null : principal
.toString();
137 public static ServerSocketChannel
bindSocket(final SocketAddress address
) throws IOErrorException
{
138 final ServerSocketChannel serverChannel
;
141 serverChannel
= address
instanceof UnixDomainSocketAddress
142 ? ServerSocketChannel
.open(StandardProtocolFamily
.UNIX
)
143 : ServerSocketChannel
.open();
144 serverChannel
.bind(address
);
145 logger
.debug("Listening on socket: " + address
);
147 } catch (IOException e
) {
148 throw new IOErrorException("Failed to bind socket " + address
+ ": " + e
.getMessage(), e
);
150 return serverChannel
;
153 private static void preBind(SocketAddress address
) throws IOException
{
154 if (address
instanceof UnixDomainSocketAddress usa
) {
155 createPrivateDirectories(usa
.getPath().toFile().getParentFile());
159 private static void postBind(SocketAddress address
) {
160 if (address
instanceof UnixDomainSocketAddress usa
) {
161 usa
.getPath().toFile().deleteOnExit();