• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 android.webkit;
18 
19 import android.net.http.EventHandler;
20 import android.net.http.RequestHandle;
21 import android.util.Log;
22 import android.webkit.CacheManager.CacheResult;
23 
24 import java.util.HashMap;
25 import java.util.Map;
26 
27 class FrameLoader {
28 
29     private final LoadListener mListener;
30     private final String mMethod;
31     private final WebSettings mSettings;
32     private Map<String, String> mHeaders;
33     private byte[] mPostData;
34     private Network mNetwork;
35     private int mCacheMode;
36     private String mReferrer;
37     private String mContentType;
38 
39     private static final int URI_PROTOCOL = 0x100;
40 
41     private static final String CONTENT_TYPE = "content-type";
42 
43     // Contents of an about:blank page
44     private static final String mAboutBlank =
45             "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" +
46             "<html><head><title>about:blank</title></head><body></body></html>";
47 
48     static final String HEADER_STR = "text/xml, text/html, " +
49             "application/xhtml+xml, image/png, text/plain, */*;q=0.8";
50 
51     private static final String LOGTAG = "webkit";
52 
FrameLoader(LoadListener listener, WebSettings settings, String method)53     FrameLoader(LoadListener listener, WebSettings settings,
54             String method) {
55         mListener = listener;
56         mHeaders = null;
57         mMethod = method;
58         mCacheMode = WebSettings.LOAD_NORMAL;
59         mSettings = settings;
60     }
61 
setReferrer(String ref)62     public void setReferrer(String ref) {
63         // only set referrer for http or https
64         if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
65     }
66 
setPostData(byte[] postData)67     public void setPostData(byte[] postData) {
68         mPostData = postData;
69     }
70 
setContentTypeForPost(String postContentType)71     public void setContentTypeForPost(String postContentType) {
72         mContentType = postContentType;
73     }
74 
setCacheMode(int cacheMode)75     public void setCacheMode(int cacheMode) {
76         mCacheMode = cacheMode;
77     }
78 
setHeaders(HashMap headers)79     public void setHeaders(HashMap headers) {
80         mHeaders = headers;
81     }
82 
getLoadListener()83     public LoadListener getLoadListener() {
84         return mListener;
85     }
86 
87     /**
88      * Issues the load request.
89      *
90      * Return value does not indicate if the load was successful or not. It
91      * simply indicates that the load request is reasonable.
92      *
93      * @return true if the load is reasonable.
94      */
executeLoad()95     public boolean executeLoad() {
96         String url = mListener.url();
97 
98         if (URLUtil.isNetworkUrl(url)){
99             if (mSettings.getBlockNetworkLoads()) {
100                 mListener.error(EventHandler.ERROR_BAD_URL,
101                         mListener.getContext().getString(
102                                 com.android.internal.R.string.httpErrorBadUrl));
103                 return false;
104             }
105             // Make sure it is correctly URL encoded before sending the request
106             if (!URLUtil.verifyURLEncoding(url)) {
107                 mListener.error(EventHandler.ERROR_BAD_URL,
108                         mListener.getContext().getString(
109                         com.android.internal.R.string.httpErrorBadUrl));
110                 return false;
111             }
112             mNetwork = Network.getInstance(mListener.getContext());
113             return handleHTTPLoad();
114         } else if (handleLocalFile(url, mListener, mSettings)) {
115             return true;
116         }
117         if (DebugFlags.FRAME_LOADER) {
118             Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
119                     + mListener.url());
120         }
121         mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
122                 mListener.getContext().getText(
123                         com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
124         return false;
125 
126     }
127 
128     /* package */
handleLocalFile(String url, LoadListener loadListener, WebSettings settings)129     static boolean handleLocalFile(String url, LoadListener loadListener,
130             WebSettings settings) {
131         // Attempt to decode the percent-encoded url before passing to the
132         // local loaders.
133         try {
134             url = new String(URLUtil.decode(url.getBytes()));
135         } catch (IllegalArgumentException e) {
136             loadListener.error(EventHandler.ERROR_BAD_URL,
137                     loadListener.getContext().getString(
138                             com.android.internal.R.string.httpErrorBadUrl));
139             // Return true here so we do not trigger an unsupported scheme
140             // error.
141             return true;
142         }
143         if (URLUtil.isAssetUrl(url)) {
144             FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
145                     true, settings.getAllowFileAccess());
146             return true;
147         } else if (URLUtil.isFileUrl(url)) {
148             FileLoader.requestUrl(url, loadListener, loadListener.getContext(),
149                     false, settings.getAllowFileAccess());
150             return true;
151         } else if (URLUtil.isContentUrl(url)) {
152             // Send the raw url to the ContentLoader because it will do a
153             // permission check and the url has to match..
154             ContentLoader.requestUrl(loadListener.url(), loadListener,
155                                      loadListener.getContext());
156             return true;
157         } else if (URLUtil.isDataUrl(url)) {
158             DataLoader.requestUrl(url, loadListener);
159             return true;
160         } else if (URLUtil.isAboutUrl(url)) {
161             loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
162             loadListener.endData();
163             return true;
164         }
165         return false;
166     }
167 
handleHTTPLoad()168     private boolean handleHTTPLoad() {
169         if (mHeaders == null) {
170             mHeaders = new HashMap<String, String>();
171         }
172         populateStaticHeaders();
173         populateHeaders();
174 
175         // response was handled by Cache, don't issue HTTP request
176         if (handleCache()) {
177             // push the request data down to the LoadListener
178             // as response from the cache could be a redirect
179             // and we may need to initiate a network request if the cache
180             // can't satisfy redirect URL
181             mListener.setRequestData(mMethod, mHeaders, mPostData);
182             return true;
183         }
184 
185         if (DebugFlags.FRAME_LOADER) {
186             Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
187                     + mListener.url());
188         }
189 
190         boolean ret = false;
191         int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
192 
193         try {
194             ret = mNetwork.requestURL(mMethod, mHeaders,
195                     mPostData, mListener);
196         } catch (android.net.ParseException ex) {
197             error = EventHandler.ERROR_BAD_URL;
198         } catch (java.lang.RuntimeException ex) {
199             /* probably an empty header set by javascript.  We want
200                the same result as bad URL  */
201             error = EventHandler.ERROR_BAD_URL;
202         }
203         if (!ret) {
204             mListener.error(error, mListener.getContext().getText(
205                     EventHandler.errorStringResources[Math.abs(error)]).toString());
206             return false;
207         }
208         return true;
209     }
210 
211     /*
212      * This function is used by handleCache to
213      * setup a load from the byte stream in a CacheResult.
214      */
startCacheLoad(CacheResult result)215     private void startCacheLoad(CacheResult result) {
216         if (DebugFlags.FRAME_LOADER) {
217             Log.v(LOGTAG, "FrameLoader: loading from cache: "
218                   + mListener.url());
219         }
220         // Tell the Listener respond with the cache file
221         CacheLoader cacheLoader =
222                 new CacheLoader(mListener, result);
223         mListener.setCacheLoader(cacheLoader);
224         cacheLoader.load();
225     }
226 
227     /*
228      * This function is used by the handleHTTPLoad to setup the cache headers
229      * correctly.
230      * Returns true if the response was handled from the cache
231      */
handleCache()232     private boolean handleCache() {
233         switch (mCacheMode) {
234             // This mode is normally used for a reload, it instructs the http
235             // loader to not use the cached content.
236             case WebSettings.LOAD_NO_CACHE:
237                 break;
238 
239 
240             // This mode is used when the content should only be loaded from
241             // the cache. If it is not there, then fail the load. This is used
242             // to load POST content in a history navigation.
243             case WebSettings.LOAD_CACHE_ONLY: {
244                 CacheResult result = CacheManager.getCacheFile(mListener.url(),
245                         null);
246                 if (result != null) {
247                     startCacheLoad(result);
248                 } else {
249                     // This happens if WebCore was first told that the POST
250                     // response was in the cache, then when we try to use it
251                     // it has gone.
252                     // Generate a file not found error
253                     int err = EventHandler.FILE_NOT_FOUND_ERROR;
254                     mListener.error(err, mListener.getContext().getText(
255                             EventHandler.errorStringResources[Math.abs(err)])
256                             .toString());
257                 }
258                 return true;
259             }
260 
261             // This mode is for when the user is doing a history navigation
262             // in the browser and should returned cached content regardless
263             // of it's state. If it is not in the cache, then go to the
264             // network.
265             case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
266                 if (DebugFlags.FRAME_LOADER) {
267                     Log.v(LOGTAG, "FrameLoader: checking cache: "
268                             + mListener.url());
269                 }
270                 // Get the cache file name for the current URL, passing null for
271                 // the validation headers causes no validation to occur
272                 CacheResult result = CacheManager.getCacheFile(mListener.url(),
273                         null);
274                 if (result != null) {
275                     startCacheLoad(result);
276                     return true;
277                 }
278                 break;
279             }
280 
281             // This is the default case, which is to check to see if the
282             // content in the cache can be used. If it can be used, then
283             // use it. If it needs revalidation then the relevant headers
284             // are added to the request.
285             default:
286             case WebSettings.LOAD_NORMAL:
287                 return mListener.checkCache(mHeaders);
288         }// end of switch
289 
290         return false;
291     }
292 
293     /**
294      * Add the static headers that don't change with each request.
295      */
populateStaticHeaders()296     private void populateStaticHeaders() {
297         // Accept header should already be there as they are built by WebCore,
298         // but in the case they are missing, add some.
299         String accept = mHeaders.get("Accept");
300         if (accept == null || accept.length() == 0) {
301             mHeaders.put("Accept", HEADER_STR);
302         }
303         mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
304 
305         String acceptLanguage = mSettings.getAcceptLanguage();
306         if (acceptLanguage.length() > 0) {
307             mHeaders.put("Accept-Language", acceptLanguage);
308         }
309 
310         mHeaders.put("User-Agent", mSettings.getUserAgentString());
311     }
312 
313     /**
314      * Add the content related headers. These headers contain user private data
315      * and is not used when we are proxying an untrusted request.
316      */
populateHeaders()317     private void populateHeaders() {
318 
319         if (mReferrer != null) mHeaders.put("Referer", mReferrer);
320         if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
321 
322         // if we have an active proxy and have proxy credentials, do pre-emptive
323         // authentication to avoid an extra round-trip:
324         if (mNetwork.isValidProxySet()) {
325             String username;
326             String password;
327             /* The proxy credentials can be set in the Network thread */
328             synchronized (mNetwork) {
329                 username = mNetwork.getProxyUsername();
330                 password = mNetwork.getProxyPassword();
331             }
332             if (username != null && password != null) {
333                 // we collect credentials ONLY if the proxy scheme is BASIC!!!
334                 String proxyHeader = RequestHandle.authorizationHeader(true);
335                 mHeaders.put(proxyHeader,
336                         "Basic " + RequestHandle.computeBasicAuthResponse(
337                                 username, password));
338             }
339         }
340 
341         // Set cookie header
342         String cookie = CookieManager.getInstance().getCookie(
343                 mListener.getWebAddress());
344         if (cookie != null && cookie.length() > 0) {
345             mHeaders.put("Cookie", cookie);
346         }
347     }
348 }
349