blob: 91fed5b269e684da535eb3df910332a1bd6bffbe [file] [log] [blame]
/*
* Copyright (c) 2011-2013 Apple Inc. All rights reserved.
*
* @APPLE_APACHE_LICENSE_HEADER_START@
*
* 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.
*
* @APPLE_APACHE_LICENSE_HEADER_END@
*/
#include "internal.h"
#ifdef __APPLE__
#include <libkern/OSByteOrder.h>
#elif __linux__
#include <endian.h>
#define OSLittleEndian __LITTLE_ENDIAN
#define OSBigEndian __BIG_ENDIAN
#define OSSwapLittleToHostInt16 le16toh
#define OSSwapBigToHostInt16 be16toh
#define OSSwapHostToLittleInt16 htole16
#define OSSwapHostToBigInt16 htobe16
#endif
#if defined(__LITTLE_ENDIAN__)
#define DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST DISPATCH_DATA_FORMAT_TYPE_UTF16LE
#define DISPATCH_DATA_FORMAT_TYPE_UTF16_REV DISPATCH_DATA_FORMAT_TYPE_UTF16BE
#elif defined(__BIG_ENDIAN__)
#define DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST DISPATCH_DATA_FORMAT_TYPE_UTF16BE
#define DISPATCH_DATA_FORMAT_TYPE_UTF16_REV DISPATCH_DATA_FORMAT_TYPE_UTF16LE
#else
#error Unsupported Endianness
#endif
enum {
_DISPATCH_DATA_FORMAT_NONE = 0x1,
_DISPATCH_DATA_FORMAT_UTF8 = 0x2,
_DISPATCH_DATA_FORMAT_UTF16LE = 0x4,
_DISPATCH_DATA_FORMAT_UTF16BE = 0x8,
_DISPATCH_DATA_FORMAT_UTF_ANY = 0x10,
_DISPATCH_DATA_FORMAT_BASE32 = 0x20,
_DISPATCH_DATA_FORMAT_BASE32HEX = 0x40,
_DISPATCH_DATA_FORMAT_BASE64 = 0x80,
};
#pragma mark -
#pragma mark baseXX tables
static const unsigned char base32_encode_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
static const signed char base32_decode_table[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 26,
27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25
};
static const ssize_t base32_decode_table_size =
sizeof(base32_decode_table) / sizeof(*base32_decode_table);
static const unsigned char base32hex_encode_table[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUV";
static const signed char base32hex_decode_table[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -2, -1, -1, -1, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31
};
static const ssize_t base32hex_decode_table_size =
sizeof(base32hex_encode_table) / sizeof(*base32hex_encode_table);
static const unsigned char base64_encode_table[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const signed char base64_decode_table[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, -1, -1, -1, -2, -1, -1, -1, 0, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26,
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
static const ssize_t base64_decode_table_size =
sizeof(base64_decode_table) / sizeof(*base64_decode_table);
#pragma mark -
#pragma mark dispatch_transform_buffer
typedef struct dispatch_transform_buffer_s {
dispatch_data_t data;
uint8_t *start;
union {
uint8_t *u8;
uint16_t *u16;
} ptr;
size_t size;
} dispatch_transform_buffer_s;
#define BUFFER_MALLOC_MAX (100*1024*1024)
static bool
_dispatch_transform_buffer_new(dispatch_transform_buffer_s *buffer,
size_t required, size_t size)
{
size_t remaining = buffer->size - (size_t)(buffer->ptr.u8 - buffer->start);
if (required == 0 || remaining < required) {
if (buffer->start) {
if (buffer->ptr.u8 > buffer->start) {
dispatch_data_t _new = dispatch_data_create(buffer->start,
(size_t)(buffer->ptr.u8 - buffer->start), NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
dispatch_data_t _concat = dispatch_data_create_concat(
buffer->data, _new);
dispatch_release(_new);
dispatch_release(buffer->data);
buffer->data = _concat;
} else {
free(buffer->start);
}
}
buffer->size = required + size;
buffer->start = NULL;
if (buffer->size > 0) {
if (buffer->size > BUFFER_MALLOC_MAX) {
return false;
}
buffer->start = (uint8_t*)malloc(buffer->size);
if (buffer->start == NULL) {
return false;
}
}
buffer->ptr.u8 = buffer->start;
}
return true;
}
#pragma mark -
#pragma mark dispatch_transform_helpers
static dispatch_data_t
_dispatch_data_subrange_map(dispatch_data_t data, const void **ptr,
size_t offset, size_t size)
{
dispatch_data_t subrange, map = NULL;
subrange = dispatch_data_create_subrange(data, offset, size);
if (dispatch_data_get_size(subrange) == size) {
map = dispatch_data_create_map(subrange, ptr, NULL);
}
dispatch_release(subrange);
return map;
}
static dispatch_data_format_type_t
_dispatch_transform_detect_utf(dispatch_data_t data)
{
const void *p;
dispatch_data_t subrange = _dispatch_data_subrange_map(data, &p, 0, 2);
if (subrange == NULL) {
return NULL;
}
const uint16_t ch = *(const uint16_t *)p;
dispatch_data_format_type_t type = DISPATCH_DATA_FORMAT_TYPE_UTF8;
if (ch == 0xfeff) {
type = DISPATCH_DATA_FORMAT_TYPE_UTF16_HOST;
} else if (ch == 0xfffe) {
type = DISPATCH_DATA_FORMAT_TYPE_UTF16_REV;
}
dispatch_release(subrange);
return type;
}
static uint16_t
_dispatch_transform_swap_to_host(uint16_t x, int32_t byteOrder)
{
if (byteOrder == OSLittleEndian) {
return OSSwapLittleToHostInt16(x);
}
return OSSwapBigToHostInt16(x);
}
static uint16_t
_dispatch_transform_swap_from_host(uint16_t x, int32_t byteOrder)
{
if (byteOrder == OSLittleEndian) {
return OSSwapHostToLittleInt16(x);
}
return OSSwapHostToBigInt16(x);
}
#pragma mark -
#pragma mark UTF-8
static uint8_t
_dispatch_transform_utf8_length(uint8_t byte)
{
if ((byte & 0x80) == 0) {
return 1;
} else if ((byte & 0xe0) == 0xc0) {
return 2;
} else if ((byte & 0xf0) == 0xe0) {
return 3;
} else if ((byte & 0xf8) == 0xf0) {
return 4;
}
return 0;
}
static uint32_t
_dispatch_transform_read_utf8_sequence(const uint8_t *bytes)
{
uint32_t wch = 0;
uint8_t seq_length = _dispatch_transform_utf8_length(*bytes);
switch (seq_length) {
case 4:
wch |= (*bytes & 0x7);
wch <<= 6;
break;
case 3:
wch |= (*bytes & 0xf);
wch <<= 6;
break;
case 2:
wch |= (*bytes & 0x1f);
wch <<= 6;
break;
case 1:
wch = (*bytes & 0x7f);
break;
default:
// Not a utf-8 sequence
break;
}
bytes++;
seq_length--;
while (seq_length > 0) {
wch |= (*bytes & 0x3f);
bytes++;
seq_length--;
if (seq_length > 0) {
wch <<= 6;
}
}
return wch;
}
#pragma mark -
#pragma mark UTF-16
static dispatch_data_t
_dispatch_transform_to_utf16(dispatch_data_t data, int32_t byteOrder)
{
__block size_t skip = 0;
__block dispatch_transform_buffer_s buffer = {
.data = dispatch_data_empty,
};
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region,
size_t offset, const void *_buffer, size_t size) {
const uint8_t *src = _buffer;
size_t i, dest_size;
if (offset == 0) {
if (os_mul_and_add_overflow(size, sizeof(uint16_t),
sizeof(uint16_t), &dest_size)) {
return (bool)false;
}
if (!_dispatch_transform_buffer_new(&buffer, dest_size, 0)) {
return (bool)false;
}
// Insert BOM
*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(0xfeff,
byteOrder);
}
// Skip is incremented if the previous block read-ahead into our block
if (skip >= size) {
skip -= size;
return (bool)true;
} else if (skip > 0) {
src += skip;
size -= skip;
skip = 0;
}
for (i = 0; i < size;) {
uint32_t wch = 0;
uint8_t byte_size = _dispatch_transform_utf8_length(*src);
size_t next;
if (byte_size == 0) {
return (bool)false;
} else if (byte_size + i > size) {
// UTF-8 byte sequence spans over into the next block(s)
const void *p;
dispatch_data_t subrange = _dispatch_data_subrange_map(data, &p,
offset + i, byte_size);
if (subrange == NULL) {
return (bool)false;
}
wch = _dispatch_transform_read_utf8_sequence(p);
skip += byte_size - (size - i);
src += byte_size;
i = size;
dispatch_release(subrange);
} else {
wch = _dispatch_transform_read_utf8_sequence(src);
src += byte_size;
i += byte_size;
}
if (os_mul_overflow(size - i, sizeof(uint16_t), &next)) {
return (bool)false;
}
if (wch >= 0xd800 && wch < 0xdfff) {
// Illegal range (surrogate pair)
return (bool)false;
} else if (wch >= 0x10000) {
// Surrogate pair
if (!_dispatch_transform_buffer_new(&buffer, 2 *
sizeof(uint16_t), next)) {
return (bool)false;
}
wch -= 0x10000;
*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
((wch >> 10) & 0x3ff) + 0xd800, byteOrder);
*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
(wch & 0x3ff) + 0xdc00, byteOrder);
} else {
if (!_dispatch_transform_buffer_new(&buffer, 1 *
sizeof(uint16_t), next)) {
return (bool)false;
}
*(buffer.ptr.u16)++ = _dispatch_transform_swap_from_host(
(wch & 0xffff), byteOrder);
}
}
(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
return (bool)true;
});
if (!success) {
(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
dispatch_release(buffer.data);
return NULL;
}
return buffer.data;
}
static dispatch_data_t
_dispatch_transform_from_utf16(dispatch_data_t data, int32_t byteOrder)
{
__block size_t skip = 0;
__block dispatch_transform_buffer_s buffer = {
.data = dispatch_data_empty,
};
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region, size_t offset,
const void *_buffer, size_t size) {
const uint16_t *src = _buffer;
if (offset == 0) {
size_t dest_size = howmany(size, 3) * 2;
// Assume first buffer will be mostly single-byte UTF-8 sequences
if (!_dispatch_transform_buffer_new(&buffer, dest_size, 0)) {
return (bool)false;
}
}
size_t i = 0, max = size / 2;
// Skip is incremented if the previous block read-ahead into our block
if (skip >= size) {
skip -= size;
return (bool)true;
} else if (skip > 0) {
src = (uint16_t *)(((uint8_t *)src) + skip);
size -= skip;
max = (size / 2);
skip = 0;
}
// If the buffer is an odd size, allow read ahead into the next region
if ((size % 2) != 0) {
max += 1;
}
for (i = 0; i < max; i++) {
uint32_t wch = 0;
uint16_t ch;
size_t next;
if ((i == (max - 1)) && (max > (size / 2))) {
// Last byte of an odd sized range
const void *p;
dispatch_data_t range = _dispatch_data_subrange_map(data, &p,
offset + (i * 2), 2);
if (range == NULL) {
return (bool)false;
}
ch = _dispatch_transform_swap_to_host((uint16_t)*(uint64_t*)p,
byteOrder);
dispatch_release(range);
skip += 1;
} else {
ch = _dispatch_transform_swap_to_host(src[i], byteOrder);
}
if (ch == 0xfffe && offset == 0 && i == 0) {
// Wrong-endian BOM at beginning of data
return (bool)false;
} else if (ch == 0xfeff && offset == 0 && i == 0) {
// Correct-endian BOM, skip it
continue;
}
if ((ch >= 0xd800) && (ch <= 0xdbff)) {
// Surrogate pair
wch = ((ch - 0xd800u) << 10);
if (++i >= max) {
// Surrogate byte isn't in this block
const void *p;
dispatch_data_t range = _dispatch_data_subrange_map(data,
&p, offset + (i * 2), 2);
if (range == NULL) {
return (bool)false;
}
ch = _dispatch_transform_swap_to_host(*(uint16_t *)p,
byteOrder);
dispatch_release(range);
skip += 2;
} else {
ch = _dispatch_transform_swap_to_host(src[i], byteOrder);
}
if (!((ch >= 0xdc00) && (ch <= 0xdfff))) {
return (bool)false;
}
wch = (wch | (ch & 0x3ff));
wch += 0x10000;
} else if ((ch >= 0xdc00) && (ch <= 0xdfff)) {
return (bool)false;
} else {
wch = ch;
}
if (os_mul_overflow(max - i, 2, &next)) {
return (bool)false;
}
if (wch < 0x80) {
if (!_dispatch_transform_buffer_new(&buffer, 1, next)) {
return (bool)false;
}
*(buffer.ptr.u8)++ = (uint8_t)(wch & 0xff);
} else if (wch < 0x800) {
if (!_dispatch_transform_buffer_new(&buffer, 2, next)) {
return (bool)false;
}
*(buffer.ptr.u8)++ = (uint8_t)(0xc0 | (wch >> 6));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
} else if (wch < 0x10000) {
if (!_dispatch_transform_buffer_new(&buffer, 3, next)) {
return (bool)false;
}
*(buffer.ptr.u8)++ = (uint8_t)(0xe0 | (wch >> 12));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 6) & 0x3f));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
} else if (wch < 0x200000) {
if (!_dispatch_transform_buffer_new(&buffer, 4, next)) {
return (bool)false;
}
*(buffer.ptr.u8)++ = (uint8_t)(0xf0 | (wch >> 18));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 12) & 0x3f));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | ((wch >> 6) & 0x3f));
*(buffer.ptr.u8)++ = (uint8_t)(0x80 | (wch & 0x3f));
}
}
(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
return (bool)true;
});
if (!success) {
(void)_dispatch_transform_buffer_new(&buffer, 0, 0);
dispatch_release(buffer.data);
return NULL;
}
return buffer.data;
}
static dispatch_data_t
_dispatch_transform_from_utf16le(dispatch_data_t data)
{
return _dispatch_transform_from_utf16(data, OSLittleEndian);
}
static dispatch_data_t
_dispatch_transform_from_utf16be(dispatch_data_t data)
{
return _dispatch_transform_from_utf16(data, OSBigEndian);
}
static dispatch_data_t
_dispatch_transform_to_utf16le(dispatch_data_t data)
{
return _dispatch_transform_to_utf16(data, OSLittleEndian);
}
static dispatch_data_t
_dispatch_transform_to_utf16be(dispatch_data_t data)
{
return _dispatch_transform_to_utf16(data, OSBigEndian);
}
#pragma mark -
#pragma mark base32
static dispatch_data_t
_dispatch_transform_from_base32_with_table(dispatch_data_t data,
const signed char* table, ssize_t table_size)
{
__block uint64_t x = 0, count = 0, pad = 0;
__block dispatch_data_t rv = dispatch_data_empty;
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region,
DISPATCH_UNUSED size_t offset, const void *buffer, size_t size) {
size_t i, dest_size = howmany(size, 8) * 5;
uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
uint8_t *ptr = dest;
if (dest == NULL) {
return (bool)false;
}
const uint8_t *bytes = buffer;
for (i = 0; i < size; i++) {
if (bytes[i] == '\n' || bytes[i] == '\t' || bytes[i] == ' ') {
continue;
}
ssize_t index = bytes[i];
if (index >= table_size || table[index] == -1) {
free(dest);
return (bool)false;
}
count++;
signed char value = table[index];
if (value == -2) {
value = 0;
pad++;
}
x <<= 5;
x += (uint64_t)value;
if ((count & 0x7) == 0) {
*ptr++ = (x >> 32) & 0xff;
*ptr++ = (x >> 24) & 0xff;
*ptr++ = (x >> 16) & 0xff;
*ptr++ = (x >> 8) & 0xff;
*ptr++ = x & 0xff;
}
}
size_t final = (size_t)(ptr - dest);
switch (pad) {
case 1:
final -= 1;
break;
case 3:
final -= 2;
break;
case 4:
final -= 3;
break;
case 6:
final -= 4;
break;
}
dispatch_data_t val = dispatch_data_create(dest, final, NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
dispatch_data_t concat = dispatch_data_create_concat(rv, val);
dispatch_release(val);
dispatch_release(rv);
rv = concat;
return (bool)true;
});
if (!success) {
dispatch_release(rv);
return NULL;
}
return rv;
}
static dispatch_data_t
_dispatch_transform_to_base32_with_table(dispatch_data_t data, const unsigned char* table)
{
size_t total = dispatch_data_get_size(data), dest_size;
__block size_t count = 0;
dest_size = howmany(total, 5);
// <rdar://problem/25676583>
// os_mul_overflow(dest_size, 8, &dest_size)
if (dest_size > SIZE_T_MAX / 8) {
return NULL;
}
dest_size *= 8;
uint8_t *dest = (uint8_t*)malloc(dest_size);
if (dest == NULL) {
return NULL;
}
__block uint8_t *ptr = dest;
/*
0 1 2 3 4
8-bit bytes: xxxxxxxx yyyyyyyy zzzzzzzz xxxxxxxx yyyyyyyy
5-bit chunks: aaaaabbb bbcccccd ddddeeee efffffgg ggghhhhh
*/
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region,
size_t offset, const void *buffer, size_t size) {
const uint8_t *bytes = buffer;
size_t i;
for (i = 0; i < size; i++, count++) {
uint8_t curr = bytes[i], last = 0;
if ((count % 5) != 0) {
if (i == 0) {
const void *p;
dispatch_data_t subrange = _dispatch_data_subrange_map(data,
&p, offset - 1, 1);
if (subrange == NULL) {
return (bool)false;
}
last = *(uint8_t*)p;
dispatch_release(subrange);
} else {
last = bytes[i - 1];
}
}
switch (count % 5) {
case 0:
// a
*ptr++ = table[(curr >> 3) & 0x1fu];
break;
case 1:
// b + c
*ptr++ = table[((last << 2)|(curr >> 6)) & 0x1f];
*ptr++ = table[(curr >> 1) & 0x1f];
break;
case 2:
// d
*ptr++ = table[((last << 4)|(curr >> 4)) & 0x1f];
break;
case 3:
// e + f
*ptr++ = table[((last << 1)|(curr >> 7)) & 0x1f];
*ptr++ = table[(curr >> 2) & 0x1f];
break;
case 4:
// g + h
*ptr++ = table[((last << 3)|(curr >> 5)) & 0x1f];
*ptr++ = table[curr & 0x1f];
break;
}
}
// Last region, insert padding bytes, if needed
if (offset + size == total) {
switch (count % 5) {
case 0:
break;
case 1:
// b[4:2]
*ptr++ = table[(bytes[size-1] << 2) & 0x1c];
break;
case 2:
// d[4]
*ptr++ = table[(bytes[size-1] << 4) & 0x10];
break;
case 3:
// e[4:1]
*ptr++ = table[(bytes[size-1] << 1) & 0x1e];
break;
case 4:
// g[2:3]
*ptr++ = table[(bytes[size-1] << 3) & 0x18];
break;
}
switch (count % 5) {
case 0:
break;
case 1:
*ptr++ = '='; // c
*ptr++ = '='; // d
case 2:
*ptr++ = '='; // e
case 3:
*ptr++ = '='; // f
*ptr++ = '='; // g
case 4:
*ptr++ = '='; // h
break;
}
}
return (bool)true;
});
if (!success) {
free(dest);
return NULL;
}
return dispatch_data_create(dest, dest_size, NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
}
static dispatch_data_t
_dispatch_transform_from_base32(dispatch_data_t data)
{
return _dispatch_transform_from_base32_with_table(data, base32_decode_table,
base32_decode_table_size);
}
static dispatch_data_t
_dispatch_transform_to_base32(dispatch_data_t data)
{
return _dispatch_transform_to_base32_with_table(data, base32_encode_table);
}
static dispatch_data_t
_dispatch_transform_from_base32hex(dispatch_data_t data)
{
return _dispatch_transform_from_base32_with_table(data,
base32hex_decode_table, base32hex_decode_table_size);
}
static dispatch_data_t
_dispatch_transform_to_base32hex(dispatch_data_t data)
{
return _dispatch_transform_to_base32_with_table(data,
base32hex_encode_table);
}
#pragma mark -
#pragma mark base64
static dispatch_data_t
_dispatch_transform_from_base64(dispatch_data_t data)
{
__block uint64_t x = 0, count = 0;
__block size_t pad = 0;
__block dispatch_data_t rv = dispatch_data_empty;
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region,
DISPATCH_UNUSED size_t offset, const void *buffer, size_t size) {
size_t i, dest_size = howmany(size, 4) * 3;
uint8_t *dest = (uint8_t*)malloc(dest_size * sizeof(uint8_t));
uint8_t *ptr = dest;
if (dest == NULL) {
return (bool)false;
}
const uint8_t *bytes = buffer;
for (i = 0; i < size; i++) {
if (bytes[i] == '\n' || bytes[i] == '\t' || bytes[i] == ' ') {
continue;
}
ssize_t index = bytes[i];
if (index >= base64_decode_table_size ||
base64_decode_table[index] == -1) {
free(dest);
return (bool)false;
}
count++;
signed char value = base64_decode_table[index];
if (value == -2) {
value = 0;
pad++;
}
x <<= 6;
x += (uint64_t)value;
if ((count & 0x3) == 0) {
*ptr++ = (x >> 16) & 0xff;
*ptr++ = (x >> 8) & 0xff;
*ptr++ = x & 0xff;
}
}
size_t final = (size_t)(ptr - dest);
if (pad > 0) {
// 2 bytes of pad means only had one char in final group
final -= pad;
}
dispatch_data_t val = dispatch_data_create(dest, final, NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
dispatch_data_t concat = dispatch_data_create_concat(rv, val);
dispatch_release(val);
dispatch_release(rv);
rv = concat;
return (bool)true;
});
if (!success) {
dispatch_release(rv);
return NULL;
}
return rv;
}
static dispatch_data_t
_dispatch_transform_to_base64(dispatch_data_t data)
{
// RFC 4648 states that we should not linebreak
// http://tools.ietf.org/html/rfc4648
size_t total = dispatch_data_get_size(data), dest_size;
__block size_t count = 0;
dest_size = howmany(total, 3);
// <rdar://problem/25676583>
// os_mul_overflow(dest_size, 4, &dest_size)
if (dest_size > SIZE_T_MAX / 4) {
return NULL;
}
dest_size *= 4;
uint8_t *dest = (uint8_t*)malloc(dest_size);
if (dest == NULL) {
return NULL;
}
__block uint8_t *ptr = dest;
/*
* 3 8-bit bytes: xxxxxxxx yyyyyyyy zzzzzzzz
* 4 6-bit chunks: aaaaaabb bbbbcccc ccdddddd
*/
bool success = dispatch_data_apply(data, ^(
DISPATCH_UNUSED dispatch_data_t region,
size_t offset, const void *buffer, size_t size) {
const uint8_t *bytes = buffer;
size_t i;
for (i = 0; i < size; i++, count++) {
uint8_t curr = bytes[i], last = 0;
if ((count % 3) != 0) {
if (i == 0) {
const void *p;
dispatch_data_t subrange = _dispatch_data_subrange_map(data,
&p, offset - 1, 1);
if (subrange == NULL) {
return (bool)false;
}
last = *(uint8_t*)p;
dispatch_release(subrange);
} else {
last = bytes[i - 1];
}
}
switch (count % 3) {
case 0:
*ptr++ = base64_encode_table[(curr >> 2) & 0x3f];
break;
case 1:
*ptr++ = base64_encode_table[((last << 4)|(curr >> 4)) & 0x3f];
break;
case 2:
*ptr++ = base64_encode_table[((last << 2)|(curr >> 6)) & 0x3f];
*ptr++ = base64_encode_table[(curr & 0x3f)];
break;
}
}
// Last region, insert padding bytes, if needed
if (offset + size == total) {
switch (count % 3) {
case 0:
break;
case 1:
*ptr++ = base64_encode_table[(bytes[size-1] << 4) & 0x30];
*ptr++ = '=';
*ptr++ = '=';
break;
case 2:
*ptr++ = base64_encode_table[(bytes[size-1] << 2) & 0x3c];
*ptr++ = '=';
break;
}
}
return (bool)true;
});
if (!success) {
free(dest);
return NULL;
}
return dispatch_data_create(dest, dest_size, NULL,
DISPATCH_DATA_DESTRUCTOR_FREE);
}
#pragma mark -
#pragma mark dispatch_data_transform
dispatch_data_t
dispatch_data_create_with_transform(dispatch_data_t data,
dispatch_data_format_type_t input, dispatch_data_format_type_t output)
{
if (input->type == _DISPATCH_DATA_FORMAT_UTF_ANY) {
input = _dispatch_transform_detect_utf(data);
if (input == NULL) {
return DISPATCH_BAD_INPUT;
}
}
if ((input->type & ~output->input_mask) != 0) {
return DISPATCH_BAD_INPUT;
}
if ((output->type & ~input->output_mask) != 0) {
return DISPATCH_BAD_INPUT;
}
if (dispatch_data_get_size(data) == 0) {
return data;
}
dispatch_data_t temp1;
if (input->decode) {
temp1 = input->decode(data);
} else {
dispatch_retain(data);
temp1 = data;
}
if (!temp1) {
return DISPATCH_BAD_INPUT;
}
dispatch_data_t temp2;
if (output->encode) {
temp2 = output->encode(temp1);
} else {
dispatch_retain(temp1);
temp2 = temp1;
}
dispatch_release(temp1);
return temp2;
}
const struct dispatch_data_format_type_s _dispatch_data_format_type_none = {
.type = _DISPATCH_DATA_FORMAT_NONE,
.input_mask = ~0u,
.output_mask = ~0u,
.decode = NULL,
.encode = NULL,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_base32 = {
.type = _DISPATCH_DATA_FORMAT_BASE32,
.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.decode = _dispatch_transform_from_base32,
.encode = _dispatch_transform_to_base32,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_base32hex =
{
.type = _DISPATCH_DATA_FORMAT_BASE32HEX,
.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.decode = _dispatch_transform_from_base32hex,
.encode = _dispatch_transform_to_base32hex,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_base64 = {
.type = _DISPATCH_DATA_FORMAT_BASE64,
.input_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.output_mask = (_DISPATCH_DATA_FORMAT_NONE | _DISPATCH_DATA_FORMAT_BASE32 |
_DISPATCH_DATA_FORMAT_BASE32HEX | _DISPATCH_DATA_FORMAT_BASE64),
.decode = _dispatch_transform_from_base64,
.encode = _dispatch_transform_to_base64,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_utf16le = {
.type = _DISPATCH_DATA_FORMAT_UTF16LE,
.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.decode = _dispatch_transform_from_utf16le,
.encode = _dispatch_transform_to_utf16le,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_utf16be = {
.type = _DISPATCH_DATA_FORMAT_UTF16BE,
.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.decode = _dispatch_transform_from_utf16be,
.encode = _dispatch_transform_to_utf16be,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_utf8 = {
.type = _DISPATCH_DATA_FORMAT_UTF8,
.input_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.output_mask = (_DISPATCH_DATA_FORMAT_UTF8 | _DISPATCH_DATA_FORMAT_UTF16BE |
_DISPATCH_DATA_FORMAT_UTF16LE),
.decode = NULL,
.encode = NULL,
};
const struct dispatch_data_format_type_s _dispatch_data_format_type_utf_any = {
.type = _DISPATCH_DATA_FORMAT_UTF_ANY,
.input_mask = 0,
.output_mask = 0,
.decode = NULL,
.encode = NULL,
};