mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-22 00:32:55 +08:00
Added "DataResource" - allows many plugins to intercept a single request
This commit is contained in:
parent
8a95ed8ee6
commit
62c3e46529
@ -26,10 +26,11 @@ import org.apache.cordova.api.CordovaInterface;
|
|||||||
import org.apache.cordova.api.LOG;
|
import org.apache.cordova.api.LOG;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URLConnection;
|
import java.io.OutputStream;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class FileHelper {
|
public class FileHelper {
|
||||||
private static final String LOG_TAG = "FileUtils";
|
private static final String LOG_TAG = "FileUtils";
|
||||||
@ -90,7 +91,7 @@ public class FileHelper {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
|
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
|
||||||
if (uriString.startsWith("content")) {
|
if (uriString.startsWith("content:")) {
|
||||||
Uri uri = Uri.parse(uriString);
|
Uri uri = Uri.parse(uriString);
|
||||||
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
return cordova.getActivity().getContentResolver().openInputStream(uri);
|
||||||
} else if (uriString.startsWith("file:///android_asset/")) {
|
} else if (uriString.startsWith("file:///android_asset/")) {
|
||||||
@ -102,6 +103,57 @@ public class FileHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OutputStream getOutputStreamFromUriString(String uriString, CordovaInterface cordova) throws FileNotFoundException{
|
||||||
|
if (uriString.startsWith("content:")) {
|
||||||
|
Uri uri = Uri.parse(uriString);
|
||||||
|
return cordova.getActivity().getContentResolver().openOutputStream(uri);
|
||||||
|
} else if (uriString.startsWith("file:") && !uriString.startsWith("file:///android_asset/")) {
|
||||||
|
String realPath = uriString.substring(7);
|
||||||
|
return new FileOutputStream(realPath);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns whether the uri can be written to by openeing a File to that uri
|
||||||
|
*
|
||||||
|
* @param the URI to test
|
||||||
|
* @return boolean indicating whether the uri is writable
|
||||||
|
*/
|
||||||
|
public static boolean isUriWritable(String uriString) {
|
||||||
|
String scheme = uriString.split(":")[0];
|
||||||
|
String writableSchemes[] = new String[]{ "content" };
|
||||||
|
|
||||||
|
if(scheme.equals("file")){
|
||||||
|
// special case file
|
||||||
|
if(uriString.startsWith("file:///android_asset/")){
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int i = writableSchemes.length - 1; i >= 0 ; i--){
|
||||||
|
if(writableSchemes[i].equals(scheme)){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the "file://" prefix exists for the given string
|
||||||
|
* If the given URI string has a "file://" prefix, it is returned unchanged
|
||||||
|
*
|
||||||
|
* @param path - the path string to operate on
|
||||||
|
* @return a String with the "file://" scheme set
|
||||||
|
*/
|
||||||
|
public static String insertFileProtocol(String path) {
|
||||||
|
if(!path.matches("^[a-z0-9+.-]+:.*")){
|
||||||
|
path = "file://" + path;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the "file://" prefix from the given URI string, if applicable.
|
* Removes the "file://" prefix from the given URI string, if applicable.
|
||||||
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
|
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import org.apache.cordova.api.CordovaInterface;
|
import org.apache.cordova.api.CordovaInterface;
|
||||||
|
import org.apache.cordova.api.DataResource;
|
||||||
|
import org.apache.cordova.api.DataResourceContext;
|
||||||
import org.apache.cordova.api.LOG;
|
import org.apache.cordova.api.LOG;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
@ -43,41 +44,24 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||||
|
// We need to support the new DataResource intercepts without breaking the shouldInterceptRequest mechanism.
|
||||||
|
DataResource dataResource = DataResource.initiateNewDataRequestForUri(url, this.appView.pluginManager, cordova,
|
||||||
|
new DataResourceContext("WebViewClient.shouldInterceptRequest", true /* this is from a browser request*/));
|
||||||
|
url = dataResource.getUri().toString();
|
||||||
|
|
||||||
//Check if plugins intercept the request
|
//Check if plugins intercept the request
|
||||||
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
|
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
|
||||||
if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url))){
|
// The below bugfix is taken care of by the dataResource mechanism
|
||||||
ret = generateWebResourceResponse(url);
|
// if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url))){
|
||||||
|
// ret = generateWebResourceResponse(url);
|
||||||
|
// }
|
||||||
|
if(ret == null) {
|
||||||
|
try {
|
||||||
|
ret = new WebResourceResponse(dataResource.getMimeType(), "UTF-8", dataResource.getIs());
|
||||||
|
} catch(IOException e) {
|
||||||
|
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebResourceResponse generateWebResourceResponse(String url) {
|
|
||||||
if (url.startsWith("file:///android_asset/")) {
|
|
||||||
String mimetype = FileHelper.getMimeType(url, cordova);
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStream stream = FileHelper.getInputStreamFromUriString(url, cordova);
|
|
||||||
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
|
|
||||||
return response;
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.e("generateWebResourceResponse", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean needsIceCreamSpaceInAssetUrlFix(String url) {
|
|
||||||
if (!url.contains("%20")){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(android.os.Build.VERSION.SDK_INT){
|
|
||||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
|
|
||||||
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,20 @@ public class CordovaPlugin {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All plugins can now choose if they want to modify any uri requests. This includes all webview requests, opening of files, content uri's etc.
|
||||||
|
* This mechanism allows several plugins to modify the same request
|
||||||
|
* @param requestSource The source of the incoming request
|
||||||
|
*
|
||||||
|
* @param dataResource The resource to be loaded.
|
||||||
|
* @param dataResourceContext Context associated with the resource load
|
||||||
|
* @return Return a new DataResource if the plugin wants o assist in loading the request or null if it doesn't.
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
public DataResource shouldInterceptDataResourceRequest(DataResource dataResource, DataResourceContext dataResourceContext) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the WebView does a top-level navigation or refreshes.
|
* Called when the WebView does a top-level navigation or refreshes.
|
||||||
*
|
*
|
||||||
|
138
framework/src/org/apache/cordova/api/DataResource.java
Normal file
138
framework/src/org/apache/cordova/api/DataResource.java
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package org.apache.cordova.api;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import org.apache.cordova.FileHelper;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All requests to access files, browser network requests etc have to go through this class.
|
||||||
|
*/
|
||||||
|
public class DataResource {
|
||||||
|
private CordovaInterface cordova;
|
||||||
|
|
||||||
|
// Uri of the request. Always required.
|
||||||
|
private Uri uri;
|
||||||
|
// Remaining fields may or may not be null
|
||||||
|
private InputStream is;
|
||||||
|
private OutputStream os;
|
||||||
|
private String mimeType;
|
||||||
|
private Boolean writable;
|
||||||
|
private File realFile;
|
||||||
|
private boolean retryLoad = true;
|
||||||
|
|
||||||
|
public DataResource(CordovaInterface cordova, Uri uri) {
|
||||||
|
super();
|
||||||
|
this.cordova = cordova;
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
public DataResource(CordovaInterface cordova, Uri uri, InputStream is,
|
||||||
|
OutputStream os, String mimeType, boolean writable, File realFile) {
|
||||||
|
this(cordova, uri);
|
||||||
|
this.is = is;
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.writable = Boolean.valueOf(writable);
|
||||||
|
this.realFile = realFile;
|
||||||
|
}
|
||||||
|
public Uri getUri() {
|
||||||
|
// Uri is always provided
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
public InputStream getIs() throws IOException {
|
||||||
|
if(is == null && retryLoad) {
|
||||||
|
try {
|
||||||
|
is = FileHelper.getInputStreamFromUriString(uri.toString(), cordova);
|
||||||
|
} finally {
|
||||||
|
// We failed loading once, don't try loading anymore
|
||||||
|
if(is == null) {
|
||||||
|
retryLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return is;
|
||||||
|
}
|
||||||
|
public OutputStream getOs() throws FileNotFoundException {
|
||||||
|
if(os == null && retryLoad) {
|
||||||
|
try {
|
||||||
|
os = FileHelper.getOutputStreamFromUriString(uri.toString(), cordova);
|
||||||
|
} finally {
|
||||||
|
// We failed loading once, don't try loading anymore
|
||||||
|
if(os == null) {
|
||||||
|
retryLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
public String getMimeType() {
|
||||||
|
if(mimeType == null && retryLoad) {
|
||||||
|
try {
|
||||||
|
mimeType = FileHelper.getMimeType(uri.toString(), cordova);
|
||||||
|
} finally {
|
||||||
|
// We failed loading once, don't try loading anymore
|
||||||
|
if(mimeType == null) {
|
||||||
|
retryLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
public boolean isWritable() {
|
||||||
|
if(writable == null && retryLoad) {
|
||||||
|
try {
|
||||||
|
writable = FileHelper.isUriWritable(uri.toString());
|
||||||
|
} finally {
|
||||||
|
// We failed loading once, don't try loading anymore
|
||||||
|
if(writable == null) {
|
||||||
|
retryLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// default to false
|
||||||
|
return writable != null? writable.booleanValue() : false;
|
||||||
|
}
|
||||||
|
public File getRealFile() {
|
||||||
|
if(realFile == null && retryLoad) {
|
||||||
|
try {
|
||||||
|
String realPath = FileHelper.getRealPath(uri, cordova);
|
||||||
|
if(realPath != null) {
|
||||||
|
realFile = new File(realPath);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
// We failed loading once, don't try loading anymore
|
||||||
|
if(realFile == null) {
|
||||||
|
retryLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return realFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static instantiation methods
|
||||||
|
public static DataResource initiateNewDataRequestForUri(String uriString, PluginManager pluginManager, CordovaInterface cordova, String requestSourceTag){
|
||||||
|
// if no protocol is specified, assume its file:
|
||||||
|
uriString = FileHelper.insertFileProtocol(uriString);
|
||||||
|
return initiateNewDataRequestForUri(Uri.parse(uriString), pluginManager, cordova, requestSourceTag);
|
||||||
|
}
|
||||||
|
public static DataResource initiateNewDataRequestForUri(Uri uri, PluginManager pluginManager, CordovaInterface cordova, String requestSourceTag){
|
||||||
|
return initiateNewDataRequestForUri(uri, pluginManager, cordova, new DataResourceContext(requestSourceTag, false /* Assume, not a browser request by default */ ));
|
||||||
|
}
|
||||||
|
public static DataResource initiateNewDataRequestForUri(String uriString, PluginManager pluginManager, CordovaInterface cordova, DataResourceContext dataResourceContext){
|
||||||
|
// if no protocol is specified, assume its file:
|
||||||
|
uriString = FileHelper.insertFileProtocol(uriString);
|
||||||
|
return initiateNewDataRequestForUri(Uri.parse(uriString), pluginManager, cordova, dataResourceContext);
|
||||||
|
}
|
||||||
|
public static DataResource initiateNewDataRequestForUri(Uri uri, PluginManager pluginManager, CordovaInterface cordova, DataResourceContext dataResourceContext){
|
||||||
|
DataResource dataResource = new DataResource(cordova, uri);
|
||||||
|
if (pluginManager != null) {
|
||||||
|
// get the resource as returned by plugins
|
||||||
|
dataResource = pluginManager.shouldInterceptDataResourceRequest(dataResource, dataResourceContext);
|
||||||
|
}
|
||||||
|
return dataResource;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package org.apache.cordova.api;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
/*
|
||||||
|
* Some context information associated with a DataRequest.
|
||||||
|
*/
|
||||||
|
public class DataResourceContext {
|
||||||
|
// A random id that is unique for a particular request.
|
||||||
|
private int requestId;
|
||||||
|
// A tag associated with the source of this dataResourceContext
|
||||||
|
private String source;
|
||||||
|
// If needed, any data associated with core plugins can be a part of the context object
|
||||||
|
// This field indicates whether the request came from a browser network request
|
||||||
|
private boolean isFromBrowser;
|
||||||
|
// If needed, any data associated with non core plugins should store data in a Map so as to not clutter the context object
|
||||||
|
private Map<String, Object> dataMap;
|
||||||
|
public DataResourceContext(String source, boolean isFromBrowser) {
|
||||||
|
super();
|
||||||
|
this.requestId = new Random().nextInt();
|
||||||
|
this.source = source;
|
||||||
|
this.isFromBrowser = isFromBrowser;
|
||||||
|
this.dataMap = new HashMap<String, Object>();
|
||||||
|
}
|
||||||
|
public int getRequestId() {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
public String getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
public boolean isFromBrowser() {
|
||||||
|
return isFromBrowser;
|
||||||
|
}
|
||||||
|
public Map<String, Object> getDataMap() {
|
||||||
|
return dataMap;
|
||||||
|
}
|
||||||
|
}
|
@ -400,4 +400,30 @@ public class PluginManager {
|
|||||||
LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
|
LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
|
||||||
LOG.e(TAG, "=====================================================================================");
|
LOG.e(TAG, "=====================================================================================");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the any resource is going to be loaded - either from the webview, files or any other resource
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param dataResource The resource request to be loaded.
|
||||||
|
* @param dataResourceContext The context of the dataResource request
|
||||||
|
* @return Return the resource request that will be loaded. The returned request may be modified or unchanged.
|
||||||
|
*/
|
||||||
|
public DataResource shouldInterceptDataResourceRequest(DataResource dataResource, DataResourceContext dataResourceContext){
|
||||||
|
boolean requestModified = true;
|
||||||
|
while(requestModified) {
|
||||||
|
requestModified = false;
|
||||||
|
for (PluginEntry entry : this.entries.values()) {
|
||||||
|
if (entry.plugin != null) {
|
||||||
|
DataResource ret = entry.plugin.shouldInterceptDataResourceRequest(dataResource, dataResourceContext);
|
||||||
|
if(ret != null) {
|
||||||
|
dataResource = ret;
|
||||||
|
requestModified = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dataResource;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user