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
