TwitterImagePipeline/Project/TIPURLStringCoding.m (81 lines of code) (raw):

// // TIPURLStringCoding.m // TwitterImagePipeline // // Created on 8/12/16. // Copyright © 2020 Twitter. All rights reserved. // #import "TIP_Project.h" #import "TIPURLStringCoding.h" NS_ASSUME_NONNULL_BEGIN static const char kHexDigits[] = "0123456789ABCDEF"; NSString * __nullable TIPURLEncodeString(NSString * __nullable string) { if (0 == string.length) { return @""; } const char *stringAsUTF8 = [string UTF8String]; if (stringAsUTF8 == NULL) { NSMutableString *byteString = [[NSMutableString alloc] init]; for (NSUInteger i = 0; i < string.length; i++) { unichar uchar = [string characterAtIndex:i]; [byteString appendFormat:@"%02X ", uchar]; } TIPLogError(@"No UTF8String for NSString:\n%@", byteString); return nil; } NSUInteger encodedLength = 0; BOOL needsEncoding = NO; for (const char *c = stringAsUTF8; *c; c++) { switch (*c) { case '0' ... '9': case 'A' ... 'Z': case 'a' ... 'z': case '-': case '.': case '_': case '~': encodedLength++; break; default: encodedLength += 3; needsEncoding = YES; break; } } if (!needsEncoding) { return string; } char *encodedBytes = malloc(encodedLength); if (NULL == encodedBytes) { TIPLogError(@"Out of memory"); return nil; } char *outPtr = encodedBytes; for (const unsigned char *c = (const unsigned char *)stringAsUTF8; *c; c++) { switch (*c) { case '0' ... '9': case 'A' ... 'Z': case 'a' ... 'z': case '-': case '.': case '_': case '~': *outPtr++ = (char)*c; break; default: *outPtr++ = '%'; *outPtr++ = kHexDigits[(*c>>4)&0xf]; *outPtr++ = kHexDigits[*c&0xf]; break; } } NSString *encodedString = [[NSString alloc] initWithBytesNoCopy:encodedBytes length:encodedLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; if (encodedString == nil) { TIPLogError(@"Can't create encodedString from '%@'", string); free(encodedBytes); } return encodedString; } NSString * __nullable TIPURLDecodeString(NSString * __nullable string, BOOL replacePlussesWithSpaces) { NSString *s = string; // replace the '+' first since if the '+' is encoded we want to preserve its value if (replacePlussesWithSpaces) { s = [s stringByReplacingOccurrencesOfString:@"+" withString:@" "]; } // the deprecated [s stringByReplacingPercentExcapesUsingEncoding:NSUTF8StringEncoding] // used to return @"" for @"" . the replacement method returns nil. // // by checking length, we preserve the old behavior for this caller in case anything depended upon it. return s.length ? [s stringByRemovingPercentEncoding] : s; } NS_ASSUME_NONNULL_END