diff --git a/c_src/decoder.c b/c_src/decoder.c index ad3e8d3..f39575a 100644 --- a/c_src/decoder.c +++ b/c_src/decoder.c @@ -52,7 +52,7 @@ typedef struct { size_t bytes_per_iter; int is_partial; int return_maps; - int use_nil; + ERL_NIF_TERM null_term; char* p; unsigned char* u; @@ -80,7 +80,7 @@ dec_new(ErlNifEnv* env) d->bytes_per_iter = DEFAULT_BYTES_PER_ITER; d->is_partial = 0; d->return_maps = 0; - d->use_nil = 0; + d->null_term = d->atoms->atom_null; d->p = NULL; d->u = NULL; @@ -711,7 +711,9 @@ decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return enif_make_badarg(env); #endif } else if(enif_compare(val, d->atoms->atom_use_nil) == 0) { - d->use_nil = 1; + d->null_term = d->atoms->atom_nil; + } else if(get_null_term(env, val, &(d->null_term))) { + continue; } else { return enif_make_badarg(env); } @@ -783,7 +785,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) ret = dec_error(d, "invalid_literal"); goto done; } - val = d->use_nil ? d->atoms->atom_nil : d->atoms->atom_null; + val = d->null_term; dec_pop(d, st_value); d->i += 4; break; diff --git a/c_src/encoder.c b/c_src/encoder.c index 471cfcc..59b1b52 100644 --- a/c_src/encoder.c +++ b/c_src/encoder.c @@ -34,6 +34,7 @@ typedef struct { int uescape; int pretty; int use_nil; + int escape_forward_slashes; int shiftcnt; int count; @@ -77,6 +78,7 @@ enc_new(ErlNifEnv* env) e->uescape = 0; e->pretty = 0; e->use_nil = 0; + e->escape_forward_slashes = 0; e->shiftcnt = 0; e->count = 0; @@ -281,6 +283,12 @@ enc_string(Encoder* e, ERL_NIF_TERM val) esc_extra += 1; i++; continue; + case '/': + if(e->escape_forward_slashes) { + esc_extra += 1; + i++; + continue; + } default: if(data[i] < 0x20) { esc_extra += 5; @@ -348,6 +356,13 @@ enc_string(Encoder* e, ERL_NIF_TERM val) e->p[e->i++] = 't'; i++; continue; + case '/': + if(e->escape_forward_slashes) { + e->p[e->i++] = '\\'; + e->u[e->i++] = data[i]; + i++; + continue; + } default: if(data[i] < 0x20) { ulen = unicode_uescape(data[i], &(e->p[e->i])); @@ -591,6 +606,8 @@ encode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) e->uescape = 1; } else if(enif_compare(val, e->atoms->atom_pretty) == 0) { e->pretty = 1; + } else if(enif_compare(val, e->atoms->atom_escape_forward_slashes) == 0) { + e->escape_forward_slashes = 1; } else if(enif_compare(val, e->atoms->atom_use_nil) == 0) { e->use_nil = 1; } else if(enif_compare(val, e->atoms->atom_force_utf8) == 0) { diff --git a/c_src/jiffy.c b/c_src/jiffy.c index 31aa854..9048f5e 100644 --- a/c_src/jiffy.c +++ b/c_src/jiffy.c @@ -28,6 +28,8 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) st->atom_return_maps = make_atom(env, "return_maps"); st->atom_nil = make_atom(env, "nil"); st->atom_use_nil = make_atom(env, "use_nil"); + st->atom_null_term = make_atom(env, "null_term"); + st->atom_escape_forward_slashes = make_atom(env, "escape_forward_slashes"); // Markers used in encoding st->ref_object = make_atom(env, "$object_ref$"); diff --git a/c_src/jiffy.h b/c_src/jiffy.h index 6d869a5..7a2b7d5 100644 --- a/c_src/jiffy.h +++ b/c_src/jiffy.h @@ -30,6 +30,8 @@ typedef struct { ERL_NIF_TERM atom_return_maps; ERL_NIF_TERM atom_nil; ERL_NIF_TERM atom_use_nil; + ERL_NIF_TERM atom_null_term; + ERL_NIF_TERM atom_escape_forward_slashes; ERL_NIF_TERM ref_object; ERL_NIF_TERM ref_array; @@ -44,6 +46,7 @@ ERL_NIF_TERM make_error(jiffy_st* st, ErlNifEnv* env, const char* error); ERL_NIF_TERM make_obj_error(jiffy_st* st, ErlNifEnv* env, const char* error, ERL_NIF_TERM obj); int get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi); +int get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term); int should_yield(size_t used, size_t limit); int consume_timeslice(ErlNifEnv* env, size_t used, size_t limit); diff --git a/c_src/util.c b/c_src/util.c index c30a6d7..f05e1e8 100644 --- a/c_src/util.c +++ b/c_src/util.c @@ -62,6 +62,34 @@ get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi) return 1; } +int +get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term) +{ + jiffy_st* st = (jiffy_st*) enif_priv_data(env); + const ERL_NIF_TERM* tuple; + int arity; + + if(!enif_get_tuple(env, val, &arity, &tuple)) { + return 0; + } + + if(arity != 2) { + return 0; + } + + if(enif_compare(tuple[0], st->atom_null_term) != 0) { + return 0; + } + + if(!enif_is_atom(env, tuple[1])) { + return 0; + } + + *null_term = tuple[1]; + + return 1; +} + int should_yield(size_t used, size_t limit) { diff --git a/test/jiffy_02_literal_tests.erl b/test/jiffy_02_literal_tests.erl index 98b163c..2699199 100644 --- a/test/jiffy_02_literal_tests.erl +++ b/test/jiffy_02_literal_tests.erl @@ -31,3 +31,14 @@ nil_test_() -> {"Decode", ?_assertEqual(nil, jiffy:decode(<<"null">>, [use_nil]))}, {"Encode", ?_assertEqual(<<"null">>, jiffy:encode(nil, [use_nil]))} ]}. + +null_term_test_() -> + T = [ + {undefined, [{null_term, undefined}]}, + {whatever, [{null_term, whatever}]}, + {undefined, [use_nil, {null_term, undefined}]}, + {nil, [{null_term, undefined}, use_nil]}, + {whatever, [{null_term, undefined}, {null_term, whatever}]} + ], + {"null_term", + [?_assertEqual(R, jiffy:decode(<<"null">>, O)) || {R, O} <- T]}. diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl index f2136d8..0d642d5 100644 --- a/test/jiffy_04_string_tests.erl +++ b/test/jiffy_04_string_tests.erl @@ -23,6 +23,8 @@ string_error_test_() -> string_utf8_test_() -> [gen(utf8, Case) || Case <- cases(utf8)]. +string_escaped_slashes_test_() -> + [gen(escaped_slashes, Case) || Case <- cases(escaped_slashes)]. gen(ok, {J, E}) -> gen(ok, {J, E, J}); @@ -50,8 +52,13 @@ gen(utf8, {Case, Fixed}) -> ?_assertThrow({error, {invalid_string, _}}, jiffy:encode(Case)), ?_assertEqual(Fixed2, jiffy:encode(Case, [force_utf8])), ?_assertThrow({error, {_, invalid_string}}, jiffy:decode(Case2)) - ]}. + ]}; +gen(escaped_slashes, {J, E}) -> + {msg("escaped_slashes - ~s", [J]), [ + {"Decode", ?_assertEqual(E, dec(J))}, + {"Encode", ?_assertEqual(J, enc(E, [escape_forward_slashes]))} + ]}. cases(ok) -> [ @@ -143,4 +150,9 @@ cases(utf8) -> {<<16#F8, 16#84, 16#80, 16#80, 16#80>>, <<16#EF, 16#BF, 16#BD>>}, {<<16#FC, 16#80, 16#80, 16#80, 16#80, 16#80>>, <<16#EF, 16#BF, 16#BD>>}, {<<16#FC, 16#82, 16#80, 16#80, 16#80, 16#80>>, <<16#EF, 16#BF, 16#BD>>} + ]; + +cases(escaped_slashes) -> + [ + {<<"\"\\/\"">>, <<"/">>} ].