Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add libxml2 app link resolver #25

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Bolts.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Pod::Spec.new do |s|

ss.ios.source_files = 'Bolts/iOS/**/*.[hm]'
ss.ios.public_header_files = 'Bolts/iOS/*.h'
ss.ios.libraries = 'xml2'
ss.ios.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
ss.osx.source_files = ''
ss.watchos.source_files = ''
ss.tvos.source_files = ''
Expand Down
24 changes: 24 additions & 0 deletions Bolts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@
8E8C8EFB17F23E5F00E3F1C7 /* TaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E9C3D1C17DE9F6500427E62 /* TaskTests.m */; };
8E8C8F2917F241FF00E3F1C7 /* TaskTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E9C3D1C17DE9F6500427E62 /* TaskTests.m */; };
8EDDA63017E17DDC00655F8A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E9C3CEC17DE9DE000427E62 /* Foundation.framework */; };
D0A9104F1A86BF8500BF399F /* BFXMLAppLinkResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A9104D1A86BF8500BF399F /* BFXMLAppLinkResolver.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0A910541A86C83E00BF399F /* BFAppLinkResolvingPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A910531A86C83E00BF399F /* BFAppLinkResolvingPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
D0B41D791C3EF97A00510849 /* BFAppLinkResolving.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A910511A86C4AF00BF399F /* BFAppLinkResolving.m */; };
D0B41D7A1C3EF97B00510849 /* BFAppLinkResolving.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A910511A86C4AF00BF399F /* BFAppLinkResolving.m */; };
D0B41D7B1C3EF98200510849 /* BFXMLAppLinkResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A9104E1A86BF8500BF399F /* BFXMLAppLinkResolver.m */; };
D0B41D7C1C3EF98300510849 /* BFXMLAppLinkResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A9104E1A86BF8500BF399F /* BFXMLAppLinkResolver.m */; };
D0B41D7D1C3EFB6300510849 /* BFAppLinkResolvingPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A910531A86C83E00BF399F /* BFAppLinkResolvingPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
D0B41D7E1C3EFB6F00510849 /* BFXMLAppLinkResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = D0A9104D1A86BF8500BF399F /* BFXMLAppLinkResolver.h */; settings = {ATTRIBUTES = (Public, ); }; };
F5AFC9EC1BA752750076E927 /* BFTaskCompletionSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103FA5319900A84000BAE3F /* BFTaskCompletionSource.m */; };
F5AFC9ED1BA752750076E927 /* BFTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103FA5119900A84000BAE3F /* BFTask.m */; };
F5AFC9EE1BA752750076E927 /* Bolts.m in Sources */ = {isa = PBXBuildFile; fileRef = 8103FA5519900A84000BAE3F /* Bolts.m */; };
Expand Down Expand Up @@ -313,6 +321,10 @@
8E9C3D1C17DE9F6500427E62 /* TaskTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TaskTests.m; sourceTree = "<group>"; };
B242FAB819A567660097ECAE /* BFMeasurementEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BFMeasurementEvent.h; sourceTree = "<group>"; };
B242FAB919A567660097ECAE /* BFMeasurementEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BFMeasurementEvent.m; sourceTree = "<group>"; };
D0A9104D1A86BF8500BF399F /* BFXMLAppLinkResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BFXMLAppLinkResolver.h; sourceTree = "<group>"; };
D0A9104E1A86BF8500BF399F /* BFXMLAppLinkResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BFXMLAppLinkResolver.m; sourceTree = "<group>"; };
D0A910511A86C4AF00BF399F /* BFAppLinkResolving.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BFAppLinkResolving.m; sourceTree = "<group>"; };
D0A910531A86C83E00BF399F /* BFAppLinkResolvingPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BFAppLinkResolvingPrivate.h; sourceTree = "<group>"; };
F5AFCA021BA752750076E927 /* Bolts.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Bolts.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F5AFCA131BA752770076E927 /* BoltsTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "BoltsTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
F5AFCA151BA752AF0076E927 /* Bolts-tvOS.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Bolts-tvOS.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -469,8 +481,12 @@
8103FA5A19900A84000BAE3F /* BFAppLinkNavigation.h */,
8103FA5B19900A84000BAE3F /* BFAppLinkNavigation.m */,
8103FA5C19900A84000BAE3F /* BFAppLinkResolving.h */,
D0A910531A86C83E00BF399F /* BFAppLinkResolvingPrivate.h */,
D0A910511A86C4AF00BF399F /* BFAppLinkResolving.m */,
8103FA6619900A84000BAE3F /* BFWebViewAppLinkResolver.h */,
8103FA6719900A84000BAE3F /* BFWebViewAppLinkResolver.m */,
D0A9104D1A86BF8500BF399F /* BFXMLAppLinkResolver.h */,
D0A9104E1A86BF8500BF399F /* BFXMLAppLinkResolver.m */,
8103FA5D19900A84000BAE3F /* BFAppLinkReturnToRefererController.h */,
8103FA5E19900A84000BAE3F /* BFAppLinkReturnToRefererController.m */,
8103FA5F19900A84000BAE3F /* BFAppLinkReturnToRefererView.h */,
Expand Down Expand Up @@ -654,6 +670,7 @@
buildActionMask = 2147483647;
files = (
1D5D7DBA1BE3CE8200FD67C7 /* BFWebViewAppLinkResolver.h in Headers */,
D0A9104F1A86BF8500BF399F /* BFXMLAppLinkResolver.h in Headers */,
81CD062B1CEED28A00497F47 /* BFTask+Exceptions.h in Headers */,
81CF830D1D0B559800633946 /* BFAppLinkReturnToRefererView_Internal.h in Headers */,
81CF83111D0B559800633946 /* BFURL_Internal.h in Headers */,
Expand All @@ -673,6 +690,7 @@
1D5D7DCC1BE3CE8200FD67C7 /* Bolts.h in Headers */,
1D5D7DCD1BE3CE8200FD67C7 /* BFCancellationToken.h in Headers */,
1D5D7DCE1BE3CE8200FD67C7 /* BFAppLink.h in Headers */,
D0A910541A86C83E00BF399F /* BFAppLinkResolvingPrivate.h in Headers */,
1D5D7DCF1BE3CE8200FD67C7 /* BFAppLinkReturnToRefererController.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -741,6 +759,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
D0B41D7E1C3EFB6F00510849 /* BFXMLAppLinkResolver.h in Headers */,
81ED94311BE1481900795F05 /* BFWebViewAppLinkResolver.h in Headers */,
81CD062A1CEED28A00497F47 /* BFTask+Exceptions.h in Headers */,
81CF830C1D0B559800633946 /* BFAppLinkReturnToRefererView_Internal.h in Headers */,
Expand All @@ -754,6 +773,7 @@
81ED94201BE147CF00795F05 /* BFExecutor.h in Headers */,
81ED94381BE1481900795F05 /* BFAppLinkTarget.h in Headers */,
81ED94301BE1481900795F05 /* BFAppLinkResolving.h in Headers */,
D0B41D7D1C3EFB6300510849 /* BFAppLinkResolvingPrivate.h in Headers */,
81ED943E1BE1481900795F05 /* BFURL.h in Headers */,
81CF830E1D0B559800633946 /* BFMeasurementEvent_Internal.h in Headers */,
81ED94221BE147CF00795F05 /* BFTaskCompletionSource.h in Headers */,
Expand Down Expand Up @@ -1070,6 +1090,8 @@
1D5D7DB41BE3CE8200FD67C7 /* BFAppLink.m in Sources */,
1D5D7DB51BE3CE8200FD67C7 /* BFExecutor.m in Sources */,
1D5D7DB61BE3CE8200FD67C7 /* BFCancellationToken.m in Sources */,
D0B41D7B1C3EF98200510849 /* BFXMLAppLinkResolver.m in Sources */,
D0B41D791C3EF97A00510849 /* BFAppLinkResolving.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1147,6 +1169,8 @@
81ED942D1BE1481900795F05 /* BFAppLink.m in Sources */,
81ED94181BE147CF00795F05 /* BFExecutor.m in Sources */,
81ED94191BE147CF00795F05 /* BFCancellationToken.m in Sources */,
D0B41D7C1C3EF98300510849 /* BFXMLAppLinkResolver.m in Sources */,
D0B41D7A1C3EF97B00510849 /* BFAppLinkResolving.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
1 change: 1 addition & 0 deletions Bolts/Common/Bolts.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#import <Bolts/BFMeasurementEvent.h>
#import <Bolts/BFURL.h>
#import <Bolts/BFWebViewAppLinkResolver.h>
#import <Bolts/BFXMLAppLinkResolver.h>
#endif


Expand Down
3 changes: 1 addition & 2 deletions Bolts/iOS/BFAppLinkNavigation.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

#import <Foundation/Foundation.h>

#import <Bolts/BFAppLink.h>

/*!
The result of calling navigate on a BFAppLinkNavigation
*/
Expand All @@ -25,6 +23,7 @@ typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) {
};

