diff --git a/Example/DynamicLinks/App/iOS/DL-Info.plist b/Example/DynamicLinks/App/iOS/DL-Info.plist
index fc26896d71d..87945bc6090 100644
--- a/Example/DynamicLinks/App/iOS/DL-Info.plist
+++ b/Example/DynamicLinks/App/iOS/DL-Info.plist
@@ -50,5 +50,11 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ FirebaseDynamicLinksCustomDomains
+
+ https://google.com
+ https://google.com/one/
+ https://a.firebase.com/mypath
+
diff --git a/Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist b/Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist
index ee3a448f005..d1df0bfa0cb 100644
--- a/Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist
+++ b/Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist
@@ -2,6 +2,20 @@
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
CFBundleURLTypes
@@ -55,22 +69,22 @@
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- 1.0
CFBundleVersion
1
+ FirebaseDynamicLinksCustomDomains
+
+ https://mydomain.com
+ https://mydomain2.com
+ https://google.com
+ https://google.com
+ google
+ mydomain.com
+ https://mydomain
+ https://mydomain3.com
+ https://google.com/one
+ https://custom.com/one/two
+ https://custom1.com/one/
+
LSRequiresIPhoneOS
UILaunchStoryboardName
diff --git a/Example/DynamicLinks/FDLBuilderTestAppObjC/ViewController.m b/Example/DynamicLinks/FDLBuilderTestAppObjC/ViewController.m
index cfacbc060ad..51450db9b4c 100644
--- a/Example/DynamicLinks/FDLBuilderTestAppObjC/ViewController.m
+++ b/Example/DynamicLinks/FDLBuilderTestAppObjC/ViewController.m
@@ -155,9 +155,9 @@ - (void)_initDefaultValues {
},
// The default value of domain appcode belongs to project: app-invites-qa
@{
- @"id" : @"domain",
- @"label" : @"App domain (required)",
- @"defaultValue" : @"testfdl.page.link",
+ @"id" : @"domainURIPrefix",
+ @"label" : @"App domainURIPrefix (required)",
+ @"defaultValue" : @"https://testfdl.page.link",
},
// analytics params
@{
@@ -289,7 +289,8 @@ - (void)_initDefaultValues {
- (void)_buildFDLLink {
NSURL *link = [NSURL URLWithString:_paramValues[@"linkString"]];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:_paramValues[@"domain"]];
+ [FIRDynamicLinkComponents componentsWithLink:link
+ domainURIPrefix:_paramValues[@"https://domain"]];
FIRDynamicLinkGoogleAnalyticsParameters *analyticsParams =
[FIRDynamicLinkGoogleAnalyticsParameters
diff --git a/Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist b/Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist
index 6c6c23c43ad..13fcbb5045d 100644
--- a/Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist
+++ b/Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist
@@ -18,5 +18,24 @@
1.0
CFBundleVersion
1
+ <?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+ <string>https://mydomain.com</string>
+ <string>https://mydomain2.com</string>
+ <string>https://google.com</string>
+ <string>https://google.com</string>
+ <string>go</string>
+ <string>g.co</string>
+ <string>https://go</string>
+ <string>https://g.co</string>
+ <string>https://google.com/one</string>
+ <string>https://custom.com/one/two</string>
+ <string>https://custom1.com/one/</string>
+</array>
+</plist>
+
+
diff --git a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m
index b1a51ee5f3b..d2cd4b8ae9a 100644
--- a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m
+++ b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m
@@ -21,7 +21,8 @@
#import
-static NSString *const kFDLURLDomain = @"xyz.page.link";
+static NSString *const kFDLURLDomain = @"https://xyz.page.link";
+static NSString *const kFDLURLCustomDomain = @"https://foo.com/path";
@interface FDLURLComponentsTests : XCTestCase
@end
@@ -461,14 +462,14 @@ - (void)testLinkOptionsParamsPropertiesSetProperly {
- (void)testFDLComponentsFactoryReturnsInstanceOfCorrectClass {
NSURL *link = [NSURL URLWithString:@"https://google.com"];
- id returnValue = [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ id returnValue = [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
XCTAssertTrue([returnValue isKindOfClass:[FIRDynamicLinkComponents class]]);
}
- (void)testFDLComponentsFactoryReturnsInstanceWithAllNilProperties {
NSURL *link = [NSURL URLWithString:@"https://google.com"];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
XCTAssertNil(components.analyticsParameters);
XCTAssertNil(components.socialMetaTagParameters);
@@ -484,11 +485,27 @@ - (void)testFDLComponentsCreatesSimplestLinkCorrectly {
NSURL *link = [NSURL URLWithString:linkString];
NSString *expectedURLString =
- [NSString stringWithFormat:@"https://%@/?link=%@", kFDLURLDomain, endcodedLinkString];
+ [NSString stringWithFormat:@"%@/?link=%@", kFDLURLDomain, endcodedLinkString];
NSURL *expectedURL = [NSURL URLWithString:expectedURLString];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
+ NSURL *actualURL = components.url;
+
+ XCTAssertEqualObjects(actualURL, expectedURL);
+}
+
+- (void)testFDLComponentsCustomDomainWithPath {
+ NSString *linkString = @"https://google.com";
+ NSString *endcodedLinkString = @"https%3A%2F%2Fgoogle%2Ecom";
+ NSURL *link = [NSURL URLWithString:linkString];
+
+ NSString *expectedURLString =
+ [NSString stringWithFormat:@"%@/?link=%@", kFDLURLCustomDomain, endcodedLinkString];
+ NSURL *expectedURL = [NSURL URLWithString:expectedURLString];
+
+ FIRDynamicLinkComponents *components =
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLCustomDomain];
NSURL *actualURL = components.url;
XCTAssertEqualObjects(actualURL, expectedURL);
@@ -499,7 +516,8 @@ - (void)testFDLComponentsFailsOnMalformedDomain {
NSURL *link = [NSURL URLWithString:linkString];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:@"this is invalid domain"];
+ [FIRDynamicLinkComponents componentsWithLink:link
+ domainURIPrefix:@"this is invalid domain URI Prefix"];
XCTAssertNil(components.url);
}
@@ -553,7 +571,7 @@ - (void)testFDLComponentsCreatesFullLinkCorrectly {
NSURL *link = [NSURL URLWithString:@"https://google.com"];
FIRDynamicLinkComponents *fdlComponents =
- [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
fdlComponents.analyticsParameters = analyticsParams;
fdlComponents.iOSParameters = iosParams;
fdlComponents.iTunesConnectParameters = itcParams;
@@ -642,7 +660,80 @@ - (void)testShortenURL {
XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
NSURL *link = [NSURL URLWithString:@"https://google.com/abc"];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
+ [components
+ shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings,
+ NSError *_Nullable error) {
+ XCTAssertEqualObjects(shortURL.absoluteString, shortURLString);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:0.1 handler:nil];
+
+ [keyProviderClassMock verify];
+ [keyProviderClassMock stopMocking];
+ [componentsClassMock verify];
+ [componentsClassMock stopMocking];
+}
+
+- (void)testDeprecatedMethodComponentsWithLinkForDomain {
+ NSString *shortURLString = @"https://xyz.page.link/abcd";
+
+ // Mock key provider
+ id keyProviderClassMock = OCMClassMock([FIRDynamicLinkComponentsKeyProvider class]);
+ [[[keyProviderClassMock expect] andReturn:@"fake-api-key"] APIKey];
+
+ id componentsClassMock = OCMClassMock([FIRDynamicLinkComponents class]);
+ [[componentsClassMock expect]
+ sendHTTPRequest:OCMOCK_ANY
+ completion:[OCMArg checkWithBlock:^BOOL(id obj) {
+ void (^completion)(NSData *_Nullable, NSError *_Nullable) = obj;
+ NSDictionary *JSON = @{@"shortLink" : shortURLString};
+ NSData *JSONData = [NSJSONSerialization dataWithJSONObject:JSON options:0 error:0];
+ completion(JSONData, nil);
+ return YES;
+ }]];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
+ NSURL *link = [NSURL URLWithString:@"https://google.com/abc"];
+ FIRDynamicLinkComponents *components =
+ [FIRDynamicLinkComponents componentsWithLink:link domain:@"xyz.page.link"];
+ [components
+ shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings,
+ NSError *_Nullable error) {
+ XCTAssertEqualObjects(shortURL.absoluteString, shortURLString);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:0.1 handler:nil];
+
+ [keyProviderClassMock verify];
+ [keyProviderClassMock stopMocking];
+ [componentsClassMock verify];
+ [componentsClassMock stopMocking];
+}
+
+- (void)testDeprecatedMethodComponentsWithLinkForDomainWithInvalidDomainScheme {
+ NSString *shortURLString = @"https://xyz.page.link/abcd";
+
+ // Mock key provider
+ id keyProviderClassMock = OCMClassMock([FIRDynamicLinkComponentsKeyProvider class]);
+ [[[keyProviderClassMock expect] andReturn:@"fake-api-key"] APIKey];
+
+ id componentsClassMock = OCMClassMock([FIRDynamicLinkComponents class]);
+ [[componentsClassMock expect]
+ sendHTTPRequest:OCMOCK_ANY
+ completion:[OCMArg checkWithBlock:^BOOL(id obj) {
+ void (^completion)(NSData *_Nullable, NSError *_Nullable) = obj;
+ NSDictionary *JSON = @{@"shortLink" : shortURLString};
+ NSData *JSONData = [NSJSONSerialization dataWithJSONObject:JSON options:0 error:0];
+ completion(JSONData, nil);
+ return YES;
+ }]];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
+ NSURL *link = [NSURL URLWithString:@"https://google.com/abc"];
+ FIRDynamicLinkComponents *components =
+ [FIRDynamicLinkComponents componentsWithLink:link domain:@"http://xyz.page.link"];
+ XCTAssertNotNil(components);
[components
shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings,
NSError *_Nullable error) {
@@ -679,7 +770,7 @@ - (void)testShortenURLReturnsErrorWhenAPIKeyMissing {
[self expectationWithDescription:@"completion called with error"];
NSURL *link = [NSURL URLWithString:@"https://google.com/abc"];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:kFDLURLDomain];
+ [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:kFDLURLDomain];
[components
shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings,
NSError *_Nullable error) {
@@ -714,20 +805,11 @@ - (void)testShortenURLReturnsErrorWhenDomainIsMalformed {
return YES;
}]];
- XCTestExpectation *expectation =
- [self expectationWithDescription:@"completion called with error"];
NSURL *link = [NSURL URLWithString:@"https://google.com/abc"];
FIRDynamicLinkComponents *components =
- [FIRDynamicLinkComponents componentsWithLink:link domain:@"this is invalid domain"];
- [components
- shortenWithCompletion:^(NSURL *_Nullable shortURL, NSArray *_Nullable warnings,
- NSError *_Nullable error) {
- XCTAssertNil(shortURL);
- if (error) {
- [expectation fulfill];
- }
- }];
- [self waitForExpectationsWithTimeout:0.1 handler:nil];
+ [FIRDynamicLinkComponents componentsWithLink:link
+ domainURIPrefix:@"this is invalid domain URI Prefix"];
+ XCTAssertNil(components);
[keyProviderClassMock verify];
[keyProviderClassMock stopMocking];
diff --git a/Example/DynamicLinks/Tests/FIRDynamicLinkNetworkingTests.m b/Example/DynamicLinks/Tests/FIRDynamicLinkNetworkingTests.m
index 3a8070e2c49..80be311184e 100644
--- a/Example/DynamicLinks/Tests/FIRDynamicLinkNetworkingTests.m
+++ b/Example/DynamicLinks/Tests/FIRDynamicLinkNetworkingTests.m
@@ -16,7 +16,7 @@
#import
-#import "OCMock.h"
+#import
#import
#import "DynamicLinks/FIRDynamicLinkNetworking+Private.h"
diff --git a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m
index 8dbb0e9a32d..7a50951eb2e 100644
--- a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m
+++ b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m
@@ -161,6 +161,9 @@ @implementation FIRDynamicLinksTest
- (void)setUp {
[super setUp];
+ if (!(FIRApp.defaultApp)) {
+ [FIRApp configure];
+ }
self.service = [[FIRDynamicLinks alloc] init];
self.userDefaults = [[NSUserDefaults alloc] init];
[self.userDefaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
@@ -223,7 +226,6 @@ - (void)testURLScheme_MinimumParameters {
}
- (void)testFactoryMethodReturnsProperClassObject {
- [FIRApp configure];
id service = [FIRDynamicLinks dynamicLinks];
XCTAssertNotNil(service, @"Factory method returned nil");
@@ -1015,6 +1017,61 @@ - (void)testSelfDiagnoseCompletionCalled {
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}
+#pragma mark - Custom domain tests
+- (void)testValidCustomDomainNames {
+ // Entries in plist file:
+ // https://google.com
+ // https://google.com/one
+ // https://a.firebase.com/mypath
+
+ NSArray *urlStrings = @[
+ @"https://google.com/mylink", // Short FDL starting with 'https://google.com'
+ @"https://google.com/one", // Short FDL starting with 'https://google.com'
+ @"https://google.com?link=abcd", // Long FDL starting with 'https://google.com'
+ @"https://google.com/one/mylink", // Long FDL starting with 'https://google.com/one'
+ @"https://a.firebase.com/mypath/mylink", // Short FDL starting https://a.firebase.com/mypath
+ @"https://a.firebase.com/mypath?link=abcd&test=1", // Long FDL starting with
+ // https://a.firebase.com/mypath
+ ];
+
+ for (NSString *urlString in urlStrings) {
+ NSURL *url = [NSURL URLWithString:urlString];
+ BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
+
+ XCTAssertTrue(matchesShortLinkFormat,
+ @"Non-DDL domain URL matched short link format with URL: %@", url);
+ }
+}
+
+- (void)testInvalidCustomDomainNames {
+ // Entries in plist file:
+ // https://google.com
+ // https://google.com/one
+ // https://a.firebase.com/mypath
+
+ NSArray *urlStrings = @[
+ @"google.com", // Valid domain. No scheme.
+ @"https://google.com", // Valid domain. No path after domainURIPrefix.
+ @"https://google.com/", // Valid domain. No path after domainURIPrefix.
+ @"https://google.com/one/", // Valid domain. No path after domainURIPrefix.
+ @"https://google.com/one/two/mylink", // domainURIPrefix not exact match.
+ @"https://google.co.in/mylink", // No matching domainURIPrefix.
+ @"https://firebase.com/mypath", // No matching domainURIPrefix: Invalid (sub)domain.
+ @"https://b.firebase.com/mypath", // No matching domainURIPrefix: Invalid subdomain.
+ @"https://a.firebase.com/mypathabc", // No matching domainURIPrefix: Invalid subdomain.
+ @"mydomain.com", // https scheme not specified for domainURIPrefix.
+ @"http://mydomain", // Domain not in plist. No path after domainURIPrefix.
+ ];
+
+ for (NSString *urlString in urlStrings) {
+ NSURL *url = [NSURL URLWithString:urlString];
+ BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
+
+ XCTAssertFalse(matchesShortLinkFormat,
+ @"Non-DDL domain URL matched short link format with URL: %@", url);
+ }
+}
+
#pragma mark - Private Helpers
- (void)removeAllFIRApps {
diff --git a/Firebase/DynamicLinks/CHANGELOG.md b/Firebase/DynamicLinks/CHANGELOG.md
index 48c29fe0237..c2aaf325693 100644
--- a/Firebase/DynamicLinks/CHANGELOG.md
+++ b/Firebase/DynamicLinks/CHANGELOG.md
@@ -1,3 +1,6 @@
+# v3.3.0
+- Introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078, #2097, #2112)
+
# v3.2.0
- Delete deprecated source files. (#2038)
diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m
index 9c6a0062ae0..5b200bf9397 100644
--- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m
+++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m
@@ -20,6 +20,7 @@
#import "DynamicLinks/FDLURLComponents/FIRDynamicLinkComponentsKeyProvider.h"
#import "DynamicLinks/Public/FDLURLComponents.h"
+#import "DynamicLinks/Logging/FDLLogging.h"
#import "DynamicLinks/Utilities/FDLUtilities.h"
/// The exact behavior of dict[key] = value is unclear when value is nil. This function safely adds
@@ -448,15 +449,49 @@ - (instancetype)init {
@implementation FIRDynamicLinkComponents
+#pragma mark Deprecated Initializers.
+ (instancetype)componentsWithLink:(NSURL *)link domain:(NSString *)domain {
return [[self alloc] initWithLink:link domain:domain];
}
- (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain {
+ NSURL *domainURL = [NSURL URLWithString:domain];
+ if (domainURL.scheme) {
+ FDLLog(FDLLogLevelWarning, FDLLogIdentifierSetupWarnHTTPSScheme,
+ @"You have supplied a domain with a scheme. Please enter a domain name without the "
+ @"scheme.");
+ }
+ NSString *domainURIPrefix = [NSString stringWithFormat:@"https://%@", domain];
+ self = [super init];
+ if (self) {
+ _link = link;
+ _domain = domainURIPrefix;
+ }
+ return self;
+}
+
+#pragma mark Initializers.
++ (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix {
+ return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix];
+}
+
+- (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix {
self = [super init];
if (self) {
_link = link;
- _domain = [domain copy];
+ /// Must be a URL that conforms to RFC 2396.
+ NSURL *domainURIPrefixURL = [NSURL URLWithString:domainURIPrefix];
+ if (!domainURIPrefixURL) {
+ FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefix,
+ @"Invalid domainURIPrefix. Please input a valid URL.");
+ return nil;
+ }
+ if (![[domainURIPrefixURL.scheme lowercaseString] isEqualToString:@"https"]) {
+ FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefixScheme,
+ @"Invalid domainURIPrefix scheme. Scheme needs to be https");
+ return nil;
+ }
+ _domain = [domainURIPrefix copy];
}
return self;
}
@@ -593,7 +628,7 @@ - (NSURL *)url {
addEntriesFromDictionaryRepresentingConformerToDictionary(_otherPlatformParameters);
NSString *queryString = FIRDLURLQueryStringFromDictionary(queryDictionary);
- NSString *urlString = [NSString stringWithFormat:@"https://%@/%@", _domain, queryString];
+ NSString *urlString = [NSString stringWithFormat:@"%@/%@", _domain, queryString];
return [NSURL URLWithString:urlString];
}
diff --git a/Firebase/DynamicLinks/FIRDynamicLinks.m b/Firebase/DynamicLinks/FIRDynamicLinks.m
index f12c0ed84c0..06e65070b42 100644
--- a/Firebase/DynamicLinks/FIRDynamicLinks.m
+++ b/Firebase/DynamicLinks/FIRDynamicLinks.m
@@ -60,6 +60,9 @@
// We should only open url once. We use the following key to store the state in the user defaults.
static NSString *const kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
+// Custom domains to be whitelisted are optionally added as an array to the info.plist.
+static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";
+
NS_ASSUME_NONNULL_BEGIN
@interface FIRDynamicLinks ()
@@ -216,6 +219,12 @@ - (void)configureDynamicLinks:(FIRApp *)app {
}
[NSException raise:kFirebaseDurableDeepLinkErrorDomain format:@"%@", message];
}
+ // Check to see if FirebaseDynamicLinksCustomDomains array is present.
+ NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
+ NSArray *customDomains = infoDictionary[kInfoPlistCustomDomainsKey];
+ if (customDomains) {
+ FIRDLAddToAllowListForCustomDomainsArray(customDomains);
+ }
}
- (instancetype)initWithAnalytics:(nullable id)analytics {
diff --git a/Firebase/DynamicLinks/Logging/FDLLogging.h b/Firebase/DynamicLinks/Logging/FDLLogging.h
index 5a9ea134d47..2d0efc572e2 100644
--- a/Firebase/DynamicLinks/Logging/FDLLogging.h
+++ b/Firebase/DynamicLinks/Logging/FDLLogging.h
@@ -33,6 +33,9 @@ typedef NS_ENUM(NSInteger, FDLLogIdentifier) {
FDLLogIdentifierSetupNilAPIKey = 0,
FDLLogIdentifierSetupNilClientID = 1,
FDLLogIdentifierSetupNonDefaultApp = 2,
+ FDLLogIdentifierSetupInvalidDomainURIPrefixScheme = 3,
+ FDLLogIdentifierSetupInvalidDomainURIPrefix = 4,
+ FDLLogIdentifierSetupWarnHTTPSScheme = 5,
};
/** The appropriate formatter for using NSInteger in FIRLogger. */
diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h
index cc27736f545..1cc8397560c 100644
--- a/Firebase/DynamicLinks/Public/FDLURLComponents.h
+++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h
@@ -512,11 +512,14 @@ FIR_SWIFT_NAME(DynamicLinkComponents)
* @param link Deep link to be stored in created Dynamic link. This link also called "payload" of
* the Dynamic link.
* @param domain Domain of your App. This value must be equal to your assigned domain from Firebase
- * Console.
+ * Console. (e.g. xyz.page.link). Note that the domain scheme is required to be https and is
+ * assumed as such by this API.
*/
+ (instancetype)componentsWithLink:(NSURL *)link
domain:(NSString *)domain
- NS_SWIFT_UNAVAILABLE("Use init(link:domain:)");
+ NS_SWIFT_UNAVAILABLE("Use init(link:domain:)")DEPRECATED_MSG_ATTRIBUTE(
+ "This method is deprecated. Please use the new method with support for "
+ "domainURIPrefix- init(link:domainURIPrefix:).");
/**
* @method initWithLink:domain:
@@ -525,9 +528,44 @@ FIR_SWIFT_NAME(DynamicLinkComponents)
* @param link Deep link to be stored in created Dynamic link. This link also called "payload" of
* the Dynamic link.
* @param domain Domain of your App. This value must be equal to your assigned domain from Firebase
- * Console.
+ * Console. (e.g. xyz.page.link). Note that the domain scheme is required to be https and is
+ * assumed as such by this API.
*/
-- (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain;
+- (instancetype)initWithLink:(NSURL *)link
+ domain:(NSString *)domain
+ DEPRECATED_MSG_ATTRIBUTE(
+ "This method is deprecated. Please use the new method with support for "
+ "domainURIPrefix- init(link:domainURIPrefix:).");
+
+/**
+ * @method componentsWithLink:domainURIPrefix:
+ * @abstract Generates a Dynamic Link URL components object with the minimum necessary parameters
+ * set to generate a fully-functional Dynamic Link.
+ * @param link Deep link to be stored in created Dynamic link. This link also called "payload" of
+ * the Dynamic link.
+ * @param domainURIPrefix Domain URI Prefix of your App. This value must be your assigned
+ * domain from the Firebase console. (e.g. https://xyz.page.link) The domain URI prefix must
+ * start with a valid HTTPS scheme (https://).
+ * @return Returns an instance of FIRDynamicLinkComponents if the parameters succeed validation,
+ * else returns nil.
+ */
++ (nullable instancetype)componentsWithLink:(NSURL *)link
+ domainURIPrefix:(NSString *)domainURIPrefix
+ NS_SWIFT_UNAVAILABLE("Use init(link:domainURIPrefix:)");
+
+/**
+ * @method initWithLink:domainURIPrefix:
+ * @abstract Generates a Dynamic Link URL components object with the minimum necessary parameters
+ * set to generate a fully-functional Dynamic Link.
+ * @param link Deep link to be stored in created Dynamic link. This link also called "payload" of
+ * the Dynamic link.
+ * @param domainURIPrefix Domain URI Prefix of your App. This value must be your assigned
+ * domain from the Firebase console. (e.g. https://xyz.page.link) The domain URI prefix must
+ * start with a valid HTTPS scheme (https://).
+ * @return Returns an instance of FIRDynamicLinkComponents if the parameters succeed validation,
+ * else returns nil.
+ */
+- (nullable instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix;
/**
* @method shortenURL:options:completion:
diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.h b/Firebase/DynamicLinks/Utilities/FDLUtilities.h
index 1aa664f136f..adeeabdf50a 100644
--- a/Firebase/DynamicLinks/Utilities/FDLUtilities.h
+++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.h
@@ -136,4 +136,9 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL);
*/
NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTypeString);
+/**
+ Add custom domains from the info.plist to the internal whitelist.
+ */
+void FIRDLAddToAllowListForCustomDomainsArray(NSArray *customDomains);
+
NS_ASSUME_NONNULL_END
diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.m b/Firebase/DynamicLinks/Utilities/FDLUtilities.m
index 425e2a52909..5ded3c42d84 100644
--- a/Firebase/DynamicLinks/Utilities/FDLUtilities.m
+++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.m
@@ -32,6 +32,7 @@
NSString *const kFIRDLParameterWeakMatchEndpoint = @"invitation_weakMatchEndpoint";
NSString *const kFIRDLParameterMatchMessage = @"match_message";
NSString *const kFIRDLParameterRequestIPVersion = @"request_ip_version";
+static NSSet *FIRDLCustomDomains = nil;
NSURL *FIRDLCookieRetrievalURL(NSString *urlScheme, NSString *bundleID) {
static NSString *const kFDLBundleIDQueryParameterName = @"fdl_ios_bundle_id";
@@ -192,6 +193,39 @@ BOOL FIRDLOSVersionSupported(NSString *_Nullable systemVersion, NSString *minSup
return timeZoneName;
}
+BOOL FIRDLIsURLForWhiteListedCustomDomain(NSURL *_Nullable URL) {
+ BOOL customDomainMatchFound = false;
+ for (NSURL *allowedCustomDomain in FIRDLCustomDomains) {
+ // All custom domain host names should match at a minimum.
+ if ([allowedCustomDomain.host isEqualToString:URL.host]) {
+ NSString *urlStr = URL.absoluteString;
+ NSString *domainURIPrefixStr = allowedCustomDomain.absoluteString;
+
+ // Next, do a string compare to check if entire domainURIPrefix matches as well.
+ if (([URL.absoluteString rangeOfString:allowedCustomDomain.absoluteString
+ options:NSCaseInsensitiveSearch | NSAnchoredSearch]
+ .location) == 0) {
+ // The (short) URL needs to be longer than the domainURIPrefix, it's first character after
+ // the domainURIPrefix needs to be '/' or '?' and should be followed by at-least one more
+ // character.
+ if (urlStr.length > domainURIPrefixStr.length + 1 &&
+ ([urlStr characterAtIndex:domainURIPrefixStr.length] == '/' ||
+ [urlStr characterAtIndex:domainURIPrefixStr.length] == '?')) {
+ // Check if there are any more '/' after the first '/' or '?' trailing the
+ // domainURIPrefix.
+ NSString *urlWithoutDomainURIPrefix =
+ [urlStr substringFromIndex:domainURIPrefixStr.length + 1];
+ if ([urlWithoutDomainURIPrefix rangeOfString:@"/"].location == NSNotFound) {
+ customDomainMatchFound = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return customDomainMatchFound;
+}
+
BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
// Handle universal links with format |https://goo.gl/app/?|.
// Also support page.link format.
@@ -200,12 +234,19 @@ BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
// Handle universal links with format |https://.app.goo.gl?| and page.link.
BOOL isDDLWithSubdomain =
[URL.host hasSuffix:@".app.goo.gl"] || [URL.host hasSuffix:@".page.link"];
- return isDDLWithAppcodeInPath || isDDLWithSubdomain;
+
+ // Handle universal links for custom domains.
+ BOOL isDDLWithCustomDomain = FIRDLIsURLForWhiteListedCustomDomain(URL);
+
+ return isDDLWithAppcodeInPath || isDDLWithSubdomain || isDDLWithCustomDomain;
}
BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) {
- // Short Durable Link URLs always have a path.
- BOOL hasPath = URL.path.length > 0;
+ // Short Durable Link URLs always have a path, except for certain custom domain URLs e.g.
+ // 'https://google.com?link=abcd' will not have a path component.
+ // FIRDLIsURLForWhiteListedCustomDomain implicitely checks for path component in custom domain
+ // URLs.
+ BOOL hasPath = URL.path.length > 0 || FIRDLIsURLForWhiteListedCustomDomain(URL);
// Must be able to parse (also checks if the URL conforms to *.app.goo.gl/* or goo.gl/app/*)
BOOL canParse = FIRDLCanParseUniversalLinkURL(URL);
// Path cannot be prefixed with /link/dismiss
@@ -227,4 +268,24 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) {
return matchMap[serverMatchTypeString] ?: @"none";
}
+void FIRDLAddToAllowListForCustomDomainsArray(NSArray *_Nonnull customDomains) {
+ // Duplicates will be weeded out when converting to a set.
+ NSMutableArray *validCustomDomains =
+ [[NSMutableArray alloc] initWithCapacity:customDomains.count];
+ for (NSString *customDomainEntry in customDomains) {
+ // We remove trailing slashes in the path if present.
+ NSString *domainEntry =
+ [customDomainEntry hasSuffix:@"/"]
+ ? [customDomainEntry substringToIndex:[customDomainEntry length] - 1]
+ : customDomainEntry;
+ NSURL *customDomainURL = [NSURL URLWithString:domainEntry];
+ // We require a valid scheme for each custom domain enumerated in the info.plist file.
+ if (customDomainURL && customDomainURL.scheme) {
+ [validCustomDomains addObject:customDomainURL];
+ }
+ }
+ // Duplicates will be weeded out when converting to a set.
+ FIRDLCustomDomains = [NSSet setWithArray:validCustomDomains];
+}
+
NS_ASSUME_NONNULL_END