diff --git a/pkgs/mruby/src/cmdline.c b/pkgs/mruby/src/cmdline.c new file mode 100644 index 0000000..be2977e --- /dev/null +++ b/pkgs/mruby/src/cmdline.c @@ -0,0 +1,71 @@ +#include "main.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define ARGV_COUNT_MAX 256 +#define BUFFER_SIZE 4096 + +static mrb_value rb_KernAux_cmdline(mrb_state *mrb, mrb_value self); + +void init_cmdline(mrb_state *const mrb) +{ + struct RClass *const rb_KernAux = mrb_module_get_id(mrb, MRB_SYM(KernAux)); + struct RClass *const rb_KernAux_Error = + mrb_class_get_under_id(mrb, rb_KernAux, MRB_SYM(Error)); + + mrb_define_class_under_id(mrb, rb_KernAux, MRB_SYM(CmdlineError), + rb_KernAux_Error); + + mrb_define_class_method(mrb, rb_KernAux, "cmdline", + rb_KernAux_cmdline, MRB_ARGS_REQ(1)); +} + +mrb_value rb_KernAux_cmdline(mrb_state *const mrb, mrb_value self) +{ + const char *str; + mrb_get_args(mrb, "z", &str); + size_t argc; + char error_msg[KERNAUX_CMDLINE_ERROR_MSG_SIZE_MAX]; + char **const argv = malloc(sizeof(char*) * ARGV_COUNT_MAX); + char *const buffer = malloc(BUFFER_SIZE); + + const bool status = kernaux_cmdline( + str, + error_msg, + &argc, + argv, + buffer, + ARGV_COUNT_MAX, + BUFFER_SIZE + ); + + if (!status) { + free(argv); + free(buffer); + struct RClass *const rb_KernAux = + mrb_module_get_id(mrb, MRB_SYM(KernAux)); + struct RClass *const rb_KernAux_CmdlineError = + mrb_class_get_under_id(mrb, rb_KernAux, MRB_SYM(CmdlineError)); + mrb_raise(mrb, rb_KernAux_CmdlineError, error_msg); + } + + mrb_value values[argc]; + for (size_t index = 0; index < argc; ++index) { + values[index] = mrb_obj_freeze( + mrb, + mrb_str_cat_cstr(mrb, mrb_str_new_lit(mrb, ""), argv[index]) + ); + } + free(argv); + free(buffer); + return mrb_obj_freeze(mrb, mrb_ary_new_from_values(mrb, argc, values)); +} diff --git a/pkgs/mruby/src/main.c b/pkgs/mruby/src/main.c index 7aeb06a..5bfb8e9 100644 --- a/pkgs/mruby/src/main.c +++ b/pkgs/mruby/src/main.c @@ -26,6 +26,7 @@ void mrb_mruby_kernaux_gem_init(mrb_state *const mrb) init_assert(mrb); init_ntoa(mrb); + init_cmdline(mrb); } void current_mrb_start(mrb_state *mrb) diff --git a/pkgs/mruby/src/main.h b/pkgs/mruby/src/main.h index 1eb543f..25ee917 100644 --- a/pkgs/mruby/src/main.h +++ b/pkgs/mruby/src/main.h @@ -9,5 +9,6 @@ mrb_state *current_mrb_get(); void init_assert(mrb_state *mrb); void init_ntoa(mrb_state *mrb); +void init_cmdline(mrb_state *mrb); #endif diff --git a/pkgs/mruby/test/cmdline.rb b/pkgs/mruby/test/cmdline.rb new file mode 100644 index 0000000..60f6560 --- /dev/null +++ b/pkgs/mruby/test/cmdline.rb @@ -0,0 +1,70 @@ +def test_cmdline(str, expected) + result = KernAux.cmdline str + + assert_true result.instance_of? Array + assert_true result.frozen? + result.each do |item| + assert_true item.instance_of? String + assert_true item.frozen? + end + + assert_equal result, expected +end + +assert 'default' do + test_cmdline 'foo bar\\ baz "car cdr"', ['foo', 'bar baz', 'car cdr'] +end + +assert 'when str is empty' do + test_cmdline '', [] +end + +assert 'when str has invalid type' do + assert_raise TypeError, 'Integer cannot be converted to String' do + KernAux.cmdline 123 + end +end + +assert 'when str has EOL after backslash' do + assert_raise KernAux::CmdlineError, 'EOL after backslash' do + KernAux.cmdline '\\' + end +end + +assert 'when str has EOL after backslash inside quote' do + assert_raise KernAux::CmdlineError, 'EOL after backslash inside quote' do + KernAux.cmdline '"\\' + end +end + +assert 'when str has unescaped quotation mark' do + assert_raise KernAux::CmdlineError, 'unescaped quotation mark' do + KernAux.cmdline 'foo"' + end +end + +assert 'when str has EOL inside quote' do + assert_raise KernAux::CmdlineError, 'EOL inside quote' do + KernAux.cmdline '"' + end +end + +assert 'when there are not too many args' do + test_cmdline 'a ' * 256, ['a'] * 256 +end + +assert 'when there are too many args' do + assert_raise KernAux::CmdlineError, 'too many args' do + KernAux.cmdline 'a ' * 257 + end +end + +assert 'when args don\'t cause buffer overflow' do + test_cmdline 'a' * 4095, ['a' * 4095] +end + +assert 'when args cause buffer overflow' do + assert_raise KernAux::CmdlineError, 'buffer overflow' do + KernAux.cmdline 'a' * 4096 + end +end