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