blob: 6bc496ca34aa5a987e4d1419aced5d33bebf1242 [file] [log] [blame]
//
// GTMStringEncoding.m
//
// Copyright 2009 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 "GTMStringEncoding.h"
NSString *const GTMStringEncodingErrorDomain = @"com.google.GTMStringEncodingErrorDomain";
NSString *const GTMStringEncodingBadCharacterIndexKey = @"GTMStringEncodingBadCharacterIndexKey";
enum {
kUnknownChar = -1,
kPaddingChar = -2,
kIgnoreChar = -3
};
@implementation GTMStringEncoding
+ (id)binaryStringEncoding {
return [self stringEncodingWithString:@"01"];
}
+ (id)hexStringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"0123456789ABCDEF"];
[ret addDecodeSynonyms:@"AaBbCcDdEeFf"];
return ret;
}
+ (id)rfc4648Base32StringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"];
[ret setPaddingChar:'='];
[ret setDoPad:YES];
return ret;
}
+ (id)rfc4648Base32HexStringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"0123456789ABCDEFGHIJKLMNOPQRSTUV"];
[ret setPaddingChar:'='];
[ret setDoPad:YES];
return ret;
}
+ (id)crockfordBase32StringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"0123456789ABCDEFGHJKMNPQRSTVWXYZ"];
[ret addDecodeSynonyms:
@"0oO1iIlLAaBbCcDdEeFfGgHhJjKkMmNnPpQqRrSsTtVvWwXxYyZz"];
return ret;
}
+ (id)rfc4648Base64StringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"];
[ret setPaddingChar:'='];
[ret setDoPad:YES];
return ret;
}
+ (id)rfc4648Base64WebsafeStringEncoding {
GTMStringEncoding *ret = [self stringEncodingWithString:
@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"];
[ret setPaddingChar:'='];
[ret setDoPad:YES];
return ret;
}
GTM_INLINE int lcm(int a, int b) {
for (int aa = a, bb = b;;) {
if (aa == bb)
return aa;
else if (aa < bb)
aa += a;
else
bb += b;
}
}
+ (id)stringEncodingWithString:(NSString *)string {
return [[[self alloc] initWithString:string] autorelease];
}
- (id)initWithString:(NSString *)string {
if ((self = [super init])) {
charMapData_ = [[string dataUsingEncoding:NSASCIIStringEncoding] retain];
if (!charMapData_) {
_GTMDevLog(@"Unable to convert string to ASCII");
[self release];
return nil;
}
charMap_ = (char *)[charMapData_ bytes];
NSUInteger length = [charMapData_ length];
if (length < 2 || length > 128 || length & (length - 1)) {
_GTMDevLog(@"Length not a power of 2 between 2 and 128");
[self release];
return nil;
}
memset(reverseCharMap_, kUnknownChar, sizeof(reverseCharMap_));
for (unsigned int i = 0; i < length; i++) {
if (reverseCharMap_[(int)charMap_[i]] != kUnknownChar) {
_GTMDevLog(@"Duplicate character at pos %d", i);
[self release];
return nil;
}
reverseCharMap_[(int)charMap_[i]] = i;
}
for (NSUInteger i = 1; i < length; i <<= 1)
shift_++;
mask_ = (1 << shift_) - 1;
padLen_ = lcm(8, shift_) / shift_;
}
return self;
}
- (void)dealloc {
[charMapData_ release];
[super dealloc];
}
- (NSString *)description {
// TODO(iwade) track synonyms
return [NSString stringWithFormat:@"<Base%d StringEncoder: %@>",
1 << shift_, charMapData_];
}
- (void)addDecodeSynonyms:(NSString *)synonyms {
char *buf = (char *)[synonyms cStringUsingEncoding:NSASCIIStringEncoding];
int val = kUnknownChar;
while (*buf) {
int c = *buf++;
if (reverseCharMap_[c] == kUnknownChar) {
reverseCharMap_[c] = val;
} else {
val = reverseCharMap_[c];
}
}
}
- (void)ignoreCharacters:(NSString *)chars {
char *buf = (char *)[chars cStringUsingEncoding:NSASCIIStringEncoding];
while (*buf) {
int c = *buf++;
_GTMDevAssert(reverseCharMap_[c] == kUnknownChar,
@"Character already mapped");
reverseCharMap_[c] = kIgnoreChar;
}
}
- (BOOL)doPad {
return doPad_;
}
- (void)setDoPad:(BOOL)doPad {
doPad_ = doPad;
}
- (void)setPaddingChar:(char)c {
paddingChar_ = c;
reverseCharMap_[(int)c] = kPaddingChar;
}
- (NSString *)encode:(NSData *)inData {
return [self encode:inData error:NULL];
}
- (NSString *)encode:(NSData *)inData error:(NSError **)error {
NSUInteger inLen = [inData length];
if (inLen <= 0) {
return @"";
}
unsigned char *inBuf = (unsigned char *)[inData bytes];
NSUInteger inPos = 0;
NSUInteger outLen = (inLen * 8 + shift_ - 1) / shift_;
if (doPad_) {
outLen = ((outLen + padLen_ - 1) / padLen_) * padLen_;
}
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
NSUInteger outPos = 0;
unsigned int buffer = inBuf[inPos++];
int bitsLeft = 8;
while (bitsLeft > 0 || inPos < inLen) {
if (bitsLeft < shift_) {
if (inPos < inLen) {
buffer <<= 8;
buffer |= (inBuf[inPos++] & 0xff);
bitsLeft += 8;
} else {
int pad = shift_ - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int idx = (buffer >> (bitsLeft - shift_)) & mask_;
bitsLeft -= shift_;
outBuf[outPos++] = charMap_[idx];
}
if (doPad_) {
while (outPos < outLen)
outBuf[outPos++] = paddingChar_;
}
[outData setLength:outPos];
NSString *value = [[[NSString alloc] initWithData:outData
encoding:NSASCIIStringEncoding] autorelease];
if (!value) {
if (error) {
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorUnableToConverToAscii
userInfo:nil];
}
}
return value;
}
- (NSString *)encodeString:(NSString *)inString {
return [self encodeString:inString error:NULL];
}
- (NSString *)encodeString:(NSString *)inString error:(NSError **)error {
NSData *data = [inString dataUsingEncoding:NSUTF8StringEncoding];
if (!data) {
if (error) {
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorUnableToConverToUTF8
userInfo:nil];
}
return nil;
}
return [self encode:data error:error];
}
- (NSData *)decode:(NSString *)inString {
return [self decode:inString error:NULL];
}
- (NSData *)decode:(NSString *)inString error:(NSError **)error {
char *inBuf = (char *)[inString cStringUsingEncoding:NSASCIIStringEncoding];
if (!inBuf) {
if (error) {
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorUnableToConverToAscii
userInfo:nil];
}
return nil;
}
NSUInteger inLen = strlen(inBuf);
NSUInteger outLen = inLen * shift_ / 8;
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
NSUInteger outPos = 0;
unsigned int buffer = 0;
int bitsLeft = 0;
BOOL expectPad = NO;
for (NSUInteger i = 0; i < inLen; i++) {
int val = reverseCharMap_[(int)inBuf[i]];
switch (val) {
case kIgnoreChar:
break;
case kPaddingChar:
expectPad = YES;
break;
case kUnknownChar: {
if (error) {
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInteger:i]
forKey:GTMStringEncodingBadCharacterIndexKey];
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorUnknownCharacter
userInfo:userInfo];
}
return nil;
}
default:
if (expectPad) {
if (error) {
NSDictionary *userInfo =
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInteger:i]
forKey:GTMStringEncodingBadCharacterIndexKey];
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorExpectedPadding
userInfo:userInfo];
}
return nil;
}
buffer <<= shift_;
buffer |= val & mask_;
bitsLeft += shift_;
if (bitsLeft >= 8) {
outBuf[outPos++] = (unsigned char)(buffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
break;
}
}
if (bitsLeft && buffer & ((1 << bitsLeft) - 1)) {
if (error) {
*error = [NSError errorWithDomain:GTMStringEncodingErrorDomain
code:GTMStringEncodingErrorIncompleteTrailingData
userInfo:nil];
}
return nil;
}
// Shorten buffer if needed due to padding chars
[outData setLength:outPos];
return outData;
}
- (NSString *)stringByDecoding:(NSString *)inString {
return [self stringByDecoding:inString error:NULL];
}
- (NSString *)stringByDecoding:(NSString *)inString error:(NSError **)error {
NSData *ret = [self decode:inString error:error];
NSString *value = nil;
if (ret) {
value = [[[NSString alloc] initWithData:ret
encoding:NSUTF8StringEncoding] autorelease];
}
return value;
}
@end