| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /*
- * Copyright 2018 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import <TargetConditionals.h>
- #if TARGET_OS_IOS
- #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
- #import <UIKit/UIDevice.h>
- #include <sys/sysctl.h>
- NS_ASSUME_NONNULL_BEGIN
- NSString *const kFIRDLParameterDeepLinkIdentifier = @"deep_link_id";
- NSString *const kFIRDLParameterLink = @"link";
- NSString *const kFIRDLParameterMinimumAppVersion = @"imv";
- NSString *const kFIRDLParameterCampaign = @"utm_campaign";
- NSString *const kFIRDLParameterContent = @"utm_content";
- NSString *const kFIRDLParameterMedium = @"utm_medium";
- NSString *const kFIRDLParameterSource = @"utm_source";
- NSString *const kFIRDLParameterTerm = @"utm_term";
- NSString *const kFIRDLParameterMatchType = @"match_type";
- 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";
- static NSString *const kFDLURLSchemeQueryParameterName = @"fdl_ios_url_scheme";
- NSURLComponents *components = [[NSURLComponents alloc] init];
- components.scheme = @"https";
- components.host = @"goo.gl";
- components.path = @"/app/_/deeplink";
- NSMutableArray *queryItems = [NSMutableArray array];
- [queryItems addObject:[NSURLQueryItem queryItemWithName:kFDLBundleIDQueryParameterName
- value:bundleID]];
- [queryItems addObject:[NSURLQueryItem queryItemWithName:kFDLURLSchemeQueryParameterName
- value:urlScheme]];
- [components setQueryItems:queryItems];
- return [components URL];
- }
- NSString *FIRDLURLQueryStringFromDictionary(NSDictionary<NSString *, NSString *> *dictionary) {
- NSMutableString *parameters = [NSMutableString string];
- NSCharacterSet *queryCharacterSet = [NSCharacterSet alphanumericCharacterSet];
- NSString *parameterFormatString = @"%@%@=%@";
- __block NSUInteger index = 0;
- [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, NSString *_Nonnull value,
- BOOL *_Nonnull stop) {
- NSString *delimiter = index++ == 0 ? @"?" : @"&";
- NSString *encodedValue =
- [value stringByAddingPercentEncodingWithAllowedCharacters:queryCharacterSet];
- NSString *parameter =
- [NSString stringWithFormat:parameterFormatString, delimiter, key, encodedValue];
- [parameters appendString:parameter];
- }];
- return parameters;
- }
- NSDictionary *FIRDLDictionaryFromQuery(NSString *queryString) {
- NSArray<NSString *> *keyValuePairs = [queryString componentsSeparatedByString:@"&"];
- NSMutableDictionary *queryDictionary =
- [NSMutableDictionary dictionaryWithCapacity:keyValuePairs.count];
- for (NSString *pair in keyValuePairs) {
- NSArray *keyValuePair = [pair componentsSeparatedByString:@"="];
- if (keyValuePair.count == 2) {
- NSString *key = keyValuePair[0];
- NSString *value = [keyValuePair[1] stringByRemovingPercentEncoding];
- [queryDictionary setObject:value forKey:key];
- }
- }
- return [queryDictionary copy];
- }
- NSURL *FIRDLDeepLinkURLWithInviteID(NSString *_Nullable inviteID,
- NSString *_Nullable deepLinkString,
- NSString *_Nullable utmSource,
- NSString *_Nullable utmMedium,
- NSString *_Nullable utmCampaign,
- NSString *_Nullable utmContent,
- NSString *_Nullable utmTerm,
- BOOL isWeakLink,
- NSString *_Nullable weakMatchEndpoint,
- NSString *_Nullable minAppVersion,
- NSString *URLScheme,
- NSString *_Nullable matchMessage) {
- // We are unable to use NSURLComponents as NSURLQueryItem is avilable beginning in iOS 8 and
- // appending our query string with NSURLComponents improperly formats the query by adding
- // a second '?' in the query.
- NSMutableDictionary *queryDictionary = [NSMutableDictionary dictionary];
- if (inviteID != nil) {
- queryDictionary[kFIRDLParameterInviteId] = inviteID;
- }
- if (deepLinkString != nil) {
- queryDictionary[kFIRDLParameterDeepLinkIdentifier] = deepLinkString;
- }
- if (utmSource != nil) {
- queryDictionary[kFIRDLParameterSource] = utmSource;
- }
- if (utmMedium != nil) {
- queryDictionary[kFIRDLParameterMedium] = utmMedium;
- }
- if (utmCampaign != nil) {
- queryDictionary[kFIRDLParameterCampaign] = utmCampaign;
- }
- if (utmContent != nil) {
- queryDictionary[kFIRDLParameterContent] = utmContent;
- }
- if (utmTerm != nil) {
- queryDictionary[kFIRDLParameterTerm] = utmTerm;
- }
- if (isWeakLink) {
- queryDictionary[kFIRDLParameterMatchType] = @"weak";
- } else {
- queryDictionary[kFIRDLParameterMatchType] = @"unique";
- }
- if (weakMatchEndpoint != nil) {
- queryDictionary[kFIRDLParameterWeakMatchEndpoint] = weakMatchEndpoint;
- }
- if (minAppVersion != nil) {
- queryDictionary[kFIRDLParameterMinimumAppVersion] = minAppVersion;
- }
- if (matchMessage != nil) {
- queryDictionary[kFIRDLParameterMatchMessage] = matchMessage;
- }
- NSString *scheme = [URLScheme lowercaseString];
- NSString *queryString = FIRDLURLQueryStringFromDictionary(queryDictionary);
- NSString *urlString = [NSString stringWithFormat:@"%@://google/link/%@", scheme, queryString];
- return [NSURL URLWithString:urlString];
- }
- BOOL FIRDLOSVersionSupported(NSString *_Nullable systemVersion, NSString *minSupportedVersion) {
- systemVersion = systemVersion ?: [UIDevice currentDevice].systemVersion;
- return [systemVersion compare:minSupportedVersion options:NSNumericSearch] != NSOrderedAscending;
- }
- NSDate *_Nullable FIRDLAppInstallationDate() {
- NSURL *documentsDirectoryURL =
- [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
- inDomains:NSUserDomainMask] firstObject];
- if (!documentsDirectoryURL) {
- return nil;
- }
- NSDictionary<NSString *, id> *attributes =
- [[NSFileManager defaultManager] attributesOfItemAtPath:documentsDirectoryURL.path error:NULL];
- if (attributes[NSFileCreationDate] &&
- [attributes[NSFileCreationDate] isKindOfClass:[NSDate class]]) {
- return attributes[NSFileCreationDate];
- }
- return nil;
- }
- NSString *FIRDLDeviceModelName() {
- // this method will return string like iPad3,3
- // for Simulator this will be x86_64
- static NSString *machineString = @"";
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- size_t size;
- // compute string size
- if (sysctlbyname("hw.machine", NULL, &size, NULL, 0) == 0) {
- // get device name
- char *machine = calloc(1, size);
- if (sysctlbyname("hw.machine", machine, &size, NULL, 0) == 0) {
- machineString = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
- }
- free(machine);
- }
- });
- return machineString;
- }
- NSString *FIRDLDeviceLocale() {
- // expected return value from this method looks like: @"en-US"
- return [[[NSLocale currentLocale] localeIdentifier] stringByReplacingOccurrencesOfString:@"_"
- withString:@"-"];
- }
- NSString *FIRDLDeviceLocaleRaw() {
- return [[NSLocale currentLocale] localeIdentifier];
- }
- NSString *FIRDLDeviceTimezone() {
- NSString *timeZoneName = [[NSTimeZone localTimeZone] name];
- return timeZoneName;
- }
- BOOL FIRDLIsURLForAllowedCustomDomain(NSURL *URL) {
- if (URL) {
- for (NSURL *allowedCustomDomain in FIRDLCustomDomains) {
- // At least one custom domain host name should match at a minimum.
- if ([URL.absoluteString hasPrefix:allowedCustomDomain.absoluteString]) {
- NSString *urlWithoutDomainURIPrefix =
- [URL.absoluteString substringFromIndex:allowedCustomDomain.absoluteString.length];
- // The urlWithoutDomainURIPrefix should be starting with '/' or '?' otherwise it means the
- // allowed domain is not exactly matching the incoming URL domain prefix.
- if ([urlWithoutDomainURIPrefix hasPrefix:@"/"] ||
- [urlWithoutDomainURIPrefix hasPrefix:@"?"]) {
- // For a valid custom domain DL Suffix the urlWithoutDomainURIPrefix should have:
- // 1. At least one path exists OR
- // 2. Should have a link query param with an http/https link
- NSURLComponents *components =
- [[NSURLComponents alloc] initWithString:urlWithoutDomainURIPrefix];
- if (components.path && components.path.length > 1) {
- // Have a path exists. So valid custom domain.
- return true;
- }
- if (components.queryItems && components.queryItems.count > 0) {
- for (NSURLQueryItem *queryItem in components.queryItems) {
- // Checks whether we have a link query param
- if ([queryItem.name caseInsensitiveCompare:@"link"] == NSOrderedSame) {
- // Checks whether link query param value starts with http/https
- if (queryItem.value && ([queryItem.value hasPrefix:@"http://"] ||
- [queryItem.value hasPrefix:@"https://"])) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- }
- return false;
- }
- /* We are validating following domains in proper format.
- *.page.link
- *.app.goo.gl
- *.page.link/i/
- *.app.goo.gl/i/
- */
- BOOL FIRDLIsAValidDLWithFDLDomain(NSURL *_Nullable URL) {
- BOOL matchesRegularExpression = false;
- NSString *urlStr = URL.absoluteString;
- if ([URL.host containsString:@".page.link"] || [URL.host containsString:@".app.goo.gl"] ||
- [URL.host containsString:@".app.google"]) {
- // Matches the *.page.link and *.app.goo.gl domains.
- matchesRegularExpression =
- ([urlStr rangeOfString:
- @"^https?://"
- @"[a-zA-Z0-9]+((\\.app\\.goo\\.gl)|(\\.page\\.link)|(\\.app\\.google))((\\/"
- @"?\\?.*link=https?.*)|(\\/[a-zA-Z0-9-_]+)((\\/?\\?.*=.*)?$|$))"
- options:NSRegularExpressionSearch]
- .location != NSNotFound);
- if (!matchesRegularExpression) {
- // Matches the *.page.link/i/ and *.app.goo.gl/i/ domains.
- // Checks whether the URL is of the format :
- // http(s)://$DOMAIN(.page.link or .app.goo.gl)/i/$ANYTHING
- matchesRegularExpression =
- ([urlStr
- rangeOfString:
- @"^https?:\\/\\/"
- @"[a-zA-Z0-9]+((\\.app\\.goo\\.gl)|(\\.page\\.link)|(\\.app\\.google))\\/i\\/.*$"
- options:NSRegularExpressionSearch]
- .location != NSNotFound);
- }
- }
- return matchesRegularExpression;
- }
- /*
- DL can be parsed if it :
- 1. Has http(s)://goo.gl/app* or http(s)://page.link/app* format
- 2. OR http(s)://$DomainPrefix.page.link or http(s)://$DomainPrefix.app.goo.gl domain with specific
- format
- 3. OR the domain is a listed custom domain
- */
- BOOL FIRDLCanParseUniversalLinkURL(NSURL *_Nullable URL) {
- // Handle universal links with format |https://goo.gl/app/<appcode>?<parameters>|.
- // Also support page.link format.
- BOOL isDDLWithAppcodeInPath = ([URL.host isEqual:@"goo.gl"] || [URL.host isEqual:@"page.link"] ||
- [URL.host isEqual:@"app.google"]) &&
- [URL.path hasPrefix:@"/app"];
- return isDDLWithAppcodeInPath || FIRDLIsAValidDLWithFDLDomain(URL) ||
- FIRDLIsURLForAllowedCustomDomain(URL);
- }
- BOOL FIRDLMatchesShortLinkFormat(NSURL *URL) {
- // Short Durable Link URLs always have a path or it should be a custom domain.
- BOOL hasPathOrCustomDomain = URL.path.length > 0 || FIRDLIsURLForAllowedCustomDomain(URL);
- // Must be able to parse (also checks if the URL conforms to *.app.goo.gl/* or goo.gl/app/* or
- // *.page.link or custom domain with valid suffix)
- BOOL canParse = FIRDLCanParseUniversalLinkURL(URL);
- // Path cannot be prefixed with /link/dismiss
- BOOL isDismiss = [[URL.path lowercaseString] hasPrefix:@"/link/dismiss"];
- // Checks short link format by having only one path after domain prefix.
- BOOL matchesRegularExpression =
- ([URL.path rangeOfString:@"/[^/]+" options:NSRegularExpressionSearch].location != NSNotFound);
- return hasPathOrCustomDomain && !isDismiss && canParse && matchesRegularExpression;
- }
- NSString *FIRDLMatchTypeStringFromServerString(NSString *_Nullable serverMatchTypeString) {
- static NSDictionary *matchMap;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- matchMap = @{
- @"WEAK" : @"weak",
- @"DEFAULT" : @"default",
- @"UNIQUE" : @"unique",
- };
- });
- 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
- #endif // TARGET_OS_IOS
|