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) ->

Reply via email to