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:15 UTC

[02/44] Remove src/chttpd

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_db.erl b/src/chttpd/src/chttpd_db.erl
deleted file mode 100644
index de99828..0000000
--- a/src/chttpd/src/chttpd_db.erl
+++ /dev/null
@@ -1,1280 +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(chttpd_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, handle_view_cleanup_req/2]).
-
--import(chttpd,
-    [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,
-    start_json_response/2,send_chunk/2,end_json_response/1,
-    start_chunked_response/3, absolute_uri/2, send/2,
-    start_response_length/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:get_value(SecondPart, DbUrlHandlers, fun db_req/2),
-        do_db_req(Req, Handler)
-    end.
-
-handle_changes_req(#httpd{method='GET'}=Req, Db) ->
-    #changes_args{filter=Raw, style=Style} = Args0 = parse_changes_query(Req),
-    ChangesArgs = Args0#changes_args{
-        filter = couch_changes:configure_filter(Raw, Style, Req, Db)
-    },
-    case ChangesArgs#changes_args.feed of
-    "normal" ->
-        T0 = os:timestamp(),
-        {ok, Info} = fabric:get_db_info(Db),
-        Etag = chttpd:make_etag(Info),
-        DeltaT = timer:now_diff(os:timestamp(), T0) / 1000,
-        couch_stats_collector:record({couchdb, dbinfo}, DeltaT),
-        chttpd:etag_respond(Req, Etag, fun() ->
-            fabric:changes(Db, fun changes_callback/2, {"normal", {"Etag",Etag}, Req},
-                ChangesArgs)
-        end);
-    Feed ->
-        % "longpoll" or "continuous"
-        fabric:changes(Db, fun changes_callback/2, {Feed, Req}, ChangesArgs)
-    end;
-handle_changes_req(#httpd{path_parts=[_,<<"_changes">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-% callbacks for continuous feed (newline-delimited JSON Objects)
-changes_callback(start, {"continuous", Req}) ->
-    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200),
-    {ok, {"continuous", Resp}};
-changes_callback({change, Change}, {"continuous", Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [?JSON_ENCODE(Change) | "\n"]),
-    {ok, {"continuous", Resp1}};
-changes_callback({stop, EndSeq0}, {"continuous", Resp}) ->
-    EndSeq = case is_old_couch(Resp) of true -> 0; false -> EndSeq0 end,
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp,
-        [?JSON_ENCODE({[{<<"last_seq">>, EndSeq}]}) | "\n"]),
-    chttpd:end_delayed_json_response(Resp1);
-
-% callbacks for longpoll and normal (single JSON Object)
-changes_callback(start, {"normal", {"Etag", Etag}, Req}) ->
-    FirstChunk = "{\"results\":[\n",
-    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200,
-        [{"Etag",Etag}], FirstChunk),
-    {ok, {"", Resp}};
-changes_callback(start, {_, Req}) ->
-    FirstChunk = "{\"results\":[\n",
-    {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [], FirstChunk),
-    {ok, {"", Resp}};
-changes_callback({change, Change}, {Prepend, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [Prepend, ?JSON_ENCODE(Change)]),
-    {ok, {",\r\n", Resp1}};
-changes_callback({stop, EndSeq}, {_, Resp}) ->
-    {ok, Resp1} = case is_old_couch(Resp) of
-    true ->
-        chttpd:send_delayed_chunk(Resp, "\n],\n\"last_seq\":0}\n");
-    false ->
-        chttpd:send_delayed_chunk(Resp,
-            ["\n],\n\"last_seq\":", ?JSON_ENCODE(EndSeq), "}\n"])
-    end,
-    chttpd:end_delayed_json_response(Resp1);
-
-changes_callback(timeout, {Prepend, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "\n"),
-    {ok, {Prepend, Resp1}};
-changes_callback({error, Reason}, {_, #httpd{}=Req}) ->
-    chttpd:send_error(Req, Reason);
-changes_callback({error, Reason}, {"normal", {"Etag", _Etag}, Req}) ->
-    chttpd:send_error(Req, Reason);
-changes_callback({error, Reason}, {_, Resp}) ->
-    chttpd:send_delayed_error(Resp, Reason).
-
-is_old_couch(Resp) ->
-    MochiReq = chttpd:get_delayed_req(Resp),
-    case MochiReq:get_header_value("user-agent") of
-    undefined ->
-        false;
-    "CouchDB/1.0.0" ->
-        true;
-    UserAgent ->
-        string:str(UserAgent, "CouchDB/0") > 0
-    end.
-
-handle_compact_req(Req, _) ->
-    Msg = <<"Compaction must be triggered on a per-shard basis in CouchDB">>,
-    couch_httpd:send_error(Req, 403, forbidden, Msg).
-
-handle_view_cleanup_req(Req, Db) ->
-    ok = fabric:cleanup_index_files(Db),
-    send_json(Req, 202, {[{ok, true}]}).
-
-handle_design_req(#httpd{
-        path_parts=[_DbName, _Design, Name, <<"_",_/binary>> = Action | _Rest],
-        design_url_handlers = DesignUrlHandlers
-    }=Req, Db) ->
-    DbName = mem3:dbname(Db#db.name),
-    case ddoc_cache:open(DbName, <<"_design/", Name/binary>>) of
-    {ok, DDoc} ->
-        Handler = couch_util:get_value(Action, DesignUrlHandlers,
-            fun bad_action_req/3),
-        Handler(Req, Db, DDoc);
-    Error ->
-        throw(Error)
-    end;
-
-handle_design_req(Req, Db) ->
-    db_req(Req, Db).
-
-bad_action_req(#httpd{path_parts=[_, _, Name|FileNameParts]}=Req, Db, _DDoc) ->
-    db_attachment_req(Req, Db, <<"_design/",Name/binary>>, FileNameParts).
-
-handle_design_info_req(#httpd{method='GET'}=Req, Db, #doc{id=Id} = DDoc) ->
-    {ok, GroupInfoList} = fabric:get_view_group_info(Db, DDoc),
-    send_json(Req, 200, {[
-        {name,  Id},
-        {view_index, {GroupInfoList}}
-    ]});
-
-handle_design_info_req(Req, _Db, _DDoc) ->
-    send_method_not_allowed(Req, "GET").
-
-create_db_req(#httpd{}=Req, DbName) ->
-    couch_httpd:verify_is_server_admin(Req),
-    N = couch_httpd:qs_value(Req, "n", config:get("cluster", "n", "3")),
-    Q = couch_httpd:qs_value(Req, "q", config:get("cluster", "q", "8")),
-    P = couch_httpd:qs_value(Req, "placement", config:get("cluster", "placement")),
-    DocUrl = absolute_uri(Req, "/" ++ couch_util:url_encode(DbName)),
-    case fabric:create_db(DbName, [{n,N}, {q,Q}, {placement,P}]) of
-    ok ->
-        send_json(Req, 201, [{"Location", DocUrl}], {[{ok, true}]});
-    accepted ->
-        send_json(Req, 202, [{"Location", DocUrl}], {[{ok, true}]});
-    {error, file_exists} ->
-        chttpd:send_error(Req, file_exists);
-    Error ->
-        throw(Error)
-    end.
-
-delete_db_req(#httpd{}=Req, DbName) ->
-    couch_httpd:verify_is_server_admin(Req),
-    case fabric:delete_db(DbName, []) of
-    ok ->
-        send_json(Req, 200, {[{ok, true}]});
-    accepted ->
-        send_json(Req, 202, {[{ok, true}]});
-    Error ->
-        throw(Error)
-    end.
-
-do_db_req(#httpd{path_parts=[DbName|_], user_ctx=Ctx}=Req, Fun) ->
-    fabric:get_security(DbName, [{user_ctx,Ctx}]), % calls check_is_reader
-    Fun(Req, #db{name=DbName, user_ctx=Ctx}).
-
-db_req(#httpd{method='GET',path_parts=[DbName]}=Req, _Db) ->
-    % measure the time required to generate the etag, see if it's worth it
-    T0 = os:timestamp(),
-    {ok, DbInfo} = fabric:get_db_info(DbName),
-    DeltaT = timer:now_diff(os:timestamp(), T0) / 1000,
-    couch_stats_collector:record({couchdb, dbinfo}, DeltaT),
-    send_json(Req, {DbInfo});
-
-db_req(#httpd{method='POST', path_parts=[DbName], user_ctx=Ctx}=Req, Db) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-
-    W = couch_httpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
-    Options = [{user_ctx,Ctx}, {w,W}],
-
-    Doc = couch_doc:from_json_obj(chttpd:json_body(Req)),
-    Doc2 = case Doc#doc.id of
-        <<"">> ->
-            Doc#doc{id=couch_uuids:new(), revs={0, []}};
-        _ ->
-            Doc
-    end,
-    DocId = Doc2#doc.id,
-    case couch_httpd:qs_value(Req, "batch") of
-    "ok" ->
-        % async_batching
-        spawn(fun() ->
-                case catch(fabric:update_doc(Db, Doc2, Options)) of
-                {ok, _} -> ok;
-                {accepted, _} -> ok;
-                Error ->
-                    twig:log(debug, "Batch doc error (~s): ~p",[DocId, Error])
-                end
-            end),
-
-        send_json(Req, 202, [], {[
-            {ok, true},
-            {id, DocId}
-        ]});
-    _Normal ->
-        % normal
-        DocUrl = absolute_uri(Req, [$/, DbName, $/, DocId]),
-        case fabric:update_doc(Db, Doc2, Options) of
-        {ok, NewRev} ->
-            HttpCode = 201;
-        {accepted, NewRev} ->
-            HttpCode = 202
-        end,
-        send_json(Req, HttpCode, [{"Location", DocUrl}], {[
-            {ok, true},
-            {id, DocId},
-            {rev, couch_doc:rev_to_str(NewRev)}
-        ]})
-    end;
-
-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) ->
-    send_json(Req, 201, {[
-        {ok, true},
-        {instance_start_time, <<"0">>}
-    ]});
-
-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">>], user_ctx=Ctx}=Req, Db) ->
-    couch_stats_collector:increment({httpd, bulk_requests}),
-    couch_httpd:validate_ctype(Req, "application/json"),
-    {JsonProps} = chttpd:json_body_obj(Req),
-    DocsArray = couch_util:get_value(<<"docs">>, JsonProps),
-    W = couch_httpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
-    case chttpd:header_value(Req, "X-Couch-Full-Commit") of
-    "true" ->
-        Options = [full_commit, {user_ctx,Ctx}, {w,W}];
-    "false" ->
-        Options = [delay_commit, {user_ctx,Ctx}, {w,W}];
-    _ ->
-        Options = [{user_ctx,Ctx}, {w,W}]
-    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 fabric: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);
-        {accepted, Results} ->
-            % output the results
-            DocResults = lists:zipwith(fun update_doc_result_to_json/2,
-                Docs, Results),
-            send_json(Req, 202, DocResults);
-        {aborted, Errors} ->
-            ErrorsJson =
-                lists:map(fun update_doc_result_to_json/1, Errors),
-            send_json(Req, 417, ErrorsJson)
-        end;
-    false ->
-        Docs = [couch_doc:from_json_obj(JsonObj) || JsonObj <- DocsArray],
-        [validate_attachment_names(D) || D <- Docs],
-        case fabric:update_docs(Db, Docs, [replicated_changes|Options]) of
-        {ok, Errors} ->
-            ErrorsJson = lists:map(fun update_doc_result_to_json/1, Errors),
-            send_json(Req, 201, ErrorsJson);
-        {accepted, Errors} ->
-            ErrorsJson = lists:map(fun update_doc_result_to_json/1, Errors),
-            send_json(Req, 202, 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} = chttpd:json_body_obj(Req),
-    IdsRevs2 = [{Id, couch_doc:parse_revs(Revs)} || {Id, Revs} <- IdsRevs],
-    case fabric: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='GET',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
-    case chttpd:qs_json_value(Req, "keys", nil) of
-    Keys when is_list(Keys) ->
-        all_docs_view(Req, Db, Keys);
-    nil ->
-        all_docs_view(Req, Db, undefined);
-    _ ->
-        throw({bad_request, "`keys` parameter must be an array."})
-    end;
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_all_docs">>]}=Req, Db) ->
-    {Fields} = chttpd:json_body_obj(Req),
-    case couch_util:get_value(<<"keys">>, Fields, nil) of
-    Keys when is_list(Keys) ->
-        all_docs_view(Req, Db, Keys);
-    nil ->
-        all_docs_view(Req, Db, nil);
-    _ ->
-        throw({bad_request, "`keys` body member must be an array."})
-    end;
-
-db_req(#httpd{path_parts=[_,<<"_all_docs">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "GET,HEAD,POST");
-
-db_req(#httpd{method='POST',path_parts=[_,<<"_missing_revs">>]}=Req, Db) ->
-    {JsonDocIdRevs} = chttpd:json_body_obj(Req),
-    {ok, Results} = fabric:get_missing_revs(Db, JsonDocIdRevs),
-    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} = chttpd:json_body_obj(Req),
-    {ok, Results} = fabric:get_missing_revs(Db, JsonDocIdRevs),
-    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">>],user_ctx=Ctx}=Req,
-        Db) ->
-    SecObj = chttpd:json_body(Req),
-    case fabric:set_security(Db, SecObj, [{user_ctx, Ctx}]) of
-        ok ->
-            send_json(Req, {[{<<"ok">>, true}]});
-        Else ->
-            throw(Else)
-    end;
-
-db_req(#httpd{method='GET',path_parts=[_,<<"_security">>],user_ctx=Ctx}=Req, Db) ->
-    send_json(Req, fabric:get_security(Db, [{user_ctx,Ctx}]));
-
-db_req(#httpd{path_parts=[_,<<"_security">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "PUT,GET");
-
-db_req(#httpd{method='PUT',path_parts=[_,<<"_revs_limit">>],user_ctx=Ctx}=Req,
-        Db) ->
-    Limit = chttpd:json_body(Req),
-    ok = fabric:set_revs_limit(Db, Limit, [{user_ctx,Ctx}]),
-    send_json(Req, {[{<<"ok">>, true}]});
-
-db_req(#httpd{method='GET',path_parts=[_,<<"_revs_limit">>]}=Req, Db) ->
-    send_json(Req, fabric:get_revs_limit(Db));
-
-db_req(#httpd{path_parts=[_,<<"_revs_limit">>]}=Req, _Db) ->
-    send_method_not_allowed(Req, "PUT,GET");
-
-% vanilla CouchDB sends a 301 here, but we just handle the request
-db_req(#httpd{path_parts=[DbName,<<"_design/",Name/binary>>|Rest]}=Req, Db) ->
-    db_req(Req#httpd{path_parts=[DbName, <<"_design">>, Name | Rest]}, Db);
-
-% 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{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).
-
-all_docs_view(Req, Db, Keys) ->
-    % measure the time required to generate the etag, see if it's worth it
-    T0 = os:timestamp(),
-    {ok, Info} = fabric:get_db_info(Db),
-    Etag = couch_httpd:make_etag(Info),
-    DeltaT = timer:now_diff(os:timestamp(), T0) / 1000,
-    couch_stats_collector:record({couchdb, dbinfo}, DeltaT),
-    QueryArgs = chttpd_view:parse_view_params(Req, Keys, map),
-    chttpd:etag_respond(Req, Etag, fun() ->
-        {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"Etag",Etag}]),
-        fabric:all_docs(Db, fun all_docs_callback/2, {nil, Resp}, QueryArgs)
-    end).
-
-all_docs_callback({total_and_offset, Total, Offset}, {_, Resp}) ->
-    Chunk = "{\"total_rows\":~p,\"offset\":~p,\"rows\":[\r\n",
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, io_lib:format(Chunk, [Total, Offset])),
-    {ok, {"", Resp1}};
-all_docs_callback({row, Row}, {Prepend, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [Prepend, ?JSON_ENCODE(Row)]),
-    {ok, {",\r\n", Resp1}};
-all_docs_callback(complete, {_, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "\r\n]}"),
-    chttpd:end_delayed_json_response(Resp1);
-all_docs_callback({error, Reason}, {_, Resp}) ->
-    chttpd:send_delayed_error(Resp, Reason).
-
-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 chttpd:qs_value(Req, "rev") of
-    undefined ->
-        Body = {[{<<"_deleted">>,true}]};
-    Rev ->
-        Body = {[{<<"_rev">>, ?l2b(Rev)},{<<"_deleted">>,true}]}
-    end,
-    update_doc(Req, Db, DocId, couch_doc_from_req(Req, DocId, Body));
-
-db_doc_req(#httpd{method='GET'}=Req, Db, DocId) ->
-    #doc_query_args{
-        rev = Rev,
-        open_revs = Revs,
-        options = Options,
-        atts_since = AttsSince
-    } = parse_doc_query(Req),
-    case Revs of
-    [] ->
-        Options2 =
-        if AttsSince /= nil ->
-            [{atts_since, AttsSince}, attachments | Options];
-        true -> Options
-        end,
-        Doc = couch_doc_open(Db, DocId, Rev, Options2),
-        send_doc(Req, Doc, Options2);
-    _ ->
-        {ok, Results} = fabric:open_revs(Db, DocId, Revs, Options),
-        AcceptedTypes = case couch_httpd:header_value(Req, "Accept") of
-            undefined       -> [];
-            AcceptHeader    -> string:tokens(AcceptHeader, ", ")
-        end,
-        case lists:member("multipart/mixed", AcceptedTypes) 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', user_ctx=Ctx}=Req, Db, DocId) ->
-    couch_httpd:validate_referer(Req),
-    couch_doc:validate_docid(DocId),
-    couch_httpd:validate_ctype(Req, "multipart/form-data"),
-
-    W = couch_httpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
-    Options = [{user_ctx,Ctx}, {w,W}],
-
-    Form = couch_httpd:parse_form(Req),
-    case proplists:is_defined("_doc", Form) of
-    true ->
-        Json = ?JSON_DECODE(couch_util:get_value("_doc", Form)),
-        Doc = couch_doc_from_req(Req, DocId, Json);
-    false ->
-        Rev = couch_doc:parse_rev(list_to_binary(couch_util:get_value("_rev", Form))),
-        {ok, [{ok, Doc}]} = fabric:open_revs(Db, DocId, [Rev], [])
-    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
-    },
-    case fabric:update_doc(Db, NewDoc, Options) of
-    {ok, NewRev} ->
-        HttpCode = 201;
-    {accepted, NewRev} ->
-        HttpCode = 202
-    end,
-    send_json(Req, HttpCode, [{"Etag", "\"" ++ ?b2l(couch_doc:rev_to_str(NewRev)) ++ "\""}], {[
-        {ok, true},
-        {id, DocId},
-        {rev, couch_doc:rev_to_str(NewRev)}
-    ]});
-
-db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
-    #doc_query_args{
-        update_type = UpdateType
-    } = parse_doc_query(Req),
-    couch_doc:validate_docid(DocId),
-
-    W = couch_httpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
-    Options = [{user_ctx,Ctx}, {w,W}],
-
-    Loc = absolute_uri(Req, [$/, Db#db.name, $/, DocId]),
-    RespHeaders = [{"Location", Loc}],
-    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, RespHeaders, UpdateType),
-            WaitFun(),
-            Result
-        catch throw:Err ->
-            % Document rejected by a validate_doc_update function.
-            couch_doc:abort_multi_part_stream(Parser),
-            throw(Err)
-        end;
-    _Else ->
-        case couch_httpd:qs_value(Req, "batch") of
-        "ok" ->
-            % batch
-            Doc = couch_doc_from_req(Req, DocId, chttpd:json_body(Req)),
-
-            spawn(fun() ->
-                    case catch(fabric:update_doc(Db, Doc, Options)) of
-                    {ok, _} -> ok;
-                    {accepted, _} -> ok;
-                    Error ->
-                        twig:log(notice, "Batch doc error (~s): ~p",[DocId, Error])
-                    end
-                end),
-            send_json(Req, 202, [], {[
-                {ok, true},
-                {id, DocId}
-            ]});
-        _Normal ->
-            % normal
-            Body = chttpd:json_body(Req),
-            Doc = couch_doc_from_req(Req, DocId, Body),
-            update_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType)
-        end
-    end;
-
-db_doc_req(#httpd{method='COPY', user_ctx=Ctx}=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
-    case fabric:update_doc(Db,
-        Doc#doc{id=TargetDocId, revs=TargetRevs}, [{user_ctx,Ctx}]) of
-    {ok, NewTargetRev} ->
-        HttpCode = 201;
-    {accepted, NewTargetRev} ->
-        HttpCode = 202
-    end,
-    % respond
-    {PartRes} = update_doc_result_to_json(TargetDocId, {ok, NewTargetRev}),
-    send_json(Req, HttpCode,
-        [{"Etag", "\"" ++ ?b2l(couch_doc:rev_to_str(NewTargetRev)) ++ "\""}],
-        {[{ok, true}] ++ PartRes});
-
-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
-        chttpd: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(Req, #doc{atts=Atts}=Doc, Headers, Options) ->
-    case lists:member(attachments, Options) of
-    true ->
-        Refs = monitor_attachments(Atts),
-        try
-        AcceptedTypes = case couch_httpd:header_value(Req, "Accept") of
-            undefined       -> [];
-            AcceptHeader    -> string:tokens(AcceptHeader, ", ")
-        end,
-        case lists:member("multipart/related", AcceptedTypes) 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
-        after
-            demonitor_refs(Refs)
-        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}) ->
-            Refs = monitor_attachments(Doc#doc.atts),
-            try
-            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>>)
-            after
-                demonitor_refs(Refs)
-            end;
-        ({{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).
-
-receive_request_data(Req) ->
-    receive_request_data(Req, chttpd:body_length(Req)).
-
-receive_request_data(Req, LenLeft) when LenLeft > 0 ->
-    Len = erlang:min(4096, LenLeft),
-    Data = chttpd:recv(Req, Len),
-    {Data, fun() -> receive_request_data(Req, LenLeft - iolist_size(Data)) end};
-receive_request_data(_Req, _) ->
-    throw(<<"expected more data">>).
-
-update_doc_result_to_json({{Id, Rev}, Error}) ->
-        {_Code, Err, Msg} = chttpd: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}) ->
-    {[{id, DocId}, {rev, couch_doc:rev_to_str(NewRev)}]};
-update_doc_result_to_json(DocId, {accepted, NewRev}) ->
-    {[{id, DocId}, {rev, couch_doc:rev_to_str(NewRev)}, {accepted, true}]};
-update_doc_result_to_json(DocId, Error) ->
-    {_Code, ErrorStr, Reason} = chttpd:error_info(Error),
-    {[{id, DocId}, {error, ErrorStr}, {reason, Reason}]}.
-
-
-update_doc(Req, Db, DocId, Json) ->
-    update_doc(Req, Db, DocId, Json, []).
-
-update_doc(Req, Db, DocId, Doc, Headers) ->
-    update_doc(Req, Db, DocId, Doc, Headers, interactive_edit).
-
-update_doc(#httpd{user_ctx=Ctx} = Req, Db, DocId, #doc{deleted=Deleted}=Doc,
-        Headers, UpdateType) ->
-    W = couch_httpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
-    Options =
-        case couch_httpd:header_value(Req, "X-Couch-Full-Commit") of
-        "true" ->
-            [full_commit, UpdateType, {user_ctx,Ctx}, {w,W}];
-        "false" ->
-            [delay_commit, UpdateType, {user_ctx,Ctx}, {w,W}];
-        _ ->
-            [UpdateType, {user_ctx,Ctx}, {w,W}]
-        end,
-    {_, Ref} = spawn_monitor(fun() -> exit(fabric:update_doc(Db, Doc, Options)) end),
-    Result = receive {'DOWN', Ref, _, _, Res} -> Res end,
-    case Result of
-    {{nocatch, Exception}, _Reason} ->
-        % Exceptions from spawned processes are swallowed and returned, rethrow
-        throw(Exception);
-    _ ->
-        ok
-    end,
-
-    case Result of
-    {ok, NewRev} ->
-        Accepted = false;
-    {accepted, NewRev} ->
-        Accepted = true
-    end,
-    NewRevStr = couch_doc:rev_to_str(NewRev),
-    ResponseHeaders = [{"Etag", <<"\"", NewRevStr/binary, "\"">>} | Headers],
-    case {Accepted, Deleted} of
-    {true, _} ->
-        HttpCode = 202;
-    {false, true} ->
-        HttpCode = 200;
-    {false, false} ->
-        HttpCode = 201
-    end,
-    send_json(Req, HttpCode, ResponseHeaders, {[
-        {ok, true},
-        {id, DocId},
-        {rev, NewRevStr}
-    ]}).
-
-couch_doc_from_req(Req, DocId, #doc{revs=Revs} = Doc) ->
-    validate_attachment_names(Doc),
-    ExplicitDocRev =
-    case Revs of
-        {Start,[RevId|_]} -> {Start, RevId};
-        _ -> undefined
-    end,
-    case extract_header_rev(Req, ExplicitDocRev) of
-    missing_rev ->
-        Revs2 = {0, []};
-    ExplicitDocRev ->
-        Revs2 = Revs;
-    {Pos, Rev} ->
-        Revs2 = {Pos, [Rev]}
-    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 fabric:open_doc(Db, DocId, Options) of
-        {ok, Doc} ->
-            Doc;
-         Error ->
-             throw(Error)
-         end;
-  _ -> % open a specific rev (deletions come back as stubs)
-      case fabric:open_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] ->
-        Refs = monitor_attachments(Att),
-        try
-        Etag = chttpd:doc_etag(Doc),
-        ReqAcceptsAttEnc = lists:member(
-           atom_to_list(Enc),
-           couch_httpd:accepted_encodings(Req)
-        ),
-        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,
-        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,
-        AttFun = case ReqAcceptsAttEnc of
-        false ->
-            fun couch_doc:att_foldl_decode/3;
-        true ->
-            fun couch_doc:att_foldl/3
-        end,
-        chttpd: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}),
-                    couch_httpd: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) ->
-                            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
-        )
-        after
-            demonitor_refs(Refs)
-        end
-    end;
-
-
-db_attachment_req(#httpd{method=Method, user_ctx=Ctx}=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 = fabric:att_receiver(Req, chttpd:body_length(Req)),
-                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 fabric:open_revs(Db, DocId, [Rev], []) of
-            {ok, [{ok, Doc0}]}  -> Doc0;
-            {ok, [Error]}       -> throw(Error)
-            end
-    end,
-
-    #doc{atts=Atts} = Doc,
-    DocEdited = Doc#doc{
-        atts = NewAtt ++ [A || A <- Atts, A#att.name /= FileName]
-    },
-    case fabric:update_doc(Db, DocEdited, [{user_ctx,Ctx}]) of
-    {ok, UpdatedRev} ->
-        HttpCode = 201;
-    {accepted, UpdatedRev} ->
-        HttpCode = 202
-    end,
-    erlang:put(mochiweb_request_recv, true),
-    #db{name=DbName} = Db,
-
-    {Status, Headers} = case Method of
-        'DELETE' ->
-            {200, []};
-        _ ->
-            {HttpCode, [{"Location", absolute_uri(Req, [$/, DbName, $/, DocId, $/,
-                FileName])}]}
-        end,
-    send_json(Req,Status, Headers, {[
-        {ok, true},
-        {id, DocId},
-        {rev, couch_doc:rev_to_str(UpdatedRev)}
-    ]});
-
-db_attachment_req(Req, _Db, _DocId, _FileNameParts) ->
-    send_method_not_allowed(Req, "DELETE,GET,HEAD,PUT").
-
-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}.
-
-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([{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).
-
-make_content_range(From, To, Len) ->
-    ?l2b(io_lib:format("bytes ~B-~B/~B", [From, To, Len])).
-
-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_rev(Rev) || Rev <- 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};
-        {"r", R} ->
-            Options = [{r,R} | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        {"w", W} ->
-            Options = [{w,W} | Args#doc_query_args.options],
-            Args#doc_query_args{options=Options};
-        _Else -> % unknown key value pair, ignore.
-            Args
-        end
-    end, #doc_query_args{}, chttpd:qs(Req)).
-
-parse_changes_query(Req) ->
-    lists:foldl(fun({Key, Value}, Args) ->
-        case {Key, Value} of
-        {"feed", _} ->
-            Args#changes_args{feed=Value};
-        {"descending", "true"} ->
-            Args#changes_args{dir=rev};
-        {"since", _} ->
-            Args#changes_args{since=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 chttpd: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) ->
-    Destination = chttpd:header_value(Req, "Destination"),
-    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.
-
-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(<<"_",Rest/binary>>) ->
-    throw({bad_request, <<"Attachment name '_", Rest/binary,
-                          "' starts with prohibited character '_'">>});
-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.
-
--spec monitor_attachments(#att{} | [#att{}]) -> [reference()].
-monitor_attachments(#att{}=Att) ->
-    monitor_attachments([Att]);
-monitor_attachments(Atts) when is_list(Atts) ->
-    [monitor(process, Fd) || #att{data={Fd,_}} <- Atts].
-
-demonitor_refs(Refs) when is_list(Refs) ->
-    [demonitor(Ref) || Ref <- Refs].

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_external.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_external.erl b/src/chttpd/src/chttpd_external.erl
deleted file mode 100644
index 4d07059..0000000
--- a/src/chttpd/src/chttpd_external.erl
+++ /dev/null
@@ -1,177 +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(chttpd_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(chttpd,[send_error/4]).
-
--include_lib("couch/include/couch_db.hrl").
-
-% handle_external_req/2
-% for the old type of config usage:
-% _external = {chttpd_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 = {chttpd_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,
-               path_parts=Path,
-               req_body=ReqBody
-            }, Db, DocId) ->
-    Body = case ReqBody of
-        undefined -> Req:recv_body();
-        Else -> Else
-    end,
-    ParsedForm = case Req:get_primary_header_value("content-type") of
-        "application/x-www-form-urlencoded" ++ _ when Method =:= 'POST' ->
-            mochiweb_util:parse_qs(Body);
-        _ ->
-            []
-    end,
-    Headers = Req:get(headers),
-    Hlist = mochiweb_headers:to_list(Headers),
-    {ok, Info} = fabric:get_db_info(Db),
-
-    % add headers...
-    {[{<<"info">>, {Info}},
-        {<<"uuid">>, couch_uuids:new()},
-        {<<"id">>, DocId},
-        {<<"method">>, Method},
-        {<<"path">>, 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)}]}.
-
-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([{<<"descending">>, Value} | Rest], Acc) ->
-    json_query_keys(Rest, [{<<"descending">>, ?JSON_DECODE(Value)}|Acc]);
-json_query_keys([Term | Rest], Acc) ->
-    json_query_keys(Rest, [Term|Acc]).
-
-send_external_response(Req, Response) ->
-    #extern_resp_args{
-        code = Code,
-        data = Data,
-        ctype = CType,
-        headers = Headers,
-        json = Json
-    } = parse_external_response(Response),
-    Headers1 = default_or_content_type(CType, Headers),
-    case Json of
-    nil ->
-        couch_httpd:send_response(Req, Code, Headers1, Data);
-    Json ->
-        couch_httpd:send_json(Req, Code, Headers1, Json)
-    end.
-
-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{
-                    json=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) ->
-    {ContentType, OtherHeaders} = lists:partition(
-        fun({HeaderName, _}) ->
-            HeaderName == "Content-Type"
-        end, Headers),
-
-    % XXX: What happens if we were passed multiple content types? We add another?
-    case ContentType of
-        [{"Content-Type", SetContentType}] ->
-            TrueContentType = SetContentType;
-        _Else ->
-            TrueContentType = DefaultContentType
-    end,
-
-    HeadersWithContentType = lists:append(OtherHeaders, [{"Content-Type", TrueContentType}]),
-    HeadersWithContentType.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_misc.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_misc.erl b/src/chttpd/src/chttpd_misc.erl
deleted file mode 100644
index 8c5f50e..0000000
--- a/src/chttpd/src/chttpd_misc.erl
+++ /dev/null
@@ -1,312 +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(chttpd_misc).
-
--export([handle_welcome_req/2,handle_favicon_req/2,handle_utils_dir_req/2,
-    handle_all_dbs_req/1,handle_replicate_req/1,handle_restart_req/1,
-    handle_uuids_req/1,handle_config_req/1,handle_log_req/1,
-    handle_task_status_req/1,handle_sleep_req/1,handle_welcome_req/1,
-    handle_utils_dir_req/1, handle_favicon_req/1, handle_system_req/1,
-    handle_up_req/1]).
-
-
--include_lib("couch/include/couch_db.hrl").
--include_lib("couch_mrview/include/couch_mrview.hrl").
-
--import(chttpd,
-    [send_json/2,send_json/3,send_method_not_allowed/2,
-    send_chunk/2,start_chunked_response/3]).
-
-% httpd global handlers
-
-handle_welcome_req(Req) ->
-    handle_welcome_req(Req, <<"Welcome">>).
-
-handle_welcome_req(#httpd{method='GET'}=Req, WelcomeMessage) ->
-    send_json(Req, {[
-        {couchdb, WelcomeMessage},
-        {version, list_to_binary(couch_server:get_version())},
-        {bigcouch, get_version()}
-    ]});
-handle_welcome_req(Req, _) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-get_version() ->
-    Releases = release_handler:which_releases(),
-    Version = case [V || {"bigcouch", V, _, current} <- Releases] of
-    [] ->
-        case [V || {"bigcouch", V, _, permanent} <- Releases] of
-        [] ->
-            "dev";
-        [Permanent] ->
-            Permanent
-        end;
-    [Current] ->
-        Current
-    end,
-    list_to_binary(Version).
-
-handle_favicon_req(Req) ->
-    handle_favicon_req(Req, config:get("chttpd", "docroot")).
-
-handle_favicon_req(#httpd{method='GET'}=Req, DocumentRoot) ->
-    chttpd:serve_file(Req, "favicon.ico", DocumentRoot);
-handle_favicon_req(Req, _) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-handle_utils_dir_req(Req) ->
-    handle_utils_dir_req(Req, config:get("chttpd", "docroot")).
-
-handle_utils_dir_req(#httpd{method='GET'}=Req, DocumentRoot) ->
-    "/" ++ UrlPath = chttpd:path(Req),
-    case chttpd:partition(UrlPath) of
-    {_ActionKey, "/", RelativePath} ->
-        % GET /_utils/path or GET /_utils/
-        chttpd:serve_file(Req, RelativePath, DocumentRoot);
-    {_ActionKey, "", _RelativePath} ->
-        % GET /_utils
-        RedirectPath = chttpd:path(Req) ++ "/",
-        chttpd:send_redirect(Req, RedirectPath)
-    end;
-handle_utils_dir_req(Req, _) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-handle_sleep_req(#httpd{method='GET'}=Req) ->
-    Time = list_to_integer(chttpd:qs_value(Req, "time")),
-    receive snicklefart -> ok after Time -> ok end,
-    send_json(Req, {[{ok, true}]});
-handle_sleep_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-handle_all_dbs_req(#httpd{method='GET'}=Req) ->
-    ShardDbName = config:get("mem3", "shard_db", "dbs"),
-    %% shard_db is not sharded but mem3:shards treats it as an edge case
-    %% so it can be pushed thru fabric
-    {ok, Info} = fabric:get_db_info(ShardDbName),
-    Etag = couch_httpd:make_etag({Info}),
-    chttpd:etag_respond(Req, Etag, fun() ->
-        {ok, Resp} = chttpd:start_delayed_json_response(Req, 200, [{"Etag",Etag}]),
-        fabric:all_docs(ShardDbName, fun all_dbs_callback/2,
-            {nil, Resp}, #mrargs{})
-    end);
-handle_all_dbs_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-all_dbs_callback({total_and_offset, _Total, _Offset}, {_, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "["),
-    {ok, {"", Resp1}};
-all_dbs_callback({row, {Row}}, {Prepend, Resp}) ->
-    case couch_util:get_value(id, Row) of <<"_design", _/binary>> ->
-        {ok, {Prepend, Resp}};
-    DbName ->
-        {ok, Resp1} = chttpd:send_delayed_chunk(Resp, [Prepend, ?JSON_ENCODE(DbName)]),
-        {ok, {",", Resp1}}
-    end;
-all_dbs_callback(complete, {_, Resp}) ->
-    {ok, Resp1} = chttpd:send_delayed_chunk(Resp, "]"),
-    chttpd:end_delayed_json_response(Resp1);
-all_dbs_callback({error, Reason}, {_, Resp}) ->
-    chttpd:send_delayed_error(Resp, Reason).
-
-handle_task_status_req(#httpd{method='GET'}=Req) ->
-    {Replies, _BadNodes} = gen_server:multi_call(couch_task_status, all),
-    Response = lists:flatmap(fun({Node, Tasks}) ->
-        [{[{node,Node} | Task]} || Task <- Tasks]
-    end, Replies),
-    send_json(Req, lists:sort(Response));
-handle_task_status_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-handle_replicate_req(#httpd{method='POST', user_ctx=Ctx} = Req) ->
-    couch_httpd:validate_ctype(Req, "application/json"),
-    %% see HACK in chttpd.erl about replication
-    PostBody = get(post_body),
-    try replicate(PostBody, Ctx, mem3_rep_manager) of
-    {ok, {continuous, RepId}} ->
-        send_json(Req, 202, {[{ok, true}, {<<"_local_id">>, RepId}]});
-    {ok, {cancelled, RepId}} ->
-        send_json(Req, 200, {[{ok, true}, {<<"_local_id">>, RepId}]});
-    {ok, {JsonResults}} ->
-        send_json(Req, {[{ok, true} | JsonResults]});
-    {ok, stopped} ->
-        send_json(Req, 200, {[{ok, stopped}]});
-    {error, {Type, Details}} ->
-        send_json(Req, 500, {[{error, Type}, {reason, Details}]});
-    {error, not_found} ->
-        send_json(Req, 404, {[{error, not_found}]});
-    {error, Reason} ->
-        try
-            send_json(Req, 500, {[{error, Reason}]})
-        catch
-        exit:{json_encode, _} ->
-            send_json(Req, 500, {[{error, couch_util:to_binary(Reason)}]})
-        end
-    catch
-    throw:{db_not_found, Msg} ->
-        send_json(Req, 404, {[{error, db_not_found}, {reason, Msg}]});
-    throw:{unauthorized, Msg} ->
-        send_json(Req, 404, {[{error, unauthorized}, {reason, Msg}]})
-    end;
-handle_replicate_req(Req) ->
-    send_method_not_allowed(Req, "POST").
-
-replicate({Props} = PostBody, Ctx, Module) ->
-    Node = choose_node([
-        couch_util:get_value(<<"source">>, Props),
-        couch_util:get_value(<<"target">>, Props)
-    ]),
-    case rpc:call(Node, couch_rep, replicate, [PostBody, Ctx, Module]) of
-    {badrpc, Reason} ->
-        erlang:error(Reason);
-    Res ->
-        Res
-    end.
-
-choose_node(Key) when is_binary(Key) ->
-    Checksum = erlang:crc32(Key),
-    Nodes = lists:sort([node()|erlang:nodes()]),
-    lists:nth(1 + Checksum rem length(Nodes), Nodes);
-choose_node(Key) ->
-    choose_node(term_to_binary(Key)).
-
-handle_restart_req(#httpd{method='POST'}=Req) ->
-    couch_server_sup:restart_core_server(),
-    send_json(Req, 200, {[{ok, true}]});
-handle_restart_req(Req) ->
-    send_method_not_allowed(Req, "POST").
-
-
-handle_uuids_req(Req) ->
-    couch_httpd_misc_handlers:handle_uuids_req(Req).
-
-
-% Config request handler
-
-
-% GET /_config/
-% GET /_config
-handle_config_req(#httpd{method='GET', path_parts=[_]}=Req) ->
-    Grouped = lists:foldl(fun({{Section, Key}, Value}, Acc) ->
-        case dict:is_key(Section, Acc) of
-        true ->
-            dict:append(Section, {list_to_binary(Key), list_to_binary(Value)}, Acc);
-        false ->
-            dict:store(Section, [{list_to_binary(Key), list_to_binary(Value)}], Acc)
-        end
-    end, dict:new(), config:all()),
-    KVs = dict:fold(fun(Section, Values, Acc) ->
-        [{list_to_binary(Section), {Values}} | Acc]
-    end, [], Grouped),
-    send_json(Req, 200, {KVs});
-% GET /_config/Section
-handle_config_req(#httpd{method='GET', path_parts=[_,Section]}=Req) ->
-    KVs = [{list_to_binary(Key), list_to_binary(Value)}
-            || {Key, Value} <- config:get(Section)],
-    send_json(Req, 200, {KVs});
-% PUT /_config/Section/Key
-% "value"
-handle_config_req(#httpd{method='PUT', path_parts=[_, Section, Key]}=Req) ->
-    Value = chttpd:json_body(Req),
-    Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
-    OldValue = config:get(Section, Key, ""),
-    ok = config:set(Section, Key, ?b2l(Value), Persist),
-    send_json(Req, 200, list_to_binary(OldValue));
-% GET /_config/Section/Key
-handle_config_req(#httpd{method='GET', path_parts=[_, Section, Key]}=Req) ->
-    case config:get(Section, Key, null) of
-    null ->
-        throw({not_found, unknown_config_value});
-    Value ->
-        send_json(Req, 200, list_to_binary(Value))
-    end;
-% DELETE /_config/Section/Key
-handle_config_req(#httpd{method='DELETE',path_parts=[_,Section,Key]}=Req) ->
-    Persist = chttpd:header_value(Req, "X-Couch-Persist") /= "false",
-    case config:get(Section, Key, null) of
-    null ->
-        throw({not_found, unknown_config_value});
-    OldValue ->
-        config:delete(Section, Key, Persist),
-        send_json(Req, 200, list_to_binary(OldValue))
-    end;
-handle_config_req(Req) ->
-    send_method_not_allowed(Req, "GET,PUT,DELETE").
-
-% httpd log handlers
-
-handle_log_req(#httpd{method='GET'}=Req) ->
-    Bytes = list_to_integer(chttpd:qs_value(Req, "bytes", "1000")),
-    Offset = list_to_integer(chttpd:qs_value(Req, "offset", "0")),
-    Chunk = couch_log:read(Bytes, Offset),
-    {ok, Resp} = start_chunked_response(Req, 200, [
-        % send a plaintext response
-        {"Content-Type", "text/plain; charset=utf-8"},
-        {"Content-Length", integer_to_list(length(Chunk))}
-    ]),
-    send_chunk(Resp, Chunk),
-    send_chunk(Resp, "");
-handle_log_req(Req) ->
-    send_method_not_allowed(Req, "GET").
-
-% Note: this resource is exposed on the backdoor interface, but it's in chttpd
-% because it's not couch trunk
-handle_system_req(Req) ->
-    Other = erlang:memory(system) - lists:sum([X || {_,X} <-
-        erlang:memory([atom, code, binary, ets])]),
-    Memory = [{other, Other} | erlang:memory([atom, atom_used, processes,
-        processes_used, binary, code, ets])],
-    {NumberOfGCs, WordsReclaimed, _} = statistics(garbage_collection),
-    {{input, Input}, {output, Output}} = statistics(io),
-    send_json(Req, {[
-        {uptime, element(1,statistics(wall_clock)) div 1000},
-        {memory, {Memory}},
-        {run_queue, statistics(run_queue)},
-        {ets_table_count, length(ets:all())},
-        {context_switches, element(1, statistics(context_switches))},
-        {reductions, element(1, statistics(reductions))},
-        {garbage_collection_count, NumberOfGCs},
-        {words_reclaimed, WordsReclaimed},
-        {io_input, Input},
-        {io_output, Output},
-        {os_proc_count, couch_proc_manager:get_proc_count()},
-        {process_count, erlang:system_info(process_count)},
-        {process_limit, erlang:system_info(process_limit)},
-        {message_queues, message_queues(registered())},
-        {internal_replication_jobs, mem3_sync:get_backlog()},
-        {distribution, {get_distribution_stats()}}
-    ]}).
-
-get_distribution_stats() ->
-    lists:map(fun({Node, Socket}) ->
-        {ok, Stats} = inet:getstat(Socket),
-        {Node, {Stats}}
-    end, erlang:system_info(dist_ctrl)).
-
-handle_up_req(#httpd{method='GET'} = Req) ->
-    case config:get("couchdb", "maintenance_mode") of
-    "true" ->
-        send_json(Req, 404, {[{status, maintenance_mode}]});
-    _ ->
-        send_json(Req, 200, {[{status, ok}]})
-    end;
-
-handle_up_req(Req) ->
-    send_method_not_allowed(Req, "GET,HEAD").
-
-message_queues(Registered) ->
-    Queues = lists:map(fun(Name) ->
-        Type = message_queue_len,
-        {Type, Length} = process_info(whereis(Name), Type),
-        {Name, Length}
-    end, Registered),
-    {Queues}.

http://git-wip-us.apache.org/repos/asf/couchdb/blob/e41cfa40/src/chttpd/src/chttpd_rewrite.erl
----------------------------------------------------------------------
diff --git a/src/chttpd/src/chttpd_rewrite.erl b/src/chttpd/src/chttpd_rewrite.erl
deleted file mode 100644
index a18fa00..0000000
--- a/src/chttpd/src/chttpd_rewrite.erl
+++ /dev/null
@@ -1,456 +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.
-%
-% bind_path is based on bind method from Webmachine
-
-
-%% @doc Module for URL rewriting by pattern matching.
-
--module(chttpd_rewrite).
--export([handle_rewrite_req/3]).
--include_lib("couch/include/couch_db.hrl").
-
--define(SEPARATOR, $\/).
--define(MATCH_ALL, {bind, <<"*">>}).
-
-
-%% doc The http rewrite handler. All rewriting is done from
-%% /dbname/_design/ddocname/_rewrite by default.
-%%
-%% each rules should be in rewrites member of the design doc.
-%% Ex of a complete rule :
-%%
-%%  {
-%%      ....
-%%      "rewrites": [
-%%      {
-%%          "from": "",
-%%          "to": "index.html",
-%%          "method": "GET",
-%%          "query": {}
-%%      }
-%%      ]
-%%  }
-%%
-%%  from: is the path rule used to bind current uri to the rule. It
-%% use pattern matching for that.
-%%
-%%  to: rule to rewrite an url. It can contain variables depending on binding
-%% variables discovered during pattern matching and query args (url args and from
-%% the query member.)
-%%
-%%  method: method to bind the request method to the rule. by default "*"
-%%  query: query args you want to define they can contain dynamic variable
-%% by binding the key to the bindings
-%%
-%%
-%% to and from are path with  patterns. pattern can be string starting with ":" or
-%% "*". ex:
-%% /somepath/:var/*
-%%
-%% This path is converted in erlang list by splitting "/". Each var are
-%% converted in atom. "*" is converted to '*' atom. The pattern matching is done
-%% by splitting "/" in request url in a list of token. A string pattern will
-%% match equal token. The star atom ('*' in single quotes) will match any number
-%% of tokens, but may only be present as the last pathtern in a pathspec. If all
-%% tokens are matched and all pathterms are used, then the pathspec matches. It works
-%% like webmachine. Each identified token will be reused in to rule and in query
-%%
-%% The pattern matching is done by first matching the request method to a rule. by
-%% default all methods match a rule. (method is equal to "*" by default). Then
-%% It will try to match the path to one rule. If no rule match, then a 404 error
-%% is displayed.
-%%
-%% Once a rule is found we rewrite the request url using the "to" and
-%% "query" members. The identified token are matched to the rule and
-%% will replace var. if '*' is found in the rule it will contain the remaining
-%% part if it exists.
-%%
-%% Examples:
-%%
-%% Dispatch rule            URL             TO                  Tokens
-%%
-%% {"from": "/a/b",         /a/b?k=v        /some/b?k=v         var =:= b
-%% "to": "/some/"}                                              k = v
-%%
-%% {"from": "/a/b",         /a/b            /some/b?var=b       var =:= b
-%% "to": "/some/:var"}
-%%
-%% {"from": "/a",           /a              /some
-%% "to": "/some/*"}
-%%
-%% {"from": "/a/*",         /a/b/c          /some/b/c
-%% "to": "/some/*"}
-%%
-%% {"from": "/a",           /a              /some
-%% "to": "/some/*"}
-%%
-%% {"from": "/a/:foo/*",    /a/b/c          /some/b/c?foo=b     foo =:= b
-%% "to": "/some/:foo/*"}
-%%
-%% {"from": "/a/:foo",     /a/b             /some/?k=b&foo=b    foo =:= b
-%% "to": "/some",
-%%  "query": {
-%%      "k": ":foo"
-%%  }}
-%%
-%% {"from": "/a",           /a?foo=b        /some/b             foo =:= b
-%% "to": "/some/:foo",
-%%  }}
-
-
-
-handle_rewrite_req(#httpd{
-        path_parts=[DbName, <<"_design">>, DesignName, _Rewrite|PathParts],
-        method=Method,
-        mochi_req=MochiReq}=Req, _Db, DDoc) ->
-
-    % we are in a design handler
-    DesignId = <<"_design/", DesignName/binary>>,
-    Prefix = <<"/", DbName/binary, "/", DesignId/binary>>,
-    QueryList = lists:map(fun decode_query_value/1, couch_httpd:qs(Req)),
-
-    #doc{body={Props}} = DDoc,
-
-    % get rules from ddoc
-    case couch_util:get_value(<<"rewrites">>, Props) of
-        undefined ->
-            couch_httpd:send_error(Req, 404, <<"rewrite_error">>,
-                <<"Invalid path.">>);
-        Bin when is_binary(Bin) ->
-            couch_httpd:send_error(Req, 400, <<"rewrite_error">>,
-                <<"Rewrite rules are a String. They must be a JSON Array.">>);
-        Rules ->
-            % create dispatch list from rules
-            DispatchList =  [make_rule(Rule) || {Rule} <- Rules],
-            Method1 = couch_util:to_binary(Method),
-
-            %% get raw path by matching url to a rule.
-            RawPath = case try_bind_path(DispatchList, Method1,
-                    PathParts, QueryList) of
-                no_dispatch_path ->
-                    throw(not_found);
-                {NewPathParts, Bindings} ->
-                    Parts = [quote_plus(X) || X <- NewPathParts],
-
-                    % build new path, reencode query args, eventually convert
-                    % them to json
-                    Bindings1 = maybe_encode_bindings(Bindings),
-                    Path = binary_to_list(
-                        iolist_to_binary([
-                                string:join(Parts, [?SEPARATOR]),
-                                [["?", mochiweb_util:urlencode(Bindings1)]
-                                    || Bindings1 =/= [] ]
-                            ])),
-
-                    % if path is relative detect it and rewrite path
-                    case mochiweb_util:safe_relative_path(Path) of
-                        undefined ->
-                            ?b2l(Prefix) ++ "/" ++ Path;
-                        P1 ->
-                            ?b2l(Prefix) ++ "/" ++ P1
-                    end
-
-                end,
-
-            % normalize final path (fix levels "." and "..")
-            RawPath1 = ?b2l(iolist_to_binary(normalize_path(RawPath))),
-
-            twig:log(debug, "rewrite to ~p ~n", [RawPath1]),
-
-            % build a new mochiweb request
-            MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
-                                             MochiReq:get(method),
-                                             RawPath1,
-                                             MochiReq:get(version),
-                                             MochiReq:get(headers)),
-
-            % cleanup, It force mochiweb to reparse raw uri.
-            MochiReq1:cleanup(),
-
-            chttpd:handle_request(MochiReq1)
-        end.
-
-quote_plus({bind, X}) ->
-    mochiweb_util:quote_plus(X);
-quote_plus(X) ->
-    mochiweb_util:quote_plus(X).
-
-%% @doc Try to find a rule matching current url. If none is found
-%% 404 error not_found is raised
-try_bind_path([], _Method, _PathParts, _QueryList) ->
-    no_dispatch_path;
-try_bind_path([Dispatch|Rest], Method, PathParts, QueryList) ->
-    [{PathParts1, Method1}, RedirectPath, QueryArgs, Formats] = Dispatch,
-    case bind_method(Method1, Method) of
-        true ->
-            case bind_path(PathParts1, PathParts, []) of
-                {ok, Remaining, Bindings} ->
-                    Bindings1 = Bindings ++ QueryList,
-                    % we parse query args from the rule and fill
-                    % it eventually with bindings vars
-                    QueryArgs1 = make_query_list(QueryArgs, Bindings1,
-                        Formats, []),
-                    % remove params in QueryLists1 that are already in
-                    % QueryArgs1
-                    Bindings2 = lists:foldl(fun({K, V}, Acc) ->
-                        K1 = to_binding(K),
-                        KV = case couch_util:get_value(K1, QueryArgs1) of
-                            undefined -> [{K1, V}];
-                            _V1 -> []
-                        end,
-                        Acc ++ KV
-                    end, [], Bindings1),
-
-                    FinalBindings = Bindings2 ++ QueryArgs1,
-                    NewPathParts = make_new_path(RedirectPath, FinalBindings,
-                                    Remaining, []),
-                    {NewPathParts, FinalBindings};
-                fail ->
-                    try_bind_path(Rest, Method, PathParts, QueryList)
-            end;
-        false ->
-            try_bind_path(Rest, Method, PathParts, QueryList)
-    end.
-
-%% rewriting dynamically the quey list given as query member in
-%% rewrites. Each value is replaced by one binding or an argument
-%% passed in url.
-make_query_list([], _Bindings, _Formats, Acc) ->
-    Acc;
-make_query_list([{Key, {Value}}|Rest], Bindings, Formats, Acc) ->
-    Value1 = {Value},
-    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_binary(Value) ->
-    Value1 = replace_var(Value, Bindings, Formats),
-    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) when is_list(Value) ->
-    Value1 = replace_var(Value, Bindings, Formats),
-    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value1}|Acc]);
-make_query_list([{Key, Value}|Rest], Bindings, Formats, Acc) ->
-    make_query_list(Rest, Bindings, Formats, [{to_binding(Key), Value}|Acc]).
-
-replace_var(<<"*">>=Value, Bindings, Formats) ->
-    get_var(Value, Bindings, Value, Formats);
-replace_var(<<":", Var/binary>> = Value, Bindings, Formats) ->
-    get_var(Var, Bindings, Value, Formats);
-replace_var(Value, _Bindings, _Formats) when is_binary(Value) ->
-    Value;
-replace_var(Value, Bindings, Formats) when is_list(Value) ->
-    lists:reverse(lists:foldl(fun
-                (<<":", Var/binary>>=Value1, Acc) ->
-                    [get_var(Var, Bindings, Value1, Formats)|Acc];
-                (Value1, Acc) ->
-                    [Value1|Acc]
-            end, [], Value));
-replace_var(Value, _Bindings, _Formats) ->
-    Value.
-
-maybe_json(Key, Value) ->
-    case lists:member(Key, [<<"key">>, <<"startkey">>, <<"start_key">>,
-                <<"endkey">>, <<"end_key">>, <<"keys">>]) of
-        true ->
-            ?JSON_ENCODE(Value);
-        false ->
-            Value
-    end.
-
-get_var(VarName, Props, Default, Formats) ->
-    VarName1 = to_binding(VarName),
-    Val = couch_util:get_value(VarName1, Props, Default),
-    maybe_format(VarName, Val, Formats).
-
-maybe_format(VarName, Value, Formats) ->
-    case couch_util:get_value(VarName, Formats) of
-        undefined ->
-             Value;
-        Format ->
-            format(Format, Value)
-    end.
-
-format(<<"int">>, Value) when is_integer(Value) ->
-    Value;
-format(<<"int">>, Value) when is_binary(Value) ->
-    format(<<"int">>, ?b2l(Value));
-format(<<"int">>, Value) when is_list(Value) ->
-    case (catch list_to_integer(Value)) of
-        IntVal when is_integer(IntVal) ->
-            IntVal;
-        _ ->
-            Value
-    end;
-format(<<"bool">>, Value) when is_binary(Value) ->
-    format(<<"bool">>, ?b2l(Value));
-format(<<"bool">>, Value) when is_list(Value) ->
-    case string:to_lower(Value) of
-        "true" -> true;
-        "false" -> false;
-        _ -> Value
-    end;
-format(_Format, Value) ->
-   Value.
-
-%% doc: build new patch from bindings. bindings are query args
-%% (+ dynamic query rewritten if needed) and bindings found in
-%% bind_path step.
-make_new_path([], _Bindings, _Remaining, Acc) ->
-    lists:reverse(Acc);
-make_new_path([?MATCH_ALL], _Bindings, Remaining, Acc) ->
-    Acc1 = lists:reverse(Acc) ++ Remaining,
-    Acc1;
-make_new_path([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) ->
-    Acc1 = lists:reverse(Acc) ++ Remaining,
-    Acc1;
-make_new_path([{bind, P}|Rest], Bindings, Remaining, Acc) ->
-    P2 = case couch_util:get_value({bind, P}, Bindings) of
-        undefined -> << "undefined">>;
-        P1 ->
-            iolist_to_binary(P1)
-    end,
-    make_new_path(Rest, Bindings, Remaining, [P2|Acc]);
-make_new_path([P|Rest], Bindings, Remaining, Acc) ->
-    make_new_path(Rest, Bindings, Remaining, [P|Acc]).
-
-
-%% @doc If method of the query fith the rule method. If the
-%% method rule is '*', which is the default, all
-%% request method will bind. It allows us to make rules
-%% depending on HTTP method.
-bind_method(?MATCH_ALL, _Method) ->
-    true;
-bind_method({bind, Method}, Method) ->
-    true;
-bind_method(_, _) ->
-    false.
-
-
-%% @doc bind path. Using the rule from we try to bind variables given
-%% to the current url by pattern matching
-bind_path([], [], Bindings) ->
-    {ok, [], Bindings};
-bind_path([?MATCH_ALL], Rest, Bindings) when is_list(Rest) ->
-    {ok, Rest, Bindings};
-bind_path(_, [], _) ->
-    fail;
-bind_path([{bind, Token}|RestToken],[Match|RestMatch],Bindings) ->
-    bind_path(RestToken, RestMatch, [{{bind, Token}, Match}|Bindings]);
-bind_path([Token|RestToken], [Token|RestMatch], Bindings) ->
-    bind_path(RestToken, RestMatch, Bindings);
-bind_path(_, _, _) ->
-    fail.
-
-
-%% normalize path.
-normalize_path(Path)  ->
-    "/" ++ string:join(normalize_path1(string:tokens(Path,
-                "/"), []), [?SEPARATOR]).
-
-
-normalize_path1([], Acc) ->
-    lists:reverse(Acc);
-normalize_path1([".."|Rest], Acc) ->
-    Acc1 = case Acc of
-        [] -> [".."|Acc];
-        [T|_] when T =:= ".." -> [".."|Acc];
-        [_|R] -> R
-    end,
-    normalize_path1(Rest, Acc1);
-normalize_path1(["."|Rest], Acc) ->
-    normalize_path1(Rest, Acc);
-normalize_path1([Path|Rest], Acc) ->
-    normalize_path1(Rest, [Path|Acc]).
-
-
-%% @doc transform json rule in erlang for pattern matching
-make_rule(Rule) ->
-    Method = case couch_util:get_value(<<"method">>, Rule) of
-        undefined -> ?MATCH_ALL;
-        M -> to_binding(M)
-    end,
-    QueryArgs = case couch_util:get_value(<<"query">>, Rule) of
-        undefined -> [];
-        {Args} -> Args
-        end,
-    FromParts  = case couch_util:get_value(<<"from">>, Rule) of
-        undefined -> [?MATCH_ALL];
-        From ->
-            parse_path(From)
-        end,
-    ToParts  = case couch_util:get_value(<<"to">>, Rule) of
-        undefined ->
-            throw({error, invalid_rewrite_target});
-        To ->
-            parse_path(To)
-        end,
-    Formats = case couch_util:get_value(<<"formats">>, Rule) of
-        undefined -> [];
-        {Fmts} -> Fmts
-    end,
-    [{FromParts, Method}, ToParts, QueryArgs, Formats].
-
-parse_path(Path) ->
-    {ok, SlashRE} = re:compile(<<"\\/">>),
-    path_to_list(re:split(Path, SlashRE), [], 0).
-
-%% @doc convert a path rule (from or to) to an erlang list
-%% * and path variable starting by ":" are converted
-%% in erlang atom.
-path_to_list([], Acc, _DotDotCount) ->
-    lists:reverse(Acc);
-path_to_list([<<>>|R], Acc, DotDotCount) ->
-    path_to_list(R, Acc, DotDotCount);
-path_to_list([<<"*">>|R], Acc, DotDotCount) ->
-    path_to_list(R, [?MATCH_ALL|Acc], DotDotCount);
-path_to_list([<<"..">>|R], Acc, DotDotCount) when DotDotCount == 2 ->
-    case config:get("httpd", "secure_rewrites", "true") of
-    "false" ->
-        path_to_list(R, [<<"..">>|Acc], DotDotCount+1);
-    _Else ->
-        twig:log(notice, "insecure_rewrite_rule ~p blocked", [lists:reverse(Acc) ++ [<<"..">>] ++ R]),
-        throw({insecure_rewrite_rule, "too many ../.. segments"})
-    end;
-path_to_list([<<"..">>|R], Acc, DotDotCount) ->
-    path_to_list(R, [<<"..">>|Acc], DotDotCount+1);
-path_to_list([P|R], Acc, DotDotCount) ->
-    P1 = case P of
-        <<":", Var/binary>> ->
-            to_binding(Var);
-        _ -> P
-    end,
-    path_to_list(R, [P1|Acc], DotDotCount).
-
-maybe_encode_bindings([]) ->
-    [];
-maybe_encode_bindings(Props) ->
-    lists:foldl(fun
-            ({{bind, <<"*">>}, _V}, Acc) ->
-                Acc;
-            ({{bind, K}, V}, Acc) ->
-                V1 = iolist_to_binary(maybe_json(K, V)),
-                [{K, V1}|Acc]
-        end, [], Props).
-
-decode_query_value({K,V}) ->
-    case lists:member(K, ["key", "startkey", "start_key",
-                "endkey", "end_key", "keys"]) of
-        true ->
-            {to_binding(K), ?JSON_DECODE(V)};
-        false ->
-            {to_binding(K), ?l2b(V)}
-    end.
-
-to_binding({bind, V}) ->
-    {bind, V};
-to_binding(V) when is_list(V) ->
-    to_binding(?l2b(V));
-to_binding(V) ->
-    {bind, V}.