mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
369 lines
14 KiB
Objective-C
369 lines
14 KiB
Objective-C
/*
|
|
* 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 available 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(void) {
|
|
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(void) {
|
|
// 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(void) {
|
|
// expected return value from this method looks like: @"en-US"
|
|
return [[[NSLocale currentLocale] localeIdentifier] stringByReplacingOccurrencesOfString:@"_"
|
|
withString:@"-"];
|
|
}
|
|
|
|
NSString *FIRDLDeviceLocaleRaw(void) {
|
|
return [[NSLocale currentLocale] localeIdentifier];
|
|
}
|
|
|
|
NSString *FIRDLDeviceTimezone(void) {
|
|
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
|