diff options
Diffstat (limited to 'src/couch/src/couch_httpd_misc_handlers.erl')
-rw-r--r-- | src/couch/src/couch_httpd_misc_handlers.erl | 288 |
1 files changed, 166 insertions, 122 deletions
diff --git a/src/couch/src/couch_httpd_misc_handlers.erl b/src/couch/src/couch_httpd_misc_handlers.erl index ea9c1cb84..d9c591875 100644 --- a/src/couch/src/couch_httpd_misc_handlers.erl +++ b/src/couch/src/couch_httpd_misc_handlers.erl @@ -12,87 +12,104 @@ -module(couch_httpd_misc_handlers). --export([handle_welcome_req/2,handle_favicon_req/2,handle_utils_dir_req/2, +-export([ + handle_welcome_req/2, + handle_favicon_req/2, + handle_utils_dir_req/2, handle_all_dbs_req/1, - handle_uuids_req/1,handle_config_req/1, - handle_task_status_req/1, handle_file_req/2]). - + handle_uuids_req/1, + handle_config_req/1, + handle_task_status_req/1, + handle_file_req/2 +]). -include_lib("couch/include/couch_db.hrl"). --import(couch_httpd, - [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2, - start_json_response/2,send_chunk/2,last_chunk/1,end_json_response/1, - start_chunked_response/3, send_error/4]). +-import( + couch_httpd, + [ + send_json/2, send_json/3, send_json/4, + send_method_not_allowed/2, + start_json_response/2, + send_chunk/2, + last_chunk/1, + end_json_response/1, + start_chunked_response/3, + send_error/4 + ] +). % httpd global handlers -handle_welcome_req(#httpd{method='GET'}=Req, WelcomeMessage) -> - send_json(Req, {[ - {couchdb, WelcomeMessage}, - {uuid, couch_server:get_uuid()}, - {version, list_to_binary(couch_server:get_version())} - ] ++ case config:get("vendor") of - [] -> - []; - Properties -> - [{vendor, {[{?l2b(K), ?l2b(V)} || {K, V} <- Properties]}}] - end +handle_welcome_req(#httpd{method = 'GET'} = Req, WelcomeMessage) -> + send_json(Req, { + [ + {couchdb, WelcomeMessage}, + {uuid, couch_server:get_uuid()}, + {version, list_to_binary(couch_server:get_version())} + ] ++ + case config:get("vendor") of + [] -> + []; + Properties -> + [{vendor, {[{?l2b(K), ?l2b(V)} || {K, V} <- Properties]}}] + end }); handle_welcome_req(Req, _) -> send_method_not_allowed(Req, "GET,HEAD"). -handle_favicon_req(#httpd{method='GET'}=Req, DocumentRoot) -> - {{Year,Month,Day},Time} = erlang:universaltime(), - OneYearFromNow = {{Year+1,Month,Day},Time}, +handle_favicon_req(#httpd{method = 'GET'} = Req, DocumentRoot) -> + {{Year, Month, Day}, Time} = erlang:universaltime(), + OneYearFromNow = {{Year + 1, Month, Day}, Time}, CachingHeaders = [ %favicon should expire a year from now {"Cache-Control", "public, max-age=31536000"}, {"Expires", couch_util:rfc1123_date(OneYearFromNow)} ], couch_httpd:serve_file(Req, "favicon.ico", DocumentRoot, CachingHeaders); - handle_favicon_req(Req, _) -> send_method_not_allowed(Req, "GET,HEAD"). -handle_file_req(#httpd{method='GET'}=Req, Document) -> +handle_file_req(#httpd{method = 'GET'} = Req, Document) -> couch_httpd:serve_file(Req, filename:basename(Document), filename:dirname(Document)); - handle_file_req(Req, _) -> send_method_not_allowed(Req, "GET,HEAD"). handle_utils_dir_req(Req, _) -> - send_error(Req, 410, <<"no_node_local_fauxton">>, - ?l2b("The web interface is no longer available on the node-local port.")). - + send_error( + Req, + 410, + <<"no_node_local_fauxton">>, + ?l2b("The web interface is no longer available on the node-local port.") + ). -handle_all_dbs_req(#httpd{method='GET'}=Req) -> +handle_all_dbs_req(#httpd{method = 'GET'} = Req) -> {ok, DbNames} = couch_server:all_databases(), send_json(Req, DbNames); handle_all_dbs_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). - -handle_task_status_req(#httpd{method='GET'}=Req) -> +handle_task_status_req(#httpd{method = 'GET'} = Req) -> ok = couch_httpd:verify_is_server_admin(Req), % convert the list of prop lists to a list of json objects send_json(Req, [{Props} || Props <- couch_task_status:all()]); handle_task_status_req(Req) -> send_method_not_allowed(Req, "GET,HEAD"). - -handle_uuids_req(#httpd{method='GET'}=Req) -> - Max = config:get_integer("uuids","max_count", 1000), - Count = try list_to_integer(couch_httpd:qs_value(Req, "count", "1")) of - N when N > Max -> - throw({bad_request, <<"count parameter too large">>}); - N when N < 0 -> - throw({bad_request, <<"count must be a positive integer">>}); - N -> N - catch - error:badarg -> - throw({bad_request, <<"count must be a positive integer">>}) - end, +handle_uuids_req(#httpd{method = 'GET'} = Req) -> + Max = config:get_integer("uuids", "max_count", 1000), + Count = + try list_to_integer(couch_httpd:qs_value(Req, "count", "1")) of + N when N > Max -> + throw({bad_request, <<"count parameter too large">>}); + N when N < 0 -> + throw({bad_request, <<"count must be a positive integer">>}); + N -> + N + catch + error:badarg -> + throw({bad_request, <<"count must be a positive integer">>}) + end, UUIDs = [couch_uuids:new() || _ <- lists:seq(1, Count)], Etag = couch_httpd:make_etag(UUIDs), couch_httpd:etag_respond(Req, Etag, fun() -> @@ -109,51 +126,60 @@ handle_uuids_req(#httpd{method='GET'}=Req) -> handle_uuids_req(Req) -> send_method_not_allowed(Req, "GET"). - % Config request handler - % GET /_config/ % GET /_config -handle_config_req(#httpd{method='GET', path_parts=[_]}=Req) -> +handle_config_req(#httpd{method = 'GET', path_parts = [_]} = Req) -> ok = couch_httpd:verify_is_server_admin(Req), - Grouped = lists:foldl(fun({{Section, Key}, Value}, Acc) -> - case dict:is_key(Section, Acc) of - true -> - dict:append(Section, {list_to_binary(Key), list_to_binary(Value)}, Acc); - false -> - dict:store(Section, [{list_to_binary(Key), list_to_binary(Value)}], Acc) - end - end, dict:new(), config:all()), - KVs = dict:fold(fun(Section, Values, Acc) -> - [{list_to_binary(Section), {Values}} | Acc] - end, [], Grouped), + Grouped = lists:foldl( + fun({{Section, Key}, Value}, Acc) -> + case dict:is_key(Section, Acc) of + true -> + dict:append(Section, {list_to_binary(Key), list_to_binary(Value)}, Acc); + false -> + dict:store(Section, [{list_to_binary(Key), list_to_binary(Value)}], Acc) + end + end, + dict:new(), + config:all() + ), + KVs = dict:fold( + fun(Section, Values, Acc) -> + [{list_to_binary(Section), {Values}} | Acc] + end, + [], + Grouped + ), send_json(Req, 200, {KVs}); % GET /_config/Section -handle_config_req(#httpd{method='GET', path_parts=[_,Section]}=Req) -> +handle_config_req(#httpd{method = 'GET', path_parts = [_, Section]} = Req) -> ok = couch_httpd:verify_is_server_admin(Req), - KVs = [{list_to_binary(Key), list_to_binary(Value)} - || {Key, Value} <- config:get(Section)], + KVs = [ + {list_to_binary(Key), list_to_binary(Value)} + || {Key, Value} <- config:get(Section) + ], send_json(Req, 200, {KVs}); % GET /_config/Section/Key -handle_config_req(#httpd{method='GET', path_parts=[_, Section, Key]}=Req) -> +handle_config_req(#httpd{method = 'GET', path_parts = [_, Section, Key]} = Req) -> ok = couch_httpd:verify_is_server_admin(Req), case config:get(Section, Key, undefined) of - undefined -> - throw({not_found, unknown_config_value}); - Value -> - send_json(Req, 200, list_to_binary(Value)) + undefined -> + throw({not_found, unknown_config_value}); + Value -> + send_json(Req, 200, list_to_binary(Value)) end; % POST /_config/_reload - Flushes unpersisted config values from RAM -handle_config_req(#httpd{method='POST', path_parts=[_, <<"_reload">>]}=Req) -> +handle_config_req(#httpd{method = 'POST', path_parts = [_, <<"_reload">>]} = Req) -> couch_httpd:validate_ctype(Req, "application/json"), _ = couch_httpd:body(Req), ok = couch_httpd:verify_is_server_admin(Req), ok = config:reload(), send_json(Req, 200, {[{ok, true}]}); % PUT or DELETE /_config/Section/Key -handle_config_req(#httpd{method=Method, path_parts=[_, Section, Key]}=Req) - when (Method == 'PUT') or (Method == 'DELETE') -> +handle_config_req(#httpd{method = Method, path_parts = [_, Section, Key]} = Req) when + (Method == 'PUT') or (Method == 'DELETE') +-> ok = couch_httpd:verify_is_server_admin(Req), couch_util:check_config_blacklist(Section), Persist = couch_httpd:header_value(Req, "X-Couch-Persist") /= "false", @@ -169,19 +195,25 @@ handle_config_req(#httpd{method=Method, path_parts=[_, Section, Key]}=Req) % variable itself. FallbackWhitelist = [{<<"chttpd">>, <<"config_whitelist">>}], - Whitelist = case couch_util:parse_term(WhitelistValue) of - {ok, Value} when is_list(Value) -> - Value; - {ok, _NonListValue} -> - FallbackWhitelist; - {error, _} -> - [{WhitelistSection, WhitelistKey}] = FallbackWhitelist, - couch_log:error("Only whitelisting ~s/~s due to error" - " parsing: ~p", - [WhitelistSection, WhitelistKey, - WhitelistValue]), - FallbackWhitelist - end, + Whitelist = + case couch_util:parse_term(WhitelistValue) of + {ok, Value} when is_list(Value) -> + Value; + {ok, _NonListValue} -> + FallbackWhitelist; + {error, _} -> + [{WhitelistSection, WhitelistKey}] = FallbackWhitelist, + couch_log:error( + "Only whitelisting ~s/~s due to error" + " parsing: ~p", + [ + WhitelistSection, + WhitelistKey, + WhitelistValue + ] + ), + FallbackWhitelist + end, IsRequestedKeyVal = fun(Element) -> case Element of @@ -207,8 +239,12 @@ handle_config_req(#httpd{method=Method, path_parts=[_, Section, Key]}=Req) handle_approved_config_req(Req, Persist); _NotWhitelisted -> % Disallow modifying this non-whitelisted variable. - send_error(Req, 400, <<"modification_not_allowed">>, - ?l2b("This config variable is read-only")) + send_error( + Req, + 400, + <<"modification_not_allowed">>, + ?l2b("This config variable is read-only") + ) end end; handle_config_req(Req) -> @@ -218,52 +254,60 @@ handle_config_req(Req) -> % "value" handle_approved_config_req(Req, Persist) -> Query = couch_httpd:qs(Req), - UseRawValue = case lists:keyfind("raw", 1, Query) of - false -> false; % Not specified - {"raw", ""} -> false; % Specified with no value, i.e. "?raw" and "?raw=" - {"raw", "false"} -> false; - {"raw", "true"} -> true; - {"raw", InvalidValue} -> InvalidValue - end, + UseRawValue = + case lists:keyfind("raw", 1, Query) of + % Not specified + false -> false; + % Specified with no value, i.e. "?raw" and "?raw=" + {"raw", ""} -> false; + {"raw", "false"} -> false; + {"raw", "true"} -> true; + {"raw", InvalidValue} -> InvalidValue + end, handle_approved_config_req(Req, Persist, UseRawValue). -handle_approved_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req, - Persist, UseRawValue) - when UseRawValue =:= false orelse UseRawValue =:= true -> +handle_approved_config_req( + #httpd{method = 'PUT', path_parts = [_, Section, Key]} = Req, + Persist, + UseRawValue +) when + UseRawValue =:= false orelse UseRawValue =:= true +-> RawValue = couch_httpd:json_body(Req), - Value = case UseRawValue of - true -> - % Client requests no change to the provided value. - RawValue; - false -> - % Pre-process the value as necessary. - case Section of - <<"admins">> -> - couch_passwords:hash_admin_password(RawValue); - _ -> - couch_util:trim(RawValue) - end - end, + Value = + case UseRawValue of + true -> + % Client requests no change to the provided value. + RawValue; + false -> + % Pre-process the value as necessary. + case Section of + <<"admins">> -> + couch_passwords:hash_admin_password(RawValue); + _ -> + couch_util:trim(RawValue) + end + end, OldValue = config:get(Section, Key, ""), case config:set(Section, Key, ?b2l(Value), Persist) of - ok -> - send_json(Req, 200, list_to_binary(OldValue)); - Error -> - throw(Error) + ok -> + send_json(Req, 200, list_to_binary(OldValue)); + Error -> + throw(Error) end; - -handle_approved_config_req(#httpd{method='PUT'}=Req, _Persist, UseRawValue) -> +handle_approved_config_req(#httpd{method = 'PUT'} = Req, _Persist, UseRawValue) -> Err = io_lib:format("Bad value for 'raw' option: ~s", [UseRawValue]), send_json(Req, 400, {[{error, ?l2b(Err)}]}); - % DELETE /_config/Section/Key -handle_approved_config_req(#httpd{method='DELETE',path_parts=[_,Section,Key]}=Req, - Persist, _UseRawValue) -> +handle_approved_config_req( + #httpd{method = 'DELETE', path_parts = [_, Section, Key]} = Req, + Persist, + _UseRawValue +) -> case config:get(Section, Key, undefined) of - undefined -> - throw({not_found, unknown_config_value}); - OldValue -> - config:delete(Section, Key, Persist), - send_json(Req, 200, list_to_binary(OldValue)) + undefined -> + throw({not_found, unknown_config_value}); + OldValue -> + config:delete(Section, Key, Persist), + send_json(Req, 200, list_to_binary(OldValue)) end. - |