blob: 0d8030285244a00bd7c0fdf466a5004c971f43a6 [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
Iain Merrick83d4a232010-11-12 14:11:16 +000019import android.net.http.ErrorStrings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import android.net.http.EventHandler;
21import android.net.http.RequestHandle;
Matthias Thomaebdb08962010-10-20 10:28:58 +020022import android.os.Build;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023import android.util.Log;
24import android.webkit.CacheManager.CacheResult;
Steve Block808751f2011-01-04 14:26:27 +000025import android.webkit.JniUtil;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026
27import java.util.HashMap;
28import java.util.Map;
29
30class FrameLoader {
31
32 private final LoadListener mListener;
33 private final String mMethod;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034 private final WebSettings mSettings;
35 private Map<String, String> mHeaders;
36 private byte[] mPostData;
37 private Network mNetwork;
38 private int mCacheMode;
39 private String mReferrer;
40 private String mContentType;
Matthias Thomaebdb08962010-10-20 10:28:58 +020041 private final String mUaprofHeader;
Patrick Scottc12544a2010-11-11 13:16:44 -050042 private final WebResourceResponse mInterceptResponse;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080043
44 private static final int URI_PROTOCOL = 0x100;
45
46 private static final String CONTENT_TYPE = "content-type";
47
48 // Contents of an about:blank page
49 private static final String mAboutBlank =
50 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" +
51 "<html><head><title>about:blank</title></head><body></body></html>";
52
53 static final String HEADER_STR = "text/xml, text/html, " +
54 "application/xhtml+xml, image/png, text/plain, */*;q=0.8";
55
56 private static final String LOGTAG = "webkit";
57
58 FrameLoader(LoadListener listener, WebSettings settings,
Patrick Scottc12544a2010-11-11 13:16:44 -050059 String method, WebResourceResponse interceptResponse) {
Steve Block808751f2011-01-04 14:26:27 +000060 assert !JniUtil.useChromiumHttpStack();
61
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062 mListener = listener;
63 mHeaders = null;
64 mMethod = method;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 mCacheMode = WebSettings.LOAD_NORMAL;
66 mSettings = settings;
Patrick Scottc12544a2010-11-11 13:16:44 -050067 mInterceptResponse = interceptResponse;
Matthias Thomaebdb08962010-10-20 10:28:58 +020068 mUaprofHeader = mListener.getContext().getResources().getString(
69 com.android.internal.R.string.config_useragentprofile_url, Build.MODEL);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070 }
71
72 public void setReferrer(String ref) {
73 // only set referrer for http or https
74 if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
75 }
76
77 public void setPostData(byte[] postData) {
78 mPostData = postData;
79 }
80
81 public void setContentTypeForPost(String postContentType) {
82 mContentType = postContentType;
83 }
84
85 public void setCacheMode(int cacheMode) {
86 mCacheMode = cacheMode;
87 }
88
89 public void setHeaders(HashMap headers) {
90 mHeaders = headers;
91 }
92
93 public LoadListener getLoadListener() {
94 return mListener;
95 }
96
97 /**
98 * Issues the load request.
99 *
100 * Return value does not indicate if the load was successful or not. It
101 * simply indicates that the load request is reasonable.
102 *
103 * @return true if the load is reasonable.
104 */
105 public boolean executeLoad() {
106 String url = mListener.url();
107
Patrick Scottc12544a2010-11-11 13:16:44 -0500108 // Process intercepted requests first as they could be any url.
109 if (mInterceptResponse != null) {
110 if (mListener.isSynchronous()) {
111 mInterceptResponse.loader(mListener).load();
112 } else {
113 WebViewWorker.getHandler().obtainMessage(
114 WebViewWorker.MSG_ADD_STREAMLOADER,
115 mInterceptResponse.loader(mListener)).sendToTarget();
116 }
117 return true;
118 } else if (URLUtil.isNetworkUrl(url)){
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 if (mSettings.getBlockNetworkLoads()) {
120 mListener.error(EventHandler.ERROR_BAD_URL,
121 mListener.getContext().getString(
122 com.android.internal.R.string.httpErrorBadUrl));
123 return false;
124 }
Kristian Monsenb2d95502010-04-12 16:42:39 +0100125 // Make sure the host part of the url is correctly
126 // encoded before sending the request
127 if (!URLUtil.verifyURLEncoding(mListener.host())) {
Grace Kloba758bf412009-08-11 11:47:24 -0700128 mListener.error(EventHandler.ERROR_BAD_URL,
129 mListener.getContext().getString(
130 com.android.internal.R.string.httpErrorBadUrl));
131 return false;
132 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800133 mNetwork = Network.getInstance(mListener.getContext());
Grace Kloba5f38e1a2010-03-01 23:10:10 -0800134 if (mListener.isSynchronous()) {
Steve Block76619af2010-03-05 18:32:52 +0000135 return handleHTTPLoad();
Grace Kloba5f38e1a2010-03-01 23:10:10 -0800136 }
Steve Block76619af2010-03-05 18:32:52 +0000137 WebViewWorker.getHandler().obtainMessage(
138 WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
Grace Kloba2036dba2010-02-15 02:15:37 -0800139 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 } else if (handleLocalFile(url, mListener, mSettings)) {
141 return true;
142 }
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400143 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
145 + mListener.url());
146 }
147 mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
148 mListener.getContext().getText(
149 com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
150 return false;
151
152 }
153
Steve Block808751f2011-01-04 14:26:27 +0000154 private static boolean handleLocalFile(String url, LoadListener loadListener,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800155 WebSettings settings) {
Steve Block808751f2011-01-04 14:26:27 +0000156 assert !JniUtil.useChromiumHttpStack();
157
Patrick Scott68e53002009-08-13 15:39:20 -0400158 // Attempt to decode the percent-encoded url before passing to the
159 // local loaders.
160 try {
161 url = new String(URLUtil.decode(url.getBytes()));
162 } catch (IllegalArgumentException e) {
163 loadListener.error(EventHandler.ERROR_BAD_URL,
164 loadListener.getContext().getString(
165 com.android.internal.R.string.httpErrorBadUrl));
166 // Return true here so we do not trigger an unsupported scheme
167 // error.
168 return true;
169 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 if (URLUtil.isAssetUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000171 if (loadListener.isSynchronous()) {
172 new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
173 true).load();
174 } else {
175 // load asset in a separate thread as it involves IO
176 WebViewWorker.getHandler().obtainMessage(
177 WebViewWorker.MSG_ADD_STREAMLOADER,
178 new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
179 true)).sendToTarget();
180 }
Grace Klobabd5c8232009-12-07 10:11:28 -0800181 return true;
182 } else if (URLUtil.isResourceUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000183 if (loadListener.isSynchronous()) {
184 new FileLoader(url, loadListener, FileLoader.TYPE_RES,
185 true).load();
186 } else {
187 // load resource in a separate thread as it involves IO
188 WebViewWorker.getHandler().obtainMessage(
189 WebViewWorker.MSG_ADD_STREAMLOADER,
190 new FileLoader(url, loadListener, FileLoader.TYPE_RES,
191 true)).sendToTarget();
192 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 return true;
194 } else if (URLUtil.isFileUrl(url)) {
Steve Block76619af2010-03-05 18:32:52 +0000195 if (loadListener.isSynchronous()) {
196 new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
197 settings.getAllowFileAccess()).load();
198 } else {
199 // load file in a separate thread as it involves IO
200 WebViewWorker.getHandler().obtainMessage(
201 WebViewWorker.MSG_ADD_STREAMLOADER,
202 new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
203 settings.getAllowFileAccess())).sendToTarget();
204 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 return true;
Patrick Scottf0b70dc2011-01-05 11:36:48 -0500206 } else if (settings.getAllowContentAccess() &&
207 URLUtil.isContentUrl(url)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 // Send the raw url to the ContentLoader because it will do a
Grace Klobaac75f562010-02-03 10:24:06 -0800209 // permission check and the url has to match.
Steve Block76619af2010-03-05 18:32:52 +0000210 if (loadListener.isSynchronous()) {
211 new ContentLoader(loadListener.url(), loadListener).load();
212 } else {
213 // load content in a separate thread as it involves IO
214 WebViewWorker.getHandler().obtainMessage(
215 WebViewWorker.MSG_ADD_STREAMLOADER,
216 new ContentLoader(loadListener.url(), loadListener))
217 .sendToTarget();
218 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 return true;
220 } else if (URLUtil.isDataUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800221 // load data in the current thread to reduce the latency
222 new DataLoader(url, loadListener).load();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800223 return true;
224 } else if (URLUtil.isAboutUrl(url)) {
225 loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
226 loadListener.endData();
227 return true;
228 }
229 return false;
230 }
Grace Kloba2036dba2010-02-15 02:15:37 -0800231
232 boolean handleHTTPLoad() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 if (mHeaders == null) {
234 mHeaders = new HashMap<String, String>();
235 }
236 populateStaticHeaders();
237 populateHeaders();
238
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800239 // response was handled by Cache, don't issue HTTP request
240 if (handleCache()) {
241 // push the request data down to the LoadListener
242 // as response from the cache could be a redirect
243 // and we may need to initiate a network request if the cache
244 // can't satisfy redirect URL
Patrick Scottfe4fec72009-07-14 15:54:30 -0400245 mListener.setRequestData(mMethod, mHeaders, mPostData);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 return true;
247 }
248
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400249 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
251 + mListener.url());
252 }
253
254 boolean ret = false;
255 int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
256
257 try {
258 ret = mNetwork.requestURL(mMethod, mHeaders,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400259 mPostData, mListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 } catch (android.net.ParseException ex) {
261 error = EventHandler.ERROR_BAD_URL;
262 } catch (java.lang.RuntimeException ex) {
263 /* probably an empty header set by javascript. We want
264 the same result as bad URL */
265 error = EventHandler.ERROR_BAD_URL;
266 }
267 if (!ret) {
Iain Merrick83d4a232010-11-12 14:11:16 +0000268 mListener.error(error, ErrorStrings.getString(error, mListener.getContext()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 return false;
270 }
271 return true;
272 }
273
274 /*
Andrei Popescu385df692009-08-13 11:59:57 +0100275 * This function is used by handleCache to
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 * setup a load from the byte stream in a CacheResult.
277 */
278 private void startCacheLoad(CacheResult result) {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400279 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800280 Log.v(LOGTAG, "FrameLoader: loading from cache: "
281 + mListener.url());
282 }
283 // Tell the Listener respond with the cache file
284 CacheLoader cacheLoader =
285 new CacheLoader(mListener, result);
286 mListener.setCacheLoader(cacheLoader);
Steve Block0b437472010-03-15 11:43:06 +0000287 if (mListener.isSynchronous()) {
288 cacheLoader.load();
289 } else {
290 // Load the cached file in a separate thread
291 WebViewWorker.getHandler().obtainMessage(
292 WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
293 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800294 }
295
296 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800297 * This function is used by the handleHTTPLoad to setup the cache headers
298 * correctly.
299 * Returns true if the response was handled from the cache
300 */
301 private boolean handleCache() {
302 switch (mCacheMode) {
303 // This mode is normally used for a reload, it instructs the http
304 // loader to not use the cached content.
305 case WebSettings.LOAD_NO_CACHE:
306 break;
307
308
309 // This mode is used when the content should only be loaded from
310 // the cache. If it is not there, then fail the load. This is used
311 // to load POST content in a history navigation.
312 case WebSettings.LOAD_CACHE_ONLY: {
313 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800314 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800315 if (result != null) {
316 startCacheLoad(result);
317 } else {
318 // This happens if WebCore was first told that the POST
319 // response was in the cache, then when we try to use it
320 // it has gone.
321 // Generate a file not found error
322 int err = EventHandler.FILE_NOT_FOUND_ERROR;
Iain Merrick83d4a232010-11-12 14:11:16 +0000323 mListener.error(err,
324 ErrorStrings.getString(err, mListener.getContext()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325 }
326 return true;
327 }
328
329 // This mode is for when the user is doing a history navigation
330 // in the browser and should returned cached content regardless
331 // of it's state. If it is not in the cache, then go to the
332 // network.
333 case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400334 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 Log.v(LOGTAG, "FrameLoader: checking cache: "
336 + mListener.url());
337 }
338 // Get the cache file name for the current URL, passing null for
339 // the validation headers causes no validation to occur
340 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800341 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800342 if (result != null) {
343 startCacheLoad(result);
344 return true;
345 }
346 break;
347 }
348
349 // This is the default case, which is to check to see if the
350 // content in the cache can be used. If it can be used, then
351 // use it. If it needs revalidation then the relevant headers
352 // are added to the request.
353 default:
354 case WebSettings.LOAD_NORMAL:
355 return mListener.checkCache(mHeaders);
356 }// end of switch
357
358 return false;
359 }
360
361 /**
362 * Add the static headers that don't change with each request.
363 */
364 private void populateStaticHeaders() {
365 // Accept header should already be there as they are built by WebCore,
366 // but in the case they are missing, add some.
367 String accept = mHeaders.get("Accept");
368 if (accept == null || accept.length() == 0) {
369 mHeaders.put("Accept", HEADER_STR);
370 }
371 mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
372
373 String acceptLanguage = mSettings.getAcceptLanguage();
374 if (acceptLanguage.length() > 0) {
375 mHeaders.put("Accept-Language", acceptLanguage);
376 }
377
378 mHeaders.put("User-Agent", mSettings.getUserAgentString());
Matthias Thomaebdb08962010-10-20 10:28:58 +0200379
380 // Set the x-wap-profile header
381 if (mUaprofHeader != null && mUaprofHeader.length() > 0) {
382 mHeaders.put("x-wap-profile", mUaprofHeader);
383 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800384 }
385
386 /**
387 * Add the content related headers. These headers contain user private data
388 * and is not used when we are proxying an untrusted request.
389 */
390 private void populateHeaders() {
391
392 if (mReferrer != null) mHeaders.put("Referer", mReferrer);
393 if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
394
395 // if we have an active proxy and have proxy credentials, do pre-emptive
396 // authentication to avoid an extra round-trip:
397 if (mNetwork.isValidProxySet()) {
398 String username;
399 String password;
400 /* The proxy credentials can be set in the Network thread */
401 synchronized (mNetwork) {
402 username = mNetwork.getProxyUsername();
403 password = mNetwork.getProxyPassword();
404 }
405 if (username != null && password != null) {
406 // we collect credentials ONLY if the proxy scheme is BASIC!!!
407 String proxyHeader = RequestHandle.authorizationHeader(true);
408 mHeaders.put(proxyHeader,
409 "Basic " + RequestHandle.computeBasicAuthResponse(
410 username, password));
411 }
412 }
413
414 // Set cookie header
415 String cookie = CookieManager.getInstance().getCookie(
416 mListener.getWebAddress());
417 if (cookie != null && cookie.length() > 0) {
Grace Kloba65c0fc42009-06-29 17:03:42 -0700418 mHeaders.put("Cookie", cookie);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800419 }
420 }
421}