398 lines
15 KiB
Java
398 lines
15 KiB
Java
/*
|
|
* Copyright (C) 2006 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.webkit;
|
|
|
|
import android.net.http.EventHandler;
|
|
import android.net.http.RequestHandle;
|
|
import android.util.Log;
|
|
import android.webkit.CacheManager.CacheResult;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
class FrameLoader {
|
|
|
|
private final LoadListener mListener;
|
|
private final String mMethod;
|
|
private final WebSettings mSettings;
|
|
private Map<String, String> mHeaders;
|
|
private byte[] mPostData;
|
|
private Network mNetwork;
|
|
private int mCacheMode;
|
|
private String mReferrer;
|
|
private String mContentType;
|
|
|
|
private static final int URI_PROTOCOL = 0x100;
|
|
|
|
private static final String CONTENT_TYPE = "content-type";
|
|
|
|
// Contents of an about:blank page
|
|
private static final String mAboutBlank =
|
|
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EB\">" +
|
|
"<html><head><title>about:blank</title></head><body></body></html>";
|
|
|
|
static final String HEADER_STR = "text/xml, text/html, " +
|
|
"application/xhtml+xml, image/png, text/plain, */*;q=0.8";
|
|
|
|
private static final String LOGTAG = "webkit";
|
|
|
|
FrameLoader(LoadListener listener, WebSettings settings,
|
|
String method) {
|
|
mListener = listener;
|
|
mHeaders = null;
|
|
mMethod = method;
|
|
mCacheMode = WebSettings.LOAD_NORMAL;
|
|
mSettings = settings;
|
|
}
|
|
|
|
public void setReferrer(String ref) {
|
|
// only set referrer for http or https
|
|
if (URLUtil.isNetworkUrl(ref)) mReferrer = ref;
|
|
}
|
|
|
|
public void setPostData(byte[] postData) {
|
|
mPostData = postData;
|
|
}
|
|
|
|
public void setContentTypeForPost(String postContentType) {
|
|
mContentType = postContentType;
|
|
}
|
|
|
|
public void setCacheMode(int cacheMode) {
|
|
mCacheMode = cacheMode;
|
|
}
|
|
|
|
public void setHeaders(HashMap headers) {
|
|
mHeaders = headers;
|
|
}
|
|
|
|
public LoadListener getLoadListener() {
|
|
return mListener;
|
|
}
|
|
|
|
/**
|
|
* Issues the load request.
|
|
*
|
|
* Return value does not indicate if the load was successful or not. It
|
|
* simply indicates that the load request is reasonable.
|
|
*
|
|
* @return true if the load is reasonable.
|
|
*/
|
|
public boolean executeLoad() {
|
|
String url = mListener.url();
|
|
|
|
if (URLUtil.isNetworkUrl(url)){
|
|
if (mSettings.getBlockNetworkLoads()) {
|
|
mListener.error(EventHandler.ERROR_BAD_URL,
|
|
mListener.getContext().getString(
|
|
com.android.internal.R.string.httpErrorBadUrl));
|
|
return false;
|
|
}
|
|
// Make sure the host part of the url is correctly
|
|
// encoded before sending the request
|
|
if (!URLUtil.verifyURLEncoding(mListener.host())) {
|
|
mListener.error(EventHandler.ERROR_BAD_URL,
|
|
mListener.getContext().getString(
|
|
com.android.internal.R.string.httpErrorBadUrl));
|
|
return false;
|
|
}
|
|
mNetwork = Network.getInstance(mListener.getContext());
|
|
if (mListener.isSynchronous()) {
|
|
return handleHTTPLoad();
|
|
}
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_HTTPLOADER, this).sendToTarget();
|
|
return true;
|
|
} else if (handleLocalFile(url, mListener, mSettings)) {
|
|
return true;
|
|
}
|
|
if (DebugFlags.FRAME_LOADER) {
|
|
Log.v(LOGTAG, "FrameLoader.executeLoad: url protocol not supported:"
|
|
+ mListener.url());
|
|
}
|
|
mListener.error(EventHandler.ERROR_UNSUPPORTED_SCHEME,
|
|
mListener.getContext().getText(
|
|
com.android.internal.R.string.httpErrorUnsupportedScheme).toString());
|
|
return false;
|
|
|
|
}
|
|
|
|
/* package */
|
|
static boolean handleLocalFile(String url, LoadListener loadListener,
|
|
WebSettings settings) {
|
|
// Attempt to decode the percent-encoded url before passing to the
|
|
// local loaders.
|
|
try {
|
|
url = new String(URLUtil.decode(url.getBytes()));
|
|
} catch (IllegalArgumentException e) {
|
|
loadListener.error(EventHandler.ERROR_BAD_URL,
|
|
loadListener.getContext().getString(
|
|
com.android.internal.R.string.httpErrorBadUrl));
|
|
// Return true here so we do not trigger an unsupported scheme
|
|
// error.
|
|
return true;
|
|
}
|
|
if (URLUtil.isAssetUrl(url)) {
|
|
if (loadListener.isSynchronous()) {
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
|
|
true).load();
|
|
} else {
|
|
// load asset in a separate thread as it involves IO
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_STREAMLOADER,
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_ASSET,
|
|
true)).sendToTarget();
|
|
}
|
|
return true;
|
|
} else if (URLUtil.isResourceUrl(url)) {
|
|
if (loadListener.isSynchronous()) {
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_RES,
|
|
true).load();
|
|
} else {
|
|
// load resource in a separate thread as it involves IO
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_STREAMLOADER,
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_RES,
|
|
true)).sendToTarget();
|
|
}
|
|
return true;
|
|
} else if (URLUtil.isFileUrl(url)) {
|
|
if (loadListener.isSynchronous()) {
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
|
|
settings.getAllowFileAccess()).load();
|
|
} else {
|
|
// load file in a separate thread as it involves IO
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_STREAMLOADER,
|
|
new FileLoader(url, loadListener, FileLoader.TYPE_FILE,
|
|
settings.getAllowFileAccess())).sendToTarget();
|
|
}
|
|
return true;
|
|
} else if (settings.getAllowContentAccess() &&
|
|
URLUtil.isContentUrl(url)) {
|
|
// Send the raw url to the ContentLoader because it will do a
|
|
// permission check and the url has to match.
|
|
if (loadListener.isSynchronous()) {
|
|
new ContentLoader(loadListener.url(), loadListener).load();
|
|
} else {
|
|
// load content in a separate thread as it involves IO
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_STREAMLOADER,
|
|
new ContentLoader(loadListener.url(), loadListener))
|
|
.sendToTarget();
|
|
}
|
|
return true;
|
|
} else if (URLUtil.isDataUrl(url)) {
|
|
// load data in the current thread to reduce the latency
|
|
new DataLoader(url, loadListener).load();
|
|
return true;
|
|
} else if (URLUtil.isAboutUrl(url)) {
|
|
loadListener.data(mAboutBlank.getBytes(), mAboutBlank.length());
|
|
loadListener.endData();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
boolean handleHTTPLoad() {
|
|
if (mHeaders == null) {
|
|
mHeaders = new HashMap<String, String>();
|
|
}
|
|
populateStaticHeaders();
|
|
populateHeaders();
|
|
|
|
// response was handled by Cache, don't issue HTTP request
|
|
if (handleCache()) {
|
|
// push the request data down to the LoadListener
|
|
// as response from the cache could be a redirect
|
|
// and we may need to initiate a network request if the cache
|
|
// can't satisfy redirect URL
|
|
mListener.setRequestData(mMethod, mHeaders, mPostData);
|
|
return true;
|
|
}
|
|
|
|
if (DebugFlags.FRAME_LOADER) {
|
|
Log.v(LOGTAG, "FrameLoader: http " + mMethod + " load for: "
|
|
+ mListener.url());
|
|
}
|
|
|
|
boolean ret = false;
|
|
int error = EventHandler.ERROR_UNSUPPORTED_SCHEME;
|
|
|
|
try {
|
|
ret = mNetwork.requestURL(mMethod, mHeaders,
|
|
mPostData, mListener);
|
|
} catch (android.net.ParseException ex) {
|
|
error = EventHandler.ERROR_BAD_URL;
|
|
} catch (java.lang.RuntimeException ex) {
|
|
/* probably an empty header set by javascript. We want
|
|
the same result as bad URL */
|
|
error = EventHandler.ERROR_BAD_URL;
|
|
}
|
|
if (!ret) {
|
|
mListener.error(error, mListener.getContext().getText(
|
|
EventHandler.errorStringResources[Math.abs(error)]).toString());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This function is used by handleCache to
|
|
* setup a load from the byte stream in a CacheResult.
|
|
*/
|
|
private void startCacheLoad(CacheResult result) {
|
|
if (DebugFlags.FRAME_LOADER) {
|
|
Log.v(LOGTAG, "FrameLoader: loading from cache: "
|
|
+ mListener.url());
|
|
}
|
|
// Tell the Listener respond with the cache file
|
|
CacheLoader cacheLoader =
|
|
new CacheLoader(mListener, result);
|
|
mListener.setCacheLoader(cacheLoader);
|
|
if (mListener.isSynchronous()) {
|
|
cacheLoader.load();
|
|
} else {
|
|
// Load the cached file in a separate thread
|
|
WebViewWorker.getHandler().obtainMessage(
|
|
WebViewWorker.MSG_ADD_STREAMLOADER, cacheLoader).sendToTarget();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function is used by the handleHTTPLoad to setup the cache headers
|
|
* correctly.
|
|
* Returns true if the response was handled from the cache
|
|
*/
|
|
private boolean handleCache() {
|
|
switch (mCacheMode) {
|
|
// This mode is normally used for a reload, it instructs the http
|
|
// loader to not use the cached content.
|
|
case WebSettings.LOAD_NO_CACHE:
|
|
break;
|
|
|
|
|
|
// This mode is used when the content should only be loaded from
|
|
// the cache. If it is not there, then fail the load. This is used
|
|
// to load POST content in a history navigation.
|
|
case WebSettings.LOAD_CACHE_ONLY: {
|
|
CacheResult result = CacheManager.getCacheFile(mListener.url(),
|
|
mListener.postIdentifier(), null);
|
|
if (result != null) {
|
|
startCacheLoad(result);
|
|
} else {
|
|
// This happens if WebCore was first told that the POST
|
|
// response was in the cache, then when we try to use it
|
|
// it has gone.
|
|
// Generate a file not found error
|
|
int err = EventHandler.FILE_NOT_FOUND_ERROR;
|
|
mListener.error(err, mListener.getContext().getText(
|
|
EventHandler.errorStringResources[Math.abs(err)])
|
|
.toString());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// This mode is for when the user is doing a history navigation
|
|
// in the browser and should returned cached content regardless
|
|
// of it's state. If it is not in the cache, then go to the
|
|
// network.
|
|
case WebSettings.LOAD_CACHE_ELSE_NETWORK: {
|
|
if (DebugFlags.FRAME_LOADER) {
|
|
Log.v(LOGTAG, "FrameLoader: checking cache: "
|
|
+ mListener.url());
|
|
}
|
|
// Get the cache file name for the current URL, passing null for
|
|
// the validation headers causes no validation to occur
|
|
CacheResult result = CacheManager.getCacheFile(mListener.url(),
|
|
mListener.postIdentifier(), null);
|
|
if (result != null) {
|
|
startCacheLoad(result);
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// This is the default case, which is to check to see if the
|
|
// content in the cache can be used. If it can be used, then
|
|
// use it. If it needs revalidation then the relevant headers
|
|
// are added to the request.
|
|
default:
|
|
case WebSettings.LOAD_NORMAL:
|
|
return mListener.checkCache(mHeaders);
|
|
}// end of switch
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Add the static headers that don't change with each request.
|
|
*/
|
|
private void populateStaticHeaders() {
|
|
// Accept header should already be there as they are built by WebCore,
|
|
// but in the case they are missing, add some.
|
|
String accept = mHeaders.get("Accept");
|
|
if (accept == null || accept.length() == 0) {
|
|
mHeaders.put("Accept", HEADER_STR);
|
|
}
|
|
mHeaders.put("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7");
|
|
|
|
String acceptLanguage = mSettings.getAcceptLanguage();
|
|
if (acceptLanguage.length() > 0) {
|
|
mHeaders.put("Accept-Language", acceptLanguage);
|
|
}
|
|
|
|
mHeaders.put("User-Agent", mSettings.getUserAgentString());
|
|
}
|
|
|
|
/**
|
|
* Add the content related headers. These headers contain user private data
|
|
* and is not used when we are proxying an untrusted request.
|
|
*/
|
|
private void populateHeaders() {
|
|
|
|
if (mReferrer != null) mHeaders.put("Referer", mReferrer);
|
|
if (mContentType != null) mHeaders.put(CONTENT_TYPE, mContentType);
|
|
|
|
// if we have an active proxy and have proxy credentials, do pre-emptive
|
|
// authentication to avoid an extra round-trip:
|
|
if (mNetwork.isValidProxySet()) {
|
|
String username;
|
|
String password;
|
|
/* The proxy credentials can be set in the Network thread */
|
|
synchronized (mNetwork) {
|
|
username = mNetwork.getProxyUsername();
|
|
password = mNetwork.getProxyPassword();
|
|
}
|
|
if (username != null && password != null) {
|
|
// we collect credentials ONLY if the proxy scheme is BASIC!!!
|
|
String proxyHeader = RequestHandle.authorizationHeader(true);
|
|
mHeaders.put(proxyHeader,
|
|
"Basic " + RequestHandle.computeBasicAuthResponse(
|
|
username, password));
|
|
}
|
|
}
|
|
|
|
// Set cookie header
|
|
String cookie = CookieManager.getInstance().getCookie(
|
|
mListener.getWebAddress());
|
|
if (cookie != null && cookie.length() > 0) {
|
|
mHeaders.put("Cookie", cookie);
|
|
}
|
|
}
|
|
}
|