Просмотр исходного кода

Move to `domainURIPrefix` for FIRDynamicLinkComponents (#2119)

* Revert "Revert premature api changes (#2097)"
The new API is optional and current API is fully supported.

This reverts commit 46cb564067c3cfb720cf2adc33366ce651c59e17.

* Update comments to reflect API changes.

* Mark the FDLURLComponents initializer return as nullable.

* Run Style.

* Allow for new initializer for FIRDynamicLinkComponents to have a nullable instancetype return.

* Update Changelog for v3.3.0

* Keep deprecated method initalization separate.

* Comments updates and other minor fixes.

* Consider domainURIPrefixes for exact matches with incoming short/long URLs.

* Add warning for deprecated API if developer passes in a domain with a scheme.
dmandar 7 лет назад
Родитель
Сommit
36b79e633f

+ 6 - 0
Example/DynamicLinks/App/iOS/DL-Info.plist

@@ -50,5 +50,11 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
+	<key>FirebaseDynamicLinksCustomDomains</key>
+	<array>
+		<string>https://google.com</string>
+		<string>https://google.com/one/</string>
+		<string>https://a.firebase.com/mypath</string>
+	</array>
 </dict>
 </plist>

+ 28 - 14
Example/DynamicLinks/FDLBuilderTestAppObjC/Info.plist

@@ -2,6 +2,20 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
 	<key>CFBundleURLTypes</key>
 	<array>
 		<dict>
@@ -55,22 +69,22 @@
 			</array>
 		</dict>
 	</array>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>en</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>$(PRODUCT_NAME)</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
 	<key>CFBundleVersion</key>
 	<string>1</string>
+	<key>FirebaseDynamicLinksCustomDomains</key>
+	<array>
+		<string>https://mydomain.com</string>
+		<string>https://mydomain2.com</string>
+		<string>https://google.com</string>
+		<string>https://google.com</string>
+		<string>google</string>
+		<string>mydomain.com</string>
+		<string>https://mydomain</string>
+		<string>https://mydomain3.com</string>
+		<string>https://google.com/one</string>
+		<string>https://custom.com/one/two</string>
+		<string>https://custom1.com/one/</string>
+	</array>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
 	<key>UILaunchStoryboardName</key>

+ 5 - 4
Example/DynamicLinks/FDLBuilderTestAppObjC/ViewController.m

@@ -155,9 +155,9 @@ static NSArray *kParamsConfiguration;
       },
       // 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 @@ static NSArray *kParamsConfiguration;
 - (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

+ 19 - 0
Example/DynamicLinks/FDLBuilderTestAppObjCTests/Info.plist

@@ -18,5 +18,24 @@
 	<string>1.0</string>
 	<key>CFBundleVersion</key>
 	<string>1</string>
+	<key>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
+&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot; &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
+&lt;plist version=&quot;1.0&quot;&gt;
+&lt;array&gt;
+	&lt;string&gt;https://mydomain.com&lt;/string&gt;
+	&lt;string&gt;https://mydomain2.com&lt;/string&gt;
+	&lt;string&gt;https://google.com&lt;/string&gt;
+	&lt;string&gt;https://google.com&lt;/string&gt;
+	&lt;string&gt;go&lt;/string&gt;
+	&lt;string&gt;g.co&lt;/string&gt;
+	&lt;string&gt;https://go&lt;/string&gt;
+	&lt;string&gt;https://g.co&lt;/string&gt;
+	&lt;string&gt;https://google.com/one&lt;/string&gt;
+	&lt;string&gt;https://custom.com/one/two&lt;/string&gt;
+	&lt;string&gt;https://custom1.com/one/&lt;/string&gt;
+&lt;/array&gt;
+&lt;/plist&gt;
+</key>
+	<string></string>
 </dict>
 </plist>

+ 103 - 21
Example/DynamicLinks/Tests/FDLURLComponentsTests.m

@@ -21,7 +21,8 @@
 
 #import <OCMock/OCMock.h>
 
-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 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
 
 - (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 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
   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 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
   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 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
 
   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 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
   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<NSString *> *_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<NSString *> *_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<NSString *> *_Nullable warnings,
                               NSError *_Nullable error) {
@@ -679,7 +770,7 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
       [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<NSString *> *_Nullable warnings,
                               NSError *_Nullable error) {
@@ -714,20 +805,11 @@ static NSString *const kFDLURLDomain = @"xyz.page.link";
              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<NSString *> *_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];

+ 1 - 1
Example/DynamicLinks/Tests/FIRDynamicLinkNetworkingTests.m

@@ -16,7 +16,7 @@
 
 #import <XCTest/XCTest.h>
 
-#import "OCMock.h"
+#import <OCMock/OCMock.h>
 
 #import <GoogleUtilities/GULSwizzler.h>
 #import "DynamicLinks/FIRDynamicLinkNetworking+Private.h"

+ 58 - 1
Example/DynamicLinks/Tests/FIRDynamicLinksTest.m

@@ -161,6 +161,9 @@ static void UnswizzleDynamicLinkNetworking() {
 
 - (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 @@ static void UnswizzleDynamicLinkNetworking() {
 }
 
 - (void)testFactoryMethodReturnsProperClassObject {
-  [FIRApp configure];
   id service = [FIRDynamicLinks dynamicLinks];
 
   XCTAssertNotNil(service, @"Factory method returned nil");
@@ -1015,6 +1017,61 @@ static void UnswizzleDynamicLinkNetworking() {
   [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<NSString *> *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<NSString *> *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 {

+ 3 - 0
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)
 

+ 37 - 2
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 @@ static NSString *const kFDLOtherPlatformParametersFallbackURLKey = @"ofl";
 
 @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 @@ static NSString *const kFDLOtherPlatformParametersFallbackURLKey = @"ofl";
   addEntriesFromDictionaryRepresentingConformerToDictionary(_otherPlatformParameters);
 
   NSString *queryString = FIRDLURLQueryStringFromDictionary(queryDictionary);
-  NSString *urlString = [NSString stringWithFormat:@"https://%@/%@", _domain, queryString];
+  NSString *urlString = [NSString stringWithFormat:@"%@/%@", _domain, queryString];
   return [NSURL URLWithString:urlString];
 }
 

+ 9 - 0
Firebase/DynamicLinks/FIRDynamicLinks.m

@@ -60,6 +60,9 @@ NSString *const kFIRDLReadDeepLinkAfterInstallKey =
 // 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 () <FIRDLRetrievalProcessDelegate>
@@ -216,6 +219,12 @@ NS_ASSUME_NONNULL_BEGIN
     }
     [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<FIRAnalyticsInterop>)analytics {

+ 3 - 0
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. */

+ 42 - 4
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:

+ 5 - 0
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

+ 64 - 3
Firebase/DynamicLinks/Utilities/FDLUtilities.m

@@ -32,6 +32,7 @@ NSString *const kFIRDLParameterInviteId = @"invitation_id";
 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 @@ NSString *FIRDLDeviceTimezone() {
   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/<appcode>?<parameters>|.
   // Also support page.link format.
@@ -200,12 +234,19 @@ BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
   // Handle universal links with format |https://<appcode>.app.goo.gl?<parameters>| 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 @@ NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTy
   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