views:

15541

answers:

12

I'd like to do base64 encoding and decoding. But I could not find any support from iPhone SDK.

Any suggestion?

Thanks.

A: 

Base64 Encoding in Cocoa. Should work on Cocoa Touch since it's OpenSSL.

codelogic
Are you sure OpenSLL library can be used by SDK apps?I don't see their headers in the SDK.
mfazekas
+10  A: 

There's a nice code sample at the bottom of this post. Very self-contained...

http://www.cocoadev.com/index.pl?BaseSixtyFour

Greg Bernhardt
+21  A: 

This is a good use case for Objective C categories.

For Base64 encoding:

#import <Foundation/NSString.h>

@interface NSString (NSStringAdditions)

+ (NSString *) base64StringFromData:(NSData *)data length:(int)length;

@end

-------------------------------------------

#import "NSStringAdditions.h"

static char base64EncodingTable[64] = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
  'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
  'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
  'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
};

@implementation NSString (NSStringAdditions)

+ (NSString *) base64StringFromData: (NSData *)data length: (int)length {
  unsigned long ixtext, lentext;
  long ctremaining;
  unsigned char input[3], output[4];
  short i, charsonline = 0, ctcopy;
  const unsigned char *raw;
  NSMutableString *result;

  lentext = [data length]; 
  if (lentext < 1)
    return @"";
  result = [NSMutableString stringWithCapacity: lentext];
  raw = [data bytes];
  ixtext = 0; 

  while (true) {
    ctremaining = lentext - ixtext;
    if (ctremaining <= 0) 
       break;        
    for (i = 0; i < 3; i++) { 
       unsigned long ix = ixtext + i;
       if (ix < lentext)
          input[i] = raw[ix];
       else
  input[i] = 0;
  }
  put[0] = (input[0] & 0xFC) >> 2;
  output[1] = ((input[0] & 0x03) << 4) | ((input[1] & 0xF0) >> 4);
  output[2] = ((input[1] & 0x0F) << 2) | ((input[2] & 0xC0) >> 6);
  output[3] = input[2] & 0x3F;
  ctcopy = 4;
  switch (ctremaining) {
    case 1: 
      ctcopy = 2; 
      break;
    case 2: 
      ctcopy = 3; 
      break;
  }

  for (i = 0; i < ctcopy; i++)
     [result appendString: [NSString stringWithFormat: @"%c", base64EncodingTable[output[i]]]];

  for (i = ctcopy; i < 4; i++)
     [result appendString: @"="];

  ixtext += 3;
  charsonline += 4;

  if ((length > 0) && (charsonline >= length))
    charsonline = 0;

  return result;
}

@end

For Base64 decoding:

#import <Foundation/Foundation.h>

@class NSString;

@interface NSData (NSDataAdditions)

+ (NSData *) base64DataFromString:(NSString *)string;

-------------------------------------------

#import "NSDataAdditions.h"

@implementation NSData (NSDataAdditions)

+ (NSData *) base64DataFromString: (NSString *)string {
  unsigned long ixtext, lentext;
  unsigned char ch, input[4], output[3];
  short i, ixinput;
  Boolean flignore, flendtext = false;
  const char *temporary;
  NSMutableData *result;

  if (!string)
    return [NSData data];

  ixtext = 0;
  temporary = [string UTF8String];
  lentext = [string length];
  result = [NSMutableData dataWithCapacity: lentext];
  ixinput = 0;
  while (true) {
    if (ixtext >= lentext)
      break;
    ch = temporary[ixtext++];
    flignore = false;
    if ((ch >= 'A') && (ch <= 'Z'))
      ch = ch - 'A';
    else if ((ch >= 'a') && (ch <= 'z'))
      ch = ch - 'a' + 26;
    else if ((ch >= '0') && (ch <= '9'))
      ch = ch - '0' + 52;
    else if (ch == '+')
      ch = 62;
    else if (ch == '=')
      flendtext = true;
    else if (ch == '/')
      ch = 63;
    else
      flignore = true; 

    if (!flignore) {
      short ctcharsinput = 3;
      Boolean flbreak = false;

      if (flendtext) {
         if (ixinput == 0)
           break;                
         if ((ixinput == 1) || (ixinput == 2)) {
           ctcharsinput = 1;
         else
           ctcharsinput = 2;

         ixinput = 3;
         flbreak = true;
      }

      input[ixinput++] = ch;

      if (ixinput == 4)
        ixinput = 0;

      output[0] = (input[0] << 2) | ((input[1] & 0x30) >> 4);
      output[1] = ((input[1] & 0x0F) << 4) | ((input[2] & 0x3C) >> 2);
      output[2] = ((input[2] & 0x03) << 6) | (input[3] & 0x3F);

      for (i = 0; i < ctcharsinput; i++)
        [result appendBytes: &output[i] length: 1];
    }

    if (flbreak)
      break;
  }
  return result;
}

@end
Alex Reynolds
If Obj-C is anything like C, you should be able to do this:static char base64EncodingTable[64] = "ABCDE[etc]789+/";
Artelius
when using base64StringFromData should the length should be the length of the NSData? I only getting the first 4 characters by using the NSData Length. Thanks
hipplar
Before I forget. Thanks for this example....
hipplar
I found why I was only getting 4 characters... There needs to be a } before the return for the while() loop. I would edit it but I doesn't look like i can.
hipplar
what is the length parameter 64 or 128 or some other value ???
Biranchi
IS it supporting 64 or 128 bytes in this code?
monish
+1  A: 

put[0] = (input[0] & 0xFC) >> 2;

should say:

output[0] = (input[0] & 0xFC) >> 2;

+8  A: 

Since this seems to be the number one google hit on base64 encoding and iphone, I felt like sharing my experience with the code snippet above.

It works, but it is extremely slow. A benchmark on a random image (0.4 mb) took 37 seconds on native iphone. The main reason is probably all the OOP magic - single char NSStrings etc, which are only autoreleased after the encoding is done.

Another suggestion posted here (ab)uses the openssl library, which feels like overkill as well.

The code below takes 70 ms - that's a 500 times speedup. This only does base64 encoding (decoding will follow as soon as I encounter it)

+ (NSString *) base64StringFromData: (NSData *)data length: (int)length {
int lentext = [data length]; 
if (lentext < 1) return @"";

char *outbuf = malloc(lentext*4/3+4); // add 4 to be sure

if ( !outbuf ) return nil;

const unsigned char *raw = [data bytes];

int inp = 0;
int outp = 0;
int do_now = lentext - (lentext%3);

for ( outp = 0, inp = 0; inp < do_now; inp += 3 )
{
    outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
    outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
    outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
    outbuf[outp++] = base64EncodingTable[raw[inp+2] & 0x3F];
}

if ( do_now < lentext )
{
    char tmpbuf[2] = {0,0};
    int left = lentext%3;
    for ( int i=0; i < left; i++ )
    {
        tmpbuf[i] = raw[do_now+i];
    }
    raw = tmpbuf;
    outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
    outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
    if ( left == 2 ) outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
}

NSString *ret = [[[NSString alloc] initWithBytes:outbuf length:outp encoding:NSASCIIStringEncoding] autorelease];
free(outbuf);

return ret;
}

I left out the line-cutting since I didn't need it, but it's trivial to add.

For those who are interested in optimizing: the goal is to minimize what happens in the main loop. Therefore all logic to deal with the last 3 bytes is treated outside the loop.

Also, try to work on data in-place, without additional copying to/from buffers. And reduce any arithmetic to the bare minimum.

Observe that the bits that are put together to look up an entry in the table, would not overlap when they were to be orred together without shifting. A major improvement could therefore be to use 4 separate 256 byte lookup tables and eliminate the shifts, like this:

outbuf[outp++] = base64EncodingTable1[(raw[inp] & 0xFC)];
outbuf[outp++] = base64EncodingTable2[(raw[inp] & 0x03) | (raw[inp+1] & 0xF0)];
outbuf[outp++] = base64EncodingTable3[(raw[inp+1] & 0x0F) | (raw[inp+2] & 0xC0)];
outbuf[outp++] = base64EncodingTable4[raw[inp+2] & 0x3F];

Of course you could take it a whole lot further, but that's beyond the scope here.

mvds
+1. Really nice answer.
Alex Reynolds
Hmm. I couldn't get this to work. I observe a different base64 encoding than my expected value. Have you tested this with the examples in RFC 4648? http://tools.ietf.org/html/rfc4648
Alex Reynolds
Struggling to see what base64EncodingTable1, base64EncodingTable2, base64EncodingTable3 and base64EncodingTable4 are referencing?
Jamie Chapman
+2  A: 

In mvds's excellent improvement, there are two problems. Change code to this:

raw = tmpbuf;
inp = 0;
outbuf[outp++] = base64EncodingTable[(raw[inp] & 0xFC) >> 2];
outbuf[outp++] = base64EncodingTable[((raw[inp] & 0x03) << 4) | ((raw[inp+1] & 0xF0) >> 4)];
if ( left == 2 ) outbuf[outp++] = base64EncodingTable[((raw[inp+1] & 0x0F) << 2) | ((raw[inp+2] & 0xC0) >> 6)];
else outbuf[outp++] = '=';
outbuf[outp++] = '=';
+1  A: 

Glad people liked it. The end-game was a little flawed I must admit. Besides rightly setting inp=0 you should either also increase tmpbuf's size to 3, like

unsigned char tmpbuf[3] = {0,0,0};

or leave out the orring of raw[inp+2]; if we would have a raw[inp+2] != 0 for this chunk we would still be in the loop of course...

Either way works, you might consider keeping the final table lookup block identical to the one in the loop for clarity. In the final version I used I did

while ( outp%4 ) outbuf[outp++] = '=';

To add the ==

Sorry I didn't check RFC's and stuff, should have done a better job!

mvds
@mvds, you already have an account here, as your previous answer is actually a different account. Also, this should be either an edit to that or a comment.
Alastair Pitts
@alastair, you seem to get an "account" every time you post an answer without registering, after cleaning cookies. I wasn't able to connect to my first "account" (even with the same email and ip address) so I just put it there as a new answer, sorry for that. -- just registered!
mvds
A: 

I can't get the decoding to work in Alex Reynolds' post. It compiles with an error at

if ((ixinput == 1) || (ixinput == 2)) {

ctcharsinput = 1;
else

with "expected expression before else"

I've removed the { since it didn't seem needed but then I get flbreak undeclared in the

if(flbreak) statement at the end.

Maybe the code is working but it is taking a really really long time. The encode takes less than a second though.

Any help or a reposting of the decode code would be greatly appreciated since it just seems to have some typos etc.

TinyTechnician
A: 

Anyone know how to get rid of the warning: pointer targets in assignment differ in signedness ?

Rob
A: 

change the following line char tmpbuf[2] = {0,0}; to unsigned char tmpbuf[3] = {0,0,0};

Satya
A: 

I got the same problem with decoding and it returns nothing after remove the extra "{" above. Anyone can post this code again ?

very appreciate !

A: 
#import "NSDataAdditions.h"
@implementation NSData (NSDataAdditions)

+ (NSData *) base64DataFromString: (NSString *)string {
  unsigned long ixtext, lentext;
  unsigned char ch, input[4], output[3];
  short i, ixinput;
  Boolean flignore, flendtext = false;
  const char *temporary;
  NSMutableData *result;

  if (!string)
    return [NSData data];

  ixtext = 0;
  temporary = [string UTF8String];
  lentext = [string length];
  result = [NSMutableData dataWithCapacity: lentext];
  ixinput = 0;

  while (true) {
    if (ixtext >= lentext)
      break;
    ch = temporary[ixtext++];
    flignore = false;

    if ((ch >= 'A') && (ch <= 'Z'))
      ch = ch - 'A';
    else if ((ch >= 'a') && (ch <= 'z'))
      ch = ch - 'a' + 26;
    else if ((ch >= '0') && (ch <= '9'))
      ch = ch - '0' + 52;
    else if (ch == '+')
      ch = 62;
    else if (ch == '=')
      flendtext = true;
    else if (ch == '/')
      ch = 63;
    else
      flignore = true;

    if (!flignore) {
      short ctcharsinput = 3;
      Boolean flbreak = false;

      if (flendtext) {
         if (ixinput == 0)
           break;              
         if ((ixinput == 1) || (ixinput == 2))
           ctcharsinput = 1;
         else
           ctcharsinput = 2;
         ixinput = 3;
         flbreak = true;
      }

      input[ixinput++] = ch;

      if (ixinput == 4){
        ixinput = 0;
        output[0] = (input[0] << 2) | ((input[1] & 0x30) >> 4);
        output[1] = ((input[1] & 0x0F) << 4) | ((input[2] & 0x3C) >> 2);
        output[2] = ((input[2] & 0x03) << 6) | (input[3] & 0x3F);
        for (i = 0; i < ctcharsinput; i++)
        [result appendBytes: &output[i] length: 1];
      }
    if (flbreak)
      break;
    }
  }
  return result;
}
@end
alpha09jp