summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Newson <rnewson@apache.org>2014-02-17 21:55:40 +0000
committerRobert Newson <rnewson@apache.org>2014-02-17 21:55:40 +0000
commit3ad1f6a76e10ccf4f9608fd6d8de06da7491c548 (patch)
treecc2ca558f4188b238745b55f5f1e9dfb8bb3fd7c
parentea9bc69e003426c256893f53063b24dd71b6ac46 (diff)
parentf7ca266b41a6fb8dd8e8167b8c8d44df00a1907f (diff)
downloadcouchdb-3ad1f6a76e10ccf4f9608fd6d8de06da7491c548.tar.gz
Merge branch '2059-feature-uri-len-negotiation'
-rw-r--r--etc/couchdb/default.ini.tpl.in2
-rw-r--r--src/couch_replicator/src/couch_replicator_api_wrap.erl40
-rw-r--r--src/couchdb/couch_httpd.erl18
3 files changed, 49 insertions, 11 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in
index 3267001ae..fd953c2fc 100644
--- a/etc/couchdb/default.ini.tpl.in
+++ b/etc/couchdb/default.ini.tpl.in
@@ -52,6 +52,8 @@ allow_jsonp = false
;socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}]
log_max_chunk_size = 1000000
enable_cors = false
+; CouchDB can optionally enforce a maximum uri length;
+; max_uri_length = 8000
[ssl]
port = 6984
diff --git a/src/couch_replicator/src/couch_replicator_api_wrap.erl b/src/couch_replicator/src/couch_replicator_api_wrap.erl
index 5a42bb31b..3cf942d69 100644
--- a/src/couch_replicator/src/couch_replicator_api_wrap.erl
+++ b/src/couch_replicator/src/couch_replicator_api_wrap.erl
@@ -50,6 +50,9 @@
-define(MAX_WAIT, 5 * 60 * 1000).
+-define(MAX_URL_LEN, 7000).
+-define(MIN_URL_LEN, 200).
+
db_uri(#httpdb{url = Url}) ->
couch_util:url_strip_password(Url);
@@ -171,13 +174,16 @@ open_doc_revs(#httpdb{} = HttpDb, Id, Revs, Options, Fun, Acc) ->
QS = options_to_query_args(HttpDb, Path, [revs, {open_revs, Revs} | Options]),
{Pid, Ref} = spawn_monitor(fun() ->
Self = self(),
- Callback = fun(200, Headers, StreamDataFun) ->
+ Callback = fun
+ (200, Headers, StreamDataFun) ->
remote_open_doc_revs_streamer_start(Self),
{<<"--">>, _, _} = couch_httpd:parse_multipart_request(
get_value("Content-Type", Headers),
StreamDataFun,
fun mp_parse_mixed/1
- )
+ );
+ (414, _, _) ->
+ exit(request_uri_too_long)
end,
Streamer = spawn_link(fun() ->
Params = [
@@ -217,6 +223,17 @@ open_doc_revs(#httpdb{} = HttpDb, Id, Revs, Options, Fun, Acc) ->
Ret;
{'DOWN', Ref, process, Pid, {{nocatch, {missing_stub,_} = Stub}, _}} ->
throw(Stub);
+ {'DOWN', Ref, process, Pid, request_uri_too_long} ->
+ NewMaxLen = get_value(max_url_len, Options, ?MAX_URL_LEN) div 2,
+ case NewMaxLen < ?MIN_URL_LEN of
+ true ->
+ throw(request_uri_too_long);
+ false ->
+ ?LOG_INFO("Reducing url length to ~B because of 414 response", [NewMaxLen]),
+ Options1 = lists:keystore(max_url_len, 1, Options,
+ {max_url_len, NewMaxLen}),
+ open_doc_revs(HttpDb, Id, Revs, Options1, Fun, Acc)
+ end;
{'DOWN', Ref, process, Pid, Else} ->
Url = couch_util:url_strip_password(
couch_replicator_httpc:full_url(HttpDb, [{path,Path}, {qs,QS}])
@@ -501,7 +518,11 @@ changes_json_req(Db, FilterName, {QueryParams}, _Options) ->
]}.
-options_to_query_args(HttpDb, Path, Options) ->
+options_to_query_args(HttpDb, Path, Options0) ->
+ case lists:keytake(max_url_len, 1, Options0) of
+ false -> MaxLen = ?MAX_URL_LEN, Options = Options0;
+ {value, {max_url_len, MaxLen}, Options} -> ok
+ end,
case lists:keytake(atts_since, 1, Options) of
false ->
options_to_query_args(Options, []);
@@ -514,7 +535,7 @@ options_to_query_args(HttpDb, Path, Options) ->
RevList = atts_since_arg(
length("GET " ++ FullUrl ++ " HTTP/1.1\r\n") +
length("&atts_since=") + 6, % +6 = % encoded [ and ]
- PAs, []),
+ PAs, MaxLen, []),
[{"atts_since", ?JSON_ENCODE(RevList)} | QueryArgs1]
end.
@@ -535,12 +556,9 @@ options_to_query_args([{open_revs, Revs} | Rest], Acc) ->
JsonRevs = ?b2l(?JSON_ENCODE(couch_doc:revs_to_strs(Revs))),
options_to_query_args(Rest, [{"open_revs", JsonRevs} | Acc]).
-
-atts_since_arg(_UrlLen, [], Acc) ->
+atts_since_arg(_UrlLen, [], _MaxLen, Acc) ->
lists:reverse(Acc);
-atts_since_arg(UrlLen, [PA | Rest], Acc) ->
- MaxUrlLen = list_to_integer(
- couch_config:get("replicator", "max_url_len", "7000")),
+atts_since_arg(UrlLen, [PA | Rest], MaxLen, Acc) ->
RevStr = couch_doc:rev_to_str(PA),
NewUrlLen = case Rest of
[] ->
@@ -550,11 +568,11 @@ atts_since_arg(UrlLen, [PA | Rest], Acc) ->
% plus 2 double quotes and a comma (% encoded)
UrlLen + size(RevStr) + 9
end,
- case NewUrlLen >= MaxUrlLen of
+ case NewUrlLen >= MaxLen of
true ->
lists:reverse(Acc);
false ->
- atts_since_arg(NewUrlLen, Rest, [RevStr | Acc])
+ atts_since_arg(NewUrlLen, Rest, MaxLen, [RevStr | Acc])
end.
diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl
index f00fdd068..7ee3e3acc 100644
--- a/src/couchdb/couch_httpd.erl
+++ b/src/couchdb/couch_httpd.erl
@@ -310,6 +310,7 @@ handle_request_int(MochiReq, DefaultFun,
{ok, Resp} =
try
+ check_request_uri_length(RawUri),
case couch_httpd_cors:is_preflight_request(HttpReq) of
#httpd{} ->
case authenticate_request(HttpReq, AuthHandlers) of
@@ -343,6 +344,8 @@ handle_request_int(MochiReq, DefaultFun,
send_error(HttpReq, {bad_otp_release, ErrorReason});
exit:{body_too_large, _} ->
send_error(HttpReq, request_entity_too_large);
+ exit:{uri_too_long, _} ->
+ send_error(HttpReq, request_uri_too_long);
throw:Error ->
Stack = erlang:get_stacktrace(),
?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
@@ -369,6 +372,19 @@ handle_request_int(MochiReq, DefaultFun,
couch_stats_collector:increment({httpd, requests}),
{ok, Resp}.
+check_request_uri_length(Uri) ->
+ check_request_uri_length(Uri, couch_config:get("httpd", "max_uri_length")).
+
+check_request_uri_length(_Uri, undefined) ->
+ ok;
+check_request_uri_length(Uri, MaxUriLen) when is_list(MaxUriLen) ->
+ case length(Uri) > list_to_integer(MaxUriLen) of
+ true ->
+ throw(request_uri_too_long);
+ false ->
+ ok
+ end.
+
% Try authentication handlers in order until one sets a user_ctx
% the auth funs also have the option of returning a response
% move this to couch_httpd_auth?
@@ -826,6 +842,8 @@ error_info(file_exists) ->
"created, the file already exists.">>};
error_info(request_entity_too_large) ->
{413, <<"too_large">>, <<"the request entity is too large">>};
+error_info(request_uri_too_long) ->
+ {414, <<"too_long">>, <<"the request entity is too long">>};
error_info({bad_ctype, Reason}) ->
{415, <<"bad_content_type">>, Reason};
error_info(requested_range_not_satisfiable) ->