You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ia...@apache.org on 2015/03/19 17:56:18 UTC

[29/50] [abbrv] ios commit: CB-8690 - Group files into folders in CordovaLib/Classes

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVViewController.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVViewController.m b/CordovaLib/Classes/Public/CDVViewController.m
new file mode 100644
index 0000000..6b40a84
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVViewController.m
@@ -0,0 +1,878 @@
+/*
+ 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 <objc/message.h>
+#import "CDV.h"
+#import "CDVPlugin+Private.h"
+#import "CDVUIWebViewDelegate.h"
+#import "CDVConfigParser.h"
+#import "CDVUserAgentUtil.h"
+#import <AVFoundation/AVFoundation.h>
+#import "NSDictionary+CordovaPreferences.h"
+#import "CDVHandleOpenURL.h"
+#import "CDVCommandDelegateImpl.h"
+
+#define degreesToRadian(x) (M_PI * (x) / 180.0)
+
+@interface CDVViewController () {
+    NSInteger _userAgentLockToken;
+}
+
+@property (nonatomic, readwrite, strong) NSXMLParser* configParser;
+@property (nonatomic, readwrite, strong) NSMutableDictionary* settings;
+@property (nonatomic, readwrite, strong) CDVWhitelist* whitelist;
+@property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects;
+@property (nonatomic, readwrite, strong) NSMutableArray* startupPluginNames;
+@property (nonatomic, readwrite, strong) NSDictionary* pluginsMap;
+@property (nonatomic, readwrite, strong) NSArray* supportedOrientations;
+@property (nonatomic, readwrite, assign) BOOL loadFromString;
+@property (nonatomic, readwrite, strong) id <CDVWebViewEngineProtocol> webViewEngine;
+
+@property (readwrite, assign) BOOL initialized;
+
+@property (atomic, strong) NSURL* openURL;
+
+@end
+
+@implementation CDVViewController
+
+@synthesize supportedOrientations;
+@synthesize pluginObjects, pluginsMap, whitelist, startupPluginNames;
+@synthesize configParser, settings, loadFromString;
+@synthesize wwwFolderName, startPage, initialized, openURL, baseUserAgent;
+@synthesize commandDelegate = _commandDelegate;
+@synthesize commandQueue = _commandQueue;
+@synthesize webViewEngine = _webViewEngine;
+@dynamic webView;
+
+- (void)__init
+{
+    if ((self != nil) && !self.initialized) {
+        _commandQueue = [[CDVCommandQueue alloc] initWithViewController:self];
+        _commandDelegate = [[CDVCommandDelegateImpl alloc] initWithViewController:self];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillTerminate:)
+                                                     name:UIApplicationWillTerminateNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillResignActive:)
+                                                     name:UIApplicationWillResignActiveNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidBecomeActive:)
+                                                     name:UIApplicationDidBecomeActiveNotification object:nil];
+
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppWillEnterForeground:)
+                                                     name:UIApplicationWillEnterForegroundNotification object:nil];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppDidEnterBackground:)
+                                                     name:UIApplicationDidEnterBackgroundNotification object:nil];
+
+        // read from UISupportedInterfaceOrientations (or UISupportedInterfaceOrientations~iPad, if its iPad) from -Info.plist
+        self.supportedOrientations = [self parseInterfaceOrientations:
+            [[[NSBundle mainBundle] infoDictionary] objectForKey:@"UISupportedInterfaceOrientations"]];
+
+        [self printVersion];
+        [self printMultitaskingInfo];
+        [self printPlatformVersionWarning];
+        self.initialized = YES;
+
+        // load config.xml settings
+        [self loadSettings];
+    }
+}
+
+- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
+{
+    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+    [self __init];
+    return self;
+}
+
+- (id)initWithCoder:(NSCoder*)aDecoder
+{
+    self = [super initWithCoder:aDecoder];
+    [self __init];
+    return self;
+}
+
+- (id)init
+{
+    self = [super init];
+    [self __init];
+    return self;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+    [super viewWillAppear:animated];
+}
+
+- (void)viewWillDisappear:(BOOL)animated
+{
+    [super viewWillDisappear:animated];
+}
+
+- (void)printVersion
+{
+    NSLog(@"Apache Cordova native platform version %@ is starting.", CDV_VERSION);
+}
+
+- (void)printPlatformVersionWarning
+{
+    if (!IsAtLeastiOSVersion(@"7.0")) {
+        NSLog(@"CRITICAL: For Cordova 4.0.0 and above, you will need to upgrade to at least iOS 7.0 or greater. Your current version of iOS is %@.",
+            [[UIDevice currentDevice] systemVersion]
+            );
+    }
+}
+
+- (void)printMultitaskingInfo
+{
+    UIDevice* device = [UIDevice currentDevice];
+    BOOL backgroundSupported = NO;
+
+    if ([device respondsToSelector:@selector(isMultitaskingSupported)]) {
+        backgroundSupported = device.multitaskingSupported;
+    }
+
+    NSNumber* exitsOnSuspend = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIApplicationExitsOnSuspend"];
+    if (exitsOnSuspend == nil) { // if it's missing, it should be NO (i.e. multi-tasking on by default)
+        exitsOnSuspend = [NSNumber numberWithBool:NO];
+    }
+
+    NSLog(@"Multi-tasking -> Device: %@, App: %@", (backgroundSupported ? @"YES" : @"NO"), (![exitsOnSuspend intValue]) ? @"YES" : @"NO");
+}
+
+- (BOOL)URLisAllowed:(NSURL*)url
+{
+    if (self.whitelist == nil) {
+        return YES;
+    }
+
+    return [self.whitelist URLIsAllowed:url];
+}
+
+- (void)loadSettings
+{
+    CDVConfigParser* delegate = [[CDVConfigParser alloc] init];
+
+    // read from config.xml in the app bundle
+    NSString* path = [[NSBundle mainBundle] pathForResource:@"config" ofType:@"xml"];
+
+    if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
+        NSAssert(NO, @"ERROR: config.xml does not exist. Please run cordova-ios/bin/cordova_plist_to_config_xml path/to/project.");
+        return;
+    }
+
+    NSURL* url = [NSURL fileURLWithPath:path];
+
+    self.configParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
+    if (self.configParser == nil) {
+        NSLog(@"Failed to initialize XML parser.");
+        return;
+    }
+    [self.configParser setDelegate:((id < NSXMLParserDelegate >)delegate)];
+    [self.configParser parse];
+
+    // Get the plugin dictionary, whitelist and settings from the delegate.
+    self.pluginsMap = delegate.pluginsDict;
+    self.startupPluginNames = delegate.startupPluginNames;
+    self.whitelist = [[CDVWhitelist alloc] initWithArray:delegate.whitelistHosts];
+    self.settings = delegate.settings;
+
+    // And the start folder/page.
+    self.wwwFolderName = @"www";
+    self.startPage = delegate.startPage;
+    if (self.startPage == nil) {
+        self.startPage = @"index.html";
+    }
+
+    // Initialize the plugin objects dict.
+    self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:20];
+}
+
+- (NSURL*)appUrl
+{
+    NSURL* appURL = nil;
+
+    if ([self.startPage rangeOfString:@"://"].location != NSNotFound) {
+        appURL = [NSURL URLWithString:self.startPage];
+    } else if ([self.wwwFolderName rangeOfString:@"://"].location != NSNotFound) {
+        appURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", self.wwwFolderName, self.startPage]];
+    } else {
+        // CB-3005 strip parameters from start page to check if page exists in resources
+        NSURL* startURL = [NSURL URLWithString:self.startPage];
+        NSString* startFilePath = [self.commandDelegate pathForResource:[startURL path]];
+
+        if (startFilePath == nil) {
+            self.loadFromString = YES;
+            appURL = nil;
+        } else {
+            appURL = [NSURL fileURLWithPath:startFilePath];
+            // CB-3005 Add on the query params or fragment.
+            NSString* startPageNoParentDirs = self.startPage;
+            NSRange r = [startPageNoParentDirs rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"] options:0];
+            if (r.location != NSNotFound) {
+                NSString* queryAndOrFragment = [self.startPage substringFromIndex:r.location];
+                appURL = [NSURL URLWithString:queryAndOrFragment relativeToURL:appURL];
+            }
+        }
+    }
+
+    return appURL;
+}
+
+- (NSURL*)errorUrl
+{
+    NSURL* errorURL = nil;
+
+    id setting = [self.settings cordovaSettingForKey:@"ErrorUrl"];
+
+    if (setting) {
+        NSString* errorUrlString = (NSString*)setting;
+        if ([errorUrlString rangeOfString:@"://"].location != NSNotFound) {
+            errorURL = [NSURL URLWithString:errorUrlString];
+        } else {
+            NSURL* url = [NSURL URLWithString:(NSString*)setting];
+            NSString* errorFilePath = [self.commandDelegate pathForResource:[url path]];
+            if (errorFilePath) {
+                errorURL = [NSURL fileURLWithPath:errorFilePath];
+            }
+        }
+    }
+
+    return errorURL;
+}
+
+- (UIView*)webView
+{
+    if (self.webViewEngine != nil) {
+        return self.webViewEngine.engineWebView;
+    }
+
+    return nil;
+}
+
+// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+
+    // // Fix the iOS 5.1 SECURITY_ERR bug (CB-347), this must be before the webView is instantiated ////
+
+    NSString* backupWebStorageType = @"cloud"; // default value
+
+    id backupWebStorage = [self.settings cordovaSettingForKey:@"BackupWebStorage"];
+    if ([backupWebStorage isKindOfClass:[NSString class]]) {
+        backupWebStorageType = backupWebStorage;
+    }
+    [self.settings setCordovaSetting:backupWebStorageType forKey:@"BackupWebStorage"];
+
+    [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType];
+
+    // // Instantiate the WebView ///////////////
+
+    if (!self.webView) {
+        [self createGapView];
+    }
+
+    // register this viewcontroller with the NSURLProtocol, only after the User-Agent is set
+    [CDVURLProtocol registerViewController:self];
+
+    // /////////////////
+
+    /*
+     * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup.
+        With minimum iOS 6/7 supported, only first clause applies.
+     */
+    if ([backupWebStorageType isEqualToString:@"local"]) {
+        NSString* localStorageFeatureName = @"localstorage";
+        if ([self.pluginsMap objectForKey:localStorageFeatureName]) { // plugin specified in config
+            [self.startupPluginNames addObject:localStorageFeatureName];
+        }
+    }
+
+    if ([self.startupPluginNames count] > 0) {
+        [CDVTimer start:@"TotalPluginStartup"];
+
+        for (NSString* pluginName in self.startupPluginNames) {
+            [CDVTimer start:pluginName];
+            [self getCommandInstance:pluginName];
+            [CDVTimer stop:pluginName];
+        }
+
+        [CDVTimer stop:@"TotalPluginStartup"];
+    }
+
+    // /////////////////
+    NSURL* appURL = [self appUrl];
+
+    [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
+        _userAgentLockToken = lockToken;
+        [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
+        if (appURL) {
+            NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
+            [self.webViewEngine loadRequest:appReq];
+        } else {
+            NSString* loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage];
+            NSLog(@"%@", loadErr);
+
+            NSURL* errorUrl = [self errorUrl];
+            if (errorUrl) {
+                errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
+                NSLog(@"%@", [errorUrl absoluteString]);
+                [self.webViewEngine loadRequest:[NSURLRequest requestWithURL:errorUrl]];
+            } else {
+                NSString* html = [NSString stringWithFormat:@"<html><body> %@ </body></html>", loadErr];
+                [self.webViewEngine loadHTMLString:html baseURL:nil];
+            }
+        }
+    }];
+}
+
+- (NSArray*)parseInterfaceOrientations:(NSArray*)orientations
+{
+    NSMutableArray* result = [[NSMutableArray alloc] init];
+
+    if (orientations != nil) {
+        NSEnumerator* enumerator = [orientations objectEnumerator];
+        NSString* orientationString;
+
+        while (orientationString = [enumerator nextObject]) {
+            if ([orientationString isEqualToString:@"UIInterfaceOrientationPortrait"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationPortraitUpsideDown"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortraitUpsideDown]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeLeft"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeLeft]];
+            } else if ([orientationString isEqualToString:@"UIInterfaceOrientationLandscapeRight"]) {
+                [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationLandscapeRight]];
+            }
+        }
+    }
+
+    // default
+    if ([result count] == 0) {
+        [result addObject:[NSNumber numberWithInt:UIInterfaceOrientationPortrait]];
+    }
+
+    return result;
+}
+
+- (NSInteger)mapIosOrientationToJsOrientation:(UIInterfaceOrientation)orientation
+{
+    switch (orientation) {
+        case UIInterfaceOrientationPortraitUpsideDown:
+            return 180;
+
+        case UIInterfaceOrientationLandscapeLeft:
+            return -90;
+
+        case UIInterfaceOrientationLandscapeRight:
+            return 90;
+
+        case UIInterfaceOrientationPortrait:
+            return 0;
+
+        default:
+            return 0;
+    }
+}
+
+- (void)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation completionHandler:(void (^)(BOOL))completionHandler
+{
+    // First, ask the webview via JS if it supports the new orientation
+    NSString* jsCall = [NSString stringWithFormat:
+        @"window.shouldRotateToOrientation && window.shouldRotateToOrientation(%ld);"
+        , (long)[self mapIosOrientationToJsOrientation:interfaceOrientation]];
+    __weak CDVViewController* weakSelf = self;
+
+    [_webViewEngine evaluateJavaScript:jsCall completionHandler:^(NSString* obj, NSError* error) {
+        if ([obj length] > 0) {
+            completionHandler([obj boolValue]);
+        } else {
+            // if js did not handle the new orientation (no return value), use values from the plist (via supportedOrientations)
+            completionHandler([weakSelf supportsOrientation:interfaceOrientation]);
+        }
+    }];
+}
+
+- (BOOL)shouldAutorotate
+{
+    return YES;
+}
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+    NSUInteger ret = 0;
+
+    if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortrait]) {
+        ret = ret | (1 << UIInterfaceOrientationPortrait);
+    }
+    if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationPortraitUpsideDown]) {
+        ret = ret | (1 << UIInterfaceOrientationPortraitUpsideDown);
+    }
+    if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]) {
+        ret = ret | (1 << UIInterfaceOrientationLandscapeRight);
+    }
+    if ([self shouldAutorotateToInterfaceOrientation:UIInterfaceOrientationLandscapeLeft]) {
+        ret = ret | (1 << UIInterfaceOrientationLandscapeLeft);
+    }
+
+    return ret;
+}
+
+- (BOOL)supportsOrientation:(UIInterfaceOrientation)orientation
+{
+    return [self.supportedOrientations containsObject:[NSNumber numberWithInt:orientation]];
+}
+
+- (UIView*)newCordovaViewWithFrame:(CGRect)bounds
+{
+    NSString* defaultWebViewEngineClass = @"CDVUIWebViewEngine";
+    NSString* webViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"];
+
+    if (!webViewEngineClass) {
+        webViewEngineClass = defaultWebViewEngineClass;
+    }
+
+    // Find webViewEngine
+    if (NSClassFromString(webViewEngineClass)) {
+        self.webViewEngine = [[NSClassFromString(webViewEngineClass) alloc] initWithFrame:bounds];
+        // if a webView engine returns nil (not supported by the current iOS version) or doesn't conform to the protocol, we use UIWebView
+        if (!self.webViewEngine || ![self.webViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)]) {
+            self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds];
+        }
+    } else {
+        self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds];
+    }
+
+    if ([self.webViewEngine isKindOfClass:[CDVPlugin class]]) {
+        [self registerPlugin:(CDVPlugin*)self.webViewEngine withClassName:webViewEngineClass];
+    }
+
+    return self.webViewEngine.engineWebView;
+}
+
+- (NSString*)userAgent
+{
+    if (_userAgent == nil) {
+        NSString* localBaseUserAgent;
+        if (self.baseUserAgent != nil) {
+            localBaseUserAgent = self.baseUserAgent;
+        } else {
+            localBaseUserAgent = [CDVUserAgentUtil originalUserAgent];
+        }
+        // Use our address as a unique number to append to the User-Agent.
+        _userAgent = [NSString stringWithFormat:@"%@ (%lld)", localBaseUserAgent, (long long)self];
+    }
+    return _userAgent;
+}
+
+- (void)createGapView
+{
+    CGRect webViewBounds = self.view.bounds;
+
+    webViewBounds.origin = self.view.bounds.origin;
+
+    UIView* view = [self newCordovaViewWithFrame:webViewBounds];
+
+    view.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
+    [self.view addSubview:view];
+    [self.view sendSubviewToBack:view];
+}
+
+- (void)didReceiveMemoryWarning
+{
+    // iterate through all the plugin objects, and call hasPendingOperation
+    // if at least one has a pending operation, we don't call [super didReceiveMemoryWarning]
+
+    NSEnumerator* enumerator = [self.pluginObjects objectEnumerator];
+    CDVPlugin* plugin;
+
+    BOOL doPurge = YES;
+
+    while ((plugin = [enumerator nextObject])) {
+        if (plugin.hasPendingOperation) {
+            NSLog(@"Plugin '%@' has a pending operation, memory purge is delayed for didReceiveMemoryWarning.", NSStringFromClass([plugin class]));
+            doPurge = NO;
+        }
+    }
+
+    if (doPurge) {
+        // Releases the view if it doesn't have a superview.
+        [super didReceiveMemoryWarning];
+    }
+
+    // Release any cached data, images, etc. that aren't in use.
+}
+
+- (void)viewDidUnload
+{
+    // Release any retained subviews of the main view.
+    // e.g. self.myOutlet = nil;
+
+    [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+    [super viewDidUnload];
+}
+
+#pragma mark UIWebViewDelegate
+
+/**
+ When web application loads Add stuff to the DOM, mainly the user-defined settings from the Settings.plist file, and
+ the device's data such as device ID, platform version, etc.
+ */
+- (void)webViewDidStartLoad:(UIWebView*)theWebView
+{
+    NSLog(@"Resetting plugins due to page load.");
+    [_commandQueue resetRequestId];
+    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginResetNotification object:self.webView]];
+}
+
+/**
+ Called when the webview finishes loading.  This stops the activity view.
+ */
+- (void)webViewDidFinishLoad:(UIWebView*)theWebView
+{
+    NSLog(@"Finished load of: %@", theWebView.request.URL);
+    // It's safe to release the lock even if this is just a sub-frame that's finished loading.
+    [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+    /*
+     * Hide the Top Activity THROBBER in the Battery Bar
+     */
+    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
+
+    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPageDidLoadNotification object:self.webView]];
+}
+
+- (void)webView:(UIWebView*)theWebView didFailLoadWithError:(NSError*)error
+{
+    [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+
+    NSString* message = [NSString stringWithFormat:@"Failed to load webpage with error: %@", [error localizedDescription]];
+    NSLog(@"%@", message);
+
+    NSURL* errorUrl = [self errorUrl];
+    if (errorUrl) {
+        errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [message stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
+        NSLog(@"%@", [errorUrl absoluteString]);
+        [theWebView loadRequest:[NSURLRequest requestWithURL:errorUrl]];
+    }
+}
+
+- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
+{
+    NSURL* url = [request URL];
+
+    /*
+     * Execute any commands queued with cordova.exec() on the JS side.
+     * The part of the URL after gap:// is irrelevant.
+     */
+    if ([[url scheme] isEqualToString:@"gap"]) {
+        [_commandQueue fetchCommandsFromJs];
+        // The delegate is called asynchronously in this case, so we don't have to use
+        // flushCommandQueueWithDelayedJs (setTimeout(0)) as we do with hash changes.
+        [_commandQueue executePending];
+        return NO;
+    }
+
+    if ([[url fragment] hasPrefix:@"%01"] || [[url fragment] hasPrefix:@"%02"]) {
+        // Delegate is called *immediately* for hash changes. This means that any
+        // calls to stringByEvaluatingJavascriptFromString will occur in the middle
+        // of an existing (paused) call stack. This doesn't cause errors, but may
+        // be unexpected to callers (exec callbacks will be called before exec() even
+        // returns). To avoid this, we do not do any synchronous JS evals by using
+        // flushCommandQueueWithDelayedJs.
+        NSString* inlineCommands = [[url fragment] substringFromIndex:3];
+        if ([inlineCommands length] == 0) {
+            // Reach in right away since the WebCore / Main thread are already synchronized.
+            [_commandQueue fetchCommandsFromJs];
+        } else {
+            inlineCommands = [inlineCommands stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+            [_commandQueue enqueueCommandBatch:inlineCommands];
+        }
+        // Switch these for minor performance improvements, and to really live on the wild side.
+        // Callbacks will occur in the middle of the location.hash = ... statement!
+        [(CDVCommandDelegateImpl*)_commandDelegate flushCommandQueueWithDelayedJs];
+        // [_commandQueue executePending];
+
+        // Although we return NO, the hash change does end up taking effect.
+        return NO;
+    }
+
+    /*
+     * Give plugins the chance to handle the url
+     */
+    for (NSString* pluginName in pluginObjects) {
+        CDVPlugin* plugin = [pluginObjects objectForKey:pluginName];
+        SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
+        if ([plugin respondsToSelector:selector]) {
+            if (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, request, navigationType) == YES) {
+                return NO;
+            }
+        }
+    }
+
+    /*
+     * If a URL is being loaded that's a file/http/https URL, just load it internally
+     */
+    if ([url isFileURL]) {
+        return YES;
+    }
+
+    /*
+     *    If we loaded the HTML from a string, we let the app handle it
+     */
+    else if (self.loadFromString == YES) {
+        self.loadFromString = NO;
+        return YES;
+    }
+
+    /*
+     * all tel: scheme urls we let the UIWebview handle it using the default behavior
+     */
+    else if ([[url scheme] isEqualToString:@"tel"]) {
+        return YES;
+    }
+
+    /*
+     * all about: scheme urls are not handled
+     */
+    else if ([[url scheme] isEqualToString:@"about"]) {
+        return NO;
+    }
+
+    /*
+     * all data: scheme urls are handled
+     */
+    else if ([[url scheme] isEqualToString:@"data"]) {
+        return YES;
+    }
+
+    /*
+     * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
+     */
+    else {
+        if ([self.whitelist schemeIsAllowed:[url scheme]]) {
+            return [self.whitelist URLIsAllowed:url];
+        } else {
+            if ([[UIApplication sharedApplication] canOpenURL:url]) {
+                [[UIApplication sharedApplication] openURL:url];
+            } else { // handle any custom schemes to plugins
+                [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
+            }
+        }
+
+        return NO;
+    }
+
+    return YES;
+}
+
+#pragma mark GapHelpers
+
+- (void)javascriptAlert:(NSString*)text
+{
+    NSString* jsString = [NSString stringWithFormat:@"alert('%@');", text];
+
+    [self.commandDelegate evalJs:jsString];
+}
+
++ (NSString*)applicationDocumentsDirectory
+{
+    NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
+    NSString* basePath = (([paths count] > 0) ? ([paths objectAtIndex:0]) : nil);
+
+    return basePath;
+}
+
+#pragma mark CordovaCommands
+
+- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className
+{
+    if ([plugin respondsToSelector:@selector(setViewController:)]) {
+        [plugin setViewController:self];
+    }
+
+    if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
+        [plugin setCommandDelegate:_commandDelegate];
+    }
+
+    [self.pluginObjects setObject:plugin forKey:className];
+    [plugin pluginInitialize];
+}
+
+- (void)registerPlugin:(CDVPlugin*)plugin withPluginName:(NSString*)pluginName
+{
+    if ([plugin respondsToSelector:@selector(setViewController:)]) {
+        [plugin setViewController:self];
+    }
+
+    if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
+        [plugin setCommandDelegate:_commandDelegate];
+    }
+
+    NSString* className = NSStringFromClass([plugin class]);
+    [self.pluginObjects setObject:plugin forKey:className];
+    [self.pluginsMap setValue:className forKey:[pluginName lowercaseString]];
+    [plugin pluginInitialize];
+}
+
+/**
+ Returns an instance of a CordovaCommand object, based on its name.  If one exists already, it is returned.
+ */
+- (id)getCommandInstance:(NSString*)pluginName
+{
+    // first, we try to find the pluginName in the pluginsMap
+    // (acts as a whitelist as well) if it does not exist, we return nil
+    // NOTE: plugin names are matched as lowercase to avoid problems - however, a
+    // possible issue is there can be duplicates possible if you had:
+    // "org.apache.cordova.Foo" and "org.apache.cordova.foo" - only the lower-cased entry will match
+    NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]];
+
+    if (className == nil) {
+        return nil;
+    }
+
+    id obj = [self.pluginObjects objectForKey:className];
+    if (!obj) {
+        obj = [[NSClassFromString(className)alloc] initWithWebViewEngine:_webViewEngine];
+
+        if (obj != nil) {
+            [self registerPlugin:obj withClassName:className];
+        } else {
+            NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName);
+        }
+    }
+    return obj;
+}
+
+#pragma mark -
+
+- (NSString*)appURLScheme
+{
+    NSString* URLScheme = nil;
+
+    NSArray* URLTypes = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleURLTypes"];
+
+    if (URLTypes != nil) {
+        NSDictionary* dict = [URLTypes objectAtIndex:0];
+        if (dict != nil) {
+            NSArray* URLSchemes = [dict objectForKey:@"CFBundleURLSchemes"];
+            if (URLSchemes != nil) {
+                URLScheme = [URLSchemes objectAtIndex:0];
+            }
+        }
+    }
+
+    return URLScheme;
+}
+
+/**
+ Returns the contents of the named plist bundle, loaded as a dictionary object
+ */
++ (NSDictionary*)getBundlePlist:(NSString*)plistName
+{
+    NSString* errorDesc = nil;
+    NSPropertyListFormat format;
+    NSString* plistPath = [[NSBundle mainBundle] pathForResource:plistName ofType:@"plist"];
+    NSData* plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
+    NSDictionary* temp = (NSDictionary*)[NSPropertyListSerialization
+        propertyListFromData:plistXML
+            mutabilityOption:NSPropertyListMutableContainersAndLeaves
+                      format:&format errorDescription:&errorDesc];
+
+    return temp;
+}
+
+#pragma mark -
+#pragma mark UIApplicationDelegate impl
+
+/*
+ This method lets your application know that it is about to be terminated and purged from memory entirely
+ */
+- (void)onAppWillTerminate:(NSNotification*)notification
+{
+    // empty the tmp directory
+    NSFileManager* fileMgr = [[NSFileManager alloc] init];
+    NSError* __autoreleasing err = nil;
+
+    // clear contents of NSTemporaryDirectory
+    NSString* tempDirectoryPath = NSTemporaryDirectory();
+    NSDirectoryEnumerator* directoryEnumerator = [fileMgr enumeratorAtPath:tempDirectoryPath];
+    NSString* fileName = nil;
+    BOOL result;
+
+    while ((fileName = [directoryEnumerator nextObject])) {
+        NSString* filePath = [tempDirectoryPath stringByAppendingPathComponent:fileName];
+        result = [fileMgr removeItemAtPath:filePath error:&err];
+        if (!result && err) {
+            NSLog(@"Failed to delete: %@ (error: %@)", filePath, err);
+        }
+    }
+}
+
+/*
+ This method is called to let your application know that it is about to move from the active to inactive state.
+ You should use this method to pause ongoing tasks, disable timer, ...
+ */
+- (void)onAppWillResignActive:(NSNotification*)notification
+{
+    // NSLog(@"%@",@"applicationWillResignActive");
+    [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resign');" scheduledOnRunLoop:NO];
+}
+
+/*
+ In iOS 4.0 and later, this method is called as part of the transition from the background to the inactive state.
+ You can use this method to undo many of the changes you made to your application upon entering the background.
+ invariably followed by applicationDidBecomeActive
+ */
+- (void)onAppWillEnterForeground:(NSNotification*)notification
+{
+    // NSLog(@"%@",@"applicationWillEnterForeground");
+    [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resume');"];
+}
+
+// This method is called to let your application know that it moved from the inactive to active state.
+- (void)onAppDidBecomeActive:(NSNotification*)notification
+{
+    // NSLog(@"%@",@"applicationDidBecomeActive");
+    [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('active');"];
+}
+
+/*
+ In iOS 4.0 and later, this method is called instead of the applicationWillTerminate: method
+ when the user quits an application that supports background execution.
+ */
+- (void)onAppDidEnterBackground:(NSNotification*)notification
+{
+    // NSLog(@"%@",@"applicationDidEnterBackground");
+    [self.commandDelegate evalJs:@"cordova.fireDocumentEvent('pause', null, true);" scheduledOnRunLoop:NO];
+}
+
+// ///////////////////////
+
+- (void)dealloc
+{
+    [CDVURLProtocol unregisterViewController:self];
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+    [CDVUserAgentUtil releaseLock:&_userAgentLockToken];
+    [_commandQueue dispose];
+    [[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h b/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h
new file mode 100644
index 0000000..18c1831
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVWebViewEngineProtocol.h
@@ -0,0 +1,41 @@
+/*
+ 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 <UIKit/UIKit.h>
+
+#define kCDVWebViewEngineScriptMessageHandlers @"kCDVWebViewEngineScriptMessageHandlers"
+#define kCDVWebViewEngineUIWebViewDelegate @"kCDVWebViewEngineUIWebViewDelegate"
+#define kCDVWebViewEngineWKNavigationDelegate @"kCDVWebViewEngineWKNavigationDelegate"
+#define kCDVWebViewEngineWKUIDelegate @"kCDVWebViewEngineWKUIDelegate"
+#define kCDVWebViewEngineWebViewPreferences @"kCDVWebViewEngineWebViewPreferences"
+
+@protocol CDVWebViewEngineProtocol <NSObject>
+
+@property (nonatomic, strong, readonly) UIView* engineWebView;
+
+- (id)loadRequest:(NSURLRequest*)request;
+- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL;
+- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler;
+
+- (NSURL*)URL;
+
+- (instancetype)initWithFrame:(CGRect)frame;
+- (void)updateWithInfo:(NSDictionary*)info;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVWhitelist.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVWhitelist.h b/CordovaLib/Classes/Public/CDVWhitelist.h
new file mode 100644
index 0000000..9165097
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVWhitelist.h
@@ -0,0 +1,34 @@
+/*
+ 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>
+
+extern NSString* const kCDVDefaultWhitelistRejectionString;
+
+@interface CDVWhitelist : NSObject
+
+@property (nonatomic, copy) NSString* whitelistRejectionFormatString;
+
+- (id)initWithArray:(NSArray*)array;
+- (BOOL)schemeIsAllowed:(NSString*)scheme;
+- (BOOL)URLIsAllowed:(NSURL*)url;
+- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure;
+- (NSString*)errorStringForURL:(NSURL*)url;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/CDVWhitelist.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/CDVWhitelist.m b/CordovaLib/Classes/Public/CDVWhitelist.m
new file mode 100644
index 0000000..8e3be75
--- /dev/null
+++ b/CordovaLib/Classes/Public/CDVWhitelist.m
@@ -0,0 +1,285 @@
+/*
+ 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 "CDVWhitelist.h"
+
+NSString* const kCDVDefaultWhitelistRejectionString = @"ERROR whitelist rejection: url='%@'";
+NSString* const kCDVDefaultSchemeName = @"cdv-default-scheme";
+
+@interface CDVWhitelistPattern : NSObject {
+    @private
+    NSRegularExpression* _scheme;
+    NSRegularExpression* _host;
+    NSNumber* _port;
+    NSRegularExpression* _path;
+}
+
++ (NSString*)regexFromPattern:(NSString*)pattern allowWildcards:(bool)allowWildcards;
+- (id)initWithScheme:(NSString*)scheme host:(NSString*)host port:(NSString*)port path:(NSString*)path;
+- (bool)matches:(NSURL*)url;
+
+@end
+
+@implementation CDVWhitelistPattern
+
++ (NSString*)regexFromPattern:(NSString*)pattern allowWildcards:(bool)allowWildcards
+{
+    NSString* regex = [NSRegularExpression escapedPatternForString:pattern];
+
+    if (allowWildcards) {
+        regex = [regex stringByReplacingOccurrencesOfString:@"\\*" withString:@".*"];
+
+        /* [NSURL path] has the peculiarity that a trailing slash at the end of a path
+         * will be omitted. This regex tweak compensates for that.
+         */
+        if ([regex hasSuffix:@"\\/.*"]) {
+            regex = [NSString stringWithFormat:@"%@(\\/.*)?", [regex substringToIndex:([regex length] - 4)]];
+        }
+    }
+    return [NSString stringWithFormat:@"%@$", regex];
+}
+
+- (id)initWithScheme:(NSString*)scheme host:(NSString*)host port:(NSString*)port path:(NSString*)path
+{
+    self = [super init];  // Potentially change "self"
+    if (self) {
+        if ((scheme == nil) || [scheme isEqualToString:@"*"]) {
+            _scheme = nil;
+        } else {
+            _scheme = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:scheme allowWildcards:NO] options:NSRegularExpressionCaseInsensitive error:nil];
+        }
+        if ([host isEqualToString:@"*"]) {
+            _host = nil;
+        } else if ([host hasPrefix:@"*."]) {
+            _host = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"([a-z0-9.-]*\\.)?%@", [CDVWhitelistPattern regexFromPattern:[host substringFromIndex:2] allowWildcards:false]] options:NSRegularExpressionCaseInsensitive error:nil];
+        } else {
+            _host = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:host allowWildcards:NO] options:NSRegularExpressionCaseInsensitive error:nil];
+        }
+        if ((port == nil) || [port isEqualToString:@"*"]) {
+            _port = nil;
+        } else {
+            _port = [[NSNumber alloc] initWithInteger:[port integerValue]];
+        }
+        if ((path == nil) || [path isEqualToString:@"/*"]) {
+            _path = nil;
+        } else {
+            _path = [NSRegularExpression regularExpressionWithPattern:[CDVWhitelistPattern regexFromPattern:path allowWildcards:YES] options:0 error:nil];
+        }
+    }
+    return self;
+}
+
+- (bool)matches:(NSURL*)url
+{
+    return (_scheme == nil || [_scheme numberOfMatchesInString:[url scheme] options:NSMatchingAnchored range:NSMakeRange(0, [[url scheme] length])]) &&
+           (_host == nil || [_host numberOfMatchesInString:[url host] options:NSMatchingAnchored range:NSMakeRange(0, [[url host] length])]) &&
+           (_port == nil || [[url port] isEqualToNumber:_port]) &&
+           (_path == nil || [_path numberOfMatchesInString:[url path] options:NSMatchingAnchored range:NSMakeRange(0, [[url path] length])])
+    ;
+}
+
+@end
+
+@interface CDVWhitelist ()
+
+@property (nonatomic, readwrite, strong) NSMutableArray* whitelist;
+@property (nonatomic, readwrite, strong) NSMutableSet* permittedSchemes;
+
+- (void)addWhiteListEntry:(NSString*)pattern;
+
+@end
+
+@implementation CDVWhitelist
+
+@synthesize whitelist, permittedSchemes, whitelistRejectionFormatString;
+
+- (id)initWithArray:(NSArray*)array
+{
+    self = [super init];
+    if (self) {
+        self.whitelist = [[NSMutableArray alloc] init];
+        self.permittedSchemes = [[NSMutableSet alloc] init];
+        self.whitelistRejectionFormatString = kCDVDefaultWhitelistRejectionString;
+
+        for (NSString* pattern in array) {
+            [self addWhiteListEntry:pattern];
+        }
+    }
+    return self;
+}
+
+- (BOOL)isIPv4Address:(NSString*)externalHost
+{
+    // an IPv4 address has 4 octets b.b.b.b where b is a number between 0 and 255.
+    // for our purposes, b can also be the wildcard character '*'
+
+    // we could use a regex to solve this problem but then I would have two problems
+    // anyways, this is much clearer and maintainable
+    NSArray* octets = [externalHost componentsSeparatedByString:@"."];
+    NSUInteger num_octets = [octets count];
+
+    // quick check
+    if (num_octets != 4) {
+        return NO;
+    }
+
+    // restrict number parsing to 0-255
+    NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
+    [numberFormatter setMinimum:[NSNumber numberWithUnsignedInteger:0]];
+    [numberFormatter setMaximum:[NSNumber numberWithUnsignedInteger:255]];
+
+    // iterate through each octet, and test for a number between 0-255 or if it equals '*'
+    for (NSUInteger i = 0; i < num_octets; ++i) {
+        NSString* octet = [octets objectAtIndex:i];
+
+        if ([octet isEqualToString:@"*"]) { // passes - check next octet
+            continue;
+        } else if ([numberFormatter numberFromString:octet] == nil) { // fails - not a number and not within our range, return
+            return NO;
+        }
+    }
+
+    return YES;
+}
+
+- (void)addWhiteListEntry:(NSString*)origin
+{
+    if (self.whitelist == nil) {
+        return;
+    }
+
+    if ([origin isEqualToString:@"*"]) {
+        NSLog(@"Unlimited access to network resources");
+        self.whitelist = nil;
+        self.permittedSchemes = nil;
+    } else { // specific access
+        NSRegularExpression* parts = [NSRegularExpression regularExpressionWithPattern:@"^((\\*|[A-Za-z-]+)://)?(((\\*\\.)?[^*/:]+)|\\*)?(:(\\d+))?(/.*)?" options:0 error:nil];
+        NSTextCheckingResult* m = [parts firstMatchInString:origin options:NSMatchingAnchored range:NSMakeRange(0, [origin length])];
+        if (m != nil) {
+            NSRange r;
+            NSString* scheme = nil;
+            r = [m rangeAtIndex:2];
+            if (r.location != NSNotFound) {
+                scheme = [origin substringWithRange:r];
+            }
+
+            NSString* host = nil;
+            r = [m rangeAtIndex:3];
+            if (r.location != NSNotFound) {
+                host = [origin substringWithRange:r];
+            }
+
+            // Special case for two urls which are allowed to have empty hosts
+            if (([scheme isEqualToString:@"file"] || [scheme isEqualToString:@"content"]) && (host == nil)) {
+                host = @"*";
+            }
+
+            NSString* port = nil;
+            r = [m rangeAtIndex:7];
+            if (r.location != NSNotFound) {
+                port = [origin substringWithRange:r];
+            }
+
+            NSString* path = nil;
+            r = [m rangeAtIndex:8];
+            if (r.location != NSNotFound) {
+                path = [origin substringWithRange:r];
+            }
+
+            if (scheme == nil) {
+                // XXX making it stupid friendly for people who forget to include protocol/SSL
+                [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:@"http" host:host port:port path:path]];
+                [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:@"https" host:host port:port path:path]];
+            } else {
+                [self.whitelist addObject:[[CDVWhitelistPattern alloc] initWithScheme:scheme host:host port:port path:path]];
+            }
+
+            if (self.permittedSchemes != nil) {
+                if ([scheme isEqualToString:@"*"]) {
+                    self.permittedSchemes = nil;
+                } else if (scheme != nil) {
+                    [self.permittedSchemes addObject:scheme];
+                }
+            }
+        }
+    }
+}
+
+- (BOOL)schemeIsAllowed:(NSString*)scheme
+{
+    if ([scheme isEqualToString:@"http"] ||
+        [scheme isEqualToString:@"https"] ||
+        [scheme isEqualToString:@"ftp"] ||
+        [scheme isEqualToString:@"ftps"]) {
+        return YES;
+    }
+
+    return (self.permittedSchemes == nil) || [self.permittedSchemes containsObject:scheme];
+}
+
+- (BOOL)URLIsAllowed:(NSURL*)url
+{
+    return [self URLIsAllowed:url logFailure:YES];
+}
+
+- (BOOL)URLIsAllowed:(NSURL*)url logFailure:(BOOL)logFailure
+{
+    // Shortcut acceptance: Are all urls whitelisted ("*" in whitelist)?
+    if (whitelist == nil) {
+        return YES;
+    }
+
+    // Shortcut rejection: Check that the scheme is supported
+    NSString* scheme = [[url scheme] lowercaseString];
+    if (![self schemeIsAllowed:scheme]) {
+        if (logFailure) {
+            NSLog(@"%@", [self errorStringForURL:url]);
+        }
+        return NO;
+    }
+
+    // http[s] and ftp[s] should also validate against the common set in the kCDVDefaultSchemeName list
+    if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"ftp"] || [scheme isEqualToString:@"ftps"]) {
+        NSURL* newUrl = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@%@", kCDVDefaultSchemeName, [url host], [[url path] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
+        // If it is allowed, we are done.  If not, continue to check for the actual scheme-specific list
+        if ([self URLIsAllowed:newUrl logFailure:NO]) {
+            return YES;
+        }
+    }
+
+    // Check the url against patterns in the whitelist
+    for (CDVWhitelistPattern* p in self.whitelist) {
+        if ([p matches:url]) {
+            return YES;
+        }
+    }
+
+    if (logFailure) {
+        NSLog(@"%@", [self errorStringForURL:url]);
+    }
+    // if we got here, the url host is not in the white-list, do nothing
+    return NO;
+}
+
+- (NSString*)errorStringForURL:(NSURL*)url
+{
+    return [NSString stringWithFormat:self.whitelistRejectionFormatString, [url absoluteString]];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSData+Base64.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSData+Base64.h b/CordovaLib/Classes/Public/NSData+Base64.h
new file mode 100644
index 0000000..02d960c
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSData+Base64.h
@@ -0,0 +1,43 @@
+//
+//  NSData+Base64.h
+//  base64
+//
+//  Created by Matt Gallagher on 2009/06/03.
+//  Copyright 2009 Matt Gallagher. All rights reserved.
+//
+//  This software is provided 'as-is', without any express or implied
+//  warranty. In no event will the authors be held liable for any damages
+//  arising from the use of this software. Permission is granted to anyone to
+//  use this software for any purpose, including commercial applications, and to
+//  alter it and redistribute it freely, subject to the following restrictions:
+//
+//  1. The origin of this software must not be misrepresented; you must not
+//     claim that you wrote the original software. If you use this software
+//     in a product, an acknowledgment in the product documentation would be
+//     appreciated but is not required.
+//  2. Altered source versions must be plainly marked as such, and must not be
+//     misrepresented as being the original software.
+//  3. This notice may not be removed or altered from any source
+//     distribution.
+//
+
+#import <Foundation/Foundation.h>
+#import "CDVAvailabilityDeprecated.h"
+
+void *CDVNewBase64Decode(
+    const char* inputBuffer,
+    size_t    length,
+    size_t    * outputLength);
+
+char *CDVNewBase64Encode(
+    const void* inputBuffer,
+    size_t    length,
+    bool      separateLines,
+    size_t    * outputLength);
+
+@interface NSData (CDVBase64)
+
++ (NSData*)cdv_dataFromBase64String:(NSString*)aString;
+- (NSString*)cdv_base64EncodedString;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSData+Base64.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSData+Base64.m b/CordovaLib/Classes/Public/NSData+Base64.m
new file mode 100644
index 0000000..43560f0
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSData+Base64.m
@@ -0,0 +1,298 @@
+//
+//  NSData+Base64.m
+//  base64
+//
+//  Created by Matt Gallagher on 2009/06/03.
+//  Copyright 2009 Matt Gallagher. All rights reserved.
+//
+//  This software is provided 'as-is', without any express or implied
+//  warranty. In no event will the authors be held liable for any damages
+//  arising from the use of this software. Permission is granted to anyone to
+//  use this software for any purpose, including commercial applications, and to
+//  alter it and redistribute it freely, subject to the following restrictions:
+//
+//  1. The origin of this software must not be misrepresented; you must not
+//     claim that you wrote the original software. If you use this software
+//     in a product, an acknowledgment in the product documentation would be
+//     appreciated but is not required.
+//  2. Altered source versions must be plainly marked as such, and must not be
+//     misrepresented as being the original software.
+//  3. This notice may not be removed or altered from any source
+//     distribution.
+//
+
+#import "NSData+Base64.h"
+
+//
+// Mapping from 6 bit pattern to ASCII character.
+//
+static unsigned char base64EncodeLookup[65] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+//
+// Definition for "masked-out" areas of the base64DecodeLookup mapping
+//
+#define xx 65
+
+//
+// Mapping from ASCII character to 6 bit pattern.
+//
+static unsigned char base64DecodeLookup[256] =
+{
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, 62, xx, xx, xx, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, xx, xx, xx, xx, xx, xx,
+    xx, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, xx, xx, xx, xx, xx,
+    xx, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+    xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx,
+};
+
+//
+// Fundamental sizes of the binary and base64 encode/decode units in bytes
+//
+#define BINARY_UNIT_SIZE 3
+#define BASE64_UNIT_SIZE 4
+
+//
+// NewBase64Decode
+//
+// Decodes the base64 ASCII string in the inputBuffer to a newly malloced
+// output buffer.
+//
+//  inputBuffer - the source ASCII string for the decode
+//	length - the length of the string or -1 (to specify strlen should be used)
+//	outputLength - if not-NULL, on output will contain the decoded length
+//
+// returns the decoded buffer. Must be free'd by caller. Length is given by
+//	outputLength.
+//
+void *CDVNewBase64Decode(
+    const char* inputBuffer,
+    size_t    length,
+    size_t    * outputLength)
+{
+    if (length == -1) {
+        length = strlen(inputBuffer);
+    }
+
+    size_t outputBufferSize =
+        ((length + BASE64_UNIT_SIZE - 1) / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE;
+    unsigned char* outputBuffer = (unsigned char*)malloc(outputBufferSize);
+
+    size_t i = 0;
+    size_t j = 0;
+
+    while (i < length) {
+        //
+        // Accumulate 4 valid characters (ignore everything else)
+        //
+        unsigned char accumulated[BASE64_UNIT_SIZE];
+        size_t accumulateIndex = 0;
+
+        while (i < length) {
+            unsigned char decode = base64DecodeLookup[inputBuffer[i++]];
+            if (decode != xx) {
+                accumulated[accumulateIndex] = decode;
+                accumulateIndex++;
+
+                if (accumulateIndex == BASE64_UNIT_SIZE) {
+                    break;
+                }
+            }
+        }
+
+        //
+        // Store the 6 bits from each of the 4 characters as 3 bytes
+        //
+        // (Uses improved bounds checking suggested by Alexandre Colucci)
+        //
+        if (accumulateIndex >= 2) {
+            outputBuffer[j] = (accumulated[0] << 2) | (accumulated[1] >> 4);
+        }
+        if (accumulateIndex >= 3) {
+            outputBuffer[j + 1] = (accumulated[1] << 4) | (accumulated[2] >> 2);
+        }
+        if (accumulateIndex >= 4) {
+            outputBuffer[j + 2] = (accumulated[2] << 6) | accumulated[3];
+        }
+        j += accumulateIndex - 1;
+    }
+
+    if (outputLength) {
+        *outputLength = j;
+    }
+    return outputBuffer;
+}
+
+//
+// NewBase64Encode
+//
+// Encodes the arbitrary data in the inputBuffer as base64 into a newly malloced
+// output buffer.
+//
+//  inputBuffer - the source data for the encode
+//	length - the length of the input in bytes
+//  separateLines - if zero, no CR/LF characters will be added. Otherwise
+//		a CR/LF pair will be added every 64 encoded chars.
+//	outputLength - if not-NULL, on output will contain the encoded length
+//		(not including terminating 0 char)
+//
+// returns the encoded buffer. Must be free'd by caller. Length is given by
+//	outputLength.
+//
+char *CDVNewBase64Encode(
+    const void* buffer,
+    size_t    length,
+    bool      separateLines,
+    size_t    * outputLength)
+{
+    const unsigned char* inputBuffer = (const unsigned char*)buffer;
+
+#define MAX_NUM_PADDING_CHARS 2
+#define OUTPUT_LINE_LENGTH 64
+#define INPUT_LINE_LENGTH ((OUTPUT_LINE_LENGTH / BASE64_UNIT_SIZE) * BINARY_UNIT_SIZE)
+#define CR_LF_SIZE 2
+
+    //
+    // Byte accurate calculation of final buffer size
+    //
+    size_t outputBufferSize =
+        ((length / BINARY_UNIT_SIZE)
+        + ((length % BINARY_UNIT_SIZE) ? 1 : 0))
+        * BASE64_UNIT_SIZE;
+    if (separateLines) {
+        outputBufferSize +=
+            (outputBufferSize / OUTPUT_LINE_LENGTH) * CR_LF_SIZE;
+    }
+
+    //
+    // Include space for a terminating zero
+    //
+    outputBufferSize += 1;
+
+    //
+    // Allocate the output buffer
+    //
+    char* outputBuffer = (char*)malloc(outputBufferSize);
+    if (!outputBuffer) {
+        return NULL;
+    }
+
+    size_t i = 0;
+    size_t j = 0;
+    const size_t lineLength = separateLines ? INPUT_LINE_LENGTH : length;
+    size_t lineEnd = lineLength;
+
+    while (true) {
+        if (lineEnd > length) {
+            lineEnd = length;
+        }
+
+        for (; i + BINARY_UNIT_SIZE - 1 < lineEnd; i += BINARY_UNIT_SIZE) {
+            //
+            // Inner loop: turn 48 bytes into 64 base64 characters
+            //
+            outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
+            outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4)
+                | ((inputBuffer[i + 1] & 0xF0) >> 4)];
+            outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i + 1] & 0x0F) << 2)
+                | ((inputBuffer[i + 2] & 0xC0) >> 6)];
+            outputBuffer[j++] = base64EncodeLookup[inputBuffer[i + 2] & 0x3F];
+        }
+
+        if (lineEnd == length) {
+            break;
+        }
+
+        //
+        // Add the newline
+        //
+        // outputBuffer[j++] = '\r';
+        // outputBuffer[j++] = '\n';
+        lineEnd += lineLength;
+    }
+
+    if (i + 1 < length) {
+        //
+        // Handle the single '=' case
+        //
+        outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
+        outputBuffer[j++] = base64EncodeLookup[((inputBuffer[i] & 0x03) << 4)
+            | ((inputBuffer[i + 1] & 0xF0) >> 4)];
+        outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i + 1] & 0x0F) << 2];
+        outputBuffer[j++] = '=';
+    } else if (i < length) {
+        //
+        // Handle the double '=' case
+        //
+        outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0xFC) >> 2];
+        outputBuffer[j++] = base64EncodeLookup[(inputBuffer[i] & 0x03) << 4];
+        outputBuffer[j++] = '=';
+        outputBuffer[j++] = '=';
+    }
+    outputBuffer[j] = 0;
+
+    //
+    // Set the output length and return the buffer
+    //
+    if (outputLength) {
+        *outputLength = j;
+    }
+    return outputBuffer;
+}
+
+@implementation NSData (CDVBase64)
+
+//
+// dataFromBase64String:
+//
+// Creates an NSData object containing the base64 decoded representation of
+// the base64 string 'aString'
+//
+// Parameters:
+//    aString - the base64 string to decode
+//
+// returns the autoreleased NSData representation of the base64 string
+//
++ (NSData*)cdv_dataFromBase64String:(NSString*)aString
+{
+    size_t outputLength = 0;
+    void* outputBuffer = CDVNewBase64Decode([aString UTF8String], [aString length], &outputLength);
+
+    return [NSData dataWithBytesNoCopy:outputBuffer length:outputLength freeWhenDone:YES];
+}
+
+//
+// base64EncodedString
+//
+// Creates an NSString object that contains the base 64 encoding of the
+// receiver's data. Lines are broken at 64 characters long.
+//
+// returns an autoreleased NSString being the base 64 representation of the
+//	receiver.
+//
+- (NSString*)cdv_base64EncodedString
+{
+    size_t outputLength = 0;
+    char* outputBuffer =
+        CDVNewBase64Encode([self bytes], [self length], true, &outputLength);
+
+    NSString* result = [[NSString alloc] initWithBytesNoCopy:outputBuffer
+                                                      length:outputLength
+                                                    encoding:NSASCIIStringEncoding
+                                                freeWhenDone:YES];
+
+    return result;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h
new file mode 100644
index 0000000..9be2be2
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.h
@@ -0,0 +1,35 @@
+/*
+ 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 <UIKit/UIKit.h>
+
+@interface NSDictionary (CordovaPreferences)
+
+- (id)cordovaSettingForKey:(NSString*)key;
+- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue;
+- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue;
+
+@end
+
+@interface NSMutableDictionary (CordovaPreferences)
+
+- (void)setCordovaSetting:(id)value forKey:(NSString*)key;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m
new file mode 100644
index 0000000..dcac40f
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m
@@ -0,0 +1,63 @@
+/*
+ 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 "NSDictionary+CordovaPreferences.h"
+#import <Foundation/Foundation.h>
+
+@implementation NSDictionary (CordovaPreferences)
+
+- (id)cordovaSettingForKey:(NSString*)key
+{
+    return [self objectForKey:[key lowercaseString]];
+}
+
+- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue
+{
+    BOOL value = defaultValue;
+    id prefObj = [self cordovaSettingForKey:key];
+
+    if (prefObj != nil) {
+        value = [(NSNumber*)prefObj boolValue];
+    }
+
+    return value;
+}
+
+- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue
+{
+    CGFloat value = defaultValue;
+    id prefObj = [self cordovaSettingForKey:key];
+
+    if (prefObj != nil) {
+        value = [prefObj floatValue];
+    }
+
+    return value;
+}
+
+@end
+
+@implementation NSMutableDictionary (CordovaPreferences)
+
+- (void)setCordovaSetting:(id)value forKey:(NSString*)key
+{
+    [self setObject:value forKey:[key lowercaseString]];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h b/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h
new file mode 100644
index 0000000..79e6516
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.h
@@ -0,0 +1,29 @@
+/*
+ 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>
+
+@interface NSMutableArray (QueueAdditions)
+
+- (id)cdv_pop;
+- (id)cdv_queueHead;
+- (id)cdv_dequeue;
+- (void)cdv_enqueue:(id)obj;
+
+@end

http://git-wip-us.apache.org/repos/asf/cordova-ios/blob/d58fa4aa/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m
----------------------------------------------------------------------
diff --git a/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m b/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m
new file mode 100644
index 0000000..2b3acdc
--- /dev/null
+++ b/CordovaLib/Classes/Public/NSMutableArray+QueueAdditions.m
@@ -0,0 +1,58 @@
+/*
+ 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 "NSMutableArray+QueueAdditions.h"
+
+@implementation NSMutableArray (QueueAdditions)
+
+- (id)cdv_queueHead
+{
+    if ([self count] == 0) {
+        return nil;
+    }
+
+    return [self objectAtIndex:0];
+}
+
+- (__autoreleasing id)cdv_dequeue
+{
+    if ([self count] == 0) {
+        return nil;
+    }
+
+    id head = [self objectAtIndex:0];
+    if (head != nil) {
+        // [[head retain] autorelease]; ARC - the __autoreleasing on the return value should so the same thing
+        [self removeObjectAtIndex:0];
+    }
+
+    return head;
+}
+
+- (id)cdv_pop
+{
+    return [self cdv_dequeue];
+}
+
+- (void)cdv_enqueue:(id)object
+{
+    [self addObject:object];
+}
+
+@end


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org