element-ios/Riot/Utils/UniversalLink.m

209 lines
6.0 KiB
Objective-C

/*
Copyright 2020 Vector Creations Ltd
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 "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