1% Licensed under the Apache License, Version 2.0 (the "License"); you may not
2% use this file except in compliance with the License. You may obtain a copy of
3% the License at
4%
5%   http://www.apache.org/licenses/LICENSE-2.0
6%
7% Unless required by applicable law or agreed to in writing, software
8% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10% License for the specific language governing permissions and limitations under
11% the License.
12
13-module(chttpd).
14
15-compile(tuple_calls).
16
17-include_lib("couch/include/couch_db.hrl").
18-include_lib("chttpd/include/chttpd.hrl").
19
20-export([start_link/0, start_link/1, start_link/2,
21    stop/0, handle_request/1, handle_request_int/1,
22    primary_header_value/2, header_value/2, header_value/3, qs_value/2,
23    qs_value/3, qs/1, qs_json_value/3, path/1, absolute_uri/2, body_length/1,
24    verify_is_server_admin/1, unquote/1, quote/1, recv/2, recv_chunked/4,
25    error_info/1, parse_form/1, json_body/1, json_body_obj/1, body/1,
26    doc_etag/1, make_etag/1, etag_respond/3, etag_match/2,
27    partition/1, serve_file/3, serve_file/4,
28    server_header/0, start_chunked_response/3,send_chunk/2,
29    start_response_length/4, send/2, start_json_response/2,
30    start_json_response/3, end_json_response/1, send_response/4,
31    send_response_no_cors/4,
32    send_method_not_allowed/2, send_error/2, send_error/4, send_redirect/2,
33    send_chunked_error/2, send_json/2,send_json/3,send_json/4,
34    validate_ctype/2]).
35
36-export([authenticate_request/3]).
37
38-export([start_delayed_json_response/2, start_delayed_json_response/3,
39    start_delayed_json_response/4,
40    start_delayed_chunked_response/3, start_delayed_chunked_response/4,
41    send_delayed_chunk/2, send_delayed_last_chunk/1,
42    send_delayed_error/2, end_delayed_json_response/1,
43    get_delayed_req/1]).
44
45-export([
46    chunked_response_buffer_size/0,
47    close_delayed_json_object/4
48]).
49
50-record(delayed_resp, {
51    start_fun,
52    req,
53    code,
54    headers,
55    chunks,
56    resp=nil,
57    buffer_response=false
58}).
59
60-define(DEFAULT_SERVER_OPTIONS, "[{recbuf, undefined}]").
61-define(DEFAULT_SOCKET_OPTIONS, "[{sndbuf, 262144}, {nodelay, true}]").
62
63start_link() ->
64    start_link(http).
65start_link(http) ->
66    Port = config:get("chttpd", "port", "5984"),
67    start_link(?MODULE, [{port, Port}]);
68
69start_link(https) ->
70    Port = config:get("ssl", "port", "6984"),
71    {ok, Ciphers} = couch_util:parse_term(config:get("ssl", "ciphers", "undefined")),
72    {ok, Versions} = couch_util:parse_term(config:get("ssl", "tls_versions", "undefined")),
73    {ok, SecureRenegotiate} = couch_util:parse_term(config:get("ssl", "secure_renegotiate", "undefined")),
74    ServerOpts0 =
75        [{cacertfile, config:get("ssl", "cacert_file", undefined)},
76         {keyfile, config:get("ssl", "key_file", undefined)},
77         {certfile, config:get("ssl", "cert_file", undefined)},
78         {password, config:get("ssl", "password", undefined)},
79         {secure_renegotiate, SecureRenegotiate},
80         {versions, Versions},
81         {ciphers, Ciphers}],
82
83    case (couch_util:get_value(keyfile, ServerOpts0) == undefined orelse
84        couch_util:get_value(certfile, ServerOpts0) == undefined) of
85        true ->
86            io:format("SSL enabled but PEM certificates are missing.", []),
87            throw({error, missing_certs});
88        false ->
89            ok
90    end,
91
92    ServerOpts = [Opt || {_, V}=Opt <- ServerOpts0, V /= undefined],
93
94    ClientOpts = case config:get("ssl", "verify_ssl_certificates", "false") of
95        "false" ->
96            [];
97        "true" ->
98            FailIfNoPeerCert = case config:get("ssl", "fail_if_no_peer_cert", "false") of
99            "false" -> false;
100            "true" -> true
101            end,
102            [{depth, list_to_integer(config:get("ssl",
103                "ssl_certificate_max_depth", "1"))},
104             {fail_if_no_peer_cert, FailIfNoPeerCert},
105             {verify, verify_peer}] ++
106            case config:get("ssl", "verify_fun", undefined) of
107                undefined -> [];
108                SpecStr ->
109                    [{verify_fun, couch_httpd:make_arity_3_fun(SpecStr)}]
110            end
111    end,
112    SslOpts = ServerOpts ++ ClientOpts,
113
114    Options0 =
115        [{port, Port},
116         {ssl, true},
117         {ssl_opts, SslOpts}],
118    CustomServerOpts = get_server_options("httpsd"),
119    Options = merge_server_options(Options0, CustomServerOpts),
120    start_link(https, Options).
121
122start_link(Name, Options) ->
123    IP = case config:get("chttpd", "bind_address", "any") of
124             "any" -> any;
125             Else -> Else
126         end,
127    ok = couch_httpd:validate_bind_address(IP),
128
129    set_auth_handlers(),
130
131    Options1 = Options ++ [
132        {loop, fun ?MODULE:handle_request/1},
133        {name, Name},
134        {ip, IP}
135    ],
136    ServerOpts = get_server_options("chttpd"),
137    Options2 = merge_server_options(Options1, ServerOpts),
138    case mochiweb_http:start(Options2) of
139    {ok, Pid} ->
140        {ok, Pid};
141    {error, Reason} ->
142        io:format("Failure to start Mochiweb: ~s~n", [Reason]),
143        {error, Reason}
144    end.
145
146get_server_options(Module) ->
147    ServerOptsCfg =
148        case Module of
149            "chttpd" -> config:get(Module,
150                "server_options", ?DEFAULT_SERVER_OPTIONS);
151            _ -> config:get(Module, "server_options", "[]")
152        end,
153    {ok, ServerOpts} = couch_util:parse_term(ServerOptsCfg),
154    ServerOpts.
155
156merge_server_options(A, B) ->
157    lists:keymerge(1, lists:sort(A), lists:sort(B)).
158
159stop() ->
160    catch mochiweb_http:stop(https),
161    mochiweb_http:stop(?MODULE).
162
163handle_request(MochiReq0) ->
164    erlang:put(?REWRITE_COUNT, 0),
165    MochiReq = couch_httpd_vhost:dispatch_host(MochiReq0),
166    handle_request_int(MochiReq).
167
168handle_request_int(MochiReq) ->
169    Begin = os:timestamp(),
170    SocketOptsCfg = config:get(
171        "chttpd", "socket_options", ?DEFAULT_SOCKET_OPTIONS),
172    {ok, SocketOpts} = couch_util:parse_term(SocketOptsCfg),
173    ok = mochiweb_socket:setopts(MochiReq:get(socket), SocketOpts),
174
175    % for the path, use the raw path with the query string and fragment
176    % removed, but URL quoting left intact
177    RawUri = MochiReq:get(raw_path),
178    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
179
180    % get requested path
181    RequestedPath = case MochiReq:get_header_value("x-couchdb-vhost-path") of
182        undefined ->
183            case MochiReq:get_header_value("x-couchdb-requested-path") of
184                undefined -> RawUri;
185                R -> R
186            end;
187        P -> P
188    end,
189
190    Peer = MochiReq:get(peer),
191
192    Method1 =
193    case MochiReq:get(method) of
194        % already an atom
195        Meth when is_atom(Meth) -> Meth;
196
197        % Non standard HTTP verbs aren't atoms (COPY, MOVE etc) so convert when
198        % possible (if any module references the atom, then it's existing).
199        Meth -> couch_util:to_existing_atom(Meth)
200    end,
201    increment_method_stats(Method1),
202
203    % allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
204    MethodOverride = MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
205    Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT", "COPY"]) of
206    true ->
207        couch_log:notice("MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]),
208        case Method1 of
209        'POST' -> couch_util:to_existing_atom(MethodOverride);
210        _ ->
211            % Ignore X-HTTP-Method-Override when the original verb isn't POST.
212            % I'd like to send a 406 error to the client, but that'd require a nasty refactor.
213            % throw({not_acceptable, <<"X-HTTP-Method-Override may only be used with POST requests.">>})
214            Method1
215        end;
216    _ -> Method1
217    end,
218
219    % alias HEAD to GET as mochiweb takes care of stripping the body
220    Method = case Method2 of
221        'HEAD' -> 'GET';
222        Other -> Other
223    end,
224
225    Nonce = couch_util:to_hex(crypto:strong_rand_bytes(5)),
226
227    HttpReq0 = #httpd{
228        mochi_req = MochiReq,
229        begin_ts = Begin,
230        peer = Peer,
231        original_method = Method1,
232        nonce = Nonce,
233        method = Method,
234        path_parts = [list_to_binary(unquote(Part))
235                || Part <- string:tokens(Path, "/")],
236        requested_path_parts = [?l2b(unquote(Part))
237                || Part <- string:tokens(RequestedPath, "/")]
238    },
239
240    % put small token on heap to keep requests synced to backend calls
241    erlang:put(nonce, Nonce),
242
243    % suppress duplicate log
244    erlang:put(dont_log_request, true),
245    erlang:put(dont_log_response, true),
246
247    {HttpReq2, Response} = case before_request(HttpReq0) of
248        {ok, HttpReq1} ->
249            process_request(HttpReq1);
250        {error, Response0} ->
251            {HttpReq0, Response0}
252    end,
253
254    {Status, Code, Reason, Resp} = split_response(Response),
255
256    HttpResp = #httpd_resp{
257        code = Code,
258        status = Status,
259        response = Resp,
260        nonce = HttpReq2#httpd.nonce,
261        reason = Reason
262    },
263
264    case after_request(HttpReq2, HttpResp) of
265        #httpd_resp{status = ok, response = Resp} ->
266            {ok, Resp};
267        #httpd_resp{status = aborted, reason = Reason} ->
268            couch_log:error("Response abnormally terminated: ~p", [Reason]),
269            exit(normal)
270    end.
271
272before_request(HttpReq) ->
273    try
274        chttpd_stats:init(),
275        chttpd_plugin:before_request(HttpReq)
276    catch ?STACKTRACE(ErrorType, Error, Stack)
277        {error, catch_error(HttpReq, ErrorType, Error, Stack)}
278    end.
279
280after_request(HttpReq, HttpResp0) ->
281    {ok, HttpResp1} =
282        try
283            chttpd_plugin:after_request(HttpReq, HttpResp0)
284        catch ?STACKTRACE(_ErrorType, Error, Stack)
285            send_error(HttpReq, {Error, nil, Stack}),
286            {ok, HttpResp0#httpd_resp{status = aborted}}
287        end,
288    HttpResp2 = update_stats(HttpReq, HttpResp1),
289    chttpd_stats:report(HttpReq, HttpResp2),
290    maybe_log(HttpReq, HttpResp2),
291    HttpResp2.
292
293process_request(#httpd{mochi_req = MochiReq} = HttpReq) ->
294    HandlerKey =
295        case HttpReq#httpd.path_parts of
296            [] -> <<>>;
297            [Key|_] -> ?l2b(quote(Key))
298        end,
299
300    RawUri = MochiReq:get(raw_path),
301
302    try
303        couch_httpd:validate_host(HttpReq),
304        check_request_uri_length(RawUri),
305        check_url_encoding(RawUri),
306        case chttpd_cors:maybe_handle_preflight_request(HttpReq) of
307        not_preflight ->
308            case chttpd_auth:authenticate(HttpReq, fun authenticate_request/1) of
309            #httpd{} = Req ->
310                handle_req_after_auth(HandlerKey, Req);
311            Response ->
312                {HttpReq, Response}
313            end;
314        Response ->
315            {HttpReq, Response}
316        end
317    catch ?STACKTRACE(ErrorType, Error, Stack)
318        {HttpReq, catch_error(HttpReq, ErrorType, Error, Stack)}
319    end.
320
321handle_req_after_auth(HandlerKey, HttpReq) ->
322    try
323        HandlerFun = chttpd_handlers:url_handler(HandlerKey,
324            fun chttpd_db:handle_request/1),
325        AuthorizedReq = chttpd_auth:authorize(possibly_hack(HttpReq),
326            fun chttpd_auth_request:authorize_request/1),
327        {AuthorizedReq, HandlerFun(AuthorizedReq)}
328    catch ?STACKTRACE(ErrorType, Error, Stack)
329        {HttpReq, catch_error(HttpReq, ErrorType, Error, Stack)}
330    end.
331
332catch_error(_HttpReq, throw, {http_head_abort, Resp}, _Stack) ->
333    {ok, Resp};
334catch_error(_HttpReq, throw, {http_abort, Resp, Reason}, _Stack) ->
335    {aborted, Resp, Reason};
336catch_error(HttpReq, throw, {invalid_json, _}, _Stack) ->
337    send_error(HttpReq, {bad_request, "invalid UTF-8 JSON"});
338catch_error(HttpReq, exit, {mochiweb_recv_error, E}, _Stack) ->
339    #httpd{
340        mochi_req = MochiReq,
341        peer = Peer,
342        original_method = Method
343    } = HttpReq,
344    couch_log:notice("mochiweb_recv_error for ~s - ~p ~s - ~p", [
345        Peer,
346        Method,
347        MochiReq:get(raw_path),
348        E]),
349    exit(normal);
350catch_error(HttpReq, exit, {uri_too_long, _}, _Stack) ->
351    send_error(HttpReq, request_uri_too_long);
352catch_error(HttpReq, exit, {body_too_large, _}, _Stack) ->
353    send_error(HttpReq, request_entity_too_large);
354catch_error(HttpReq, throw, Error, _Stack) ->
355    send_error(HttpReq, Error);
356catch_error(HttpReq, error, database_does_not_exist, _Stack) ->
357    send_error(HttpReq, database_does_not_exist);
358catch_error(HttpReq, Tag, Error, Stack) ->
359    % TODO improve logging and metrics collection for client disconnects
360    case {Tag, Error, Stack} of
361        {exit, normal, [{mochiweb_request, send, _, _} | _]} ->
362            exit(normal); % Client disconnect (R15+)
363        _Else ->
364            send_error(HttpReq, {Error, nil, Stack})
365    end.
366
367split_response({ok, #delayed_resp{resp=Resp}}) ->
368    {ok, Resp:get(code), undefined, Resp};
369split_response({ok, Resp}) ->
370    {ok, Resp:get(code), undefined, Resp};
371split_response({aborted, Resp, AbortReason}) ->
372    {aborted, Resp:get(code), AbortReason, Resp}.
373
374update_stats(HttpReq, #httpd_resp{end_ts = undefined} = Res) ->
375    update_stats(HttpReq, Res#httpd_resp{end_ts = os:timestamp()});
376update_stats(#httpd{begin_ts = BeginTime}, #httpd_resp{} = Res) ->
377    #httpd_resp{status = Status, end_ts = EndTime} = Res,
378    RequestTime = timer:now_diff(EndTime, BeginTime) / 1000,
379    couch_stats:update_histogram([couchdb, request_time], RequestTime),
380    case Status of
381        ok ->
382            couch_stats:increment_counter([couchdb, httpd, requests]);
383        aborted ->
384            couch_stats:increment_counter([couchdb, httpd, aborted_requests])
385    end,
386    Res.
387
388maybe_log(#httpd{} = HttpReq, #httpd_resp{should_log = true} = HttpResp) ->
389    #httpd{
390        mochi_req = MochiReq,
391        begin_ts = BeginTime,
392        original_method = Method,
393        peer = Peer
394    } = HttpReq,
395    #httpd_resp{
396        end_ts = EndTime,
397        code = Code,
398        status = Status
399    } = HttpResp,
400    User = get_user(HttpReq),
401    Host = MochiReq:get_header_value("Host"),
402    RawUri = MochiReq:get(raw_path),
403    RequestTime = timer:now_diff(EndTime, BeginTime) / 1000,
404    couch_log:notice("~s ~s ~s ~s ~s ~B ~p ~B", [Host, Peer, User,
405        Method, RawUri, Code, Status, round(RequestTime)]);
406maybe_log(_HttpReq, #httpd_resp{should_log = false}) ->
407    ok.
408
409
410%% HACK: replication currently handles two forms of input, #db{} style
411%% and #http_db style. We need a third that makes use of fabric. #db{}
412%% works fine for replicating the dbs and nodes database because they
413%% aren't sharded. So for now when a local db is specified as the source or
414%% the target, it's hacked to make it a full url and treated as a remote.
415possibly_hack(#httpd{path_parts=[<<"_replicate">>]}=Req) ->
416    {Props0} = chttpd:json_body_obj(Req),
417    Props1 = fix_uri(Req, Props0, <<"source">>),
418    Props2 = fix_uri(Req, Props1, <<"target">>),
419    Req#httpd{req_body={Props2}};
420possibly_hack(Req) ->
421    Req.
422
423check_request_uri_length(Uri) ->
424    check_request_uri_length(Uri,
425        chttpd_util:get_chttpd_config("max_uri_length")).
426
427check_request_uri_length(_Uri, undefined) ->
428    ok;
429check_request_uri_length(Uri, MaxUriLen) when is_list(MaxUriLen) ->
430    case length(Uri) > list_to_integer(MaxUriLen) of
431        true ->
432            throw(request_uri_too_long);
433        false ->
434            ok
435    end.
436
437check_url_encoding([]) ->
438    ok;
439check_url_encoding([$%, A, B | Rest]) when ?is_hex(A), ?is_hex(B) ->
440    check_url_encoding(Rest);
441check_url_encoding([$% | _]) ->
442    throw({bad_request, invalid_url_encoding});
443check_url_encoding([_ | Rest]) ->
444    check_url_encoding(Rest).
445
446fix_uri(Req, Props, Type) ->
447    case replication_uri(Type, Props) of
448    undefined ->
449        Props;
450    Uri0 ->
451        case is_http(Uri0) of
452        true ->
453            Props;
454        false ->
455            Uri = make_uri(Req, quote(Uri0)),
456            [{Type,Uri}|proplists:delete(Type,Props)]
457        end
458    end.
459
460replication_uri(Type, PostProps) ->
461    case couch_util:get_value(Type, PostProps) of
462    {Props} ->
463        couch_util:get_value(<<"url">>, Props);
464    Else ->
465        Else
466    end.
467
468is_http(<<"http://", _/binary>>) ->
469    true;
470is_http(<<"https://", _/binary>>) ->
471    true;
472is_http(_) ->
473    false.
474
475make_uri(Req, Raw) ->
476    Port = integer_to_list(mochiweb_socket_server:get(chttpd, port)),
477    Url = list_to_binary(["http://", config:get("httpd", "bind_address"),
478                          ":", Port, "/", Raw]),
479    Headers = [
480        {<<"authorization">>, ?l2b(header_value(Req,"authorization",""))},
481        {<<"cookie">>, ?l2b(extract_cookie(Req))}
482    ],
483    {[{<<"url">>,Url}, {<<"headers">>,{Headers}}]}.
484
485extract_cookie(#httpd{mochi_req = MochiReq}) ->
486    case MochiReq:get_cookie_value("AuthSession") of
487        undefined ->
488            "";
489        AuthSession ->
490            "AuthSession=" ++ AuthSession
491    end.
492%%% end hack
493
494set_auth_handlers() ->
495    AuthenticationDefault =  "{chttpd_auth, cookie_authentication_handler},
496      {chttpd_auth, default_authentication_handler}",
497    AuthenticationSrcs = couch_httpd:make_fun_spec_strs(
498        config:get("chttpd", "authentication_handlers", AuthenticationDefault)),
499    AuthHandlers = lists:map(
500        fun(A) -> {auth_handler_name(A), couch_httpd:make_arity_1_fun(A)} end, AuthenticationSrcs),
501    AuthenticationFuns = AuthHandlers ++ [
502        fun chttpd_auth:party_mode_handler/1 %% must be last
503    ],
504    ok = application:set_env(chttpd, auth_handlers, AuthenticationFuns).
505
506% SpecStr is a string like "{my_module, my_fun}"
507% Takes the first token of the function name in front '_' as auth handler name
508% e.g.
509% chttpd_auth:default_authentication_handler: default
510% chttpd_auth_cookie_authentication_handler: cookie
511% couch_http_auth:proxy_authentication_handler: proxy
512%
513% couch_http:auth_handler_name can't be used here, since it assumes the name
514% of the auth handler to be the 6th token split by [\\W_]
515% - this only works for modules with exactly two underscores in their name
516% - is not very robust (a space after the ',' is assumed)
517auth_handler_name(SpecStr) ->
518    {ok, {_, Fun}} = couch_util:parse_term(SpecStr),
519    hd(binary:split(atom_to_binary(Fun, latin1), <<"_">>)).
520
521authenticate_request(Req) ->
522    {ok, AuthenticationFuns} = application:get_env(chttpd, auth_handlers),
523    authenticate_request(Req, chttpd_auth_cache, AuthenticationFuns).
524
525authenticate_request(#httpd{} = Req0, AuthModule, AuthFuns) ->
526    Req = Req0#httpd{
527        auth_module = AuthModule,
528        authentication_handlers = AuthFuns},
529    authenticate_request(Req, AuthFuns).
530
531% Try authentication handlers in order until one returns a result
532authenticate_request(#httpd{user_ctx=#user_ctx{}} = Req, _AuthFuns) ->
533    Req;
534authenticate_request(#httpd{} = Req, [{Name, AuthFun}|Rest]) ->
535    authenticate_request(maybe_set_handler(AuthFun(Req), Name), Rest);
536authenticate_request(#httpd{} = Req, [AuthFun|Rest]) ->
537    authenticate_request(AuthFun(Req), Rest);
538authenticate_request(Response, _AuthFuns) ->
539    Response.
540
541maybe_set_handler(#httpd{user_ctx=#user_ctx{} = UserCtx} = Req, Name) ->
542    Req#httpd{user_ctx = UserCtx#user_ctx{handler = Name}};
543maybe_set_handler(Else, _) ->
544    Else.
545
546increment_method_stats(Method) ->
547    couch_stats:increment_counter([couchdb, httpd_request_methods, Method]).
548
549% Utilities
550
551partition(Path) ->
552    mochiweb_util:partition(Path, "/").
553
554header_value(#httpd{mochi_req=MochiReq}, Key) ->
555    MochiReq:get_header_value(Key).
556
557header_value(#httpd{mochi_req=MochiReq}, Key, Default) ->
558    case MochiReq:get_header_value(Key) of
559    undefined -> Default;
560    Value -> Value
561    end.
562
563primary_header_value(#httpd{mochi_req=MochiReq}, Key) ->
564    MochiReq:get_primary_header_value(Key).
565
566serve_file(Req, RelativePath, DocumentRoot) ->
567    serve_file(Req, RelativePath, DocumentRoot, []).
568
569serve_file(Req0, RelativePath0, DocumentRoot0, ExtraHeaders) ->
570    couch_httpd:serve_file(Req0, RelativePath0, DocumentRoot0, ExtraHeaders).
571
572qs_value(Req, Key) ->
573    qs_value(Req, Key, undefined).
574
575qs_value(Req, Key, Default) ->
576    couch_util:get_value(Key, qs(Req), Default).
577
578qs_json_value(Req, Key, Default) ->
579    case qs_value(Req, Key, Default) of
580        Default ->
581            Default;
582        Result ->
583            ?JSON_DECODE(Result)
584    end.
585
586qs(#httpd{mochi_req = MochiReq, qs = undefined}) ->
587    MochiReq:parse_qs();
588qs(#httpd{qs = QS}) ->
589    QS.
590
591path(#httpd{mochi_req=MochiReq}) ->
592    MochiReq:get(path).
593
594absolute_uri(#httpd{mochi_req=MochiReq, absolute_uri = undefined}, Path) ->
595    XHost = chttpd_util:get_chttpd_config(
596        "x_forwarded_host", "X-Forwarded-Host"),
597    Host = case MochiReq:get_header_value(XHost) of
598        undefined ->
599            case MochiReq:get_header_value("Host") of
600                undefined ->
601                    {ok, {Address, Port}} = case MochiReq:get(socket) of
602                        {ssl, SslSocket} -> ssl:sockname(SslSocket);
603                        Socket -> inet:sockname(Socket)
604                    end,
605                    inet_parse:ntoa(Address) ++ ":" ++ integer_to_list(Port);
606                Value1 ->
607                    Value1
608            end;
609        Value -> Value
610    end,
611    XSsl = chttpd_util:get_chttpd_config("x_forwarded_ssl", "X-Forwarded-Ssl"),
612    Scheme = case MochiReq:get_header_value(XSsl) of
613        "on" -> "https";
614        _ ->
615            XProto = chttpd_util:get_chttpd_config(
616                "x_forwarded_proto", "X-Forwarded-Proto"),
617            case MochiReq:get_header_value(XProto) of
618                % Restrict to "https" and "http" schemes only
619                "https" -> "https";
620                _ ->
621                    case MochiReq:get(scheme) of
622                        https ->
623                            "https";
624                        http ->
625                            "http"
626                    end
627            end
628    end,
629    Scheme ++ "://" ++ Host ++ Path;
630absolute_uri(#httpd{absolute_uri = URI}, Path) ->
631    URI ++ Path.
632
633unquote(UrlEncodedString) ->
634    case config:get_boolean("chttpd", "decode_plus_to_space", true) of
635        true -> mochiweb_util:unquote(UrlEncodedString);
636        false -> mochiweb_util:unquote_path(UrlEncodedString)
637    end.
638
639quote(UrlDecodedString) ->
640    mochiweb_util:quote_plus(UrlDecodedString).
641
642parse_form(#httpd{mochi_req=MochiReq}) ->
643    mochiweb_multipart:parse_form(MochiReq).
644
645recv(#httpd{mochi_req=MochiReq}, Len) ->
646    MochiReq:recv(Len).
647
648recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) ->
649    % Fun is called once with each chunk
650    % Fun({Length, Binary}, State)
651    % called with Length == 0 on the last time.
652    MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState).
653
654body_length(#httpd{mochi_req=MochiReq}) ->
655    MochiReq:get(body_length).
656
657body(#httpd{mochi_req=MochiReq, req_body=ReqBody}) ->
658    case ReqBody of
659        undefined ->
660            % Maximum size of document PUT request body (4GB)
661            MaxSize = chttpd_util:get_chttpd_config_integer(
662                "max_http_request_size", 4294967296),
663            Begin = os:timestamp(),
664            try
665                MochiReq:recv_body(MaxSize)
666            after
667                T = timer:now_diff(os:timestamp(), Begin) div 1000,
668                put(body_time, T)
669            end;
670        _Else ->
671            ReqBody
672    end.
673
674validate_ctype(Req, Ctype) ->
675    couch_httpd:validate_ctype(Req, Ctype).
676
677json_body(#httpd{req_body=undefined} = Httpd) ->
678    case body(Httpd) of
679        undefined ->
680            throw({bad_request, "Missing request body"});
681        Body ->
682            ?JSON_DECODE(maybe_decompress(Httpd, Body))
683    end;
684
685json_body(#httpd{req_body=ReqBody}) ->
686    ReqBody.
687
688json_body_obj(Httpd) ->
689    case json_body(Httpd) of
690        {Props} -> {Props};
691        _Else ->
692            throw({bad_request, "Request body must be a JSON object"})
693    end.
694
695
696doc_etag(#doc{id=Id, body=Body, revs={Start, [DiskRev|_]}}) ->
697    couch_httpd:doc_etag(Id, Body, {Start, DiskRev}).
698
699make_etag(Term) ->
700    <<SigInt:128/integer>> = couch_hash:md5_hash(term_to_binary(Term)),
701    list_to_binary(io_lib:format("\"~.36B\"",[SigInt])).
702
703etag_match(Req, CurrentEtag) when is_binary(CurrentEtag) ->
704    etag_match(Req, binary_to_list(CurrentEtag));
705
706etag_match(Req, CurrentEtag) ->
707    EtagsToMatch0 = string:tokens(
708        chttpd:header_value(Req, "If-None-Match", ""), ", "),
709    EtagsToMatch = lists:map(fun strip_weak_prefix/1, EtagsToMatch0),
710    lists:member(CurrentEtag, EtagsToMatch).
711
712strip_weak_prefix([$W, $/ | Etag]) ->
713    Etag;
714strip_weak_prefix(Etag) ->
715    Etag.
716
717etag_respond(Req, CurrentEtag, RespFun) ->
718    case etag_match(Req, CurrentEtag) of
719    true ->
720        % the client has this in their cache.
721        Headers = [{"ETag", CurrentEtag}],
722        chttpd:send_response(Req, 304, Headers, <<>>);
723    false ->
724        % Run the function.
725        RespFun()
726    end.
727
728verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) ->
729    case lists:member(<<"_admin">>, Roles) of
730    true -> ok;
731    false -> throw({unauthorized, <<"You are not a server admin.">>})
732    end.
733
734start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers0, Length) ->
735    Headers1 = basic_headers(Req, Headers0),
736    Resp = handle_response(Req, Code, Headers1, Length, start_response_length),
737    case MochiReq:get(method) of
738    'HEAD' -> throw({http_head_abort, Resp});
739    _ -> ok
740    end,
741    {ok, Resp}.
742
743send(Resp, Data) ->
744    Resp:send(Data),
745    {ok, Resp}.
746
747start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers0) ->
748    Headers1 = basic_headers(Req, Headers0),
749    Resp = handle_response(Req, Code, Headers1, chunked, respond),
750    case MochiReq:get(method) of
751    'HEAD' -> throw({http_head_abort, Resp});
752    _ -> ok
753    end,
754    {ok, Resp}.
755
756send_chunk({remote, _Pid, _Ref} = Resp, Data) ->
757    couch_httpd:send_chunk(Resp, Data);
758send_chunk(Resp, Data) ->
759    Resp:write_chunk(Data),
760    {ok, Resp}.
761
762send_response(Req, Code, Headers0, Body) ->
763    Headers1 = [timing(), reqid() | Headers0],
764    couch_httpd:send_response(Req, Code, Headers1, Body).
765
766send_response_no_cors(Req, Code, Headers0, Body) ->
767    Headers1 = [timing(), reqid() | Headers0],
768    couch_httpd:send_response_no_cors(Req, Code, Headers1, Body).
769
770send_method_not_allowed(Req, Methods) ->
771    send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>,
772        ?l2b("Only " ++ Methods ++ " allowed"), []).
773
774send_json(Req, Value) ->
775    send_json(Req, 200, Value).
776
777send_json(Req, Code, Value) ->
778    send_json(Req, Code, [], Value).
779
780send_json(Req, Code, Headers0, Value) ->
781    Headers1 = [timing(), reqid() | Headers0],
782    couch_httpd:send_json(Req, Code, Headers1, Value).
783
784start_json_response(Req, Code) ->
785    start_json_response(Req, Code, []).
786
787start_json_response(Req, Code, Headers0) ->
788    Headers1 = [timing(), reqid() | Headers0],
789    couch_httpd:start_json_response(Req, Code, Headers1).
790
791end_json_response(Resp) ->
792    couch_httpd:end_json_response(Resp).
793
794
795start_delayed_json_response(Req, Code) ->
796    start_delayed_json_response(Req, Code, []).
797
798
799start_delayed_json_response(Req, Code, Headers) ->
800    start_delayed_json_response(Req, Code, Headers, "").
801
802
803start_delayed_json_response(Req, Code, Headers, FirstChunk) ->
804    {ok, #delayed_resp{
805        start_fun = fun start_json_response/3,
806        req = Req,
807        code = Code,
808        headers = Headers,
809        chunks = [FirstChunk],
810        buffer_response = buffer_response(Req)}}.
811
812
813start_delayed_chunked_response(Req, Code, Headers) ->
814    start_delayed_chunked_response(Req, Code, Headers, "").
815
816
817start_delayed_chunked_response(Req, Code, Headers, FirstChunk) ->
818    {ok, #delayed_resp{
819        start_fun = fun start_chunked_response/3,
820        req = Req,
821        code = Code,
822        headers = Headers,
823        chunks = [FirstChunk],
824        buffer_response = buffer_response(Req)}}.
825
826
827send_delayed_chunk(#delayed_resp{buffer_response=false}=DelayedResp, Chunk) ->
828    {ok, #delayed_resp{resp=Resp}=DelayedResp1} =
829        start_delayed_response(DelayedResp),
830    {ok, Resp} = send_chunk(Resp, Chunk),
831    {ok, DelayedResp1};
832
833send_delayed_chunk(#delayed_resp{buffer_response=true}=DelayedResp, Chunk) ->
834    #delayed_resp{chunks = Chunks} = DelayedResp,
835    {ok, DelayedResp#delayed_resp{chunks = [Chunk | Chunks]}}.
836
837
838send_delayed_last_chunk(Req) ->
839    send_delayed_chunk(Req, []).
840
841
842send_delayed_error(#delayed_resp{req=Req,resp=nil}=DelayedResp, Reason) ->
843    {Code, ErrorStr, ReasonStr} = error_info(Reason),
844    {ok, Resp} = send_error(Req, Code, ErrorStr, ReasonStr),
845    {ok, DelayedResp#delayed_resp{resp=Resp}};
846send_delayed_error(#delayed_resp{resp=Resp, req=Req}, Reason) ->
847    update_timeout_stats(Reason, Req),
848    log_error_with_stack_trace(Reason),
849    throw({http_abort, Resp, Reason}).
850
851
852close_delayed_json_object(Resp, Buffer, Terminator, 0) ->
853    % Use a separate chunk to close the streamed array to maintain strict
854    % compatibility with earlier versions. See COUCHDB-2724
855    {ok, R1} = chttpd:send_delayed_chunk(Resp, Buffer),
856    send_delayed_chunk(R1, Terminator);
857close_delayed_json_object(Resp, Buffer, Terminator, _Threshold) ->
858    send_delayed_chunk(Resp, [Buffer | Terminator]).
859
860
861end_delayed_json_response(#delayed_resp{buffer_response=false}=DelayedResp) ->
862    {ok, #delayed_resp{resp=Resp}} =
863        start_delayed_response(DelayedResp),
864    end_json_response(Resp);
865
866end_delayed_json_response(#delayed_resp{buffer_response=true}=DelayedResp) ->
867    #delayed_resp{
868        start_fun = StartFun,
869        req = Req,
870        code = Code,
871        headers = Headers,
872        chunks = Chunks
873    } = DelayedResp,
874    {ok, Resp} = StartFun(Req, Code, Headers),
875    lists:foreach(fun
876        ([]) -> ok;
877        (Chunk) -> send_chunk(Resp, Chunk)
878    end, lists:reverse(Chunks)),
879    end_json_response(Resp).
880
881
882get_delayed_req(#delayed_resp{req=#httpd{mochi_req=MochiReq}}) ->
883    MochiReq;
884get_delayed_req(Resp) ->
885    Resp:get(request).
886
887start_delayed_response(#delayed_resp{resp=nil}=DelayedResp) ->
888    #delayed_resp{
889        start_fun=StartFun,
890        req=Req,
891        code=Code,
892        headers=Headers,
893        chunks=[FirstChunk]
894    }=DelayedResp,
895    {ok, Resp} = StartFun(Req, Code, Headers),
896    case FirstChunk of
897        "" -> ok;
898        _ -> {ok, Resp} = send_chunk(Resp, FirstChunk)
899    end,
900    {ok, DelayedResp#delayed_resp{resp=Resp}};
901start_delayed_response(#delayed_resp{}=DelayedResp) ->
902    {ok, DelayedResp}.
903
904
905buffer_response(Req) ->
906    case chttpd:qs_value(Req, "buffer_response") of
907        "false" ->
908            false;
909        "true" ->
910            true;
911        _ ->
912            config:get_boolean("chttpd", "buffer_response", false)
913    end.
914
915
916error_info({Error, Reason}) when is_list(Reason) ->
917    error_info({Error, couch_util:to_binary(Reason)});
918error_info(bad_request) ->
919    {400, <<"bad_request">>, <<>>};
920error_info({bad_request, Reason}) ->
921    {400, <<"bad_request">>, Reason};
922error_info({bad_request, Error, Reason}) ->
923    {400, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
924error_info({query_parse_error, Reason}) ->
925    {400, <<"query_parse_error">>, Reason};
926error_info(database_does_not_exist) ->
927    {404, <<"not_found">>, <<"Database does not exist.">>};
928error_info(not_found) ->
929    {404, <<"not_found">>, <<"missing">>};
930error_info({not_found, Reason}) ->
931    {404, <<"not_found">>, Reason};
932error_info({filter_fetch_error, Reason}) ->
933    {404, <<"not_found">>, Reason};
934error_info(ddoc_updated) ->
935    {404, <<"not_found">>, <<"Design document was updated or deleted.">>};
936error_info({not_acceptable, Reason}) ->
937    {406, <<"not_acceptable">>, Reason};
938error_info(conflict) ->
939    {409, <<"conflict">>, <<"Document update conflict.">>};
940error_info({conflict, _}) ->
941    {409, <<"conflict">>, <<"Document update conflict.">>};
942error_info({partition_overflow, DocId}) ->
943    Descr = <<
944            "Partition limit exceeded due to update on '", DocId/binary, "'"
945        >>,
946    {403, <<"partition_overflow">>, Descr};
947error_info({{not_found, missing}, {_, _}}) ->
948    {409, <<"not_found">>, <<"missing_rev">>};
949error_info({forbidden, Error, Msg}) ->
950    {403, Error, Msg};
951error_info({forbidden, Msg}) ->
952    {403, <<"forbidden">>, Msg};
953error_info({unauthorized, Msg}) ->
954    {401, <<"unauthorized">>, Msg};
955error_info(file_exists) ->
956    {412, <<"file_exists">>, <<"The database could not be "
957        "created, the file already exists.">>};
958error_info({error, {nodedown, Reason}}) ->
959    {412, <<"nodedown">>, Reason};
960error_info({maintenance_mode, Node}) ->
961    {412, <<"nodedown">>, Node};
962error_info({maintenance_mode, nil, Node}) ->
963    {412, <<"nodedown">>, Node};
964error_info({w_quorum_not_met, Reason}) ->
965    {500, <<"write_quorum_not_met">>, Reason};
966error_info(request_uri_too_long) ->
967    {414, <<"too_long">>, <<"the request uri is too long">>};
968error_info({bad_ctype, Reason}) ->
969    {415, <<"bad_content_type">>, Reason};
970error_info(requested_range_not_satisfiable) ->
971    {416, <<"requested_range_not_satisfiable">>, <<"Requested range not satisfiable">>};
972error_info({error, {illegal_database_name, Name}}) ->
973    Message = <<"Name: '", Name/binary, "'. Only lowercase characters (a-z), ",
974        "digits (0-9), and any of the characters _, $, (, ), +, -, and / ",
975        "are allowed. Must begin with a letter.">>,
976    {400, <<"illegal_database_name">>, Message};
977error_info({illegal_docid, Reason}) ->
978    {400, <<"illegal_docid">>, Reason};
979error_info({illegal_partition, Reason}) ->
980    {400, <<"illegal_partition">>, Reason};
981error_info({_DocID,{illegal_docid,DocID}}) ->
982    {400, <<"illegal_docid">>,DocID};
983error_info({error, {database_name_too_long, DbName}}) ->
984    {400, <<"database_name_too_long">>,
985        <<"At least one path segment of `", DbName/binary, "` is too long.">>};
986error_info({doc_validation, Reason}) ->
987    {400, <<"doc_validation">>, Reason};
988error_info({missing_stub, Reason}) ->
989    {412, <<"missing_stub">>, Reason};
990error_info(request_entity_too_large) ->
991    {413, <<"too_large">>, <<"the request entity is too large">>};
992error_info({request_entity_too_large, {attachment, AttName}}) ->
993    {413, <<"attachment_too_large">>, AttName};
994error_info({request_entity_too_large, DocID}) ->
995    {413, <<"document_too_large">>, DocID};
996error_info({error, security_migration_updates_disabled}) ->
997    {503, <<"security_migration">>, <<"Updates to security docs are disabled during "
998        "security migration.">>};
999error_info(all_workers_died) ->
1000    {503, <<"service unvailable">>, <<"Nodes are unable to service this "
1001        "request due to overloading or maintenance mode.">>};
1002error_info(not_implemented) ->
1003    {501, <<"not_implemented">>, <<"this feature is not yet implemented">>};
1004error_info(timeout) ->
1005    {500, <<"timeout">>, <<"The request could not be processed in a reasonable"
1006        " amount of time.">>};
1007error_info({service_unavailable, Reason}) ->
1008    {503, <<"service unavailable">>, Reason};
1009error_info({timeout, _Reason}) ->
1010    error_info(timeout);
1011error_info({'EXIT', {Error, _Stack}}) ->
1012    error_info(Error);
1013error_info({Error, null}) ->
1014    error_info(Error);
1015error_info({_Error, _Reason} = Error) ->
1016    maybe_handle_error(Error);
1017error_info({Error, nil, _Stack}) ->
1018    error_info(Error);
1019error_info({Error, Reason, _Stack}) ->
1020    error_info({Error, Reason});
1021error_info(Error) ->
1022    maybe_handle_error(Error).
1023
1024maybe_handle_error(Error) ->
1025    case chttpd_plugin:handle_error(Error) of
1026        {_Code, _Reason, _Description} = Result ->
1027            Result;
1028        {Err, Reason} ->
1029            {500, couch_util:to_binary(Err), couch_util:to_binary(Reason)};
1030        normal ->
1031            exit(normal);
1032        Error ->
1033            {500, <<"unknown_error">>, couch_util:to_binary(Error)}
1034    end.
1035
1036
1037error_headers(#httpd{mochi_req=MochiReq}=Req, 401=Code, ErrorStr, ReasonStr) ->
1038    % this is where the basic auth popup is triggered
1039    case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of
1040    undefined ->
1041        case chttpd_util:get_chttpd_config("WWW-Authenticate") of
1042        undefined ->
1043            % If the client is a browser and the basic auth popup isn't turned on
1044            % redirect to the session page.
1045            case ErrorStr of
1046            <<"unauthorized">> ->
1047                case chttpd_util:get_chttpd_auth_config(
1048                    "authentication_redirect", "/_utils/session.html") of
1049                undefined -> {Code, []};
1050                AuthRedirect ->
1051                    case chttpd_util:get_chttpd_auth_config_boolean(
1052                        "require_valid_user", false) of
1053                    true ->
1054                        % send the browser popup header no matter what if we are require_valid_user
1055                        {Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]};
1056                    false ->
1057                        case MochiReq:accepts_content_type("application/json") of
1058                        true ->
1059                            {Code, []};
1060                        false ->
1061                            case MochiReq:accepts_content_type("text/html") of
1062                            true ->
1063                                % Redirect to the path the user requested, not
1064                                % the one that is used internally.
1065                                UrlReturnRaw = case MochiReq:get_header_value("x-couchdb-vhost-path") of
1066                                undefined ->
1067                                    MochiReq:get(path);
1068                                VHostPath ->
1069                                    VHostPath
1070                                end,
1071                                RedirectLocation = lists:flatten([
1072                                    AuthRedirect,
1073                                    "?return=", couch_util:url_encode(UrlReturnRaw),
1074                                    "&reason=", couch_util:url_encode(ReasonStr)
1075                                ]),
1076                                {302, [{"Location", absolute_uri(Req, RedirectLocation)}]};
1077                            false ->
1078                                {Code, []}
1079                            end
1080                        end
1081                    end
1082                end;
1083            _Else ->
1084                {Code, []}
1085            end;
1086        Type ->
1087            {Code, [{"WWW-Authenticate", Type}]}
1088        end;
1089    Type ->
1090       {Code, [{"WWW-Authenticate", Type}]}
1091    end;
1092error_headers(_, Code, _, _) ->
1093    {Code, []}.
1094
1095send_error(#httpd{} = Req, Error) ->
1096    update_timeout_stats(Error, Req),
1097
1098    {Code, ErrorStr, ReasonStr} = error_info(Error),
1099    {Code1, Headers} = error_headers(Req, Code, ErrorStr, ReasonStr),
1100    send_error(Req, Code1, Headers, ErrorStr, ReasonStr, json_stack(Error)).
1101
1102send_error(#httpd{} = Req, Code, ErrorStr, ReasonStr) ->
1103    update_timeout_stats(ErrorStr, Req),
1104    send_error(Req, Code, [], ErrorStr, ReasonStr, []).
1105
1106send_error(Req, Code, Headers, ErrorStr, ReasonStr, []) ->
1107    send_json(Req, Code, Headers,
1108        {[{<<"error">>,  ErrorStr},
1109        {<<"reason">>, ReasonStr}]});
1110send_error(Req, Code, Headers, ErrorStr, ReasonStr, Stack) ->
1111    log_error_with_stack_trace({ErrorStr, ReasonStr, Stack}),
1112    send_json(Req, Code, [stack_trace_id(Stack) | Headers],
1113        {[{<<"error">>,  ErrorStr},
1114        {<<"reason">>, ReasonStr} |
1115        case Stack of [] -> []; _ -> [{<<"ref">>, stack_hash(Stack)}] end
1116    ]}).
1117
1118update_timeout_stats(<<"timeout">>, #httpd{requested_path_parts = PathParts}) ->
1119    update_timeout_stats(PathParts);
1120update_timeout_stats(timeout, #httpd{requested_path_parts = PathParts}) ->
1121    update_timeout_stats(PathParts);
1122update_timeout_stats(_, _) ->
1123    ok.
1124
1125update_timeout_stats([_, <<"_partition">>, _, <<"_design">>, _,
1126        <<"_view">> | _]) ->
1127    couch_stats:increment_counter([couchdb, httpd, partition_view_timeouts]);
1128update_timeout_stats([_, <<"_partition">>, _, <<"_find">>| _]) ->
1129    couch_stats:increment_counter([couchdb, httpd, partition_find_timeouts]);
1130update_timeout_stats([_, <<"_partition">>, _, <<"_explain">>| _]) ->
1131    couch_stats:increment_counter([couchdb, httpd, partition_explain_timeouts]);
1132update_timeout_stats([_, <<"_partition">>, _, <<"_all_docs">> | _]) ->
1133    couch_stats:increment_counter([couchdb, httpd, partition_all_docs_timeouts]);
1134update_timeout_stats([_, <<"_design">>, _, <<"_view">> | _]) ->
1135    couch_stats:increment_counter([couchdb, httpd, view_timeouts]);
1136update_timeout_stats([_, <<"_find">>| _]) ->
1137    couch_stats:increment_counter([couchdb, httpd, find_timeouts]);
1138update_timeout_stats([_, <<"_explain">>| _]) ->
1139    couch_stats:increment_counter([couchdb, httpd, explain_timeouts]);
1140update_timeout_stats([_, <<"_all_docs">> | _]) ->
1141    couch_stats:increment_counter([couchdb, httpd, all_docs_timeouts]);
1142update_timeout_stats(_) ->
1143    ok.
1144
1145% give the option for list functions to output html or other raw errors
1146send_chunked_error(Resp, {_Error, {[{<<"body">>, Reason}]}}) ->
1147    send_chunk(Resp, Reason),
1148    send_chunk(Resp, []);
1149
1150send_chunked_error(Resp, Error) ->
1151    Stack = json_stack(Error),
1152    log_error_with_stack_trace(Error),
1153    {Code, ErrorStr, ReasonStr} = error_info(Error),
1154    JsonError = {[{<<"code">>, Code},
1155        {<<"error">>,  ErrorStr},
1156        {<<"reason">>, ReasonStr} |
1157        case Stack of [] -> []; _ -> [{<<"ref">>, stack_hash(Stack)}] end
1158    ]},
1159    send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n])),
1160    send_chunk(Resp, []).
1161
1162send_redirect(Req, Path) ->
1163    Headers = [{"Location", chttpd:absolute_uri(Req, Path)}],
1164    send_response(Req, 301, Headers, <<>>).
1165
1166server_header() ->
1167    couch_httpd:server_header().
1168
1169timing() ->
1170    case get(body_time) of
1171        undefined ->
1172            {"X-CouchDB-Body-Time", "0"};
1173        Time ->
1174            {"X-CouchDB-Body-Time", integer_to_list(Time)}
1175    end.
1176
1177reqid() ->
1178    {"X-Couch-Request-ID", get(nonce)}.
1179
1180json_stack({bad_request, _, _}) ->
1181    [];
1182json_stack({_Error, _Reason, Stack}) when is_list(Stack) ->
1183    lists:map(fun json_stack_item/1, Stack);
1184json_stack(_) ->
1185    [].
1186
1187json_stack_item({M,F,A}) ->
1188    list_to_binary(io_lib:format("~s:~s/~B", [M, F, json_stack_arity(A)]));
1189json_stack_item({M,F,A,L}) ->
1190    case proplists:get_value(line, L) of
1191    undefined -> json_stack_item({M,F,A});
1192    Line -> list_to_binary(io_lib:format("~s:~s/~B L~B",
1193        [M, F, json_stack_arity(A), Line]))
1194    end;
1195json_stack_item(_) ->
1196    <<"bad entry in stacktrace">>.
1197
1198json_stack_arity(A) ->
1199    if is_integer(A) -> A; is_list(A) -> length(A); true -> 0 end.
1200
1201maybe_decompress(Httpd, Body) ->
1202    case header_value(Httpd, "Content-Encoding", "identity") of
1203    "gzip" ->
1204        try
1205            zlib:gunzip(Body)
1206        catch error:data_error ->
1207            throw({bad_request, "Request body is not properly gzipped."})
1208        end;
1209    "identity" ->
1210        Body;
1211    Else ->
1212        throw({bad_ctype, [Else, " is not a supported content encoding."]})
1213    end.
1214
1215log_error_with_stack_trace({bad_request, _, _}) ->
1216    ok;
1217log_error_with_stack_trace({Error, Reason, Stack}) ->
1218    EFmt = if is_binary(Error) -> "~s"; true -> "~w" end,
1219    RFmt = if is_binary(Reason) -> "~s"; true -> "~w" end,
1220    Fmt = "req_err(~w) " ++ EFmt ++ " : " ++ RFmt ++ "~n    ~p",
1221    couch_log:error(Fmt, [stack_hash(Stack), Error, Reason, Stack]);
1222log_error_with_stack_trace(_) ->
1223    ok.
1224
1225stack_trace_id(Stack) ->
1226    {"X-Couch-Stack-Hash", stack_hash(Stack)}.
1227
1228stack_hash(Stack) ->
1229    erlang:crc32(term_to_binary(Stack)).
1230
1231%% @doc CouchDB uses a chunked transfer-encoding to stream responses to
1232%% _all_docs, _changes, _view and other similar requests. This configuration
1233%% value sets the maximum size of a chunk; the system will buffer rows in the
1234%% response until it reaches this threshold and then send all the rows in one
1235%% chunk to improve network efficiency. The default value is chosen so that
1236%% the assembled chunk fits into the default Ethernet frame size (some reserved
1237%% padding is necessary to accommodate the reporting of the chunk length). Set
1238%% this value to 0 to restore the older behavior of sending each row in a
1239%% dedicated chunk.
1240chunked_response_buffer_size() ->
1241    chttpd_util:get_chttpd_config_integer("chunked_response_buffer", 1490).
1242
1243basic_headers(Req, Headers0) ->
1244    Headers = Headers0
1245        ++ server_header()
1246        ++ couch_httpd_auth:cookie_auth_header(Req, Headers0),
1247    Headers1 = chttpd_cors:headers(Req, Headers),
1248    Headers2 = chttpd_xframe_options:header(Req, Headers1),
1249    Headers3 = [reqid(), timing() | Headers2],
1250    chttpd_prefer_header:maybe_return_minimal(Req, Headers3).
1251
1252handle_response(Req0, Code0, Headers0, Args0, Type) ->
1253    {ok, {Req1, Code1, Headers1, Args1}} =
1254        chttpd_plugin:before_response(Req0, Code0, Headers0, Args0),
1255    couch_stats:increment_counter([couchdb, httpd_status_codes, Code1]),
1256    respond_(Req1, Code1, Headers1, Args1, Type).
1257
1258respond_(#httpd{mochi_req = MochiReq}, Code, Headers, _Args, start_response) ->
1259    MochiReq:start_response({Code, Headers});
1260respond_(#httpd{mochi_req = MochiReq}, Code, Headers, Args, Type) ->
1261    MochiReq:Type({Code, Headers, Args}).
1262
1263get_user(#httpd{user_ctx = #user_ctx{name = null}}) ->
1264    % admin party
1265    "undefined";
1266get_user(#httpd{user_ctx = #user_ctx{name = User}}) ->
1267    couch_util:url_encode(User);
1268get_user(#httpd{user_ctx = undefined}) ->
1269    "undefined".
1270
1271-ifdef(TEST).
1272
1273-include_lib("eunit/include/eunit.hrl").
1274
1275check_url_encoding_pass_test_() ->
1276    [
1277        ?_assertEqual(ok, check_url_encoding("/dbname")),
1278        ?_assertEqual(ok, check_url_encoding("/dbname/doc_id")),
1279        ?_assertEqual(ok, check_url_encoding("/dbname/doc_id?rev=1-abcdefgh")),
1280        ?_assertEqual(ok, check_url_encoding("/dbname%25")),
1281        ?_assertEqual(ok, check_url_encoding("/dbname/doc_id%25")),
1282        ?_assertEqual(ok, check_url_encoding("/dbname%25%3a")),
1283        ?_assertEqual(ok, check_url_encoding("/dbname/doc_id%25%3a")),
1284        ?_assertEqual(ok, check_url_encoding("/user%2Fdbname")),
1285        ?_assertEqual(ok, check_url_encoding("/user%2Fdbname/doc_id")),
1286        ?_assertEqual(ok, check_url_encoding("/dbname/escaped%25doc_id")),
1287        ?_assertEqual(ok, check_url_encoding("/dbname/doc%2eid")),
1288        ?_assertEqual(ok, check_url_encoding("/dbname/doc%2Eid")),
1289        ?_assertEqual(ok, check_url_encoding("/dbname-with-dash")),
1290        ?_assertEqual(ok, check_url_encoding("/dbname/doc_id-with-dash"))
1291    ].
1292
1293check_url_encoding_fail_test_() ->
1294    [
1295        ?_assertThrow({bad_request, invalid_url_encoding},
1296            check_url_encoding("/dbname%")),
1297        ?_assertThrow({bad_request, invalid_url_encoding},
1298            check_url_encoding("/dbname/doc_id%")),
1299        ?_assertThrow({bad_request, invalid_url_encoding},
1300            check_url_encoding("/dbname/doc_id%?rev=1-abcdefgh")),
1301        ?_assertThrow({bad_request, invalid_url_encoding},
1302            check_url_encoding("/dbname%2")),
1303        ?_assertThrow({bad_request, invalid_url_encoding},
1304            check_url_encoding("/dbname/doc_id%2")),
1305        ?_assertThrow({bad_request, invalid_url_encoding},
1306            check_url_encoding("/user%2Fdbname%")),
1307        ?_assertThrow({bad_request, invalid_url_encoding},
1308            check_url_encoding("/user%2Fdbname/doc_id%")),
1309        ?_assertThrow({bad_request, invalid_url_encoding},
1310            check_url_encoding("%")),
1311        ?_assertThrow({bad_request, invalid_url_encoding},
1312            check_url_encoding("/%")),
1313        ?_assertThrow({bad_request, invalid_url_encoding},
1314            check_url_encoding("/%2")),
1315        ?_assertThrow({bad_request, invalid_url_encoding},
1316            check_url_encoding("/dbname%2%3A")),
1317        ?_assertThrow({bad_request, invalid_url_encoding},
1318            check_url_encoding("/dbname%%3Ae")),
1319        ?_assertThrow({bad_request, invalid_url_encoding},
1320            check_url_encoding("/dbname%2g")),
1321        ?_assertThrow({bad_request, invalid_url_encoding},
1322            check_url_encoding("/dbname%g2"))
1323    ].
1324
1325log_format_test() ->
1326    ?assertEqual(
1327        "127.0.0.1:15984 127.0.0.1 undefined "
1328        "GET /_cluster_setup 201 ok 10000",
1329        test_log_request("/_cluster_setup", undefined)),
1330    ?assertEqual(
1331        "127.0.0.1:15984 127.0.0.1 user_foo "
1332        "GET /_all_dbs 201 ok 10000",
1333        test_log_request("/_all_dbs", #user_ctx{name = <<"user_foo">>})),
1334
1335    %% Utf8Name = unicode:characters_to_binary(Something),
1336    Utf8User = <<227,130,136,227,129,134,227,129,147,227,129,157>>,
1337    ?assertEqual(
1338        "127.0.0.1:15984 127.0.0.1 %E3%82%88%E3%81%86%E3%81%93%E3%81%9D "
1339        "GET /_all_dbs 201 ok 10000",
1340        test_log_request("/_all_dbs", #user_ctx{name = Utf8User})),
1341    ok.
1342
1343test_log_request(RawPath, UserCtx) ->
1344    Headers = mochiweb_headers:make([{"HOST", "127.0.0.1:15984"}]),
1345    MochiReq = mochiweb_request:new(socket, [], 'POST', RawPath, version, Headers),
1346    Req = #httpd{
1347        mochi_req = MochiReq,
1348        begin_ts = {1458,588713,124003},
1349        original_method = 'GET',
1350        peer = "127.0.0.1",
1351        nonce = "nonce",
1352        user_ctx = UserCtx
1353    },
1354    Resp = #httpd_resp{
1355        end_ts = {1458,588723,124303},
1356        code = 201,
1357        status = ok
1358    },
1359    ok = meck:new(couch_log, [passthrough]),
1360    ok = meck:expect(couch_log, notice, fun(Format, Args) ->
1361        lists:flatten(io_lib:format(Format, Args))
1362    end),
1363    Message = maybe_log(Req, Resp),
1364    ok = meck:unload(couch_log),
1365    Message.
1366
1367handle_req_after_auth_test() ->
1368    Headers = mochiweb_headers:make([{"HOST", "127.0.0.1:15984"}]),
1369    MochiReq = mochiweb_request:new(socket, [], 'PUT', "/newdb", version,
1370        Headers),
1371    UserCtx = #user_ctx{name = <<"retain_user">>},
1372    Roles = [<<"_reader">>],
1373    AuthorizedCtx = #user_ctx{name = <<"retain_user">>, roles = Roles},
1374    Req = #httpd{
1375        mochi_req = MochiReq,
1376        begin_ts = {1458,588713,124003},
1377        original_method = 'PUT',
1378        peer = "127.0.0.1",
1379        nonce = "nonce",
1380        user_ctx = UserCtx
1381    },
1382    AuthorizedReq = Req#httpd{user_ctx = AuthorizedCtx},
1383    ok = meck:new(chttpd_handlers, [passthrough]),
1384    ok = meck:new(chttpd_auth, [passthrough]),
1385    ok = meck:expect(chttpd_handlers, url_handler, fun(_Key, _Fun) ->
1386         fun(_Req) -> handled_authorized_req end
1387    end),
1388    ok = meck:expect(chttpd_auth, authorize, fun(_Req, _Fun) ->
1389        AuthorizedReq
1390    end),
1391    ?assertEqual({AuthorizedReq, handled_authorized_req},
1392        handle_req_after_auth(foo_key, Req)),
1393    ok = meck:expect(chttpd_auth, authorize, fun(_Req, _Fun) ->
1394        meck:exception(throw, {http_abort, resp, some_reason})
1395    end),
1396    ?assertEqual({Req, {aborted, resp, some_reason}},
1397        handle_req_after_auth(foo_key, Req)),
1398    ok = meck:unload(chttpd_handlers),
1399    ok = meck:unload(chttpd_auth).
1400
1401-endif.
1402