summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2017-05-08 11:32:28 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2017-05-09 16:39:23 -0400
commitc3d540410a37ab6137efd86c05c7de722ea6b499 (patch)
tree39f33423d5d39c00ad3de6aad19c594498d837d0
parent7c3aef6999691bd02fbae56a72c991a6150af261 (diff)
downloadcouchdb-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.erl105
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.