View Javadoc
1   //
2   // $Id: HttpConnect.java 286 2007-06-17 09:03:29 +0000 (dim., 17 juin 2007)
3   // felfert $
4   //
5   // jupload - A file upload applet.
6   //
7   // Copyright 2007 The JUpload Team
8   //
9   // Created: 07.05.2007
10  // Creator: felfert
11  // Last modified: $Date: 2015-01-31 14:39:06 +0100 (sam., 31 janv. 2015) $
12  //
13  // This program is free software; you can redistribute it and/or modify
14  // it under the terms of the GNU General Public License as published by
15  // the Free Software Foundation; either version 2 of the License, or
16  // (at your option) any later version.
17  //
18  // This program is distributed in the hope that it will be useful,
19  // but WITHOUT ANY WARRANTY; without even the implied warranty of
20  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  // GNU General Public License for more details.
22  //
23  // You should have received a copy of the GNU General Public License
24  // along with this program; if not, write to the Free Software
25  // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26  
27  package wjhk.jupload2.upload.helper;
28  
29  import java.io.IOException;
30  import java.net.ConnectException;
31  import java.net.InetSocketAddress;
32  import java.net.Proxy;
33  import java.net.ProxySelector;
34  import java.net.Socket;
35  import java.net.URISyntaxException;
36  import java.net.URL;
37  import java.net.UnknownHostException;
38  import java.security.KeyManagementException;
39  import java.security.KeyStoreException;
40  import java.security.NoSuchAlgorithmException;
41  import java.security.UnrecoverableKeyException;
42  import java.security.cert.CertificateException;
43  import java.util.regex.Matcher;
44  import java.util.regex.Pattern;
45  
46  import javax.net.ssl.SSLContext;
47  
48  import wjhk.jupload2.exception.JUploadException;
49  import wjhk.jupload2.policies.UploadPolicy;
50  import javax.net.ssl.X509TrustManager;
51  
52  import java.security.cert.X509Certificate;
53  
54  import java.io.InputStreamReader;
55  import java.io.OutputStreamWriter;
56  import java.io.BufferedReader;
57  import java.io.BufferedWriter;
58  
59  /**
60   * This class implements the task of connecting to a HTTP(S) url using a proxy.
61   * 
62   * @author felfert
63   */
64  public class HttpConnect {
65  
66      // TrustManager to allow all certificates
67      private final class TM implements X509TrustManager {
68          public void checkClientTrusted(X509Certificate[] arg0, String arg1)
69                  throws CertificateException {
70          }
71  
72          public void checkServerTrusted(X509Certificate[] chain, String authType)
73                  throws CertificateException {
74          }
75  
76          public X509Certificate[] getAcceptedIssuers() {
77              return new X509Certificate[0];
78          }
79      }
80  
81      private final static String HTTPCONNECT_DEFAULT_PROTOCOL = "HTTP/1.1";
82  
83      /**
84       * The current upload policy. Used for logging, and to get the post URL.
85       * Also used to change this URL, when it has moved (301, 302 or 303 return
86       * code)
87       */
88      private UploadPolicy uploadPolicy;
89  
90      /**
91       * Connects to a given URL.
92       * 
93       * @param url The URL to connect to
94       * @param proxy The proxy to be used, may be null if direct connection is
95       *            needed
96       * @return A socket, connected to the specified URL. May be null if an error
97       *         occurs.
98       * @throws NoSuchAlgorithmException
99       * @throws KeyManagementException
100      * @throws IOException
101      * @throws UnknownHostException
102      * @throws ConnectException
103      * @throws CertificateException
104      * @throws KeyStoreException
105      * @throws UnrecoverableKeyException
106      * @throws IllegalArgumentException
107      */
108     public Socket connect(URL url, Proxy proxy)
109             throws NoSuchAlgorithmException, KeyManagementException,
110             ConnectException, UnknownHostException, IOException,
111             KeyStoreException, CertificateException, IllegalArgumentException,
112             UnrecoverableKeyException {
113         // Temporary socket for SOCKS support
114         Socket tsock;
115         Socket ret = null;
116         String host = url.getHost();
117         int port;
118         boolean useProxy = ((proxy != null) && (proxy.type() != Proxy.Type.DIRECT));
119 
120         // Check if SSL connection is needed
121         if (url.getProtocol().equals("https")) {
122             port = (-1 == url.getPort()) ? 443 : url.getPort();
123             SSLContext context = SSLContext.getInstance("SSL");
124 
125             // Still Work in Progress
126             boolean bNoChange = false;
127             if (bNoChange) {
128                 // Allow all certificates
129                 context.init(null, new X509TrustManager[] {
130                     new TM()
131                 }, null);
132             } else {
133                 switch (uploadPolicy.getSslVerifyCert()) {
134                     case InteractiveTrustManager.NONE:
135                         // Allow all certificates
136                         context.init(null, new X509TrustManager[] {
137                             new TM()
138                         }, null);
139                         break;
140                     case InteractiveTrustManager.SERVER:
141                     case InteractiveTrustManager.STRICT:
142                     case InteractiveTrustManager.CLIENT:
143                         // Use the specific TrustManager.
144                         InteractiveTrustManager tm = new InteractiveTrustManager(
145                                 uploadPolicy, host, null);
146                         context.init(tm.getKeyManagers(),
147                                 new X509TrustManager[] {
148                                     tm
149                                 }, null);
150                         break;
151                     default:
152                         throw new IllegalArgumentException(
153                                 "Unknown value for sslVerifyCert: "
154                                         + uploadPolicy.getSslVerifyCert());
155                 }// switch
156             }
157             if (useProxy) {
158                 if (proxy.type() == Proxy.Type.HTTP) {
159                     // First establish a CONNECT, then do a normal SSL
160                     // thru that connection.
161 
162                     // Patch given by cuspy (Patch 3043476 on Sourceforge)
163                     BufferedWriter writer;
164                     BufferedReader reader;
165                     String proxyhost;
166                     int proxyport;
167                     InetSocketAddress sa = (InetSocketAddress) proxy.address();
168                     proxyhost = (sa.isUnresolved()) ? sa.getHostName() : sa
169                             .getAddress().getHostAddress();
170                     proxyport = sa.getPort();
171                     this.uploadPolicy.displayDebug(
172                             "Using SSL socket, via HTTP proxy", 20);
173                     tsock = new Socket(proxyhost, proxyport);
174                     writer = new BufferedWriter(new OutputStreamWriter(
175                             tsock.getOutputStream()));
176                     reader = new BufferedReader(new InputStreamReader(
177                             tsock.getInputStream()));
178                     String hostport = host + ":" + port;
179                     String req = "CONNECT " + hostport + " HTTP/1.0\r\n\r\n";
180                     writer.write(req);
181                     writer.flush();
182                     String res = reader.readLine();
183                     String[] status = res.split(" ", 3);
184                     if (status.length < 2 || !status[1].startsWith("200")) {
185                         this.uploadPolicy.displayDebug("res: " + res, 10);
186                         throw new ConnectException("proxy connection error");
187                     }
188                     ret = context.getSocketFactory().createSocket(tsock,
189                             url.getHost(), port, true);
190                 } else if (proxy.type() == Proxy.Type.SOCKS) {
191                     this.uploadPolicy.displayDebug(
192                             "Using SSL socket, via SOCKS proxy", 20);
193                     tsock = new Socket(proxy);
194                     tsock.connect(new InetSocketAddress(host, port));
195                     ret = context.getSocketFactory().createSocket(tsock, host,
196                             port, true);
197 
198                 } else
199                     throw new ConnectException("Unkown proxy type "
200                             + proxy.type());
201             } else {
202                 // If port not specified then use default https port
203                 // 443.
204                 this.uploadPolicy.displayDebug(
205                         "Using SSL socket, direct connection", 20);
206                 ret = context.getSocketFactory().createSocket(host, port);
207             }
208         } else {
209             // If we are not in SSL, just use the old code.
210             port = (-1 == url.getPort()) ? 80 : url.getPort();
211             if (useProxy) {
212                 if (proxy.type() == Proxy.Type.HTTP) {
213                     InetSocketAddress sa = (InetSocketAddress) proxy.address();
214                     host = (sa.isUnresolved()) ? sa.getHostName() : sa
215                             .getAddress().getHostAddress();
216                     port = sa.getPort();
217                     this.uploadPolicy.displayDebug(
218                             "Using non SSL socket, proxy=" + host + ":" + port,
219                             20);
220                     ret = new Socket(host, port);
221                 } else if (proxy.type() == Proxy.Type.SOCKS) {
222                     this.uploadPolicy.displayDebug(
223                             "Using non SSL socket, via SOCKS proxy", 20);
224                     tsock = new Socket(proxy);
225                     tsock.connect(new InetSocketAddress(host, port));
226                     ret = tsock;
227                 } else
228                     throw new ConnectException("Unkown proxy type "
229                             + proxy.type());
230             } else {
231                 this.uploadPolicy.displayDebug(
232                         "Using non SSL socket, direct connection", 20);
233                 ret = new Socket(host, port);
234             }
235         }
236         return ret;
237     }
238 
239     /**
240      * Connects to a given URL automatically using a proxy.
241      * 
242      * @param url The URL to connect to
243      * @return A socket, connected to the specified URL. May be null if an error
244      *         occurs.
245      * @throws NoSuchAlgorithmException
246      * @throws KeyManagementException
247      * @throws IOException
248      * @throws UnknownHostException
249      * @throws ConnectException
250      * @throws URISyntaxException
251      * @throws UnrecoverableKeyException
252      * @throws CertificateException
253      * @throws KeyStoreException
254      * @throws UnrecoverableKeyException
255      * @throws IllegalArgumentException
256      */
257     public Socket connect(URL url) throws NoSuchAlgorithmException,
258             KeyManagementException, ConnectException, UnknownHostException,
259             IOException, URISyntaxException, KeyStoreException,
260             CertificateException, IllegalArgumentException,
261             UnrecoverableKeyException {
262         Proxy proxy = ProxySelector.getDefault().select(url.toURI()).get(0);
263         return connect(url, proxy);
264     }
265 
266     /**
267      * Retrieve the protocol to be used for the postURL of the current policy.
268      * This method issues a HEAD request to the postURL and then examines the
269      * protocol version returned in the response.
270      * 
271      * @return The string, describing the protocol (e.g. "HTTP/1.1")
272      * @throws URISyntaxException
273      * @throws IOException
274      * @throws UnrecoverableKeyException
275      * @throws IllegalArgumentException
276      * @throws CertificateException
277      * @throws KeyStoreException
278      * @throws UnknownHostException
279      * @throws NoSuchAlgorithmException
280      * @throws KeyManagementException
281      * @throws JUploadException
282      */
283     public String getProtocol() throws URISyntaxException,
284             KeyManagementException, NoSuchAlgorithmException,
285             UnknownHostException, KeyStoreException, CertificateException,
286             IllegalArgumentException, UnrecoverableKeyException, IOException,
287             JUploadException {
288 
289         String protocol = HTTPCONNECT_DEFAULT_PROTOCOL;
290         URL url = new URL(this.uploadPolicy.getPostURL());
291         this.uploadPolicy
292                 .displayDebug("Checking protocol with URL: " + url, 30);
293         HTTPConnectionHelper connectionHelper = new HTTPConnectionHelper(url,
294                 "HEAD", false, true, this.uploadPolicy);
295         connectionHelper.append("\r\n");
296         this.uploadPolicy.displayDebug("Before sendRequest()", 30);
297         connectionHelper.sendRequest();
298         this.uploadPolicy.displayDebug("After sendRequest()", 30);
299         connectionHelper.getOutputStream().flush();
300         if (this.uploadPolicy.getDebugLevel() >= 80) {
301             this.uploadPolicy
302                     .displayDebug(
303                             "-------------------------------------------------------------------------",
304                             80);
305             this.uploadPolicy
306                     .displayDebug(
307                             "-----------------   HEAD message sent (start)  --------------------------",
308                             80);
309             this.uploadPolicy
310                     .displayDebug(
311                             "-------------------------------------------------------------------------",
312                             80);
313             this.uploadPolicy.displayDebug(connectionHelper
314                     .getByteArrayEncoder().getString(), 80);
315             this.uploadPolicy
316                     .displayDebug(
317                             "-------------------------------------------------------------------------",
318                             80);
319             this.uploadPolicy
320                     .displayDebug(
321                             "-----------------   HEAD message sent (end) -----------------------------",
322                             80);
323             this.uploadPolicy
324                     .displayDebug(
325                             "-------------------------------------------------------------------------",
326                             80);
327         }
328 
329         int status = connectionHelper.readHttpResponse();
330         this.uploadPolicy.displayDebug("HEAD status: " + status, 30);
331         String headers = connectionHelper.getResponseHeaders();
332 
333         // Let's look for the protocol
334         Matcher m = Pattern.compile("^(HTTP/\\d\\.\\d)\\s(.*)\\s.*$",
335                 Pattern.MULTILINE).matcher(headers);
336         if (!m.find()) {
337             // Using default value. Already initialized.
338             this.uploadPolicy
339                     .displayErr("Unexpected HEAD response (can't find the protocol): will use the default one.");
340         } else {
341             // We will return the found protocol.
342             protocol = m.group(1);
343             this.uploadPolicy.displayDebug("HEAD protocol: " + protocol, 30);
344         }
345 
346         // Let's check if we're facing an IIS server. The applet is compatible
347         // with IIS, only if allowHttpPersistent is false.
348         Pattern pIIS = Pattern.compile("^Server: .*IIS*$", Pattern.MULTILINE);
349         Matcher mIIS = pIIS.matcher(headers);
350         if (mIIS.find()) {
351             try {
352                 this.uploadPolicy.setProperty(
353                         UploadPolicy.PROP_ALLOW_HTTP_PERSISTENT, "false");
354                 this.uploadPolicy
355                         .displayWarn(UploadPolicy.PROP_ALLOW_HTTP_PERSISTENT
356                                 + "' forced to false, for IIS compatibility (in HttpConnect.getProtocol())");
357             } catch (JUploadException e) {
358                 this.uploadPolicy.displayWarn("Can't set property '"
359                         + UploadPolicy.PROP_ALLOW_HTTP_PERSISTENT
360                         + "' to false, in HttpConnect.getProtocol()");
361             }
362         }
363 
364         // if we got a redirection code, we must find the new Location.
365         if (status == 301 || status == 302 || status == 303) {
366             Pattern pLocation = Pattern.compile("^Location: (.*)$",
367                     Pattern.MULTILINE);
368             Matcher mLocation = pLocation.matcher(headers);
369             if (mLocation.find()) {
370                 // We found the location where we should go instead of the
371                 // original postURL
372                 this.uploadPolicy.displayDebug(
373                         "Location read: " + mLocation.group(1), 50);
374                 changePostURL(mLocation.group(1));
375             }
376         }
377 
378         // Let's free any used resource.
379         connectionHelper.dispose();
380         connectionHelper.close();
381 
382         return protocol;
383     } // getProtocol()
384 
385     /**
386      * Reaction of the applet when a 301, 302 et 303 return code is returned.
387      * The postURL is changed according to the Location header returned.
388      * 
389      * @param newLocation This new location may contain the
390      *            http://host.name.domain part of the URL ... or not
391      */
392     private void changePostURL(String newLocation) throws JUploadException {
393         String currentPostURL = this.uploadPolicy.getPostURL();
394         String newPostURL;
395         Pattern pHostName = Pattern.compile("http://([^/]*)/.*");
396         Matcher mOldPostURL = Pattern.compile("(.*)\\?(.*)").matcher(
397                 currentPostURL);
398 
399         // If there is an interrogation point in the original postURL, we'll
400         // keep the parameters, and just changed the URI part.
401         if (mOldPostURL.matches()) {
402             newPostURL = newLocation + '?' + mOldPostURL.group(2);
403             // Otherwise, we change the whole URL.
404         } else {
405             newPostURL = newLocation;
406         }
407 
408         // There are three main cases or newLocation:
409         // 1- It's a full URL, with host name...
410         // 2- It's a local full path on the same server (begins with /)
411         // 3- It's a relative path (for instance, add of a prefix in the
412         // filename) (doesn't begin with /)
413         Matcher mHostOldPostURL = pHostName.matcher(currentPostURL);
414         if (!mHostOldPostURL.matches()) {
415             // Oups ! There is a little trouble here !
416             throw new JUploadException(
417                     "[HttpConnect.changePostURL()] No host found in the old postURL !");
418         }
419 
420         // Let's analyze the given newLocation for these three cases.
421         Matcher mHostNewLocation = pHostName.matcher(newLocation);
422         if (mHostNewLocation.matches()) {
423             // 1- It's a full URL, with host name. We already got this URL, in
424             // the newPostURL initialization.
425         } else if (newLocation.startsWith("/")) {
426             // 2- It's a local full path on the same server (begins with /)
427             newPostURL = "http://" + mHostOldPostURL.group(1) + newPostURL;
428         } else {
429             // 3- It's a relative path (for instance, add of a prefix in the
430             // filename) (doesn't begin with /)
431             Matcher mOldPostURLAllButFilename = Pattern
432                     .compile("(.*)/([^/]*)$").matcher(currentPostURL);
433             if (!mOldPostURLAllButFilename.matches()) {
434                 // Hum, that won't be easy.
435                 throw new JUploadException(
436                         "[HttpConnect.changePostURL()] Can't find the filename in the URL !");
437             }
438             newPostURL = mOldPostURLAllButFilename.group(1) + "/" + newPostURL;
439         }
440 
441         // Let's store this new postURL, and display some info about the change
442         this.uploadPolicy.setPostURL(newPostURL);
443         this.uploadPolicy.displayInfo("postURL switched from " + currentPostURL
444                 + " to " + newPostURL);
445     }
446 
447     /**
448      * Creates a new instance.
449      * 
450      * @param policy The UploadPolicy to be used for logging.
451      */
452     public HttpConnect(UploadPolicy policy) {
453         this.uploadPolicy = policy;
454     }
455 }