001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.network;
020
021import java.awt.Desktop;
022import java.awt.Desktop.Action;
023import java.io.BufferedInputStream;
024import java.io.BufferedReader;
025import java.io.ByteArrayOutputStream;
026import java.io.DataOutputStream;
027import java.io.EOFException;
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.InputStreamReader;
033import java.io.UnsupportedEncodingException;
034import java.lang.reflect.Method;
035import java.net.Authenticator;
036import java.net.HttpURLConnection;
037import java.net.InetSocketAddress;
038import java.net.PasswordAuthentication;
039import java.net.Socket;
040import java.net.URI;
041import java.net.URISyntaxException;
042import java.net.URL;
043import java.net.URLConnection;
044import java.net.URLDecoder;
045import java.net.URLEncoder;
046import java.security.cert.CertificateException;
047import java.security.cert.X509Certificate;
048import java.util.HashMap;
049import java.util.HashSet;
050import java.util.Map;
051import java.util.Map.Entry;
052import java.util.Set;
053
054import javax.net.ssl.HostnameVerifier;
055import javax.net.ssl.HttpsURLConnection;
056import javax.net.ssl.SSLContext;
057import javax.net.ssl.SSLEngine;
058import javax.net.ssl.SSLSession;
059import javax.net.ssl.TrustManager;
060
061import icy.common.Version;
062import icy.common.listener.ProgressListener;
063import icy.common.listener.weak.WeakListener;
064import icy.file.FileUtil;
065import icy.preferences.NetworkPreferences;
066import icy.system.IcyExceptionHandler;
067import icy.system.SystemUtil;
068import icy.system.audit.Audit;
069import icy.system.thread.ThreadUtil;
070import icy.util.StringUtil;
071
072/**
073 * @author stephane
074 */
075public class NetworkUtil
076{
077    /**
078     * URL
079     */
080    public static final String WEBSITE_HOST = "icy.bioimageanalysis.org";
081    public static final String WEBSITE_URL = "http://" + WEBSITE_HOST + "/";
082
083    static final String REPORT_URL = WEBSITE_URL + "index.php";
084
085    /**
086     * Parameters id
087     */
088    public static final String ID_KERNELVERSION = "kernelVersion";
089    public static final String ID_BETAALLOWED = "betaAllowed";
090    public static final String ID_JAVANAME = "javaName";
091    public static final String ID_JAVAVERSION = "javaVersion";
092    public static final String ID_JAVABITS = "javaBits";
093    public static final String ID_OSNAME = "osName";
094    public static final String ID_OSVERSION = "osVersion";
095    public static final String ID_OSARCH = "osArch";
096    public static final String ID_PLUGINCLASSNAME = "pluginClassName";
097    public static final String ID_PLUGINVERSION = "pluginVersion";
098    public static final String ID_DEVELOPERID = "developerId";
099    public static final String ID_ERRORLOG = "errorLog";
100
101    /**
102     * Proxy config ID
103     */
104    public static final int NO_PROXY = 0;
105    public static final int SYSTEM_PROXY = 1;
106    public static final int USER_PROXY = 2;
107
108    public interface InternetAccessListener
109    {
110        /**
111         * Internet connection available.
112         */
113        public void internetUp();
114
115        /**
116         * Internet connection no more available.
117         */
118        public void internetDown();
119    }
120
121    /**
122     * Weak listener wrapper for NetworkConnectionListener.
123     * 
124     * @author Stephane
125     */
126    public static class WeakInternetAccessListener extends WeakListener<InternetAccessListener>
127            implements InternetAccessListener
128    {
129        public WeakInternetAccessListener(InternetAccessListener listener)
130        {
131            super(listener);
132        }
133
134        @Override
135        public void removeListener(Object source)
136        {
137            removeInternetAccessListener(this);
138        }
139
140        @Override
141        public void internetUp()
142        {
143            final InternetAccessListener listener = getListener();
144
145            if (listener != null)
146                listener.internetUp();
147        }
148
149        @Override
150        public void internetDown()
151        {
152            final InternetAccessListener listener = getListener();
153
154            if (listener != null)
155                listener.internetDown();
156        }
157    }
158
159    /**
160     * Internet monitor thread
161     */
162    private static class InternetMonitorThread extends Thread
163    {
164        public InternetMonitorThread()
165        {
166            super("Internet monitor");
167        }
168
169        @Override
170        public void run()
171        {
172            while (!isInterrupted())
173            {
174                try
175                {
176                    final Socket socket = new Socket();
177
178                    // timeout = 3 seconds
179                    socket.setSoTimeout(3000);
180                    socket.connect(new InetSocketAddress(WEBSITE_HOST, 80), 3000);
181                    socket.close();
182
183                    // we have internet access
184                    setInternetAccess(true);
185                }
186                catch (Throwable t1)
187                {
188                    // in case we use proxy
189                    try
190                    {
191                        final URLConnection urlConnection = openConnection("http://www.google.com", true, false);
192
193                        if (urlConnection != null)
194                        {
195                            urlConnection.setConnectTimeout(3000);
196                            urlConnection.setReadTimeout(3000);
197                            urlConnection.getInputStream();
198
199                            // we have internet access
200                            setInternetAccess(true);
201                        }
202                        else
203                            // we don't have internet access
204                            setInternetAccess(false);
205                    }
206                    catch (Throwable t2)
207                    {
208                        // we don't have internet access
209                        setInternetAccess(false);
210                    }
211                }
212
213                // wait a bit depending connection state
214                ThreadUtil.sleep(hasInternetAccess() ? 30000 : 5000);
215            }
216        }
217    };
218
219    /**
220     * 'Accept all' host name verifier
221     */
222    private static class FakeHostnameVerifier implements HostnameVerifier
223    {
224        public FakeHostnameVerifier()
225        {
226            super();
227        }
228
229        @Override
230        public boolean verify(String hostname, SSLSession session)
231        {
232            // always return true
233            return true;
234        }
235    };
236
237    /**
238     * List of all listeners on network connection changes.
239     */
240    private final static Set<InternetAccessListener> listeners = new HashSet<InternetAccessListener>();;
241
242    /**
243     * Internet monitor
244     */
245    public static final InternetMonitorThread internetMonitor = new InternetMonitorThread();
246    /**
247     * Internet access up flag
248     */
249    private static boolean internetAccess;
250    /**
251     * internal HTTPS compatibility for the new web site
252     */
253    private static boolean httpsSupported;
254
255    public static void init()
256    {
257        internetAccess = false;
258        httpsSupported = false;
259
260        // check for HTTPS "let's encrypt" certificate compatibility
261        final Version javaVersion = SystemUtil.getJavaVersionAsVersion();
262        final int javaInt = javaVersion.getMajor();
263
264        if (javaInt == 7)
265            httpsSupported = javaVersion.isGreaterOrEqual(new Version("7.0.111"));
266        else if (javaInt == 8)
267            httpsSupported = javaVersion.isGreaterOrEqual(new Version("8.0.101"));
268        else
269            httpsSupported = (javaInt >= 9);
270
271        updateNetworkSetting();
272        // accept all HTTPS connections by default
273        installTruster();
274
275        // String addr;
276        //
277        // // --> connection HTTPS: fails with java 7, ok with java 8 (let's encrypt certificate)
278        // addr = "https://icy.yhello.co";
279        // // addr = "http://icy.yhello.co/update/update.php?arch=win64&version=1.9.8.2";
280        // // addr = "https://icy.yhello.co/update/update.php?arch=win64&version=1.9.8.2";
281        // // addr = "https://icy.yhello.co/register/getLinkedUserInfo.php?IcyId=4817172";
282        // // addr = "https://randomuser.me/";
283        //
284        // try
285        // {
286        // HttpURLConnection uc = (HttpURLConnection) new URL(addr).openConnection();
287        // uc.connect();
288        // if (uc instanceof HttpsURLConnection)
289        // System.out.println(((HttpsURLConnection) uc).getLocalPrincipal());
290        // if (uc instanceof HttpURLConnection)
291        // System.out.println(((HttpURLConnection) uc).getResponseCode() + " - "
292        // + ((HttpURLConnection) uc).getResponseMessage());
293        //
294        // InputStream inputStream = uc.getInputStream();
295        // inputStream.read();
296        // uc.disconnect();
297        // }
298        // catch (Exception e)
299        // {
300        // // TODO Auto-generated catch block
301        // e.printStackTrace();
302        // }
303
304        // start monitor thread
305        internetMonitor.setPriority(Thread.MIN_PRIORITY);
306        internetMonitor.start();
307    }
308
309    private static void installTruster()
310    {
311        // enable support for TLS v1.X (used by new Icy web site: TLS 1.2 or TLS 1.3)
312        System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
313
314        try
315        {
316            // install the accept all host name verifier
317            HttpsURLConnection.setDefaultHostnameVerifier(new FakeHostnameVerifier());
318
319            // create a trust manager that does not validate certificate chains (Accept all certificates)
320            final TrustManager[] trustAllCerts = new TrustManager[] {new javax.net.ssl.X509ExtendedTrustManager()
321            {
322                @Override
323                public java.security.cert.X509Certificate[] getAcceptedIssuers()
324                {
325                    return null;
326                }
327
328                @Override
329                public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
330                {
331                    // ignore
332                    // System.out.println(certs.length);
333                }
334
335                @Override
336                public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
337                {
338                    // ignore
339                    // System.out.println(certs.length + " - " + authType);
340                }
341
342                @Override
343                public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
344                        throws CertificateException
345                {
346                    // ignore
347                    // System.out.println(chain.length + " - " + authType);
348                }
349
350                @Override
351                public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
352                        throws CertificateException
353                {
354                    // ignore
355                    // System.out.println(chain.length + " - " + authType);
356                }
357
358                @Override
359                public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
360                        throws CertificateException
361                {
362                    // ignore
363                    // System.out.println(chain.length + " - " + authType);
364                }
365
366                @Override
367                public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
368                        throws CertificateException
369                {
370                    // ignore
371                    // System.out.println(chain.length + " - " + authType);
372                }
373            }};
374
375            // install the all-trusting trust manager
376            SSLContext sc;
377
378            sc = SSLContext.getInstance("SSL");
379            if (sc != null)
380                sc.init(null, trustAllCerts, new java.security.SecureRandom());
381            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
382
383            sc = SSLContext.getInstance("TLS");
384            if (sc != null)
385                sc.init(null, trustAllCerts, new java.security.SecureRandom());
386        }
387        catch (Exception e)
388        {
389            IcyExceptionHandler.showErrorMessage(e, false, true);
390        }
391    }
392
393    /**
394     * Update network setting from the actual preferences
395     */
396    public static void updateNetworkSetting()
397    {
398        HttpURLConnection.setFollowRedirects(false);
399
400        final int proxySetting = NetworkPreferences.getProxySetting();
401
402        if (proxySetting == NO_PROXY)
403        {
404            // no proxy
405            disableProxySetting();
406            disableHTTPProxySetting();
407            disableHTTPSProxySetting();
408            disableFTPProxySetting();
409            disableSOCKSProxySetting();
410            disableSystemProxy();
411        }
412        else if (proxySetting == SYSTEM_PROXY)
413        {
414            // system proxy
415            disableProxySetting();
416            disableHTTPProxySetting();
417            disableHTTPSProxySetting();
418            disableFTPProxySetting();
419            disableSOCKSProxySetting();
420            enableSystemProxy();
421        }
422        else
423        {
424            final String user = NetworkPreferences.getProxyUser();
425            final String pass = NetworkPreferences.getProxyPassword();
426            final boolean auth = NetworkPreferences.getProxyAuthentication() && (!StringUtil.isEmpty(user))
427                    && (!StringUtil.isEmpty(pass));
428            String host;
429
430            // authentication enabled ?
431            if (auth)
432            {
433                Authenticator.setDefault(new Authenticator()
434                {
435                    @Override
436                    public PasswordAuthentication getPasswordAuthentication()
437                    {
438                        return new PasswordAuthentication(user, pass.toCharArray());
439                    }
440                });
441            }
442
443            // manual proxy
444            disableSystemProxy();
445
446            // HTTP proxy (use it as general proxy)
447            host = NetworkPreferences.getProxyHTTPHost();
448            if (!StringUtil.isEmpty(host))
449            {
450                final int port = NetworkPreferences.getProxyHTTPPort();
451
452                setProxyHost(host);
453                setProxyPort(port);
454                setHTTPProxyHost(host);
455                setHTTPProxyPort(port);
456                if (auth)
457                {
458                    setHTTPProxyUser(user);
459                    setHTTPProxyPassword(pass);
460                }
461                enableProxySetting();
462                enableHTTPProxySetting();
463            }
464            else
465            {
466                disableProxySetting();
467                disableHTTPProxySetting();
468            }
469
470            // HTTPS proxy
471            host = NetworkPreferences.getProxyHTTPSHost();
472            if (!StringUtil.isEmpty(host))
473            {
474                setHTTPSProxyHost(host);
475                setHTTPSProxyPort(NetworkPreferences.getProxyHTTPSPort());
476                if (auth)
477                {
478                    setHTTPSProxyUser(user);
479                    setHTTPSProxyPassword(pass);
480                }
481                enableHTTPSProxySetting();
482            }
483            else
484                disableHTTPSProxySetting();
485
486            // FTP proxy
487            host = NetworkPreferences.getProxyFTPHost();
488            if (!StringUtil.isEmpty(host))
489            {
490                setFTPProxyHost(host);
491                setFTPProxyPort(NetworkPreferences.getProxyFTPPort());
492                if (auth)
493                {
494                    setFTPProxyUser(user);
495                    setFTPProxyPassword(pass);
496                }
497                enableFTPProxySetting();
498            }
499            else
500                disableFTPProxySetting();
501
502            // SOCKS proxy
503            host = NetworkPreferences.getProxySOCKSHost();
504            if (!StringUtil.isEmpty(host))
505            {
506                setSOCKSProxyHost(host);
507                setSOCKSProxyPort(NetworkPreferences.getProxySOCKSPort());
508                if (auth)
509                {
510                    setSOCKSProxyUser(user);
511                    setSOCKSProxyPassword(pass);
512                }
513                enableSOCKSProxySetting();
514            }
515            else
516                disableSOCKSProxySetting();
517        }
518    }
519
520    static void setInternetAccess(boolean value)
521    {
522        if (internetAccess != value)
523        {
524            internetAccess = value;
525
526            fireInternetConnectionEvent(value);
527
528            // local stuff to do on connection recovery
529            if (value)
530            {
531                // process id audit
532                Audit.onConnect();
533            }
534        }
535    }
536
537    private static void fireInternetConnectionEvent(boolean value)
538    {
539        if (value)
540        {
541            for (InternetAccessListener l : listeners)
542                l.internetUp();
543        }
544        else
545        {
546            for (InternetAccessListener l : listeners)
547                l.internetDown();
548        }
549    }
550
551    /**
552     * Adds a new listener on internet access change.
553     */
554    public static void addInternetAccessListener(InternetAccessListener listener)
555    {
556        listeners.add(listener);
557    }
558
559    /**
560     * Removes a listener on internet access change.
561     */
562    public static void removeInternetAccessListener(InternetAccessListener listener)
563    {
564        listeners.remove(listener);
565    }
566
567    /**
568     * Returns true if we currently have Internet connection.
569     */
570    public static boolean hasInternetAccess()
571    {
572        return internetAccess;
573    }
574
575    /**
576     * @deprecated Use {@link #hasInternetAccess()} instead.
577     */
578    @Deprecated
579    public static boolean hasInternetConnection()
580    {
581        return hasInternetAccess();
582    }
583
584    /**
585     * Returns true if HTTPS is supported for the new web site.
586     */
587    public static boolean isHTTPSSupported()
588    {
589        return httpsSupported;
590    }
591
592    /**
593     * Open an URL in the default system browser
594     */
595    public static boolean openBrowser(String url)
596    {
597        return openBrowser(URLUtil.getURL(url));
598    }
599
600    /**
601     * Open an URL in the default system browser
602     */
603    public static boolean openBrowser(URL url)
604    {
605        if (url == null)
606            return false;
607
608        try
609        {
610            return openBrowser(url.toURI());
611        }
612        catch (URISyntaxException e)
613        {
614            // use other method
615            return systemOpenBrowser(url.toString());
616        }
617    }
618
619    /**
620     * Open an URL in the default system browser
621     */
622    public static boolean openBrowser(URI uri)
623    {
624        if (uri == null)
625            return false;
626
627        final Desktop desktop = SystemUtil.getDesktop();
628
629        if ((desktop != null) && desktop.isSupported(Action.BROWSE))
630        {
631            try
632            {
633                desktop.browse(uri);
634                return true;
635            }
636            catch (IOException e)
637            {
638                // ignore
639            }
640        }
641
642        // not
643        return systemOpenBrowser(uri.toString());
644    }
645
646    /**
647     * Open an URL in the default system browser (low level method)
648     */
649    private static boolean systemOpenBrowser(String url)
650    {
651        if (StringUtil.isEmpty(url))
652            return false;
653
654        try
655        {
656            if (SystemUtil.isMac())
657            {
658                Class<?> fileMgr = Class.forName("com.apple.eio.FileManager");
659                Method openURL = fileMgr.getDeclaredMethod("openURL", new Class[] {String.class});
660                openURL.invoke(null, new Object[] {url});
661            }
662            else if (SystemUtil.isWindows())
663                Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
664            else
665            {
666                // assume Unix or Linux
667                String[] browsers = {"firefox", "opera", "konqueror", "epiphany", "mozilla", "netscape"};
668                String browser = null;
669                for (int count = 0; count < browsers.length && browser == null; count++)
670                {
671                    if (Runtime.getRuntime().exec("which " + browsers[count]).waitFor() == 0)
672                        browser = browsers[count];
673                }
674                if (browser == null)
675                    throw new Exception("Could not find web browser");
676
677                Runtime.getRuntime().exec(new String[] {browser, url});
678            }
679
680            return true;
681        }
682        catch (Exception e)
683        {
684            System.err.println("Error while opening system browser :\n" + e.toString());
685            return false;
686        }
687    }
688
689    /**
690     * @deprecated Use {@link #openBrowser(String)} instead.
691     */
692    @Deprecated
693    public static void openURL(String url)
694    {
695        openBrowser(url);
696    }
697
698    /**
699     * @deprecated Use {@link #openBrowser(URL)} instead.
700     */
701    @Deprecated
702    public static void openURL(URL url)
703    {
704        openBrowser(url);
705    }
706
707    /**
708     * @deprecated Use {@link #openBrowser(URI)} instead.
709     */
710    @Deprecated
711    public static void openURL(URI uri)
712    {
713        openBrowser(uri);
714    }
715
716    /**
717     * Download data from specified URL string and return it as an array of byte
718     */
719    public static byte[] download(String path, ProgressListener listener, boolean displayError)
720    {
721        return download(path, null, null, listener, displayError);
722    }
723
724    /**
725     * Download data from specified URL string and return it as an array of byte
726     * Process authentication process if login / pass are not null.
727     */
728    public static byte[] download(String path, String login, String pass, ProgressListener listener,
729            boolean displayError)
730    {
731        final File file = new File(FileUtil.getGenericPath(path));
732
733        // path define a file ?
734        if (file.exists())
735            return download(file, listener, displayError);
736
737        final URL url = URLUtil.getURL(path);
738
739        // error while building URL ?
740        if (url == null)
741        {
742            if (displayError)
743                System.out.println("Can't download '" + path + "', incorrect path !");
744
745            return null;
746        }
747
748        return download(url, login, pass, listener, displayError);
749    }
750
751    /**
752     * Download data from specified URL and return it as an array of byte
753     */
754    public static byte[] download(URL url, ProgressListener listener, boolean displayError)
755    {
756        return download(url, null, null, listener, displayError);
757    }
758
759    /**
760     * Download data from specified URL and return it as an array of byte.<br>
761     * Process authentication process if login / pass fields are not null.<br>
762     * It returns <code>null</code> if an error occurred.
763     */
764    public static byte[] download(URL url, String login, String pass, ProgressListener listener, boolean displayError)
765    {
766        // check if this is a file
767        if ((url != null) && URLUtil.isFileURL(url))
768        {
769            try
770            {
771                return download(new File(url.toURI()), listener, displayError);
772            }
773            catch (URISyntaxException e)
774            {
775                if (displayError)
776                    System.out.println("Can't download from '" + url + "', incorrect path !");
777
778                return null;
779            }
780        }
781
782        // get connection object and connect it
783        final URLConnection uc = openConnection(url, login, pass, true, true, displayError);
784        final InputStream ip = getInputStream(uc, displayError);
785
786        // error --> exit
787        if (ip == null)
788            return null;
789
790        try
791        {
792            return download(ip, uc.getContentLength(), listener);
793        }
794        catch (Exception e)
795        {
796            if (displayError)
797            {
798                System.out.println("Error while downloading '" + uc.getURL() + "' :");
799                IcyExceptionHandler.showErrorMessage(e, false, false);
800            }
801
802            return null;
803        }
804        finally
805        {
806            try
807            {
808                ip.close();
809            }
810            catch (IOException e)
811            {
812                // ignore...
813            }
814        }
815    }
816
817    /**
818     * Download data from File and return it as an array of byte.<br>
819     * It returns <code>null</code> if an error occurred (file not found or not existing, IO
820     * error...)
821     */
822    public static byte[] download(File f, ProgressListener listener, boolean displayError)
823    {
824        if (!f.exists())
825        {
826            System.err.println("File not found: " + f.getPath());
827            return null;
828        }
829
830        try
831        {
832            return download(new FileInputStream(f), f.length(), listener);
833        }
834        catch (Exception e)
835        {
836            if (displayError)
837            {
838                System.out.println("NetworkUtil.download('" + f.getPath() + "',...) error :");
839                IcyExceptionHandler.showErrorMessage(e, false, false);
840            }
841
842            return null;
843        }
844    }
845
846    /**
847     * Download data from specified InputStream and return it as an array of byte.<br>
848     * Returns <code>null</code> if load operation was interrupted by user.
849     */
850    public static byte[] download(InputStream in, long len, ProgressListener listener) throws IOException
851    {
852        final int READ_BLOCKSIZE = 64 * 1024;
853        final BufferedInputStream bin;
854
855        if (in instanceof BufferedInputStream)
856            bin = (BufferedInputStream) in;
857        else
858            bin = new BufferedInputStream(in);
859
860        final ByteArrayOutputStream bout = new ByteArrayOutputStream((int) ((len > 0) ? len : READ_BLOCKSIZE));
861        // read per block of 64 KB
862        final byte[] data = new byte[READ_BLOCKSIZE];
863
864        try
865        {
866            int off = 0;
867            int count = 0;
868
869            while (count >= 0)
870            {
871                count = bin.read(data);
872                if (count <= 0)
873                {
874                    // unexpected length
875                    if ((len != -1) && (off != len))
876                        throw new EOFException("Unexpected end of file at " + off + " (" + len + " expected)");
877                }
878                else
879                    off += count;
880
881                // copy to dynamic buffer
882                if (count > 0)
883                    bout.write(data, 0, count);
884
885                if (listener != null)
886                {
887                    // download canceled ?
888                    if (!listener.notifyProgress(off, len))
889                    {
890                        in.close();
891                        System.out.println("Interrupted by user.");
892                        return null;
893                    }
894                }
895            }
896        }
897        finally
898        {
899            bin.close();
900        }
901
902        return bout.toByteArray();
903    }
904
905    /**
906     * Download data from specified InputStream and return it as an array of byte.<br>
907     * It returns <code>null</code> if an error occurred.
908     */
909    public static byte[] download(InputStream in) throws IOException
910    {
911        return download(in, -1, null);
912    }
913
914    /**
915     * Returns a new {@link URLConnection} from specified URL (null if an error occurred).
916     * 
917     * @param url
918     *        url to connect.
919     * @param login
920     *        login if the connection requires authentication.<br>
921     *        Set it to null if no authentication needed.
922     * @param pass
923     *        login if the connection requires authentication.
924     *        Set it to null if no authentication needed.
925     * @param disableCache
926     *        Disable proxy cache if any.
927     * @param doConnect
928     *        do the connection before return the {@link URLConnection} object
929     * @param displayError
930     *        Display error message in console if something wrong happen.
931     */
932    public static URLConnection openConnection(URL url, String login, String pass, boolean disableCache,
933            boolean doConnect, boolean displayError)
934    {
935        if (url == null)
936        {
937            if (displayError)
938                System.out.println("NetworkUtil.openConnection(...) error: URL is null !");
939
940            return null;
941        }
942
943        URLConnection uc = null;
944
945        try
946        {
947            uc = url.openConnection();
948            boolean redirect;
949
950            do
951            {
952                redirect = false;
953
954                if (disableCache)
955                    disableCache(uc);
956
957                // authentication
958                if (!StringUtil.isEmpty(login) && !StringUtil.isEmpty(pass))
959                    setAuthentication(uc, login, pass);
960
961                if (doConnect)
962                {
963                    // try to connect
964                    if (!connect(uc, displayError))
965                        // error ? --> return null
966                        return null;
967
968                    // we test response code for HTTP connection
969                    if (uc instanceof HttpURLConnection)
970                    {
971                        final int respCode = ((HttpURLConnection) uc).getResponseCode();
972
973                        redirect = (respCode == HttpURLConnection.HTTP_MOVED_PERM)
974                                || (respCode == HttpURLConnection.HTTP_MOVED_TEMP)
975                                || (respCode == HttpURLConnection.HTTP_SEE_OTHER);
976
977                        // redirection ?
978                        if (redirect)
979                        {
980                            // restart connection with new URL
981                            String location = ((HttpURLConnection) uc).getHeaderField("Location");
982                            ((HttpURLConnection) uc).disconnect();
983                            location = URLDecoder.decode(location, "UTF-8");
984                            uc = new URL(location).openConnection();
985                        }
986                    }
987                }
988            }
989            while (redirect);
990
991            return uc;
992        }
993        catch (IOException e)
994        {
995            if (displayError)
996            {
997                // HTTPS not supported while we have a HTTPS connection to icy web site
998                if (!isHTTPSSupported() && (uc != null)
999                        && uc.getURL().toString().toLowerCase().startsWith("https://icy"))
1000                {
1001                    System.err.println("NetworkUtil.openConnection('" + uc.getURL()
1002                            + "') error: HTTPS connection not supported (see detail below).");
1003                    IcyExceptionHandler.showErrorMessage(e, false, false);
1004                }
1005                else
1006                {
1007                    System.out.println("NetworkUtil.openConnection('" + url + "') error :");
1008                    IcyExceptionHandler.showErrorMessage(e, false, false);
1009                }
1010            }
1011
1012            return null;
1013        }
1014    }
1015
1016    /**
1017     * Returns a new {@link URLConnection} from specified URL (null if an error occurred).
1018     * 
1019     * @param url
1020     *        url to connect.
1021     * @param login
1022     *        login if the connection requires authentication.<br>
1023     *        Set it to null if no authentication needed.
1024     * @param pass
1025     *        login if the connection requires authentication.
1026     *        Set it to null if no authentication needed.
1027     * @param disableCache
1028     *        Disable proxy cache if any.
1029     * @param displayError
1030     *        Display error message in console if something wrong happen.
1031     */
1032    public static URLConnection openConnection(URL url, String login, String pass, boolean disableCache,
1033            boolean displayError)
1034    {
1035        return openConnection(url, login, pass, disableCache, false, displayError);
1036    }
1037
1038    /**
1039     * Returns a new {@link URLConnection} from specified URL (null if an error occurred).
1040     * 
1041     * @param url
1042     *        url to connect.
1043     * @param auth
1044     *        Authentication informations.
1045     * @param disableCache
1046     *        Disable proxy cache if any.
1047     * @param displayError
1048     *        Display error message in console if something wrong happen.
1049     */
1050    public static URLConnection openConnection(URL url, AuthenticationInfo auth, boolean disableCache,
1051            boolean displayError)
1052    {
1053        if ((auth != null) && auth.isEnabled())
1054            return openConnection(url, auth.getLogin(), auth.getPassword(), disableCache, displayError);
1055
1056        return openConnection(url, null, null, disableCache, displayError);
1057    }
1058
1059    /**
1060     * Returns a new {@link URLConnection} from specified URL (null if an error occurred).
1061     * 
1062     * @param url
1063     *        url to connect.
1064     * @param disableCache
1065     *        Disable proxy cache if any.
1066     * @param displayError
1067     *        Display error message in console if something wrong happen.
1068     */
1069    public static URLConnection openConnection(URL url, boolean disableCache, boolean displayError)
1070    {
1071        return openConnection(url, null, null, disableCache, displayError);
1072    }
1073
1074    /**
1075     * Returns a new {@link URLConnection} from specified path.<br>
1076     * Returns <code>null</code> if an error occurred.
1077     * 
1078     * @param path
1079     *        path to connect.
1080     * @param disableCache
1081     *        Disable proxy cache if any.
1082     * @param displayError
1083     *        Display error message in console if something wrong happen.
1084     */
1085    public static URLConnection openConnection(String path, boolean disableCache, boolean displayError)
1086    {
1087        return openConnection(URLUtil.getURL(path), disableCache, displayError);
1088    }
1089
1090    /**
1091     * Connect the specified {@link URLConnection}.<br>
1092     * Returns false if the connection failed or if response code is not ok.
1093     * 
1094     * @param uc
1095     *        URLConnection to connect.
1096     * @param displayError
1097     *        Display error message in console if something wrong happen.
1098     */
1099    public static boolean connect(URLConnection uc, boolean displayError)
1100    {
1101        try
1102        {
1103            // final URL prevUrl = uc.getURL();
1104
1105            // connect
1106            uc.connect();
1107
1108            // // we have to test that as sometime url are automatically modified / fixed by host!
1109            // if (!uc.getURL().toString().toLowerCase().equals(prevUrl.toString().toLowerCase()))
1110            // {
1111            // // TODO : do something better
1112            // System.out.println("Host URL change rejected : " + prevUrl + " --> " + uc.getURL());
1113            // return false;
1114            // }
1115
1116            // we test response code for HTTP connection
1117            if (uc instanceof HttpURLConnection)
1118            {
1119                final HttpURLConnection huc = (HttpURLConnection) uc;
1120
1121                // not ok ?
1122                if (huc.getResponseCode() >= 0x400)
1123                {
1124                    if (displayError)
1125                    {
1126                        System.out.println("NetworkUtil.connect('" + huc.getURL() + "' error:");
1127                        System.out.println(huc.getResponseMessage());
1128                    }
1129
1130                    return false;
1131                }
1132            }
1133        }
1134        catch (Exception e)
1135        {
1136            if (displayError)
1137            {
1138                if (uc.getURL().getProtocol().equalsIgnoreCase("file"))
1139                    IcyExceptionHandler.showErrorMessage(e, false, false);
1140                else
1141                {
1142                    if (!hasInternetAccess())
1143                        System.out.println("Can't connect to '" + uc.getURL() + "' (no internet connection).");
1144                    else
1145                    {
1146                        // HTTPS not supported while we have a HTTPS connection to icy web site
1147                        if (!isHTTPSSupported() && uc.getURL().toString().toLowerCase().startsWith("https://icy"))
1148                        {
1149                            System.err.println("NetworkUtil.connect('" + uc.getURL()
1150                                    + "') error: HTTPS connection not supported (see detail below).");
1151                            IcyExceptionHandler.showErrorMessage(e, false, false);
1152                        }
1153                        else
1154                        {
1155                            System.out.println("NetworkUtil.connect('" + uc.getURL() + "' error:");
1156                            IcyExceptionHandler.showErrorMessage(e, false, false);
1157                        }
1158                    }
1159                }
1160            }
1161
1162            return false;
1163        }
1164
1165        return true;
1166    }
1167
1168    /**
1169     * Returns a new {@link InputStream} from specified {@link URLConnection} (null if an error
1170     * occurred).
1171     * 
1172     * @param uc
1173     *        URLConnection object.
1174     * @param displayError
1175     *        Display error message in console if something wrong happen.
1176     */
1177    public static InputStream getInputStream(URLConnection uc, boolean displayError)
1178    {
1179        if (uc == null)
1180        {
1181            if (displayError)
1182            {
1183                System.out.print("NetworkUtil.getInputStream(URLConnection uc) error: URLConnection object is null !");
1184            }
1185
1186            return null;
1187        }
1188
1189        try
1190        {
1191            return uc.getInputStream();
1192        }
1193        catch (IOException e)
1194        {
1195            if (displayError)
1196            {
1197                if (!hasInternetAccess())
1198                    System.out.println("Can't connect to '" + uc.getURL() + "' (no internet connection).");
1199                // HTTPS not supported while we have a HTTPS connection to icy web site
1200                else if (!isHTTPSSupported() && (uc != null)
1201                        && uc.getURL().toString().toLowerCase().startsWith("https://icy"))
1202                {
1203                    System.err.println("NetworkUtil.getInputStream('" + uc.getURL()
1204                            + "') error: HTTPS connection not supported !");
1205                    IcyExceptionHandler.showErrorMessage(e, false, false);
1206                }
1207                else
1208                {
1209                    System.out.println("NetworkUtil.getInputStream('" + uc.getURL() + "') error:");
1210                    IcyExceptionHandler.showErrorMessage(e, false, false);
1211                }
1212            }
1213
1214            return null;
1215        }
1216    }
1217
1218    /**
1219     * Returns a new {@link InputStream} from specified URL (null if an error occurred).
1220     * 
1221     * @param url
1222     *        url we want to connect and retrieve the InputStream.
1223     * @param login
1224     *        login if the connection requires authentication.<br>
1225     *        Set it to null if no authentication needed.
1226     * @param pass
1227     *        login if the connection requires authentication.
1228     *        Set it to null if no authentication needed.
1229     * @param disableCache
1230     *        Disable proxy cache if any.
1231     * @param displayError
1232     *        Display error message in console if something wrong happen.
1233     */
1234    public static InputStream getInputStream(URL url, String login, String pass, boolean disableCache,
1235            boolean displayError)
1236    {
1237        final URLConnection uc = openConnection(url, login, pass, disableCache, true, displayError);
1238
1239        if (uc != null)
1240            return getInputStream(uc, displayError);
1241
1242        return null;
1243    }
1244
1245    /**
1246     * Returns a new {@link InputStream} from specified URL (null if an error occurred).
1247     * 
1248     * @param url
1249     *        url we want to connect and retrieve the InputStream.
1250     * @param auth
1251     *        Authentication informations.
1252     * @param disableCache
1253     *        Disable proxy cache if any.
1254     * @param displayError
1255     *        Display error message in console if something wrong happen.
1256     */
1257    public static InputStream getInputStream(URL url, AuthenticationInfo auth, boolean disableCache,
1258            boolean displayError)
1259    {
1260        if ((auth != null) && (auth.isEnabled()))
1261            return getInputStream(url, auth.getLogin(), auth.getPassword(), disableCache, displayError);
1262
1263        return getInputStream(url, null, null, disableCache, displayError);
1264    }
1265
1266    public static void disableCache(URLConnection uc)
1267    {
1268        uc.setDefaultUseCaches(false);
1269        uc.setUseCaches(false);
1270        uc.setRequestProperty("Cache-Control", "no-cache");
1271        uc.setRequestProperty("Pragma", "no-cache");
1272    }
1273
1274    /**
1275     * Process authentication on specified {@link URLConnection} with specified login and pass.
1276     */
1277    public static void setAuthentication(URLConnection uc, String login, String pass)
1278    {
1279        final String req = login + ":" + pass;
1280        final String encoded;
1281
1282        if (SystemUtil.getJavaVersionAsNumber() >= 8d)
1283            encoded = java.util.Base64.getEncoder().encodeToString(req.getBytes());
1284        else
1285            encoded = new sun.misc.BASE64Encoder().encode(req.getBytes());
1286
1287        uc.setRequestProperty("Authorization", "Basic " + encoded);
1288    }
1289
1290    public static String getContentString(Map<String, String> values)
1291    {
1292        String result = "";
1293
1294        for (Entry<String, String> entry : values.entrySet())
1295        {
1296            try
1297            {
1298                final String key = entry.getKey();
1299
1300                if (!StringUtil.isEmpty(key))
1301                {
1302                    final String value = entry.getValue();
1303
1304                    result += "&" + URLEncoder.encode(key, "UTF-8") + "=";
1305
1306                    if (!StringUtil.isEmpty(value))
1307                        result += URLEncoder.encode(value, "UTF-8");
1308                }
1309            }
1310            catch (UnsupportedEncodingException e)
1311            {
1312                // no encoding
1313                result += "&" + entry.getKey() + "=" + entry.getValue();
1314            }
1315        }
1316
1317        // remove the initial "&" character
1318        return result.substring(1);
1319    }
1320
1321    public static String postData(String target, Map<String, String> values, String login, String pass)
1322            throws IOException
1323    {
1324        return postData(target, getContentString(values), login, pass);
1325    }
1326
1327    public static String postData(String target, String content, String login, String pass) throws IOException
1328    {
1329        String response = "";
1330
1331        final URLConnection uc = openConnection(target, true, true);
1332
1333        if (uc == null)
1334            return null;
1335
1336        // set connection parameters
1337        uc.setDoInput(true);
1338        uc.setDoOutput(true);
1339
1340        // authentication needed ?
1341        if (login != null)
1342            setAuthentication(uc, login, pass);
1343
1344        // make server believe we are form data...
1345        uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
1346
1347        final DataOutputStream out = new DataOutputStream(uc.getOutputStream());
1348        try
1349        {
1350            // write out the bytes of the content string to the stream
1351            out.writeBytes(content);
1352            out.flush();
1353        }
1354        finally
1355        {
1356            out.close();
1357        }
1358
1359        // read response from the input stream.
1360        final InputStream inStream = getInputStream(uc, false);
1361        if (inStream == null)
1362            return null;
1363
1364        final BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
1365
1366        try
1367        {
1368            String temp;
1369            while ((temp = in.readLine()) != null)
1370                response += temp + "\n";
1371        }
1372        finally
1373        {
1374            in.close();
1375        }
1376
1377        return response;
1378    }
1379
1380    public static String postData(String target, Map<String, String> values) throws IOException
1381    {
1382        return postData(target, values, null, null);
1383    }
1384
1385    public static String postData(String target, String content) throws IOException
1386    {
1387        return postData(target, content, null, null);
1388    }
1389
1390    /**
1391     * Send report (asynchronous processing)<br>
1392     * 
1393     * @param values
1394     *        list of <key,value>
1395     */
1396    public static void report(final Map<String, String> values)
1397    {
1398        ThreadUtil.bgRun(new Runnable()
1399        {
1400            @Override
1401            public void run()
1402            {
1403                try
1404                {
1405                    if (postData(REPORT_URL, values) == null)
1406                        System.out.println("Error while reporting data, verifying your internet connection.");
1407                }
1408                catch (IOException e)
1409                {
1410                    System.out.println("Error while reporting data :");
1411                    IcyExceptionHandler.showErrorMessage(e, false, false);
1412                }
1413            }
1414        });
1415    }
1416
1417    /**
1418     * @deprecated Use {@link #getContentString(Map)} instead.
1419     */
1420    @Deprecated
1421    public static String getContentString(HashMap<String, String> values)
1422    {
1423        return getContentString((Map<String, String>) values);
1424    }
1425
1426    /**
1427     * @deprecated Use {@link #postData(String, Map, String, String)} instead.
1428     */
1429    @Deprecated
1430    public static String postData(String target, HashMap<String, String> values, String login, String pass)
1431            throws IOException
1432    {
1433        return postData(target, (Map<String, String>) values, login, pass);
1434    }
1435
1436    /**
1437     * @deprecated Use {@link #postData(String, Map)} instead.
1438     */
1439    @Deprecated
1440    public static String postData(String target, HashMap<String, String> values) throws IOException
1441    {
1442        return postData(target, (Map<String, String>) values);
1443    }
1444
1445    /**
1446     * @deprecated Use {@link #report(Map)} instead.
1447     */
1448    @Deprecated
1449    public static void report(final HashMap<String, String> values)
1450    {
1451        report((Map<String, String>) values);
1452    }
1453
1454    public static void enableSystemProxy()
1455    {
1456        SystemUtil.setProperty("java.net.useSystemProxies", "true");
1457    }
1458
1459    public static void disableSystemProxy()
1460    {
1461        SystemUtil.setProperty("java.net.useSystemProxies", "false");
1462    }
1463
1464    public static void enableProxySetting()
1465    {
1466        SystemUtil.setProperty("proxySet", "true");
1467    }
1468
1469    public static void disableProxySetting()
1470    {
1471        SystemUtil.setProperty("proxySet", "false");
1472    }
1473
1474    public static void enableHTTPProxySetting()
1475    {
1476        SystemUtil.setProperty("http.proxySet", "true");
1477    }
1478
1479    public static void disableHTTPProxySetting()
1480    {
1481        SystemUtil.setProperty("http.proxySet", "false");
1482    }
1483
1484    public static void enableHTTPSProxySetting()
1485    {
1486        SystemUtil.setProperty("https.proxySet", "true");
1487    }
1488
1489    public static void disableHTTPSProxySetting()
1490    {
1491        SystemUtil.setProperty("https.proxySet", "false");
1492    }
1493
1494    public static void enableFTPProxySetting()
1495    {
1496        SystemUtil.setProperty("ftp.proxySet", "true");
1497    }
1498
1499    public static void disableFTPProxySetting()
1500    {
1501        SystemUtil.setProperty("ftp.proxySet", "false");
1502    }
1503
1504    public static void enableSOCKSProxySetting()
1505    {
1506        SystemUtil.setProperty("socksProxySet", "true");
1507    }
1508
1509    public static void disableSOCKSProxySetting()
1510    {
1511        SystemUtil.setProperty("socksProxySet", "false");
1512    }
1513
1514    public static void setProxyHost(String host)
1515    {
1516        SystemUtil.setProperty("proxy.server", host);
1517    }
1518
1519    public static void setProxyPort(int port)
1520    {
1521        SystemUtil.setProperty("proxy.port", Integer.toString(port));
1522    }
1523
1524    public static void setHTTPProxyHost(String host)
1525    {
1526        SystemUtil.setProperty("http.proxyHost", host);
1527    }
1528
1529    public static void setHTTPProxyPort(int port)
1530    {
1531        SystemUtil.setProperty("http.proxyPort", Integer.toString(port));
1532    }
1533
1534    public static void setHTTPProxyUser(String user)
1535    {
1536        SystemUtil.setProperty("http.proxyUser", user);
1537    }
1538
1539    public static void setHTTPProxyPassword(String password)
1540    {
1541        SystemUtil.setProperty("http.proxyPassword", password);
1542    }
1543
1544    public static void setHTTPSProxyHost(String host)
1545    {
1546        SystemUtil.setProperty("https.proxyHost", host);
1547    }
1548
1549    public static void setHTTPSProxyPort(int port)
1550    {
1551        SystemUtil.setProperty("https.proxyPort", Integer.toString(port));
1552    }
1553
1554    public static void setHTTPSProxyUser(String user)
1555    {
1556        SystemUtil.setProperty("https.proxyUser", user);
1557    }
1558
1559    public static void setHTTPSProxyPassword(String password)
1560    {
1561        SystemUtil.setProperty("https.proxyPassword", password);
1562    }
1563
1564    public static void setFTPProxyHost(String host)
1565    {
1566        SystemUtil.setProperty("ftp.proxyHost", host);
1567    }
1568
1569    public static void setFTPProxyPort(int port)
1570    {
1571        SystemUtil.setProperty("ftp.proxyPort", Integer.toString(port));
1572    }
1573
1574    public static void setFTPProxyUser(String user)
1575    {
1576        SystemUtil.setProperty("ftp.proxyUser", user);
1577    }
1578
1579    public static void setFTPProxyPassword(String password)
1580    {
1581        SystemUtil.setProperty("ftp.proxyPassword", password);
1582    }
1583
1584    public static void setSOCKSProxyHost(String host)
1585    {
1586        SystemUtil.setProperty("socksProxyHost", host);
1587    }
1588
1589    public static void setSOCKSProxyPort(int port)
1590    {
1591        SystemUtil.setProperty("socksProxyPort", Integer.toString(port));
1592    }
1593
1594    public static void setSOCKSProxyUser(String user)
1595    {
1596        SystemUtil.setProperty("socksProxyUser", user);
1597    }
1598
1599    public static void setSOCKSProxyPassword(String password)
1600    {
1601        SystemUtil.setProperty("socksProxyPassword", password);
1602    }
1603
1604    public static String getProxyHost()
1605    {
1606        return SystemUtil.getProperty("proxy.server");
1607    }
1608
1609    public static int getProxyPort()
1610    {
1611        try
1612        {
1613            return Integer.parseInt(SystemUtil.getProperty("proxy.port"));
1614        }
1615        catch (NumberFormatException e)
1616        {
1617            return 0;
1618        }
1619    }
1620
1621    public static String getHTTPProxyHost()
1622    {
1623        return SystemUtil.getProperty("http.proxyHost");
1624    }
1625
1626    public static int getHTTPProxyPort()
1627    {
1628        try
1629        {
1630            return Integer.parseInt(SystemUtil.getProperty("http.proxyPort"));
1631        }
1632        catch (NumberFormatException e)
1633        {
1634            return 0;
1635        }
1636    }
1637
1638    public static String getHTTPSProxyHost()
1639    {
1640        return SystemUtil.getProperty("https.proxyHost");
1641    }
1642
1643    public static int getHTTPSProxyPort()
1644    {
1645        try
1646        {
1647            return Integer.parseInt(SystemUtil.getProperty("https.proxyPort"));
1648        }
1649        catch (NumberFormatException e)
1650        {
1651            return 0;
1652        }
1653    }
1654
1655    public static String getFTPProxyHost()
1656    {
1657        return SystemUtil.getProperty("ftp.proxyHost");
1658    }
1659
1660    public static int getFTPProxyPort()
1661    {
1662        try
1663        {
1664            return Integer.parseInt(SystemUtil.getProperty("ftp.proxyPort"));
1665        }
1666        catch (NumberFormatException e)
1667        {
1668            return 0;
1669        }
1670    }
1671
1672    public static String getSOCKSProxyHost()
1673    {
1674        return SystemUtil.getProperty("socksProxyHost");
1675    }
1676
1677    public static int getSOCKSProxyPort()
1678    {
1679        try
1680        {
1681            return Integer.parseInt(SystemUtil.getProperty("socksProxyPort"));
1682        }
1683        catch (NumberFormatException e)
1684        {
1685            return 0;
1686        }
1687    }
1688
1689}