diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2017-05-08 11:32:28 -0400 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2017-05-09 16:39:23 -0400 |
commit | c3d540410a37ab6137efd86c05c7de722ea6b499 (patch) | |
tree | 39f33423d5d39c00ad3de6aad19c594498d837d0 | |
parent | 7c3aef6999691bd02fbae56a72c991a6150af261 (diff) | |
download | couchdb-c3d540410a37ab6137efd86c05c7de722ea6b499.tar.gz |
Handle non-default _replicator dbs in _scheduler/docs endpoint
Previously _scheduler/docs assumed only the default _replicator db.
To provide consistency and to allow disambiguation between a db named
'db/_replicator' and the document named 'db/_replicator' in the
default replicator db, access to the single document API is changed to
always require the replicator db. That is `/docid` should not be
`/_replicator/docid`.
Now these kinds of paths are accepted after `_scheduler/docs`:
* `/` : all docs from default _replicator db
* `/_replicator` : all docs from default replicator db
* `/other%2f_replicator` : non-default replicator db, urlencoded
* `/other/_replicator` : non-default replicator db, unencoded
* `/other%2f_replicator/docid` : doc from a non-default db, urlencoded
* `/other/_replicator/docid` : doc from a non-default db, db is unencoded
Because `_replicator` is not a valid document ID, it's possible to unambiguously
parse unescaped db paths.
Issue: #506
-rw-r--r-- | src/couch_replicator/src/couch_replicator_httpd.erl | 105 |
1 files changed, 81 insertions, 24 deletions
diff --git a/src/couch_replicator/src/couch_replicator_httpd.erl b/src/couch_replicator/src/couch_replicator_httpd.erl index 364d09858..0f78ce1d5 100644 --- a/src/couch_replicator/src/couch_replicator_httpd.erl +++ b/src/couch_replicator/src/couch_replicator_httpd.erl @@ -33,6 +33,9 @@ -define(DEFAULT_TASK_LIMIT, 100). -define(REPDB, <<"_replicator">>). +% This is a macro so it can be used as a guard +-define(ISREPDB(X), X =:= ?REPDB orelse binary_part(X, {byte_size(X), -12}) + =:= <<"/_replicator">>). handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"jobs">>]}=Req) -> @@ -59,30 +62,25 @@ handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"jobs">>,JobId]}=Req) throw(not_found) end; handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"docs">>]}=Req) -> - VArgs0 = couch_mrview_http:parse_params(Req, undefined), - StatesQs = chttpd:qs_value(Req, "states"), - States = couch_replicator_httpd_util:parse_replication_state_filter(StatesQs), - VArgs1 = VArgs0#mrargs{ - view_type = map, - include_docs = true, - reduce = false, - extra = [{filter_states, States}] - }, - VArgs2 = couch_mrview_util:validate_args(VArgs1), - Opts = [{user_ctx, Req#httpd.user_ctx}], - Db = ?REPDB, - Max = chttpd:chunked_response_buffer_size(), - Acc = couch_replicator_httpd_util:docs_acc_new(Req, Db, Max), - Cb = fun couch_replicator_httpd_util:docs_cb/2, - {ok, RAcc} = couch_replicator_fabric:docs(Db, Opts, VArgs2, Cb, Acc), - {ok, couch_replicator_httpd_util:docs_acc_response(RAcc)}; -handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"docs">>,DocId]}=Req) -> - UserCtx = Req#httpd.user_ctx, - case couch_replicator:doc(?REPDB, DocId, UserCtx#user_ctx.roles) of - {ok, DocInfo} -> - send_json(Req, couch_replicator_httpd_util:update_db_name(DocInfo)); - {error, not_found} -> - throw(not_found) + handle_scheduler_docs(?REPDB, Req); +handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"docs">>,Db]}=Req) + when ?ISREPDB(Db) -> + handle_scheduler_docs(Db, Req); +handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"docs">>,Db,DocId]} + = Req) when ?ISREPDB(Db) -> + handle_scheduler_doc(Db, DocId, Req); +% Allow users to pass in unencoded _replicator database names (/ are not +% escaped). This is possible here because _replicator is not a valid document +% ID so can disambiguate between an element of a db path and the document ID. +handle_scheduler_req(#httpd{method='GET', path_parts=[_,<<"docs">>|Unquoted]} + = Req) -> + case parse_unquoted_docs_path(Unquoted) of + {db_only, Db} -> + handle_scheduler_docs(Db, Req); + {db_and_doc, Db, DocId} -> + handle_scheduler_doc(Db, DocId, Req); + {error, invalid} -> + throw(bad_request) end; handle_scheduler_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). @@ -112,3 +110,62 @@ handle_req(#httpd{method = 'POST', user_ctx = UserCtx} = Req) -> handle_req(Req) -> send_method_not_allowed(Req, "POST"). + + +handle_scheduler_docs(Db, Req) when is_binary(Db) -> + VArgs0 = couch_mrview_http:parse_params(Req, undefined), + StatesQs = chttpd:qs_value(Req, "states"), + States = couch_replicator_httpd_util:parse_replication_state_filter(StatesQs), + VArgs1 = VArgs0#mrargs{ + view_type = map, + include_docs = true, + reduce = false, + extra = [{filter_states, States}] + }, + VArgs2 = couch_mrview_util:validate_args(VArgs1), + Opts = [{user_ctx, Req#httpd.user_ctx}], + Max = chttpd:chunked_response_buffer_size(), + Acc = couch_replicator_httpd_util:docs_acc_new(Req, Db, Max), + Cb = fun couch_replicator_httpd_util:docs_cb/2, + {ok, RAcc} = couch_replicator_fabric:docs(Db, Opts, VArgs2, Cb, Acc), + {ok, couch_replicator_httpd_util:docs_acc_response(RAcc)}. + + +handle_scheduler_doc(Db, DocId, Req) when is_binary(Db), is_binary(DocId) -> + UserCtx = Req#httpd.user_ctx, + case couch_replicator:doc(Db, DocId, UserCtx#user_ctx.roles) of + {ok, DocInfo} -> + send_json(Req, couch_replicator_httpd_util:update_db_name(DocInfo)); + {error, not_found} -> + throw(not_found) + end. + + +parse_unquoted_docs_path([_, _ | _] = Unquoted) -> + DbAndAfter = lists:dropwhile(fun(E) -> E =/= ?REPDB end, Unquoted), + BeforeRDb = lists:takewhile(fun(E) -> E =/= ?REPDB end, Unquoted), + case DbAndAfter of + [] -> + {error, invalid}; + [?REPDB] -> + {db_only, filename:join(BeforeRDb ++ [?REPDB])}; + [?REPDB, DocId] -> + {db_and_doc, filename:join(BeforeRDb ++ [?REPDB]), DocId} + end. + + +-ifdef(TEST). + +-include_lib("eunit/include/eunit.hrl"). + +unquoted_scheduler_docs_path_test_() -> + [?_assertEqual(Res, parse_unquoted_docs_path(Path)) || {Res, Path} <- [ + {{error, invalid}, [<<"a">>,<< "b">>]}, + {{db_only, <<"a/_replicator">>}, [<<"a">>, ?REPDB]}, + {{db_only, <<"a/b/_replicator">>}, [<<"a">>, <<"b">>, ?REPDB]}, + {{db_and_doc, <<"_replicator">>, <<"x">>}, [?REPDB, <<"x">>]}, + {{db_and_doc, <<"a/_replicator">>, <<"x">>}, [<<"a">>, ?REPDB, <<"x">>]}, + {{error, invalid}, [<<"a/_replicator">>,<<"x">>]} + ]]. + +-endif. |