@protocol BFAppLinkResolving;
@class BFAppLink;
@class BFTask;

/*!
Expand Down
184 changes: 184 additions & 0 deletions Bolts/iOS/BFAppLinkResolving.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

#import <UIKit/UIKit.h>

#import "BFAppLinkResolving.h"
#import "BFAppLinkResolvingPrivate.h"
#import "BFAppLink.h"
#import "BFAppLinkTarget.h"
#import "BFTask.h"
#import "BFTaskCompletionSource.h"

NSString *const BFAppLinkResolverPreferHeader = @"Prefer-Html-Meta-Tags";
NSString *const BFAppLinkResolverMetaTagPrefix = @"al";

NSString *const BFAppLinkResolverRedirectDataKey = @"data";
NSString *const BFAppLinkResolverRedirectResponseKey = @"response";

static NSString *const BFAppLinkResolverIOSURLKey = @"url";
static NSString *const BFAppLinkResolverIOSAppStoreIdKey = @"app_store_id";
static NSString *const BFAppLinkResolverIOSAppNameKey = @"app_name";
static NSString *const BFAppLinkResolverDictionaryValueKey = @"_value";
static NSString *const BFAppLinkResolverWebKey = @"web";
static NSString *const BFAppLinkResolverIOSKey = @"ios";
static NSString *const BFAppLinkResolverIPhoneKey = @"iphone";
static NSString *const BFAppLinkResolverIPadKey = @"ipad";
static NSString *const BFAppLinkResolverWebURLKey = @"url";
static NSString *const BFAppLinkResolverShouldFallbackKey = @"should_fallback";

NSDictionary *BFAppLinkResolverParseALData(NSArray *dataArray) {
NSMutableDictionary *al = [NSMutableDictionary dictionary];
for (NSDictionary *tag in dataArray) {
NSString *name = tag[@"property"];
if (![name isKindOfClass:[NSString class]]) {
continue;
}
NSArray *nameComponents = [name componentsSeparatedByString:@":"];
if (![nameComponents[0] isEqualToString:BFAppLinkResolverMetaTagPrefix]) {
continue;
}
NSMutableDictionary *root = al;
for (int i = 1; i < nameComponents.count; i++) {
NSMutableArray *children = root[nameComponents[i]];
if (!children) {
children = [NSMutableArray array];
root[nameComponents[i]] = children;
}
NSMutableDictionary *child = children.lastObject;
if (!child || i == nameComponents.count - 1) {
child = [NSMutableDictionary dictionary];
[children addObject:child];
}
root = child;
}
if (tag[@"content"]) {
root[BFAppLinkResolverDictionaryValueKey] = tag[@"content"];
}
}
return al;
}

BFAppLink *BFAppLinkResolverAppLinkFromALData(NSDictionary *appLinkDict, NSURL *destination) {
NSMutableArray *linkTargets = [NSMutableArray array];

NSArray *platformData = nil;
switch (UI_USER_INTERFACE_IDIOM()) {
case UIUserInterfaceIdiomPad:
platformData = @[ appLinkDict[BFAppLinkResolverIPadKey] ?: @{},
appLinkDict[BFAppLinkResolverIOSKey] ?: @{} ];
break;
case UIUserInterfaceIdiomPhone:
platformData = @[ appLinkDict[BFAppLinkResolverIPhoneKey] ?: @{},
appLinkDict[BFAppLinkResolverIOSKey] ?: @{} ];
break;
#ifdef __TVOS_9_0
case UIUserInterfaceIdiomTV:
#endif
#ifdef __IPHONE_9_3
case UIUserInterfaceIdiomCarPlay:
#endif
case UIUserInterfaceIdiomUnspecified:
default:
// Future-proofing. Other User Interface idioms should only hit ios.
platformData = @[ appLinkDict[BFAppLinkResolverIOSKey] ?: @{} ];
break;
}

for (NSArray *platformObjects in platformData) {
for (NSDictionary *platformDict in platformObjects) {
// The schema requires a single url/app store id/app name,
// but we could find multiple of them. We'll make a best effort
// to interpret this data.
NSArray *urls = platformDict[BFAppLinkResolverIOSURLKey];
NSArray *appStoreIds = platformDict[BFAppLinkResolverIOSAppStoreIdKey];
NSArray *appNames = platformDict[BFAppLinkResolverIOSAppNameKey];

NSUInteger maxCount = MAX(urls.count, MAX(appStoreIds.count, appNames.count));

for (NSUInteger i = 0; i < maxCount; i++) {
NSString *urlString = urls[i][BFAppLinkResolverDictionaryValueKey];
NSURL *url = urlString ? [NSURL URLWithString:urlString] : nil;
NSString *appStoreId = appStoreIds[i][BFAppLinkResolverDictionaryValueKey];
NSString *appName = appNames[i][BFAppLinkResolverDictionaryValueKey];
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:url
appStoreId:appStoreId
appName:appName];
[linkTargets addObject:target];
}
}
}

NSDictionary *webDict = appLinkDict[BFAppLinkResolverWebKey][0];
NSString *webUrlString = webDict[BFAppLinkResolverWebURLKey][0][BFAppLinkResolverDictionaryValueKey];
NSString *shouldFallbackString = webDict[BFAppLinkResolverShouldFallbackKey][0][BFAppLinkResolverDictionaryValueKey];

NSURL *webUrl = destination;

if (shouldFallbackString &&
[@[ @"no", @"false", @"0" ] containsObject:[shouldFallbackString lowercaseString]]) {
webUrl = nil;
}
if (webUrl && webUrlString) {
webUrl = [NSURL URLWithString:webUrlString];
}

return [BFAppLink appLinkWithSourceURL:destination
targets:linkTargets
webURL:webUrl];
}

BFTask *BFFollowRedirects(NSURL *url) {
// This task will be resolved with either the redirect NSURL
// or a dictionary with the response data to be returned.
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:BFAppLinkResolverMetaTagPrefix forHTTPHeaderField:BFAppLinkResolverPreferHeader];

void (^completion)(NSURLResponse *response, NSData *data, NSError *error) = ^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[tcs setError:error];
return;
}

if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;

// NSURLConnection usually follows redirects automatically, but the
// documentation is unclear what the default is. This helps it along.
if (httpResponse.statusCode >= 300 && httpResponse.statusCode < 400) {
NSString *redirectString = httpResponse.allHeaderFields[@"Location"];
NSURL *redirectURL = [NSURL URLWithString:redirectString];
[tcs setResult:redirectURL];
return;
}
}

[tcs setResult:@{ BFAppLinkResolverRedirectResponseKey : response, BFAppLinkResolverRedirectDataKey : data }];
};

#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completion(response, data, error);
}] resume];
#else
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:completion];
#endif

return [tcs.task continueWithSuccessBlock:^id(BFTask *task) {
// If we redirected, just keep recursing.
if ([task.result isKindOfClass:[NSURL class]]) {
return BFFollowRedirects(task.result);
}
return task;
}];
}

34 changes: 34 additions & 0 deletions Bolts/iOS/BFAppLinkResolvingPrivate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/

@class BFAppLink;

/*
Builds up a data structure filled with the app link data from the meta tags on a page.
The structure of this object is a dictionary where each key holds an array of app link
data dictionaries. Values are stored in a key called "_value".
*/
extern NSDictionary *BFAppLinkResolverParseALData(NSArray *dataArray);

/*
Converts app link data into a BFAppLink containing the targets relevant for this platform.
*/
extern BFAppLink *BFAppLinkResolverAppLinkFromALData(NSDictionary *appLinkDict, NSURL *destination);

/*
The returned task will be resolved with a dictionary containing the response data
*/
extern BFTask *BFFollowRedirects(NSURL *url);

extern NSString *const BFAppLinkResolverPreferHeader;
extern NSString *const BFAppLinkResolverMetaTagPrefix;

extern NSString *const BFAppLinkResolverRedirectDataKey;
extern NSString *const BFAppLinkResolverRedirectResponseKey;
Loading