Refactor Base64 decoding from HTTP server to str.c

Also add a Base64 encoding function, not tested yet
This commit is contained in:
Andrew Bettison 2013-11-15 17:09:55 +10:30
parent 4fd94783d3
commit f2b652c094
3 changed files with 190 additions and 38 deletions

View File

@ -344,9 +344,14 @@ static int _skip_literal_nocase(struct http_request *r, const char *literal)
return *literal == '\0';
}
static int is_http_space(char c)
{
return c == ' ' || c == '\t';
}
static int _skip_optional_space(struct http_request *r)
{
while (!_run_out(r) && (*r->cursor == ' ' || *r->cursor == '\t'))
while (!_run_out(r) && is_http_space(*r->cursor))
++r->cursor;
return 1;
}
@ -546,43 +551,7 @@ static int _parse_content_type(struct http_request *r, struct mime_content_type
static size_t _parse_base64(struct http_request *r, char *bin, size_t binsize)
{
uint8_t buf = 0;
size_t digits = 0;
size_t bytes = 0;
for (; !_run_out(r) && is_base64_digit(*r->cursor); _skip_optional_space(r), ++r->cursor) {
if (bytes < binsize) {
uint8_t d = base64_digit(*r->cursor);
switch (digits++ & 3) {
case 0:
buf = d << 2;
break;
case 1:
if (bin)
bin[bytes] = buf | (d >> 4);
++bytes;
buf = d << 4;
break;
case 2:
if (bin)
bin[bytes] = buf | (d >> 2);
++bytes;
buf = d << 6;
break;
case 3:
if (bin)
bin[bytes] = buf | d;
++bytes;
break;
}
}
}
if (digits == 0)
return 0;
if (!_run_out(r) && is_base64_pad(*r->cursor))
++r->cursor;
if (!_run_out(r) && is_base64_pad(*r->cursor))
++r->cursor;
return bytes;
return base64_decode((unsigned char *)bin, binsize, r->cursor, r->end - r->cursor, &r->cursor, B64_CONSUME_ALL, is_http_space);
}
static int _parse_authorization_credentials_basic(struct http_request *r, struct http_client_credentials_basic *cred, char *buf, size_t bufsz)

122
str.c
View File

@ -79,6 +79,128 @@ size_t strn_fromhex(unsigned char *dstBinary, ssize_t dstlen, const char *srcHex
return dstBinary - dstorig;
}
const char base64_symbols[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','+','/'
};
size_t base64_encode(char *const dstBase64, const unsigned char *src, size_t srclen)
{
char *dst = dstBase64;
unsigned place = 0;
unsigned char buf = 0;
for (; srclen; --srclen, ++src) {
switch (place) {
case 0:
*dst++ = base64_symbols[*src >> 2];
buf = (*src << 4) & 0x3f;
place = 1;
break;
case 1:
*dst++ = base64_symbols[(*src >> 4) | buf];
buf = (*src << 2) & 0x3f;
place = 2;
break;
case 2:
*dst++ = base64_symbols[(*src >> 6) | buf];
*dst++ = base64_symbols[*src & 0x3f];
place = 0;
break;
}
}
if (place)
*dst++ = base64_symbols[buf];
switch (place) {
case 2:
*dst++ = '=';
case 1:
*dst++ = '=';
}
return dst - dstBase64;
}
char *to_base64_str(char *const dstBase64, const unsigned char *srcBinary, size_t srcBytes)
{
dstBase64[base64_encode(dstBase64, srcBinary, srcBytes)] = '\0';
return dstBase64;
}
size_t base64_decode(unsigned char *dstBinary, size_t dstsiz, const char *const srcBase64, size_t srclen,
const char **afterp, int flags, int (*skip_pred)(char))
{
uint8_t buf = 0;
size_t digits = 0;
unsigned pads = 0;
size_t bytes = 0;
const char *const srcend = srcBase64 + srclen;
const char *src = srcBase64;
const char *first_pad = NULL;
for (; srclen == 0 || (src < srcend); ++src) {
int isdigit = is_base64_digit(*src);
int ispad = is_base64_pad(*src);
if (!isdigit && !ispad && skip_pred && skip_pred(*src))
continue;
assert(pads <= 2);
if (pads == 2)
break;
int place = digits & 3;
if (pads == 1) {
if (place == 3)
break;
assert(place == 2);
if (ispad) {
++pads;
continue; // consume trailing space before ending
}
// If only one pad character was present but there should be two, then don't consume the first
// one.
assert(first_pad != NULL);
src = first_pad;
break;
}
assert(pads == 0);
if (ispad && place >= 2) {
first_pad = src;
++pads;
continue;
}
if (!isdigit)
break;
++digits;
if (dstBinary && bytes < dstsiz) {
uint8_t d = base64_digit(*src);
switch (place) {
case 0:
buf = d << 2;
break;
case 1:
dstBinary[bytes++] = buf | (d >> 4);
buf = d << 4;
break;
case 2:
dstBinary[bytes++] = buf | (d >> 2);
buf = d << 6;
break;
case 3:
dstBinary[bytes++] = buf | d;
break;
}
} else if (flags & B64_CONSUME_ALL) {
switch (place) {
case 1: case 2: case 3: ++bytes;
}
} else
break;
}
if (afterp)
*afterp = src;
else if (*src)
return 0;
return bytes;
}
#define _B64 _SERVAL_CTYPE_0_BASE64
#define _BND _SERVAL_CTYPE_0_MULTIPART_BOUNDARY

61
str.h
View File

@ -116,6 +116,67 @@ int fromhexstr(unsigned char *dstBinary, const char *srcHex, size_t nbinary);
*/
size_t strn_fromhex(unsigned char *dstBinary, ssize_t dstlen, const char *src, const char **afterp);
/* -------------------- Base64 encoding and decoding -------------------- */
/* Return the number of bytes required to represent 'binaryBytes' bytes of binary data encoded
* into Base64 form.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
__SERVAL_DNA_STR_INLINE size_t base64_encode_len(size_t binaryBytes) {
return (binaryBytes + 2) / 3 * 4;
}
const char base64_symbols[64];
/* Encode 'srcBytes' bytes of binary data at 'srcBinary' into Base64 representation at 'dstBase64',
* which must point to at least 'base64_encode_len(srcBytes)' bytes. The encoding is terminated
* by a "=" or "==" pad to bring the total number of encoded bytes up to a multiple of 4.
*
* Returns the total number of encoded bytes writtent at 'dstBase64'.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
size_t base64_encode(char *dstBase64, const unsigned char *srcBinary, size_t srcBytes);
/* The same as base64_encode() but appends a terminating NUL character to the encoded string,
* so 'dstBase64' must point to at least 'base64_encode_len(srcBytes) + 1' bytes.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
char *to_base64_str(char *dstBase64, const unsigned char *srcBinary, size_t srcBytes);
#define alloca_base64(buf,len) to_base64_str(alloca(base64_encode_len(len) + 1), (buf), (len))
/* Decode the string at 'srcBase64' as ASCII Base-64, writing up to 'dstsiz' decoded binary bytes at
* 'dstBinary'. Returns the number of decoded binary bytes produced. If 'dstsiz' is zero or
* 'dstBinary' is NULL, no binary bytes are produced and returns zero.
*
* If the 'afterp' pointer is not NULL, then sets *afterp to point to the first character in
* 'srcBase64' where decoding stopped for whatever reason.
*
* If 'srclen' is 0, then the string at 'stcBase64' is assumed to be NUL-terminated, and decoding
* runs until the first non-Base64-digit is encountered. If 'srclen' is nonzero, then decoding will
* cease at the first non-Base64-digit or when 'srclen' bytes at 'srcBase64' have been decoded,
* whichever comes first.
*
* If 'skip_pred' is not NULL, then all leading, internal and trailing characters C which are not a
* valid Base64 digit or pad '=' will be skipped if skip_pred(C) returns true. Otherwise, decoding
* ends at C.
*
* If the B64_CONSUME_ALL flag is set, then once the 'dstsiz' limit is reached (or if 'dstBinary' is
* NULL), the Base64 decoding process continues without actually writing decoded bytes, but instead
* counts them and advances through the 'srcBase64' buffer as usual. The return value is then the
* number of binary bytes that would be decoded were all available Base64 decoded from 'srcBase64',
* and *afterp points to the first character beyond the end of the decoded source characters.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/
size_t base64_decode(unsigned char *dstBinary, size_t dstsiz, const char *const srcBase64, size_t srclen,
const char **afterp, int flags, int (*skip_pred)(char));
#define B64_CONSUME_ALL (1 << 0)
/* -------------------- Character classes -------------------- */
#define _SERVAL_CTYPE_0_BASE64_MASK 0x3f