Swiftgram/submodules/Stripe/Sources/STPFormEncoder.m
2019-08-12 23:42:50 +03:00

189 lines
7.7 KiB
Objective-C
Executable File

//
// STPFormEncoder.m
// Stripe
//
// Created by Jack Flintermann on 1/8/15.
// Copyright (c) 2015 Stripe, Inc. All rights reserved.
//
#import "STPFormEncoder.h"
#import "STPCardParams.h"
FOUNDATION_EXPORT NSString * STPPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * STPQueryStringFromParameters(NSDictionary *parameters);
@implementation STPFormEncoder
+ (NSString *)stringByReplacingSnakeCaseWithCamelCase:(NSString *)input {
NSArray *parts = [input componentsSeparatedByString:@"_"];
NSMutableString *camelCaseParam = [NSMutableString string];
[parts enumerateObjectsUsingBlock:^(NSString *part, NSUInteger idx, __unused BOOL *stop) {
[camelCaseParam appendString:(idx == 0 ? part : [part capitalizedString])];
}];
return [camelCaseParam copy];
}
+ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject<STPFormEncodable> *)object {
NSDictionary *keyPairs = [self keyPairDictionaryForObject:object];
NSString *rootObjectName = [object.class rootObjectName];
NSDictionary *dict = rootObjectName != nil ? @{ rootObjectName: keyPairs } : keyPairs;
return [STPQueryStringFromParameters(dict) dataUsingEncoding:NSUTF8StringEncoding];
}
+ (NSDictionary *)keyPairDictionaryForObject:(nonnull NSObject<STPFormEncodable> *)object {
NSMutableDictionary *keyPairs = [NSMutableDictionary dictionary];
[[object.class propertyNamesToFormFieldNamesMapping] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull propertyName, NSString * _Nonnull formFieldName, __unused BOOL * _Nonnull stop) {
id value = [self formEncodableValueForObject:[object valueForKey:propertyName]];
if (value) {
keyPairs[formFieldName] = value;
}
}];
[object.additionalAPIParameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull additionalFieldName, id _Nonnull additionalFieldValue, __unused BOOL * _Nonnull stop) {
id value = [self formEncodableValueForObject:additionalFieldValue];
if (value) {
keyPairs[additionalFieldName] = value;
}
}];
return [keyPairs copy];
}
+ (id)formEncodableValueForObject:(NSObject *)object {
if ([object conformsToProtocol:@protocol(STPFormEncodable)]) {
return [self keyPairDictionaryForObject:(NSObject<STPFormEncodable>*)object];
} else {
return object;
}
}
+ (NSString *)stringByURLEncoding:(NSString *)string {
return STPPercentEscapedStringFromString(string);
}
+ (NSString *)queryStringFromParameters:(NSDictionary *)parameters {
return STPQueryStringFromParameters(parameters);
}
@end
// This code is adapted from https://github.com/AFNetworking/AFNetworking/blob/master/AFNetworking/AFURLRequestSerialization.m . The only modifications are to replace the AF namespace with the STP namespace to avoid collisions with apps that are using both Stripe and AFNetworking.
NSString * STPPercentEscapedStringFromString(NSString *string) {
static NSString * const kSTPCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kSTPCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kSTPCharactersGeneralDelimitersToEncode stringByAppendingString:kSTPCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}
#pragma mark -
@interface STPQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
@implementation STPQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
_field = field;
_value = value;
return self;
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return STPPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", STPPercentEscapedStringFromString([self.field description]), STPPercentEscapedStringFromString([self.value description])];
}
}
@end
#pragma mark -
FOUNDATION_EXPORT NSArray * STPQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * STPQueryStringPairsFromKeyAndValue(NSString *key, id value);
NSString * STPQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (STPQueryStringPair *pair in STPQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * STPQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return STPQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * STPQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSString *descriptionSelector = NSStringFromSelector(@selector(description));
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:descriptionSelector ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:STPQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:STPQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:STPQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[STPQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}