001/*
002 * Copyright (C) 2015-2018 Neo Visionaries Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package NeoVisionaries.WebSockets;
017
018
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.net.URL;
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Map;
025import java.util.TreeMap;
026import javax.net.SocketFactory;
027import javax.net.ssl.SSLContext;
028import javax.net.ssl.SSLSocketFactory;
029
030
031/**
032 * Proxy settings.
033 * 
034 * <EMBED CLASS='external-html' DATA-FILE-ID=LICENSE><BR />
035 *
036 * <p>
037 * If a proxy server's host name is set (= if {@link #getHost()}
038 * returns a non-null value), a socket factory that creates a
039 * socket to communicate with the proxy server is selected based
040 * on the settings of this {@code ProxySettings} instance. The
041 * following is the concrete flow to select a socket factory.
042 * </p>
043 *
044 * <blockquote>
045 * <ol>
046 * <li>
047 *   If {@link #isSecure()} returns {@code true},
048 *   <ol type="i">
049 *     <li>
050 *       If an {@link SSLContext} instance has been set by {@link
051 *       #setSSLContext(SSLContext)}, the value returned from {@link
052 *       SSLContext#getSocketFactory()} method of the instance is used.
053 *     </li>
054 *     <li>
055 *       Otherwise, if an {@link SSLSocketFactory} instance has been
056 *       set by {@link #setSSLSocketFactory(SSLSocketFactory)}, the
057 *       instance is used.
058 *     </li>
059 *     <li>
060 *       Otherwise, the value returned from {@link SSLSocketFactory#getDefault()}
061 *       is used.
062 *     </li>
063 *   </ol>
064 * </li>
065 * <li>
066 *   Otherwise (= {@link #isSecure()} returns {@code false}),
067 *   <ol type="i">
068 *     <li>
069 *       If a {@link SocketFactory} instance has been set by {@link
070 *       #setSocketFactory(SocketFactory)}, the instance is used.
071 *     </li>
072 *     <li>
073 *       Otherwise, the value returned from {@link SocketFactory#getDefault()}
074 *       is used.
075 *     </li>
076 *   </ol>
077 * </li>
078 * </ol>
079 * </blockquote>
080 *
081 * <p>
082 * Note that the current implementation supports only Basic Authentication
083 * for authentication at the proxy server.
084 * </p>
085 *
086 * @see WebSocketFactory#getProxySettings()
087 *
088 * @since 1.3
089 */
090public class ProxySettings
091{
092    private final WebSocketFactory mWebSocketFactory;
093    private final Map<String, List<String>> mHeaders;
094    private final SocketFactorySettings mSocketFactorySettings;
095    private boolean mSecure;
096    private String mHost;
097    private int mPort;
098    private String mId;
099    private String mPassword;
100    private String[] mServerNames;
101
102
103    /**
104     * Constructor.
105     *         A {@code WebSocketFactory} instance to be associated with.
106     *
107     * @param factory factory
108     */
109    ProxySettings(WebSocketFactory factory)
110    {
111        mWebSocketFactory = factory;
112        mHeaders = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER);
113        mSocketFactorySettings = new SocketFactorySettings();
114
115        reset();
116    }
117
118
119    /**
120     * Constructor with settings.
121     *
122     * @param factory
123     *         A {@code WebSocketFactory} instance to be associated with.
124     *
125     * @param settings
126     *         Settings to copy.
127     *
128     * @since 2.10
129     */
130    ProxySettings(WebSocketFactory factory, ProxySettings settings)
131    {
132        this(factory);
133
134        mHeaders.putAll(settings.mHeaders);
135        mSecure   = settings.mSecure;
136        mHost     = settings.mHost;
137        mPort     = settings.mPort;
138        mId       = settings.mId;
139        mPassword = settings.mPassword;
140
141        if (settings.mServerNames != null)
142        {
143            mServerNames = new String[settings.mServerNames.length];
144            System.arraycopy(settings.mServerNames, 0, mServerNames, 0, mServerNames.length);
145        }
146    }
147
148
149    /**
150     * Get the associated {@link WebSocketFactory} instance.
151     */
152    public WebSocketFactory getWebSocketFactory()
153    {
154        return mWebSocketFactory;
155    }
156
157
158    /**
159     * Reset the proxy settings. To be concrete, parameter values are
160     * set as shown below.
161     *
162     * <blockquote>
163     * <table border="1" cellpadding="5" style="border-collapse: collapse;">
164     *   <thead>
165     *     <tr>
166     *       <th>Name</th>
167     *       <th>Value</th>
168     *       <th>Description</th>
169     *     </tr>
170     *   </thead>
171     *   <tbody>
172     *     <tr>
173     *       <td>Secure</td>
174     *       <td>{@code false}</td>
175     *       <td>Use TLS to connect to the proxy server or not.</td>
176     *     </tr>
177     *     <tr>
178     *       <td>Host</td>
179     *       <td>{@code null}</td>
180     *       <td>The host name of the proxy server.</td>
181     *     </tr>
182     *     <tr>
183     *       <td>Port</td>
184     *       <td>{@code -1}</td>
185     *       <td>The port number of the proxy server.</td>
186     *     </tr>
187     *     <tr>
188     *       <td>ID</td>
189     *       <td>{@code null}</td>
190     *       <td>The ID for authentication at the proxy server.</td>
191     *     </tr>
192     *     <tr>
193     *       <td>Password</td>
194     *       <td>{@code null}</td>
195     *       <td>The password for authentication at the proxy server.</td>
196     *     </tr>
197     *     <tr>
198     *       <td>Headers</td>
199     *       <td>Cleared</td>
200     *       <td>Additional HTTP headers passed to the proxy server.</td>
201     *     </tr>
202     *     <tr>
203     *       <td>Server Names</td>
204     *       <td>{@code null}</td>
205     *       <td>Server names for SNI (Server Name Indication).</td>
206     *     </tr>
207     *   </tbody>
208     * </table>
209     * </blockquote>
210     *
211     * @return
212     *         {@code this} object.
213     */
214    public ProxySettings reset()
215    {
216        mSecure   = false;
217        mHost     = null;
218        mPort     = -1;
219        mId       = null;
220        mPassword = null;
221        mHeaders.clear();
222        mServerNames = null;
223
224        return this;
225    }
226
227
228    /**
229     * Check whether use of TLS is enabled or disabled.
230     *
231     * @return
232     *         {@code true} if TLS is used in the communication with
233     *         the proxy server.
234     */
235    public boolean isSecure()
236    {
237        return mSecure;
238    }
239
240
241    /**
242     * Enable or disable use of TLS.
243     *
244     * @param secure
245     *         {@code true} to use TLS in the communication with
246     *         the proxy server.
247     *
248     * @return
249     *         {@code this} object.
250     */
251    public ProxySettings setSecure(boolean secure)
252    {
253        mSecure = secure;
254
255        return this;
256    }
257
258
259    /**
260     * Get the host name of the proxy server.
261     *
262     * <p>
263     * The default value is {@code null}. If this method returns
264     * a non-null value, it is used as the proxy server.
265     * </p>
266     *
267     * @return
268     *         The host name of the proxy server.
269     */
270    public String getHost()
271    {
272        return mHost;
273    }
274
275
276    /**
277     * Set the host name of the proxy server.
278     *
279     * <p>
280     * If a non-null value is set, it is used as the proxy server.
281     * </p>
282     *
283     * @param host
284     *         The host name of the proxy server.
285     *
286     * @return
287     *         {@code this} object.
288     */
289    public ProxySettings setHost(String host)
290    {
291        mHost = host;
292
293        return this;
294    }
295
296
297    /**
298     * Get the port number of the proxy server.
299     *
300     * <p>
301     * The default value is {@code -1}. {@code -1} means that
302     * the default port number ({@code 80} for non-secure
303     * connections and {@code 443} for secure connections)
304     * should be used.
305     * </p>
306     *
307     * @return
308     *         The port number of the proxy server.
309     */
310    public int getPort()
311    {
312        return mPort;
313    }
314
315
316    /**
317     * Set the port number of the proxy server.
318     *
319     * <p>
320     * If {@code -1} is set, the default port number ({@code 80}
321     * for non-secure connections and {@code 443} for secure
322     * connections) is used.
323     * </p>
324     *
325     * @param port
326     *         The port number of the proxy server.
327     *
328     * @return
329     *         {@code this} object.
330     */
331    public ProxySettings setPort(int port)
332    {
333        mPort = port;
334
335        return this;
336    }
337
338
339    /**
340     * Get the ID for authentication at the proxy server.
341     *
342     * <p>
343     * The default value is {@code null}. If this method returns
344     * a non-null value, it is used as the ID for authentication
345     * at the proxy server. To be concrete, the value is used to
346     * generate the value of <code><a href=
347     * "http://tools.ietf.org/html/rfc2616#section-14.34"
348     * >Proxy-Authorization</a></code> header.
349     * </p>
350     *
351     * @return
352     *         The ID for authentication at the proxy server.
353     */
354    public String getId()
355    {
356        return mId;
357    }
358
359
360    /**
361     * Set the ID for authentication at the proxy server.
362     *
363     * <p>
364     * If a non-null value is set, it is used as the ID for
365     * authentication at the proxy server. To be concrete, the
366     * value is used to generate the value of <code><a href=
367     * "http://tools.ietf.org/html/rfc2616#section-14.34"
368     * >Proxy-Authorization</a></code> header.
369     * </p>
370     *
371     * @param id
372     *         The ID for authentication at the proxy server.
373     *
374     * @return
375     *         {@code this} object.
376     */
377    public ProxySettings setId(String id)
378    {
379        mId = id;
380
381        return this;
382    }
383
384
385    /**
386     * Get the password for authentication at the proxy server.
387     *
388     * @return
389     *         The password for authentication at the proxy server.
390     */
391    public String getPassword()
392    {
393        return mPassword;
394    }
395
396
397    /**
398     * Set the password for authentication at the proxy server.
399     *
400     * @param password
401     *         The password for authentication at the proxy server.
402     *
403     * @return
404     *         {@code this} object.
405     */
406    public ProxySettings setPassword(String password)
407    {
408        mPassword = password;
409
410        return this;
411    }
412
413
414    /**
415     * Set credentials for authentication at the proxy server.
416     * This method is an alias of {@link #setId(String) setId}{@code
417     * (id).}{@link #setPassword(String) setPassword}{@code
418     * (password)}.
419     *
420     * @param id
421     *         The ID.
422     *
423     * @param password
424     *         The password.
425     *
426     * @return
427     *         {@code this} object.
428     */
429    public ProxySettings setCredentials(String id, String password)
430    {
431        return setId(id).setPassword(password);
432    }
433
434
435    /**
436     * Set the proxy server by a URI. See the description of
437     * {@link #setServer(URI)} about how the parameters are updated.
438     *
439     * @param uri
440     *         The URI of the proxy server. If {@code null} is given,
441     *         none of the parameters are updated.
442     *
443     * @return
444     *         {@code this} object.
445     *
446     * @throws IllegalArgumentException
447     *         Failed to convert the given string to a {@link URI} instance.
448     */
449    public ProxySettings setServer(String uri)
450    {
451        if (uri == null)
452        {
453            return this;
454        }
455
456        return setServer(URI.create(uri));
457    }
458
459
460    /**
461     * Set the proxy server by a URL. See the description of
462     * {@link #setServer(URI)} about how the parameters are updated.
463     *
464     * @param url
465     *         The URL of the proxy server. If {@code null} is given,
466     *         none of the parameters are updated.
467     *
468     * @return
469     *         {@code this} object.
470     *
471     * @throws IllegalArgumentException
472     *         Failed to convert the given URL to a {@link URI} instance.
473     */
474    public ProxySettings setServer(URL url)
475    {
476        if (url == null)
477        {
478            return this;
479        }
480
481        try
482        {
483            return setServer(url.toURI());
484        }
485        catch (URISyntaxException e)
486        {
487            throw new IllegalArgumentException(e);
488        }
489    }
490
491
492    /**
493     * Set the proxy server by a URI. The parameters are updated as
494     * described below.
495     *
496     * <blockquote>
497     * <dl>
498     *   <dt>Secure</dt>
499     *   <dd><p>
500     *     If the URI contains the scheme part and its value is
501     *     either {@code "http"} or {@code "https"} (case-insensitive),
502     *     the {@code secure} parameter is updated to {@code false}
503     *     or to {@code true} accordingly. In other cases, the parameter
504     *     is not updated.
505     *   </p></dd>
506     *   <dt>ID &amp; Password</dt>
507     *   <dd><p>
508     *     If the URI contains the userinfo part and the ID embedded
509     *     in the userinfo part is not an empty string, the {@code
510     *     id} parameter and the {@code password} parameter are updated
511     *     accordingly. In other cases, the parameters are not updated.
512     *   </p></dd>
513     *   <dt>Host</dt>
514     *   <dd><p>
515     *     The {@code host} parameter is always updated by the given URI.
516     *   </p></dd>
517     *   <dt>Port</dt>
518     *   <dd><p>
519     *     The {@code port} parameter is always updated by the given URI.
520     *   </p></dd>
521     * </dl>
522     * </blockquote>
523     *
524     * @param uri
525     *         The URI of the proxy server. If {@code null} is given,
526     *         none of the parameters is updated.
527     *
528     * @return
529     *         {@code this} object.
530     */
531    public ProxySettings setServer(URI uri)
532    {
533        if (uri == null)
534        {
535            return this;
536        }
537
538        String scheme   = uri.getScheme();
539        String userInfo = uri.getUserInfo();
540        String host     = uri.getHost();
541        int port        = uri.getPort();
542
543        return setServer(scheme, userInfo, host, port);
544    }
545
546
547    private ProxySettings setServer(String scheme, String userInfo, String host, int port)
548    {
549        setByScheme(scheme);
550        setByUserInfo(userInfo);
551        mHost = host;
552        mPort = port;
553
554        return this;
555    }
556
557
558    private void setByScheme(String scheme)
559    {
560        if ("http".equalsIgnoreCase(scheme))
561        {
562            mSecure = false;
563        }
564        else if ("https".equalsIgnoreCase(scheme))
565        {
566            mSecure = true;
567        }
568    }
569
570
571    private void setByUserInfo(String userInfo)
572    {
573        if (userInfo == null)
574        {
575            return;
576        }
577
578        String[] pair = userInfo.split(":", 2);
579        String id;
580        String pw;
581
582        switch (pair.length)
583        {
584            case 2:
585                id = pair[0];
586                pw = pair[1];
587                break;
588
589            case 1:
590                id = pair[0];
591                pw = null;
592                break;
593
594            default:
595                return;
596        }
597
598        if (id.length() == 0)
599        {
600            return;
601        }
602
603        mId       = id;
604        mPassword = pw;
605    }
606
607
608    /**
609     * Get additional HTTP headers passed to the proxy server.
610     *
611     * @return
612     *         Additional HTTP headers passed to the proxy server.
613     *         The comparator of the returned map is {@link
614     *         String#CASE_INSENSITIVE_ORDER}.
615     */
616    public Map<String, List<String>> getHeaders()
617    {
618        return mHeaders;
619    }
620
621
622    /**
623     * Add an additional HTTP header passed to the proxy server.
624     *
625     * @param name
626     *         The name of an HTTP header (case-insensitive).
627     *         If {@code null} or an empty string is given,
628     *         nothing is added.
629     *
630     * @param value
631     *         The value of the HTTP header.
632     *
633     * @return
634     *         {@code this} object.
635     */
636    public ProxySettings addHeader(String name, String value)
637    {
638        if (name == null || name.length() == 0)
639        {
640            return this;
641        }
642
643        List<String> list = mHeaders.get(name);
644
645        if (list == null)
646        {
647            list = new ArrayList<String>();
648            mHeaders.put(name, list);
649        }
650
651        list.add(value);
652
653        return this;
654    }
655
656
657    /**
658     * Get the socket factory that has been set by {@link
659     * #setSocketFactory(SocketFactory)}.
660     *
661     * @return
662     *         The socket factory.
663     */
664    public SocketFactory getSocketFactory()
665    {
666        return mSocketFactorySettings.getSocketFactory();
667    }
668
669
670    /**
671     * Set a socket factory.
672     *
673     * @param factory
674     *         A socket factory.
675     *
676     * @return
677     *         {@code this} instance.
678     */
679    public ProxySettings setSocketFactory(SocketFactory factory)
680    {
681        mSocketFactorySettings.setSocketFactory(factory);
682
683        return this;
684    }
685
686
687    /**
688     * Get the SSL socket factory that has been set by {@link
689     * #setSSLSocketFactory(SSLSocketFactory)}.
690     *
691     * @return
692     *         The SSL socket factory.
693     */
694    public SSLSocketFactory getSSLSocketFactory()
695    {
696        return mSocketFactorySettings.getSSLSocketFactory();
697    }
698
699
700    /**
701     * Set an SSL socket factory.
702     *
703     * @param factory
704     *         An SSL socket factory.
705     *
706     * @return
707     *         {@code this} instance.
708     */
709    public ProxySettings setSSLSocketFactory(SSLSocketFactory factory)
710    {
711        mSocketFactorySettings.setSSLSocketFactory(factory);
712
713        return this;
714    }
715
716
717    /**
718     * Get the SSL context that has been set by {@link #setSSLContext(SSLContext)}.
719     *
720     * @return
721     *         The SSL context.
722     */
723    public SSLContext getSSLContext()
724    {
725        return mSocketFactorySettings.getSSLContext();
726    }
727
728
729    /**
730     * Set an SSL context to get a socket factory.
731     *
732     * @param context
733     *         An SSL context.
734     *
735     * @return
736     *         {@code this} instance.
737     */
738    public ProxySettings setSSLContext(SSLContext context)
739    {
740        mSocketFactorySettings.setSSLContext(context);
741
742        return this;
743    }
744
745
746    SocketFactory selectSocketFactory()
747    {
748        return mSocketFactorySettings.selectSocketFactory(mSecure);
749    }
750
751
752    /**
753     * Get server names for SNI (Server Name Indication).
754     *
755     * @return
756     *         List of host names.
757     *
758     * @since 2.4
759     */
760    public String[] getServerNames()
761    {
762        return mServerNames;
763    }
764
765
766    /**
767     * Set server names for SNI (Server Name Indication).
768     *
769     * If {@code setServerNames(List<SNIServerName>)} method of
770     * {@link javax.net.ssl.SSLParameters SSLParameters} class is available
771     * in the underlying system, the method is called to set up server names
772     * for SNI (Server Name Indication).
773     *
774     * @param serverNames
775     *         List of host names.
776     *
777     * @return
778     *         {@code this} object.
779     *
780     * @since 2.4
781     */
782    public ProxySettings setServerNames(String[] serverNames)
783    {
784        mServerNames = serverNames;
785
786        return this;
787    }
788
789
790    /**
791     * Set a server name for SNI (Server Name Indication).
792     *
793     * This method internally creates a String array of size 1 which
794     * contains the given {@code serverName} and calls {@link
795     * #setServerNames(String[])}.
796     *
797     * @param serverName
798     *         A host name.
799     *
800     * @return
801     *         {@code this} object.
802     *
803     * @since 2.4
804     */
805    public ProxySettings setServerName(String serverName)
806    {
807        return setServerNames(new String[] { serverName });
808    }
809}