xref: /5.5.2/couchdb/src/couchdb/couch_httpd.erl (revision ba0c3c12)
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(couch_httpd).
14-include("couch_db.hrl").
15
16-export([start_link/0, start_link/1, stop/0, config_change/2,
17        handle_request/6]).
18
19-export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
20-export([path/1,absolute_uri/2,body_length/1]).
21-export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4,error_info/1]).
22-export([make_fun_spec_strs/1]).
23-export([make_arity_1_fun/1, make_arity_2_fun/1, make_arity_3_fun/1]).
24-export([parse_form/1,json_body/1,json_body_obj/1,body/1,doc_etag/1, make_etag/1, etag_respond/3]).
25-export([primary_header_value/2,partition/1,serve_file/3,serve_file/4, server_header/0]).
26-export([start_chunked_response/3,send_chunk/2,log_request/2]).
27-export([start_response_length/4, start_response/3, send/2]).
28-export([start_json_response/2, start_json_response/3, end_json_response/1]).
29-export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
30-export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
31-export([accepted_encodings/1,validate_referer/1,validate_ctype/2]).
32-export([is_ctype/2, negotiate_content_type/1]).
33
34start_link() ->
35    start_link(http).
36start_link(http) ->
37    Port = couch_config:get("httpd", "port", "5984"),
38    start_link(?MODULE, [{port, Port}]);
39start_link(https) ->
40    Port = couch_config:get("ssl", "port", "6984"),
41    CertFile = couch_config:get("ssl", "cert_file", nil),
42    KeyFile = couch_config:get("ssl", "key_file", nil),
43    Options = case CertFile /= nil andalso KeyFile /= nil of
44                  true ->
45                      [{port, Port},
46                       {ssl, true},
47                       {ssl_opts, [
48                             {certfile, CertFile},
49                             {keyfile, KeyFile}]}];
50                  false ->
51                      io:format("SSL enabled but PEM certificates are missing.", []),
52                      throw({error, missing_certs})
53              end,
54    start_link(https, Options).
55start_link(Name, Options) ->
56    % read config and register for configuration changes
57
58    % just stop if one of the config settings change. couch_server_sup
59    % will restart us and then we will pick up the new settings.
60
61    Field = case misc:is_ipv6() of
62                true -> "ip6_bind_address";
63                false -> "ip4_bind_address"
64            end,
65    BindAddress = couch_config:get("httpd", Field, any),
66    DefaultSpec = "{couch_httpd_db, handle_request}",
67    DefaultFun = make_arity_1_fun(
68        couch_config:get("httpd", "default_handler", DefaultSpec)
69    ),
70
71    UrlHandlersList = lists:map(
72        fun({UrlKey, SpecStr}) ->
73            {?l2b(UrlKey), make_arity_1_fun(SpecStr)}
74        end, couch_config:get("httpd_global_handlers")),
75
76    DbUrlHandlersList = lists:map(
77        fun({UrlKey, SpecStr}) ->
78            {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
79        end, couch_config:get("httpd_db_handlers")),
80
81    DesignUrlHandlersList = lists:map(
82        fun({UrlKey, SpecStr}) ->
83            {?l2b(UrlKey), make_arity_3_fun(SpecStr)}
84        end, couch_config:get("httpd_design_handlers")),
85
86    UrlHandlers = dict:from_list(UrlHandlersList),
87    DbUrlHandlers = dict:from_list(DbUrlHandlersList),
88    DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),
89    {ok, ServerOptions} = couch_util:parse_term(
90        couch_config:get("httpd", "server_options", "[]")),
91    {ok, SocketOptions} = couch_util:parse_term(
92        couch_config:get("httpd", "socket_options", "[]")),
93
94    DbFrontendModule = list_to_atom(couch_config:get("httpd", "db_frontend", "couch_db_frontend")),
95
96    Loop = fun(Req)->
97        case SocketOptions of
98        [] ->
99            ok;
100        _ ->
101            ok = mochiweb_socket:setopts(Req:get(socket), SocketOptions)
102        end,
103        apply(?MODULE, handle_request, [
104            Req, DbFrontendModule, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers
105        ])
106    end,
107
108    % set mochiweb options
109    FinalOptions = lists:append([Options, ServerOptions, [
110            {loop, Loop},
111            {name, Name},
112            {ip, BindAddress}]]),
113
114    % launch mochiweb
115    {ok, Pid} = case mochiweb_http:start(FinalOptions) of
116        {ok, MochiPid} ->
117            {ok, MochiPid};
118        {error, Reason} ->
119            io:format("Failure to start Mochiweb: ~s~n",[Reason]),
120            throw({error, Reason})
121    end,
122
123    ok = couch_config:register(fun ?MODULE:config_change/2, Pid),
124    {ok, Pid}.
125
126
127stop() ->
128    mochiweb_http:stop(?MODULE).
129
130config_change("httpd", "ip4_bind_address") ->
131    ?MODULE:stop();
132config_change("httpd", "ip6_bind_address") ->
133    ?MODULE:stop();
134config_change("httpd", "port") ->
135    ?MODULE:stop();
136config_change("httpd", "default_handler") ->
137    ?MODULE:stop();
138config_change("httpd", "server_options") ->
139    ?MODULE:stop();
140config_change("httpd", "socket_options") ->
141    ?MODULE:stop();
142config_change("httpd_global_handlers", _) ->
143    ?MODULE:stop();
144config_change("httpd_db_handlers", _) ->
145    ?MODULE:stop();
146config_change("ssl", _) ->
147    ?MODULE:stop().
148
149% SpecStr is a string like "{my_module, my_fun}"
150%  or "{my_module, my_fun, <<"my_arg">>}"
151make_arity_1_fun(SpecStr) ->
152    case couch_util:parse_term(SpecStr) of
153    {ok, {Mod, Fun, SpecArg}} ->
154        fun(Arg) -> Mod:Fun(Arg, SpecArg) end;
155    {ok, {Mod, Fun}} ->
156        fun(Arg) -> Mod:Fun(Arg) end
157    end.
158
159make_arity_2_fun(SpecStr) ->
160    case couch_util:parse_term(SpecStr) of
161    {ok, {Mod, Fun, SpecArg}} ->
162        fun(Arg1, Arg2) -> Mod:Fun(Arg1, Arg2, SpecArg) end;
163    {ok, {Mod, Fun}} ->
164        fun(Arg1, Arg2) -> Mod:Fun(Arg1, Arg2) end
165    end.
166
167make_arity_3_fun(SpecStr) ->
168    case couch_util:parse_term(SpecStr) of
169    {ok, {Mod, Fun, SpecArg}} ->
170        fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3, SpecArg) end;
171    {ok, {Mod, Fun}} ->
172        fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3) end
173    end.
174
175% SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}"
176make_fun_spec_strs(SpecStr) ->
177    re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]).
178
179handle_request(MochiReq, DbFrontendModule, DefaultFun,
180            UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->
181    % for the path, use the raw path with the query string and fragment
182    % removed, but URL quoting left intact
183    RawUri = MochiReq:get(raw_path),
184    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
185
186    HandlerKey =
187    case mochiweb_util:partition(Path, "/") of
188    {"", "", ""} ->
189        <<"/">>; % Special case the root url handler
190    {FirstPart, _, _} ->
191        list_to_binary(FirstPart)
192    end,
193
194    Method1 =
195    case MochiReq:get(method) of
196        % already an atom
197        Meth when is_atom(Meth) -> Meth;
198
199        % Non standard HTTP verbs aren't atoms (COPY, MOVE etc) so convert when
200        % possible (if any module references the atom, then it's existing).
201        Meth -> couch_util:to_existing_atom(Meth)
202    end,
203
204    % alias HEAD to GET as mochiweb takes care of stripping the body
205    Method = case Method1 of
206        'HEAD' -> 'GET';
207        Other -> Other
208    end,
209
210    PathParts = [?l2b(unquote(Part)) || Part <- string:tokens(Path, "/")],
211    HttpReq = #httpd{
212        mochi_req = MochiReq,
213        peer = MochiReq:get(peer),
214        method = Method,
215        path_parts = PathParts,
216        db_frontend = DbFrontendModule,
217        db_url_handlers = DbUrlHandlers,
218        design_url_handlers = DesignUrlHandlers,
219        default_fun = DefaultFun,
220        url_handlers = UrlHandlers,
221        user_ctx = #user_ctx{roles = [<<"_admin">>]}
222    },
223
224    HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun),
225
226    {ok, Resp} =
227    try
228        HandlerFun(HttpReq)
229    catch
230        throw:{http_head_abort, Resp0} ->
231            {ok, Resp0};
232        throw:{not_found, _} = Error ->
233            send_error(HttpReq, Error);
234        throw:{invalid_json, S} ->
235            ?LOG_ERROR("attempted upload of invalid JSON (set log_level to debug to log it)", []),
236            ?LOG_DEBUG("Invalid JSON: ~p",[S]),
237            send_error(HttpReq, {bad_request, io_lib:format("invalid UTF-8 JSON: ~p",[S])});
238        throw:unacceptable_encoding ->
239            ?LOG_ERROR("unsupported encoding method for the response", []),
240            send_error(HttpReq, {not_acceptable, "unsupported encoding"});
241        throw:bad_accept_encoding_value ->
242            ?LOG_ERROR("received invalid Accept-Encoding header", []),
243            send_error(HttpReq, bad_request);
244        exit:normal ->
245            exit(normal);
246        exit:snappy_nif_not_loaded ->
247            ErrorReason = "To access the database or view index, Apache CouchDB"
248                " must be built with Erlang OTP R13B04 or higher.",
249            ?LOG_ERROR("~s", [ErrorReason]),
250            send_error(HttpReq, {bad_otp_release, ErrorReason});
251        throw:{error, set_view_outdated} = Error ->
252            % More details logged by the view merger with INFO level
253            send_error(HttpReq, Error);
254        throw:{query_parse_error, Reason} = Error->
255            ?LOG_ERROR("query parameter error: ~p", [Reason]),
256            send_error(HttpReq, Error);
257        Tag:Error ->
258            Stack = erlang:get_stacktrace(),
259            ?LOG_ERROR("Uncaught error in HTTP request: ~p~n~n"
260                       "Stacktrace: ~s", [{Tag, Error}, ?LOG_USERDATA(Stack)]),
261            send_error(HttpReq, Error)
262    end,
263    {ok, Resp}.
264
265validate_referer(Req) ->
266    Host = host_for_request(Req),
267    Referer = header_value(Req, "Referer", fail),
268    case Referer of
269    fail ->
270        throw({bad_request, <<"Referer header required.">>});
271    Referer ->
272        {_,RefererHost,_,_,_} = mochiweb_util:urlsplit(Referer),
273        if
274            RefererHost =:= Host -> ok;
275            true -> throw({bad_request, <<"Referer header must match host.">>})
276        end
277    end.
278
279validate_ctype(Req, Ctype) ->
280    case is_ctype(Req, Ctype) of
281    true ->
282        ok;
283    false ->
284        throw({bad_ctype, "Content-Type must be "++Ctype})
285    end.
286
287is_ctype(Req, Ctype) ->
288    case header_value(Req, "Content-Type") of
289    undefined ->
290        false;
291    ReqCtype ->
292        case string:tokens(ReqCtype, ";") of
293        [Ctype] -> true;
294        [Ctype, _Rest] -> true;
295        _Else ->
296            false
297        end
298    end.
299
300% Utilities
301
302partition(Path) ->
303    mochiweb_util:partition(Path, "/").
304
305header_value(#httpd{mochi_req=MochiReq}, Key) ->
306    MochiReq:get_header_value(Key).
307
308header_value(#httpd{mochi_req=MochiReq}, Key, Default) ->
309    case MochiReq:get_header_value(Key) of
310    undefined -> Default;
311    Value -> Value
312    end.
313
314primary_header_value(#httpd{mochi_req=MochiReq}, Key) ->
315    MochiReq:get_primary_header_value(Key).
316
317accepted_encodings(#httpd{mochi_req=MochiReq}) ->
318    case MochiReq:accepted_encodings(["gzip", "identity"]) of
319    bad_accept_encoding_value ->
320        throw(bad_accept_encoding_value);
321    [] ->
322        throw(unacceptable_encoding);
323    EncList ->
324        EncList
325    end.
326
327serve_file(Req, RelativePath, DocumentRoot) ->
328    serve_file(Req, RelativePath, DocumentRoot, []).
329
330serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot, ExtraHeaders) ->
331    log_request(Req, 200),
332    {ok, MochiReq:serve_file(RelativePath, DocumentRoot, ExtraHeaders)}.
333
334qs_value(Req, Key) ->
335    qs_value(Req, Key, undefined).
336
337qs_value(Req, Key, Default) ->
338    couch_util:get_value(Key, qs(Req), Default).
339
340qs_json_value(Req, Key, Default) ->
341    case qs_value(Req, Key, Default) of
342    Default ->
343        Default;
344    Result ->
345        ?JSON_DECODE(Result)
346    end.
347
348qs(#httpd{mochi_req=MochiReq}) ->
349    MochiReq:parse_qs().
350
351path(#httpd{mochi_req=MochiReq}) ->
352    MochiReq:get(path).
353
354host_for_request(#httpd{mochi_req=MochiReq}) ->
355    XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
356    case MochiReq:get_header_value(XHost) of
357        undefined ->
358            case MochiReq:get_header_value("Host") of
359                undefined ->
360                    {ok, {Address, Port}} = inet:sockname(MochiReq:get(socket)),
361                    inet_parse:ntoa(Address) ++ ":" ++ integer_to_list(Port);
362                Value1 ->
363                    Value1
364            end;
365        Value -> Value
366    end.
367
368absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
369    Host = host_for_request(Req),
370    XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
371    Scheme = case MochiReq:get_header_value(XSsl) of
372                 "on" -> "https";
373                 _ ->
374                     XProto = couch_config:get("httpd", "x_forwarded_proto", "X-Forwarded-Proto"),
375                     case MochiReq:get_header_value(XProto) of
376                         %% Restrict to "https" and "http" schemes only
377                         "https" -> "https";
378                         _ -> case MochiReq:get(scheme) of
379                                  https -> "https";
380                                  http -> "http"
381                              end
382                     end
383             end,
384    Scheme ++ "://" ++ Host ++ Path.
385
386unquote(UrlEncodedString) ->
387    mochiweb_util:unquote(UrlEncodedString).
388
389quote(UrlDecodedString) ->
390    mochiweb_util:quote_plus(UrlDecodedString).
391
392parse_form(#httpd{mochi_req=MochiReq}) ->
393    mochiweb_multipart:parse_form(MochiReq).
394
395recv(#httpd{mochi_req=MochiReq}, Len) ->
396    MochiReq:recv(Len).
397
398recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) ->
399    % Fun is called once with each chunk
400    % Fun({Length, Binary}, State)
401    % called with Length == 0 on the last time.
402    MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState).
403
404body_length(Req) ->
405    case header_value(Req, "Transfer-Encoding") of
406        undefined ->
407            case header_value(Req, "Content-Length") of
408                undefined -> undefined;
409                Length -> list_to_integer(Length)
410            end;
411        "chunked" -> chunked;
412        Unknown -> {unknown_transfer_encoding, Unknown}
413    end.
414
415body(#httpd{mochi_req=MochiReq, req_body=undefined} = Req) ->
416    case body_length(Req) of
417        undefined ->
418            MaxSize = list_to_integer(
419                couch_config:get("couchdb", "max_document_size", "4294967296")),
420            MochiReq:recv_body(MaxSize);
421        chunked ->
422            ChunkFun = fun({0, _Footers}, Acc) ->
423                lists:reverse(Acc);
424            ({_Len, Chunk}, Acc) ->
425                [Chunk | Acc]
426            end,
427            recv_chunked(Req, 8192, ChunkFun, []);
428        Len ->
429            MochiReq:recv_body(Len)
430    end;
431body(#httpd{req_body=ReqBody}) ->
432    ReqBody.
433
434json_body(Httpd) ->
435    ?JSON_DECODE(body(Httpd)).
436
437json_body_obj(Httpd) ->
438    case json_body(Httpd) of
439        {Props} -> {Props};
440        _Else ->
441            throw({bad_request, "Request body must be a JSON object"})
442    end.
443
444
445
446doc_etag(#doc{rev={Start, DiskRev}}) ->
447    "\"" ++ ?b2l(couch_doc:rev_to_str({Start, DiskRev})) ++ "\"".
448
449make_etag(Term) ->
450    <<SigInt:128/integer>> = couch_util:md5(term_to_binary(Term)),
451    iolist_to_binary([$", io_lib:format("~.36B", [SigInt]), $"]).
452
453etag_match(Req, CurrentEtag) when is_binary(CurrentEtag) ->
454    etag_match(Req, binary_to_list(CurrentEtag));
455
456etag_match(Req, CurrentEtag) ->
457    EtagsToMatch = string:tokens(
458        header_value(Req, "If-None-Match", ""), ", "),
459    lists:member(CurrentEtag, EtagsToMatch).
460
461etag_respond(Req, CurrentEtag, RespFun) ->
462    case etag_match(Req, CurrentEtag) of
463    true ->
464        % the client has this in their cache.
465        send_response(Req, 304, [{"Etag", CurrentEtag}], <<>>);
466    false ->
467        % Run the function.
468        RespFun()
469    end.
470
471verify_is_server_admin(#httpd{user_ctx=UserCtx}) ->
472    verify_is_server_admin(UserCtx);
473verify_is_server_admin(#user_ctx{roles=Roles}) ->
474    case lists:member(<<"_admin">>, Roles) of
475    true -> ok;
476    false -> throw({unauthorized, <<"You are not a server admin.">>})
477    end.
478
479log_request(#httpd{mochi_req=MochiReq,peer=Peer}=Req, Code) ->
480    Path = case MochiReq:get(method) of
481    'POST' ->
482        log_parse_post(Req);
483    _ ->
484        MochiReq:get(raw_path)
485    end,
486    ?LOG_INFO("~s -- ~s ~s ~B", [?LOG_USERDATA(Peer),
487                                 MochiReq:get(method),
488                                 ?LOG_USERDATA(Path),
489                                 Code]).
490
491log_parse_post(Req) ->
492    try log_do_parse(Req) of Str -> ?l2b(Str)
493    catch _:_ -> "" end.
494
495log_do_parse(#httpd{method='POST'} = Req) ->
496    {[{Bucket, {Props}}]} = couch_util:get_nested_json_value(
497        json_body_obj(Req), [<<"views">>, <<"sets">>]),
498    ViewName = couch_util:get_value(<<"view">>, Props),
499    {DDoc, View} = couch_util:parse_view_name(ViewName),
500    [<<"/">>, Bucket, <<"/">>, DDoc, <<"/_view/">>, View].
501
502log_volume(#httpd{path_parts=Parts, mochi_req=MochiReq} = Req, Code) ->
503    try
504        {Origin, Path} = case Parts of
505            [_, <<"_design">>, _, <<"_view">>, _] ->
506                {external, MochiReq:get(path)};
507            _ ->
508                {internal, ?b2l(?l2b(log_do_parse(Req)))}
509        end,
510        Staleness = list_to_existing_atom(string:to_lower(
511            couch_httpd:qs_value(Req, "stale", "update_after"))),
512        ok = couch_query_logger:log(Path, Origin, Staleness)
513    catch
514        _:_ ->
515            log_request(Req, Code)
516    end.
517
518start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
519    log_request(Req, Code),
520    Resp = MochiReq:start_response_length({Code, Headers, Length}),
521    case MochiReq:get(method) of
522    'HEAD' -> throw({http_head_abort, Resp});
523    _ -> ok
524    end,
525    {ok, Resp}.
526
527start_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
528    log_request(Req, Code),
529    Resp = MochiReq:start_response({Code, Headers}),
530    case MochiReq:get(method) of
531        'HEAD' -> throw({http_head_abort, Resp});
532        _ -> ok
533    end,
534    {ok, Resp}.
535
536send(Resp, Data) ->
537    Resp:send(Data),
538    {ok, Resp}.
539
540no_resp_conn_header([]) ->
541    true;
542no_resp_conn_header([{Hdr, _}|Rest]) ->
543    case string:to_lower(Hdr) of
544        "connection" -> false;
545        _ -> no_resp_conn_header(Rest)
546    end.
547
548http_1_0_keep_alive(Req, Headers) ->
549    case (Req:get(version) == {1, 0}) andalso
550        (Req:should_close() == false) andalso
551        no_resp_conn_header(Headers) of
552    true ->
553        [{"Connection", "Keep-Alive"} | Headers];
554    false ->
555        Headers
556    end.
557
558start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
559    log_volume(Req, Code),
560    Headers2 = http_1_0_keep_alive(MochiReq, Headers),
561    Resp = MochiReq:respond({Code, Headers2, chunked}),
562    case MochiReq:get(method) of
563    'HEAD' -> throw({http_head_abort, Resp});
564    _ -> ok
565    end,
566    {ok, Resp}.
567
568send_chunk(Resp, Data) ->
569    case iolist_size(Data) of
570    0 -> ok; % do nothing
571    _ -> Resp:write_chunk(Data)
572    end,
573    {ok, Resp}.
574
575last_chunk(Resp) ->
576    Resp:write_chunk([]),
577    {ok, Resp}.
578
579send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
580    log_request(Req, Code),
581    Headers2 = http_1_0_keep_alive(MochiReq, Headers),
582    if Code >= 400 ->
583        ?LOG_DEBUG("httpd ~p error response:~n ~s", [?LOG_USERDATA(Code), ?LOG_USERDATA(Body)]);
584    true -> ok
585    end,
586    {ok, MochiReq:respond({Code, Headers2, Body})}.
587
588send_method_not_allowed(Req, Methods) ->
589    send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>, ?l2b("Only " ++ Methods ++ " allowed")).
590
591send_json(Req, Value) ->
592    send_json(Req, 200, Value).
593
594send_json(Req, Code, Value) ->
595    send_json(Req, Code, [], Value).
596
597send_json(Req, Code, Headers, Value) ->
598    DefaultHeaders = [
599        {"Content-Type", negotiate_content_type(Req)},
600        {"Cache-Control", "must-revalidate"}
601    ],
602    Body = [start_jsonp(Req), ?JSON_ENCODE(Value), end_jsonp(), $\n],
603    send_response(Req, Code, DefaultHeaders ++ Headers, Body).
604
605start_json_response(Req, Code) ->
606    start_json_response(Req, Code, []).
607
608start_json_response(Req, Code, Headers) ->
609    DefaultHeaders = [
610        {"Content-Type", negotiate_content_type(Req)},
611        {"Cache-Control", "must-revalidate"}
612    ],
613    start_jsonp(Req), % Validate before starting chunked.
614    {ok, Resp} = start_chunked_response(Req, Code, DefaultHeaders ++ Headers),
615    case start_jsonp(Req) of
616        [] -> ok;
617        Start -> send_chunk(Resp, Start)
618    end,
619    {ok, Resp}.
620
621end_json_response(Resp) ->
622    send_chunk(Resp, end_jsonp() ++ [$\n]),
623    last_chunk(Resp).
624
625start_jsonp(Req) ->
626    case get(jsonp) of
627        undefined -> put(jsonp, qs_value(Req, "callback", no_jsonp));
628        _ -> ok
629    end,
630    case get(jsonp) of
631        no_jsonp -> [];
632        [] -> [];
633        CallBack ->
634            try
635                % make sure jsonp is configured on (default off)
636                case couch_config:get("httpd", "allow_jsonp", "false") of
637                "true" ->
638                    validate_callback(CallBack),
639                    CallBack ++ "(";
640                _Else ->
641                    % this could throw an error message, but instead we just ignore the
642                    % jsonp parameter
643                    % throw({bad_request, <<"JSONP must be configured before using.">>})
644                    put(jsonp, no_jsonp),
645                    []
646                end
647            catch
648                Error ->
649                    put(jsonp, no_jsonp),
650                    throw(Error)
651            end
652    end.
653
654end_jsonp() ->
655    case erlang:erase(jsonp) of
656        no_jsonp -> [];
657        [] -> [];
658        _ -> ");"
659    end.
660
661validate_callback(CallBack) when is_binary(CallBack) ->
662    validate_callback(binary_to_list(CallBack));
663validate_callback([]) ->
664    ok;
665validate_callback([Char | Rest]) ->
666    case Char of
667        _ when Char >= $a andalso Char =< $z -> ok;
668        _ when Char >= $A andalso Char =< $Z -> ok;
669        _ when Char >= $0 andalso Char =< $9 -> ok;
670        _ when Char == $. -> ok;
671        _ when Char == $_ -> ok;
672        _ when Char == $[ -> ok;
673        _ when Char == $] -> ok;
674        _ ->
675            throw({bad_request, invalid_callback})
676    end,
677    validate_callback(Rest).
678
679
680error_info({Error, Reason}) when is_list(Reason) ->
681    error_info({Error, couch_util:to_binary(Reason)});
682error_info(bad_request) ->
683    {400, <<"bad_request">>, <<>>};
684error_info({bad_request, Reason}) ->
685    {400, <<"bad_request">>, Reason};
686error_info({query_parse_error, Reason}) ->
687    {400, <<"query_parse_error">>, Reason};
688error_info({invalid_design_doc, Reason}) ->
689    {400, <<"invalid_design_document">>, Reason};
690% Prior art for md5 mismatch resulting in a 400 is from AWS S3
691error_info(md5_mismatch) ->
692    {400, <<"content_md5_mismatch">>, <<"Possible message corruption.">>};
693error_info(not_found) ->
694    {404, <<"not_found">>, <<"missing">>};
695error_info({not_found, Reason}) ->
696    {404, <<"not_found">>, Reason};
697error_info({not_acceptable, Reason}) ->
698    {406, <<"not_acceptable">>, Reason};
699error_info(conflict) ->
700    {409, <<"conflict">>, <<"Document update conflict.">>};
701error_info({forbidden, Msg}) ->
702    {403, <<"forbidden">>, Msg};
703error_info({unauthorized, Msg}) ->
704    {401, <<"unauthorized">>, Msg};
705error_info(file_exists) ->
706    {412, <<"file_exists">>, <<"The database could not be "
707        "created, the file already exists.">>};
708error_info({bad_ctype, Reason}) ->
709    {415, <<"bad_content_type">>, Reason};
710error_info(requested_range_not_satisfiable) ->
711    {416, <<"requested_range_not_satisfiable">>, <<"Requested range not satisfiable">>};
712error_info({error, illegal_database_name}) ->
713    {400, <<"illegal_database_name">>, <<"Only lowercase characters (a-z), "
714        "digits (0-9), and any of the characters _, $, (, ), +, -, and / "
715        "are allowed. Must begin with a letter.">>};
716error_info({missing_stub, Reason}) ->
717    {412, <<"missing_stub">>, Reason};
718error_info({Error, Reason}) ->
719    {500, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
720error_info({Status, Error, Reason}) ->
721    {Status, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
722error_info(Error) ->
723    {500, <<"unknown_error">>, couch_util:to_binary(Error)}.
724
725error_headers(#httpd{mochi_req=MochiReq}, Code) ->
726    if Code == 401 ->
727        % this is where the basic auth popup is triggered
728        case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of
729        undefined ->
730            case couch_config:get("httpd", "WWW-Authenticate", nil) of
731            nil ->
732                {Code, []};
733            Type ->
734                {Code, [{"WWW-Authenticate", Type}]}
735            end;
736        Type ->
737           {Code, [{"WWW-Authenticate", Type}]}
738        end;
739    true ->
740        {Code, []}
741    end.
742
743send_error(_Req, {already_sent, Resp, _Error}) ->
744    {ok, Resp};
745
746send_error(Req, Error) ->
747    {Code, ErrorStr, ReasonStr} = error_info(Error),
748    {Code1, Headers} = error_headers(Req, Code),
749    send_error(Req, Code1, Headers, ErrorStr, ReasonStr).
750
751send_error(Req, Code, ErrorStr, ReasonStr) ->
752    send_error(Req, Code, [], ErrorStr, ReasonStr).
753
754send_error(Req, Code, Headers, ErrorStr, ReasonStr) ->
755    send_json(Req, Code, Headers,
756        {[{<<"error">>,  ErrorStr},
757         {<<"reason">>, ReasonStr}]}).
758
759% give the option for list functions to output html or other raw errors
760send_chunked_error(Resp, {_Error, {[{<<"body">>, Reason}]}}) ->
761    send_chunk(Resp, Reason),
762    last_chunk(Resp);
763
764send_chunked_error(Resp, Error) ->
765    {Code, ErrorStr, ReasonStr} = error_info(Error),
766    JsonError = {[{<<"code">>, Code},
767        {<<"error">>,  ErrorStr},
768        {<<"reason">>, ReasonStr}]},
769    send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n])),
770    last_chunk(Resp).
771
772send_redirect(Req, Path) ->
773     send_response(Req, 301, [{"Location", absolute_uri(Req, Path)}], <<>>).
774
775negotiate_content_type(#httpd{mochi_req=MochiReq}) ->
776    %% Determine the appropriate Content-Type header for a JSON response
777    %% depending on the Accept header in the request. A request that explicitly
778    %% lists the correct JSON MIME type will get that type, otherwise the
779    %% response will have the generic MIME type "text/plain"
780    AcceptedTypes = case MochiReq:get_header_value("Accept") of
781        undefined       -> [];
782        AcceptHeader    -> string:tokens(AcceptHeader, ", ")
783    end,
784    case lists:member("application/json", AcceptedTypes) of
785        true  -> "application/json";
786        false -> "text/plain;charset=utf-8"
787    end.
788
789server_header() ->
790    [{"Server", "CouchDB/" ++ couch_server:get_version() ++
791                " (Erlang OTP/" ++ erlang:system_info(otp_release) ++ ")"}].
792
793
794-record(mp, {boundary, buffer, data_fun, callback}).
795
796
797parse_multipart_request(ContentType, DataFun, Callback) ->
798    Boundary0 = iolist_to_binary(get_boundary(ContentType)),
799    Boundary = <<"\r\n--", Boundary0/binary>>,
800    Mp = #mp{boundary= Boundary,
801            buffer= <<>>,
802            data_fun=DataFun,
803            callback=Callback},
804    {Mp2, _NilCallback} = read_until(Mp, <<"--", Boundary0/binary>>,
805        fun nil_callback/1),
806    #mp{buffer=Buffer, data_fun=DataFun2, callback=Callback2} =
807            parse_part_header(Mp2),
808    {Buffer, DataFun2, Callback2}.
809
810nil_callback(_Data)->
811    fun nil_callback/1.
812
813get_boundary({"multipart/" ++ _, Opts}) ->
814    case couch_util:get_value("boundary", Opts) of
815        S when is_list(S) ->
816            S
817    end;
818get_boundary(ContentType) ->
819    {"multipart/" ++ _ , Opts} = mochiweb_util:parse_header(ContentType),
820    get_boundary({"multipart/", Opts}).
821
822
823
824split_header(<<>>) ->
825    [];
826split_header(Line) ->
827    {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end,
828                                           binary_to_list(Line)),
829    [{string:to_lower(string:strip(Name)),
830     mochiweb_util:parse_header(Value)}].
831
832read_until(#mp{data_fun=DataFun, buffer=Buffer}=Mp, Pattern, Callback) ->
833    case find_in_binary(Pattern, Buffer) of
834    not_found ->
835        Callback2 = Callback(Buffer),
836        {Buffer2, DataFun2} = DataFun(),
837        Buffer3 = iolist_to_binary(Buffer2),
838        read_until(Mp#mp{data_fun=DataFun2,buffer=Buffer3}, Pattern, Callback2);
839    {partial, 0} ->
840        {NewData, DataFun2} = DataFun(),
841        read_until(Mp#mp{data_fun=DataFun2,
842                buffer= iolist_to_binary([Buffer,NewData])},
843                Pattern, Callback);
844    {partial, Skip} ->
845        <<DataChunk:Skip/binary, Rest/binary>> = Buffer,
846        Callback2 = Callback(DataChunk),
847        {NewData, DataFun2} = DataFun(),
848        read_until(Mp#mp{data_fun=DataFun2,
849                buffer= iolist_to_binary([Rest | NewData])},
850                Pattern, Callback2);
851    {exact, 0} ->
852        PatternLen = size(Pattern),
853        <<_:PatternLen/binary, Rest/binary>> = Buffer,
854        {Mp#mp{buffer= Rest}, Callback};
855    {exact, Skip} ->
856        PatternLen = size(Pattern),
857        <<DataChunk:Skip/binary, _:PatternLen/binary, Rest/binary>> = Buffer,
858        Callback2 = Callback(DataChunk),
859        {Mp#mp{buffer= Rest}, Callback2}
860    end.
861
862
863parse_part_header(#mp{callback=UserCallBack}=Mp) ->
864    {Mp2, AccCallback} = read_until(Mp, <<"\r\n\r\n">>,
865            fun(Next) -> acc_callback(Next, []) end),
866    HeaderData = AccCallback(get_data),
867
868    Headers =
869    lists:foldl(fun(Line, Acc) ->
870            split_header(Line) ++ Acc
871        end, [], re:split(HeaderData,<<"\r\n">>, [])),
872    NextCallback = UserCallBack({headers, Headers}),
873    parse_part_body(Mp2#mp{callback=NextCallback}).
874
875parse_part_body(#mp{boundary=Prefix, callback=Callback}=Mp) ->
876    {Mp2, WrappedCallback} = read_until(Mp, Prefix,
877            fun(Data) -> body_callback_wrapper(Data, Callback) end),
878    Callback2 = WrappedCallback(get_callback),
879    Callback3 = Callback2(body_end),
880    case check_for_last(Mp2#mp{callback=Callback3}) of
881    {last, #mp{callback=Callback3}=Mp3} ->
882        Mp3#mp{callback=Callback3(eof)};
883    {more, Mp3} ->
884        parse_part_header(Mp3)
885    end.
886
887acc_callback(get_data, Acc)->
888    iolist_to_binary(lists:reverse(Acc));
889acc_callback(Data, Acc)->
890    fun(Next) -> acc_callback(Next, [Data | Acc]) end.
891
892body_callback_wrapper(get_callback, Callback) ->
893    Callback;
894body_callback_wrapper(Data, Callback) ->
895    Callback2 = Callback({body, Data}),
896    fun(Next) -> body_callback_wrapper(Next, Callback2) end.
897
898
899check_for_last(#mp{buffer=Buffer, data_fun=DataFun}=Mp) ->
900    case Buffer of
901    <<"--",_/binary>> -> {last, Mp};
902    <<_, _, _/binary>> -> {more, Mp};
903    _ -> % not long enough
904        {Data, DataFun2} = DataFun(),
905        check_for_last(Mp#mp{buffer= <<Buffer/binary, Data/binary>>,
906                data_fun = DataFun2})
907    end.
908
909find_in_binary(B, Data) when size(B) > 0 ->
910    case size(Data) - size(B) of
911        Last when Last < 0 ->
912            partial_find(B, Data, 0, size(Data));
913        Last ->
914            find_in_binary(B, size(B), Data, 0, Last)
915    end.
916
917find_in_binary(B, BS, D, N, Last) when N =< Last->
918    case D of
919        <<_:N/binary, B:BS/binary, _/binary>> ->
920            {exact, N};
921        _ ->
922            find_in_binary(B, BS, D, 1 + N, Last)
923    end;
924find_in_binary(B, BS, D, N, Last) when N =:= 1 + Last ->
925    partial_find(B, D, N, BS - 1).
926
927partial_find(_B, _D, _N, 0) ->
928    not_found;
929partial_find(B, D, N, K) ->
930    <<B1:K/binary, _/binary>> = B,
931    case D of
932        <<_Skip:N/binary, B1/binary>> ->
933            {partial, N};
934        _ ->
935            partial_find(B, D, 1 + N, K - 1)
936    end.
937