blob: dacb33f9b2eaa8bcdb2cd848262ac503437b9b1c [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()) {
114 handleHTTPLoad();
115 } else {
116 WebViewWorker.getHandler().obtainMessage(
117 WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
118 }
Grace Kloba2036dba2010-02-15 02:15:37 -0800119 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 } else if (handleLocalFile(url, mListener, mSettings)) {
121 return true;
122 }
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400123 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
125 + mListener.url());
126 }
127 mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
128 mListener.getContext().getText(
129 com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
130 return false;
131
132 }
133
134 /* package */
135 static boolean handleLocalFile(String url, LoadListener loadListener,
136 WebSettings settings) {
Patrick Scott68e53002009-08-13 15:39:20 -0400137 // Attempt to decode the percent-encoded url before passing to the
138 // local loaders.
139 try {
140 url = new String(URLUtil.decode(url.getBytes()));
141 } catch (IllegalArgumentException e) {
142 loadListener.error(EventHandler.ERROR_BAD_URL,
143 loadListener.getContext().getString(
144 com.android.internal.R.string.httpErrorBadUrl));
145 // Return true here so we do not trigger an unsupported scheme
146 // error.
147 return true;
148 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 if (URLUtil.isAssetUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800150 // load asset in a separate thread as it involves IO
Grace Kloba2036dba2010-02-15 02:15:37 -0800151 WebViewWorker.getHandler().obtainMessage(
152 WebViewWorker.MSG_ADD_STREAMLOADER,
153 new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
154 true)).sendToTarget();
Grace Klobabd5c8232009-12-07 10:11:28 -0800155 return true;
156 } else if (URLUtil.isResourceUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800157 // load resource in a separate thread as it involves IO
Grace Kloba2036dba2010-02-15 02:15:37 -0800158 WebViewWorker.getHandler().obtainMessage(
159 WebViewWorker.MSG_ADD_STREAMLOADER,
160 new FileLoader(url, loadListener, FileLoader.TYPE_RES,
161 true)).sendToTarget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 return true;
163 } else if (URLUtil.isFileUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800164 // load file in a separate thread as it involves IO
Grace Kloba2036dba2010-02-15 02:15:37 -0800165 WebViewWorker.getHandler().obtainMessage(
166 WebViewWorker.MSG_ADD_STREAMLOADER,
167 new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
168 settings.getAllowFileAccess())).sendToTarget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 return true;
170 } else if (URLUtil.isContentUrl(url)) {
171 // Send the raw url to the ContentLoader because it will do a
Grace Klobaac75f562010-02-03 10:24:06 -0800172 // permission check and the url has to match.
173 // load content in a separate thread as it involves IO
Grace Kloba2036dba2010-02-15 02:15:37 -0800174 WebViewWorker.getHandler().obtainMessage(
175 WebViewWorker.MSG_ADD_STREAMLOADER,
176 new ContentLoader(loadListener.url(), loadListener))
177 .sendToTarget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 return true;
179 } else if (URLUtil.isDataUrl(url)) {
Grace Klobaac75f562010-02-03 10:24:06 -0800180 // load data in the current thread to reduce the latency
181 new DataLoader(url, loadListener).load();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 return true;
183 } else if (URLUtil.isAboutUrl(url)) {
184 loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
185 loadListener.endData();
186 return true;
187 }
188 return false;
189 }
Grace Kloba2036dba2010-02-15 02:15:37 -0800190
191 boolean handleHTTPLoad() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800192 if (mHeaders == null) {
193 mHeaders = new HashMap<String, String>();
194 }
195 populateStaticHeaders();
196 populateHeaders();
197
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800198 // response was handled by Cache, don't issue HTTP request
199 if (handleCache()) {
200 // push the request data down to the LoadListener
201 // as response from the cache could be a redirect
202 // and we may need to initiate a network request if the cache
203 // can't satisfy redirect URL
Patrick Scottfe4fec72009-07-14 15:54:30 -0400204 mListener.setRequestData(mMethod, mHeaders, mPostData);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 return true;
206 }
207
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400208 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800209 Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
210 + mListener.url());
211 }
212
213 boolean ret = false;
214 int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
215
216 try {
217 ret = mNetwork.requestURL(mMethod, mHeaders,
Patrick Scottfe4fec72009-07-14 15:54:30 -0400218 mPostData, mListener);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800219 } catch (android.net.ParseException ex) {
220 error = EventHandler.ERROR_BAD_URL;
221 } catch (java.lang.RuntimeException ex) {
222 /* probably an empty header set by javascript. We want
223 the same result as bad URL */
224 error = EventHandler.ERROR_BAD_URL;
225 }
226 if (!ret) {
227 mListener.error(error, mListener.getContext().getText(
228 EventHandler.errorStringResources[Math.abs(error)]).toString());
229 return false;
230 }
231 return true;
232 }
233
234 /*
Andrei Popescu385df692009-08-13 11:59:57 +0100235 * This function is used by handleCache to
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 * setup a load from the byte stream in a CacheResult.
237 */
238 private void startCacheLoad(CacheResult result) {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400239 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 Log.v(LOGTAG, "FrameLoader: loading from cache: "
241 + mListener.url());
242 }
243 // Tell the Listener respond with the cache file
244 CacheLoader cacheLoader =
245 new CacheLoader(mListener, result);
246 mListener.setCacheLoader(cacheLoader);
Grace Kloba2036dba2010-02-15 02:15:37 -0800247 // Load the cached file in a separate thread
248 WebViewWorker.getHandler().obtainMessage(
249 WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 }
251
252 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 * This function is used by the handleHTTPLoad to setup the cache headers
254 * correctly.
255 * Returns true if the response was handled from the cache
256 */
257 private boolean handleCache() {
258 switch (mCacheMode) {
259 // This mode is normally used for a reload, it instructs the http
260 // loader to not use the cached content.
261 case WebSettings.LOAD_NO_CACHE:
262 break;
263
264
265 // This mode is used when the content should only be loaded from
266 // the cache. If it is not there, then fail the load. This is used
267 // to load POST content in a history navigation.
268 case WebSettings.LOAD_CACHE_ONLY: {
269 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800270 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800271 if (result != null) {
272 startCacheLoad(result);
273 } else {
274 // This happens if WebCore was first told that the POST
275 // response was in the cache, then when we try to use it
276 // it has gone.
277 // Generate a file not found error
278 int err = EventHandler.FILE_NOT_FOUND_ERROR;
279 mListener.error(err, mListener.getContext().getText(
280 EventHandler.errorStringResources[Math.abs(err)])
281 .toString());
282 }
283 return true;
284 }
285
286 // This mode is for when the user is doing a history navigation
287 // in the browser and should returned cached content regardless
288 // of it's state. If it is not in the cache, then go to the
289 // network.
290 case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
Derek Sollenberger2e5c1502009-06-03 10:44:42 -0400291 if (DebugFlags.FRAME_LOADER) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 Log.v(LOGTAG, "FrameLoader: checking cache: "
293 + mListener.url());
294 }
295 // Get the cache file name for the current URL, passing null for
296 // the validation headers causes no validation to occur
297 CacheResult result = CacheManager.getCacheFile(mListener.url(),
Grace Kloba8c92c392009-11-08 19:01:55 -0800298 mListener.postIdentifier(), null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800299 if (result != null) {
300 startCacheLoad(result);
301 return true;
302 }
303 break;
304 }
305
306 // This is the default case, which is to check to see if the
307 // content in the cache can be used. If it can be used, then
308 // use it. If it needs revalidation then the relevant headers
309 // are added to the request.
310 default:
311 case WebSettings.LOAD_NORMAL:
312 return mListener.checkCache(mHeaders);
313 }// end of switch
314
315 return false;
316 }
317
318 /**
319 * Add the static headers that don't change with each request.
320 */
321 private void populateStaticHeaders() {
322 // Accept header should already be there as they are built by WebCore,
323 // but in the case they are missing, add some.
324 String accept = mHeaders.get("Accept");
325 if (accept == null || accept.length() == 0) {
326 mHeaders.put("Accept", HEADER_STR);
327 }
328 mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
329
330 String acceptLanguage = mSettings.getAcceptLanguage();
331 if (acceptLanguage.length() > 0) {
332 mHeaders.put("Accept-Language", acceptLanguage);
333 }
334
335 mHeaders.put("User-Agent", mSettings.getUserAgentString());
336 }
337
338 /**
339 * Add the content related headers. These headers contain user private data
340 * and is not used when we are proxying an untrusted request.
341 */
342 private void populateHeaders() {
343
344 if (mReferrer != null) mHeaders.put("Referer", mReferrer);
345 if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
346
347 // if we have an active proxy and have proxy credentials, do pre-emptive
348 // authentication to avoid an extra round-trip:
349 if (mNetwork.isValidProxySet()) {
350 String username;
351 String password;
352 /* The proxy credentials can be set in the Network thread */
353 synchronized (mNetwork) {
354 username = mNetwork.getProxyUsername();
355 password = mNetwork.getProxyPassword();
356 }
357 if (username != null && password != null) {
358 // we collect credentials ONLY if the proxy scheme is BASIC!!!
359 String proxyHeader = RequestHandle.authorizationHeader(true);
360 mHeaders.put(proxyHeader,
361 "Basic " + RequestHandle.computeBasicAuthResponse(
362 username, password));
363 }
364 }
365
366 // Set cookie header
367 String cookie = CookieManager.getInstance().getCookie(
368 mListener.getWebAddress());
369 if (cookie != null && cookie.length() > 0) {
Grace Kloba65c0fc42009-06-29 17:03:42 -0700370 mHeaders.put("Cookie", cookie);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 }
372 }
373}