Import AppBundle from https://github.com/MobileChromeApps/AppBundle

It really belongs in this repo.


Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/41e2b4d6
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/41e2b4d6
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/41e2b4d6

Branch: refs/heads/master
Commit: 41e2b4d6e27059fa94513862abf2e23c53a858f0
Parents: 9ae368e
Author: Andrew Grieve <[email protected]>
Authored: Tue Oct 15 22:55:51 2013 -0400
Committer: Andrew Grieve <[email protected]>
Committed: Wed Oct 23 21:55:17 2013 -0400

----------------------------------------------------------------------
 AppBundle/README.md                  |  74 ++++++++
 AppBundle/appBundle.js               |  23 +++
 AppBundle/plugin.xml                 |  39 ++++
 AppBundle/src/android/AppBundle.java | 214 ++++++++++++++++++++++
 AppBundle/src/ios/AppBundle.h        |  80 ++++++++
 AppBundle/src/ios/AppBundle.m        | 293 ++++++++++++++++++++++++++++++
 6 files changed, 723 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/README.md
----------------------------------------------------------------------
diff --git a/AppBundle/README.md b/AppBundle/README.md
new file mode 100644
index 0000000..4d852ea
--- /dev/null
+++ b/AppBundle/README.md
@@ -0,0 +1,74 @@
+AppBundle
+=========
+
+This plugin allows any uri's that are loaded to be replaced with modified uri's
+These include uri's being loaded by the browser such as page navigation, 
script tags.
+This also includes file uris being opened by file api etc. (currently android 
only)
+
+##AppBundle Uri's
+This plugin allows the use of "app-bundle://" instead of platform specific 
urls such "file:///android_asset" etc.
+For example to refer to an index.html file in the "www" folder with an 
absolute path, you can use
+
+    "app-bundle:///index.html"
+
+This will navigate you to "file:///android_asset/www/index.html" on android 
and similarly for ios.
+
+##Custom Uri replacement schemes
+You can add your own url replacements to this plugin as well.
+
+###Api
+
+    appBundle.addAlias(string matchRegex, string replaceRegex, string 
replaceString, boolean redirect, function callback(succeeded){});
+    appBundle.clearAllAliases(function callback(succeeded){});
+
+*   matchRegex -> allows you to specify a regex that determines which url's 
are replaced.
+*   replaceRegex -> allows you to specify what part of the url's are replaced.
+*   replacerString -> what to replace the above match with
+*   redirect -> this affects top level browser navigation only (changing your 
browser's location)
+    Assume you redirect requests from http://mysite.com/ to 
file:///storage/www/
+    If you set this to true, the document.location after redirection would be 
"file:///storage/www/", if you set it to false it will be "http://mysite.com/";
+
+The algorithm operates as follows
+
+    currently loading 'url'
+    if(url matches matchRegex){
+        newUrl = url.replace(replaceRegex, replacerString)
+        if(this is topLevelRequest){
+            stopLoadingUrl()
+            loadUrl(newUrl)
+            return
+        } else {
+            url = newUrl
+        }
+    }
+    continue loading 'url'
+
+###Examples
+
+Map http requests to file
+
+    appBundle.addAlias("^http://mysite\.com/.*";, "^http://mysite\.com/";, 
"file://storage_card/", false, function(succeded){});
+
+Map http requests to another http location, or file to file
+
+    appBundle.addAlias("^http://mysite\.com/.*";, "^http://mysite\.com/";, 
"http:///mysiteproxy.com/";, false, function(succeded){});
+
+Map http requests to your bundle
+
+    appBundle.addAlias("^http://mysite\.com/.*";, "^http://mysite\.com/";, 
"app-bundle:///", false, function(succeded){});
+
+Map bundle requests to http or file. Note the usage of the "{BUNDLE_WWW}" 
param here. This is replaced by "file:///android_asset/" on android and 
similarly on ios.
+
+Why use "BUNDLE_WWW" and not "app-bundle:///"? "app-bundle:" uri's always 
point to your bundle and CANNOT be redirected. Also other parts of your program 
may make requests to to "file://android_assets/www" directly (which many 
plugins actually do). You would expect all requests to get redirected. The 
"BUNDLE_WWW" takes care of this.
+
+    appBundle.addAlias("^{BUNDLE_WWW}.*", "^{BUNDLE_WWW}", 
"http://mysite\.com";, false, function(succeded){});
+
+Apply recursive replacements. A request to http://mysite.com/blah should give 
http:///mysiteproxy2.com/blah as the recursive replacements apply the rules 
last to first.
+
+    appBundle.addAlias("^http://mysite\.com/.*";, "^http://mysite\.com/";, 
"{BUNDLE_WWW}", false, function(succeded){});
+    appBundle.addAlias("^{BUNDLE_WWW}.*", "^{BUNDLE_WWW}", 
"file://storage/www/", false, function(succeded){});
+    appBundle.addAlias("^http://mysite\.com/.*";, "^http://mysite\.com/";, 
"http:///mysiteproxy.com/";, false, function(succeded){});
+    appBundle.addAlias("^http:///mysiteproxy\.com/.*";, 
"^http:///mysiteproxy\.com/";, "http:///mysiteproxy2.com/";, false, 
function(succeded){});
+
+##Installation - Cordova  2.7 or later
+        cordova plugin add directory-of-the-AppBundle-plugin

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/appBundle.js
----------------------------------------------------------------------
diff --git a/AppBundle/appBundle.js b/AppBundle/appBundle.js
new file mode 100644
index 0000000..469ed7a
--- /dev/null
+++ b/AppBundle/appBundle.js
@@ -0,0 +1,23 @@
+var exec = cordova.require('cordova/exec');
+
+exports.addAlias = function(sourceUriMatchRegex, sourceUriReplaceRegex, 
replaceString, redirectToReplacedUrl, callback) {
+    var win = callback && function() {
+        callback(true);
+    };
+    var fail = callback && function(error) {
+        callback(false);
+        console.error("AppBundle error: " + error);
+    };
+    exec(win, fail, 'AppBundle', 'addAlias', [sourceUriMatchRegex, 
sourceUriReplaceRegex, replaceString, redirectToReplacedUrl]);
+};
+
+exports.clearAllAliases = function(callback){
+    var win = callback && function() {
+        callback(true);
+    };
+    var fail = callback && function(error) {
+        callback(false);
+        console.error("AppBundle error: " + error);
+    };
+    exec(win, fail, 'AppBundle', 'clearAllAliases', []);
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/plugin.xml
----------------------------------------------------------------------
diff --git a/AppBundle/plugin.xml b/AppBundle/plugin.xml
new file mode 100644
index 0000000..d1cb1c2
--- /dev/null
+++ b/AppBundle/plugin.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<plugin xmlns="http://phonegap.com/ns/plugins/1.0";
+    xmlns:android="http://schemas.android.com/apk/res/android";
+    id="AppBundle"
+    version="1.0.0">
+  <engines>
+    <engine name="cordova" version=">=2.6.0" />
+  </engines>
+
+  <name>App Bundle Url's</name>
+
+  <js-module src="appBundle.js" name="AppBundle">
+  </js-module>
+
+  <platform name="android">
+    <source-file src="src/android/AppBundle.java" 
target-dir="src/org/apache/cordova" />
+
+    <config-file target="res/xml/config.xml" parent="/widget">
+      <feature name="AppBundle">
+        <param name="android-package" value="org.apache.cordova.AppBundle"/>
+        <param name="onload" value="true"/>
+      </feature>
+      <access origin="app-bundle://*" />
+    </config-file>
+  </platform>
+
+  <platform name="ios">
+    <source-file src="src/ios/AppBundle.m" />
+    <header-file src="src/ios/AppBundle.h" />
+
+    <config-file target="config.xml" parent="/widget">
+      <feature name="AppBundle">
+        <param name="ios-package" value="AppBundle"/>
+        <param name="onload" value="true"/>
+      </feature>
+      <access origin="app-bundle://*" />
+    </config-file>
+  </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/src/android/AppBundle.java
----------------------------------------------------------------------
diff --git a/AppBundle/src/android/AppBundle.java 
b/AppBundle/src/android/AppBundle.java
new file mode 100644
index 0000000..6fa1f3a
--- /dev/null
+++ b/AppBundle/src/android/AppBundle.java
@@ -0,0 +1,214 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you 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 org.apache.cordova;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.util.Log;
+
+public class AppBundle extends CordovaPlugin {
+
+    /*
+    This plugin allows any uri's that are loaded to be replaced with modified 
uri's
+    These include uri's being loaded by the browser such as page navigation, 
script tags, as well as file uris being opened by file api etc.
+    There are 4 parameters here that affect the replacement.
+    The matchRegex, replaceRegex, replacerString, shouldRedirect
+
+    The algorithm operates as follows
+
+    currently loading 'url'
+    if(url matches matchRegex){
+        newUrl = url.replace(replaceRegex, replacerString)
+        if(this is topLevelRequest){
+            stopLoadingUrl()
+            loadUrl(newUrl)
+            return
+        } else {
+            url = newUrl
+        }
+    }
+    continue loading 'url'
+
+    There are some implementation details involved here such as the ability to 
distinguish between top level requests and other requests.
+    Please see the code for details regarding this.
+
+    There is an array of {matchRegex, replaceRegex, replacerString, 
shouldRedirect} stored referred to as the reroute map
+
+    "app-bundle:" uri's are absolute uri's that point to your bundle.
+    By default the rerouteMap contains the parameters required to redirect
+        app-bundle:///blah -> file:///android_asset/www/blah
+
+    The rerouteMap can be modified from javascript by calling the addAlias and 
clearAlias methods
+
+    Recursive replacements are supported by this plugin as well.
+    Consider the reroute map contains
+         1) http://mysite.com/ -> file:///android_asset/www/
+         2) file:///android_asset/www/blah -> file:///storage/www/
+         3) http://mysite.com/ -> http:///mysiteproxy.com/
+         4) http://mysiteproxy.com/ -> http:///mysiteproxy2.com/
+    A request to http://mysite.com/blah should give 
http:///mysiteproxy2.com/blah
+    Also note that the recursive replacements apply the rules last to first.
+
+    CAVEAT: Recursive replacements to app-bundle: should not occur
+    For example lets say the we have the rerouteParams set up as
+         1) app-bundle:/// -> file:///android_asset/www/
+         2) file:///android_asset/www/ -> file:///storage/www/
+    A request to app-bundle:///blah should give file:///android_asset/www/ NOT 
file:///storage/www/
+    This requirement is required by the definition of app-bundle: uris.
+    */
+    private static final String LOG_TAG = "AppBundle";
+    private static final String APP_BUNDLE_REPLACED = "AppBundleReplaced";
+
+    private static class RouteParams {
+        public String matchRegex;
+        public String replaceRegex;
+        public String replacer;
+        public boolean redirectToReplacedUrl;
+
+        public RouteParams(String matchRegex, String replaceRegex, String 
replacer, boolean redirectToReplacedUrl){
+            this.matchRegex = matchRegex;
+            this.replaceRegex = replaceRegex;
+            this.replacer = replacer;
+            this.redirectToReplacedUrl = redirectToReplacedUrl;
+        }
+    }
+
+    private final String BUNDLE_PATH = "file:///android_asset/www/";
+    // Have a default replacement path that redirects app-bundle: uri's to the 
bundle
+    private final RouteParams appBundleParams = new 
RouteParams("^app-bundle:///.*", "^app-bundle:///", BUNDLE_PATH, true);
+    private List<RouteParams> rerouteParams = new ArrayList<RouteParams>();
+
+    @Override
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+        super.initialize(cordova, webView);
+        resetMap();
+    }
+    private void resetMap(){
+        rerouteParams.clear();
+        rerouteParams.add(appBundleParams);
+    }
+
+    @Override
+    public boolean execute(String action, CordovaArgs args, final 
CallbackContext callbackContext) {
+        if ("addAlias".equals(action)) {
+            addAlias(args, callbackContext);
+            return true;
+        } else if ("clearAllAliases".equals(action)) {
+            clearAllAliases(args, callbackContext);
+            return true;
+        }
+        return false;
+    }
+
+    private void addAlias(CordovaArgs args, CallbackContext callbackContext) {
+        try {
+            String sourceUrlMatchRegex = 
args.getString(0).replace("{BUNDLE_WWW}", getRegex(BUNDLE_PATH));
+            String sourceUrlReplaceRegex = 
args.getString(1).replace("{BUNDLE_WWW}", getRegex(BUNDLE_PATH));
+            String replaceString = args.getString(2).replace("{BUNDLE_WWW}", 
BUNDLE_PATH);
+            boolean redirectToReplacedUrl = args.getBoolean(3);
+            if(replaceString.matches(sourceUrlMatchRegex)){
+                callbackContext.error("The replaceString cannot match the 
match regex. This would lead to recursive replacements.");
+            } else {
+                rerouteParams.add(new RouteParams(sourceUrlMatchRegex, 
sourceUrlReplaceRegex, replaceString, redirectToReplacedUrl));
+                callbackContext.success();
+            }
+        } catch(Exception e) {
+            callbackContext.error("Could not add alias");
+            Log.e(LOG_TAG, "Could not add alias");
+        }
+    }
+
+    private void clearAllAliases(CordovaArgs args, CallbackContext 
callbackContext) {
+        try {
+            resetMap();
+            callbackContext.success();
+        } catch(Exception e) {
+            callbackContext.error("Could not clear aliases");
+            Log.e(LOG_TAG, "Could not clear aliases");
+        }
+    }
+
+    private String getRegex(String string){
+        return Pattern.quote(string);
+    }
+
+    private RouteParams getChosenParams(String uri){
+        if(uri == null) {
+            return null;
+        } else {
+            uri = Uri.parse(uri).toString();
+        }
+        for(int i = rerouteParams.size() - 1; i >= 0; i--){
+            RouteParams param = rerouteParams.get(i);
+            if(uri.matches(param.matchRegex)){
+                return param;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Object onMessage(String id, Object data) {
+        // Look for top level navigation changes
+        if("onPageStarted".equals(id)){
+            String url = data == null? null: data.toString();
+            RouteParams params = getChosenParams(url);
+            // Check if we need to replace the url
+            if(params != null && params.redirectToReplacedUrl) {
+                Uri uri = Uri.parse(url);
+                // Check that the APP_BUNDLE_REPLACED query param doesn't 
exist.
+                // If it exists this means a previous app-bundle uri was 
rerouted to 'uri'. So we shouldn't reroute further.
+                if(uri.getQueryParameter(APP_BUNDLE_REPLACED) == null){
+                    String newPath = url.replaceAll(params.replaceRegex, 
params.replacer);
+                    //We need to special case app-bundle: uri's to make sure 
they aren't redirected when we load the modified url
+                    if(params.equals(appBundleParams)){
+                        // Throw in a APP_BUNDLE_REPLACED query parameter
+                        Builder builder = Uri.parse(newPath).buildUpon();
+                        builder.appendQueryParameter(APP_BUNDLE_REPLACED, 
"true");
+                        newPath = builder.build().toString();
+                    }
+                    webView.stopLoading();
+                    webView.loadUrl(newPath);
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Uri remapUri(Uri uri) {
+        String uriAsString = uri.toString();
+        RouteParams params = getChosenParams(uriAsString);
+        if (params != null){
+            // Just send data as we can't tell if this is top level or not.
+            // If this is a top level request, it will get trapped in the 
onPageStarted event handled above.
+            String newUri = uriAsString.replaceAll(params.replaceRegex, 
params.replacer);
+            return webView.getResourceApi().remapUri(Uri.parse(newUri));
+        } else {
+            return uri;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/src/ios/AppBundle.h
----------------------------------------------------------------------
diff --git a/AppBundle/src/ios/AppBundle.h b/AppBundle/src/ios/AppBundle.h
new file mode 100644
index 0000000..4d07487
--- /dev/null
+++ b/AppBundle/src/ios/AppBundle.h
@@ -0,0 +1,80 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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.
+ */
+#import <Foundation/Foundation.h>
+#import <Cordova/CDVPlugin.h>
+
+/*
+This plugin allows any uri's that are loaded to be replaced with modified uri's
+These include uri's being loaded by the browser such as page navigation, 
script tags.
+*******************TODO: shravanrn************************
+There is currently a DataResource mechanism bering reviewed for ios. This 
mechanism will provide a unified way to load and listen to all uri requets.
+Once this goes through, this plugin can be modified to be able listen to 
oading of other uris such file uris being opened by file api as well.
+*******************TODO: shravanrn************************
+
+There are 4 parameters here that affect the replacement.
+The matchRegex, replaceRegex, replacerString, shouldRedirect
+
+The algorithm operates as follows
+
+currently loading 'url'
+if(url matches matchRegex){
+    newUrl = url.replace(replaceRegex, replacerString)
+    if(this is topLevelRequest){
+        stopLoadingUrl()
+        loadUrl(newUrl)
+        return
+    } else {
+        url = newUrl
+    }
+}
+continue loading 'url'
+
+There are some implementation details involved here such as the ability to 
distinguish between top level requests and other requests.
+Please see the code for details regarding this.
+
+There is an array of {matchRegex, replaceRegex, replacerString, 
shouldRedirect} stored referred to as the reroute map
+
+"app-bundle:" uri's are absolute uri's that point to your bundle.
+By default the rerouteMap contains the parameters required to redirect
+    app-bundle:///blah -> file:///android_asset/www/blah
+
+The rerouteMap can be modified from javascript by calling the addAlias and 
clearAlias methods
+
+Recursive replacements are supported by this plugin as well.
+Consider the reroute map contains
+     1) http://mysite.com/ -> file:///android_asset/www/
+     2) file:///android_asset/www/blah -> file:///storage/www/
+     3) http://mysite.com/ -> http:///mysiteproxy.com/
+     4) http://mysiteproxy.com/ -> http:///mysiteproxy2.com/
+A request to http://mysite.com/blah should give http:///mysiteproxy2.com/blah
+Also note that the recursive replacements apply the rules last to first.
+
+*******************TODO: shravanrn************************
+Recursive replacements to app-bundle: should not occur
+For example lets say the we have the rerouteParams set up as
+     1) app-bundle:/// -> file:///android_asset/www/
+     2) file:///android_asset/www/ -> file:///storage/www/
+A request to app-bundle:///blah should give file:///android_asset/www/ NOT 
file:///storage/www/
+This requirement is required by the definition of app-bundle: uris.
+*******************TODO: shravanrn************************
+*/
+@interface AppBundle : CDVPlugin {}
+- (void)addAlias:(CDVInvokedUrlCommand*)command;
+- (void)clearAllAliases:(CDVInvokedUrlCommand*)command;
+@end

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/41e2b4d6/AppBundle/src/ios/AppBundle.m
----------------------------------------------------------------------
diff --git a/AppBundle/src/ios/AppBundle.m b/AppBundle/src/ios/AppBundle.m
new file mode 100644
index 0000000..6776b8c
--- /dev/null
+++ b/AppBundle/src/ios/AppBundle.m
@@ -0,0 +1,293 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you 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.
+ */
+#import "AppBundle.h"
+
+// Note: See header for details about this plugin's behaviour
+
+#pragma mark declare
+
+@interface AppBundle()
+{}
++ (NSString*) insertFileScheme:(NSString*)path;
+- (void) resetMap;
++ (NSString*) getRegex:(NSString*)string;
+@end
+
+@interface RouteParams : NSObject
+{
+}
+@property (nonatomic, readwrite, strong) NSRegularExpression* matchRegex;
+@property (nonatomic, readwrite, strong) NSRegularExpression* replaceRegex;
+@property (nonatomic, readwrite, strong) NSString* replacer;
+@property (nonatomic, readwrite, assign) BOOL redirectToReplacedUrl;
+- (RouteParams*)initWithMatchRegex:(NSRegularExpression*) matchRegex1 
ReplaceRegex:(NSRegularExpression*)replaceRegex1 Replacer:(NSString*) replacer1 
ShouldRedirect:(BOOL) redirectToReplacedUrl1;
+
+@end
+
+@interface AppBundleURLProtocol : NSURLProtocol
++ (RouteParams*) getChosenParams:(NSString*)uriString;
+- (void)issueNSURLResponseForFile:(NSString*)file;
+- (void)issueRedirectResponseForFile:(NSString*)file;
+- (void)issueNotFoundResponse;
+@end
+
+NSString* const appBundlePrefix = @"app-bundle:///";
+static NSString* pathPrefix;
+static UIWebView* uiwebview;
+static NSMutableArray* rerouteParams;
+static RouteParams* appBundleParams;
+
+#pragma mark AppBundle
+
+@implementation AppBundle
+
++ (NSString*) insertFileScheme:(NSString*)path
+{
+    NSRange wholeStringRange = NSMakeRange(0, [path length]);
+    NSRegularExpression* schemeRegex = [NSRegularExpression 
regularExpressionWithPattern:@"^[0-9a-zA-Z+.-]+:" options:0 error:nil];
+    NSRange range = [schemeRegex rangeOfFirstMatchInString:path options:0 
range:wholeStringRange];
+    if(NSEqualRanges(range, NSMakeRange(NSNotFound, 0)))
+    {
+        return [NSString stringWithFormat:@"file://%@", path];
+    }
+    return path;
+}
+
+- (void) resetMap
+{
+    NSError *error = NULL;
+    NSRegularExpression* bundleMatchRegex = [NSRegularExpression 
regularExpressionWithPattern:@"^app-bundle:///.*" options:0 error:&error];
+    NSRegularExpression* bundleReplaceRegex = [NSRegularExpression 
regularExpressionWithPattern:@"^app-bundle:///" options:0 error:&error];
+    appBundleParams = [[RouteParams alloc] initWithMatchRegex:bundleMatchRegex 
ReplaceRegex:bundleReplaceRegex Replacer:pathPrefix ShouldRedirect:YES];
+    rerouteParams = [[NSMutableArray alloc] init];
+    [rerouteParams addObject:appBundleParams];
+}
++ (NSString*) getRegex:(NSString*)string
+{
+    return [NSRegularExpression escapedPatternForString:string];
+}
+
+- (CDVPlugin*)initWithWebView:(UIWebView*)theWebView
+{
+    self = [super initWithWebView:theWebView];
+    uiwebview = theWebView;
+    if (self) {
+        [NSURLProtocol registerClass:[AppBundleURLProtocol class]];
+        pathPrefix = [[NSBundle mainBundle] pathForResource:@"cordova.js" 
ofType:@"" inDirectory:@"www"];
+        NSRange range = [pathPrefix rangeOfString:@"/www/"];
+        pathPrefix = [[pathPrefix substringToIndex:NSMaxRange(range)] 
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+        pathPrefix = [[AppBundle insertFileScheme:pathPrefix] 
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        [self resetMap];
+    }
+    return self;
+}
+- (void)addAlias:(CDVInvokedUrlCommand*)command
+{
+    CDVPluginResult* pluginResult = nil;
+    @try {
+        NSError* error;
+        NSString* sourceUrlMatchRegexString = [[command.arguments 
objectAtIndex:0] stringByReplacingOccurrencesOfString:@"{BUNDLE_WWW}" 
withString:[AppBundle getRegex:pathPrefix]];
+        NSRegularExpression* sourceUrlMatchRegex = [NSRegularExpression 
regularExpressionWithPattern:sourceUrlMatchRegexString options:0 error:&error];
+        if(error) {
+            pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Match regex is 
invalid"];
+            return;
+        }
+        NSString* sourceUrlReplaceRegexString = [[command.arguments 
objectAtIndex:1] stringByReplacingOccurrencesOfString:@"{BUNDLE_WWW}" 
withString:[AppBundle getRegex:pathPrefix]];
+        NSRegularExpression* sourceUrlReplaceRegex = [NSRegularExpression 
regularExpressionWithPattern:sourceUrlReplaceRegexString options:0 
error:&error];
+        if(error) {
+            pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Replace regex is 
invalid"];
+            return;
+        }
+        NSString* replaceString = [[command.arguments objectAtIndex:2] 
stringByReplacingOccurrencesOfString:@"{BUNDLE_WWW}" withString:pathPrefix];
+        replaceString = [replaceString 
stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+        BOOL redirectToReplacedUrl = [[command.arguments objectAtIndex:3] 
boolValue];
+
+        NSRange wholeStringRange = NSMakeRange(0, [replaceString length]);
+        NSRange range = [sourceUrlMatchRegex 
rangeOfFirstMatchInString:replaceString options:0 range:wholeStringRange];
+        if(!NSEqualRanges(range, NSMakeRange(NSNotFound, 0))) {
+            pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"The replaceString 
cannot match the match regex. This would lead to recursive replacements."];
+        } else {
+            RouteParams* params = [[RouteParams alloc] 
initWithMatchRegex:sourceUrlMatchRegex ReplaceRegex:sourceUrlReplaceRegex 
Replacer:replaceString ShouldRedirect:redirectToReplacedUrl];
+            [rerouteParams addObject:params];
+            pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_OK];
+        }
+    } @catch(NSException *exception) {
+        pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Could not add alias"];
+        NSLog(@"Could not add alias - %@", [exception debugDescription]);
+    } @finally {
+        [self.commandDelegate sendPluginResult:pluginResult 
callbackId:command.callbackId];
+    }
+}
+- (void)clearAllAliases:(CDVInvokedUrlCommand*)command
+{
+    CDVPluginResult* pluginResult = nil;
+    @try {
+        [self resetMap];
+        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+    }
+    @catch (NSException *exception) {
+        pluginResult = [CDVPluginResult 
resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Could not clear 
aliases"];
+        NSLog(@"Could not clear aliases - %@", [exception debugDescription]);
+    }
+    @finally {
+        [self.commandDelegate sendPluginResult:pluginResult 
callbackId:command.callbackId];
+    }
+}
+@end
+
+#pragma mark RouteParams
+
+@implementation RouteParams
+
+@synthesize matchRegex;
+@synthesize replaceRegex;
+@synthesize replacer;
+@synthesize redirectToReplacedUrl;
+
+- (RouteParams*)initWithMatchRegex:(NSRegularExpression*) matchRegex1 
ReplaceRegex:(NSRegularExpression*)replaceRegex1 Replacer:(NSString*) replacer1 
ShouldRedirect:(BOOL) redirectToReplacedUrl1
+{
+    self = [super init];
+    if(self)
+    {
+        [self setMatchRegex:matchRegex1];
+        [self setReplaceRegex:replaceRegex1];
+        [self setReplacer:replacer1];
+        [self setRedirectToReplacedUrl:redirectToReplacedUrl1];
+    }
+    return self;
+}
+
+@end
+
+#pragma mark AppBundleURLProtocol
+
+@implementation AppBundleURLProtocol
+
++ (RouteParams*) getChosenParams:(NSString*)uriString
+{
+    NSRange wholeStringRange = NSMakeRange(0, [uriString length]);
+    for(RouteParams* param in rerouteParams) {
+        NSRange rangeOfMatch = [param.matchRegex 
rangeOfFirstMatchInString:uriString options:0 range:wholeStringRange];
+        if (NSEqualRanges(rangeOfMatch, wholeStringRange)) {
+            return param;
+        }
+    }
+    return nil;
+}
+
++ (BOOL)canInitWithRequest:(NSURLRequest*)request
+{
+    NSURL* url = [request URL];
+    NSString* urlString = [url absoluteString];
+    RouteParams* params = [AppBundleURLProtocol getChosenParams:urlString];
+    if(params != nil) {
+        NSURL* mainUrl = [request mainDocumentURL];
+        NSString* mainUrlString = [mainUrl absoluteString];
+        if([mainUrlString isEqualToString:urlString]){
+            return params.redirectToReplacedUrl;
+        } else {
+            return YES;
+        }
+    }
+    return NO;
+}
+
++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request
+{
+    return request;
+}
+
+- (void)issueNotFoundResponse
+{
+    NSURL* url = [[self request] URL];
+    NSURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url 
statusCode:404 HTTPVersion:@"HTTP/1.1" headerFields:@{}];
+    [[self client] URLProtocol:self didReceiveResponse:response 
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+    [[self client] URLProtocolDidFinishLoading:self];
+}
+
+- (void)issueNSURLResponseForFile:(NSString*)file
+{
+    NSURL* uri = [[self request] URL];
+    NSString* uriString = [uri absoluteString];
+    RouteParams* params = [AppBundleURLProtocol getChosenParams:uriString];
+    if(params != nil) {
+        NSRange wholeStringRange = NSMakeRange(0, [uriString length]);
+        NSString* newUrlString = [params.replaceRegex 
stringByReplacingMatchesInString:uriString options:0 range:wholeStringRange 
withTemplate:params.replacer];
+        if([newUrlString hasPrefix:@"file://"]) {
+            NSURL *newUrl = [NSURL URLWithString:newUrlString];
+            NSString* path = [newUrl path];
+            FILE* fp = fopen([path UTF8String], "r");
+            if (fp) {
+                NSURLResponse *response = [[NSHTTPURLResponse alloc] 
initWithURL:uri statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:@{}];
+                [[self client] URLProtocol:self didReceiveResponse:response 
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+
+                char buf[32768];
+                size_t len;
+                while ((len = fread(buf,1,sizeof(buf),fp))) {
+                    [[self client] URLProtocol:self didLoadData:[NSData 
dataWithBytes:buf length:len]];
+                }
+                fclose(fp);
+
+                [[self client] URLProtocolDidFinishLoading:self];
+            } else {
+                [self issueNotFoundResponse];
+            }
+        } else {
+            NSLog(@"Cannot redirect to %@. You can only redirect to file: 
uri's", newUrlString);
+            [self issueNotFoundResponse];
+        }
+    }
+}
+
+- (void)issueRedirectResponseForFile:(NSString*)uriString
+{
+    RouteParams* params = [AppBundleURLProtocol getChosenParams:uriString];
+    if(params != nil && params.redirectToReplacedUrl)
+    {
+        if([uiwebview isLoading]) {
+            [uiwebview stopLoading];
+        }
+        NSRange wholeStringRange = NSMakeRange(0, [uriString length]);
+        NSString* newUrlString = [params.replaceRegex 
stringByReplacingMatchesInString:uriString options:0 range:wholeStringRange 
withTemplate:params.replacer];
+        NSURL *newUrl = [NSURL URLWithString:newUrlString];
+        NSURLRequest *request = [NSURLRequest requestWithURL:newUrl];
+        [uiwebview loadRequest:request];
+    }
+}
+
+- (void)startLoading
+{
+    NSURL *url = [[self request] URL];
+    NSString* urlString = [url absoluteString];
+    NSURL* mainUrl = [[self request] mainDocumentURL];
+    NSString* mainUrlString = [mainUrl absoluteString];
+    
+    if([mainUrlString isEqualToString:urlString]){
+        [self issueRedirectResponseForFile:urlString];
+    } else {
+        [self issueNSURLResponseForFile:urlString];
+    }
+}
+
+- (void)stopLoading
+{
+    // do any cleanup here
+}
+
+@end

Reply via email to