View Javadoc
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 }