diff --git a/http_server.c b/http_server.c index ff58d585..22c141aa 100644 --- a/http_server.c +++ b/http_server.c @@ -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) diff --git a/str.c b/str.c index 391aef7c..75c30201 100644 --- a/str.c +++ b/str.c @@ -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 diff --git a/str.h b/str.h index 6106b038..9704db27 100644 --- a/str.h +++ b/str.h @@ -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 + */ +__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 + */ +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 + */ +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 + */ +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