201 lines
5.6 KiB
Objective-C
201 lines
5.6 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2020 Vector Creations Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "UniversalLink.h"
|
|
#import "NSArray+Element.h"
|
|
|
|
@implementation UniversalLink
|
|
|
|
- (id)initWithUrl:(NSURL *)url
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_url = url;
|
|
|
|
// Extract required parameters from the link
|
|
[self parsePathAndQueryParamsForURL:url];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithUrl:(NSURL *)url updatedFragment:(NSString *)fragment
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
_url = url;
|
|
|
|
// Update the url with the fragment
|
|
NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];
|
|
components.fragment = fragment;
|
|
[self parsePathAndQueryParamsForURL:components.URL];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
Extract params from the URL fragment part (after '#') of a vector.im Universal link:
|
|
|
|
The fragment can contain a '?'. So there are two kinds of parameters: path params and query params.
|
|
It is in the form of /[pathParam1]/[pathParam2]?[queryParam1Key]=[queryParam1Value]&[queryParam2Key]=[queryParam2Value]
|
|
*/
|
|
- (void)parsePathAndQueryParamsForURL:(NSURL *)url
|
|
{
|
|
NSArray<NSString*> *pathParams;
|
|
NSMutableDictionary *queryParams = [NSMutableDictionary dictionary];
|
|
|
|
NSArray<NSString*> *fragments = [url.fragment componentsSeparatedByString:@"?"];
|
|
|
|
// Extract path params
|
|
pathParams = [[fragments[0] stringByRemovingPercentEncoding] componentsSeparatedByString:@"/"];
|
|
|
|
// Remove the first empty path param string
|
|
pathParams = [pathParams filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]];
|
|
|
|
// URL decode each path param
|
|
pathParams = [pathParams vc_map:^id _Nonnull(NSString * _Nonnull item) {
|
|
return [item stringByRemovingPercentEncoding];
|
|
}];
|
|
|
|
// Extract query params
|
|
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
|
|
for (NSURLQueryItem *item in components.queryItems)
|
|
{
|
|
if (item.value)
|
|
{
|
|
NSString *key = item.name;
|
|
NSString *value = item.value;
|
|
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
|
value = [value stringByRemovingPercentEncoding];
|
|
|
|
if ([key isEqualToString:@"via"])
|
|
{
|
|
// Special case the via parameter
|
|
// As we can have several of them, store each value into an array
|
|
if (!queryParams[key])
|
|
{
|
|
queryParams[key] = [NSMutableArray array];
|
|
}
|
|
|
|
[queryParams[key] addObject:value];
|
|
}
|
|
else
|
|
{
|
|
queryParams[key] = value;
|
|
}
|
|
}
|
|
}
|
|
// Query params are in the form [queryParam1Key]=[queryParam1Value], so the
|
|
// presence of at least one '=' character is mandatory
|
|
if (fragments.count == 2 && (NSNotFound != [fragments[1] rangeOfString:@"="].location))
|
|
{
|
|
for (NSString *keyValue in [fragments[1] componentsSeparatedByString:@"&"])
|
|
{
|
|
// Get the parameter name
|
|
NSString *key = [keyValue componentsSeparatedByString:@"="][0];
|
|
|
|
// Get the parameter value
|
|
NSString *value = [keyValue componentsSeparatedByString:@"="][1];
|
|
if (value.length)
|
|
{
|
|
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
|
value = [value stringByRemovingPercentEncoding];
|
|
|
|
if ([key isEqualToString:@"via"])
|
|
{
|
|
// Special case the via parameter
|
|
// As we can have several of them, store each value into an array
|
|
if (!queryParams[key])
|
|
{
|
|
queryParams[key] = [NSMutableArray array];
|
|
}
|
|
|
|
if (![queryParams[key] containsObject:value])
|
|
{
|
|
[queryParams[key] addObject:value];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
queryParams[key] = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_pathParams = pathParams;
|
|
_queryParams = queryParams;
|
|
}
|
|
|
|
- (NSString *)homeserverUrl
|
|
{
|
|
return _queryParams[@"hs_url"];
|
|
}
|
|
|
|
- (NSString *)identityServerUrl
|
|
{
|
|
return _queryParams[@"is_url"];
|
|
}
|
|
|
|
- (NSArray<NSString *> *)via
|
|
{
|
|
NSArray<NSString *> *result = _queryParams[@"via"];
|
|
if (!result)
|
|
{
|
|
return @[];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
- (BOOL)isEqual:(id)other
|
|
{
|
|
if (other == self)
|
|
return YES;
|
|
|
|
if (![other isKindOfClass:UniversalLink.class])
|
|
return NO;
|
|
|
|
UniversalLink *otherLink = (UniversalLink *)other;
|
|
|
|
return [_url isEqual:otherLink.url]
|
|
&& [_pathParams isEqualToArray:otherLink.pathParams]
|
|
&& [_queryParams isEqualToDictionary:otherLink.queryParams];
|
|
}
|
|
|
|
- (NSUInteger)hash
|
|
{
|
|
NSUInteger prime = 31;
|
|
NSUInteger result = 1;
|
|
|
|
result = prime * result + [_url hash];
|
|
result = prime * result + [_pathParams hash];
|
|
result = prime * result + [_queryParams hash];
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
return [NSString stringWithFormat:@"<UniversalLink: %@>", _url.absoluteString];
|
|
}
|
|
|
|
#pragma mark - NSCopying
|
|
- (id)copyWithZone:(NSZone *)zone
|
|
{
|
|
UniversalLink *link = [[self.class allocWithZone:zone] init];
|
|
|
|
link->_url = [_url copyWithZone:zone];
|
|
link->_pathParams = [_pathParams copyWithZone:zone];
|
|
link->_queryParams = [_queryParams copyWithZone:zone];
|
|
|
|
return link;
|
|
}
|
|
|
|
@end
|