diff --git a/ext/jsonnet/callbacks.c b/ext/jsonnet/callbacks.c index 733d4cf..800e503 100644 --- a/ext/jsonnet/callbacks.c +++ b/ext/jsonnet/callbacks.c @@ -130,8 +130,7 @@ import_callback_entrypoint(void *ctx, const char *base, const char *rel, char ** if (state) { VALUE msg = rescue_callback(state, "cannot import %s from %s", rel, base); #ifdef HAVE_JSONNET_IMPORT_CALLBACK_0_19 - *buf = rubyjsonnet_str_to_cstr(vm->vm, msg); - *buflen = RSTRING_LEN(msg); + *buf = rubyjsonnet_str_to_ptr(vm->vm, msg, buflen); return 1; #else *success = 0; @@ -142,8 +141,7 @@ import_callback_entrypoint(void *ctx, const char *base, const char *rel, char ** result = rb_Array(result); *found_here = rubyjsonnet_str_to_cstr(vm->vm, rb_ary_entry(result, 1)); #ifdef HAVE_JSONNET_IMPORT_CALLBACK_0_19 - *buf = rubyjsonnet_str_to_cstr(vm->vm, rb_ary_entry(result, 0)); - *buflen = RSTRING_LEN(rb_ary_entry(result, 0)); + *buf = rubyjsonnet_str_to_ptr(vm->vm, rb_ary_entry(result, 0), buflen); return 0; #else *success = 1; diff --git a/ext/jsonnet/helpers.c b/ext/jsonnet/helpers.c index 5958d60..966770b 100644 --- a/ext/jsonnet/helpers.c +++ b/ext/jsonnet/helpers.c @@ -34,6 +34,13 @@ rubyjsonnet_assert_asciicompat(VALUE str) /** * Allocates a C string whose content is equal to \c str with jsonnet_realloc. + * + * Note that this function does not allow NUL characters in the string. + * You should use rubyjsonnet_str_to_ptr() if you want to handle NUL characters. + * + * @param[in] vm a Jsonnet VM + * @param[in] str a String-like object + * @return the allocated C string */ char * rubyjsonnet_str_to_cstr(struct JsonnetVm *vm, VALUE str) @@ -44,6 +51,25 @@ rubyjsonnet_str_to_cstr(struct JsonnetVm *vm, VALUE str) return buf; } +/** + * Allocates a byte sequence whose content is equal to \c str with jsonnet_realloc. + * + * @param[in] vm a Jsonnet VM + * @param[in] str a String-like object + * @param[out] buflen the length of the allocated buffer + * @return the allocated buffer + */ +char * +rubyjsonnet_str_to_ptr(struct JsonnetVm *vm, VALUE str, size_t *buflen) +{ + StringValue(str); + size_t len = RSTRING_LEN(str); + char *buf = jsonnet_realloc(vm, NULL, len); + memcpy(buf, RSTRING_PTR(str), len); + *buflen = len; + return buf; +} + /** * @return a human readable string which contains the class name of the * exception and its message. It might be nil on failure diff --git a/ext/jsonnet/ruby_jsonnet.h b/ext/jsonnet/ruby_jsonnet.h index aa91c72..b33b57e 100644 --- a/ext/jsonnet/ruby_jsonnet.h +++ b/ext/jsonnet/ruby_jsonnet.h @@ -33,6 +33,7 @@ struct JsonnetJsonValue *rubyjsonnet_obj_to_json(struct JsonnetVm *vm, VALUE obj rb_encoding *rubyjsonnet_assert_asciicompat(VALUE str); char *rubyjsonnet_str_to_cstr(struct JsonnetVm *vm, VALUE str); +char *rubyjsonnet_str_to_ptr(struct JsonnetVm *vm, VALUE str, size_t *buflen); VALUE rubyjsonnet_format_exception(VALUE exc); int rubyjsonnet_jump_tag(const char *exc_mesg); diff --git a/test/test_vm.rb b/test/test_vm.rb index 27ca116..45ff770 100644 --- a/test/test_vm.rb +++ b/test/test_vm.rb @@ -287,6 +287,26 @@ class TestVM < Test::Unit::TestCase assert_equal expected, JSON.parse(result) end + test "Jsonnet::VM#import_callback allows NUL char in Jsonnet v0.19 or later" do + return unless Jsonnet.libversion >= "v0.19" + + example_str = "\x0\x1".force_encoding(Encoding::BINARY) + + vm = Jsonnet::VM.new + vm.import_callback = ->(base, rel) { + case [base, rel] + when ['/path/to/base/', 'foo.bin'] + return "\x0\x1".force_encoding(Encoding::BINARY), '/path/to/base/foo.bin' + else + raise Errno::ENOENT, "#{rel} at #{base}" + end + } + result = vm.evaluate(<<-EOS, filename: "/path/to/base/example.jsonnet") + importbin "foo.bin" + EOS + assert_equal [0, 1], JSON.parse(result) + end + test "Jsonnet::VM#evaluate returns an error if customized import callback raises an exception" do vm = Jsonnet::VM.new called = false