diff options
author | Robert Newson <rnewson@apache.org> | 2014-02-17 21:55:40 +0000 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2014-02-17 21:55:40 +0000 |
commit | 3ad1f6a76e10ccf4f9608fd6d8de06da7491c548 (patch) | |
tree | cc2ca558f4188b238745b55f5f1e9dfb8bb3fd7c | |
parent | ea9bc69e003426c256893f53063b24dd71b6ac46 (diff) | |
parent | f7ca266b41a6fb8dd8e8167b8c8d44df00a1907f (diff) | |
download | couchdb-3ad1f6a76e10ccf4f9608fd6d8de06da7491c548.tar.gz |
Merge branch '2059-feature-uri-len-negotiation'
-rw-r--r-- | etc/couchdb/default.ini.tpl.in | 2 | ||||
-rw-r--r-- | src/couch_replicator/src/couch_replicator_api_wrap.erl | 40 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 18 |
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) -> |