blob: 303e4176281d546c3a383f90e7f40765cf81e0c4 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.webkit;
18
19import android.net.http.EventHandler;
20import android.net.http.RequestHandle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.util.Log;
22import android.webkit.CacheManager.CacheResult;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
24import java.util.HashMap;
25import java.util.Map;
26
27class FrameLoader {
28
29 private final LoadListener mListener;
30 private final String mMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031 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
53 FrameLoader(LoadListener listener, WebSettings settings,
Patrick Scottfe4fec72009-07-14 15:54:30 -040054 String method) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055 mListener = listener;
56 mHeaders = null;
57 mMethod = method;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 mCacheMode = WebSettings.LOAD_NORMAL;
59 mSettings = settings;
60 }
61
62 public void setReferrer(String ref) {
63 // only set referrer for http or https
64 if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
65 }
66
67 public void setPostData(byte[] postData) {
68 mPostData = postData;
69 }
70
71 public void setContentTypeForPost(String postContentType) {
72 mContentType = postContentType;
73 }
74
75 public void setCacheMode(int cacheMode) {
76 mCacheMode = cacheMode;
77 }
78
79 public void setHeaders(HashMap headers) {
80 mHeaders = headers;
81 }
82
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 */
95 public boolean executeLoad() {
96 String url = mListener.url();
97
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098 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 }
Grace Kloba758bf412009-08-11 11:47:24 -0700105 // 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112 mNetwork = Network.getInstance(mListener.getContext());
Grace Kloba5f38e1a2010-03-01 23:10:10 -0800113 if (mListener.isSynchronous()) {
Steve Block76619af2010-03-05 18:32:52 +0000114 return handleHTTPLoad();
Grace Kloba5f38e1a2010-03-01 23:10:10 -0800115 }
Steve Block76619af2010-03-05 18:32:52 +0000116 WebViewWorker.getHandler().obtainMessage(
117 WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
Grace Kloba2036dba2010-02-15 02:15:37 -0800118 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 } else if (handleLocalFile(url, mListener, mSettings)) {
120 return true;
121 }
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400122 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123 Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
124 + mListener.url());
125 }
126 mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
127 mListener.getContext().getText(
128 com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
129 return false;
130
131 }
132
133 /* package */
134 static boolean handleLocalFile(String url, LoadListener loadListener,
135 WebSettings settings) {
Patrick Scott68e53002009-08-13 15:39:20 -0400136 // Attempt to decode the percent-encoded url before passing to the
137 // local loaders.
138 try {
139 url = new String(URLUtil.decode(url.getBytes()));
140 } catch (IllegalArgumentException e) {
141 loadListener.error(EventHandler.ERROR_BAD_URL,
142 loadListener.getContext().getString(
143 com.android.internal.R.string.httpErrorBadUrl));
144 // Return true here so we do not trigger an unsupported scheme
145 // error.
146 return true;
147 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 if (URLUtil.isAssetUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000149 if (loadListener.isSynchronous()) {
150 new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
151 true).load();
152 } else {
153 // load asset in a separate thread as it involves IO
154 WebViewWorker.getHandler().obtainMessage(
155 WebViewWorker.MSG_ADD_STREAMLOADER,
156 new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
157 true)).sendToTarget();
158 }
Grace Klobabd5c8232009-12-07 10:11:28 -0800159 return true;
160 } else if (URLUtil.isResourceUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000161 if (loadListener.isSynchronous()) {
162 new FileLoader(url, loadListener, FileLoader.TYPE_RES,
163 true).load();
164 } else {
165 // load resource in a separate thread as it involves IO
166 WebViewWorker.getHandler().obtainMessage(
167 WebViewWorker.MSG_ADD_STREAMLOADER,
168 new FileLoader(url, loadListener, FileLoader.TYPE_RES,
169 true)).sendToTarget();
170 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 return true;
172 } else if (URLUtil.isFileUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000173 if (loadListener.isSynchronous()) {
174 new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
175 settings.getAllowFileAccess()).load();
176 } else {
177 // load file in a separate thread as it involves IO
178 WebViewWorker.getHandler().obtainMessage(
179 WebViewWorker.MSG_ADD_STREAMLOADER,
180 new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
181 settings.getAllowFileAccess())).sendToTarget();
182 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 return true;
184 } else if (URLUtil.isContentUrl(url)) {
185 // Send the raw url to the ContentLoader because it will do a
Grace Klobaac75f562010-02-03 10:24:06 -0800186 // permission check and the url has to match.
Steve Block76619af2010-03-05 18:32:52 +0000187 if (loadListener.isSynchronous()) {
188 new ContentLoader(loadListener.url(), loadListener).load();
189 } else {
190 // load content in a separate thread as it involves IO
191 WebViewWorker.getHandler().obtainMessage(
192 WebViewWorker.MSG_ADD_STREAMLOADER,
193 new ContentLoader(loadListener.url(), loadListener))
194 .sendToTarget();
195 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800196 return true;
197 } else if (URLUtil.isDataUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800198 // load data in the current thread to reduce the latency
199 new DataLoader(url, loadListener).load();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 return true;
201 } else if (URLUtil.isAboutUrl(url)) {
202 loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
203 loadListener.endData();
204 return true;
205 }
206 return false;
207 }
Grace Kloba2036dba2010-02-15 02:15:37 -0800208
209 boolean handleHTTPLoad() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800210 if (mHeaders == null) {
211 mHeaders = new HashMap<String, String>();
212 }
213 populateStaticHeaders();
214 populateHeaders();
215
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 // response was handled by Cache, don't issue HTTP request
217 if (handleCache()) {
218 // push the request data down to the LoadListener
219 // as response from the cache could be a redirect
220 // and we may need to initiate a network request if the cache
221 // can't satisfy redirect URL
Patrick Scottfe4fec72009-07-14 15:54:30 -0400222 mListener.setRequestData(mMethod, mHeaders, mPostData);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 return true;
224 }
225
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400226 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800227 Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
228 + mListener.url());
229 }
230
231 boolean ret = false;
232 int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
233
234 try {
235 ret = mNetwork.requestURL(mMethod, mHeaders,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400236 mPostData, mListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800237 } catch (android.net.ParseException ex) {
238 error = EventHandler.ERROR_BAD_URL;
239 } catch (java.lang.RuntimeException ex) {
240 /* probably an empty header set by javascript. We want
241 the same result as bad URL */
242 error = EventHandler.ERROR_BAD_URL;
243 }
244 if (!ret) {
245 mListener.error(error, mListener.getContext().getText(
246 EventHandler.errorStringResources[Math.abs(error)]).toString());
247 return false;
248 }
249 return true;
250 }
251
252 /*
Andrei Popescu385df692009-08-13 11:59:57 +0100253 * This function is used by handleCache to
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 * setup a load from the byte stream in a CacheResult.
255 */
256 private void startCacheLoad(CacheResult result) {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400257 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 Log.v(LOGTAG, "FrameLoader: loading from cache: "
259 + mListener.url());
260 }
261 // Tell the Listener respond with the cache file
262 CacheLoader cacheLoader =
263 new CacheLoader(mListener, result);
264 mListener.setCacheLoader(cacheLoader);
Grace Kloba2036dba2010-02-15 02:15:37 -0800265 // Load the cached file in a separate thread
266 WebViewWorker.getHandler().obtainMessage(
267 WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 }
269
270 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 * This function is used by the handleHTTPLoad to setup the cache headers
272 * correctly.
273 * Returns true if the response was handled from the cache
274 */
275 private boolean handleCache() {
276 switch (mCacheMode) {
277 // This mode is normally used for a reload, it instructs the http
278 // loader to not use the cached content.
279 case WebSettings.LOAD_NO_CACHE:
280 break;
281
282
283 // This mode is used when the content should only be loaded from
284 // the cache. If it is not there, then fail the load. This is used
285 // to load POST content in a history navigation.
286 case WebSettings.LOAD_CACHE_ONLY: {
287 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800288 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 if (result != null) {
290 startCacheLoad(result);
291 } else {
292 // This happens if WebCore was first told that the POST
293 // response was in the cache, then when we try to use it
294 // it has gone.
295 // Generate a file not found error
296 int err = EventHandler.FILE_NOT_FOUND_ERROR;
297 mListener.error(err, mListener.getContext().getText(
298 EventHandler.errorStringResources[Math.abs(err)])
299 .toString());
300 }
301 return true;
302 }
303
304 // This mode is for when the user is doing a history navigation
305 // in the browser and should returned cached content regardless
306 // of it's state. If it is not in the cache, then go to the
307 // network.
308 case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400309 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800310 Log.v(LOGTAG, "FrameLoader: checking cache: "
311 + mListener.url());
312 }
313 // Get the cache file name for the current URL, passing null for
314 // the validation headers causes no validation to occur
315 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800316 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317 if (result != null) {
318 startCacheLoad(result);
319 return true;
320 }
321 break;
322 }
323
324 // This is the default case, which is to check to see if the
325 // content in the cache can be used. If it can be used, then
326 // use it. If it needs revalidation then the relevant headers
327 // are added to the request.
328 default:
329 case WebSettings.LOAD_NORMAL:
330 return mListener.checkCache(mHeaders);
331 }// end of switch
332
333 return false;
334 }
335
336 /**
337 * Add the static headers that don't change with each request.
338 */
339 private void populateStaticHeaders() {
340 // Accept header should already be there as they are built by WebCore,
341 // but in the case they are missing, add some.
342 String accept = mHeaders.get("Accept");
343 if (accept == null || accept.length() == 0) {
344 mHeaders.put("Accept", HEADER_STR);
345 }
346 mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
347
348 String acceptLanguage = mSettings.getAcceptLanguage();
349 if (acceptLanguage.length() > 0) {
350 mHeaders.put("Accept-Language", acceptLanguage);
351 }
352
353 mHeaders.put("User-Agent", mSettings.getUserAgentString());
354 }
355
356 /**
357 * Add the content related headers. These headers contain user private data
358 * and is not used when we are proxying an untrusted request.
359 */
360 private void populateHeaders() {
361
362 if (mReferrer != null) mHeaders.put("Referer", mReferrer);
363 if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
364
365 // if we have an active proxy and have proxy credentials, do pre-emptive
366 // authentication to avoid an extra round-trip:
367 if (mNetwork.isValidProxySet()) {
368 String username;
369 String password;
370 /* The proxy credentials can be set in the Network thread */
371 synchronized (mNetwork) {
372 username = mNetwork.getProxyUsername();
373 password = mNetwork.getProxyPassword();
374 }
375 if (username != null && password != null) {
376 // we collect credentials ONLY if the proxy scheme is BASIC!!!
377 String proxyHeader = RequestHandle.authorizationHeader(true);
378 mHeaders.put(proxyHeader,
379 "Basic " + RequestHandle.computeBasicAuthResponse(
380 username, password));
381 }
382 }
383
384 // Set cookie header
385 String cookie = CookieManager.getInstance().getCookie(
386 mListener.getWebAddress());
387 if (cookie != null && cookie.length() > 0) {
Grace Kloba65c0fc42009-06-29 17:03:42 -0700388 mHeaders.put("Cookie", cookie);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800389 }
390 }
391}