1 /*
2 * junixsocket
3 *
4 * Copyright 2009-2024 Christian Kohlschütter
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.newsclub.net.unix;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.IOException;
23 import java.net.InetAddress;
24 import java.net.InetSocketAddress;
25 import java.net.SocketAddress;
26 import java.net.SocketException;
27 import java.net.URI;
28 import java.net.URISyntaxException;
29 import java.nio.ByteBuffer;
30 import java.nio.charset.Charset;
31 import java.nio.charset.StandardCharsets;
32 import java.nio.file.Path;
33 import java.util.Arrays;
34 import java.util.HashSet;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.Set;
38
39 import org.eclipse.jdt.annotation.NonNull;
40 import org.newsclub.net.unix.pool.ObjectPool.Lease;
41
42 /**
43 * Describes an {@link InetSocketAddress} that actually uses AF_UNIX sockets instead of AF_INET.
44 *
45 * The ability to specify a port number is not specified by AF_UNIX sockets, but we need it
46 * sometimes, for example for RMI-over-AF_UNIX.
47 *
48 * @author Christian Kohlschütter
49 */
50 @SuppressWarnings("PMD.ShortMethodName")
51 public final class AFUNIXSocketAddress extends AFSocketAddress {
52 private static final long serialVersionUID = 1L; // do not change!
53
54 private static final Charset ADDRESS_CHARSET = Charset.defaultCharset();
55
56 @SuppressWarnings("null")
57 static final AFAddressFamily<@NonNull AFUNIXSocketAddress> AF_UNIX = AFAddressFamily
58 .registerAddressFamily("un", //
59 AFUNIXSocketAddress.class, new AFSocketAddressConfig<AFUNIXSocketAddress>() {
60
61 private final AFSocketAddressConstructor<AFUNIXSocketAddress> addrConstr =
62 isUseDeserializationForInit() ? AFUNIXSocketAddress::newAFSocketAddress
63 : AFUNIXSocketAddress::new;
64
65 @Override
66 public AFUNIXSocketAddress parseURI(URI u, int port) throws SocketException {
67 return AFUNIXSocketAddress.of(u, port);
68 }
69
70 @Override
71 protected AFSocketAddressConstructor<AFUNIXSocketAddress> addressConstructor() {
72 return addrConstr;
73 }
74
75 @Override
76 protected String selectorProviderClassname() {
77 return AFUNIXSelectorProvider.class.getName();
78 }
79
80 @Override
81 protected Set<String> uriSchemes() {
82 return new HashSet<>(Arrays.asList("unix", "http+unix", "https+unix"));
83 }
84
85 @Override
86 protected SocketAddress nullBindAddress() throws IOException {
87 return AFUNIXSocketAddress.ofNewTempFile();
88 }
89 });
90
91 private AFUNIXSocketAddress(int port, final byte[] socketAddress, Lease<ByteBuffer> nativeAddress)
92 throws SocketException {
93 super(port, socketAddress, nativeAddress, AF_UNIX);
94 }
95
96 /**
97 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
98 * file and port. <b>Legacy constructor, do not use!</b>
99 *
100 * @param socketFile The socket to connect to.
101 * @throws SocketException if the operation fails.
102 * @deprecated Use {@link #of(File)} instead.
103 * @see #of(File)
104 */
105 @Deprecated
106 public AFUNIXSocketAddress(File socketFile) throws SocketException {
107 this(socketFile, 0);
108 }
109
110 /**
111 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
112 * file. <b>Legacy constructor, do not use!</b>
113 *
114 * @param socketFile The socket to connect to.
115 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
116 * @throws SocketException if the operation fails.
117 * @deprecated Use {@link #of(File, int)} instead.
118 * @see #of(File, int)
119 */
120 @Deprecated
121 public AFUNIXSocketAddress(File socketFile, int port) throws SocketException {
122 this(port, of(socketFile, port).getPathAsBytes(), of(socketFile, port)
123 .getNativeAddressDirectBuffer());
124 }
125
126 static AFUNIXSocketAddress newAFSocketAddress(int port, final byte[] socketAddress,
127 Lease<ByteBuffer> nativeAddress) throws SocketException {
128 return newDeserializedAFSocketAddress(port, socketAddress, nativeAddress, AF_UNIX,
129 AFUNIXSocketAddress::new);
130 }
131
132 /**
133 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
134 * file.
135 *
136 * @param socketFile The socket to connect to.
137 * @return A corresponding {@link AFUNIXSocketAddress} instance.
138 * @throws SocketException if the operation fails.
139 */
140 public static AFUNIXSocketAddress of(final File socketFile) throws SocketException {
141 return of(socketFile, 0);
142 }
143
144 /**
145 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
146 * file, assigning the given port to it.
147 *
148 * @param socketFile The socket to connect to.
149 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
150 * @return A corresponding {@link AFUNIXSocketAddress} instance.
151 * @throws SocketException if the operation fails.
152 */
153 public static AFUNIXSocketAddress of(final File socketFile, int port) throws SocketException {
154 return of(socketFile.getPath().getBytes(ADDRESS_CHARSET), port);
155 }
156
157 /**
158 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
159 * byte sequence.
160 *
161 * NOTE: By specifying a byte array that starts with a zero byte, you indicate that the abstract
162 * namespace is to be used. This feature is not available on all target platforms.
163 *
164 * @param socketAddress The socket address (as bytes).
165 * @return A corresponding {@link AFUNIXSocketAddress} instance.
166 * @throws SocketException if the operation fails.
167 * @see AFUNIXSocketAddress#inAbstractNamespace(String)
168 */
169 public static AFUNIXSocketAddress of(final byte[] socketAddress) throws SocketException {
170 return of(socketAddress, 0);
171 }
172
173 /**
174 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
175 * byte sequence, assigning the given port to it.
176 *
177 * NOTE: By specifying a byte array that starts with a zero byte, you indicate that the abstract
178 * namespace is to be used. This feature is not available on all target platforms.
179 *
180 * @param socketAddress The socket address (as bytes).
181 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
182 * @return A corresponding {@link AFUNIXSocketAddress} instance.
183 * @throws SocketException if the operation fails.
184 * @see AFUNIXSocketAddress#inAbstractNamespace(String,int)
185 */
186 public static AFUNIXSocketAddress of(final byte[] socketAddress, int port)
187 throws SocketException {
188 return AFSocketAddress.resolveAddress(socketAddress, port, AF_UNIX);
189 }
190
191 /**
192 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
193 * path.
194 *
195 * @param socketPath The socket to connect to.
196 * @return A corresponding {@link AFUNIXSocketAddress} instance.
197 * @throws SocketException if the operation fails.
198 */
199 public static AFUNIXSocketAddress of(Path socketPath) throws SocketException {
200 return of(socketPath, 0);
201 }
202
203 /**
204 * Returns an {@link AFUNIXSocketAddress} that points to the AF_UNIX socket specified by the given
205 * path, assigning the given port to it.
206 *
207 * @param socketPath The socket to connect to.
208 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
209 * @return A corresponding {@link AFUNIXSocketAddress} instance.
210 * @throws SocketException if the operation fails.
211 */
212 public static AFUNIXSocketAddress of(Path socketPath, int port) throws SocketException {
213 if (!PathUtil.isPathInDefaultFileSystem(socketPath)) {
214 throw new SocketException("Path is not in the default file system");
215 }
216
217 return of(socketPath.toString().getBytes(ADDRESS_CHARSET), port);
218 }
219
220 /**
221 * Returns an {@link AFUNIXSocketAddress} for the given URI, if possible.
222 *
223 * @param u The URI.
224 * @return The address.
225 * @throws SocketException if the operation fails.
226 */
227 public static AFUNIXSocketAddress of(URI u) throws SocketException {
228 return of(u, -1);
229 }
230
231 /**
232 * Returns an {@link AFUNIXSocketAddress} for the given URI, if possible.
233 *
234 * @param u The URI.
235 * @param overridePort The port to forcibly use, or {@code -1} for "don't override".
236 * @return The address.
237 * @throws SocketException if the operation fails.
238 */
239 public static AFUNIXSocketAddress of(URI u, int overridePort) throws SocketException {
240 switch (u.getScheme()) {
241 case "file":
242 case "unix":
243 String path = u.getPath();
244 if (path == null || path.isEmpty()) {
245 String auth = u.getAuthority();
246 if (auth != null && !auth.isEmpty() && u.getRawSchemeSpecificPart().indexOf('@') == -1) {
247 path = auth;
248 } else {
249 throw new SocketException("Cannot find UNIX socket path component from URI: " + u);
250 }
251 }
252 return of(new File(path), overridePort != -1 ? overridePort : u.getPort());
253 case "http+unix":
254 case "https+unix":
255 HostAndPort hp = HostAndPort.parseFrom(u);
256 return of(new File(hp.getHostname()), overridePort != -1 ? overridePort : hp.getPort());
257 default:
258 throw new SocketException("Invalid URI");
259 }
260 }
261
262 /**
263 * Returns an {@link AFUNIXSocketAddress} that points to a temporary, non-existent but accessible
264 * path in the file system.
265 *
266 * @return A corresponding {@link AFUNIXSocketAddress} instance.
267 * @throws IOException if the operation fails.
268 */
269 public static AFUNIXSocketAddress ofNewTempFile() throws IOException {
270 return ofNewTempPath(0);
271 }
272
273 /**
274 * Returns an {@link AFUNIXSocketAddress} that points to a temporary, non-existent but accessible
275 * path in the file system, assigning the given port to it.
276 *
277 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
278 * @return A corresponding {@link AFUNIXSocketAddress} instance.
279 * @throws IOException if the operation fails.
280 */
281 public static AFUNIXSocketAddress ofNewTempPath(int port) throws IOException {
282 return of(newTempPath(true), port);
283 }
284
285 /**
286 * Returns an {@link AFUNIXSocketAddress} based on the given {@link SocketAddress}.
287 *
288 * This either simply casts an existing {@link AFUNIXSocketAddress}, or converts a
289 * {@code UnixDomainSocketAddress} to it.
290 *
291 * @param address The address to convert.
292 * @return A corresponding {@link AFUNIXSocketAddress} instance.
293 * @throws SocketException if the operation fails.
294 */
295 public static AFUNIXSocketAddress of(SocketAddress address) throws IOException {
296 AFUNIXSocketAddress addr = unwrap(Objects.requireNonNull(address));
297 if (addr == null) {
298 throw new SocketException("Could not convert SocketAddress to AFUNIXSocketAddress");
299 }
300 return addr;
301 }
302
303 static File newTempPath(boolean deleteOnExit) throws IOException {
304 File f = File.createTempFile("jux", ".sock");
305 if (deleteOnExit) {
306 f.deleteOnExit(); // always delete on exit to clean-up sockets created under that name
307 }
308 if (!f.delete() && f.exists()) {
309 throw new IOException("Could not delete temporary file that we just created: " + f);
310 }
311 return f;
312 }
313
314 /**
315 * Returns an {@link AFUNIXSocketAddress} given a special {@link InetAddress} that encodes the
316 * byte sequence of an AF_UNIX socket address, like those returned by {@link #wrapAddress()}.
317 *
318 * @param address The "special" {@link InetAddress}.
319 * @param port The port (use 0 for "none").
320 * @return The {@link AFUNIXSocketAddress} instance.
321 * @throws SocketException if the operation fails, for example when an unsupported address is
322 * specified.
323 */
324 public static AFUNIXSocketAddress unwrap(InetAddress address, int port) throws SocketException {
325 return AFSocketAddress.unwrap(address, port, AF_UNIX);
326 }
327
328 /**
329 * Returns an {@link AFUNIXSocketAddress} given a generic {@link SocketAddress}.
330 *
331 * @param address The address to unwrap.
332 * @return The {@link AFUNIXSocketAddress} instance.
333 * @throws SocketException if the operation fails, for example when an unsupported address is
334 * specified.
335 */
336 public static AFUNIXSocketAddress unwrap(SocketAddress address) throws SocketException {
337 Objects.requireNonNull(address);
338 AFSupplier<AFUNIXSocketAddress> supplier = supportedAddressSupplier(address);
339 if (supplier == null) {
340 throw new SocketException("Unsupported address");
341 }
342 return supplier.get();
343 }
344
345 /**
346 * Returns an {@link AFUNIXSocketAddress} given a special {@link InetAddress} hostname that
347 * encodes the byte sequence of an AF_UNIX socket address, like those returned by
348 * {@link #wrapAddress()}.
349 *
350 * @param hostname The "special" hostname, as provided by {@link InetAddress#getHostName()}.
351 * @param port The port (use 0 for "none").
352 * @return The {@link AFUNIXSocketAddress} instance.
353 * @throws SocketException if the operation fails, for example when an unsupported address is
354 * specified.
355 */
356 public static AFUNIXSocketAddress unwrap(String hostname, int port) throws SocketException {
357 return AFSocketAddress.unwrap(hostname, port, AF_UNIX);
358 }
359
360 /**
361 * Convenience method to create an {@link AFUNIXSocketAddress} in the abstract namespace.
362 *
363 * The returned socket address will use the byte representation of this identifier (using the
364 * system's default character encoding), prefixed with a null byte (to indicate the abstract
365 * namespace is used).
366 *
367 * @param name The identifier in the abstract namespace, without trailing zero or @.
368 * @return The address.
369 * @throws SocketException if the operation fails.
370 */
371 public static AFUNIXSocketAddress inAbstractNamespace(String name) throws SocketException {
372 return inAbstractNamespace(name, 0);
373 }
374
375 /**
376 * Convenience method to create an {@link AFUNIXSocketAddress} in the abstract namespace.
377 *
378 * The returned socket address will use the byte representation of this identifier (using the
379 * system's default character encoding), prefixed with a null byte (to indicate the abstract
380 * namespace is used).
381 *
382 * @param name The identifier in the abstract namespace, without trailing zero or @.
383 * @param port The port associated with this socket, or {@code 0} when no port should be assigned.
384 * @return The address.
385 * @throws SocketException if the operation fails.
386 */
387 public static AFUNIXSocketAddress inAbstractNamespace(String name, int port)
388 throws SocketException {
389 byte[] bytes = name.getBytes(ADDRESS_CHARSET);
390 byte[] addr = new byte[bytes.length + 1];
391 System.arraycopy(bytes, 0, addr, 1, bytes.length);
392 return AFUNIXSocketAddress.of(addr, port);
393 }
394
395 private static String prettyPrint(byte[] data) {
396 final int dataLength = data.length;
397 if (dataLength == 0) {
398 return "";
399 }
400 StringBuilder sb = new StringBuilder(dataLength + 16);
401 for (int i = 0; i < dataLength; i++) {
402 byte c = data[i];
403 if (c >= 32 && c < 127) {
404 sb.append((char) c);
405 } else {
406 sb.append("\\x");
407 sb.append(String.format(Locale.ENGLISH, "%02x", c));
408 }
409 }
410 return sb.toString();
411 }
412
413 @Override
414 public String toString() {
415 int port = getPort();
416 return getClass().getName() + "[" + (port == 0 ? "" : "port=" + port + ";") + "path="
417 + prettyPrint(getBytes()) + "]";
418 }
419
420 /**
421 * Returns the path to the UNIX domain socket, as a human-readable string using the default
422 * encoding.
423 *
424 * For addresses in the abstract namespace, the US_ASCII encoding is used; zero-bytes are
425 * converted to '@', other non-printable bytes are converted to '.'
426 *
427 * @return The path.
428 * @see #getPathAsBytes()
429 */
430 public String getPath() {
431 byte[] bytes = getBytes();
432 if (bytes.length == 0) {
433 return "";
434 } else if (bytes[0] != 0) {
435 return new String(bytes, ADDRESS_CHARSET);
436 }
437
438 byte[] by = bytes.clone();
439 for (int i = 0; i < by.length; i++) {
440 byte b = by[i];
441 if (b == 0) {
442 by[i] = '@';
443 } else if (b >= 32 && b < 127) {
444 // print as-is
445 } else {
446 by[i] = '.';
447 }
448 }
449 return new String(by, StandardCharsets.US_ASCII);
450 }
451
452 /**
453 * Returns the {@link Charset} used to encode/decode {@link AFUNIXSocketAddress}es.
454 *
455 * This is usually the system default charset, unless that is {@link StandardCharsets#US_ASCII}
456 * (7-bit), in which case {@link StandardCharsets#ISO_8859_1} is used instead.
457 *
458 * @return The charset.
459 */
460 public static Charset addressCharset() {
461 return ADDRESS_CHARSET;
462 }
463
464 /**
465 * Returns the path to the UNIX domain socket, as bytes.
466 *
467 * @return The path.
468 * @see #getPath()
469 */
470 public byte[] getPathAsBytes() {
471 return getBytes().clone();
472 }
473
474 /**
475 * Checks if the address is in the abstract namespace (or, for Haiku OS, in the internal
476 * namespace).
477 *
478 * @return {@code true} if the address is in the abstract namespace.
479 */
480 public boolean isInAbstractNamespace() {
481 byte[] bytes = getBytes();
482 return bytes.length > 0 && bytes[0] == 0;
483 }
484
485 @Override
486 public boolean hasFilename() {
487 byte[] bytes = getBytes();
488 return bytes.length > 0 && bytes[0] != 0;
489 }
490
491 @Override
492 public File getFile() throws FileNotFoundException {
493 if (isInAbstractNamespace()) {
494 throw new FileNotFoundException("Socket is in abstract namespace");
495 }
496 byte[] bytes = getBytes();
497
498 if (bytes.length == 0) {
499 throw new FileNotFoundException("No name");
500 }
501 return new File(new String(bytes, ADDRESS_CHARSET));
502 }
503
504 /**
505 * Checks if an {@link InetAddress} can be unwrapped to an {@link AFUNIXSocketAddress}.
506 *
507 * @param addr The instance to check.
508 * @return {@code true} if so.
509 * @see #wrapAddress()
510 * @see #unwrap(InetAddress, int)
511 */
512 public static boolean isSupportedAddress(InetAddress addr) {
513 return AFInetAddress.isSupportedAddress(addr, AF_UNIX);
514 }
515
516 /**
517 * Checks if a {@link SocketAddress} can be unwrapped to an {@link AFUNIXSocketAddress}.
518 *
519 * @param addr The instance to check.
520 * @return {@code true} if so.
521 * @see #unwrap(InetAddress, int)
522 */
523 public static boolean isSupportedAddress(SocketAddress addr) {
524 return supportedAddressSupplier(addr) != null;
525 }
526
527 /**
528 * Checks if the given address can be unwrapped to an {@link AFUNIXSocketAddress}, and if so,
529 * returns a supplier function; if not, {@code null} is returned.
530 *
531 * @param addr The address.
532 * @return The supplier, or {@code null}.
533 */
534 static AFSupplier<AFUNIXSocketAddress> supportedAddressSupplier(SocketAddress addr) {
535 if (addr == null) {
536 return null;
537 } else if (addr instanceof AFUNIXSocketAddress) {
538 return () -> ((AFUNIXSocketAddress) addr);
539 } else {
540 return SocketAddressUtil.supplyAFUNIXSocketAddress(addr);
541 }
542 }
543
544 /**
545 * Returns the corresponding {@link AFAddressFamily}.
546 *
547 * @return The address family instance.
548 */
549 @SuppressWarnings("null")
550 public static AFAddressFamily<AFUNIXSocketAddress> addressFamily() {
551 return AFUNIXSelectorProvider.getInstance().addressFamily();
552 }
553
554 @Override
555 public URI toURI(String scheme, URI template) throws IOException {
556 switch (scheme) {
557 case "unix":
558 case "file":
559 try {
560 if (getPort() > 0 && !"file".equals(scheme)) {
561 return new URI(scheme, null, "localhost", getPort(), getPath(), null, (String) null);
562 } else {
563 return new URI(scheme, null, null, -1, getPath(), null, null);
564 }
565 } catch (URISyntaxException e) {
566 throw new IOException(e);
567 }
568 case "http+unix":
569 case "https+unix":
570 HostAndPort hp = new HostAndPort(getPath(), getPort());
571 return hp.toURI(scheme, template);
572 default:
573 return super.toURI(scheme, template);
574 }
575 }
576
577 @Override
578 public AFUNIXSocket newConnectedSocket() throws IOException {
579 return (AFUNIXSocket) super.newConnectedSocket();
580 }
581
582 @Override
583 public AFUNIXServerSocket newBoundServerSocket() throws IOException {
584 return (AFUNIXServerSocket) super.newBoundServerSocket();
585 }
586
587 @Override
588 public AFUNIXServerSocket newForceBoundServerSocket() throws IOException {
589 return (AFUNIXServerSocket) super.newForceBoundServerSocket();
590 }
591 }