View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 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.IOException;
21  import java.net.InetAddress;
22  import java.net.Socket;
23  import java.net.SocketAddress;
24  import java.net.SocketException;
25  import java.util.Objects;
26  
27  import javax.net.SocketFactory;
28  
29  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
30  
31  /**
32   * The base for a SocketFactory that connects to UNIX sockets.
33   *
34   * Typically, the "hostname" is used as a reference to a socketFile on the file system. The actual
35   * mapping is left to the implementor.
36   *
37   * @see AFUNIXSocketFactory
38   * @param <A> The supported address type.
39   */
40  @SuppressFBWarnings("UNENCRYPTED_SOCKET")
41  public abstract class AFSocketFactory<A extends AFSocketAddress> extends SocketFactory implements
42      AFSocketAddressFromHostname<A> {
43  
44    private final Class<? extends AFSocketAddress> socketAddressClass;
45  
46    /**
47     * Creates a new socket factory instance.
48     *
49     * @param socketAddressClass The AFSocketAddress subclass.
50     */
51    protected AFSocketFactory(Class<? extends AFSocketAddress> socketAddressClass) {
52      super();
53      this.socketAddressClass = socketAddressClass;
54    }
55  
56    /**
57     * Checks whether the given {@link InetAddress} is supported by this socket factory. If not, calls
58     * to createSocket will cause a {@link SocketException}.
59     *
60     * By default, this only checks the hostname part of the address via
61     * {@link #isHostnameSupported(String)}.
62     *
63     * @param address The address to check.
64     * @return {@code true} if supported.
65     */
66    protected final boolean isInetAddressSupported(InetAddress address) {
67      return address != null && isHostnameSupported(address.getHostName());
68    }
69  
70    @Override
71    public abstract Socket createSocket() throws SocketException;
72  
73    /**
74     * Creates a new {@link AFSocket}, connected to the given address.
75     *
76     * @param addr The address to connect to.
77     * @return The socket instance.
78     * @throws IOException on error.
79     */
80    protected abstract Socket connectTo(A addr) throws IOException;
81  
82    @SuppressWarnings("unchecked")
83    @SuppressFBWarnings("UNENCRYPTED_SOCKET")
84    private Socket connectTo(SocketAddress addr) throws IOException {
85      if (AFSocketAddress.canMap(addr, socketAddressClass)) {
86        return connectTo((A) AFSocketAddress.mapOrFail(addr, socketAddressClass));
87      } else {
88        Socket sock = new Socket();
89        sock.connect(addr);
90        return sock;
91      }
92    }
93  
94    @Override
95    public final Socket createSocket(String host, int port) throws IOException {
96      if (!isHostnameSupported(host)) {
97        throw new SocketException("Unsupported hostname");
98      }
99      if (port < 0) {
100       throw new IllegalArgumentException("Illegal port");
101     }
102 
103     SocketAddress socketAddress = addressFromHost(host, port);
104     return connectTo(socketAddress);
105   }
106 
107   @Override
108   public final Socket createSocket(String host, int port, InetAddress localHost, int localPort)
109       throws IOException {
110     if (!isHostnameSupported(host)) {
111       throw new SocketException("Unsupported hostname");
112     }
113     if (localPort < 0) {
114       throw new IllegalArgumentException("Illegal local port");
115     }
116     // NOTE: we simply ignore localHost and localPort
117     return createSocket(host, port);
118   }
119 
120   @Override
121   public final Socket createSocket(InetAddress address, int port) throws IOException {
122     if (!isInetAddressSupported(address)) {
123       throw new SocketException("Unsupported address");
124     }
125     String hostname = address.getHostName();
126     if (!isHostnameSupported(hostname)) {
127       throw new SocketException("Unsupported hostname");
128     }
129     return createSocket(hostname, port);
130   }
131 
132   @Override
133   public final Socket createSocket(InetAddress address, int port, InetAddress localAddress,
134       int localPort) throws IOException {
135     if (!isInetAddressSupported(address)) {
136       throw new SocketException("Unsupported address");
137     }
138     if (localPort < 0) {
139       throw new IllegalArgumentException("Illegal local port");
140     }
141     // NOTE: we simply ignore localAddress and localPort
142     return createSocket(address, port);
143   }
144 
145   /**
146    * A socket factory that always connects to a fixed socket address, no matter what.
147    */
148   public static final class FixedAddressSocketFactory extends AFSocketFactory<AFSocketAddress> {
149     private final SocketAddress forceAddr;
150 
151     /**
152      * Creates a {@link FixedAddressSocketFactory}.
153      *
154      * @param address The address to use for all connections.
155      */
156     public FixedAddressSocketFactory(SocketAddress address) {
157       super(AFSocketAddress.class);
158       this.forceAddr = Objects.requireNonNull(address);
159     }
160 
161     @Override
162     public boolean isHostnameSupported(String host) {
163       return true;
164     }
165 
166     @Override
167     public SocketAddress addressFromHost(String host, int port) throws SocketException {
168       return forceAddr;
169     }
170 
171     @Override
172     public Socket createSocket() throws SocketException {
173       try {
174         if (AFSocketAddress.canMap(forceAddr)) {
175           AFSocket<?> socket = AFSocketAddress.mapOrFail(forceAddr).getAddressFamily().newSocket();
176           socket.forceConnectAddress(forceAddr);
177           return socket;
178         } else {
179           return new Socket() {
180             @Override
181             public void connect(SocketAddress endpoint) throws IOException {
182               super.connect(forceAddr);
183             }
184 
185             @Override
186             public void connect(SocketAddress endpoint, int timeout) throws IOException {
187               super.connect(forceAddr, timeout);
188             }
189           };
190         }
191       } catch (SocketException e) {
192         throw e;
193       } catch (IOException e) {
194         throw (SocketException) new SocketException().initCause(e);
195       }
196     }
197 
198     @Override
199     protected Socket connectTo(AFSocketAddress addr) throws IOException {
200       Socket sock = createSocket();
201       sock.connect(forceAddr);
202       return sock;
203     }
204   }
205 }