Skip to content

Commit

Permalink
Fix replication ID parsing in URL paths
Browse files Browse the repository at this point in the history
Previously users had to URL encode replication IDs when using
`_scheduler/jobs/<job_id>` endpoint because Mochiweb incorrectly decoded the
`+` character from URL path. So users were forced to encode so that the
replicator would correctly receive a `+` after Mochiweb parsing.

`+` is decoded as ` ` (space) probably because in query strings that's a valid
application/x-www-form-urlencoded encoding, but that decoding is not meant for
decoding URL paths, only query strings.

Notice RFC 3986 https://tools.ietf.org/html/rfc3986#section-2.2

`+` is a `sub-delim` (term from RFC)  and in the path component it can be used
unquoted as a delimiter.

https://tools.ietf.org/html/rfc3986#section-3.3

Indeed, the replication ID is a compound ID and `+` is a valid delimiter
which separates the base part from the extensions.

For more details see also:

perwendel/spark#490

https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1

Fixes apache#825
  • Loading branch information
nickva authored and willholley committed May 22, 2018
1 parent e7de210 commit f4c28a6
Showing 1 changed file with 15 additions and 1 deletion.
16 changes: 15 additions & 1 deletion src/couch_replicator/src/couch_replicator_ids.erl
Expand Up @@ -78,7 +78,11 @@ replication_id(#rep{user_ctx = UserCtx} = Rep, 1) ->
-spec convert([_] | binary() | {string(), string()}) -> {string(), string()}.
convert(Id) when is_list(Id) ->
convert(?l2b(Id));
convert(Id) when is_binary(Id) ->
convert(Id0) when is_binary(Id0) ->
% Spaces can result from mochiweb incorrectly unquoting + characters from
% the URL path. So undo the incorrect parsing here to avoid forcing
% users to url encode + characters.
Id = binary:replace(Id0, <<" ">>, <<"+">>, [global]),
lists:splitwith(fun(Char) -> Char =/= $+ end, ?b2l(Id));
convert({BaseId, Ext} = Id) when is_list(BaseId), is_list(Ext) ->
Id.
Expand Down Expand Up @@ -222,6 +226,16 @@ get_non_default_port(_Schema, Port) ->

-include_lib("eunit/include/eunit.hrl").


replication_id_convert_test_() ->
[?_assertEqual(Expected, convert(Id)) || {Expected, Id} <- [
{{"abc", ""}, "abc"},
{{"abc", ""}, <<"abc">>},
{{"abc", "+x+y"}, <<"abc+x+y">>},
{{"abc", "+x+y"}, {"abc", "+x+y"}},
{{"abc", "+x+y"}, <<"abc x y">>}
]].

http_v4_endpoint_test_() ->
[?_assertMatch({remote, User, Host, Port, Path, HeadersNoAuth, undefined},
get_v4_endpoint(nil, #httpdb{url = Url, headers = Headers})) ||
Expand Down

0 comments on commit f4c28a6

Please sign in to comment.