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