Browse Source

Add allow_ctrl_chars decoding option

Takeru Ohta 9 years ago
parent
commit
00cdb69a6f
6 changed files with 50 additions and 19 deletions
  1. 1 1
      README.md
  2. 6 2
      doc/jsone.md
  3. 12 7
      src/jsone.erl
  4. 12 6
      src/jsone_decode.erl
  5. 3 3
      src/jsone_encode.erl
  6. 16 0
      test/jsone_decode_tests.erl

+ 1 - 1
README.md

@@ -9,7 +9,7 @@ An Erlang library for encoding, decoding [JSON](http://json.org/index.html) data
 Features
 --------
 - Provides simple encode/decode function only
-- [RFC4627](http://www.ietf.org/rfc/rfc4627.txt)-compliant
+- [RFC7159](http://www.ietf.org/rfc/rfc7159.txt)-compliant
 - Supports UTF-8 encoded binary
 - Pure Erlang
 - Highly Efficient

+ 6 - 2
doc/jsone.md

@@ -60,16 +60,20 @@ datetime_format() = iso8601
 
 
 <pre><code>
-decode_option() = {object_format, tuple | proplist | map}
+decode_option() = {object_format, tuple | proplist | map} | {allow_ctrl_chars, boolean()}
 </code></pre>
 
- object_format: <br />
+`object_format`: <br />
 - Decoded JSON object format <br />
 - `tuple`: An object is decoded as `{[]}` if it is empty, otherwise `{[{Key, Value}]}`. <br />
 - `proplist`: An object is decoded as `[{}]` if it is empty, otherwise `[{Key, Value}]`. <br />
 - `map`: An object is decoded as `#{}` if it is empty, otherwise `#{Key => Value}`. <br />
 - default: `map` <br />
 
+`allow_ctrl_chars`: <br />
+- If the value is `true`, strings which contain ununescaped control characters will be regarded as a legal JSON string <br />
+- default: `false` <br />
+
 
 
 ### <a name="type-encode_option">encode_option()</a> ###

+ 12 - 7
src/jsone.erl

@@ -167,13 +167,18 @@
 %% - Inserts a newline and `N' spaces for each level of indentation <br />
 %% - default: `0' <br />
 
--type decode_option() :: {object_format, tuple | proplist | map}.
-%% object_format: <br />
-%%  - Decoded JSON object format <br />
-%%  - `tuple': An object is decoded as `{[]}' if it is empty, otherwise `{[{Key, Value}]}'. <br />
-%%  - `proplist': An object is decoded as `[{}]' if it is empty, otherwise `[{Key, Value}]'. <br />
-%%  - `map': An object is decoded as `#{}' if it is empty, otherwise `#{Key => Value}'. <br />
-%%  - default: `map' <br />
+-type decode_option() :: {object_format, tuple | proplist | map}
+                       | {allow_ctrl_chars, boolean()}.
+%% `object_format': <br />
+%% - Decoded JSON object format <br />
+%% - `tuple': An object is decoded as `{[]}' if it is empty, otherwise `{[{Key, Value}]}'. <br />
+%% - `proplist': An object is decoded as `[{}]' if it is empty, otherwise `[{Key, Value}]'. <br />
+%% - `map': An object is decoded as `#{}' if it is empty, otherwise `#{Key => Value}'. <br />
+%% - default: `map' <br />
+%%
+%% `allow_ctrl_chars': <br />
+%% - If the value is `true', strings which contain ununescaped control characters will be regarded as a legal JSON string <br />
+%% - default: `false' <br />
 
 %%--------------------------------------------------------------------------------
 %% Exported Functions

+ 12 - 6
src/jsone_decode.erl

@@ -51,9 +51,9 @@
 
 -type decode_result() :: {ok, jsone:json_value(), Rest::binary()} | {error, {Reason::term(), [erlang:stack_item()]}}.
 
--record(decode_opt_v1, { object_format=map :: tuple | proplist | map}).
--define(OPT, #decode_opt_v1).
--type opt() :: #decode_opt_v1{}.
+-record(decode_opt_v2, { object_format=map :: tuple | proplist | map, allow_ctrl_chars=false :: boolean()}).
+-define(OPT, #decode_opt_v2).
+-type opt() :: #decode_opt_v2{}.
 
 %%--------------------------------------------------------------------------------
 %% Exported Functions
@@ -158,8 +158,10 @@ string(<<$\\, B/binary>>, Base, Start, Nexts, Buf, Opt) ->
         <<$u, Bin/binary>> -> unicode_string(Bin, Start, Nexts, <<Buf/binary, Prefix/binary>>, Opt);
         _                  -> ?ERROR(string, [<<$\\, B/binary>>, Base, Start, Nexts, Buf, Opt])
     end;
-string(<<C, Bin/binary>>, Base, Start, Nexts, Buf, Opt) when 16#20 =< C ->
-    string(Bin, Base, Start, Nexts, Buf, Opt).
+string(<<C, Bin/binary>>, Base, Start, Nexts, Buf, Opt) when 16#20 =< C; Opt?OPT.allow_ctrl_chars ->
+    string(Bin, Base, Start, Nexts, Buf, Opt);
+string(Bin, Base, Start, Nexts, Buf, Opt) ->
+    ?ERROR(string, [Bin, Base, Start, Nexts, Buf, Opt]).
 
 -spec unicode_string(binary(), non_neg_integer(), [next()], binary(), opt()) -> decode_result().
 unicode_string(<<N:4/binary, Bin/binary>>, Start, Nexts, Buf, Opt) ->
@@ -276,4 +278,8 @@ parse_options(Options) ->
 -spec parse_option([jsone:decode_option()], opt()) -> opt().
 parse_option([], Opt) -> Opt;
 parse_option([{object_format,F}|T], Opt) when F =:= tuple; F =:= proplist; F =:= map ->
-    parse_option(T, Opt?OPT{object_format=F}).
+    parse_option(T, Opt?OPT{object_format=F});
+parse_option([{allow_ctrl_chars,B}|T], Opt) when is_boolean(B) ->
+    parse_option(T, Opt?OPT{allow_ctrl_chars=B});
+parse_option(List, Opt) ->
+    error(badarg, [List, Opt]).

+ 3 - 3
src/jsone_encode.erl

@@ -50,7 +50,7 @@
               | {object_members, jsone:json_object_members()}
               | {char, binary()}.
 
--record(encode_opt_v1, {
+-record(encode_opt_v2, {
           native_utf8 = false :: boolean(),
           float_format = [{scientific, 20}] :: [jsone:float_format_option()],
           datetime_format = {iso8601, 0} :: {jsone:datetime_format(), jsone:utc_offset_seconds()},
@@ -58,8 +58,8 @@
           space = 0 :: non_neg_integer(),
           indent = 0 :: non_neg_integer()
          }).
--define(OPT, #encode_opt_v1).
--type opt() :: #encode_opt_v1{}.
+-define(OPT, #encode_opt_v2).
+-type opt() :: #encode_opt_v2{}.
 
 %%--------------------------------------------------------------------------------
 %% Exported Functions

+ 16 - 0
test/jsone_decode_tests.erl

@@ -115,6 +115,22 @@ decode_test_() ->
               Expected = <<"𢁉𢂚𢃼">>,
               ?assertEqual({ok, Expected, <<"">>}, jsone_decode:decode(Input))
       end},
+     {"string: control characters",
+      fun () ->
+              Ctrls = lists:seq(0, 16#1f),
+              lists:foreach(
+                fun (C) ->
+                        %% Control characters are unacceptable
+                        ?assertMatch({error, {badarg, _}}, jsone_decode:decode(<<$", C, $">>))
+                end,
+                Ctrls),
+              lists:foreach(
+                fun (C) ->
+                        %% `allow_ctrl_chars' option allows strings which contain unescaped control characters
+                        ?assertEqual({ok, <<C>>, <<"">>}, jsone_decode:decode(<<$", C, $">>, [{allow_ctrl_chars, true}]))
+                end,
+                Ctrls)
+      end},
      {"string: invalid escape characters",
       fun () ->
               ?assertMatch({error, {badarg, _}}, jsone_decode:decode(<<"\"\\z\"">>)),    % '\z' is undefined