• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.sdklib.internal.repository;
18 
19 import com.android.util.Pair;
20 
21 import org.apache.http.HttpEntity;
22 import org.apache.http.HttpResponse;
23 import org.apache.http.HttpStatus;
24 import org.apache.http.auth.AuthScope;
25 import org.apache.http.auth.AuthState;
26 import org.apache.http.auth.Credentials;
27 import org.apache.http.auth.UsernamePasswordCredentials;
28 import org.apache.http.client.ClientProtocolException;
29 import org.apache.http.client.methods.HttpGet;
30 import org.apache.http.client.protocol.ClientContext;
31 import org.apache.http.impl.client.DefaultHttpClient;
32 import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
33 import org.apache.http.protocol.BasicHttpContext;
34 import org.apache.http.protocol.HttpContext;
35 
36 import java.io.FileNotFoundException;
37 import java.io.FilterInputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.net.ProxySelector;
41 import java.net.URL;
42 import java.util.HashMap;
43 import java.util.Map;
44 
45 /**
46  * This class holds methods for adding URLs management.
47  * @see #openUrl(String, ITaskMonitor)
48  */
49 public class UrlOpener {
50 
51     public static class CanceledByUserException extends Exception {
52         private static final long serialVersionUID = -7669346110926032403L;
53 
CanceledByUserException(String message)54         public CanceledByUserException(String message) {
55             super(message);
56         }
57     }
58 
59     private static Map<String, Pair<String, String>> sRealmCache =
60             new HashMap<String, Pair<String, String>>();
61 
62     /**
63      * Opens a URL. It can be a simple URL or one which requires basic
64      * authentication.
65      * <p/>
66      * Tries to access the given URL. If http response is either
67      * {@code HttpStatus.SC_UNAUTHORIZED} or
68      * {@code HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED}, asks for
69      * login/password and tries to authenticate into proxy server and/or URL.
70      * <p/>
71      * This implementation relies on the Apache Http Client due to its
72      * capabilities of proxy/http authentication. <br/>
73      * Proxy configuration is determined by {@link ProxySelectorRoutePlanner} using the JVM proxy
74      * settings by default.
75      * <p/>
76      * For more information see: <br/>
77      * - {@code http://hc.apache.org/httpcomponents-client-ga/} <br/>
78      * - {@code http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/conn/ProxySelectorRoutePlanner.html}
79      * <p/>
80      * There's a very simple <b>cache</b> implementation.
81      * Login/Password for each realm are stored in a static {@link Map}.
82      * Before asking the user the method verifies if the information is already available in cache.
83      *
84      * @param url the URL string to be opened.
85      * @param monitor {@link ITaskMonitor} which is related to this URL
86      *            fetching.
87      * @return Returns an {@link InputStream} holding the URL content.
88      * @throws IOException Exception thrown when there are problems retrieving
89      *             the URL or its content.
90      * @throws CanceledByUserException Exception thrown if the user cancels the
91      *              authentication dialog.
92      */
openUrl(String url, ITaskMonitor monitor)93     static InputStream openUrl(String url, ITaskMonitor monitor)
94         throws IOException, CanceledByUserException {
95 
96         try {
97             return openWithHttpClient(url, monitor);
98 
99         } catch (ClientProtocolException e) {
100             // If the protocol is not supported by HttpClient (e.g. file:///),
101             // revert to the standard java.net.Url.open
102 
103             URL u = new URL(url);
104             return u.openStream();
105         }
106     }
107 
openWithHttpClient(String url, ITaskMonitor monitor)108     private static InputStream openWithHttpClient(String url, ITaskMonitor monitor)
109             throws IOException, ClientProtocolException, CanceledByUserException {
110         Pair<String, String> result = null;
111         String realm = null;
112 
113         // use the simple one
114         final DefaultHttpClient httpClient = new DefaultHttpClient();
115 
116         // create local execution context
117         HttpContext localContext = new BasicHttpContext();
118         HttpGet httpget = new HttpGet(url);
119 
120         // retrieve local java configured network in case there is the need to
121         // authenticate a proxy
122         ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
123                     httpClient.getConnectionManager().getSchemeRegistry(),
124                     ProxySelector.getDefault());
125         httpClient.setRoutePlanner(routePlanner);
126 
127         boolean trying = true;
128         // loop while the response is being fetched
129         while (trying) {
130             // connect and get status code
131             HttpResponse response = httpClient.execute(httpget, localContext);
132             int statusCode = response.getStatusLine().getStatusCode();
133 
134             // check whether any authentication is required
135             AuthState authenticationState = null;
136             if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
137                 // Target host authentication required
138                 authenticationState = (AuthState) localContext
139                         .getAttribute(ClientContext.TARGET_AUTH_STATE);
140             }
141             if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
142                 // Proxy authentication required
143                 authenticationState = (AuthState) localContext
144                         .getAttribute(ClientContext.PROXY_AUTH_STATE);
145             }
146             if (statusCode == HttpStatus.SC_OK) {
147                 // in case the status is OK and there is a realm and result,
148                 // cache it
149                 if (realm != null && result != null) {
150                     sRealmCache.put(realm, result);
151                 }
152             }
153 
154             // there is the need for authentication
155             if (authenticationState != null) {
156 
157                 // get scope and realm
158                 AuthScope authScope = authenticationState.getAuthScope();
159 
160                 // If the current realm is different from the last one it means
161                 // a pass was performed successfully to the last URL, therefore
162                 // cache the last realm
163                 if (realm != null && !realm.equals(authScope.getRealm())) {
164                     sRealmCache.put(realm, result);
165                 }
166 
167                 realm = authScope.getRealm();
168 
169                 // in case there is cache for this Realm, use it to authenticate
170                 if (sRealmCache.containsKey(realm)) {
171                     result = sRealmCache.get(realm);
172                 } else {
173                     // since there is no cache, request for login and password
174                     result = monitor.displayLoginPasswordPrompt("Site Authentication",
175                             "Please login to the following domain: " + realm +
176                             "\n\nServer requiring authentication:\n" + authScope.getHost());
177                     if (result == null) {
178                         throw new CanceledByUserException("User canceled login dialog.");
179                     }
180                 }
181 
182                 // retrieve authentication data
183                 String user = result.getFirst();
184                 String password = result.getSecond();
185 
186                 // proceed in case there is indeed a user
187                 if (user != null && user.length() > 0) {
188                     Credentials credentials = new UsernamePasswordCredentials(user, password);
189                     httpClient.getCredentialsProvider().setCredentials(authScope, credentials);
190                     trying = true;
191                 } else {
192                     trying = false;
193                 }
194             } else {
195                 trying = false;
196             }
197 
198             HttpEntity entity = response.getEntity();
199 
200             if (entity != null) {
201                 if (trying) {
202                     // in case another pass to the Http Client will be performed, close the entity.
203                     entity.getContent().close();
204                 } else {
205                     // since no pass to the Http Client is needed, retrieve the
206                     // entity's content.
207 
208                     // Note: don't use something like a BufferedHttpEntity since it would consume
209                     // all content and store it in memory, resulting in an OutOfMemory exception
210                     // on a large download.
211 
212                     return new FilterInputStream(entity.getContent()) {
213                         @Override
214                         public void close() throws IOException {
215                             super.close();
216 
217                             // since Http Client is no longer needed, close it
218                             httpClient.getConnectionManager().shutdown();
219                         }
220                     };
221                 }
222             }
223         }
224 
225         // We get here if we did not succeed. Callers do not expect a null result.
226         httpClient.getConnectionManager().shutdown();
227         throw new FileNotFoundException(url);
228     }
229 }
230