You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/02/11 09:07:37 UTC

[35/41] couch commit: updated refs/heads/import-rcouch to f07bbfc

make couch_httpd a full couch application

With this change, the HTTP API is now handled by its own erlang
application and supervision. It also improved the way you can reload the
configuration and the modules. Now upgrading a listener or its
configuration is first removing it from the supervsion then start the
new process with the new configuration. This behaviour is similar to the
one you have in nginx.


Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/a278e0db
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/a278e0db
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/a278e0db

Branch: refs/heads/import-rcouch
Commit: a278e0db5c761878d54f5e1433dfc3aa9ee62d72
Parents: fd93bf9
Author: benoitc <be...@apache.org>
Authored: Sat Jan 11 11:20:27 2014 +0100
Committer: Paul J. Davis <pa...@gmail.com>
Committed: Tue Feb 11 02:05:21 2014 -0600

----------------------------------------------------------------------
 include/couch_js_functions.hrl     |  170 +++++
 src/couch.app.src.script           |    5 +-
 src/couch_app.erl                  |    2 -
 src/couch_httpd.erl                | 1114 ----------------------------
 src/couch_httpd_auth.erl           |  380 ----------
 src/couch_httpd_cors.erl           |  351 ---------
 src/couch_httpd_db.erl             | 1226 -------------------------------
 src/couch_httpd_external.erl       |  177 -----
 src/couch_httpd_misc_handlers.erl  |  318 --------
 src/couch_httpd_oauth.erl          |  387 ----------
 src/couch_httpd_proxy.erl          |  426 -----------
 src/couch_httpd_rewrite.erl        |  484 ------------
 src/couch_httpd_stats_handlers.erl |   56 --
 src/couch_httpd_vhost.erl          |  383 ----------
 src/couch_js_functions.hrl         |  170 -----
 src/couch_server_sup.erl           |   49 +-
 16 files changed, 173 insertions(+), 5525 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/include/couch_js_functions.hrl
----------------------------------------------------------------------
diff --git a/include/couch_js_functions.hrl b/include/couch_js_functions.hrl
new file mode 100644
index 0000000..a48feae
--- /dev/null
+++ b/include/couch_js_functions.hrl
@@ -0,0 +1,170 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License.  You may obtain a copy of
+% the License at
+%
+%   http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-define(AUTH_DB_DOC_VALIDATE_FUNCTION, <<"
+    function(newDoc, oldDoc, userCtx, secObj) {
+        if (newDoc._deleted === true) {
+            // allow deletes by admins and matching users
+            // without checking the other fields
+            if ((userCtx.roles.indexOf('_admin') !== -1) ||
+                (userCtx.name == oldDoc.name)) {
+                return;
+            } else {
+                throw({forbidden: 'Only admins may delete other user docs.'});
+            }
+        }
+
+        if ((oldDoc && oldDoc.type !== 'user') || newDoc.type !== 'user') {
+            throw({forbidden : 'doc.type must be user'});
+        } // we only allow user docs for now
+
+        if (!newDoc.name) {
+            throw({forbidden: 'doc.name is required'});
+        }
+
+        if (!newDoc.roles) {
+            throw({forbidden: 'doc.roles must exist'});
+        }
+
+        if (!isArray(newDoc.roles)) {
+            throw({forbidden: 'doc.roles must be an array'});
+        }
+
+        for (var idx = 0; idx < newDoc.roles.length; idx++) {
+            if (typeof newDoc.roles[idx] !== 'string') {
+                throw({forbidden: 'doc.roles can only contain strings'});
+            }
+        }
+
+        if (newDoc._id !== ('org.couchdb.user:' + newDoc.name)) {
+            throw({
+                forbidden: 'Doc ID must be of the form org.couchdb.user:name'
+            });
+        }
+
+        if (oldDoc) { // validate all updates
+            if (oldDoc.name !== newDoc.name) {
+                throw({forbidden: 'Usernames can not be changed.'});
+            }
+        }
+
+        if (newDoc.password_sha && !newDoc.salt) {
+            throw({
+                forbidden: 'Users with password_sha must have a salt.' +
+                    'See /_utils/script/couch.js for example code.'
+            });
+        }
+
+        if (newDoc.password_scheme === \"pbkdf2\") {
+            if (typeof(newDoc.iterations) !== \"number\") {
+               throw({forbidden: \"iterations must be a number.\"});
+            }
+            if (typeof(newDoc.derived_key) !== \"string\") {
+               throw({forbidden: \"derived_key must be a string.\"});
+            }
+        }
+
+        var is_server_or_database_admin = function(userCtx, secObj) {
+            // see if the user is a server admin
+            if(userCtx.roles.indexOf('_admin') !== -1) {
+                return true; // a server admin
+            }
+
+            // see if the user a database admin specified by name
+            if(secObj && secObj.admins && secObj.admins.names) {
+                if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
+                    return true; // database admin
+                }
+            }
+
+            // see if the user a database admin specified by role
+            if(secObj && secObj.admins && secObj.admins.roles) {
+                var db_roles = secObj.admins.roles;
+                for(var idx = 0; idx < userCtx.roles.length; idx++) {
+                    var user_role = userCtx.roles[idx];
+                    if(db_roles.indexOf(user_role) !== -1) {
+                        return true; // role matches!
+                    }
+                }
+            }
+
+            return false; // default to no admin
+        }
+
+        if (!is_server_or_database_admin(userCtx, secObj)) {
+            if (oldDoc) { // validate non-admin updates
+                if (userCtx.name !== newDoc.name) {
+                    throw({
+                        forbidden: 'You may only update your own user document.'
+                    });
+                }
+                // validate role updates
+                var oldRoles = oldDoc.roles.sort();
+                var newRoles = newDoc.roles.sort();
+
+                if (oldRoles.length !== newRoles.length) {
+                    throw({forbidden: 'Only _admin may edit roles'});
+                }
+
+                for (var i = 0; i < oldRoles.length; i++) {
+                    if (oldRoles[i] !== newRoles[i]) {
+                        throw({forbidden: 'Only _admin may edit roles'});
+                    }
+                }
+            } else if (newDoc.roles.length > 0) {
+                throw({forbidden: 'Only _admin may set roles'});
+            }
+        }
+
+        // no system roles in users db
+        for (var i = 0; i < newDoc.roles.length; i++) {
+            if (newDoc.roles[i][0] === '_') {
+                throw({
+                    forbidden:
+                    'No system roles (starting with underscore) in users db.'
+                });
+            }
+        }
+
+        // no system names as names
+        if (newDoc.name[0] === '_') {
+            throw({forbidden: 'Username may not start with underscore.'});
+        }
+
+        var badUserNameChars = [':'];
+
+        for (var i = 0; i < badUserNameChars.length; i++) {
+            if (newDoc.name.indexOf(badUserNameChars[i]) >= 0) {
+                throw({forbidden: 'Character `' + badUserNameChars[i] +
+                        '` is not allowed in usernames.'});
+            }
+        }
+    }
+">>).
+
+
+-define(OAUTH_MAP_FUN, <<"
+    function(doc) {
+        if (doc.type === 'user' && doc.oauth && doc.oauth.consumer_keys) {
+            for (var consumer_key in doc.oauth.consumer_keys) {
+                for (var token in doc.oauth.tokens) {
+                    var obj = {
+                        'consumer_secret': doc.oauth.consumer_keys[consumer_key],
+                        'token_secret': doc.oauth.tokens[token],
+                        'username': doc.name
+                    };
+                    emit([consumer_key, token], obj);
+                }
+            }
+        }
+    }
+">>).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/src/couch.app.src.script
----------------------------------------------------------------------
diff --git a/src/couch.app.src.script b/src/couch.app.src.script
index c406e02..d8962fa 100644
--- a/src/couch.app.src.script
+++ b/src/couch.app.src.script
@@ -49,7 +49,6 @@ end,
             couch_db_update_notifier_sup,
             couch_external_manager,
             couch_index_sup,
-            couch_httpd,
             couch_log,
             couch_primary_services,
             couch_query_servers,
@@ -62,7 +61,7 @@ end,
         ]},
         {mod, {couch_app, []}},
         {env, [{couch_rel, RelVsn}]},
-        {applications, [kernel, stdlib, crypto, sasl, asn1, public_key, ssl,
-                        inets, ibrowse, os_mon]}
+        {applications, [kernel, stdlib, crypto, sasl, asn1, public_key,
+                        ssl, os_mon, inets]}
     ]}
 ].

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/src/couch_app.erl
----------------------------------------------------------------------
diff --git a/src/couch_app.erl b/src/couch_app.erl
index 2e1e5bd..414a5c9 100644
--- a/src/couch_app.erl
+++ b/src/couch_app.erl
@@ -33,6 +33,4 @@ get_ini_files() ->
     Defaults = lists:map(fun(FName) ->
                     filename:join(DefaultConfDir, FName)
             end, ?CONF_FILES),
