You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2026/06/19 15:30:05 UTC

(couchdb) branch fix-revs-diff-function-claause created (now 9fcf73bd1)

This is an automated email from the ASF dual-hosted git repository.

nickva pushed a change to branch fix-revs-diff-function-claause
in repository https://gitbox.apache.org/repos/asf/couchdb.git


      at 9fcf73bd1 Fix invalid revisions function_clause

This branch includes the following new commits:

     new 9fcf73bd1 Fix invalid revisions function_clause

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(couchdb) 01/01: Fix invalid revisions function_clause

Posted by va...@apache.org.
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 <va...@gmail.com>
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) ->