Allow limiting length of document ID Previously it was not possibly to define a maxum document ID size. That meant large document ID would hit various limitations and corner cases. For example, large document IDs could be inserted via a _bulk_docs endpoint but then trying to insert the same document via a single HTTP method like PUT would fail because of a limitation in Mochiweb's HTTP parser.
Let operators specify a maxium document ID length via the ``` couchdb.max_document_id_length = infinity | Integer ``` configuration. The default value of `infinity` keeps the current behavior where document ID length is not checked. COUCHDB-3293 Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/2a263f84 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/2a263f84 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/2a263f84 Branch: refs/heads/COUCHDB-3288-remove-public-db-record Commit: 2a263f84db62e4849a41322b92588c6893169198 Parents: bbbd532 Author: Nick Vatamaniuc <vatam...@apache.org> Authored: Thu Feb 9 10:13:42 2017 -0500 Committer: Nick Vatamaniuc <vatam...@apache.org> Committed: Thu Feb 9 10:13:42 2017 -0500 ---------------------------------------------------------------------- src/couch_doc.erl | 8 ++++++++ test/couch_doc_json_tests.erl | 7 +++++++ test/couch_doc_tests.erl | 20 +++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a263f84/src/couch_doc.erl ---------------------------------------------------------------------- diff --git a/src/couch_doc.erl b/src/couch_doc.erl index af14038..a913eee 100644 --- a/src/couch_doc.erl +++ b/src/couch_doc.erl @@ -174,6 +174,14 @@ validate_docid(<<"_design/">>) -> validate_docid(<<"_local/">>) -> throw({illegal_docid, <<"Illegal document id `_local/`">>}); validate_docid(Id) when is_binary(Id) -> + MaxLen = case config:get("couchdb", "max_document_id_length", "infinity") of + "infinity" -> infinity; + IntegerVal -> list_to_integer(IntegerVal) + end, + case MaxLen > 0 andalso byte_size(Id) > MaxLen of + true -> throw({illegal_docid, <<"Document id is too long">>}); + false -> ok + end, case couch_util:validate_utf8(Id) of false -> throw({illegal_docid, <<"Document id must be valid UTF-8">>}); true -> ok http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a263f84/test/couch_doc_json_tests.erl ---------------------------------------------------------------------- diff --git a/test/couch_doc_json_tests.erl b/test/couch_doc_json_tests.erl index ae4d73c..9003d06 100644 --- a/test/couch_doc_json_tests.erl +++ b/test/couch_doc_json_tests.erl @@ -18,11 +18,13 @@ setup() -> mock(couch_log), + mock(config), mock(couch_db_plugin), ok. teardown(_) -> meck:unload(couch_log), + meck:unload(config), meck:unload(couch_db_plugin), ok. @@ -33,6 +35,11 @@ mock(couch_db_plugin) -> mock(couch_log) -> ok = meck:new(couch_log, [passthrough]), ok = meck:expect(couch_log, debug, fun(_, _) -> ok end), + ok; +mock(config) -> + meck:new(config, [passthrough]), + meck:expect(config, get, fun(_, _) -> undefined end), + meck:expect(config, get, fun(_, _, Default) -> Default end), ok. http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/2a263f84/test/couch_doc_tests.erl ---------------------------------------------------------------------- diff --git a/test/couch_doc_tests.erl b/test/couch_doc_tests.erl index fce4ff7..d24cd67 100644 --- a/test/couch_doc_tests.erl +++ b/test/couch_doc_tests.erl @@ -29,8 +29,10 @@ doc_from_multi_part_stream_test() -> ContentType = "multipart/related;boundary=multipart_related_boundary~~~~~~~~~~~~~~~~~~~~", DataFun = fun() -> request(start) end, + mock_config_max_document_id_length(), {ok, #doc{id = <<"doc0">>, atts = [_]}, _Fun, _Parser} = couch_doc:doc_from_multi_part_stream(ContentType, DataFun), + meck:unload(config), ok. doc_to_multi_part_stream_test() -> @@ -75,16 +77,19 @@ len_doc_to_multi_part_stream_test() -> validate_docid_test_() -> {setup, fun() -> + mock_config_max_document_id_length(), ok = meck:new(couch_db_plugin, [passthrough]), meck:expect(couch_db_plugin, validate_docid, fun(_) -> false end) end, fun(_) -> + meck:unload(config), meck:unload(couch_db_plugin) end, [ ?_assertEqual(ok, couch_doc:validate_docid(<<"idx">>)), ?_assertEqual(ok, couch_doc:validate_docid(<<"_design/idx">>)), ?_assertEqual(ok, couch_doc:validate_docid(<<"_local/idx">>)), + ?_assertEqual(ok, couch_doc:validate_docid(large_id(1024))), ?_assertThrow({illegal_docid, _}, couch_doc:validate_docid(<<>>)), ?_assertThrow({illegal_docid, _}, @@ -96,10 +101,15 @@ validate_docid_test_() -> ?_assertThrow({illegal_docid, _}, couch_doc:validate_docid(<<"_design/">>)), ?_assertThrow({illegal_docid, _}, - couch_doc:validate_docid(<<"_local/">>)) + couch_doc:validate_docid(<<"_local/">>)), + ?_assertThrow({illegal_docid, _}, + couch_doc:validate_docid(large_id(1025))) ] }. +large_id(N) -> + << <<"x">> || _ <- lists:seq(1, N) >>. + request(start) -> {ok, Doc} = file:read_file(?REQUEST_FIXTURE), {Doc, fun() -> request(stop) end}; @@ -116,3 +126,11 @@ send(Data, Acc) -> collected() -> B = binary:replace(iolist_to_binary(get(data)), <<"\r\n">>, <<0>>, [global]), binary:split(B, [<<0>>], [global]). + +mock_config_max_document_id_length() -> + ok = meck:new(config, [passthrough]), + meck:expect(config, get, + fun("couchdb", "max_document_id_length", "infinity") -> "1024"; + (Key, Val, Default) -> meck:passthrough([Key, Val, Default]) + end + ).