-    io:format("default files ~p~n", [couch:get_app_env(config_files,
-                                                       Defaults)]),
     couch:get_app_env(config_files, Defaults).

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/src/couch_httpd.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd.erl b/src/couch_httpd.erl
deleted file mode 100644
index 28932ba..0000000
--- a/src/couch_httpd.erl
+++ /dev/null
@@ -1,1114 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_httpd).
--include("couch_db.hrl").
-
--export([start_link/0, start_link/1, stop/0, config_change/2,
-        handle_request/5]).
-
--export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,qs_json_value/3]).
--export([path/1,absolute_uri/2,body_length/1]).
--export([verify_is_server_admin/1,unquote/1,quote/1,recv/2,recv_chunked/4,error_info/1]).
--export([make_fun_spec_strs/1]).
--export([make_arity_1_fun/1, make_arity_2_fun/1, make_arity_3_fun/1]).
--export([parse_form/1,json_body/1,json_body_obj/1,body/1]).
--export([doc_etag/1, make_etag/1, etag_match/2, etag_respond/3, etag_maybe/2]).
--export([primary_header_value/2,partition/1,serve_file/3,serve_file/4, server_header/0]).
--export([start_chunked_response/3,send_chunk/2,log_request/2]).
--export([start_response_length/4, start_response/3, send/2]).
--export([start_json_response/2, start_json_response/3, end_json_response/1]).
--export([send_response/4,send_method_not_allowed/2,send_error/4, send_redirect/2,send_chunked_error/2]).
--export([send_json/2,send_json/3,send_json/4,last_chunk/1,parse_multipart_request/3]).
--export([accepted_encodings/1,handle_request_int/5,validate_referer/1,validate_ctype/2]).
--export([http_1_0_keep_alive/2]).
-
-start_link() ->
-    start_link(http).
-start_link(http) ->
-    Port = couch_config:get("httpd", "port", "5984"),
-    start_link(?MODULE, [{port, Port}]);
-start_link(https) ->
-    Port = couch_config:get("ssl", "port", "6984"),
-    CertFile = couch_config:get("ssl", "cert_file", nil),
-    KeyFile = couch_config:get("ssl", "key_file", nil),
-    Options = case CertFile /= nil andalso KeyFile /= nil of
-        true ->
-            SslOpts = [{certfile, CertFile}, {keyfile, KeyFile}],
-
-            %% set password if one is needed for the cert
-            SslOpts1 = case couch_config:get("ssl", "password", nil) of
-                nil -> SslOpts;
-                Password ->
-                    SslOpts ++ [{password, Password}]
-            end,
-            % do we verify certificates ?
-            FinalSslOpts = case couch_config:get("ssl",
-                    "verify_ssl_certificates", "false") of
-                "false" -> SslOpts1;
-                "true" ->
-                    case couch_config:get("ssl",
-                            "cacert_file", nil) of
-                        nil ->
-                            io:format("Verify SSL certificate "
-                                ++"enabled but file containing "
-                                ++"PEM encoded CA certificates is "
-                                ++"missing", []),
-                            throw({error, missing_cacerts});
-                        CaCertFile ->
-                            Depth = list_to_integer(couch_config:get("ssl",
-                                    "ssl_certificate_max_depth",
-                                    "1")),
-                            FinalOpts = [
-                                {cacertfile, CaCertFile},
-                                {depth, Depth},
-                                {verify, verify_peer}],
-                            % allows custom verify fun.
-                            case couch_config:get("ssl",
-                                    "verify_fun", nil) of
-                                nil -> FinalOpts;
-                                SpecStr ->
-                                    FinalOpts
-                                    ++ [{verify_fun, make_arity_3_fun(SpecStr)}]
-                            end
-                    end
-            end,
-
-            [{port, Port},
-                {ssl, true},
-                {ssl_opts, FinalSslOpts}];
-        false ->
-            io:format("SSL enabled but PEM certificates are missing.", []),
-            throw({error, missing_certs})
-    end,
-    start_link(https, Options).
-start_link(Name, Options) ->
-    % read config and register for configuration changes
-
-    % just stop if one of the config settings change. couch_server_sup
-    % will restart us and then we will pick up the new settings.
-
-    BindAddress = couch_config:get("httpd", "bind_address", any),
-    validate_bind_address(BindAddress),
-    DefaultSpec = "{couch_httpd_db, handle_request}",
-    DefaultFun = make_arity_1_fun(
-        couch_config:get("httpd", "default_handler", DefaultSpec)
-    ),
-
-    UrlHandlersList = lists:map(
-        fun({UrlKey, SpecStr}) ->
-            {?l2b(UrlKey), make_arity_1_fun(SpecStr)}
-        end, couch_config:get("httpd_global_handlers")),
-
-    DbUrlHandlersList = lists:map(
-        fun({UrlKey, SpecStr}) ->
-            {?l2b(UrlKey), make_arity_2_fun(SpecStr)}
-        end, couch_config:get("httpd_db_handlers")),
-
-    DesignUrlHandlersList = lists:map(
-        fun({UrlKey, SpecStr}) ->
-            {?l2b(UrlKey), make_arity_3_fun(SpecStr)}
-        end, couch_config:get("httpd_design_handlers")),
-
-    UrlHandlers = dict:from_list(UrlHandlersList),
-    DbUrlHandlers = dict:from_list(DbUrlHandlersList),
-    DesignUrlHandlers = dict:from_list(DesignUrlHandlersList),
-    {ok, ServerOptions} = couch_util:parse_term(
-        couch_config:get("httpd", "server_options", "[]")),
-    {ok, SocketOptions} = couch_util:parse_term(
-        couch_config:get("httpd", "socket_options", "[]")),
-
-    set_auth_handlers(),
-
-    % ensure uuid is set so that concurrent replications
-    % get the same value.
-    couch_server:get_uuid(),
-
-    Loop = fun(Req)->
-        case SocketOptions of
-        [] ->
-            ok;
-        _ ->
-            ok = mochiweb_socket:setopts(Req:get(socket), SocketOptions)
-        end,
-        apply(?MODULE, handle_request, [
-            Req, DefaultFun, UrlHandlers, DbUrlHandlers, DesignUrlHandlers
-        ])
-    end,
-
-    % set mochiweb options
-    FinalOptions = lists:append([Options, ServerOptions, [
-            {loop, Loop},
-            {name, Name},
-            {ip, BindAddress}]]),
-
-    % launch mochiweb
-    {ok, Pid} = case mochiweb_http:start(FinalOptions) of
-        {ok, MochiPid} ->
-            {ok, MochiPid};
-        {error, Reason} ->
-            io:format("Failure to start Mochiweb: ~s~n",[Reason]),
-            throw({error, Reason})
-    end,
-
-    ok = couch_config:register(fun ?MODULE:config_change/2, Pid),
-    {ok, Pid}.
-
-
-stop() ->
-    mochiweb_http:stop(couch_httpd),
-    mochiweb_http:stop(https).
-
-config_change("httpd", "bind_address") ->
-    ?MODULE:stop();
-config_change("httpd", "port") ->
-    ?MODULE:stop();
-config_change("httpd", "default_handler") ->
-    ?MODULE:stop();
-config_change("httpd", "server_options") ->
-    ?MODULE:stop();
-config_change("httpd", "socket_options") ->
-    ?MODULE:stop();
-config_change("httpd", "authentication_handlers") ->
-    set_auth_handlers();
-config_change("httpd_global_handlers", _) ->
-    ?MODULE:stop();
-config_change("httpd_db_handlers", _) ->
-    ?MODULE:stop();
-config_change("ssl", _) ->
-    ?MODULE:stop().
-
-set_auth_handlers() ->
-    AuthenticationSrcs = make_fun_spec_strs(
-        couch_config:get("httpd", "authentication_handlers", "")),
-    AuthHandlers = lists:map(
-        fun(A) -> {make_arity_1_fun(A), ?l2b(A)} end, AuthenticationSrcs),
-    ok = application:set_env(couch, auth_handlers, AuthHandlers).
-
-% SpecStr is a string like "{my_module, my_fun}"
-%  or "{my_module, my_fun, <<"my_arg">>}"
-make_arity_1_fun(SpecStr) ->
-    case couch_util:parse_term(SpecStr) of
-    {ok, {Mod, Fun, SpecArg}} ->
-        fun(Arg) -> Mod:Fun(Arg, SpecArg) end;
-    {ok, {Mod, Fun}} ->
-        fun(Arg) -> Mod:Fun(Arg) end
-    end.
-
-make_arity_2_fun(SpecStr) ->
-    case couch_util:parse_term(SpecStr) of
-    {ok, {Mod, Fun, SpecArg}} ->
-        fun(Arg1, Arg2) -> Mod:Fun(Arg1, Arg2, SpecArg) end;
-    {ok, {Mod, Fun}} ->
-        fun(Arg1, Arg2) -> Mod:Fun(Arg1, Arg2) end
-    end.
-
-make_arity_3_fun(SpecStr) ->
-    case couch_util:parse_term(SpecStr) of
-    {ok, {Mod, Fun, SpecArg}} ->
-        fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3, SpecArg) end;
-    {ok, {Mod, Fun}} ->
-        fun(Arg1, Arg2, Arg3) -> Mod:Fun(Arg1, Arg2, Arg3) end
-    end.
-
-% SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}"
-make_fun_spec_strs(SpecStr) ->
-    re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]).
-
-handle_request(MochiReq, DefaultFun, UrlHandlers, DbUrlHandlers,
-    DesignUrlHandlers) ->
-    %% reset rewrite count for new request
-    erlang:put(?REWRITE_COUNT, 0),
-
-    MochiReq1 = couch_httpd_vhost:dispatch_host(MochiReq),
-
-    handle_request_int(MochiReq1, DefaultFun,
-                UrlHandlers, DbUrlHandlers, DesignUrlHandlers).
-
-handle_request_int(MochiReq, DefaultFun,
-            UrlHandlers, DbUrlHandlers, DesignUrlHandlers) ->
-    Begin = now(),
-    % for the path, use the raw path with the query string and fragment
-    % removed, but URL quoting left intact
-    RawUri = MochiReq:get(raw_path),
-    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
-
-    Headers = MochiReq:get(headers),
-
-    % get requested path
-    RequestedPath = case MochiReq:get_header_value("x-couchdb-vhost-path") of
-        undefined ->
-            case MochiReq:get_header_value("x-couchdb-requested-path") of
-                undefined -> RawUri;
-                R -> R
-            end;
-        P -> P
-    end,
-
-    HandlerKey =
-    case mochiweb_util:partition(Path, "/") of
-    {"", "", ""} ->
-        <<"/">>; % Special case the root url handler
-    {FirstPart, _, _} ->
-        list_to_binary(FirstPart)
-    end,
-    ?LOG_DEBUG("~p ~s ~p from ~p~nHeaders: ~p", [
-        MochiReq:get(method),
-        RawUri,
-        MochiReq:get(version),
-        MochiReq:get(peer),
-        mochiweb_headers:to_list(MochiReq:get(headers))
-    ]),
-
-    Method1 =
-    case MochiReq:get(method) of
-        % already an atom
-        Meth when is_atom(Meth) -> Meth;
-
-        % Non standard HTTP verbs aren't atoms (COPY, MOVE etc) so convert when
-        % possible (if any module references the atom, then it's existing).
-        Meth -> couch_util:to_existing_atom(Meth)
-    end,
-    increment_method_stats(Method1),
-
-    % allow broken HTTP clients to fake a full method vocabulary with an X-HTTP-METHOD-OVERRIDE header
-    MethodOverride = MochiReq:get_primary_header_value("X-HTTP-Method-Override"),
-    Method2 = case lists:member(MethodOverride, ["GET", "HEAD", "POST",
-                                                 "PUT", "DELETE",
-                                                 "TRACE", "CONNECT",
-                                                 "COPY"]) of
-    true ->
-        ?LOG_INFO("MethodOverride: ~s (real method was ~s)", [MethodOverride, Method1]),
-        case Method1 of
-        'POST' -> couch_util:to_existing_atom(MethodOverride);
-        _ ->
-            % Ignore X-HTTP-Method-Override when the original verb isn't POST.
-            % I'd like to send a 406 error to the client, but that'd require a nasty refactor.
-            % throw({not_acceptable, <<"X-HTTP-Method-Override may only be used with POST requests.">>})
-            Method1
-        end;
-    _ -> Method1
-    end,
-
-    % alias HEAD to GET as mochiweb takes care of stripping the body
-    Method = case Method2 of
-        'HEAD' -> 'GET';
-        Other -> Other
-    end,
-
-    HttpReq = #httpd{
-        mochi_req = MochiReq,
-        peer = MochiReq:get(peer),
-        method = Method,
-        requested_path_parts =
-            [?l2b(unquote(Part)) || Part <- string:tokens(RequestedPath, "/")],
-        path_parts = [?l2b(unquote(Part)) || Part <- string:tokens(Path, "/")],
-        db_url_handlers = DbUrlHandlers,
-        design_url_handlers = DesignUrlHandlers,
-        default_fun = DefaultFun,
-        url_handlers = UrlHandlers,
-        user_ctx = erlang:erase(pre_rewrite_user_ctx),
-        auth = erlang:erase(pre_rewrite_auth)
-    },
-
-    HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun),
-    {ok, AuthHandlers} = application:get_env(couch, auth_handlers),
-
-    {ok, Resp} =
-    try
-        case couch_httpd_cors:is_preflight_request(HttpReq) of
-        #httpd{} ->
-            case authenticate_request(HttpReq, AuthHandlers) of
-            #httpd{} = Req ->
-                HandlerFun(Req);
-            Response ->
-                Response
-            end;
-        Response ->
-            Response
-        end
-    catch
-        throw:{http_head_abort, Resp0} ->
-            {ok, Resp0};
-        throw:{invalid_json, S} ->
-            ?LOG_ERROR("attempted upload of invalid JSON (set log_level to debug to log it)", []),
-            ?LOG_DEBUG("Invalid JSON: ~p",[S]),
-            send_error(HttpReq, {bad_request, invalid_json});
-        throw:unacceptable_encoding ->
-            ?LOG_ERROR("unsupported encoding method for the response", []),
-            send_error(HttpReq, {not_acceptable, "unsupported encoding"});
-        throw:bad_accept_encoding_value ->
-            ?LOG_ERROR("received invalid Accept-Encoding header", []),
-            send_error(HttpReq, bad_request);
-        exit:normal ->
-            exit(normal);
-        exit:snappy_nif_not_loaded ->
-            ErrorReason = "To access the database or view index, Apache CouchDB"
-                " must be built with Erlang OTP R13B04 or higher.",
-            ?LOG_ERROR("~s", [ErrorReason]),
-            send_error(HttpReq, {bad_otp_release, ErrorReason});
-        exit:{body_too_large, _} ->
-            send_error(HttpReq, request_entity_too_large);
-        throw:Error ->
-            Stack = erlang:get_stacktrace(),
-            ?LOG_DEBUG("Minor error in HTTP request: ~p",[Error]),
-            ?LOG_DEBUG("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, Error);
-        error:badarg ->
-            Stack = erlang:get_stacktrace(),
-            ?LOG_ERROR("Badarg error in HTTP request",[]),
-            ?LOG_INFO("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, badarg);
-        error:function_clause ->
-            Stack = erlang:get_stacktrace(),
-            ?LOG_ERROR("function_clause error in HTTP request",[]),
-            ?LOG_INFO("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, function_clause);
-        Tag:Error ->
-            Stack = erlang:get_stacktrace(),
-            ?LOG_ERROR("Uncaught error in HTTP request: ~p",[{Tag, Error}]),
-            ?LOG_INFO("Stacktrace: ~p",[Stack]),
-            send_error(HttpReq, Error)
-    end,
-    RequestTime = round(timer:now_diff(now(), Begin)/1000),
-    couch_stats_collector:record({couchdb, request_time}, RequestTime),
-    couch_stats_collector:increment({httpd, requests}),
-    {ok, Resp}.
-
-% Try authentication handlers in order until one sets a user_ctx
-% the auth funs also have the option of returning a response
-% move this to couch_httpd_auth?
-authenticate_request(#httpd{user_ctx=#user_ctx{}} = Req, _AuthHandlers) ->
-    Req;
-authenticate_request(#httpd{} = Req, []) ->
-    case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
-    "true" ->
-        throw({unauthorized, <<"Authentication required.">>});
-    "false" ->
-        Req#httpd{user_ctx=#user_ctx{}}
-    end;
-authenticate_request(#httpd{} = Req, [{AuthFun, AuthSrc} | RestAuthHandlers]) ->
-    R = case AuthFun(Req) of
-        #httpd{user_ctx=#user_ctx{}=UserCtx}=Req2 ->
-            Req2#httpd{user_ctx=UserCtx#user_ctx{handler=AuthSrc}};
-        Else -> Else
-    end,
-    authenticate_request(R, RestAuthHandlers);
-authenticate_request(Response, _AuthSrcs) ->
-    Response.
-
-increment_method_stats(Method) ->
-    couch_stats_collector:increment({httpd_request_methods, Method}).
-
-validate_referer(Req) ->
-    Host = host_for_request(Req),
-    Referer = header_value(Req, "Referer", fail),
-    case Referer of
-    fail ->
-        throw({bad_request, <<"Referer header required.">>});
-    Referer ->
-        {_,RefererHost,_,_,_} = mochiweb_util:urlsplit(Referer),
-        if
-            RefererHost =:= Host -> ok;
-            true -> throw({bad_request, <<"Referer header must match host.">>})
-        end
-    end.
-
-validate_ctype(Req, Ctype) ->
-    case header_value(Req, "Content-Type") of
-    undefined ->
-        throw({bad_ctype, "Content-Type must be "++Ctype});
-    ReqCtype ->
-        case string:tokens(ReqCtype, ";") of
-        [Ctype] -> ok;
-        [Ctype, _Rest] -> ok;
-        _Else ->
-            throw({bad_ctype, "Content-Type must be "++Ctype})
-        end
-    end.
-
-% Utilities
-
-partition(Path) ->
-    mochiweb_util:partition(Path, "/").
-
-header_value(#httpd{mochi_req=MochiReq}, Key) ->
-    MochiReq:get_header_value(Key).
-
-header_value(#httpd{mochi_req=MochiReq}, Key, Default) ->
-    case MochiReq:get_header_value(Key) of
-    undefined -> Default;
-    Value -> Value
-    end.
-
-primary_header_value(#httpd{mochi_req=MochiReq}, Key) ->
-    MochiReq:get_primary_header_value(Key).
-
-accepted_encodings(#httpd{mochi_req=MochiReq}) ->
-    case MochiReq:accepted_encodings(["gzip", "identity"]) of
-    bad_accept_encoding_value ->
-        throw(bad_accept_encoding_value);
-    [] ->
-        throw(unacceptable_encoding);
-    EncList ->
-        EncList
-    end.
-
-serve_file(Req, RelativePath, DocumentRoot) ->
-    serve_file(Req, RelativePath, DocumentRoot, []).
-
-serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot,
-           ExtraHeaders) ->
-    log_request(Req, 200),
-    ResponseHeaders = server_header()
-        ++ couch_httpd_auth:cookie_auth_header(Req, [])
-        ++ ExtraHeaders,
-    {ok, MochiReq:serve_file(RelativePath, DocumentRoot,
-            couch_httpd_cors:cors_headers(Req, ResponseHeaders))}.
-
-qs_value(Req, Key) ->
-    qs_value(Req, Key, undefined).
-
-qs_value(Req, Key, Default) ->
-    couch_util:get_value(Key, qs(Req), Default).
-
-qs_json_value(Req, Key, Default) ->
-    case qs_value(Req, Key, Default) of
-    Default ->
-        Default;
-    Result ->
-        ?JSON_DECODE(Result)
-    end.
-
-qs(#httpd{mochi_req=MochiReq}) ->
-    MochiReq:parse_qs().
-
-path(#httpd{mochi_req=MochiReq}) ->
-    MochiReq:get(path).
-
-host_for_request(#httpd{mochi_req=MochiReq}) ->
-    XHost = couch_config:get("httpd", "x_forwarded_host", "X-Forwarded-Host"),
-    case MochiReq:get_header_value(XHost) of
-        undefined ->
-            case MochiReq:get_header_value("Host") of
-                undefined ->
-                    {ok, {Address, Port}} = case MochiReq:get(socket) of
-                        {ssl, SslSocket} -> ssl:sockname(SslSocket);
-                        Socket -> inet:sockname(Socket)
-                    end,
-                    inet_parse:ntoa(Address) ++ ":" ++ integer_to_list(Port);
-                Value1 ->
-                    Value1
-            end;
-        Value -> Value
-    end.
-
-absolute_uri(#httpd{mochi_req=MochiReq}=Req, Path) ->
-    Host = host_for_request(Req),
-    XSsl = couch_config:get("httpd", "x_forwarded_ssl", "X-Forwarded-Ssl"),
-    Scheme = case MochiReq:get_header_value(XSsl) of
-                 "on" -> "https";
-                 _ ->
-                     XProto = couch_config:get("httpd", "x_forwarded_proto", "X-Forwarded-Proto"),
-                     case MochiReq:get_header_value(XProto) of
-                         %% Restrict to "https" and "http" schemes only
-                         "https" -> "https";
-                         _ -> case MochiReq:get(scheme) of
-                                  https -> "https";
-                                  http -> "http"
-                              end
-                     end
-             end,
-    Scheme ++ "://" ++ Host ++ Path.
-
-unquote(UrlEncodedString) ->
-    mochiweb_util:unquote(UrlEncodedString).
-
-quote(UrlDecodedString) ->
-    mochiweb_util:quote_plus(UrlDecodedString).
-
-parse_form(#httpd{mochi_req=MochiReq}) ->
-    mochiweb_multipart:parse_form(MochiReq).
-
-recv(#httpd{mochi_req=MochiReq}, Len) ->
-    MochiReq:recv(Len).
-
-recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) ->
-    % Fun is called once with each chunk
-    % Fun({Length, Binary}, State)
-    % called with Length == 0 on the last time.
-    MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState).
-
-body_length(#httpd{mochi_req=MochiReq}) ->
-    MochiReq:get(body_length).
-
-body(#httpd{mochi_req=MochiReq, req_body=undefined}) ->
-    MaxSize = list_to_integer(
-        couch_config:get("couchdb", "max_document_size", "4294967296")),
-    MochiReq:recv_body(MaxSize);
-body(#httpd{req_body=ReqBody}) ->
-    ReqBody.
-
-json_body(Httpd) ->
-    ?JSON_DECODE(body(Httpd)).
-
-json_body_obj(Httpd) ->
-    case json_body(Httpd) of
-        {Props} -> {Props};
-        _Else ->
-            throw({bad_request, "Request body must be a JSON object"})
-    end.
-
-
-
-doc_etag(#doc{revs={Start, [DiskRev|_]}}) ->
-    "\"" ++ ?b2l(couch_doc:rev_to_str({Start, DiskRev})) ++ "\"".
-
-make_etag(Term) ->
-    <<SigInt:128/integer>> = couch_util:md5(term_to_binary(Term)),
-    iolist_to_binary([$", io_lib:format("~.36B", [SigInt]), $"]).
-
-etag_match(Req, CurrentEtag) when is_binary(CurrentEtag) ->
-    etag_match(Req, binary_to_list(CurrentEtag));
-
-etag_match(Req, CurrentEtag) ->
-    EtagsToMatch = string:tokens(
-        header_value(Req, "If-None-Match", ""), ", "),
-    lists:member(CurrentEtag, EtagsToMatch).
-
-etag_respond(Req, CurrentEtag, RespFun) ->
-    case etag_match(Req, CurrentEtag) of
-    true ->
-        % the client has this in their cache.
-        send_response(Req, 304, [{"ETag", CurrentEtag}], <<>>);
-    false ->
-        % Run the function.
-        RespFun()
-    end.
-
-etag_maybe(Req, RespFun) ->
-    try
-        RespFun()
-    catch
-        throw:{etag_match, ETag} ->
-            send_response(Req, 304, [{"ETag", ETag}], <<>>)
-    end.
-
-verify_is_server_admin(#httpd{user_ctx=UserCtx}) ->
-    verify_is_server_admin(UserCtx);
-verify_is_server_admin(#user_ctx{roles=Roles}) ->
-    case lists:member(<<"_admin">>, Roles) of
-    true -> ok;
-    false -> throw({unauthorized, <<"You are not a server admin.">>})
-    end.
-
-log_request(#httpd{mochi_req=MochiReq,peer=Peer}=Req, Code) ->
-    ?LOG_INFO("~s - - ~s ~s ~B", [
-        Peer,
-        MochiReq:get(method),
-        MochiReq:get(raw_path),
-        Code
-    ]),
-    gen_event:notify(couch_plugin, {log_request, Req, Code}).
-
-
-start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Length) ->
-    log_request(Req, Code),
-    couch_stats_collector:increment({httpd_status_codes, Code}),
-    Headers1 = Headers ++ server_header() ++
-               couch_httpd_auth:cookie_auth_header(Req, Headers),
-    Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
-    Resp = MochiReq:start_response_length({Code, Headers2, Length}),
-    case MochiReq:get(method) of
-    'HEAD' -> throw({http_head_abort, Resp});
-    _ -> ok
-    end,
-    {ok, Resp}.
-
-start_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
-    log_request(Req, Code),
-    couch_stats_collector:increment({httpd_status_codes, Code}),
-    CookieHeader = couch_httpd_auth:cookie_auth_header(Req, Headers),
-    Headers1 = Headers ++ server_header() ++ CookieHeader,
-    Headers2 = couch_httpd_cors:cors_headers(Req, Headers1),
-    Resp = MochiReq:start_response({Code, Headers2}),
-    case MochiReq:get(method) of
-        'HEAD' -> throw({http_head_abort, Resp});
-        _ -> ok
-    end,
-    {ok, Resp}.
-
-send(Resp, Data) ->
-    Resp:send(Data),
-    {ok, Resp}.
-
-no_resp_conn_header([]) ->
-    true;
-no_resp_conn_header([{Hdr, _}|Rest]) ->
-    case string:to_lower(Hdr) of
-        "connection" -> false;
-        _ -> no_resp_conn_header(Rest)
-    end.
-
-http_1_0_keep_alive(Req, Headers) ->
-    KeepOpen = Req:should_close() == false,
-    IsHttp10 = Req:get(version) == {1, 0},
-    NoRespHeader = no_resp_conn_header(Headers),
-    case KeepOpen andalso IsHttp10 andalso NoRespHeader of
-        true -> [{"Connection", "Keep-Alive"} | Headers];
-        false -> Headers
-    end.
-
-start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers) ->
-    log_request(Req, Code),
-    couch_stats_collector:increment({httpd_status_codes, Code}),
-    Headers1 = http_1_0_keep_alive(MochiReq, Headers),
-    Headers2 = Headers1 ++ server_header() ++
-               couch_httpd_auth:cookie_auth_header(Req, Headers1),
-    Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),
-    Resp = MochiReq:respond({Code, Headers3, chunked}),
-    case MochiReq:get(method) of
-    'HEAD' -> throw({http_head_abort, Resp});
-    _ -> ok
-    end,
-    {ok, Resp}.
-
-send_chunk(Resp, Data) ->
-    case iolist_size(Data) of
-    0 -> ok; % do nothing
-    _ -> Resp:write_chunk(Data)
-    end,
-    {ok, Resp}.
-
-last_chunk(Resp) ->
-    Resp:write_chunk([]),
-    {ok, Resp}.
-
-send_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) ->
-    log_request(Req, Code),
-    couch_stats_collector:increment({httpd_status_codes, Code}),
-    Headers1 = http_1_0_keep_alive(MochiReq, Headers),
-    if Code >= 500 ->
-        ?LOG_ERROR("httpd ~p error response:~n ~s", [Code, Body]);
-    Code >= 400 ->
-        ?LOG_DEBUG("httpd ~p error response:~n ~s", [Code, Body]);
-    true -> ok
-    end,
-    Headers2 = Headers1 ++ server_header() ++
-               couch_httpd_auth:cookie_auth_header(Req, Headers1),
-    Headers3 = couch_httpd_cors:cors_headers(Req, Headers2),
-
-    {ok, MochiReq:respond({Code, Headers3, Body})}.
-
-send_method_not_allowed(Req, Methods) ->
-    send_error(Req, 405, [{"Allow", Methods}], <<"method_not_allowed">>, ?l2b("Only " ++ Methods ++ " allowed")).
-
-send_json(Req, Value) ->
-    send_json(Req, 200, Value).
-
-send_json(Req, Code, Value) ->
-    send_json(Req, Code, [], Value).
-
-send_json(Req, Code, Headers, Value) ->
-    initialize_jsonp(Req),
-    DefaultHeaders = [
-        {"Content-Type", negotiate_content_type(Req)},
-        {"Cache-Control", "must-revalidate"}
-    ],
-    Body = [start_jsonp(), ?JSON_ENCODE(Value), end_jsonp(), $\n],
-    send_response(Req, Code, DefaultHeaders ++ Headers, Body).
-
-start_json_response(Req, Code) ->
-    start_json_response(Req, Code, []).
-
-start_json_response(Req, Code, Headers) ->
-    initialize_jsonp(Req),
-    DefaultHeaders = [
-        {"Content-Type", negotiate_content_type(Req)},
-        {"Cache-Control", "must-revalidate"}
-    ],
-    {ok, Resp} = start_chunked_response(Req, Code, DefaultHeaders ++ Headers),
-    case start_jsonp() of
-        [] -> ok;
-        Start -> send_chunk(Resp, Start)
-    end,
-    {ok, Resp}.
-
-end_json_response(Resp) ->
-    send_chunk(Resp, end_jsonp() ++ [$\n]),
-    last_chunk(Resp).
-
-initialize_jsonp(Req) ->
-    case get(jsonp) of
-        undefined -> put(jsonp, qs_value(Req, "callback", no_jsonp));
-        _ -> ok
-    end,
-    case get(jsonp) of
-        no_jsonp -> [];
-        [] -> [];
-        CallBack ->
-            try
-                % make sure jsonp is configured on (default off)
-                case couch_config:get("httpd", "allow_jsonp", "false") of
-                "true" ->
-                    validate_callback(CallBack);
-                _Else ->
-                    put(jsonp, no_jsonp)
-                end
-            catch
-                Error ->
-                    put(jsonp, no_jsonp),
-                    throw(Error)
-            end
-    end.
-
-start_jsonp() ->
-    case get(jsonp) of
-        no_jsonp -> [];
-        [] -> [];
-        CallBack -> ["/* CouchDB */", CallBack, "("]
-    end.
-
-end_jsonp() ->
-    case erlang:erase(jsonp) of
-        no_jsonp -> [];
-        [] -> [];
-        _ -> ");"
-    end.
-
-validate_callback(CallBack) when is_binary(CallBack) ->
-    validate_callback(binary_to_list(CallBack));
-validate_callback([]) ->
-    ok;
-validate_callback([Char | Rest]) ->
-    case Char of
-        _ when Char >= $a andalso Char =< $z -> ok;
-        _ when Char >= $A andalso Char =< $Z -> ok;
-        _ when Char >= $0 andalso Char =< $9 -> ok;
-        _ when Char == $. -> ok;
-        _ when Char == $_ -> ok;
-        _ when Char == $[ -> ok;
-        _ when Char == $] -> ok;
-        _ ->
-            throw({bad_request, invalid_callback})
-    end,
-    validate_callback(Rest).
-
-
-error_info({Error, Reason}) when is_list(Reason) ->
-    error_info({Error, ?l2b(Reason)});
-error_info(bad_request) ->
-    {400, <<"bad_request">>, <<>>};
-error_info({bad_request, Reason}) ->
-    {400, <<"bad_request">>, Reason};
-error_info({query_parse_error, Reason}) ->
-    {400, <<"query_parse_error">>, Reason};
-% Prior art for md5 mismatch resulting in a 400 is from AWS S3
-error_info(md5_mismatch) ->
-    {400, <<"content_md5_mismatch">>, <<"Possible message corruption.">>};
-error_info(not_found) ->
-    {404, <<"not_found">>, <<"missing">>};
-error_info({not_found, Reason}) ->
-    {404, <<"not_found">>, Reason};
-error_info({not_acceptable, Reason}) ->
-    {406, <<"not_acceptable">>, Reason};
-error_info(conflict) ->
-    {409, <<"conflict">>, <<"Document update conflict.">>};
-error_info({forbidden, Msg}) ->
-    {403, <<"forbidden">>, Msg};
-error_info({unauthorized, Msg}) ->
-    {401, <<"unauthorized">>, Msg};
-error_info(file_exists) ->
-    {412, <<"file_exists">>, <<"The database could not be "
-        "created, the file already exists.">>};
-error_info(request_entity_too_large) ->
-    {413, <<"too_large">>, <<"the request entity is too large">>};
-error_info({bad_ctype, Reason}) ->
-    {415, <<"bad_content_type">>, Reason};
-error_info(requested_range_not_satisfiable) ->
-    {416, <<"requested_range_not_satisfiable">>, <<"Requested range not satisfiable">>};
-error_info({error, illegal_database_name, Name}) ->
-    Message = "Name: '" ++ Name ++ "'. Only lowercase characters (a-z), "
-        ++ "digits (0-9), and any of the characters _, $, (, ), +, -, and / "
-        ++ "are allowed. Must begin with a letter.",
-    {400, <<"illegal_database_name">>, couch_util:to_binary(Message)};
-error_info({missing_stub, Reason}) ->
-    {412, <<"missing_stub">>, Reason};
-error_info({Error, Reason}) ->
-    {500, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
-error_info(Error) ->
-    {500, <<"unknown_error">>, couch_util:to_binary(Error)}.
-
-error_headers(#httpd{mochi_req=MochiReq}=Req, Code, ErrorStr, ReasonStr) ->
-    if Code == 401 ->
-        % this is where the basic auth popup is triggered
-        case MochiReq:get_header_value("X-CouchDB-WWW-Authenticate") of
-        undefined ->
-            case couch_config:get("httpd", "WWW-Authenticate", nil) of
-            nil ->
-                % If the client is a browser and the basic auth popup isn't turned on
-                % redirect to the session page.
-                case ErrorStr of
-                <<"unauthorized">> ->
-                    case couch_config:get("couch_httpd_auth", "authentication_redirect", nil) of
-                    nil -> {Code, []};
-                    AuthRedirect ->
-                        case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
-                        "true" ->
-                            % send the browser popup header no matter what if we are require_valid_user
-                            {Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]};
-                        _False ->
-                            case MochiReq:accepts_content_type("application/json") of
-                            true ->
-                                {Code, []};
-                            false ->
-                                case MochiReq:accepts_content_type("text/html") of
-                                true ->
-                                    % Redirect to the path the user requested, not
-                                    % the one that is used internally.
-                                    UrlReturnRaw = case MochiReq:get_header_value("x-couchdb-vhost-path") of
-                                    undefined ->
-                                        MochiReq:get(path);
-                                    VHostPath ->
-                                        VHostPath
-                                    end,
-                                    RedirectLocation = lists:flatten([
-                                        AuthRedirect,
-                                        "?return=", couch_util:url_encode(UrlReturnRaw),
-                                        "&reason=", couch_util:url_encode(ReasonStr)
-                                    ]),
-                                    {302, [{"Location", absolute_uri(Req, RedirectLocation)}]};
-                                false ->
-                                    {Code, []}
-                                end
-                            end
-                        end
-                    end;
-                _Else ->
-                    {Code, []}
-                end;
-            Type ->
-                {Code, [{"WWW-Authenticate", Type}]}
-            end;
-        Type ->
-           {Code, [{"WWW-Authenticate", Type}]}
-        end;
-    true ->
-        {Code, []}
-    end.
-
-send_error(_Req, {already_sent, Resp, _Error}) ->
-    {ok, Resp};
-
-send_error(Req, Error) ->
-    {Code, ErrorStr, ReasonStr} = error_info(Error),
-    {Code1, Headers} = error_headers(Req, Code, ErrorStr, ReasonStr),
-    send_error(Req, Code1, Headers, ErrorStr, ReasonStr).
-
-send_error(Req, Code, ErrorStr, ReasonStr) ->
-    send_error(Req, Code, [], ErrorStr, ReasonStr).
-
-send_error(Req, Code, Headers, ErrorStr, ReasonStr) ->
-    send_json(Req, Code, Headers,
-        {[{<<"error">>,  ErrorStr},
-         {<<"reason">>, ReasonStr}]}).
-
-% give the option for list functions to output html or other raw errors
-send_chunked_error(Resp, {_Error, {[{<<"body">>, Reason}]}}) ->
-    send_chunk(Resp, Reason),
-    last_chunk(Resp);
-
-send_chunked_error(Resp, Error) ->
-    {Code, ErrorStr, ReasonStr} = error_info(Error),
-    JsonError = {[{<<"code">>, Code},
-        {<<"error">>,  ErrorStr},
-        {<<"reason">>, ReasonStr}]},
-    send_chunk(Resp, ?l2b([$\n,?JSON_ENCODE(JsonError),$\n])),
-    last_chunk(Resp).
-
-send_redirect(Req, Path) ->
-     send_response(Req, 301, [{"Location", absolute_uri(Req, Path)}], <<>>).
-
-negotiate_content_type(Req) ->
-    case get(jsonp) of
-        no_jsonp -> negotiate_content_type1(Req);
-        [] -> negotiate_content_type1(Req);
-        _Callback -> "text/javascript"
-    end.
-
-negotiate_content_type1(#httpd{mochi_req=MochiReq}) ->
-    %% Determine the appropriate Content-Type header for a JSON response
-    %% depending on the Accept header in the request. A request that explicitly
-    %% lists the correct JSON MIME type will get that type, otherwise the
-    %% response will have the generic MIME type "text/plain"
-    AcceptedTypes = case MochiReq:get_header_value("Accept") of
-        undefined       -> [];
-        AcceptHeader    -> string:tokens(AcceptHeader, ", ")
-    end,
-    case lists:member("application/json", AcceptedTypes) of
-        true  -> "application/json";
-        false -> "text/plain; charset=utf-8"
-    end.
-
-server_header() ->
-    [{"Server", "CouchDB/" ++ couch_server:get_version() ++
-                " (Erlang OTP/" ++ erlang:system_info(otp_release) ++ ")"}].
-
-
--record(mp, {boundary, buffer, data_fun, callback}).
-
-
-parse_multipart_request(ContentType, DataFun, Callback) ->
-    Boundary0 = iolist_to_binary(get_boundary(ContentType)),
-    Boundary = <<"\r\n--", Boundary0/binary>>,
-    Mp = #mp{boundary= Boundary,
-            buffer= <<>>,
-            data_fun=DataFun,
-            callback=Callback},
-    {Mp2, _NilCallback} = read_until(Mp, <<"--", Boundary0/binary>>,
-        fun nil_callback/1),
-    #mp{buffer=Buffer, data_fun=DataFun2, callback=Callback2} =
-            parse_part_header(Mp2),
-    {Buffer, DataFun2, Callback2}.
-
-nil_callback(_Data)->
-    fun nil_callback/1.
-
-get_boundary({"multipart/" ++ _, Opts}) ->
-    case couch_util:get_value("boundary", Opts) of
-        S when is_list(S) ->
-            S
-    end;
-get_boundary(ContentType) ->
-    {"multipart/" ++ _ , Opts} = mochiweb_util:parse_header(ContentType),
-    get_boundary({"multipart/", Opts}).
-
-
-
-split_header(<<>>) ->
-    [];
-split_header(Line) ->
-    {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end,
-                                           binary_to_list(Line)),
-    [{string:to_lower(string:strip(Name)),
-     mochiweb_util:parse_header(Value)}].
-
-read_until(#mp{data_fun=DataFun, buffer=Buffer}=Mp, Pattern, Callback) ->
-    case find_in_binary(Pattern, Buffer) of
-    not_found ->
-        Callback2 = Callback(Buffer),
-        {Buffer2, DataFun2} = DataFun(),
-        Buffer3 = iolist_to_binary(Buffer2),
-        read_until(Mp#mp{data_fun=DataFun2,buffer=Buffer3}, Pattern, Callback2);
-    {partial, 0} ->
-        {NewData, DataFun2} = DataFun(),
-        read_until(Mp#mp{data_fun=DataFun2,
-                buffer= iolist_to_binary([Buffer,NewData])},
-                Pattern, Callback);
-    {partial, Skip} ->
-        <<DataChunk:Skip/binary, Rest/binary>> = Buffer,
-        Callback2 = Callback(DataChunk),
-        {NewData, DataFun2} = DataFun(),
-        read_until(Mp#mp{data_fun=DataFun2,
-                buffer= iolist_to_binary([Rest | NewData])},
-                Pattern, Callback2);
-    {exact, 0} ->
-        PatternLen = size(Pattern),
-        <<_:PatternLen/binary, Rest/binary>> = Buffer,
-        {Mp#mp{buffer= Rest}, Callback};
-    {exact, Skip} ->
-        PatternLen = size(Pattern),
-        <<DataChunk:Skip/binary, _:PatternLen/binary, Rest/binary>> = Buffer,
-        Callback2 = Callback(DataChunk),
-        {Mp#mp{buffer= Rest}, Callback2}
-    end.
-
-
-parse_part_header(#mp{callback=UserCallBack}=Mp) ->
-    {Mp2, AccCallback} = read_until(Mp, <<"\r\n\r\n">>,
-            fun(Next) -> acc_callback(Next, []) end),
-    HeaderData = AccCallback(get_data),
-
-    Headers =
-    lists:foldl(fun(Line, Acc) ->
-            split_header(Line) ++ Acc
-        end, [], re:split(HeaderData,<<"\r\n">>, [])),
-    NextCallback = UserCallBack({headers, Headers}),
-    parse_part_body(Mp2#mp{callback=NextCallback}).
-
-parse_part_body(#mp{boundary=Prefix, callback=Callback}=Mp) ->
-    {Mp2, WrappedCallback} = read_until(Mp, Prefix,
-            fun(Data) -> body_callback_wrapper(Data, Callback) end),
-    Callback2 = WrappedCallback(get_callback),
-    Callback3 = Callback2(body_end),
-    case check_for_last(Mp2#mp{callback=Callback3}) of
-    {last, #mp{callback=Callback3}=Mp3} ->
-        Mp3#mp{callback=Callback3(eof)};
-    {more, Mp3} ->
-        parse_part_header(Mp3)
-    end.
-
-acc_callback(get_data, Acc)->
-    iolist_to_binary(lists:reverse(Acc));
-acc_callback(Data, Acc)->
-    fun(Next) -> acc_callback(Next, [Data | Acc]) end.
-
-body_callback_wrapper(get_callback, Callback) ->
-    Callback;
-body_callback_wrapper(Data, Callback) ->
-    Callback2 = Callback({body, Data}),
-    fun(Next) -> body_callback_wrapper(Next, Callback2) end.
-
-
-check_for_last(#mp{buffer=Buffer, data_fun=DataFun}=Mp) ->
-    case Buffer of
-    <<"--",_/binary>> -> {last, Mp};
-    <<_, _, _/binary>> -> {more, Mp};
-    _ -> % not long enough
-        {Data, DataFun2} = DataFun(),
-        check_for_last(Mp#mp{buffer= <<Buffer/binary, Data/binary>>,
-                data_fun = DataFun2})
-    end.
-
-find_in_binary(_B, <<>>) ->
-    not_found;
-
-find_in_binary(B, Data) ->
-    case binary:match(Data, [B], []) of
-    nomatch ->
-        partial_find(binary:part(B, {0, byte_size(B) - 1}),
-                     binary:part(Data, {byte_size(Data), -byte_size(Data) + 1}), 1);
-    {Pos, _Len} ->
-        {exact, Pos}
-    end.
-
-partial_find(<<>>, _Data, _Pos) ->
-    not_found;
-
-partial_find(B, Data, N) when byte_size(Data) > 0 ->
-    case binary:match(Data, [B], []) of
-    nomatch ->
-        partial_find(binary:part(B, {0, byte_size(B) - 1}),
-                     binary:part(Data, {byte_size(Data), -byte_size(Data) + 1}), N + 1);
-    {Pos, _Len} ->
-        {partial, N + Pos}
-    end;
-
-partial_find(_B, _Data, _N) ->
-    not_found.
-
-
-validate_bind_address(Address) ->
-    case inet_parse:address(Address) of
-        {ok, _} -> ok;
-        _ -> throw({error, invalid_bind_address})
-    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl
deleted file mode 100644
index b8c4e26..0000000
--- a/src/couch_httpd_auth.erl
+++ /dev/null
@@ -1,380 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License.  You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
--module(couch_httpd_auth).
--include("couch_db.hrl").
-
--export([default_authentication_handler/1,special_test_authentication_handler/1]).
--export([cookie_authentication_handler/1]).
--export([null_authentication_handler/1]).
--export([proxy_authentication_handler/1, proxy_authentification_handler/1]).
--export([cookie_auth_header/2]).
--export([handle_session_req/1]).
-
--import(couch_httpd, [header_value/2, send_json/2,send_json/4, send_method_not_allowed/2]).
-
-special_test_authentication_handler(Req) ->
-    case header_value(Req, "WWW-Authenticate") of
-    "X-Couch-Test-Auth " ++ NamePass ->
-        % NamePass is a colon separated string: "joe schmoe:a password".
-        [Name, Pass] = re:split(NamePass, ":", [{return, list}, {parts, 2}]),
-        case {Name, Pass} of
-        {"Jan Lehnardt", "apple"} -> ok;
-        {"Christopher Lenz", "dog food"} -> ok;
-        {"Noah Slater", "biggiesmalls endian"} -> ok;
-        {"Chris Anderson", "mp3"} -> ok;
-        {"Damien Katz", "pecan pie"} -> ok;
-        {_, _} ->
-            throw({unauthorized, <<"Name or password is incorrect.">>})
-        end,
-        Req#httpd{user_ctx=#user_ctx{name=?l2b(Name)}};
-    _ ->
-        % No X-Couch-Test-Auth credentials sent, give admin access so the
-        % previous authentication can be restored after the test
-        Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}}
-    end.
-
-basic_name_pw(Req) ->
-    AuthorizationHeader = header_value(Req, "Authorization"),
-    case AuthorizationHeader of
-    "Basic " ++ Base64Value ->
-        case re:split(base64:decode(Base64Value), ":",
-                      [{return, list}, {parts, 2}]) of
-        ["_", "_"] ->
-            % special name and pass to be logged out
-            nil;
-        [User, Pass] ->
-            {User, Pass};
-        _ ->
-            nil
-        end;
-    _ ->
-        nil
-    end.
-
-default_authentication_handler(Req) ->
-    case basic_name_pw(Req) of
-    {User, Pass} ->
-        case couch_auth_cache:get_user_creds(User) of
-            nil ->
-                throw({unauthorized, <<"Name or password is incorrect.">>});
-            UserProps ->
-                case authenticate(?l2b(Pass), UserProps) of
-                    true ->
-                        Req#httpd{user_ctx=#user_ctx{
-                            name=?l2b(User),
-                            roles=couch_util:get_value(<<"roles">>, UserProps, [])
-                        }};
-                    _Else ->
-                        throw({unauthorized, <<"Name or password is incorrect.">>})
-                end
-        end;
-    nil ->
-        case couch_server:has_admins() of
-        true ->
-            Req;
-        false ->
-            case couch_config:get("couch_httpd_auth", "require_valid_user", "false") of
-                "true" -> Req;
-                % If no admins, and no user required, then everyone is admin!
-                % Yay, admin party!
-                _ -> Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}}
-            end
-        end
-    end.
-
-null_authentication_handler(Req) ->
-    Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}}.
-
-%% @doc proxy auth handler.
-%
-% This handler allows creation of a userCtx object from a user authenticated remotly.
-% The client just pass specific headers to CouchDB and the handler create the userCtx.
-% Headers  name can be defined in local.ini. By thefault they are :
-%
-%   * X-Auth-CouchDB-UserName : contain the username, (x_auth_username in
-%   couch_httpd_auth section)
-%   * X-Auth-CouchDB-Roles : contain the user roles, list of roles separated by a
-%   comma (x_auth_roles in couch_httpd_auth section)
-%   * X-Auth-CouchDB-Token : token to authenticate the authorization (x_auth_token
-%   in couch_httpd_auth section). This token is an hmac-sha1 created from secret key
-%   and username. The secret key should be the same in the client and couchdb node. s
-%   ecret key is the secret key in couch_httpd_auth section of ini. This token is optional
-%   if value of proxy_use_secret key in couch_httpd_auth section of ini isn't true.
-%
-proxy_authentication_handler(Req) ->
-    case proxy_auth_user(Req) of
-        nil -> Req;
-        Req2 -> Req2
-    end.
-
-%% @deprecated
-proxy_authentification_handler(Req) ->
-    proxy_authentication_handler(Req).
-    
-proxy_auth_user(Req) ->
-    XHeaderUserName = couch_config:get("couch_httpd_auth", "x_auth_username",
-                                "X-Auth-CouchDB-UserName"),
-    XHeaderRoles = couch_config:get("couch_httpd_auth", "x_auth_roles",
-                                "X-Auth-CouchDB-Roles"),
-    XHeaderToken = couch_config:get("couch_httpd_auth", "x_auth_token",
-                                "X-Auth-CouchDB-Token"),
-    case header_value(Req, XHeaderUserName) of
-        undefined -> nil;
-        UserName ->
-            Roles = case header_value(Req, XHeaderRoles) of
-                undefined -> [];
-                Else ->
-                    [?l2b(R) || R <- string:tokens(Else, ",")]
-            end,
-            case couch_config:get("couch_httpd_auth", "proxy_use_secret", "false") of
-                "true" ->
-                    case couch_config:get("couch_httpd_auth", "secret", nil) of
-                        nil ->
-                            Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}};
-                        Secret ->
-                            ExpectedToken = couch_util:to_hex(crypto:sha_mac(Secret, UserName)),
-                            case header_value(Req, XHeaderToken) of
-                                Token when Token == ExpectedToken ->
-                                    Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName),
-                                                            roles=Roles}};
-                                _ -> nil
-                            end
-                    end;
-                _ ->
-                    Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}}
-            end
-    end.
-
-
-cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) ->
-    case MochiReq:get_cookie_value("AuthSession") of
-    undefined -> Req;
-    [] -> Req;
-    Cookie ->
-        [User, TimeStr, HashStr] = try
-            AuthSession = couch_util:decodeBase64Url(Cookie),
-            [_A, _B, _Cs] = re:split(?b2l(AuthSession), ":",
-                                     [{return, list}, {parts, 3}])
-        catch
-            _:_Error ->
-                Reason = <<"Malformed AuthSession cookie. Please clear your cookies.">>,
-                throw({bad_request, Reason})
-        end,
-        % Verify expiry and hash
-        CurrentTime = make_cookie_time(),
-        case couch_config:get("couch_httpd_auth", "secret", nil) of
-        nil ->
-            ?LOG_DEBUG("cookie auth secret is not set",[]),
-            Req;
-        SecretStr ->
-            Secret = ?l2b(SecretStr),
-            case couch_auth_cache:get_user_creds(User) of
-            nil -> Req;
-            UserProps ->
-                UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>),
-                FullSecret = <<Secret/binary, UserSalt/binary>>,
-                ExpectedHash = crypto:sha_mac(FullSecret, User ++ ":" ++ TimeStr),
-                Hash = ?l2b(HashStr),
-                Timeout = list_to_integer(
-                    couch_config:get("couch_httpd_auth", "timeout", "600")),
-                ?LOG_DEBUG("timeout ~p", [Timeout]),
-                case (catch erlang:list_to_integer(TimeStr, 16)) of
-                    TimeStamp when CurrentTime < TimeStamp + Timeout ->
-                        case couch_passwords:verify(ExpectedHash, Hash) of
-                            true ->
-                                TimeLeft = TimeStamp + Timeout - CurrentTime,
-                                ?LOG_DEBUG("Successful cookie auth as: ~p", [User]),
-                                Req#httpd{user_ctx=#user_ctx{
-                                    name=?l2b(User),
-                                    roles=couch_util:get_value(<<"roles">>, UserProps, [])
-                                }, auth={FullSecret, TimeLeft < Timeout*0.9}};
-                            _Else ->
-                                Req
-                        end;
-                    _Else ->
-                        Req
-                end
-            end
-        end
-    end.
-
-cookie_auth_header(#httpd{user_ctx=#user_ctx{name=null}}, _Headers) -> [];
-cookie_auth_header(#httpd{user_ctx=#user_ctx{name=User}, auth={Secret, true}}=Req, Headers) ->
-    % Note: we only set the AuthSession cookie if:
-    %  * a valid AuthSession cookie has been received
-    %  * we are outside a 10% timeout window
-    %  * and if an AuthSession cookie hasn't already been set e.g. by a login
-    %    or logout handler.
-    % The login and logout handlers need to set the AuthSession cookie
-    % themselves.
-    CookieHeader = couch_util:get_value("Set-Cookie", Headers, ""),
-    Cookies = mochiweb_cookies:parse_cookie(CookieHeader),
-    AuthSession = couch_util:get_value("AuthSession", Cookies),
-    if AuthSession == undefined ->
-        TimeStamp = make_cookie_time(),
-        [cookie_auth_cookie(Req, ?b2l(User), Secret, TimeStamp)];
-    true ->
-        []
-    end;
-cookie_auth_header(_Req, _Headers) -> [].
-
-cookie_auth_cookie(Req, User, Secret, TimeStamp) ->
-    SessionData = User ++ ":" ++ erlang:integer_to_list(TimeStamp, 16),
-    Hash = crypto:sha_mac(Secret, SessionData),
-    mochiweb_cookies:cookie("AuthSession",
-        couch_util:encodeBase64Url(SessionData ++ ":" ++ ?b2l(Hash)),
-        [{path, "/"}] ++ cookie_scheme(Req) ++ max_age()).
-
-ensure_cookie_auth_secret() ->
-    case couch_config:get("couch_httpd_auth", "secret", nil) of
-        nil ->
-            NewSecret = ?b2l(couch_uuids:random()),
-            couch_config:set("couch_httpd_auth", "secret", NewSecret),
-            NewSecret;
-        Secret -> Secret
-    end.
-
-% session handlers
-% Login handler with user db
-handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) ->
-    ReqBody = MochiReq:recv_body(),
-    Form = case MochiReq:get_primary_header_value("content-type") of
-        % content type should be json
-        "application/x-www-form-urlencoded" ++ _ ->
-            mochiweb_util:parse_qs(ReqBody);
-        "application/json" ++ _ ->
-            {Pairs} = ?JSON_DECODE(ReqBody),
-            lists:map(fun({Key, Value}) ->
-              {?b2l(Key), ?b2l(Value)}
-            end, Pairs);
-        _ ->
-            []
-    end,
-    UserName = ?l2b(couch_util:get_value("name", Form, "")),
-    Password = ?l2b(couch_util:get_value("password", Form, "")),
-    ?LOG_DEBUG("Attempt Login: ~s",[UserName]),
-    User = case couch_auth_cache:get_user_creds(UserName) of
-        nil -> [];
-        Result -> Result
-    end,
-    UserSalt = couch_util:get_value(<<"salt">>, User, <<>>),
-    case authenticate(Password, User) of
-        true ->
-            % setup the session cookie
-            Secret = ?l2b(ensure_cookie_auth_secret()),
-            CurrentTime = make_cookie_time(),
-            Cookie = cookie_auth_cookie(Req, ?b2l(UserName), <<Secret/binary, UserSalt/binary>>, CurrentTime),
-            % TODO document the "next" feature in Futon
-            {Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
-                nil ->
-                    {200, [Cookie]};
-                Redirect ->
-                    {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
-            end,
-            send_json(Req#httpd{req_body=ReqBody}, Code, Headers,
-                {[
-                    {ok, true},
-                    {name, couch_util:get_value(<<"name">>, User, null)},
-                    {roles, couch_util:get_value(<<"roles">>, User, [])}
-                ]});
-        _Else ->
-            % clear the session
-            Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)),
-            {Code, Headers} = case couch_httpd:qs_value(Req, "fail", nil) of
-                nil ->
-                    {401, [Cookie]};
-                Redirect ->
-                    {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
-            end,
-            send_json(Req, Code, Headers, {[{error, <<"unauthorized">>},{reason, <<"Name or password is incorrect.">>}]})
-    end;
-% get user info
-% GET /_session
-handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) ->
-    Name = UserCtx#user_ctx.name,
-    ForceLogin = couch_httpd:qs_value(Req, "basic", "false"),
-    case {Name, ForceLogin} of
-        {null, "true"} ->
-            throw({unauthorized, <<"Please login.">>});
-        {Name, _} ->
-            send_json(Req, {[
-                % remove this ok
-                {ok, true},
-                {<<"userCtx">>, {[
-                    {name, Name},
-                    {roles, UserCtx#user_ctx.roles}
-                ]}},
-                {info, {[
-                    {authentication_db, ?l2b(couch_config:get("couch_httpd_auth", "authentication_db"))},
-                    {authentication_handlers, [auth_name(H) || H <- couch_httpd:make_fun_spec_strs(
-                            couch_config:get("httpd", "authentication_handlers"))]}
-                ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler, fun(Handler) ->
-                        auth_name(?b2l(Handler))
-                    end)}}
-            ]})
-    end;
-% logout by deleting the session
-handle_session_req(#httpd{method='DELETE'}=Req) ->
-    Cookie = mochiweb_cookies:cookie("AuthSession", "", [{path, "/"}] ++ cookie_scheme(Req)),
-    {Code, Headers} = case couch_httpd:qs_value(Req, "next", nil) of
-        nil ->
-            {200, [Cookie]};
-        Redirect ->
-            {302, [Cookie, {"Location", couch_httpd:absolute_uri(Req, Redirect)}]}
-    end,
-    send_json(Req, Code, Headers, {[{ok, true}]});
-handle_session_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD,POST,DELETE").
-
-maybe_value(_Key, undefined, _Fun) -> [];
-maybe_value(Key, Else, Fun) ->
-    [{Key, Fun(Else)}].
-
-authenticate(Pass, UserProps) ->
-    UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<>>),
-    {PasswordHash, ExpectedHash} =
-        case couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>) of
-        <<"simple">> ->
-            {couch_passwords:simple(Pass, UserSalt),
-            couch_util:get_value(<<"password_sha">>, UserProps, nil)};
-        <<"pbkdf2">> ->
-            Iterations = couch_util:get_value(<<"iterations">>, UserProps, 10000),
-            {couch_passwords:pbkdf2(Pass, UserSalt, Iterations),
-             couch_util:get_value(<<"derived_key">>, UserProps, nil)}
-    end,
-    couch_passwords:verify(PasswordHash, ExpectedHash).
-
-auth_name(String) when is_list(String) ->
-    [_,_,_,_,_,Name|_] = re:split(String, "[\\W_]", [{return, list}]),
-    ?l2b(Name).
-
-make_cookie_time() ->
-    {NowMS, NowS, _} = erlang:now(),
-    NowMS * 1000000 + NowS.
-
-cookie_scheme(#httpd{mochi_req=MochiReq}) ->
-    [{http_only, true}] ++
-    case MochiReq:get(scheme) of
-        http -> [];
-        https -> [{secure, true}]
-    end.
-
-max_age() ->
-    case couch_config:get("couch_httpd_auth", "allow_persistent_cookies", "false") of
-        "false" ->
-            [];
-        "true" ->
-            Timeout = list_to_integer(
-                couch_config:get("couch_httpd_auth", "timeout", "600")),
-            [{max_age, Timeout}]
-    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a278e0db/src/couch_httpd_cors.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_cors.erl b/src/couch_httpd_cors.erl
deleted file mode 100644
index d9462d1..0000000
--- a/src/couch_httpd_cors.erl
+++ /dev/null
@@ -1,351 +0,0 @@
-% Licensed under the Apache License, Version 2.0 (the "License"); you may not
-% use this file except in compliance with the License. You may obtain a copy of
-% the License at
-%
-%   http://www.apache.org/licenses/LICENSE-2.0
-%
-% Unless required by applicable law or agreed to in writing, software
-% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-% License for the specific language governing permissions and limitations under
-% the License.
-
-%% @doc module to handle Cross-Origin Resource Sharing
-%%
-%% This module handles CORS requests and preflight request for
-%% CouchDB. The configuration is done in the ini file.
-%%
-%% This implements http://www.w3.org/TR/cors/
-
-
--module(couch_httpd_cors).
-
--include("couch_db.hrl").
-
--export([is_preflight_request/1, cors_headers/2]).
-
--define(SUPPORTED_HEADERS, "Accept, Accept-Language, Content-Type," ++
-        "Expires, Last-Modified, Pragma, Origin, Content-Length," ++
-        "If-Match, Destination, X-Requested-With, " ++
-        "X-Http-Method-Override, Content-Range").
-
--define(SUPPORTED_METHODS, "GET, HEAD, POST, PUT, DELETE," ++
-        "TRACE, CONNECT, COPY, OPTIONS").
-
-% as defined in http://www.w3.org/TR/cors/#terminology
--define(SIMPLE_HEADERS, ["Cache-Control", "Content-Language",
-        "Content-Type", "Expires", "Last-Modified", "Pragma"]).
--define(ALLOWED_HEADERS, lists:sort(["Server", "Etag",
-        "Accept-Ranges" | ?SIMPLE_HEADERS])).
--define(SIMPLE_CONTENT_TYPE_VALUES, ["application/x-www-form-urlencoded",
-        "multipart/form-data", "text/plain"]).
-
-% TODO: - pick a sane default
--define(CORS_DEFAULT_MAX_AGE, 12345).
-
-%% is_preflight_request/1
-
-% http://www.w3.org/TR/cors/#resource-preflight-requests
-
-is_preflight_request(#httpd{method=Method}=Req) when Method /= 'OPTIONS' ->
-    Req;
-is_preflight_request(Req) ->
-    EnableCors = enable_cors(),
-    is_preflight_request(Req, EnableCors).
-
-is_preflight_request(Req, false) ->
-    Req;
-is_preflight_request(#httpd{mochi_req=MochiReq}=Req, true) ->
-    case preflight_request(MochiReq) of
-    {ok, PreflightHeaders} ->
-        send_preflight_response(Req, PreflightHeaders);
-    _ ->
-        Req
-    end.
-
-
-preflight_request(MochiReq) ->
-    Origin = MochiReq:get_header_value("Origin"),
-    preflight_request(MochiReq, Origin).
-
-preflight_request(MochiReq, undefined) ->
-    % If the Origin header is not present terminate this set of
-    % steps. The request is outside the scope of this specification.
-    % http://www.w3.org/TR/cors/#resource-preflight-requests
-    MochiReq;
-preflight_request(MochiReq, Origin) ->
-    Host = couch_httpd_vhost:host(MochiReq),
-    AcceptedOrigins = get_accepted_origins(Host),
-    AcceptAll = lists:member("*", AcceptedOrigins),
-
-    HandlerFun = fun() ->
-        OriginList = couch_util:to_list(Origin),
-        handle_preflight_request(OriginList, Host, MochiReq)
-    end,
-
-    case AcceptAll of
-    true ->
-        % Always matching is acceptable since the list of
-        % origins can be unbounded.
-        % http://www.w3.org/TR/cors/#resource-preflight-requests
-        HandlerFun();
-    false ->
-        case lists:member(Origin, AcceptedOrigins) of
-        % The Origin header can only contain a single origin as
-        % the user agent will not follow redirects.
-        % http://www.w3.org/TR/cors/#resource-preflight-requests
-        % TODO: Square against multi origin thinger in Security Considerations
-        true ->
-            HandlerFun();
-        false ->
-            % If the value of the Origin header is not a
-            % case-sensitive match for any of the values
-            % in list of origins do not set any additional
-            % headers and terminate this set of steps.
-            % http://www.w3.org/TR/cors/#resource-preflight-requests
-            false
-        end
-    end.
-
-
-handle_preflight_request(Origin, Host, MochiReq) ->
-    %% get supported methods
-    SupportedMethods = split_list(cors_config(Host, "methods",
-                                              ?SUPPORTED_METHODS)),
-
-    % get supported headers
-    AllSupportedHeaders = split_list(cors_config(Host, "headers",
-                                                 ?SUPPORTED_HEADERS)),
-
-    SupportedHeaders = [string:to_lower(H) || H <- AllSupportedHeaders],
-
-    % get max age
-    MaxAge = cors_config(Host, "max_age", ?CORS_DEFAULT_MAX_AGE),
-
-    PreflightHeaders0 = maybe_add_credentials(Origin, Host, [
-        {"Access-Control-Allow-Origin", Origin},
-        {"Access-Control-Max-Age", MaxAge},
-        {"Access-Control-Allow-Methods",
-            string:join(SupportedMethods, ", ")}]),
-
-    case MochiReq:get_header_value("Access-Control-Request-Method") of
-    undefined ->
-        % If there is no Access-Control-Request-Method header
-        % or if parsing failed, do not set any additional headers
-        % and terminate this set of steps. The request is outside
-        % the scope of this specification.
-        % http://www.w3.org/TR/cors/#resource-preflight-requests
-        {ok, PreflightHeaders0};
-    Method ->
-        case lists:member(Method, SupportedMethods) of
-        true ->
-            % method ok , check headers
-            AccessHeaders = MochiReq:get_header_value(
-                    "Access-Control-Request-Headers"),
-            {FinalReqHeaders, ReqHeaders} = case AccessHeaders of
-                undefined -> {"", []};
-                Headers ->
-                    % transform header list in something we
-                    % could check. make sure everything is a
-                    % list
-                    RH = [string:to_lower(H)
-                          || H <- split_headers(Headers)],
-                    {Headers, RH}
-            end,
-            % check if headers are supported
-            case ReqHeaders -- SupportedHeaders of
-            [] ->
-                PreflightHeaders = PreflightHeaders0 ++
-                                   [{"Access-Control-Allow-Headers",
-                                     FinalReqHeaders}],
-                {ok, PreflightHeaders};
-            _ ->
-                false
-            end;
-        false ->
-        % If method is not a case-sensitive match for any of
-        % the values in list of methods do not set any additional
-        % headers and terminate this set of steps.
-        % http://www.w3.org/TR/cors/#resource-preflight-requests
-            false
-        end
-    end.
-
-
-send_preflight_response(#httpd{mochi_req=MochiReq}=Req, Headers) ->
-    couch_httpd:log_request(Req, 204),
-    couch_stats_collector:increment({httpd_status_codes, 204}),
-    Headers1 = couch_httpd:http_1_0_keep_alive(MochiReq, Headers),
-    Headers2 = Headers1 ++ couch_httpd:server_header() ++
-               couch_httpd_auth:cookie_auth_header(Req, Headers1),
-    {ok, MochiReq:respond({204, Headers2, <<>>})}.
-
-
-% cors_headers/1
-
-cors_headers(MochiReq, RequestHeaders) ->
-    EnableCors = enable_cors(),
-    CorsHeaders = do_cors_headers(MochiReq, EnableCors),
-    maybe_apply_cors_headers(CorsHeaders, RequestHeaders).
-
-do_cors_headers(#httpd{mochi_req=MochiReq}, true) ->
-    Host = couch_httpd_vhost:host(MochiReq),
-    AcceptedOrigins = get_accepted_origins(Host),
-    case MochiReq:get_header_value("Origin") of
-    undefined ->
-        % If the Origin header is not present terminate
-        % this set of steps. The request is outside the scope
-        % of this specification.
-        % http://www.w3.org/TR/cors/#resource-processing-model
-        [];
-    Origin ->
-        handle_cors_headers(couch_util:to_list(Origin),
-                            Host, AcceptedOrigins)
-    end;
-do_cors_headers(_MochiReq, false) ->
-    [].
-
-maybe_apply_cors_headers([], RequestHeaders) ->
-    RequestHeaders;
-maybe_apply_cors_headers(CorsHeaders, RequestHeaders0) ->
-    % for each RequestHeader that isn't in SimpleHeaders,
-    % (or Content-Type with SIMPLE_CONTENT_TYPE_VALUES)
-    % append to Access-Control-Expose-Headers
-    % return: RequestHeaders ++ CorsHeaders ++ ACEH
-
-    RequestHeaders = [K || {K,_V} <- RequestHeaders0],
-    ExposedHeaders0 = reduce_headers(RequestHeaders, ?ALLOWED_HEADERS),
-
-    % here we may have not moved Content-Type into ExposedHeaders,
-    % now we need to check whether the Content-Type valus is
-    % in ?SIMPLE_CONTENT_TYPE_VALUES and if it isn’t add Content-
-    % Type to to ExposedHeaders
-    ContentType =  proplists:get_value("Content-Type", RequestHeaders0),
-    IncludeContentType = case ContentType of
-    undefined ->
-        false;
-    _ ->
-        ContentType_ = string:to_lower(ContentType),
-        lists:member(ContentType_, ?SIMPLE_CONTENT_TYPE_VALUES)
-    end,
-    ExposedHeaders = case IncludeContentType of
-    false ->
-        lists:umerge(ExposedHeaders0, ["Content-Type"]);
-    true ->
-        ExposedHeaders0
-    end,
-    CorsHeaders
-    ++ RequestHeaders0
-    ++ [{"Access-Control-Expose-Headers",
-            string:join(ExposedHeaders, ", ")}].
-
-
-reduce_headers(A, B) ->
-    reduce_headers0(A, B, []).
-
-reduce_headers0([], _B, Result) ->
-    lists:sort(Result);
-reduce_headers0([ElmA|RestA], B, Result) ->
-    R = case member_nocase(ElmA, B) of
-    false -> Result;
-    _Else -> [ElmA | Result]
-    end,
-    reduce_headers0(RestA, B, R).
-
-member_nocase(ElmA, List) ->
-    lists:any(fun(ElmB) ->
-        string:to_lower(ElmA) =:= string:to_lower(ElmB)
-    end, List).
-
-handle_cors_headers(_Origin, _Host, []) ->
-    [];
-handle_cors_headers(Origin, Host, AcceptedOrigins) ->
-    AcceptAll = lists:member("*", AcceptedOrigins),
-    case {AcceptAll, lists:member(Origin, AcceptedOrigins)} of
-    {true, _} ->
-        make_cors_header(Origin, Host);
-    {false, true}  ->
-        make_cors_header(Origin, Host);
-    _ ->
-        % If the value of the Origin header is not a
-        % case-sensitive match for any of the values
-        % in list of origins, do not set any additional
-        % headers and terminate this set of steps.
-        % http://www.w3.org/TR/cors/#resource-requests
-        []
-    end.
-
-
-make_cors_header(Origin, Host) ->
-    Headers = [{"Access-Control-Allow-Origin", Origin}],
-    maybe_add_credentials(Origin, Host, Headers).
-
-
-%% util
-
-maybe_add_credentials(Origin, Host, Headers) ->
-    maybe_add_credentials(Headers, allow_credentials(Origin, Host)).
-
-maybe_add_credentials(Headers, false) ->
-    Headers;
-maybe_add_credentials(Headers, true) ->
-    Headers ++ [{"Access-Control-Allow-Credentials", "true"}].
-
-
-allow_credentials("*", _Host) ->
-    false;
-allow_credentials(_Origin, Host) ->
-    Default = get_bool_config("cors", "credentials", false),
-    get_bool_config(cors_section(Host), "credentials", Default).
-
-
-
-cors_config(Host, Key, Default) ->
-    couch_config:get(cors_section(Host), Key,
-                     couch_config:get("cors", Key, Default)).
-
-cors_section(Host0) ->
-    {Host, _Port} = split_host_port(Host0),
-    "cors:" ++ Host.
-
-enable_cors() ->
-    get_bool_config("httpd", "enable_cors", false).
-
-get_bool_config(Section, Key, Default) ->
-    case couch_config:get(Section, Key) of
-    undefined ->
-        Default;
-    "true" ->
-        true;
-    "false" ->
-        false
-    end.
-
-get_accepted_origins(Host) ->
-    split_list(cors_config(Host, "origins", [])).
-
-split_list(S) ->
-    re:split(S, "\\s*,\\s*", [trim, {return, list}]).
-
-split_headers(H) ->
-    re:split(H, ",\\s*", [{return,list}, trim]).
-
-split_host_port(HostAsString) ->
-    % split at semicolon ":"
-    Split = string:rchr(HostAsString, $:),
-    split_host_port(HostAsString, Split).
-
-split_host_port(HostAsString, 0) ->
-    % no semicolon
-    {HostAsString, '*'};
-split_host_port(HostAsString, N) ->
-    HostPart = string:substr(HostAsString, 1, N-1),
-    % parse out port
-    % is there a nicer way?
-    case (catch erlang:list_to_integer(string:substr(HostAsString,
-                    N+1, length(HostAsString)))) of
-    {'EXIT', _} ->
-        {HostAsString, '*'};
-    Port ->
-        {HostPart, Port}
-    end.