From 5116bdcf1c7f58312b2dab86ff3a7006f67561d6 Mon Sep 17 00:00:00 2001 From: Mandar Date: Mon, 26 Nov 2018 11:20:18 -0800 Subject: [PATCH 01/10] Revert "Revert premature api changes (#2097)" The new API is optional and current API is fully supported. This reverts commit 46cb564067c3cfb720cf2adc33366ce651c59e17. --- Example/DynamicLinks/App/iOS/DL-Info.plist | 6 ++ .../FDLBuilderTestAppObjC/Info.plist | 42 ++++++--- .../FDLBuilderTestAppObjC/ViewController.m | 9 +- .../FDLBuilderTestAppObjCTests/Info.plist | 19 ++++ .../Tests/FDLURLComponentsTests.m | 87 ++++++++++++++----- .../Tests/FIRDynamicLinkNetworkingTests.m | 2 +- .../DynamicLinks/Tests/FIRDynamicLinksTest.m | 51 +++++++++++ Firebase/DynamicLinks/CHANGELOG.md | 1 + .../FDLURLComponents/FDLURLComponents.m | 54 +++++++++++- Firebase/DynamicLinks/FIRDynamicLinks.m | 9 ++ Firebase/DynamicLinks/Logging/FDLLogging.h | 3 + .../DynamicLinks/Public/FDLURLComponents.h | 44 +++++++++- .../DynamicLinks/Utilities/FDLUtilities.h | 5 ++ .../DynamicLinks/Utilities/FDLUtilities.m | 39 ++++++++- 14 files changed, 323 insertions(+), 48 deletions(-) diff --git a/Example/DynamicLinks/App/iOS/DL-Info.plist b/Example/DynamicLinks/App/iOS/DL-Info.plist index fc26896d71d..755ff83d4c2 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..c7fa6f6bc8b 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,43 @@ - (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) { @@ -679,7 +733,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 +768,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..c9ed77c5bdb 100644 --- a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m +++ b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m @@ -1015,6 +1015,57 @@ - (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/1", // Valid domain. Any path. + @"https://google.com/2", // Valid domain. Any path. + @"https://google.com/one", // Valid domain. Specified path. + @"https://a.firebase.com/mypath/", // Valid subdomain. + @"https://a.firebase.com/mypath/abcd/efgh", // Long path. + @"https://a.firebase.com/mypath?link=abcd&test=1", // Long path. + ]; + + 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 = @[ + @"mydomain.com", // Domain not in plist. Also, no scheme. + @"http://mydomain", // Domain not in plist. No path. + @"google.com", // Valid domain. No scheme. + @"https://google.com", // Valid domain. No path. + @"http://google.com", // Valid domain. Invalid scheme. + @"https://google.co.in/abc", // Invalid domain starts with valid domain name. + @"https://firebase.com/mypath", // Invalid (sub)domain. + @"https://b.firebase.com/mypath" // Invalid subdomain. + ]; + + 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..79655c21b1b 100644 --- a/Firebase/DynamicLinks/CHANGELOG.md +++ b/Firebase/DynamicLinks/CHANGELOG.md @@ -1,4 +1,5 @@ # v3.2.0 +- Add support for creating and receiving dynamic links from custom domains. This feature is not yet available for public consumption. (#1962, #2017, #2078) - Delete deprecated source files. (#2038) # v3.1.1 diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index 9c6a0062ae0..2fe4323bbba 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,62 @@ - (instancetype)init { @implementation FIRDynamicLinkComponents +#pragma mark Deprecated Initializers. + (instancetype)componentsWithLink:(NSURL *)link domain:(NSString *)domain { - return [[self alloc] initWithLink:link domain:domain]; + NSURL *domainURL = [NSURL URLWithString:domain]; + if (!domainURL.scheme) { + FDLLog(FDLLogLevelWarning, FDLLogIdentifierSetupWarnHTTPSScheme, + @"Only https scheme is allowed. The supplied domain's scheme will be treated as https."); + } + NSString *domainURIPrefix = + domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; + return [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:domainURIPrefix]; } - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { + NSURL *domainURL = [NSURL URLWithString:domain]; + if (!domainURL.scheme) { + FDLLog(FDLLogLevelWarning, FDLLogIdentifierSetupWarnHTTPSScheme, + @"Only https scheme is allowed. The supplied domain's scheme will be treated as https."); + } + NSString *domainURIPrefix = + domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; + return [self initWithLink:link domainURIPrefix:domainURIPrefix]; +} + +#pragma mark Initializers. ++ (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { + 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; + } + 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 +641,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..0dacdd8b93e 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) { + FIRDLAddToWhiteListForCustomDomainsArray(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..a06dc5a1585 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- componentsWithLink:domainURIPrefix."); /** * @method initWithLink:domain: @@ -525,9 +528,42 @@ 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- initWithLink:domainURIPrefix."); + +/** + * @method componentsWithLink:domain: + * @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 either a. your assigned + * domain from the Firebase console or b. your custom domain or c. your custom domain with a valid + * path that is registered for Dynamic Links. The domain URI prefix must start with a valid scheme + * (https://) + */ ++ (instancetype)componentsWithLink:(NSURL *)link + domainURIPrefix:(NSString *)domainURIPrefix + NS_SWIFT_UNAVAILABLE("Use init(link:domainURIPrefix:)"); + +/** + * @method initWithLink:domain: + * @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 either a. your assigned + * domain from the Firebase console or b. your custom domain or c. your custom domain with a valid + * path that is registered for Dynamic Links. The domain URI prefix must start with a valid scheme + * (https://). + */ +- (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..63c570e3d20 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 FIRDLAddToWhiteListForCustomDomainsArray(NSArray *_Nonnull customDomains); + NS_ASSUME_NONNULL_END diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.m b/Firebase/DynamicLinks/Utilities/FDLUtilities.m index 425e2a52909..8217bb489e8 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,23 @@ 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]) { + // Next, do a string compare to check if the full path matches as well. + if (([URL.absoluteString rangeOfString:allowedCustomDomain.absoluteString + options:NSCaseInsensitiveSearch | NSAnchoredSearch] + .location) == 0) { + 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,7 +218,11 @@ 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) { @@ -227,4 +249,19 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) { return matchMap[serverMatchTypeString] ?: @"none"; } +void FIRDLAddToWhiteListForCustomDomainsArray(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) { + NSURL *customDomainURL = [NSURL URLWithString:customDomainEntry]; + // 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 From 2e8203457ec7b0539896b5b99df50f534f8a51a8 Mon Sep 17 00:00:00 2001 From: Mandar Date: Tue, 27 Nov 2018 11:19:54 -0800 Subject: [PATCH 02/10] Update comments to reflect API changes. --- Firebase/DynamicLinks/CHANGELOG.md | 2 +- .../DynamicLinks/Public/FDLURLComponents.h | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Firebase/DynamicLinks/CHANGELOG.md b/Firebase/DynamicLinks/CHANGELOG.md index 79655c21b1b..d1846aed6c3 100644 --- a/Firebase/DynamicLinks/CHANGELOG.md +++ b/Firebase/DynamicLinks/CHANGELOG.md @@ -1,5 +1,5 @@ # v3.2.0 -- Add support for creating and receiving dynamic links from custom domains. This feature is not yet available for public consumption. (#1962, #2017, #2078) +- introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078) - Delete deprecated source files. (#2038) # v3.1.1 diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h index a06dc5a1585..780b3f4a917 100644 --- a/Firebase/DynamicLinks/Public/FDLURLComponents.h +++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h @@ -538,30 +538,28 @@ FIR_SWIFT_NAME(DynamicLinkComponents) "domainURIPrefix- initWithLink:domainURIPrefix."); /** - * @method componentsWithLink:domain: + * @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 either a. your assigned - * domain from the Firebase console or b. your custom domain or c. your custom domain with a valid - * path that is registered for Dynamic Links. The domain URI prefix must start with a valid scheme - * (https://) + * @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://). */ + (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix NS_SWIFT_UNAVAILABLE("Use init(link:domainURIPrefix:)"); /** - * @method initWithLink:domain: + * @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 either a. your assigned - * domain from the Firebase console or b. your custom domain or c. your custom domain with a valid - * path that is registered for Dynamic Links. The domain URI prefix must start with a valid scheme - * (https://). + * @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://). */ - (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix; From 4260fdeb7becf76e203ddc557826dc7c91110dd9 Mon Sep 17 00:00:00 2001 From: Mandar Date: Tue, 27 Nov 2018 18:05:18 -0800 Subject: [PATCH 03/10] Mark the FDLURLComponents initializer return as nullable. --- Firebase/DynamicLinks/Public/FDLURLComponents.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h index 780b3f4a917..36e10fd11ba 100644 --- a/Firebase/DynamicLinks/Public/FDLURLComponents.h +++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h @@ -547,7 +547,7 @@ FIR_SWIFT_NAME(DynamicLinkComponents) * domain from the Firebase console. (e.g. https://xyz.page.link) The domain URI prefix must * start with a valid HTTPS scheme (https://). */ -+ (instancetype)componentsWithLink:(NSURL *)link ++ (nullable instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix NS_SWIFT_UNAVAILABLE("Use init(link:domainURIPrefix:)"); @@ -561,7 +561,7 @@ FIR_SWIFT_NAME(DynamicLinkComponents) * domain from the Firebase console. (e.g. https://xyz.page.link) The domain URI prefix must * start with a valid HTTPS scheme (https://). */ -- (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix; +- (nullable instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix; /** * @method shortenURL:options:completion: From 2a3f2600d0bbdb72e77820cee2fa0001e8f5a8f9 Mon Sep 17 00:00:00 2001 From: Mandar Date: Tue, 27 Nov 2018 18:12:55 -0800 Subject: [PATCH 04/10] Run Style. --- Firebase/DynamicLinks/Public/FDLURLComponents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h index 36e10fd11ba..154e750cf33 100644 --- a/Firebase/DynamicLinks/Public/FDLURLComponents.h +++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h @@ -548,7 +548,7 @@ FIR_SWIFT_NAME(DynamicLinkComponents) * start with a valid HTTPS scheme (https://). */ + (nullable instancetype)componentsWithLink:(NSURL *)link - domainURIPrefix:(NSString *)domainURIPrefix + domainURIPrefix:(NSString *)domainURIPrefix NS_SWIFT_UNAVAILABLE("Use init(link:domainURIPrefix:)"); /** From 16c4aabd5300e8d699b8be8a4177f50cf0959201 Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 10:32:40 -0800 Subject: [PATCH 05/10] Allow for new initializer for FIRDynamicLinkComponents to have a nullable instancetype return. --- .../Tests/FDLURLComponentsTests.m | 37 +++++++++++++++++++ Firebase/DynamicLinks/CHANGELOG.md | 4 +- .../FDLURLComponents/FDLURLComponents.m | 31 ++++++++-------- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m index c7fa6f6bc8b..d2cd4b8ae9a 100644 --- a/Example/DynamicLinks/Tests/FDLURLComponentsTests.m +++ b/Example/DynamicLinks/Tests/FDLURLComponentsTests.m @@ -711,6 +711,43 @@ - (void)testDeprecatedMethodComponentsWithLinkForDomain { [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) { + XCTAssertEqualObjects(shortURL.absoluteString, shortURLString); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:0.1 handler:nil]; + + [keyProviderClassMock verify]; + [keyProviderClassMock stopMocking]; + [componentsClassMock verify]; + [componentsClassMock stopMocking]; +} + - (void)testShortenURLReturnsErrorWhenAPIKeyMissing { NSString *shortURLString = @"https://xyz.page.link/abcd"; diff --git a/Firebase/DynamicLinks/CHANGELOG.md b/Firebase/DynamicLinks/CHANGELOG.md index d1846aed6c3..5df5b7f7d0d 100644 --- a/Firebase/DynamicLinks/CHANGELOG.md +++ b/Firebase/DynamicLinks/CHANGELOG.md @@ -1,5 +1,7 @@ +# v3.3.0 +- Introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078, #2097) + # v3.2.0 -- introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078) - Delete deprecated source files. (#2038) # v3.1.1 diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index 2fe4323bbba..5341c98d2b5 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m @@ -458,7 +458,7 @@ + (instancetype)componentsWithLink:(NSURL *)link domain:(NSString *)domain { } NSString *domainURIPrefix = domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; - return [FIRDynamicLinkComponents componentsWithLink:link domainURIPrefix:domainURIPrefix]; + return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:NO]; } - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { @@ -469,26 +469,23 @@ - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { } NSString *domainURIPrefix = domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; - return [self initWithLink:link domainURIPrefix:domainURIPrefix]; + return [self initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:NO]; } #pragma mark Initializers. + (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { - 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; - } return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix]; } - (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { + return [self initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:YES]; +} + +/// Deprecated init API has nonnull return type. If the check fails, support returning an instance +/// for the deprecated API (the backend call will return an error. This is pre-existing behavior). +- (instancetype)initWithLink:(NSURL *)link + domainURIPrefix:(NSString *)domainURIPrefix + allowNilReturn:(BOOL)allowNilReturn { self = [super init]; if (self) { _link = link; @@ -497,12 +494,16 @@ - (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURI if (!domainURIPrefixURL) { FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefix, @"Invalid domainURIPrefix. Please input a valid URL."); - return nil; + if (allowNilReturn) { + return nil; + } } if (![[domainURIPrefixURL.scheme lowercaseString] isEqualToString:@"https"]) { FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefixScheme, @"Invalid domainURIPrefix scheme. Scheme needs to be https"); - return nil; + if (allowNilReturn) { + return nil; + } } _domain = [domainURIPrefix copy]; } From 2665390fac3f8227cb219e4d992c8add49afe931 Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 10:36:35 -0800 Subject: [PATCH 06/10] Update Changelog for v3.3.0 --- Firebase/DynamicLinks/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firebase/DynamicLinks/CHANGELOG.md b/Firebase/DynamicLinks/CHANGELOG.md index 5df5b7f7d0d..c2aaf325693 100644 --- a/Firebase/DynamicLinks/CHANGELOG.md +++ b/Firebase/DynamicLinks/CHANGELOG.md @@ -1,5 +1,5 @@ # v3.3.0 -- Introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078, #2097) +- Introduced a new componentsWithLink:domainURIPrefix: and deprecated the existing componentsWithLink:domain:. (#1962, #2017, #2078, #2097, #2112) # v3.2.0 - Delete deprecated source files. (#2038) From 2519db4ab5c1a1eb311f7929bee28164cc5e07f6 Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 12:33:20 -0800 Subject: [PATCH 07/10] Keep deprecated method initalization separate. --- .../FDLURLComponents/FDLURLComponents.m | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index 5341c98d2b5..d513a8a9876 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m @@ -451,14 +451,7 @@ @implementation FIRDynamicLinkComponents #pragma mark Deprecated Initializers. + (instancetype)componentsWithLink:(NSURL *)link domain:(NSString *)domain { - NSURL *domainURL = [NSURL URLWithString:domain]; - if (!domainURL.scheme) { - FDLLog(FDLLogLevelWarning, FDLLogIdentifierSetupWarnHTTPSScheme, - @"Only https scheme is allowed. The supplied domain's scheme will be treated as https."); - } - NSString *domainURIPrefix = - domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; - return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:NO]; + return [[self alloc] initWithLink:link domain:domain]; } - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { @@ -469,7 +462,12 @@ - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { } NSString *domainURIPrefix = domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; - return [self initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:NO]; + self = [super init]; + if (self) { + _link = link; + _domain = domainURIPrefix; + } + return self; } #pragma mark Initializers. @@ -477,15 +475,9 @@ + (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)dom return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix]; } -- (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { - return [self initWithLink:link domainURIPrefix:domainURIPrefix allowNilReturn:YES]; -} - /// Deprecated init API has nonnull return type. If the check fails, support returning an instance /// for the deprecated API (the backend call will return an error. This is pre-existing behavior). -- (instancetype)initWithLink:(NSURL *)link - domainURIPrefix:(NSString *)domainURIPrefix - allowNilReturn:(BOOL)allowNilReturn { +- (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { self = [super init]; if (self) { _link = link; @@ -494,16 +486,12 @@ - (instancetype)initWithLink:(NSURL *)link if (!domainURIPrefixURL) { FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefix, @"Invalid domainURIPrefix. Please input a valid URL."); - if (allowNilReturn) { - return nil; - } + return nil; } if (![[domainURIPrefixURL.scheme lowercaseString] isEqualToString:@"https"]) { FDLLog(FDLLogLevelError, FDLLogIdentifierSetupInvalidDomainURIPrefixScheme, @"Invalid domainURIPrefix scheme. Scheme needs to be https"); - if (allowNilReturn) { - return nil; - } + return nil; } _domain = [domainURIPrefix copy]; } From ead9b7b2522287067ce27d76916fc31ec7f33a8e Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 13:29:17 -0800 Subject: [PATCH 08/10] Comments updates and other minor fixes. --- Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m | 2 -- Firebase/DynamicLinks/FIRDynamicLinks.m | 2 +- Firebase/DynamicLinks/Public/FDLURLComponents.h | 8 ++++++-- Firebase/DynamicLinks/Utilities/FDLUtilities.m | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index d513a8a9876..e1776415ac4 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m @@ -475,8 +475,6 @@ + (instancetype)componentsWithLink:(NSURL *)link domainURIPrefix:(NSString *)dom return [[self alloc] initWithLink:link domainURIPrefix:domainURIPrefix]; } -/// Deprecated init API has nonnull return type. If the check fails, support returning an instance -/// for the deprecated API (the backend call will return an error. This is pre-existing behavior). - (instancetype)initWithLink:(NSURL *)link domainURIPrefix:(NSString *)domainURIPrefix { self = [super init]; if (self) { diff --git a/Firebase/DynamicLinks/FIRDynamicLinks.m b/Firebase/DynamicLinks/FIRDynamicLinks.m index 0dacdd8b93e..06e65070b42 100644 --- a/Firebase/DynamicLinks/FIRDynamicLinks.m +++ b/Firebase/DynamicLinks/FIRDynamicLinks.m @@ -223,7 +223,7 @@ - (void)configureDynamicLinks:(FIRApp *)app { NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary; NSArray *customDomains = infoDictionary[kInfoPlistCustomDomainsKey]; if (customDomains) { - FIRDLAddToWhiteListForCustomDomainsArray(customDomains); + FIRDLAddToAllowListForCustomDomainsArray(customDomains); } } diff --git a/Firebase/DynamicLinks/Public/FDLURLComponents.h b/Firebase/DynamicLinks/Public/FDLURLComponents.h index 154e750cf33..1cc8397560c 100644 --- a/Firebase/DynamicLinks/Public/FDLURLComponents.h +++ b/Firebase/DynamicLinks/Public/FDLURLComponents.h @@ -519,7 +519,7 @@ FIR_SWIFT_NAME(DynamicLinkComponents) domain:(NSString *)domain NS_SWIFT_UNAVAILABLE("Use init(link:domain:)")DEPRECATED_MSG_ATTRIBUTE( "This method is deprecated. Please use the new method with support for " - "domainURIPrefix- componentsWithLink:domainURIPrefix."); + "domainURIPrefix- init(link:domainURIPrefix:)."); /** * @method initWithLink:domain: @@ -535,7 +535,7 @@ FIR_SWIFT_NAME(DynamicLinkComponents) domain:(NSString *)domain DEPRECATED_MSG_ATTRIBUTE( "This method is deprecated. Please use the new method with support for " - "domainURIPrefix- initWithLink:domainURIPrefix."); + "domainURIPrefix- init(link:domainURIPrefix:)."); /** * @method componentsWithLink:domainURIPrefix: @@ -546,6 +546,8 @@ FIR_SWIFT_NAME(DynamicLinkComponents) * @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 @@ -560,6 +562,8 @@ FIR_SWIFT_NAME(DynamicLinkComponents) * @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; diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.m b/Firebase/DynamicLinks/Utilities/FDLUtilities.m index 8217bb489e8..4f57d34be00 100644 --- a/Firebase/DynamicLinks/Utilities/FDLUtilities.m +++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.m @@ -249,7 +249,7 @@ BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) { return matchMap[serverMatchTypeString] ?: @"none"; } -void FIRDLAddToWhiteListForCustomDomainsArray(NSArray *_Nonnull customDomains) { +void FIRDLAddToAllowListForCustomDomainsArray(NSArray *_Nonnull customDomains) { // Duplicates will be weeded out when converting to a set. NSMutableArray *validCustomDomains = [[NSMutableArray alloc] initWithCapacity:customDomains.count]; From be2f9b8754b932f8c49c326cc3d5e5984950469e Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 18:07:16 -0800 Subject: [PATCH 09/10] Consider domainURIPrefixes for exact matches with incoming short/long URLs. --- .../DynamicLinks/Tests/FIRDynamicLinksTest.m | 33 +++++++++-------- .../DynamicLinks/Utilities/FDLUtilities.h | 2 +- .../DynamicLinks/Utilities/FDLUtilities.m | 36 +++++++++++++++---- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m index c9ed77c5bdb..7ecfc59e3ce 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"); @@ -1023,11 +1025,11 @@ - (void)testValidCustomDomainNames { // https://a.firebase.com/mypath NSArray *urlStrings = @[ - @"https://google.com/1", // Valid domain. Any path. - @"https://google.com/2", // Valid domain. Any path. - @"https://google.com/one", // Valid domain. Specified path. - @"https://a.firebase.com/mypath/", // Valid subdomain. - @"https://a.firebase.com/mypath/abcd/efgh", // Long path. + @"https://google.com/mylink", // Valid url for domainURIPrefix of 'https://google.com' + @"https://google.com/one", // Valid url for domainURIPrefix of 'https://google.com' + @"https://google.com?link=abcd", // Valid url for domainURIPrefix of 'https://google.com' + @"https://google.com/one/mylink", // Valid url for domainURIPrefix of 'https://google.com/one' + @"https://a.firebase.com/mypath/mylink", // Valid subdomain. @"https://a.firebase.com/mypath?link=abcd&test=1", // Long path. ]; @@ -1047,14 +1049,17 @@ - (void)testInvalidCustomDomainNames { // https://a.firebase.com/mypath NSArray *urlStrings = @[ - @"mydomain.com", // Domain not in plist. Also, no scheme. - @"http://mydomain", // Domain not in plist. No path. - @"google.com", // Valid domain. No scheme. - @"https://google.com", // Valid domain. No path. - @"http://google.com", // Valid domain. Invalid scheme. - @"https://google.co.in/abc", // Invalid domain starts with valid domain name. - @"https://firebase.com/mypath", // Invalid (sub)domain. - @"https://b.firebase.com/mypath" // Invalid subdomain. + @"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) { diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.h b/Firebase/DynamicLinks/Utilities/FDLUtilities.h index 63c570e3d20..adeeabdf50a 100644 --- a/Firebase/DynamicLinks/Utilities/FDLUtilities.h +++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.h @@ -139,6 +139,6 @@ NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTy /** Add custom domains from the info.plist to the internal whitelist. */ -void FIRDLAddToWhiteListForCustomDomainsArray(NSArray *_Nonnull customDomains); +void FIRDLAddToAllowListForCustomDomainsArray(NSArray *customDomains); NS_ASSUME_NONNULL_END diff --git a/Firebase/DynamicLinks/Utilities/FDLUtilities.m b/Firebase/DynamicLinks/Utilities/FDLUtilities.m index 4f57d34be00..5ded3c42d84 100644 --- a/Firebase/DynamicLinks/Utilities/FDLUtilities.m +++ b/Firebase/DynamicLinks/Utilities/FDLUtilities.m @@ -198,12 +198,28 @@ BOOL FIRDLIsURLForWhiteListedCustomDomain(NSURL *_Nullable URL) { for (NSURL *allowedCustomDomain in FIRDLCustomDomains) { // All custom domain host names should match at a minimum. if ([allowedCustomDomain.host isEqualToString:URL.host]) { - // Next, do a string compare to check if the full path matches as well. + 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) { - customDomainMatchFound = true; - break; + // 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; + } + } } } } @@ -226,8 +242,11 @@ BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) { } 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 @@ -254,7 +273,12 @@ void FIRDLAddToAllowListForCustomDomainsArray(NSArray *_Nonnull customDomains) { NSMutableArray *validCustomDomains = [[NSMutableArray alloc] initWithCapacity:customDomains.count]; for (NSString *customDomainEntry in customDomains) { - NSURL *customDomainURL = [NSURL URLWithString:customDomainEntry]; + // 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]; From 155ae377681f439727271f032dd75a1b54d0799f Mon Sep 17 00:00:00 2001 From: Mandar Date: Wed, 28 Nov 2018 20:50:36 -0800 Subject: [PATCH 10/10] Add warning for deprecated API if developer passes in a domain with a scheme. --- Example/DynamicLinks/App/iOS/DL-Info.plist | 2 +- Example/DynamicLinks/Tests/FIRDynamicLinksTest.m | 13 +++++++------ .../FDLURLComponents/FDLURLComponents.m | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Example/DynamicLinks/App/iOS/DL-Info.plist b/Example/DynamicLinks/App/iOS/DL-Info.plist index 755ff83d4c2..87945bc6090 100644 --- a/Example/DynamicLinks/App/iOS/DL-Info.plist +++ b/Example/DynamicLinks/App/iOS/DL-Info.plist @@ -53,7 +53,7 @@ FirebaseDynamicLinksCustomDomains https://google.com - https://google.com/one + https://google.com/one/ https://a.firebase.com/mypath diff --git a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m index 7ecfc59e3ce..7a50951eb2e 100644 --- a/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m +++ b/Example/DynamicLinks/Tests/FIRDynamicLinksTest.m @@ -1025,12 +1025,13 @@ - (void)testValidCustomDomainNames { // https://a.firebase.com/mypath NSArray *urlStrings = @[ - @"https://google.com/mylink", // Valid url for domainURIPrefix of 'https://google.com' - @"https://google.com/one", // Valid url for domainURIPrefix of 'https://google.com' - @"https://google.com?link=abcd", // Valid url for domainURIPrefix of 'https://google.com' - @"https://google.com/one/mylink", // Valid url for domainURIPrefix of 'https://google.com/one' - @"https://a.firebase.com/mypath/mylink", // Valid subdomain. - @"https://a.firebase.com/mypath?link=abcd&test=1", // Long path. + @"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) { diff --git a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m index e1776415ac4..5b200bf9397 100644 --- a/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m +++ b/Firebase/DynamicLinks/FDLURLComponents/FDLURLComponents.m @@ -456,12 +456,12 @@ + (instancetype)componentsWithLink:(NSURL *)link domain:(NSString *)domain { - (instancetype)initWithLink:(NSURL *)link domain:(NSString *)domain { NSURL *domainURL = [NSURL URLWithString:domain]; - if (!domainURL.scheme) { + if (domainURL.scheme) { FDLLog(FDLLogLevelWarning, FDLLogIdentifierSetupWarnHTTPSScheme, - @"Only https scheme is allowed. The supplied domain's scheme will be treated as https."); + @"You have supplied a domain with a scheme. Please enter a domain name without the " + @"scheme."); } - NSString *domainURIPrefix = - domainURL.scheme ? domain : [NSString stringWithFormat:@"https://%@", domain]; + NSString *domainURIPrefix = [NSString stringWithFormat:@"https://%@", domain]; self = [super init]; if (self) { _link = link;