This is an automated email from the ASF dual-hosted git repository. nickva pushed a commit to branch fix-revs-diff-function-claause in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 9fcf73bd119c60d8176e2f583dc3f39f319a2051 Author: Nick Vatamaniuc <[email protected]> AuthorDate: Fri Jun 19 11:19:24 2026 -0400 Fix invalid revisions function_clause Previously, invalid revisions for `_missing_revs`, `_revs_diff` and likely `_purge` could trigger a function clause [1]. Instead since we're letting user provided values in there we should throw a proper "bad request" exception. [1] ``` POST /db_foo/_missing_revs { "test_doc_001": ["1-ad7c8ee69b8215bd0a0062732e9b2f59"], "_deleted": true, "new_edits": true } HTTP/2 500 Internal Server Error {"error":"unknown_error","reason":"function_clause","ref":4294547805} ``` --- src/chttpd/test/eunit/chttpd_revs_diff_tests.erl | 30 +++++++++++++++++++++++- src/fabric/src/fabric.erl | 8 +++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/chttpd/test/eunit/chttpd_revs_diff_tests.erl b/src/chttpd/test/eunit/chttpd_revs_diff_tests.erl index 910c541cc..6998228aa 100644 --- a/src/chttpd/test/eunit/chttpd_revs_diff_tests.erl +++ b/src/chttpd/test/eunit/chttpd_revs_diff_tests.erl @@ -72,11 +72,15 @@ chttpd_revs_diff_test_() -> ?TDEF_FE(t_revs_diff_non_existent_doc), ?TDEF_FE(t_revs_diff_all_revs), ?TDEF_FE(t_revs_diff_some_missing_some_not), + ?TDEF_FE(t_revs_diff_invalid_non_list_revs), + ?TDEF_FE(t_revs_diff_invalid_rev_in_list), ?TDEF_FE(t_empty_missing_revs), ?TDEF_FE(t_missing_revs_no_revs), ?TDEF_FE(t_missing_revs_non_existent_doc), ?TDEF_FE(t_missing_revs_all_revs), - ?TDEF_FE(t_missing_revs_some_missing_some_not) + ?TDEF_FE(t_missing_revs_some_missing_some_not), + ?TDEF_FE(t_missing_revs_invalid_non_list_revs), + ?TDEF_FE(t_missing_revs_invalid_rev_in_list) ] } } @@ -140,6 +144,18 @@ t_revs_diff_some_missing_some_not({Top, Db}) -> #{<<"possible_ancestors">> := PAs1} = Doc1, ?assertEqual([<<"2-revb">>, <<"2-revc">>], lists:sort(PAs1)). +t_revs_diff_invalid_non_list_revs({Top, Db}) -> + Body = #{?DOC1 => true}, + {Code, Res} = req(post, Top ++ Db ++ "/_revs_diff", Body), + ?assertEqual(400, Code), + ?assertMatch(#{<<"error">> := <<"bad_request">>}, Res). + +t_revs_diff_invalid_rev_in_list({Top, Db}) -> + Body = #{?DOC1 => [true]}, + {Code, Res} = req(post, Top ++ Db ++ "/_revs_diff", Body), + ?assertEqual(400, Code), + ?assertMatch(#{<<"error">> := <<"bad_request">>}, Res). + t_empty_missing_revs({Top, Db}) -> {Code, Res} = req(post, Top ++ Db ++ "/_missing_revs", #{}), ?assertEqual(200, Code), @@ -190,6 +206,18 @@ t_missing_revs_some_missing_some_not({Top, Db}) -> Res ). +t_missing_revs_invalid_non_list_revs({Top, Db}) -> + Body = #{?DOC1 => true}, + {Code, Res} = req(post, Top ++ Db ++ "/_missing_revs", Body), + ?assertEqual(400, Code), + ?assertMatch(#{<<"error">> := <<"bad_request">>}, Res). + +t_missing_revs_invalid_rev_in_list({Top, Db}) -> + Body = #{?DOC1 => [true]}, + {Code, Res} = req(post, Top ++ Db ++ "/_missing_revs", Body), + ?assertEqual(400, Code), + ?assertMatch(#{<<"error">> := <<"bad_request">>}, Res). + create_db(Top, Db) -> case req(put, Top ++ Db) of {201, #{}} -> diff --git a/src/fabric/src/fabric.erl b/src/fabric/src/fabric.erl index 47895374f..ab575f135 100644 --- a/src/fabric/src/fabric.erl +++ b/src/fabric/src/fabric.erl @@ -722,12 +722,16 @@ design_doc(GroupName) -> <<"_design/", GroupName/binary>>. idrevs({Id, Revs}) when is_list(Revs) -> - {docid(Id), [rev(R) || R <- Revs]}. + {docid(Id), [rev(R) || R <- Revs]}; +idrevs({_Id, _Revs}) -> + throw({bad_request, <<"Invalid revisions, must be a list">>}). rev(Rev) when is_list(Rev); is_binary(Rev) -> couch_doc:parse_rev(Rev); rev({Seq, Hash} = Rev) when is_integer(Seq), is_binary(Hash) -> - Rev. + Rev; +rev(_BadRev) -> + throw({bad_request, <<"Invalid rev format">>}). %% @doc convenience method, useful when testing or calling fabric from the shell opts(Options) ->
