#include "base64.h"
#include "string.h"
-static const char Base64[] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-static const char Pad64 = '=';
-
+static const unsigned char base64_tab[256] = {
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 00-07 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 08-0f */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 10-17 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 18-1f */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 20-2f */
+ 255, 255, 255, 62, 255, 255, 255, 63, /* 28-2f */
+ 52 , 53, 54, 55, 56, 57, 58, 59, /* 30-37 */
+ 60 , 61, 255, 255, 255, 255, 255, 255, /* 38-3f */
+ 255, 0, 1, 2, 3, 4, 5, 6, /* 40-47 */
+ 7 , 8, 9, 10, 11, 12, 13, 14, /* 48-4f */
+ 15 , 16, 17, 18, 19, 20, 21, 22, /* 50-57 */
+ 23 , 24, 25, 255, 255, 255, 255, 255, /* 58-5f */
+ 255, 26, 27, 28, 29, 30, 31, 32, /* 60-6f */
+ 33 , 34, 35, 36, 37, 38, 39, 40, /* 68-6f */
+ 41 , 42, 43, 44, 45, 46, 47, 48, /* 70-77 */
+ 49 , 50, 51, 255, 255, 255, 255, 255, /* 78-7f */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 80-87 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 88-8f */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 90-97 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* 98-9f */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* a0-a7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* a8-af */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* b0-b7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* b8-bf */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* c0-c7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* c8-cf */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* d0-d7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* d8-df */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* e0-e7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* e8-ef */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* f0-f7 */
+ 255, 255, 255, 255, 255, 255, 255, 255, /* f8-ff */
+};
+
+/** Maximal possible size of the decoded data. */
+#define BASE64_MAX_DECODED_SIZE(_encoded_size) ((_encoded_size) / 4 * 3)
+
+#define PAD64 '='
/**
* base64-decode a buffer.
*
* \param src The buffer to decode.
- * \param target Result is stored here.
- * \param targsize Number of bytes of \a target.
+ * \param encoded_size The special value -1 means: look for terminating zero byte.
+ * \param result Points to dynamically allocated target buffer on success.
+ * \param decoded_size Number of bytes written to \a result.
*
* Skips all whitespace anywhere. Converts characters, four at a time, starting
* at (or after) src from base - 64 numbers into three 8 bit bytes in the
* target area.
*
- * \return The number of data bytes stored at the target, -E_BASE64 on errors.
+ * It is OK to pass a \p NULL pointer as \a decoded_size. The result is
+ * terminated with a zero byte.
+ *
+ * \return Standard. The contents of result \a and \a decoded_size are
+ * undefined on failure.
*/
-int base64_decode(char const *src, unsigned char *target, size_t targsize)
+int base64_decode(char const *src, size_t encoded_size, char **result,
+ size_t *decoded_size)
{
- unsigned int tarindex, state;
- int ch;
- char *pos;
-
- state = 0;
- tarindex = 0;
-
- while ((ch = *src++) != '\0') {
- if (para_isspace(ch)) /* Skip whitespace anywhere. */
+ size_t i, j, state; /* source/target indices */
+ const char *end = src + encoded_size, *p;
+ unsigned char *target, uch;
+
+ if (encoded_size == (size_t)-1)
+ encoded_size = strlen(src);
+ target = para_malloc(BASE64_MAX_DECODED_SIZE(encoded_size) + 1);
+
+ for (
+ i = 0, j = 0, state = 0;
+ i < encoded_size && (uch = src[i]) != '\0';
+ i++
+ ) {
+ if (para_isspace(uch)) /* Skip whitespace anywhere. */
continue;
-
- if (ch == Pad64)
+ if (uch == PAD64)
break;
-
- pos = strchr(Base64, ch);
- if (pos == NULL) /* A non-base64 character. */
- return -E_BASE64;
-
+ if (base64_tab[uch] == 255) /* A non-base64 character. */
+ goto fail;
+ uch = base64_tab[uch];
switch (state) {
case 0:
- if (tarindex >= targsize)
- return -E_BASE64;
- target[tarindex] = (pos - Base64) << 2;
- state = 1;
+ target[j] = uch << 2;
break;
case 1:
- if (tarindex + 1 >= targsize)
- return -E_BASE64;
- target[tarindex] |= (pos - Base64) >> 4;
- target[tarindex + 1] = ((pos - Base64) & 0x0f) << 4;
- tarindex++;
- state = 2;
+ target[j] |= uch >> 4;
+ j++;
+ target[j] = (uch & 0x0f) << 4;
break;
case 2:
- if (tarindex + 1 >= targsize)
- return -E_BASE64;
- target[tarindex] |= (pos - Base64) >> 2;
- target[tarindex + 1] = ((pos - Base64) & 0x03) << 6;
- tarindex++;
- state = 3;
+ target[j] |= uch >> 2;
+ j++;
+ target[j] = (uch & 0x03) << 6;
break;
case 3:
- if (tarindex >= targsize)
- return -E_BASE64;
- target[tarindex] |= pos - Base64;
- tarindex++;
- state = 0;
+ target[j] |= uch;
+ j++;
break;
}
+ state = (state + 1) % 4;
}
-
+ p = (i < encoded_size)? src + i : NULL;
/*
* We are done decoding Base-64 chars. Let's see if we ended
* on a byte boundary, and/or with erroneous trailing characters.
*/
-
- if (ch == Pad64) { /* We got a pad char. */
- ch = *src++; /* Skip it, get next. */
+ if (p && *p == PAD64) { /* We got a pad char. Skip it, get next. */
+ p++;
switch (state) {
- case 0: /* Invalid = in first position */
- case 1: /* Invalid = in second position */
- return -E_BASE64;
+ case 0: /* Invalid = in first position */
+ case 1: /* Invalid = in second position */
+ goto fail;
- case 2: /* Valid, means one byte of info */
+ case 2: /* Valid, means one byte of info */
/* Skip any number of spaces. */
- for (; ch != '\0'; ch = *src++)
- if (!isspace(ch))
+ for (; p < end && *p != '\0'; p++)
+ if (!para_isspace(*p))
break;
/* Make sure there is another trailing = sign. */
- if (ch != Pad64)
- return -E_BASE64;
- ch = *src++; /* Skip the = */
+ if (*p != PAD64)
+ goto fail;
/* Fall through to "single trailing =" case. */
- /* FALLTHROUGH */
+ p++;
- case 3: /* Valid, means two bytes of info */
+ case 3: /* Valid, means two bytes of info */
/*
* We know this char is an =. Is there anything but
* whitespace after it?
*/
- for (; ch != '\0'; ch = *src++)
- if (!isspace(ch))
- return -E_BASE64;
-
+ for (; p < end && *p != '\0'; p++)
+ if (!para_isspace(*p))
+ goto fail;
/*
* Now make sure for cases 2 and 3 that the "extra"
* bits that slopped past the last full byte were
* zeros. If we don't check them, they become a
* subliminal channel.
*/
- if (target[tarindex] != 0)
- return -E_BASE64;
+ if (target[j] != 0)
+ goto fail;
}
} else {
/*
* have no partial bytes lying around.
*/
if (state != 0)
- return -E_BASE64;
+ goto fail;
}
- return tarindex;
+ /* success */
+ target[j] = '\0'; /* just to be sure */
+ if (decoded_size)
+ *decoded_size = j;
+ *result = (char *)target;
+ return 1;
+fail:
+ free(target);
+ return -E_BASE64;
}
/**
- * uudecode a buffer.
+ * Decode a buffer using the uuencode Base64 algorithm.
*
* \param src The buffer to decode.
- * \param target Result buffer.
- * \param targsize The length of \a target in bytes.
+ * \param encoded_size Number of input bytes in the source buffer.
+ * \param result Contains the decoded data on success.
+ * \param decoded_size Number of output bytes on success.
+ *
+ * This is just a simple wrapper for \ref base64_decode() which strips
+ * whitespace.
*
- * This is just a simple wrapper for base64_decode() which strips whitespace.
+ * \return The return value of the underlying call to \ref base64_decode().
*
- * \return The return value of the underlying call to base64_decode().
+ * \sa uuencode(1), uudecode(1).
*/
-int uudecode(const char *src, unsigned char *target, size_t targsize)
+int uudecode(char const *src, size_t encoded_size, char **result,
+ size_t *decoded_size)
{
- int len;
- char *encoded, *p;
+ const char *end = src + encoded_size, *p;
- /* copy the 'readonly' source */
- encoded = para_strdup(src);
/* skip whitespace and data */
- for (p = encoded; *p == ' ' || *p == '\t'; p++)
+ for (p = src; p < end && (*p == ' ' || *p == '\t'); p++)
;
- for (; *p != '\0' && *p != ' ' && *p != '\t'; p++)
+ for (; p < end && *p != '\0' && *p != ' ' && *p != '\t'; p++)
;
/* and remove trailing whitespace because base64_decode needs this */
- *p = '\0';
- len = base64_decode(encoded, target, targsize);
- free(encoded);
- return len;
+ return base64_decode(src, p - src, result, decoded_size);
}