2
0
MasterPassword/External/google-plus-ios-sdk/OpenSource/GTL/GTLUtilities.m

359 lines
12 KiB
Mathematica
Raw Normal View History

/* Copyright (c) 2011 Google Inc.
*
* 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 "GTLUtilities.h"
#include <objc/runtime.h>
@implementation GTLUtilities
#pragma mark String encoding
// URL Encoding
+ (NSString *)stringByURLEncodingString:(NSString *)str {
NSString *result = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
// NSURL's stringByAddingPercentEscapesUsingEncoding: does not escape
// some characters that should be escaped in URL parameters, like / and ?;
// we'll use CFURL to force the encoding of those
//
// Reference: http://www.ietf.org/rfc/rfc3986.txt
const CFStringRef kCharsToForceEscape = CFSTR("!*'();:@&=+$,/?%#[]");
+ (NSString *)stringByURLEncodingForURI:(NSString *)str {
NSString *resultStr = str;
CFStringRef originalString = (CFStringRef) str;
CFStringRef leaveUnescaped = NULL;
CFStringRef escapedStr;
escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
originalString,
leaveUnescaped,
kCharsToForceEscape,
kCFStringEncodingUTF8);
if (escapedStr) {
resultStr = [(id)CFMakeCollectable(escapedStr) autorelease];
}
return resultStr;
}
+ (NSString *)stringByURLEncodingStringParameter:(NSString *)str {
// For parameters, we'll explicitly leave spaces unescaped now, and replace
// them with +'s
NSString *resultStr = str;
CFStringRef originalString = (CFStringRef) str;
CFStringRef leaveUnescaped = CFSTR(" ");
CFStringRef escapedStr;
escapedStr = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
originalString,
leaveUnescaped,
kCharsToForceEscape,
kCFStringEncodingUTF8);
if (escapedStr) {
NSMutableString *mutableStr = [NSMutableString stringWithString:(NSString *)escapedStr];
CFRelease(escapedStr);
// replace spaces with plusses
[mutableStr replaceOccurrencesOfString:@" "
withString:@"+"
options:0
range:NSMakeRange(0, [mutableStr length])];
resultStr = mutableStr;
}
return resultStr;
}
+ (NSString *)stringByPercentEncodingUTF8ForString:(NSString *)inputStr {
// Encode per http://bitworking.org/projects/atom/rfc5023.html#rfc.section.9.7
//
// This is used for encoding upload slug headers
//
// Step through the string as UTF-8, and replace characters outside 20..7E
// (and the percent symbol itself, 25) with percent-encodings
//
// We avoid creating an encoding string unless we encounter some characters
// that require it
const char* utf8 = [inputStr UTF8String];
if (utf8 == NULL) {
return nil;
}
NSMutableString *encoded = nil;
for (unsigned int idx = 0; utf8[idx] != '\0'; idx++) {
unsigned char currChar = (unsigned char)utf8[idx];
if (currChar < 0x20 || currChar == 0x25 || currChar > 0x7E) {
if (encoded == nil) {
// Start encoding and catch up on the character skipped so far
encoded = [[[NSMutableString alloc] initWithBytes:utf8
length:idx
encoding:NSUTF8StringEncoding] autorelease];
}
// append this byte as a % and then uppercase hex
[encoded appendFormat:@"%%%02X", currChar];
} else {
// This character does not need encoding
//
// Encoded is nil here unless we've encountered a previous character
// that needed encoding
[encoded appendFormat:@"%c", currChar];
}
}
if (encoded) {
return encoded;
}
return inputStr;
}
#pragma mark Key-Value Coding Searches in an Array
+ (NSArray *)objectsFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath {
// Step through all entries, get the value from
// the key path, and see if it's equal to the
// desired value
NSMutableArray *results = [NSMutableArray array];
for(id obj in sourceArray) {
id val = [obj valueForKeyPath:keyPath];
if (GTL_AreEqualOrBothNil(val, desiredValue)) {
// found a match; add it to the results array
[results addObject:obj];
}
}
return results;
}
+ (id)firstObjectFromArray:(NSArray *)sourceArray
withValue:(id)desiredValue
forKeyPath:(NSString *)keyPath {
for (id obj in sourceArray) {
id val = [obj valueForKeyPath:keyPath];
if (GTL_AreEqualOrBothNil(val, desiredValue)) {
// found a match; return it
return obj;
}
}
return nil;
}
#pragma mark Version helpers
// compareVersion compares two strings in 1.2.3.4 format
// missing fields are interpreted as zeros, so 1.2 = 1.2.0.0
+ (NSComparisonResult)compareVersion:(NSString *)ver1 toVersion:(NSString *)ver2 {
static NSCharacterSet* dotSet = nil;
if (dotSet == nil) {
dotSet = [[NSCharacterSet characterSetWithCharactersInString:@"."] retain];
}
if (ver1 == nil) ver1 = @"";
if (ver2 == nil) ver2 = @"";
NSScanner* scanner1 = [NSScanner scannerWithString:ver1];
NSScanner* scanner2 = [NSScanner scannerWithString:ver2];
[scanner1 setCharactersToBeSkipped:dotSet];
[scanner2 setCharactersToBeSkipped:dotSet];
int partA1 = 0, partA2 = 0, partB1 = 0, partB2 = 0;
int partC1 = 0, partC2 = 0, partD1 = 0, partD2 = 0;
if ([scanner1 scanInt:&partA1] && [scanner1 scanInt:&partB1]
&& [scanner1 scanInt:&partC1] && [scanner1 scanInt:&partD1]) {
}
if ([scanner2 scanInt:&partA2] && [scanner2 scanInt:&partB2]
&& [scanner2 scanInt:&partC2] && [scanner2 scanInt:&partD2]) {
}
if (partA1 != partA2) return ((partA1 < partA2) ? NSOrderedAscending : NSOrderedDescending);
if (partB1 != partB2) return ((partB1 < partB2) ? NSOrderedAscending : NSOrderedDescending);
if (partC1 != partC2) return ((partC1 < partC2) ? NSOrderedAscending : NSOrderedDescending);
if (partD1 != partD2) return ((partD1 < partD2) ? NSOrderedAscending : NSOrderedDescending);
return NSOrderedSame;
}
#pragma mark URL builder
+ (NSURL *)URLWithString:(NSString *)urlString
queryParameters:(NSDictionary *)queryParameters {
if ([urlString length] == 0) return nil;
NSString *fullURLString;
if ([queryParameters count] > 0) {
NSMutableArray *queryItems = [NSMutableArray arrayWithCapacity:[queryParameters count]];
// sort the custom parameter keys so that we have deterministic parameter
// order for unit tests
NSArray *queryKeys = [queryParameters allKeys];
NSArray *sortedQueryKeys = [queryKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
for (NSString *paramKey in sortedQueryKeys) {
NSString *paramValue = [queryParameters valueForKey:paramKey];
NSString *paramItem = [NSString stringWithFormat:@"%@=%@",
[self stringByURLEncodingStringParameter:paramKey],
[self stringByURLEncodingStringParameter:paramValue]];
[queryItems addObject:paramItem];
}
NSString *paramStr = [queryItems componentsJoinedByString:@"&"];
BOOL hasQMark = ([urlString rangeOfString:@"?"].location == NSNotFound);
char joiner = hasQMark ? '?' : '&';
fullURLString = [NSString stringWithFormat:@"%@%c%@",
urlString, joiner, paramStr];
} else {
fullURLString = urlString;
}
NSURL *result = [NSURL URLWithString:fullURLString];
return result;
}
#pragma mark Collections
+ (NSMutableDictionary *)newStaticDictionary {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
// make the dictionary ineligible for garbage collection
#if !GTL_IPHONE
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:dict];
#endif
return dict;
}
+ (NSDictionary *)mergedClassDictionaryForSelector:(SEL)selector
startClass:(Class)startClass
ancestorClass:(Class)ancestorClass
cache:(NSMutableDictionary *)cache {
NSDictionary *result;
@synchronized(cache) {
result = [cache objectForKey:startClass];
if (result == nil) {
// Collect the class's dictionary.
NSDictionary *classDict = [startClass performSelector:selector];
// Collect the parent class's merged dictionary.
NSDictionary *parentClassMergedDict;
if ([startClass isEqual:ancestorClass]) {
parentClassMergedDict = nil;
} else {
Class parentClass = class_getSuperclass(startClass);
parentClassMergedDict =
[GTLUtilities mergedClassDictionaryForSelector:selector
startClass:parentClass
ancestorClass:ancestorClass
cache:cache];
}
// Merge this class's into the parent's so things properly override.
NSMutableDictionary *mergeDict;
if (parentClassMergedDict != nil) {
mergeDict =
[NSMutableDictionary dictionaryWithDictionary:parentClassMergedDict];
} else {
mergeDict = [NSMutableDictionary dictionary];
}
if (classDict != nil) {
[mergeDict addEntriesFromDictionary:classDict];
}
// Make an immutable version.
result = [NSDictionary dictionaryWithDictionary:mergeDict];
// Save it.
[cache setObject:result forKey:(id<NSCopying>)startClass];
}
}
return result;
}
@end
// isEqual: has the fatal flaw that it doesn't deal well with the receiver
// being nil. We'll use this utility instead.
BOOL GTL_AreEqualOrBothNil(id obj1, id obj2) {
if (obj1 == obj2) {
return YES;
}
if (obj1 && obj2) {
BOOL areEqual = [obj1 isEqual:obj2];
return areEqual;
}
return NO;
}
BOOL GTL_AreBoolsEqual(BOOL b1, BOOL b2) {
// avoid comparison problems with boolean types by negating
// both booleans
return (!b1 == !b2);
}
NSNumber *GTL_EnsureNSNumber(NSNumber *num) {
// If the server returned a string object where we expect a number, try
// to make a number object.
if ([num isKindOfClass:[NSString class]]) {
NSNumber *newNum;
NSString *str = (NSString *)num;
if ([str rangeOfString:@"."].location != NSNotFound) {
// This is a floating-point number.
// Force the parser to use '.' as the decimal separator.
static NSLocale *usLocale = nil;
@synchronized([GTLUtilities class]) {
if (usLocale == nil) {
usLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
}
newNum = [NSDecimalNumber decimalNumberWithString:(NSString*)num
locale:(id)usLocale];
}
} else {
// NSDecimalNumber +decimalNumberWithString:locale:
// does not correctly create an NSNumber for large values like
// 71100000000007780.
if ([str hasPrefix:@"-"]) {
newNum = [NSNumber numberWithLongLong:[str longLongValue]];
} else {
const char *utf8 = [str UTF8String];
unsigned long long ull = strtoull(utf8, NULL, 10);
newNum = [NSNumber numberWithUnsignedLongLong:ull];
}
}
if (newNum) {
num = newNum;
}
}
return num;
}