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/05 00:44:22 UTC

[09/44] Remove src/couch

http://git-wip-us.apache.org/repos/asf/couchdb/blob/ed98610c/src/couch/src/couch_httpd_auth.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
deleted file mode 100644
index a747869..0000000
--- a/src/couch/src/couch_httpd_auth.erl
+++ /dev/null
@@ -1,376 +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_lib("couch/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_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 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_authentification_handler(Req) ->
-    case proxy_auth_user(Req) of
-        nil -> Req;
-        Req2 -> Req2
-    end.
-    
-proxy_auth_user(Req) ->
-    XHeaderUserName = config:get("couch_httpd_auth", "x_auth_username",
-                                "X-Auth-CouchDB-UserName"),
-    XHeaderRoles = config:get("couch_httpd_auth", "x_auth_roles",
-                                "X-Auth-CouchDB-Roles"),
-    XHeaderToken = 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 config:get("couch_httpd_auth", "proxy_use_secret", "false") of
-                "true" ->
-                    case 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 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(
-                    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 config:get("couch_httpd_auth", "secret", nil) of
-        nil ->
-            NewSecret = ?b2l(couch_uuids:random()),
-            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(config:get("couch_httpd_auth", "authentication_db"))},
-                    {authentication_handlers, [auth_name(H) || H <- couch_httpd:make_fun_spec_strs(
-                            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 config:get("couch_httpd_auth", "allow_persistent_cookies", "false") of
-        "false" ->
-            [];
-        "true" ->
-            Timeout = list_to_integer(
-                config:get("couch_httpd_auth", "timeout", "600")),
-            [{max_age, Timeout}]
-    end.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/ed98610c/src/couch/src/couch_httpd_cors.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_httpd_cors.erl b/src/couch/src/couch_httpd_cors.erl
deleted file mode 100644
index d98357a..0000000
--- a/src/couch/src/couch_httpd_cors.erl
+++ /dev/null
@@ -1,343 +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 CROSS requests and preflight request for a
-%% couchdb Node. The config is done in the ini file.
-
-
--module(couch_httpd_cors).
-
--include_lib("couch/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(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, ?SIMPLE_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 = string:to_lower(
-        proplists:get_value("Content-Type", RequestHeaders0)),
-
-    IncludeContentType = lists:member(ContentType, ?SIMPLE_CONTENT_TYPE_VALUES),
-    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) ->
-    Result;
-reduce_headers0([ElmA|RestA], B, Result) ->
-    R = case member_nocase(ElmA, B) of
-    true -> 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) ->
-    config:get(cors_section(Host), Key,
-                     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 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.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/ed98610c/src/couch/src/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_httpd_db.erl b/src/couch/src/couch_httpd_db.erl
deleted file mode 100644
index 50fba6c..0000000
--- a/src/couch/src/couch_httpd_db.erl
+++ /dev/null
@@ -1,1210 +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_db).
--include_lib("couch/include/couch_db.hrl").
-
--export([handle_request/1, handle_compact_req/2, handle_design_req/2,
-    db_req/2, couch_doc_open/4,handle_changes_req/2,
-    update_doc_result_to_json/1, update_doc_result_to_json/2,
-    handle_design_info_req/3]).
-
--import(couch_httpd,
-    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
-    start_json_response/2,send_chunk/2,last_chunk/1,end_json_response/1,
-    start_chunked_response/3, absolute_uri/2, send/2,
-    start_response_length/4, send_error/4]).
-
--record(doc_query_args, {
-    options = [],
-    rev = nil,
-    open_revs = [],
-    update_type = interactive_edit,
-    atts_since = nil
-}).
-
-% Database request handlers
-handle_request(#httpd{path_parts=[DbName|RestParts],method=Method,
-        db_url_handlers=DbUrlHandlers}=Req)->
-    case {Method, RestParts} of
-    {'PUT', []} ->
-        create_db_req(Req, DbName);
-    {'DELETE', []} ->
-        % if we get ?rev=... the user is using a faulty script where the
-        % document id is empty by accident. Let them recover safely.
-        case couch_httpd:qs_value(Req, "rev", false) of
-            false -> delete_db_req(Req, DbName);
-            _Rev -> throw({bad_request,
-                "You tried to DELETE a database with a ?=rev parameter. "
-                ++ "Did you mean to DELETE a document instead?"})
-        end;
-    {_, []} ->
-        do_db_req(Req, fun db_req/2);
-    {_, [SecondPart|_]} ->
-        Handler = couch_util:dict_find(SecondPart, DbUrlHandlers, fun db_req/2),
-        do_db_req(Req, Handler)
-    end.
-
-handle_changes_req(#httpd{method='POST'}=Req, Db) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
-    handle_changes_req1(Req, Db);
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "GET,HEAD,POST").
-
-handle_changes_req1(Req, #db{name=DbName}=Db) ->
-    AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")),
-    case AuthDbName of
-    DbName ->
-        % in the authentication database, _changes is admin-only.
-        ok = couch_db:check_is_admin(Db);
-    _Else ->
-        % on other databases, _changes is free for all.
-        ok
-    end,
-    handle_changes_req2(Req, Db).
-
-handle_changes_req2(Req, Db) ->
-    MakeCallback = fun(Resp) ->
-        fun({change, {ChangeProp}=Change, _}, "eventsource") ->
-            Seq = proplists:get_value(<<"seq">>, ChangeProp),
-            send_chunk(Resp, ["data: ", ?JSON_ENCODE(Change),
-                              "\n", "id: ", ?JSON_ENCODE(Seq),
-                              "\n\n"]);
-        ({change, Change, _}, "continuous") ->
-            send_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]);
-        ({change, Change, Prepend}, _) ->
-            send_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]);
-        (start, "eventsource") ->
-            ok;
-        (start, "continuous") ->
-            ok;
-        (start, _) ->
-            send_chunk(Resp, "{\"results\":[\n");
-        ({stop, _EndSeq}, "eventsource") ->
-            end_json_response(Resp);
-        ({stop, EndSeq}, "continuous") ->
-            send_chunk(
-                Resp,
-                [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]
-            ),
-            end_json_response(Resp);
-        ({stop, EndSeq}, _) ->
-            send_chunk(
-                Resp,
-                io_lib:format("\n],\n\"last_seq\":~w}\n", [EndSeq])
-            ),
-            end_json_response(Resp);
-        (timeout, _) ->
-            send_chunk(Resp, "\n")
-        end
-    end,
-    ChangesArgs = parse_changes_query(Req, Db),
-    ChangesFun = couch_changes:handle_changes(ChangesArgs, Req, Db),
-    WrapperFun = case ChangesArgs#changes_args.feed of
-    "normal" ->
-        {ok, Info} = couch_db:get_db_info(Db),
-        CurrentEtag = couch_httpd:make_etag(Info),
-        fun(FeedChangesFun) ->
-            couch_httpd:etag_respond(
-                Req,
-                CurrentEtag,
-                fun() ->
-                    {ok, Resp} = couch_httpd:start_json_response(
-                         Req, 200, [{"ETag", CurrentEtag}]
-                    ),
-                    FeedChangesFun(MakeCallback(Resp))
-                end
-            )
-        end;
-    "eventsource" ->
-        Headers = [
-            {"Content-Type", "text/event-stream"},
-            {"Cache-Control", "no-cache"}
-        ],
-        {ok, Resp} = couch_httpd:start_chunked_response(Req, 200, Headers),
-        fun(FeedChangesFun) ->
-            FeedChangesFun(MakeCallback(Resp))
-        end;
-    _ ->
-        % "longpoll" or "continuous"
-        {ok, Resp} = couch_httpd:start_json_response(Req, 200),
-        fun(FeedChangesFun) ->
-            FeedChangesFun(MakeCallback(Resp))
-        end
-    end,
-    couch_stats_collector:increment(
-        {httpd, clients_requesting_changes}
-    ),
-    try
-        WrapperFun(ChangesFun)
-    after
-    couch_stats_collector:decrement(
-        {httpd, clients_requesting_changes}
-    )
-    end.
-
-handle_compact_req(#httpd{method='POST'}=Req, Db) ->
-    case Req#httpd.path_parts of
-        [_DbName, <<"_compact">>] ->
-            ok = couch_db:check_is_admin(Db),
-            couch_httpd:validate_ctype(Req, "application/json"),
-            {ok, _} = couch_db:start_compact(Db),
-            send_json(Req, 202, {[{ok, true}]});
-        [_DbName, <<"_compact">>, DesignName | _] ->
-            DesignId = <<"_design/", DesignName/binary>>,
-            DDoc = couch_httpd_db:couch_doc_open(
-                Db, DesignId, nil, [ejson_body]
-            ),
-            couch_mrview_http:handle_compact_req(Req, Db, DDoc)
-    end;
-
-handle_compact_req(Req, _Db) ->
-    send_method_not_allowed(Req, "POST").
-
-
-handle_design_req(#httpd{
-        path_parts=[_DbName, _Design, DesignName, <<"_",_/binary>> = Action | _Rest],
-        design_url_handlers = DesignUrlHandlers
-    }=Req, Db) ->
-    case couch_db:is_system_db(Db) of
-    true ->
-        case (catch couch_db:check_is_admin(Db)) of
-        ok -> ok;
-        _ ->
-            throw({forbidden, <<"Only admins can access design document",
-                " actions for system databases.">>})
-        end;
-    false -> ok
-    end,
-
-    % load ddoc
-    DesignId = <<"_design/", DesignName/binary>>,
-    DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, [ejson_body]),
-    Handler = couch_util:dict_find(Action, DesignUrlHandlers, fun(_, _, _) ->
-        throw({not_found, <<"missing handler: ", Action/binary>>})
-    end),
-    Handler(Req, Db, DDoc);
-
-handle_design_req(Req, Db) ->
-    db_req(Req, Db).
-
-handle_design_info_req(#httpd{
-            method='GET',
-            path_parts=[_DbName, _Design, DesignName, _]
-        }=Req, Db, _DDoc) ->
-    DesignId = <<"_design/", DesignName/binary>>,
-    DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, [ejson_body]),
-    couch_mrview_http:handle_info_req(Req, Db, DDoc).
-
-create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
-    ok = couch_httpd:verify_is_server_admin(Req),
-    case couch_server:create(DbName, [{user_ctx, UserCtx}]) of
-    {ok, Db} ->
-        couch_db:close(Db),
-        DbUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)),
-        send_json(Req, 201, [{"Location", DbUrl}], {[{ok, true}]});
-    Error ->
-        throw(Error)
-    end.
-
-delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
-    ok = couch_httpd:verify_is_server_admin(Req),
-    Options = case couch_httpd:qs_value(Req, "sync") of
-        "true" -> [sync, {user_ctx, UserCtx}];
-        _ -> [{user_ctx, UserCtx}]
-    end,
-    case couch_server:delete(DbName, Options) of
-    ok ->
-        send_json(Req, 200, {[{ok, true}]});
-    Error ->
-        throw(Error)
-    end.
-
-do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
-    case couch_db:open(DbName, [{user_ctx, UserCtx}]) of
-    {ok, Db} ->
-        try
-            Fun(Req, Db)
-        after
-            catch couch_db:close(Db)
-        end;
-    Error ->
-        throw(Error)
-    end.
-
-db_req(#httpd{method='GET',path_parts=[_DbName]}=Req, Db) ->
-    {ok, DbInfo} = couch_db:get_db_info(Db),
-    send_json(Req, {DbInfo});
-
-db_req(#httpd{method='POST',path_parts=[_DbName]}=Req, Db) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)),
-    validate_attachment_names(Doc),
-    Doc2 = case Doc#doc.id of
-        <<"">> ->
-            Doc#doc{id=couch_uuids:new(), revs={0, []}};
-        _ ->
-            Doc
-    end,
-    DocId = Doc2#doc.id,
-    update_doc(Req, Db, DocId, Doc2);
-
-db_req(#httpd{path_parts=[_DbName]}=Req, _Db) ->
-    send_method_not_allowed(Req, "DELETE,GET,HEAD,POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_ensure_full_commit">>]}=Req, Db) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    UpdateSeq = couch_db:get_update_seq(Db),
-    CommittedSeq = couch_db:get_committed_update_seq(Db),
-    {ok, StartTime} =
-    case couch_httpd:qs_value(Req, "seq") of
-    undefined ->
-        couch_db:ensure_full_commit(Db);
-    RequiredStr ->
-        RequiredSeq = list_to_integer(RequiredStr),
-        if RequiredSeq > UpdateSeq ->
-            throw({bad_request,
-                "can't do a full commit ahead of current update_seq"});
-        RequiredSeq > CommittedSeq ->
-            couch_db:ensure_full_commit(Db);
-        true ->
-            {ok, Db#db.instance_start_time}
-        end
-    end,
-    send_json(Req, 201, {[
-        {ok, true},
-        {instance_start_time, StartTime}
-    ]});
-
-db_req(#httpd{path_parts=[_,<<"_ensure_full_commit">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
-    couch_stats_collector:increment({httpd, bulk_requests}),
-    couch_httpd:validate_ctype(Req, "application/json"),
-    {JsonProps} = couch_httpd:json_body_obj(Req),
-    case couch_util:get_value(<<"docs">>, JsonProps) of
-    undefined ->
-        send_error(Req, 400, <<"bad_request">>, <<"Missing JSON list of 'docs'">>);
-    DocsArray ->
-        case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
-        "true" ->
-            Options = [full_commit];
-        "false" ->
-            Options = [delay_commit];
-        _ ->
-            Options = []
-        end,
-        case couch_util:get_value(<<"new_edits">>, JsonProps, true) of
-        true ->
-            Docs = lists:map(
-                fun({ObjProps} = JsonObj) ->
-                    Doc = couch_doc:from_json_obj(JsonObj),
-                    validate_attachment_names(Doc),
-                    Id = case Doc#doc.id of
-                        <<>> -> couch_uuids:new();
-                        Id0 -> Id0
-                    end,
-                    case couch_util:get_value(<<"_rev">>, ObjProps) of
-                    undefined ->
-                       Revs = {0, []};
-                    Rev  ->
-                        {Pos, RevId} = couch_doc:parse_rev(Rev),
-                        Revs = {Pos, [RevId]}
-                    end,
-                    Doc#doc{id=Id,revs=Revs}
-                end,
-                DocsArray),
-            Options2 =
-            case couch_util:get_value(<<"all_or_nothing">>, JsonProps) of
-            true  -> [all_or_nothing|Options];
-            _ -> Options
-            end,
-            case couch_db:update_docs(Db, Docs, Options2) of
-            {ok, Results} ->
-                % output the results
-                DocResults = lists:zipwith(fun update_doc_result_to_json/2,
-                    Docs, Results),
-                send_json(Req, 201, DocResults);
-            {aborted, Errors} ->
-                ErrorsJson =
-                    lists:map(fun update_doc_result_to_json/1, Errors),
-                send_json(Req, 417, ErrorsJson)
-            end;
-        false ->
-            Docs = lists:map(fun(JsonObj) ->
-                    Doc = couch_doc:from_json_obj(JsonObj),
-                    validate_attachment_names(Doc),
-                    Doc
-                end, DocsArray),
-            {ok, Errors} = couch_db:update_docs(Db, Docs, Options, replicated_changes),
-            ErrorsJson =
-                lists:map(fun update_doc_result_to_json/1, Errors),
-            send_json(Req, 201, ErrorsJson)
-        end
-    end;
-db_req(#httpd{path_parts=[_,<<"_bulk_docs">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_purge">>]}=Req, Db) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    {IdsRevs} = couch_httpd:json_body_obj(Req),
-    IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],
-
-    case couch_db:purge_docs(Db, IdsRevs2) of
-    {ok, PurgeSeq, PurgedIdsRevs} ->
-        PurgedIdsRevs2 = [{Id, couch_doc:revs_to_strs(Revs)} || {Id, Revs} <- PurgedIdsRevs],
-        send_json(Req, 200, {[{<<"purge_seq">>, PurgeSeq}, {<<"purged">>, {PurgedIdsRevs2}}]});
-    Error ->
-        throw(Error)
-    end;
-
-db_req(#httpd{path_parts=[_,<<"_purge">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
-    {JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
-    JsonDocIdRevs2 = [{Id, [couch_doc:parse_rev(RevStr) || RevStr <- RevStrs]} || {Id, RevStrs} <- JsonDocIdRevs],
-    {ok, Results} = couch_db:get_missing_revs(Db, JsonDocIdRevs2),
-    Results2 = [{Id, couch_doc:revs_to_strs(Revs)} || {Id, Revs, _} <- Results],
-    send_json(Req, {[
-        {missing_revs, {Results2}}
-    ]});
-
-db_req(#httpd{path_parts=[_,<<"_missing_revs">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_revs_diff">>]}=Req, Db) ->
-    {JsonDocIdRevs} = couch_httpd:json_body_obj(Req),
-    JsonDocIdRevs2 =
-        [{Id, couch_doc:parse_revs(RevStrs)} || {Id, RevStrs} <- JsonDocIdRevs],
-    {ok, Results} = couch_db:get_missing_revs(Db, JsonDocIdRevs2),
-    Results2 =
-    lists:map(fun({Id, MissingRevs, PossibleAncestors}) ->
-        {Id,
-            {[{missing, couch_doc:revs_to_strs(MissingRevs)}] ++
-                if PossibleAncestors == [] ->
-                    [];
-                true ->
-                    [{possible_ancestors,
-                        couch_doc:revs_to_strs(PossibleAncestors)}]
-                end}}
-    end, Results),
-    send_json(Req, {Results2});
-
-db_req(#httpd{path_parts=[_,<<"_revs_diff">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "POST");
-
-db_req(#httpd{method='PUT',path_parts=[_,<<"_security">>]}=Req, Db) ->
-    SecObj = couch_httpd:json_body(Req),
-    ok = couch_db:set_security(Db, SecObj),
-    send_json(Req, {[{<<"ok">>, true}]});
-
-db_req(#httpd{method='GET',path_parts=[_,<<"_security">>]}=Req, Db) ->
-    send_json(Req, couch_db:get_security(Db));
-
-db_req(#httpd{path_parts=[_,<<"_security">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "PUT,GET");
-
-db_req(#httpd{method='PUT',path_parts=[_,<<"_revs_limit">>]}=Req,
-        Db) ->
-    Limit = couch_httpd:json_body(Req),
-   case is_integer(Limit) of
-   true ->
-       ok = couch_db:set_revs_limit(Db, Limit),
-       send_json(Req, {[{<<"ok">>, true}]});
-   false ->
-       throw({bad_request, <<"Rev limit has to be an integer">>})
-   end;
-
-db_req(#httpd{method='GET',path_parts=[_,<<"_revs_limit">>]}=Req, Db) ->
-    send_json(Req, couch_db:get_revs_limit(Db));
-
-db_req(#httpd{path_parts=[_,<<"_revs_limit">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "PUT,GET");
-
-% Special case to enable using an unencoded slash in the URL of design docs,
-% as slashes in document IDs must otherwise be URL encoded.
-db_req(#httpd{method='GET',mochi_req=MochiReq, path_parts=[DbName,<<"_design/",_/binary>>|_]}=Req, _Db) ->
-    PathFront = "/" ++ couch_httpd:quote(binary_to_list(DbName)) ++ "/",
-    [_|PathTail] = re:split(MochiReq:get(raw_path), "_design%2F",
-        [{return, list}]),
-    couch_httpd:send_redirect(Req, PathFront ++ "_design/" ++
-        mochiweb_util:join(PathTail, "_design%2F"));
-
-db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name]}=Req, Db) ->
-    db_doc_req(Req, Db, <<"_design/",Name/binary>>);
-
-db_req(#httpd{path_parts=[_DbName,<<"_design">>,Name|FileNameParts]}=Req, Db) ->
-    db_attachment_req(Req, Db, <<"_design/",Name/binary>>, FileNameParts);
-
-
-% Special case to allow for accessing local documents without %2F
-% encoding the docid. Throws out requests that don't have the second
-% path part or that specify an attachment name.
-db_req(#httpd{path_parts=[_DbName, <<"_local">>]}, _Db) ->
-    throw({bad_request, <<"Invalid _local document id.">>});
-
-db_req(#httpd{path_parts=[_DbName, <<"_local/">>]}, _Db) ->
-    throw({bad_request, <<"Invalid _local document id.">>});
-
-db_req(#httpd{path_parts=[_DbName, <<"_local">>, Name]}=Req, Db) ->
-    db_doc_req(Req, Db, <<"_local/", Name/binary>>);
-
-db_req(#httpd{path_parts=[_DbName, <<"_local">> | _Rest]}, _Db) ->
-    throw({bad_request, <<"_local documents do not accept attachments.">>});
-
-db_req(#httpd{path_parts=[_, DocId]}=Req, Db) ->
-    db_doc_req(Req, Db, DocId);
-
-db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) ->
-    db_attachment_req(Req, Db, DocId, FileNameParts).
-
-db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) ->
-    % check for the existence of the doc to handle the 404 case.
-    couch_doc_open(Db, DocId, nil, []),
-    case couch_httpd:qs_value(Req, "rev") of
-    undefined ->
-        update_doc(Req, Db, DocId,
-                couch_doc_from_req(Req, DocId, {[{<<"_deleted">>,true}]}));
-    Rev ->
-        update_doc(Req, Db, DocId,
-                couch_doc_from_req(Req, DocId,
-                    {[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]}))
-    end;
-
-db_doc_req(#httpd{method = 'GET', mochi_req = MochiReq} = Req, Db, DocId) ->
-    #doc_query_args{
-        rev = Rev,
-        open_revs = Revs,
-        options = Options1,
-        atts_since = AttsSince
-    } = parse_doc_query(Req),
-    Options = case AttsSince of
-    nil ->
-        Options1;
-    RevList when is_list(RevList) ->
-        [{atts_since, RevList}, attachments | Options1]
-    end,
-    case Revs of
-    [] ->
-        Doc = couch_doc_open(Db, DocId, Rev, Options),
-        send_doc(Req, Doc, Options);
-    _ ->
-        {ok, Results} = couch_db:open_doc_revs(Db, DocId, Revs, Options),
-        case MochiReq:accepts_content_type("multipart/mixed") of
-        false ->
-            {ok, Resp} = start_json_response(Req, 200),
-            send_chunk(Resp, "["),
-            % We loop through the docs. The first time through the separator
-            % is whitespace, then a comma on subsequent iterations.
-            lists:foldl(
-                fun(Result, AccSeparator) ->
-                    case Result of
-                    {ok, Doc} ->
-                        JsonDoc = couch_doc:to_json_obj(Doc, Options),
-                        Json = ?JSON_ENCODE({[{ok, JsonDoc}]}),
-                        send_chunk(Resp, AccSeparator ++ Json);
-                    {{not_found, missing}, RevId} ->
-                        RevStr = couch_doc:rev_to_str(RevId),
-                        Json = ?JSON_ENCODE({[{"missing", RevStr}]}),
-                        send_chunk(Resp, AccSeparator ++ Json)
-                    end,
-                    "," % AccSeparator now has a comma
-                end,
-                "", Results),
-            send_chunk(Resp, "]"),
-            end_json_response(Resp);
-        true ->
-            send_docs_multipart(Req, Results, Options)
-        end
-    end;
-
-
-db_doc_req(#httpd{method='POST'}=Req, Db, DocId) ->
-    couch_httpd:validate_referer(Req),
-    couch_doc:validate_docid(DocId),
-    couch_httpd:validate_ctype(Req, "multipart/form-data"),
-    Form = couch_httpd:parse_form(Req),
-    case couch_util:get_value("_doc", Form) of
-    undefined ->
-        Rev = couch_doc:parse_rev(couch_util:get_value("_rev", Form)),
-        {ok, [{ok, Doc}]} = couch_db:open_doc_revs(Db, DocId, [Rev], []);
-    Json ->
-        Doc = couch_doc_from_req(Req, DocId, ?JSON_DECODE(Json))
-    end,
-    UpdatedAtts = [
-        #att{name=validate_attachment_name(Name),
-            type=list_to_binary(ContentType),
-            data=Content} ||
-        {Name, {ContentType, _}, Content} <-
-        proplists:get_all_values("_attachments", Form)
-    ],
-    #doc{atts=OldAtts} = Doc,
-    OldAtts2 = lists:flatmap(
-        fun(#att{name=OldName}=Att) ->
-            case [1 || A <- UpdatedAtts, A#att.name == OldName] of
-            [] -> [Att]; % the attachment wasn't in the UpdatedAtts, return it
-            _ -> [] % the attachment was in the UpdatedAtts, drop it
-            end
-        end, OldAtts),
-    NewDoc = Doc#doc{
-        atts = UpdatedAtts ++ OldAtts2
-    },
-    update_doc(Req, Db, DocId, NewDoc);
-
-db_doc_req(#httpd{method='PUT'}=Req, Db, DocId) ->
-    couch_doc:validate_docid(DocId),
-
-    case couch_util:to_list(couch_httpd:header_value(Req, "Content-Type")) of
-    ("multipart/related;" ++ _) = ContentType ->
-        {ok, Doc0, WaitFun, Parser} = couch_doc:doc_from_multi_part_stream(
-            ContentType, fun() -> receive_request_data(Req) end),
-        Doc = couch_doc_from_req(Req, DocId, Doc0),
-        try
-            Result = update_doc(Req, Db, DocId, Doc),
-            WaitFun(),
-            Result
-        catch throw:Err ->
-            % Document rejected by a validate_doc_update function.
-            couch_doc:abort_multi_part_stream(Parser),
-            throw(Err)
-        end;
-    _Else ->
-        Body = couch_httpd:json_body(Req),
-        Doc = couch_doc_from_req(Req, DocId, Body),
-        update_doc(Req, Db, DocId, Doc)
-    end;
-
-db_doc_req(#httpd{method='COPY'}=Req, Db, SourceDocId) ->
-    SourceRev =
-    case extract_header_rev(Req, couch_httpd:qs_value(Req, "rev")) of
-        missing_rev -> nil;
-        Rev -> Rev
-    end,
-    {TargetDocId, TargetRevs} = parse_copy_destination_header(Req),
-    % open old doc
-    Doc = couch_doc_open(Db, SourceDocId, SourceRev, []),
-    % save new doc
-    update_doc(Req, Db, TargetDocId, Doc#doc{id=TargetDocId, revs=TargetRevs});
-
-db_doc_req(Req, _Db, _DocId) ->
-    send_method_not_allowed(Req, "DELETE,GET,HEAD,POST,PUT,COPY").
-
-
-send_doc(Req, Doc, Options) ->
-    case Doc#doc.meta of
-    [] ->
-        DiskEtag = couch_httpd:doc_etag(Doc),
-        % output etag only when we have no meta
-        couch_httpd:etag_respond(Req, DiskEtag, fun() ->
-            send_doc_efficiently(Req, Doc, [{"ETag", DiskEtag}], Options)
-        end);
-    _ ->
-        send_doc_efficiently(Req, Doc, [], Options)
-    end.
-
-
-send_doc_efficiently(Req, #doc{atts=[]}=Doc, Headers, Options) ->
-        send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options));
-send_doc_efficiently(#httpd{mochi_req = MochiReq} = Req,
-    #doc{atts = Atts} = Doc, Headers, Options) ->
-    case lists:member(attachments, Options) of
-    true ->
-        case MochiReq:accepts_content_type("multipart/related") of
-        false ->
-            send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options));
-        true ->
-            Boundary = couch_uuids:random(),
-            JsonBytes = ?JSON_ENCODE(couch_doc:to_json_obj(Doc,
-                    [attachments, follows, att_encoding_info | Options])),
-            {ContentType, Len} = couch_doc:len_doc_to_multi_part_stream(
-                    Boundary,JsonBytes, Atts, true),
-            CType = {<<"Content-Type">>, ContentType},
-            {ok, Resp} = start_response_length(Req, 200, [CType|Headers], Len),
-            couch_doc:doc_to_multi_part_stream(Boundary,JsonBytes,Atts,
-                    fun(Data) -> couch_httpd:send(Resp, Data) end, true)
-        end;
-    false ->
-        send_json(Req, 200, Headers, couch_doc:to_json_obj(Doc, Options))
-    end.
-
-send_docs_multipart(Req, Results, Options1) ->
-    OuterBoundary = couch_uuids:random(),
-    InnerBoundary = couch_uuids:random(),
-    Options = [attachments, follows, att_encoding_info | Options1],
-    CType = {"Content-Type",
-        "multipart/mixed; boundary=\"" ++ ?b2l(OuterBoundary) ++ "\""},
-    {ok, Resp} = start_chunked_response(Req, 200, [CType]),
-    couch_httpd:send_chunk(Resp, <<"--", OuterBoundary/binary>>),
-    lists:foreach(
-        fun({ok, #doc{atts=Atts}=Doc}) ->
-            JsonBytes = ?JSON_ENCODE(couch_doc:to_json_obj(Doc, Options)),
-            {ContentType, _Len} = couch_doc:len_doc_to_multi_part_stream(
-                    InnerBoundary, JsonBytes, Atts, true),
-            couch_httpd:send_chunk(Resp, <<"\r\nContent-Type: ",
-                    ContentType/binary, "\r\n\r\n">>),
-            couch_doc:doc_to_multi_part_stream(InnerBoundary, JsonBytes, Atts,
-                    fun(Data) -> couch_httpd:send_chunk(Resp, Data)
-                    end, true),
-             couch_httpd:send_chunk(Resp, <<"\r\n--", OuterBoundary/binary>>);
-        ({{not_found, missing}, RevId}) ->
-             RevStr = couch_doc:rev_to_str(RevId),
-             Json = ?JSON_ENCODE({[{"missing", RevStr}]}),
-             couch_httpd:send_chunk(Resp,
-                [<<"\r\nContent-Type: application/json; error=\"true\"\r\n\r\n">>,
-                Json,
-                <<"\r\n--", OuterBoundary/binary>>])
-         end, Results),
-    couch_httpd:send_chunk(Resp, <<"--">>),
-    couch_httpd:last_chunk(Resp).
-
-send_ranges_multipart(Req, ContentType, Len, Att, Ranges) ->
-    Boundary = couch_uuids:random(),
-    CType = {"Content-Type",
-        "multipart/byteranges; boundary=\"" ++ ?b2l(Boundary) ++ "\""},
-    {ok, Resp} = start_chunked_response(Req, 206, [CType]),
-    couch_httpd:send_chunk(Resp, <<"--", Boundary/binary>>),
-    lists:foreach(fun({From, To}) ->
-        ContentRange = make_content_range(From, To, Len),
-        couch_httpd:send_chunk(Resp,
-            <<"\r\nContent-Type: ", ContentType/binary, "\r\n",
-            "Content-Range: ", ContentRange/binary, "\r\n",
-           "\r\n">>),
-        couch_doc:range_att_foldl(Att, From, To + 1,
-            fun(Seg, _) -> send_chunk(Resp, Seg) end, {ok, Resp}),
-        couch_httpd:send_chunk(Resp, <<"\r\n--", Boundary/binary>>)
-    end, Ranges),
-    couch_httpd:send_chunk(Resp, <<"--">>),
-    couch_httpd:last_chunk(Resp),
-    {ok, Resp}.
-
-receive_request_data(Req) ->
-    receive_request_data(Req, couch_httpd:body_length(Req)).
-
-receive_request_data(Req, LenLeft) when LenLeft > 0 ->
-    Len = erlang:min(4096, LenLeft),
-    Data = couch_httpd:recv(Req, Len),
-    {Data, fun() -> receive_request_data(Req, LenLeft - iolist_size(Data)) end};
-receive_request_data(_Req, _) ->
-    throw(<<"expected more data">>).
-
-make_content_range(From, To, Len) ->
-    ?l2b(io_lib:format("bytes ~B-~B/~B", [From, To, Len])).
-
-update_doc_result_to_json({{Id, Rev}, Error}) ->
-        {_Code, Err, Msg} = couch_httpd:error_info(Error),
-        {[{id, Id}, {rev, couch_doc:rev_to_str(Rev)},
-            {error, Err}, {reason, Msg}]}.
-
-update_doc_result_to_json(#doc{id=DocId}, Result) ->
-    update_doc_result_to_json(DocId, Result);
-update_doc_result_to_json(DocId, {ok, NewRev}) ->
-    {[{ok, true}, {id, DocId}, {rev, couch_doc:rev_to_str(NewRev)}]};
-update_doc_result_to_json(DocId, Error) ->
-    {_Code, ErrorStr, Reason} = couch_httpd:error_info(Error),
-    {[{id, DocId}, {error, ErrorStr}, {reason, Reason}]}.
-
-
-update_doc(Req, Db, DocId, #doc{deleted=false}=Doc) ->
-    Loc = absolute_uri(Req, "/" ++ ?b2l(Db#db.name) ++ "/" ++ ?b2l(DocId)),
-    update_doc(Req, Db, DocId, Doc, [{"Location", Loc}]);
-update_doc(Req, Db, DocId, Doc) ->
-    update_doc(Req, Db, DocId, Doc, []).
-
-update_doc(Req, Db, DocId, Doc, Headers) ->
-    #doc_query_args{
-        update_type = UpdateType
-    } = parse_doc_query(Req),
-    update_doc(Req, Db, DocId, Doc, Headers, UpdateType).
-
-update_doc(Req, Db, DocId, #doc{deleted=Deleted}=Doc, Headers, UpdateType) ->
-    case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
-    "true" ->
-        Options = [full_commit];
-    "false" ->
-        Options = [delay_commit];
-    _ ->
-        Options = []
-    end,
-    case couch_httpd:qs_value(Req, "batch") of
-    "ok" ->
-        % async batching
-        spawn(fun() ->
-                case catch(couch_db:update_doc(Db, Doc, Options, UpdateType)) of
-                {ok, _} -> ok;
-                Error ->
-                    ?LOG_INFO("Batch doc error (~s): ~p",[DocId, Error])
-                end
-            end),
-        send_json(Req, 202, Headers, {[
-            {ok, true},
-            {id, DocId}
-        ]});
-    _Normal ->
-        % normal
-        {ok, NewRev} = couch_db:update_doc(Db, Doc, Options, UpdateType),
-        NewRevStr = couch_doc:rev_to_str(NewRev),
-        ResponseHeaders = [{"ETag", <<"\"", NewRevStr/binary, "\"">>}] ++ Headers,
-        send_json(Req,
-            if Deleted orelse Req#httpd.method == 'DELETE' -> 200;
-            true -> 201 end,
-            ResponseHeaders, {[
-                {ok, true},
-                {id, DocId},
-                {rev, NewRevStr}]})
-    end.
-
-couch_doc_from_req(Req, DocId, #doc{revs=Revs}=Doc) ->
-    validate_attachment_names(Doc),
-    Rev = case couch_httpd:qs_value(Req, "rev") of
-    undefined ->
-        undefined;
-    QSRev ->
-        couch_doc:parse_rev(QSRev)
-    end,
-    Revs2 =
-    case Revs of
-    {Start, [RevId|_]} ->
-        if Rev /= undefined andalso Rev /= {Start, RevId} ->
-            throw({bad_request, "Document rev from request body and query "
-                   "string have different values"});
-        true ->
-            case extract_header_rev(Req, {Start, RevId}) of
-            missing_rev -> {0, []};
-            _ -> Revs
-            end
-        end;
-    _ ->
-        case extract_header_rev(Req, Rev) of
-        missing_rev -> {0, []};
-        {Pos, RevId2} -> {Pos, [RevId2]}
-        end
-    end,
-    Doc#doc{id=DocId, revs=Revs2};
-couch_doc_from_req(Req, DocId, Json) ->
-    couch_doc_from_req(Req, DocId, couch_doc:from_json_obj(Json)).
-
-% Useful for debugging
-% couch_doc_open(Db, DocId) ->
-%   couch_doc_open(Db, DocId, nil, []).
-
-couch_doc_open(Db, DocId, Rev, Options) ->
-    case Rev of
-    nil -> % open most recent rev
-        case couch_db:open_doc(Db, DocId, Options) of
-        {ok, Doc} ->
-            Doc;
-         Error ->
-             throw(Error)
-         end;
-  _ -> % open a specific rev (deletions come back as stubs)
-      case couch_db:open_doc_revs(Db, DocId, [Rev], Options) of
-          {ok, [{ok, Doc}]} ->
-              Doc;
-          {ok, [{{not_found, missing}, Rev}]} ->
-              throw(not_found);
-          {ok, [Else]} ->
-              throw(Else)
-      end
-  end.
-
-% Attachment request handlers
-
-db_attachment_req(#httpd{method='GET',mochi_req=MochiReq}=Req, Db, DocId, FileNameParts) ->
-    FileName = list_to_binary(mochiweb_util:join(lists:map(fun binary_to_list/1, FileNameParts),"/")),
-    #doc_query_args{
-        rev=Rev,
-        options=Options
-    } = parse_doc_query(Req),
-    #doc{
-        atts=Atts
-    } = Doc = couch_doc_open(Db, DocId, Rev, Options),
-    case [A || A <- Atts, A#att.name == FileName] of
-    [] ->
-        throw({not_found, "Document is missing attachment"});
-    [#att{type=Type, encoding=Enc, disk_len=DiskLen, att_len=AttLen}=Att] ->
-        Etag = case Att#att.md5 of
-            <<>> -> couch_httpd:doc_etag(Doc);
-            Md5 -> "\"" ++ ?b2l(base64:encode(Md5)) ++ "\""
-        end,
-        ReqAcceptsAttEnc = lists:member(
-           atom_to_list(Enc),
-           couch_httpd:accepted_encodings(Req)
-        ),
-        Len = case {Enc, ReqAcceptsAttEnc} of
-        {identity, _} ->
-            % stored and served in identity form
-            DiskLen;
-        {_, false} when DiskLen =/= AttLen ->
-            % Stored encoded, but client doesn't accept the encoding we used,
-            % so we need to decode on the fly.  DiskLen is the identity length
-            % of the attachment.
-            DiskLen;
-        {_, true} ->
-            % Stored and served encoded.  AttLen is the encoded length.
-            AttLen;
-        _ ->
-            % We received an encoded attachment and stored it as such, so we
-            % don't know the identity length.  The client doesn't accept the
-            % encoding, and since we cannot serve a correct Content-Length
-            % header we'll fall back to a chunked response.
-            undefined
-        end,
-        Headers = [
-            {"ETag", Etag},
-            {"Cache-Control", "must-revalidate"},
-            {"Content-Type", binary_to_list(Type)}
-        ] ++ case ReqAcceptsAttEnc of
-        true when Enc =/= identity ->
-            % RFC 2616 says that the 'identify' encoding should not be used in
-            % the Content-Encoding header
-            [{"Content-Encoding", atom_to_list(Enc)}];
-        _ ->
-            []
-        end ++ case Enc of
-            identity ->
-                [{"Accept-Ranges", "bytes"}];
-            _ ->
-                [{"Accept-Ranges", "none"}]
-        end,
-        AttFun = case ReqAcceptsAttEnc of
-        false ->
-            fun couch_doc:att_foldl_decode/3;
-        true ->
-            fun couch_doc:att_foldl/3
-        end,
-        couch_httpd:etag_respond(
-            Req,
-            Etag,
-            fun() ->
-                case Len of
-                undefined ->
-                    {ok, Resp} = start_chunked_response(Req, 200, Headers),
-                    AttFun(Att, fun(Seg, _) -> send_chunk(Resp, Seg) end, {ok, Resp}),
-                    last_chunk(Resp);
-                _ ->
-                    Ranges = parse_ranges(MochiReq:get(range), Len),
-                    case {Enc, Ranges} of
-                        {identity, [{From, To}]} ->
-                            Headers1 = [{<<"Content-Range">>, make_content_range(From, To, Len)}]
-                                ++ Headers,
-                            {ok, Resp} = start_response_length(Req, 206, Headers1, To - From + 1),
-                            couch_doc:range_att_foldl(Att, From, To + 1,
-                                fun(Seg, _) -> send(Resp, Seg) end, {ok, Resp});
-                        {identity, Ranges} when is_list(Ranges) andalso length(Ranges) < 10 ->
-                            send_ranges_multipart(Req, Type, Len, Att, Ranges);
-                        _ ->
-                            Headers1 = Headers ++
-                                if Enc =:= identity orelse ReqAcceptsAttEnc =:= true ->
-                                    [{"Content-MD5", base64:encode(Att#att.md5)}];
-                                true ->
-                                    []
-                            end,
-                            {ok, Resp} = start_response_length(Req, 200, Headers1, Len),
-                            AttFun(Att, fun(Seg, _) -> send(Resp, Seg) end, {ok, Resp})
-                    end
-                end
-            end
-        )
-    end;
-
-
-db_attachment_req(#httpd{method=Method,mochi_req=MochiReq}=Req, Db, DocId, FileNameParts)
-        when (Method == 'PUT') or (Method == 'DELETE') ->
-    FileName = validate_attachment_name(
-                    mochiweb_util:join(
-                        lists:map(fun binary_to_list/1,
-                            FileNameParts),"/")),
-
-    NewAtt = case Method of
-        'DELETE' ->
-            [];
-        _ ->
-            [#att{
-                name = FileName,
-                type = case couch_httpd:header_value(Req,"Content-Type") of
-                    undefined ->
-                        % We could throw an error here or guess by the FileName.
-                        % Currently, just giving it a default.
-                        <<"application/octet-stream">>;
-                    CType ->
-                        list_to_binary(CType)
-                    end,
-                data = case couch_httpd:body_length(Req) of
-                    undefined ->
-                        <<"">>;
-                    {unknown_transfer_encoding, Unknown} ->
-                        exit({unknown_transfer_encoding, Unknown});
-                    chunked ->
-                        fun(MaxChunkSize, ChunkFun, InitState) ->
-                            couch_httpd:recv_chunked(Req, MaxChunkSize,
-                                ChunkFun, InitState)
-                        end;
-                    0 ->
-                        <<"">>;
-                    Length when is_integer(Length) ->
-                        Expect = case couch_httpd:header_value(Req, "expect") of
-                                     undefined ->
-                                         undefined;
-                                     Value when is_list(Value) ->
-                                         string:to_lower(Value)
-                                 end,
-                        case Expect of
-                            "100-continue" ->
-                                MochiReq:start_raw_response({100, gb_trees:empty()});
-                            _Else ->
-                                ok
-                        end,
-
-
-                        fun(Size) -> couch_httpd:recv(Req, Size) end
-                    end,
-                att_len = case couch_httpd:header_value(Req,"Content-Length") of
-                    undefined ->
-                        undefined;
-                    Length ->
-                        list_to_integer(Length)
-                    end,
-                md5 = get_md5_header(Req),
-                encoding = case string:to_lower(string:strip(
-                    couch_httpd:header_value(Req,"Content-Encoding","identity")
-                )) of
-                "identity" ->
-                   identity;
-                "gzip" ->
-                   gzip;
-                _ ->
-                   throw({
-                       bad_ctype,
-                       "Only gzip and identity content-encodings are supported"
-                   })
-                end
-            }]
-    end,
-
-    Doc = case extract_header_rev(Req, couch_httpd:qs_value(Req, "rev")) of
-        missing_rev -> % make the new doc
-            couch_doc:validate_docid(DocId),
-            #doc{id=DocId};
-        Rev ->
-            case couch_db:open_doc_revs(Db, DocId, [Rev], []) of
-                {ok, [{ok, Doc0}]} -> Doc0;
-                {ok, [{{not_found, missing}, Rev}]} -> throw(conflict);
-                {ok, [Error]} -> throw(Error)
-            end
-    end,
-
-    #doc{atts=Atts} = Doc,
-    DocEdited = Doc#doc{
-        atts = NewAtt ++ [A || A <- Atts, A#att.name /= FileName]
-    },
-
-    Headers = case Method of
-    'DELETE' ->
-        [];
-    _ ->
-        [{"Location", absolute_uri(Req, "/" ++
-            ?b2l(Db#db.name) ++ "/" ++
-            ?b2l(DocId) ++ "/" ++
-            ?b2l(FileName)
-        )}]
-    end,
-    update_doc(Req, Db, DocId, DocEdited, Headers);
-
-db_attachment_req(Req, _Db, _DocId, _FileNameParts) ->
-    send_method_not_allowed(Req, "DELETE,GET,HEAD,PUT").
-
-parse_ranges(undefined, _Len) ->
-    undefined;
-parse_ranges(fail, _Len) ->
-    undefined;
-parse_ranges(Ranges, Len) ->
-    parse_ranges(Ranges, Len, []).
-
-parse_ranges([], _Len, Acc) ->
-    lists:reverse(Acc);
-parse_ranges([{0, none}|_], _Len, _Acc) ->
-    undefined;
-parse_ranges([{From, To}|_], _Len, _Acc) when is_integer(From) andalso is_integer(To) andalso To < From ->
-    throw(requested_range_not_satisfiable);
-parse_ranges([{From, To}|Rest], Len, Acc) when is_integer(To) andalso To >= Len ->
-    parse_ranges([{From, Len-1}] ++ Rest, Len, Acc);
-parse_ranges([{none, To}|Rest], Len, Acc) ->
-    parse_ranges([{Len - To, Len - 1}] ++ Rest, Len, Acc);
-parse_ranges([{From, none}|Rest], Len, Acc) ->
-    parse_ranges([{From, Len - 1}] ++ Rest, Len, Acc);
-parse_ranges([{From,To}|Rest], Len, Acc) ->
-    parse_ranges(Rest, Len, [{From, To}] ++ Acc).
-
-get_md5_header(Req) ->
-    ContentMD5 = couch_httpd:header_value(Req, "Content-MD5"),
-    Length = couch_httpd:body_length(Req),
-    Trailer = couch_httpd:header_value(Req, "Trailer"),
-    case {ContentMD5, Length, Trailer} of
-        _ when is_list(ContentMD5) orelse is_binary(ContentMD5) ->
-            base64:decode(ContentMD5);
-        {_, chunked, undefined} ->
-            <<>>;
-        {_, chunked, _} ->
-            case re:run(Trailer, "\\bContent-MD5\\b", [caseless]) of
-                {match, _} ->
-                    md5_in_footer;
-                _ ->
-                    <<>>
-            end;
-        _ ->
-            <<>>
-    end.
-
-parse_doc_query(Req) ->
-    lists:foldl(fun({Key,Value}, Args) ->
-        case {Key, Value} of
-        {"attachments", "true"} ->
-            Options = [attachments | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"meta", "true"} ->
-            Options = [revs_info, conflicts, deleted_conflicts | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"revs", "true"} ->
-            Options = [revs | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"local_seq", "true"} ->
-            Options = [local_seq | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"revs_info", "true"} ->
-            Options = [revs_info | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"conflicts", "true"} ->
-            Options = [conflicts | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"deleted_conflicts", "true"} ->
-            Options = [deleted_conflicts | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"rev", Rev} ->
-            Args#doc_query_args{rev=couch_doc:parse_rev(Rev)};
-        {"open_revs", "all"} ->
-            Args#doc_query_args{open_revs=all};
-        {"open_revs", RevsJsonStr} ->
-            JsonArray = ?JSON_DECODE(RevsJsonStr),
-            Args#doc_query_args{open_revs=couch_doc:parse_revs(JsonArray)};
-        {"latest", "true"} ->
-            Options = [latest | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"atts_since", RevsJsonStr} ->
-            JsonArray = ?JSON_DECODE(RevsJsonStr),
-            Args#doc_query_args{atts_since = couch_doc:parse_revs(JsonArray)};
-        {"new_edits", "false"} ->
-            Args#doc_query_args{update_type=replicated_changes};
-        {"new_edits", "true"} ->
-            Args#doc_query_args{update_type=interactive_edit};
-        {"att_encoding_info", "true"} ->
-            Options = [att_encoding_info | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        _Else -> % unknown key value pair, ignore.
-            Args
-        end
-    end, #doc_query_args{}, couch_httpd:qs(Req)).
-
-parse_changes_query(Req, Db) ->
-    lists:foldl(fun({Key, Value}, Args) ->
-        case {string:to_lower(Key), Value} of
-        {"feed", _} ->
-            Args#changes_args{feed=Value};
-        {"descending", "true"} ->
-            Args#changes_args{dir=rev};
-        {"since", "now"} ->
-            UpdateSeq = couch_util:with_db(Db#db.name, fun(WDb) ->
-                                        couch_db:get_update_seq(WDb)
-                                end),
-            Args#changes_args{since=UpdateSeq};
-        {"since", _} ->
-            Args#changes_args{since=list_to_integer(Value)};
-        {"last-event-id", _} ->
-            Args#changes_args{since=list_to_integer(Value)};
-        {"limit", _} ->
-            Args#changes_args{limit=list_to_integer(Value)};
-        {"style", _} ->
-            Args#changes_args{style=list_to_existing_atom(Value)};
-        {"heartbeat", "true"} ->
-            Args#changes_args{heartbeat=true};
-        {"heartbeat", _} ->
-            Args#changes_args{heartbeat=list_to_integer(Value)};
-        {"timeout", _} ->
-            Args#changes_args{timeout=list_to_integer(Value)};
-        {"include_docs", "true"} ->
-            Args#changes_args{include_docs=true};
-        {"conflicts", "true"} ->
-            Args#changes_args{conflicts=true};
-        {"filter", _} ->
-            Args#changes_args{filter=Value};
-        _Else -> % unknown key value pair, ignore.
-            Args
-        end
-    end, #changes_args{}, couch_httpd:qs(Req)).
-
-extract_header_rev(Req, ExplicitRev) when is_binary(ExplicitRev) or is_list(ExplicitRev)->
-    extract_header_rev(Req, couch_doc:parse_rev(ExplicitRev));
-extract_header_rev(Req, ExplicitRev) ->
-    Etag = case couch_httpd:header_value(Req, "If-Match") of
-        undefined -> undefined;
-        Value -> couch_doc:parse_rev(string:strip(Value, both, $"))
-    end,
-    case {ExplicitRev, Etag} of
-    {undefined, undefined} -> missing_rev;
-    {_, undefined} -> ExplicitRev;
-    {undefined, _} -> Etag;
-    _ when ExplicitRev == Etag -> Etag;
-    _ ->
-        throw({bad_request, "Document rev and etag have different values"})
-    end.
-
-
-parse_copy_destination_header(Req) ->
-    case couch_httpd:header_value(Req, "Destination") of
-    undefined ->
-        throw({bad_request, "Destination header is mandatory for COPY."});
-    Destination ->
-        case re:run(Destination, "^https?://", [{capture, none}]) of
-        match ->
-            throw({bad_request, "Destination URL must be relative."});
-        nomatch ->
-            % see if ?rev=revid got appended to the Destination header
-            case re:run(Destination, "\\?", [{capture, none}]) of
-            nomatch ->
-                {list_to_binary(Destination), {0, []}};
-            match ->
-                [DocId, RevQs] = re:split(Destination, "\\?", [{return, list}]),
-                [_RevQueryKey, Rev] = re:split(RevQs, "=", [{return, list}]),
-                {Pos, RevId} = couch_doc:parse_rev(Rev),
-                {list_to_binary(DocId), {Pos, [RevId]}}
-            end
-        end
-    end.
-
-validate_attachment_names(Doc) ->
-    lists:foreach(fun(#att{name=Name}) ->
-        validate_attachment_name(Name)
-    end, Doc#doc.atts).
-
-validate_attachment_name(Name) when is_list(Name) ->
-    validate_attachment_name(list_to_binary(Name));
-validate_attachment_name(<<"_",_/binary>>) ->
-    throw({bad_request, <<"Attachment name can't start with '_'">>});
-validate_attachment_name(Name) ->
-    case couch_util:validate_utf8(Name) of
-        true -> Name;
-        false -> throw({bad_request, <<"Attachment name is not UTF-8 encoded">>})
-    end.
-

http://git-wip-us.apache.org/repos/asf/couchdb/blob/ed98610c/src/couch/src/couch_httpd_external.erl
----------------------------------------------------------------------
diff --git a/src/couch/src/couch_httpd_external.erl b/src/couch/src/couch_httpd_external.erl
deleted file mode 100644
index 8322dcd..0000000
--- a/src/couch/src/couch_httpd_external.erl
+++ /dev/null
@@ -1,173 +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_external).
-
--export([handle_external_req/2, handle_external_req/3]).
--export([send_external_response/2, json_req_obj/2, json_req_obj/3]).
--export([default_or_content_type/2, parse_external_response/1]).
-
--import(couch_httpd,[send_error/4]).
-
--include_lib("couch/include/couch_db.hrl").
-
-% handle_external_req/2
-% for the old type of config usage:
-% _external = {couch_httpd_external, handle_external_req}
-% with urls like
-% /db/_external/action/design/name
-handle_external_req(#httpd{
-                        path_parts=[_DbName, _External, UrlName | _Path]
-                    }=HttpReq, Db) ->
-    process_external_req(HttpReq, Db, UrlName);
-handle_external_req(#httpd{path_parts=[_, _]}=Req, _Db) ->
-    send_error(Req, 404, <<"external_server_error">>, <<"No server name specified.">>);
-handle_external_req(Req, _) ->
-    send_error(Req, 404, <<"external_server_error">>, <<"Broken assumption">>).
-
-% handle_external_req/3
-% for this type of config usage:
-% _action = {couch_httpd_external, handle_external_req, <<"action">>}
-% with urls like
-% /db/_action/design/name
-handle_external_req(HttpReq, Db, Name) ->
-    process_external_req(HttpReq, Db, Name).
-
-process_external_req(HttpReq, Db, Name) ->
-
-    Response = couch_external_manager:execute(binary_to_list(Name),
-        json_req_obj(HttpReq, Db)),
-
-    case Response of
-    {unknown_external_server, Msg} ->
-        send_error(HttpReq, 404, <<"external_server_error">>, Msg);
-    _ ->
-        send_external_response(HttpReq, Response)
-    end.
-json_req_obj(Req, Db) -> json_req_obj(Req, Db, null).
-json_req_obj(#httpd{mochi_req=Req,
-               method=Method,
-               requested_path_parts=RequestedPath,
-               path_parts=Path,
-               req_body=ReqBody
-            }, Db, DocId) ->
-    Body = case ReqBody of
-        undefined ->
-            MaxSize = list_to_integer(
-                config:get("couchdb", "max_document_size", "4294967296")),
-            Req:recv_body(MaxSize);
-        Else -> Else
-    end,
-    ParsedForm = case Req:get_primary_header_value("content-type") of
-        "application/x-www-form-urlencoded" ++ _ ->
-            case Body of
-            undefined -> [];
-            _ -> mochiweb_util:parse_qs(Body)
-            end;
-        _ ->
-            []
-    end,
-    Headers = Req:get(headers),
-    Hlist = mochiweb_headers:to_list(Headers),
-    {ok, Info} = couch_db:get_db_info(Db),
-    
-% add headers...
-    {[{<<"info">>, {Info}},
-        {<<"id">>, DocId},
-        {<<"uuid">>, couch_uuids:new()},
-        {<<"method">>, Method},
-        {<<"requested_path">>, RequestedPath},
-        {<<"path">>, Path},
-        {<<"raw_path">>, ?l2b(Req:get(raw_path))},
-        {<<"query">>, json_query_keys(to_json_terms(Req:parse_qs()))},
-        {<<"headers">>, to_json_terms(Hlist)},
-        {<<"body">>, Body},
-        {<<"peer">>, ?l2b(Req:get(peer))},
-        {<<"form">>, to_json_terms(ParsedForm)},
-        {<<"cookie">>, to_json_terms(Req:parse_cookie())},
-        {<<"userCtx">>, couch_util:json_user_ctx(Db)},
-        {<<"secObj">>, couch_db:get_security(Db)}]}.
-
-to_json_terms(Data) ->
-    to_json_terms(Data, []).
-
-to_json_terms([], Acc) ->
-    {lists:reverse(Acc)};
-to_json_terms([{Key, Value} | Rest], Acc) when is_atom(Key) ->
-    to_json_terms(Rest, [{list_to_binary(atom_to_list(Key)), list_to_binary(Value)} | Acc]);
-to_json_terms([{Key, Value} | Rest], Acc) ->
-    to_json_terms(Rest, [{list_to_binary(Key), list_to_binary(Value)} | Acc]).
-
-json_query_keys({Json}) ->
-    json_query_keys(Json, []).
-json_query_keys([], Acc) ->
-    {lists:reverse(Acc)};
-json_query_keys([{<<"startkey">>, Value} | Rest], Acc) ->
-    json_query_keys(Rest, [{<<"startkey">>, ?JSON_DECODE(Value)}|Acc]);
-json_query_keys([{<<"endkey">>, Value} | Rest], Acc) ->
-    json_query_keys(Rest, [{<<"endkey">>, ?JSON_DECODE(Value)}|Acc]);
-json_query_keys([{<<"key">>, Value} | Rest], Acc) ->
-    json_query_keys(Rest, [{<<"key">>, ?JSON_DECODE(Value)}|Acc]);
-json_query_keys([Term | Rest], Acc) ->
-    json_query_keys(Rest, [Term|Acc]).
-
-send_external_response(#httpd{mochi_req=MochiReq}=Req, Response) ->
-    #extern_resp_args{
-        code = Code,
-        data = Data,
-        ctype = CType,
-        headers = Headers
-    } = parse_external_response(Response),
-    couch_httpd:log_request(Req, Code),
-    Resp = MochiReq:respond({Code,
-        default_or_content_type(CType, Headers ++ couch_httpd:server_header()), Data}),
-    {ok, Resp}.
-
-parse_external_response({Response}) ->
-    lists:foldl(fun({Key,Value}, Args) ->
-        case {Key, Value} of
-            {"", _} ->
-                Args;
-            {<<"code">>, Value} ->
-                Args#extern_resp_args{code=Value};
-            {<<"stop">>, true} ->
-                Args#extern_resp_args{stop=true};
-            {<<"json">>, Value} ->
-                Args#extern_resp_args{
-                    data=?JSON_ENCODE(Value),
-                    ctype="application/json"};
-            {<<"body">>, Value} ->
-                Args#extern_resp_args{data=Value, ctype="text/html; charset=utf-8"};
-            {<<"base64">>, Value} ->
-                Args#extern_resp_args{
-                    data=base64:decode(Value),
-                    ctype="application/binary"
-                };
-            {<<"headers">>, {Headers}} ->
-                NewHeaders = lists:map(fun({Header, HVal}) ->
-                    {binary_to_list(Header), binary_to_list(HVal)}
-                end, Headers),
-                Args#extern_resp_args{headers=NewHeaders};
-            _ -> % unknown key
-                Msg = lists:flatten(io_lib:format("Invalid data from external server: ~p", [{Key, Value}])),
-                throw({external_response_error, Msg})
-            end
-        end, #extern_resp_args{}, Response).
-
-default_or_content_type(DefaultContentType, Headers) ->
-    IsContentType = fun({X, _}) -> string:to_lower(X) == "content-type" end,
-    case lists:any(IsContentType, Headers) of
-    false ->
-        [{"Content-Type", DefaultContentType} | Headers];
-    true ->
-        Headers
-    end.