// internal inplace reader for when it straddles the end of the buffer static const char* mpack_read_bytes_inplace_big(mpack_reader_t* reader, size_t count) { // we should only arrive here from inplace straddle; this should already be checked mpack_assert(mpack_reader_error(reader) == mpack_ok, "already in error state? %s", mpack_error_to_string(mpack_reader_error(reader))); mpack_assert(reader->left < count, "already enough bytes in buffer: %i left, %i count", (int)reader->left, (int)count); // we'll need a fill function to get more data. if there's no // fill function, the buffer should contain an entire MessagePack // object, so we raise mpack_error_invalid instead of mpack_error_io // on truncated data. if (reader->fill == NULL) { mpack_reader_flag_error(reader, mpack_error_invalid); return NULL; } // make sure the buffer is big enough to actually fit the data if (count > reader->size) { mpack_reader_flag_error(reader, mpack_error_too_big); return NULL; } // re-fill as much as possible mpack_partial_fill(reader); if (reader->left < count) { mpack_reader_flag_error(reader, mpack_error_io); return NULL; } reader->pos += count; reader->left -= count; return reader->buffer; }
char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated) { // track the bytes first in case it jumps mpack_reader_track_bytes(reader, count); if (mpack_reader_error(reader) != mpack_ok) return NULL; // cannot allocate zero bytes. this is not an error. if (count == 0 && null_terminated == false) return NULL; // allocate data char* data = (char*)MPACK_MALLOC(count + (null_terminated ? 1 : 0)); // TODO: can this overflow? if (data == NULL) { mpack_reader_flag_error(reader, mpack_error_memory); return NULL; } // read with error callback disabled so we don't leak our buffer mpack_read_native_noerrorfn(reader, data, count); // report flagged errors if (mpack_reader_error(reader) != mpack_ok) { MPACK_FREE(data); if (reader->error_fn) reader->error_fn(reader, mpack_reader_error(reader)); return NULL; } if (null_terminated) data[count] = '\0'; return data; }
size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t bufsize) { size_t binsize = mpack_expect_bin(reader); if (mpack_reader_error(reader)) return 0; if (binsize > bufsize) { mpack_reader_flag_error(reader, mpack_error_too_big); return 0; } mpack_read_bytes(reader, buf, binsize); if (mpack_reader_error(reader)) return 0; mpack_done_bin(reader); return binsize; }
void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil) { *out_count = 0; uint32_t count; bool has_array = true; if (allow_nil) has_array = mpack_expect_array_max_or_nil(reader, max_count, &count); else count = mpack_expect_array_max(reader, max_count); if (mpack_reader_error(reader)) return NULL; // size 0 is not an error; we return NULL for no elements. if (count == 0) { // we call mpack_done_array() automatically ONLY if we are using // the _or_nil variant. this is the only way to allow nil and empty // to work the same way. if (allow_nil && has_array) mpack_done_array(reader); return NULL; } void* p = MPACK_MALLOC(element_size * count); if (p == NULL) { mpack_reader_flag_error(reader, mpack_error_memory); return NULL; } *out_count = count; return p; }
// Converts MessagePack bin/ext bytes to JSON base64 string static bool base64(mpack_reader_t* reader, yajl_gen gen, options_t* options, uint32_t len, char* output, char* p, bool prefix) { if (prefix) { memcpy(p, b64_str, strlen(b64_str)); p += strlen(b64_str); } base64_encodestate state; base64_init_encodestate(&state); while (len > 0) { char buf[4096]; uint32_t count = (len < sizeof(buf)) ? len : sizeof(buf); len -= count; mpack_read_bytes(reader, buf, count); if (mpack_reader_error(reader) != mpack_ok) { fprintf(stderr, "%s: error reading base64 bytes\n", options->command); return false; } p += base64_encode_block(buf, (int)count, p, &state); } p += base64_encode_blockend(p, &state); bool ret = yajl_gen_string(gen, (const unsigned char*)output, p - output) == yajl_gen_status_ok; return ret; }
void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count) { mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)byte_count); mpack_reader_track_str_bytes_all(reader, byte_count); mpack_read_native(reader, p, byte_count); if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(p, byte_count)) mpack_reader_flag_error(reader, mpack_error_type); }
size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) { size_t length = mpack_expect_str(reader); if (mpack_reader_error(reader)) return 0; if (length > bufsize) { mpack_reader_flag_error(reader, mpack_error_too_big); return 0; } mpack_read_bytes(reader, buf, length); if (mpack_reader_error(reader)) return 0; mpack_done_str(reader); return length; }
void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) { mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count); // check encoding if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check_no_null(buf, byte_count)) { buf[0] = 0; mpack_reader_flag_error(reader, mpack_error_type); } }
const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) { mpack_reader_track_str_bytes_all(reader, count); const char* str = mpack_read_bytes_inplace_notrack(reader, count); if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(str, count)) { mpack_reader_flag_error(reader, mpack_error_type); return NULL; } return str; }
void mpack_discard(mpack_reader_t* reader) { mpack_tag_t var = mpack_read_tag(reader); if (mpack_reader_error(reader)) return; switch (var.type) { case mpack_type_str: mpack_skip_bytes(reader, var.v.l); mpack_done_str(reader); break; case mpack_type_bin: mpack_skip_bytes(reader, var.v.l); mpack_done_bin(reader); break; case mpack_type_ext: mpack_skip_bytes(reader, var.v.l); mpack_done_ext(reader); break; case mpack_type_array: { for (; var.v.n > 0; --var.v.n) { mpack_discard(reader); if (mpack_reader_error(reader)) break; } mpack_done_array(reader); break; } case mpack_type_map: { for (; var.v.n > 0; --var.v.n) { mpack_discard(reader); mpack_discard(reader); if (mpack_reader_error(reader)) break; } mpack_done_map(reader); break; } default: break; } }
// read inplace without tracking (since there are different // tracking modes for different inplace readers) static const char* mpack_read_bytes_inplace_notrack(mpack_reader_t* reader, size_t count) { if (mpack_reader_error(reader) != mpack_ok) return NULL; // if we have enough bytes already in the buffer, we can return it directly. if (reader->left >= count) { reader->pos += count; reader->left -= count; return reader->buffer + reader->pos - count; } return mpack_read_bytes_inplace_big(reader, count); }
size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) { if (mpack_reader_error(reader) != mpack_ok) return 0; #if MPACK_READ_TRACKING if (mpack_reader_flag_if_error(reader, mpack_track_check_empty(&reader->track)) != mpack_ok) return 0; #endif if (data) *data = reader->buffer + reader->pos; return reader->left; }
mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) { // clean up tracking, asserting if we're not already in an error state #if MPACK_READ_TRACKING mpack_reader_flag_if_error(reader, mpack_track_destroy(&reader->track, mpack_reader_error(reader) != mpack_ok)); #endif if (reader->teardown) reader->teardown(reader); reader->teardown = NULL; return reader->error; }
mpack_tag_t mpack_peek_tag(mpack_reader_t* reader) { mpack_log("peeking tag\n"); // make sure we can peek a tag if (mpack_reader_error(reader) != mpack_ok) return mpack_tag_nil(); if (mpack_reader_track_peek_element(reader) != mpack_ok) return mpack_tag_nil(); mpack_tag_t tag; mpack_memset(&tag, 0, sizeof(tag)); if (mpack_parse_tag(reader, &tag) == 0) return mpack_tag_nil(); return tag; }
static size_t mpack_expect_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t bufsize) { // make sure buffer makes sense mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator"); // expect a str size_t length = mpack_expect_str_buf(reader, buf, bufsize - 1); if (mpack_reader_error(reader)) { buf[0] = 0; return 0; } buf[length] = 0; return length; }
// Reads MessagePack string bytes and outputs a JSON string static bool string(mpack_reader_t* reader, yajl_gen gen, options_t* options, uint32_t len) { char* str = (char*)malloc(len); mpack_read_bytes(reader, str, len); if (mpack_reader_error(reader) != mpack_ok) { fprintf(stderr, "%s: error reading string bytes\n", options->command); free(str); return false; } mpack_done_str(reader); yajl_gen_status status = yajl_gen_string(gen, (const unsigned char*)str, len); free(str); return status == yajl_gen_status_ok; }
mpack_tag_t mpack_read_tag(mpack_reader_t* reader) { mpack_log("reading tag\n"); // make sure we can read a tag if (mpack_reader_error(reader) != mpack_ok) return mpack_tag_nil(); if (mpack_reader_track_element(reader) != mpack_ok) return mpack_tag_nil(); mpack_tag_t tag; mpack_memset(&tag, 0, sizeof(tag)); size_t count = mpack_parse_tag(reader, &tag); if (count == 0) return mpack_tag_nil(); #if MPACK_READ_TRACKING mpack_error_t track_error = mpack_ok; switch (tag.type) { case mpack_type_map: case mpack_type_array: track_error = mpack_track_push(&reader->track, tag.type, tag.v.l); break; case mpack_type_str: case mpack_type_bin: case mpack_type_ext: track_error = mpack_track_push(&reader->track, tag.type, tag.v.n); break; default: break; } if (track_error != mpack_ok) { mpack_reader_flag_error(reader, track_error); return mpack_tag_nil(); } #endif // the tag is guaranteed to have been read out of // the buffer, so we advance past it reader->pos += count; reader->left -= count; return tag; }
void mpack_skip_bytes(mpack_reader_t* reader, size_t count) { if (mpack_reader_error(reader) != mpack_ok) return; mpack_log("skip requested for %i bytes\n", (int)count); mpack_reader_track_bytes(reader, count); // check if we have enough in the buffer already if (reader->left >= count) { mpack_log("skipping %i bytes still in buffer\n", (int)count); reader->left -= count; reader->pos += count; return; } // we'll need at least a fill function to skip more data. if there's // no fill function, the buffer should contain an entire MessagePack // object, so we raise mpack_error_invalid instead of mpack_error_io // on truncated data. (see mpack_read_native_big()) if (reader->fill == NULL) { mpack_log("reader has no fill function!\n"); mpack_reader_flag_error(reader, mpack_error_invalid); return; } // discard whatever's left in the buffer mpack_log("discarding %i bytes still in buffer\n", (int)reader->left); count -= reader->left; reader->pos += reader->left; reader->left = 0; #if !MPACK_OPTIMIZE_FOR_SIZE // use the skip function if we've got one, and if we're trying // to skip a lot of data. if we only need to skip some tiny // fraction of the buffer size, it's probably better to just // fill the buffer and skip from it instead of trying to seek. if (reader->skip && count > reader->size / 16) { mpack_log("calling skip function for %i bytes\n", (int)count); reader->skip(reader, count); return; } #endif mpack_reader_skip_using_fill(reader, count); }
static void mpack_read_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) { mpack_assert(buf != NULL, "destination for read of %i bytes is NULL", (int)byte_count); mpack_assert(buffer_size >= 1, "buffer size is zero; you must have room for at least a null-terminator"); if (mpack_reader_error(reader)) { buf[0] = 0; return; } if (byte_count > buffer_size - 1) { mpack_reader_flag_error(reader, mpack_error_too_big); buf[0] = 0; return; } mpack_reader_track_str_bytes_all(reader, byte_count); mpack_read_native(reader, buf, byte_count); buf[byte_count] = 0; }
void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t len) { // expect a str the correct length if (len > UINT32_MAX) mpack_reader_flag_error(reader, mpack_error_type); mpack_expect_str_length(reader, (uint32_t)len); if (mpack_reader_error(reader)) return; // check each byte for (size_t i = 0; i < len; ++i) { mpack_reader_track_bytes(reader, 1); if (mpack_read_native_u8(reader) != *str++) { mpack_reader_flag_error(reader, mpack_error_type); return; } } mpack_done_str(reader); }
void mpack_print_file(const char* data, size_t len, FILE* file) { mpack_assert(data != NULL, "data is NULL"); mpack_assert(file != NULL, "file is NULL"); mpack_reader_t reader; mpack_reader_init_data(&reader, data, len); int depth = 2; for (int i = 0; i < depth; ++i) fprintf(file, " "); mpack_print_element(&reader, depth, file); putc('\n', file); size_t remaining = mpack_reader_remaining(&reader, NULL); if (mpack_reader_destroy(&reader) != mpack_ok) fprintf(file, "<mpack parsing error %s>\n", mpack_error_to_string(mpack_reader_error(&reader))); else if (remaining > 0) fprintf(file, "<%i extra bytes at end of mpack>\n", (int)remaining); }
static void mpack_file_reader_skip(mpack_reader_t* reader, size_t count) { if (mpack_reader_error(reader) != mpack_ok) return; FILE* file = (FILE*)reader->context; // We call ftell() to test whether the stream is seekable // without causing a file error. if (ftell(file) >= 0) { mpack_log("seeking forward %i bytes\n", (int)count); if (fseek(file, (long int)count, SEEK_CUR) == 0) return; mpack_log("fseek() didn't return zero!\n"); if (ferror(file)) { mpack_reader_flag_error(reader, mpack_error_io); return; } } // If the stream is not seekable, fall back to the fill function. mpack_reader_skip_using_fill(reader, count); }
void test_read_error_handler(mpack_reader_t* reader, mpack_error_t error) { TEST_TRUE(test_read_error == mpack_ok, "error handler was called multiple times"); TEST_TRUE(error != mpack_ok, "error handler was called with mpack_ok"); TEST_TRUE(mpack_reader_error(reader) == error, "reader error does not match given error"); test_read_error = error; }
void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) { if (mpack_reader_error(reader) == mpack_ok) mpack_reader_flag_if_error(reader, mpack_track_pop(&reader->track, type)); }
static bool element(mpack_reader_t* reader, yajl_gen gen, options_t* options, int depth) { const mpack_tag_t tag = mpack_read_tag(reader); if (mpack_reader_error(reader) != mpack_ok) return false; if (!options->debug && depth == 0 && (tag.type != mpack_type_map && tag.type != mpack_type_array)) { fprintf(stderr, "%s: Top-level object must be a map or array. Try debug viewing mode (-d)\n", options->command); return false; } // TODO check not depth zero switch (tag.type) { case mpack_type_bool: return yajl_gen_bool(gen, tag.v.b) == yajl_gen_status_ok; case mpack_type_nil: return yajl_gen_null(gen) == yajl_gen_status_ok; case mpack_type_int: return yajl_gen_integer(gen, tag.v.i) == yajl_gen_status_ok; case mpack_type_float: return yajl_gen_double(gen, tag.v.f) == yajl_gen_status_ok; case mpack_type_double: return yajl_gen_double(gen, tag.v.d) == yajl_gen_status_ok; case mpack_type_uint: if (tag.v.u > (uint64_t)INT64_MAX) { char buf[32]; snprintf(buf, sizeof(buf), "%" PRIu64, tag.v.u); return yajl_gen_string(gen, (const unsigned char*)buf, strlen(buf)) == yajl_gen_status_ok; } return yajl_gen_integer(gen, (int64_t)tag.v.u) == yajl_gen_status_ok; case mpack_type_str: return string(reader, gen, options, tag.v.l); case mpack_type_bin: if (options->base64) { return base64_bin(reader, gen, options, tag.v.l, options->base64_prefix); } else if (options->debug) { mpack_skip_bytes(reader, tag.v.l); mpack_done_bin(reader); // output nothing to allow us to print our debug string skip_quotes = true; if (yajl_gen_string(gen, (const unsigned char*)"", 0) != yajl_gen_status_ok) return false; skip_quotes = false; char buf[64]; snprintf(buf, sizeof(buf), "<bin of size %u>", tag.v.l); print(out_file, buf, strlen(buf)); return true; } else { fprintf(stderr, "%s: bin unencodable in JSON. Try debug viewing mode (-d)\n", options->command); return false; } case mpack_type_ext: if (options->base64) { return base64_ext(reader, gen, options, tag.exttype, tag.v.l); } else if (options->debug) { mpack_skip_bytes(reader, tag.v.l); mpack_done_ext(reader); // output nothing to allow us to print our debug string skip_quotes = true; if (yajl_gen_string(gen, (const unsigned char*)"", 0) != yajl_gen_status_ok) return false; skip_quotes = false; char buf[64]; snprintf(buf, sizeof(buf), "<ext of type %i size %u>", tag.exttype, tag.v.l); print(out_file, buf, strlen(buf)); return true; } else { fprintf(stderr, "%s: ext type %i unencodable in JSON. Try debug viewing mode (-d)\n", options->command, tag.exttype); return false; } case mpack_type_array: if (yajl_gen_array_open(gen) != yajl_gen_status_ok) return false; for (size_t i = 0; i < tag.v.l; ++i) if (!element(reader, gen, options, depth + 1)) return false; mpack_done_array(reader); return yajl_gen_array_close(gen) == yajl_gen_status_ok; case mpack_type_map: if (yajl_gen_map_open(gen) != yajl_gen_status_ok) return false; for (size_t i = 0; i < tag.v.l; ++i) { if (options->debug) { element(reader, gen, options, depth + 1); } else { uint32_t len = mpack_expect_str(reader); if (mpack_reader_error(reader) != mpack_ok) { fprintf(stderr, "%s: map key is not a string. Try debug viewing mode (-d)\n", options->command); return false; } if (!string(reader, gen, options, len)) return false; } if (!element(reader, gen, options, depth + 1)) return false; } mpack_done_map(reader); return yajl_gen_map_close(gen) == yajl_gen_status_ok; } return true; }
static void mpack_print_element(mpack_reader_t* reader, size_t depth, FILE* file) { mpack_tag_t val = mpack_read_tag(reader); if (mpack_reader_error(reader) != mpack_ok) return; switch (val.type) { case mpack_type_nil: fprintf(file, "null"); break; case mpack_type_bool: fprintf(file, val.v.b ? "true" : "false"); break; case mpack_type_float: fprintf(file, "%f", val.v.f); break; case mpack_type_double: fprintf(file, "%f", val.v.d); break; case mpack_type_int: fprintf(file, "%" PRIi64, val.v.i); break; case mpack_type_uint: fprintf(file, "%" PRIu64, val.v.u); break; case mpack_type_bin: fprintf(file, "<binary data of length %u>", val.v.l); mpack_skip_bytes(reader, val.v.l); mpack_done_bin(reader); break; case mpack_type_ext: fprintf(file, "<ext data of type %i and length %u>", val.exttype, val.v.l); mpack_skip_bytes(reader, val.v.l); mpack_done_ext(reader); break; case mpack_type_str: putc('"', file); for (size_t i = 0; i < val.v.l; ++i) { char c; mpack_read_bytes(reader, &c, 1); if (mpack_reader_error(reader) != mpack_ok) return; switch (c) { case '\n': fprintf(file, "\\n"); break; case '\\': fprintf(file, "\\\\"); break; case '"': fprintf(file, "\\\""); break; default: putc(c, file); break; } } putc('"', file); mpack_done_str(reader); break; case mpack_type_array: fprintf(file, "[\n"); for (size_t i = 0; i < val.v.n; ++i) { for (size_t j = 0; j < depth + 1; ++j) fprintf(file, " "); mpack_print_element(reader, depth + 1, file); if (mpack_reader_error(reader) != mpack_ok) return; if (i != val.v.n - 1) putc(',', file); putc('\n', file); } for (size_t i = 0; i < depth; ++i) fprintf(file, " "); putc(']', file); mpack_done_array(reader); break; case mpack_type_map: fprintf(file, "{\n"); for (size_t i = 0; i < val.v.n; ++i) { for (size_t j = 0; j < depth + 1; ++j) fprintf(file, " "); mpack_print_element(reader, depth + 1, file); if (mpack_reader_error(reader) != mpack_ok) return; fprintf(file, ": "); mpack_print_element(reader, depth + 1, file); if (mpack_reader_error(reader) != mpack_ok) return; if (i != val.v.n - 1) putc(',', file); putc('\n', file); } for (size_t i = 0; i < depth; ++i) fprintf(file, " "); putc('}', file); mpack_done_map(reader); break; } }
// Reads count bytes into p. Used when there are not enough bytes // left in the buffer to satisfy a read. void mpack_read_native_big(mpack_reader_t* reader, char* p, size_t count) { mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count); if (mpack_reader_error(reader) != mpack_ok) { mpack_memset(p, 0, count); return; } mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n", (int)count, p, (int)reader->left, (int)reader->size); if (count <= reader->left) { mpack_assert(0, "big read requested for %i bytes, but there are %i bytes " "left in buffer. call mpack_read_native() instead", (int)count, (int)reader->left); mpack_reader_flag_error(reader, mpack_error_bug); mpack_memset(p, 0, count); return; } // we'll need a fill function to get more data. if there's no // fill function, the buffer should contain an entire MessagePack // object, so we raise mpack_error_invalid instead of mpack_error_io // on truncated data. if (reader->fill == NULL) { mpack_reader_flag_error(reader, mpack_error_invalid); mpack_memset(p, 0, count); return; } if (reader->size == 0) { // somewhat debatable what error should be returned here. when // initializing a reader with an in-memory buffer it's not // necessarily a bug if the data is blank; it might just have // been truncated to zero. for this reason we return the same // error as if the data was truncated. mpack_reader_flag_error(reader, mpack_error_io); mpack_memset(p, 0, count); return; } // flush what's left of the buffer if (reader->left > 0) { mpack_log("flushing %i bytes remaining in buffer\n", (int)reader->left); mpack_memcpy(p, reader->buffer + reader->pos, reader->left); count -= reader->left; p += reader->left; reader->pos += reader->left; reader->left = 0; } // we read only in multiples of the buffer size. read the middle portion, if any size_t middle = count - (count % reader->size); if (middle > 0) { mpack_log("reading %i bytes in middle\n", (int)middle); if (mpack_fill(reader, p, middle) < middle) { mpack_reader_flag_error(reader, mpack_error_io); mpack_memset(p, 0, count); return; } count -= middle; p += middle; if (count == 0) return; } // fill the buffer reader->pos = 0; reader->left = mpack_fill(reader, reader->buffer, reader->size); mpack_log("filled %i bytes into buffer\n", (int)reader->left); if (reader->left < count) { mpack_reader_flag_error(reader, mpack_error_io); mpack_memset(p, 0, count); return; } // serve the remainder mpack_log("serving %i remaining bytes from %p to %p\n", (int)count, reader->buffer+reader->pos,p); mpack_memcpy(p, reader->buffer + reader->pos, count); reader->pos += count; reader->left -= count; }