-
Notifications
You must be signed in to change notification settings - Fork 299
/
Copy pathADJLinkResolution.m
206 lines (166 loc) · 6.19 KB
/
ADJLinkResolution.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//
// ADJLinkResolution.m
// Adjust
//
// Created by Pedro S. on 26.04.21.
// Copyright © 2021 adjust GmbH. All rights reserved.
//
#import "ADJLinkResolution.h"
static NSUInteger kMaxRecursions = 10;
@interface ADJLinkResolutionDelegate : NSObject<NSURLSessionTaskDelegate>
+ (nonnull ADJLinkResolutionDelegate *)sharedInstance;
+ (nullable NSURL *)convertUrlToHttps:(nullable NSURL *)url;
@end
@implementation ADJLinkResolutionDelegate
- (nonnull instancetype)init {
self = [super init];
return self;
}
+ (nonnull ADJLinkResolutionDelegate *)sharedInstance {
static ADJLinkResolutionDelegate *sharedInstance = nil;
static dispatch_once_t onceToken; // onceToken = 0
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
{
NSURL *_Nullable convertedUrl = [ADJLinkResolutionDelegate convertUrlToHttps:request.URL];
if (request.URL != nil && convertedUrl != nil && ! [request.URL isEqual:convertedUrl]) {
completionHandler([ADJLinkResolutionDelegate replaceUrlWithRequest:request
urlToReplace:convertedUrl]);
} else {
completionHandler(request);
}
}
+ (nullable NSURL *)convertUrlToHttps:(nullable NSURL *)url {
if (url == nil) {
return nil;
}
if (! [url.absoluteString hasPrefix:@"http:"]) {
return url;
}
NSString *_Nonnull urlStringWithoutPrefix = [url.absoluteString substringFromIndex:5];
return [NSURL URLWithString:
[NSString stringWithFormat:@"https:%@", urlStringWithoutPrefix]];
}
+ (NSURLRequest *)replaceUrlWithRequest:(NSURLRequest *)request
urlToReplace:(nonnull NSURL *)urlToReplace
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setURL:urlToReplace];
return [mutableRequest copy];
}
@end
@implementation ADJLinkResolution
+ (void)resolveLinkWithUrl:(nonnull NSURL *)url
resolveUrlSuffixArray:(nullable NSArray<NSString *> *)resolveUrlSuffixArray
callback:(nonnull void (^)(NSURL *_Nullable resolvedLink))callback
{
if (callback == nil) {
return;
}
if (url == nil) {
callback(nil);
return;
}
if (! [ADJLinkResolution urlMatchesSuffixWithHost:url.host
suffixArray:resolveUrlSuffixArray])
{
callback(url);
return;
}
ADJLinkResolutionDelegate *_Nonnull linkResolutionDelegate =
[ADJLinkResolutionDelegate sharedInstance];
NSURLSession *_Nonnull session =
[NSURLSession
sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration
delegate:linkResolutionDelegate
delegateQueue:nil];
NSURL *_Nullable httpsUrl = [ADJLinkResolutionDelegate convertUrlToHttps:url];
NSURLSessionDataTask *task =
[session
dataTaskWithURL:httpsUrl
completionHandler:
^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error)
{
// bootstrap the recursion of resolving the link
[ADJLinkResolution
resolveLinkWithResponseUrl:response != nil ? response.URL : nil
previousUrl:httpsUrl
recursionNumber:0
session:session
callback:callback];
}];
[task resume];
}
+ (void)resolveLinkWithResponseUrl:(nullable NSURL *)responseUrl
previousUrl:(nullable NSURL *)previousUrl
recursionNumber:(NSUInteger)recursionNumber
session:(nonnull NSURLSession *)session
callback:(nonnull void (^)(NSURL *_Nullable resolvedLink))callback
{
// return (possible nil) previous url when the current one does not exist
if (responseUrl == nil) {
callback(previousUrl);
return;
}
// return found url with expected host
if ([ADJLinkResolution isTerminalUrlWithHost:responseUrl.host]) {
callback(responseUrl);
return;
}
// return previous (non-nil) url when it reached the max number of recursive tries
if (recursionNumber >= kMaxRecursions) {
callback(responseUrl);
return;
}
// when found a non expected url host, use it to recursively resolve the link
NSURLSessionDataTask *task =
[session
dataTaskWithURL:responseUrl
completionHandler:
^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error)
{
[ADJLinkResolution resolveLinkWithResponseUrl:response != nil ? response.URL : nil
previousUrl:responseUrl
recursionNumber:(recursionNumber + 1)
session:session
callback:callback];
}];
[task resume];
}
+ (BOOL)isTerminalUrlWithHost:(nullable NSString *)urlHost {
if (urlHost == nil) {
return NO;
}
NSArray<NSString *> *_Nonnull terminalUrlHostSuffixArray =
@[@"adjust.com", @"adj.st", @"go.link"];
return [ADJLinkResolution urlMatchesSuffixWithHost:urlHost
suffixArray:terminalUrlHostSuffixArray];
}
+ (BOOL)urlMatchesSuffixWithHost:(nullable NSString *)urlHost
suffixArray:(nullable NSArray<NSString *> *)suffixArray
{
if (urlHost == nil) {
return NO;
}
if (suffixArray == nil) {
return NO;
}
for (NSString *_Nonnull expectedHostSuffix in suffixArray) {
if ([urlHost hasSuffix:expectedHostSuffix]) {
return YES;
}
}
return NO;
}
@end