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/06 18:40:03 UTC

[21/50] [abbrv] inital move to rebar compilation

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_httpd_proxy.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_proxy.erl b/src/couch_httpd_proxy.erl
new file mode 100644
index 0000000..6a4557c
--- /dev/null
+++ b/src/couch_httpd_proxy.erl
@@ -0,0 +1,426 @@
+% 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_proxy).
+
+-export([handle_proxy_req/2]).
+
+-include("couch_db.hrl").
+-include_lib("ibrowse/include/ibrowse.hrl").
+
+-define(TIMEOUT, infinity).
+-define(PKT_SIZE, 4096).
+
+
+handle_proxy_req(Req, ProxyDest) ->
+    Method = get_method(Req),
+    Url = get_url(Req, ProxyDest),
+    Version = get_version(Req),
+    Headers = get_headers(Req),
+    Body = get_body(Req),
+    Options = [
+        {http_vsn, Version},
+        {headers_as_is, true},
+        {response_format, binary},
+        {stream_to, {self(), once}}
+    ],
+    case ibrowse:send_req(Url, Headers, Method, Body, Options, ?TIMEOUT) of
+        {ibrowse_req_id, ReqId} ->
+            stream_response(Req, ProxyDest, ReqId);
+        {error, Reason} ->
+            throw({error, Reason})
+    end.
+
+
+get_method(#httpd{mochi_req=MochiReq}) ->
+    case MochiReq:get(method) of
+        Method when is_atom(Method) ->
+            list_to_atom(string:to_lower(atom_to_list(Method)));
+        Method when is_list(Method) ->
+            list_to_atom(string:to_lower(Method));
+        Method when is_binary(Method) ->
+            list_to_atom(string:to_lower(?b2l(Method)))
+    end.
+
+
+get_url(Req, ProxyDest) when is_binary(ProxyDest) ->
+    get_url(Req, ?b2l(ProxyDest));
+get_url(#httpd{mochi_req=MochiReq}=Req, ProxyDest) ->
+    BaseUrl = case mochiweb_util:partition(ProxyDest, "/") of
+        {[], "/", _} -> couch_httpd:absolute_uri(Req, ProxyDest);
+        _ -> ProxyDest
+    end,
+    ProxyPrefix = "/" ++ ?b2l(hd(Req#httpd.path_parts)),
+    RequestedPath = MochiReq:get(raw_path),
+    case mochiweb_util:partition(RequestedPath, ProxyPrefix) of
+        {[], ProxyPrefix, []} ->
+            BaseUrl;
+        {[], ProxyPrefix, [$/ | DestPath]} ->
+            remove_trailing_slash(BaseUrl) ++ "/" ++ DestPath;
+        {[], ProxyPrefix, DestPath} ->
+            remove_trailing_slash(BaseUrl) ++ "/" ++ DestPath;
+        _Else ->
+            throw({invalid_url_path, {ProxyPrefix, RequestedPath}})
+    end.
+
+get_version(#httpd{mochi_req=MochiReq}) ->
+    MochiReq:get(version).
+
+
+get_headers(#httpd{mochi_req=MochiReq}) ->
+    to_ibrowse_headers(mochiweb_headers:to_list(MochiReq:get(headers)), []).
+
+to_ibrowse_headers([], Acc) ->
+    lists:reverse(Acc);
+to_ibrowse_headers([{K, V} | Rest], Acc) when is_atom(K) ->
+    to_ibrowse_headers([{atom_to_list(K), V} | Rest], Acc);
+to_ibrowse_headers([{K, V} | Rest], Acc) when is_list(K) ->
+    case string:to_lower(K) of
+        "content-length" ->
+            to_ibrowse_headers(Rest, [{content_length, V} | Acc]);
+        % This appears to make ibrowse too smart.
+        %"transfer-encoding" ->
+        %    to_ibrowse_headers(Rest, [{transfer_encoding, V} | Acc]);
+        _ ->
+            to_ibrowse_headers(Rest, [{K, V} | Acc])
+    end.
+
+get_body(#httpd{method='GET'}) ->
+    fun() -> eof end;
+get_body(#httpd{method='HEAD'}) ->
+    fun() -> eof end;
+get_body(#httpd{method='DELETE'}) ->
+    fun() -> eof end;
+get_body(#httpd{mochi_req=MochiReq}) ->
+    case MochiReq:get(body_length) of
+        undefined ->
+            <<>>;
+        {unknown_transfer_encoding, Unknown} ->
+            exit({unknown_transfer_encoding, Unknown});
+        chunked ->
+            {fun stream_chunked_body/1, {init, MochiReq, 0}};
+        0 ->
+            <<>>;
+        Length when is_integer(Length) andalso Length > 0 ->
+            {fun stream_length_body/1, {init, MochiReq, Length}};
+        Length ->
+            exit({invalid_body_length, Length})
+    end.
+
+
+remove_trailing_slash(Url) ->
+    rem_slash(lists:reverse(Url)).
+
+rem_slash([]) ->
+    [];
+rem_slash([$\s | RevUrl]) ->
+    rem_slash(RevUrl);
+rem_slash([$\t | RevUrl]) ->
+    rem_slash(RevUrl);
+rem_slash([$\r | RevUrl]) ->
+    rem_slash(RevUrl);
+rem_slash([$\n | RevUrl]) ->
+    rem_slash(RevUrl);
+rem_slash([$/ | RevUrl]) ->
+    rem_slash(RevUrl);
+rem_slash(RevUrl) ->
+    lists:reverse(RevUrl).
+
+
+stream_chunked_body({init, MReq, 0}) ->
+    % First chunk, do expect-continue dance.
+    init_body_stream(MReq),
+    stream_chunked_body({stream, MReq, 0, [], ?PKT_SIZE});
+stream_chunked_body({stream, MReq, 0, Buf, BRem}) ->
+    % Finished a chunk, get next length. If next length
+    % is 0, its time to try and read trailers.
+    {CRem, Data} = read_chunk_length(MReq),
+    case CRem of
+        0 ->
+            BodyData = lists:reverse(Buf, Data),
+            {ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}};
+        _ ->
+            stream_chunked_body(
+                {stream, MReq, CRem, [Data | Buf], BRem-size(Data)}
+            )
+    end;
+stream_chunked_body({stream, MReq, CRem, Buf, BRem}) when BRem =< 0 ->
+    % Time to empty our buffers to the upstream socket.
+    BodyData = lists:reverse(Buf),
+    {ok, BodyData, {stream, MReq, CRem, [], ?PKT_SIZE}};
+stream_chunked_body({stream, MReq, CRem, Buf, BRem}) ->
+    % Buffer some more data from the client.
+    Length = lists:min([CRem, BRem]),
+    Socket = MReq:get(socket),
+    NewState = case mochiweb_socket:recv(Socket, Length, ?TIMEOUT) of
+        {ok, Data} when size(Data) == CRem ->
+            case mochiweb_socket:recv(Socket, 2, ?TIMEOUT) of
+                {ok, <<"\r\n">>} ->
+                    {stream, MReq, 0, [<<"\r\n">>, Data | Buf], BRem-Length-2};
+                _ ->
+                    exit(normal)
+            end;
+        {ok, Data} ->
+            {stream, MReq, CRem-Length, [Data | Buf], BRem-Length};
+        _ ->
+            exit(normal)
+    end,
+    stream_chunked_body(NewState);
+stream_chunked_body({trailers, MReq, Buf, BRem}) when BRem =< 0 ->
+    % Empty our buffers and send data upstream.
+    BodyData = lists:reverse(Buf),
+    {ok, BodyData, {trailers, MReq, [], ?PKT_SIZE}};
+stream_chunked_body({trailers, MReq, Buf, BRem}) ->
+    % Read another trailer into the buffer or stop on an
+    % empty line.
+    Socket = MReq:get(socket),
+    mochiweb_socket:setopts(Socket, [{packet, line}]),
+    case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of
+        {ok, <<"\r\n">>} ->
+            mochiweb_socket:setopts(Socket, [{packet, raw}]),
+            BodyData = lists:reverse(Buf, <<"\r\n">>),
+            {ok, BodyData, eof};
+        {ok, Footer} ->
+            mochiweb_socket:setopts(Socket, [{packet, raw}]),
+            NewState = {trailers, MReq, [Footer | Buf], BRem-size(Footer)},
+            stream_chunked_body(NewState);
+        _ ->
+            exit(normal)
+    end;
+stream_chunked_body(eof) ->
+    % Tell ibrowse we're done sending data.
+    eof.
+
+
+stream_length_body({init, MochiReq, Length}) ->
+    % Do the expect-continue dance
+    init_body_stream(MochiReq),
+    stream_length_body({stream, MochiReq, Length});
+stream_length_body({stream, _MochiReq, 0}) ->
+    % Finished streaming.
+    eof;
+stream_length_body({stream, MochiReq, Length}) ->
+    BufLen = lists:min([Length, ?PKT_SIZE]),
+    case MochiReq:recv(BufLen) of
+        <<>> -> eof;
+        Bin -> {ok, Bin, {stream, MochiReq, Length-BufLen}}
+    end.
+
+
+init_body_stream(MochiReq) ->
+    Expect = case MochiReq:get_header_value("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.
+
+
+read_chunk_length(MochiReq) ->
+    Socket = MochiReq:get(socket),
+    mochiweb_socket:setopts(Socket, [{packet, line}]),
+    case mochiweb_socket:recv(Socket, 0, ?TIMEOUT) of
+        {ok, Header} ->
+            mochiweb_socket:setopts(Socket, [{packet, raw}]),
+            Splitter = fun(C) ->
+                C =/= $\r andalso C =/= $\n andalso C =/= $\s
+            end,
+            {Hex, _Rest} = lists:splitwith(Splitter, ?b2l(Header)),
+            {mochihex:to_int(Hex), Header};
+        _ ->
+            exit(normal)
+    end.
+
+
+stream_response(Req, ProxyDest, ReqId) ->
+    receive
+        {ibrowse_async_headers, ReqId, "100", _} ->
+            % ibrowse doesn't handle 100 Continue responses which
+            % means we have to discard them so the proxy client
+            % doesn't get confused.
+            ibrowse:stream_next(ReqId),
+            stream_response(Req, ProxyDest, ReqId);
+        {ibrowse_async_headers, ReqId, Status, Headers} ->
+            {Source, Dest} = get_urls(Req, ProxyDest),
+            FixedHeaders = fix_headers(Source, Dest, Headers, []),
+            case body_length(FixedHeaders) of
+                chunked ->
+                    {ok, Resp} = couch_httpd:start_chunked_response(
+                        Req, list_to_integer(Status), FixedHeaders
+                    ),
+                    ibrowse:stream_next(ReqId),
+                    stream_chunked_response(Req, ReqId, Resp),
+                    {ok, Resp};
+                Length when is_integer(Length) ->
+                    {ok, Resp} = couch_httpd:start_response_length(
+                        Req, list_to_integer(Status), FixedHeaders, Length
+                    ),
+                    ibrowse:stream_next(ReqId),
+                    stream_length_response(Req, ReqId, Resp),
+                    {ok, Resp};
+                _ ->
+                    {ok, Resp} = couch_httpd:start_response(
+                        Req, list_to_integer(Status), FixedHeaders
+                    ),
+                    ibrowse:stream_next(ReqId),
+                    stream_length_response(Req, ReqId, Resp),
+                    % XXX: MochiWeb apparently doesn't look at the
+                    % response to see if it must force close the
+                    % connection. So we help it out here.
+                    erlang:put(mochiweb_request_force_close, true),
+                    {ok, Resp}
+            end
+    end.
+
+
+stream_chunked_response(Req, ReqId, Resp) ->
+    receive
+        {ibrowse_async_response, ReqId, {error, Reason}} ->
+            throw({error, Reason});
+        {ibrowse_async_response, ReqId, Chunk} ->
+            couch_httpd:send_chunk(Resp, Chunk),
+            ibrowse:stream_next(ReqId),
+            stream_chunked_response(Req, ReqId, Resp);
+        {ibrowse_async_response_end, ReqId} ->
+            couch_httpd:last_chunk(Resp)
+    end.
+
+
+stream_length_response(Req, ReqId, Resp) ->
+    receive
+        {ibrowse_async_response, ReqId, {error, Reason}} ->
+            throw({error, Reason});
+        {ibrowse_async_response, ReqId, Chunk} ->
+            couch_httpd:send(Resp, Chunk),
+            ibrowse:stream_next(ReqId),
+            stream_length_response(Req, ReqId, Resp);
+        {ibrowse_async_response_end, ReqId} ->
+            ok
+    end.
+
+
+get_urls(Req, ProxyDest) ->
+    SourceUrl = couch_httpd:absolute_uri(Req, "/" ++ hd(Req#httpd.path_parts)),
+    Source = parse_url(?b2l(iolist_to_binary(SourceUrl))),
+    case (catch parse_url(ProxyDest)) of
+        Dest when is_record(Dest, url) ->
+            {Source, Dest};
+        _ ->
+            DestUrl = couch_httpd:absolute_uri(Req, ProxyDest),
+            {Source, parse_url(DestUrl)}
+    end.
+
+
+fix_headers(_, _, [], Acc) ->
+    lists:reverse(Acc);
+fix_headers(Source, Dest, [{K, V} | Rest], Acc) ->
+    Fixed = case string:to_lower(K) of
+        "location" -> rewrite_location(Source, Dest, V);
+        "content-location" -> rewrite_location(Source, Dest, V);
+        "uri" -> rewrite_location(Source, Dest, V);
+        "destination" -> rewrite_location(Source, Dest, V);
+        "set-cookie" -> rewrite_cookie(Source, Dest, V);
+        _ -> V
+    end,
+    fix_headers(Source, Dest, Rest, [{K, Fixed} | Acc]).
+
+
+rewrite_location(Source, #url{host=Host, port=Port, protocol=Proto}, Url) ->
+    case (catch parse_url(Url)) of
+        #url{host=Host, port=Port, protocol=Proto} = Location ->
+            DestLoc = #url{
+                protocol=Source#url.protocol,
+                host=Source#url.host,
+                port=Source#url.port,
+                path=join_url_path(Source#url.path, Location#url.path)
+            },
+            url_to_url(DestLoc);
+        #url{} ->
+            Url;
+        _ ->
+            url_to_url(Source#url{path=join_url_path(Source#url.path, Url)})
+    end.
+
+
+rewrite_cookie(_Source, _Dest, Cookie) ->
+    Cookie.
+
+
+parse_url(Url) when is_binary(Url) ->
+    ibrowse_lib:parse_url(?b2l(Url));
+parse_url(Url) when is_list(Url) ->
+    ibrowse_lib:parse_url(?b2l(iolist_to_binary(Url))).
+
+
+join_url_path(Src, Dst) ->
+    Src2 = case lists:reverse(Src) of
+        "/" ++ RestSrc -> lists:reverse(RestSrc);
+        _ -> Src
+    end,
+    Dst2 = case Dst of
+        "/" ++ RestDst -> RestDst;
+        _ -> Dst
+    end,
+    Src2 ++ "/" ++ Dst2.
+
+
+url_to_url(#url{host=Host, port=Port, path=Path, protocol=Proto} = Url) ->
+    LPort = case {Proto, Port} of
+        {http, 80} -> "";
+        {https, 443} -> "";
+        _ -> ":" ++ integer_to_list(Port)
+    end,
+    LPath = case Path of
+        "/" ++ _RestPath -> Path;
+        _ -> "/" ++ Path
+    end,
+    HostPart = case Url#url.host_type of
+        ipv6_address ->
+            "[" ++ Host ++ "]";
+        _ ->
+            Host
+    end,
+    atom_to_list(Proto) ++ "://" ++ HostPart ++ LPort ++ LPath.
+
+
+body_length(Headers) ->
+    case is_chunked(Headers) of
+        true -> chunked;
+        _ -> content_length(Headers)
+    end.
+
+
+is_chunked([]) ->
+    false;
+is_chunked([{K, V} | Rest]) ->
+    case string:to_lower(K) of
+        "transfer-encoding" ->
+            string:to_lower(V) == "chunked";
+        _ ->
+            is_chunked(Rest)
+    end.
+
+content_length([]) ->
+    undefined;
+content_length([{K, V} | Rest]) ->
+    case string:to_lower(K) of
+        "content-length" ->
+            list_to_integer(V);
+        _ ->
+            content_length(Rest)
+    end.
+

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_httpd_rewrite.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_rewrite.erl b/src/couch_httpd_rewrite.erl
new file mode 100644
index 0000000..1187397
--- /dev/null
+++ b/src/couch_httpd_rewrite.erl
@@ -0,0 +1,484 @@
+% 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(couch_httpd_rewrite).
+-export([handle_rewrite_req/3]).
+-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 = <<"/", (?l2b(couch_util:url_encode(DbName)))/binary, "/", DesignId/binary>>,
+    QueryList = lists:map(fun decode_query_value/1, couch_httpd:qs(Req)),
+
+    RewritesSoFar = erlang:get(?REWRITE_COUNT),
+    MaxRewrites = list_to_integer(couch_config:get("httpd", "rewrite_limit", "100")),
+    case RewritesSoFar >= MaxRewrites of
+        true ->
+            throw({bad_request, <<"Exceeded rewrite recursion limit">>});
+        false ->
+            erlang:put(?REWRITE_COUNT, RewritesSoFar + 1)
+    end,
+
+    #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))),
+
+            % In order to do OAuth correctly, we have to save the
+            % requested path. We use default so chained rewriting
+            % wont replace the original header.
+            Headers = mochiweb_headers:default("x-couchdb-requested-path",
+                                             MochiReq:get(raw_path),
+                                             MochiReq:get(headers)),
+
+            ?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),
+                                             Headers),
+
+            % cleanup, It force mochiweb to reparse raw uri.
+            MochiReq1:cleanup(),
+
+            #httpd{
+                db_url_handlers = DbUrlHandlers,
+                design_url_handlers = DesignUrlHandlers,
+                default_fun = DefaultFun,
+                url_handlers = UrlHandlers,
+                user_ctx = UserCtx,
+               auth = Auth
+            } = Req,
+
+            erlang:put(pre_rewrite_auth, Auth),
+            erlang:put(pre_rewrite_user_ctx, UserCtx),
+            couch_httpd:handle_request_int(MochiReq1, DefaultFun,
+                    UrlHandlers, DbUrlHandlers, DesignUrlHandlers)
+        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], [Match|_RestMatch]=Rest, Bindings) ->
+    {ok, Rest, [{?MATCH_ALL, Match}|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 couch_config:get("httpd", "secure_rewrites", "true") of
+    "false" ->
+        path_to_list(R, [<<"..">>|Acc], DotDotCount+1);
+    _Else ->
+        ?LOG_INFO("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}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_httpd_stats_handlers.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_stats_handlers.erl b/src/couch_httpd_stats_handlers.erl
new file mode 100644
index 0000000..d6973f6
--- /dev/null
+++ b/src/couch_httpd_stats_handlers.erl
@@ -0,0 +1,56 @@
+% 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_stats_handlers).
+-include("couch_db.hrl").
+
+-export([handle_stats_req/1]).
+-import(couch_httpd, [
+    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, send_error/4
+]).
+
+handle_stats_req(#httpd{method='GET', path_parts=[_]}=Req) ->
+    flush(Req),
+    send_json(Req, couch_stats_aggregator:all(range(Req)));
+
+handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod]}) ->
+    throw({bad_request, <<"Stat names must have exactly two parts.">>});
+
+handle_stats_req(#httpd{method='GET', path_parts=[_, Mod, Key]}=Req) ->
+    flush(Req),
+    Stats = couch_stats_aggregator:get_json({list_to_atom(binary_to_list(Mod)),
+        list_to_atom(binary_to_list(Key))}, range(Req)),
+    send_json(Req, {[{Mod, {[{Key, Stats}]}}]});
+
+handle_stats_req(#httpd{method='GET', path_parts=[_, _Mod, _Key | _Extra]}) ->
+    throw({bad_request, <<"Stat names must have exactly two parts.">>});
+
+handle_stats_req(Req) ->
+    send_method_not_allowed(Req, "GET").
+
+range(Req) ->
+    case couch_util:get_value("range", couch_httpd:qs(Req)) of
+        undefined ->
+            0;
+        Value ->
+            list_to_integer(Value)
+    end.
+
+flush(Req) ->
+    case couch_util:get_value("flush", couch_httpd:qs(Req)) of
+        "true" ->
+            couch_stats_aggregator:collect_sample();
+        _Else ->
+            ok
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_httpd_vhost.erl
----------------------------------------------------------------------
diff --git a/src/couch_httpd_vhost.erl b/src/couch_httpd_vhost.erl
new file mode 100644
index 0000000..4c3ebfe
--- /dev/null
+++ b/src/couch_httpd_vhost.erl
@@ -0,0 +1,383 @@
+% 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_vhost).
+-behaviour(gen_server).
+
+-export([start_link/0, config_change/2, reload/0, get_state/0, dispatch_host/1]).
+-export([urlsplit_netloc/2, redirect_to_vhost/2]).
+-export([host/1, split_host_port/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-include("couch_db.hrl").
+
+-define(SEPARATOR, $\/).
+-define(MATCH_ALL, {bind, '*'}).
+
+-record(vhosts_state, {
+        vhosts,
+        vhost_globals,
+        vhosts_fun}).
+
+%% doc the vhost manager.
+%% This gen_server keep state of vhosts added to the ini and try to
+%% match the Host header (or forwarded) against rules built against
+%% vhost list.
+%%
+%% Declaration of vhosts take place in the configuration file :
+%%
+%% [vhosts]
+%% example.com = /example
+%% *.example.com = /example
+%%
+%% The first line will rewrite the rquest to display the content of the
+%% example database. This rule works only if the Host header is
+%% 'example.com' and won't work for CNAMEs. Second rule on the other hand
+%% match all CNAMES to example db. So www.example.com or db.example.com
+%% will work.
+%%
+%% The wildcard ('*') should always be the last in the cnames:
+%%
+%%      "*.db.example.com = /"  will match all cname on top of db
+%% examples to the root of the machine.
+%%
+%%
+%% Rewriting Hosts to path
+%% -----------------------
+%%
+%% Like in the _rewrite handler you could match some variable and use
+%them to create the target path. Some examples:
+%%
+%%    [vhosts]
+%%    *.example.com = /*
+%%    :dbname.example.com = /:dbname
+%%    :ddocname.:dbname.example.com = /:dbname/_design/:ddocname/_rewrite
+%%
+%% First rule pass wildcard as dbname, second do the same but use a
+%% variable name and the third one allows you to use any app with
+%% @ddocname in any db with @dbname .
+%%
+%% You could also change the default function to handle request by
+%% changing the setting `redirect_vhost_handler` in `httpd` section of
+%% the Ini:
+%%
+%%    [httpd]
+%%    redirect_vhost_handler = {Module, Fun}
+%%
+%% The function take 2 args : the mochiweb request object and the target
+%%% path.
+
+start_link() ->
+    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%% @doc reload vhosts rules
+reload() ->
+    gen_server:call(?MODULE, reload).
+
+get_state() ->
+    gen_server:call(?MODULE, get_state).
+
+%% @doc Try to find a rule matching current Host heade. some rule is
+%% found it rewrite the Mochiweb Request else it return current Request.
+dispatch_host(MochiReq) ->
+    #vhosts_state{
+        vhost_globals = VHostGlobals,
+        vhosts = VHosts,
+        vhosts_fun=Fun} = get_state(),
+
+    {"/" ++ VPath, Query, Fragment} = mochiweb_util:urlsplit_path(MochiReq:get(raw_path)),
+    VPathParts =  string:tokens(VPath, "/"),
+
+    VHost = host(MochiReq),
+    {VHostParts, VhostPort} = split_host_port(VHost),
+    FinalMochiReq = case try_bind_vhost(VHosts, lists:reverse(VHostParts),
+            VhostPort, VPathParts) of
+        no_vhost_matched -> MochiReq;
+        {VhostTarget, NewPath} ->
+            case vhost_global(VHostGlobals, MochiReq) of
+                true ->
+                    MochiReq;
+                _Else ->
+                    NewPath1 = mochiweb_util:urlunsplit_path({NewPath, Query,
+                                          Fragment}),
+                    MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
+                                      MochiReq:get(method),
+                                      NewPath1,
+                                      MochiReq:get(version),
+                                      MochiReq:get(headers)),
+                    Fun(MochiReq1, VhostTarget)
+            end
+    end,
+    FinalMochiReq.
+
+append_path("/"=_Target, "/"=_Path) ->
+    "/";
+append_path(Target, Path) ->
+    Target ++ Path.
+
+% default redirect vhost handler
+redirect_to_vhost(MochiReq, VhostTarget) ->
+    Path = MochiReq:get(raw_path),
+    Target = append_path(VhostTarget, Path),
+
+    ?LOG_DEBUG("Vhost Target: '~p'~n", [Target]),
+
+    Headers = mochiweb_headers:enter("x-couchdb-vhost-path", Path,
+        MochiReq:get(headers)),
+
+    % build a new mochiweb request
+    MochiReq1 = mochiweb_request:new(MochiReq:get(socket),
+                                      MochiReq:get(method),
+                                      Target,
+                                      MochiReq:get(version),
+                                      Headers),
+    % cleanup, It force mochiweb to reparse raw uri.
+    MochiReq1:cleanup(),
+    MochiReq1.
+
+%% if so, then it will not be rewritten, but will run as a normal couchdb request.
+%* normally you'd use this for _uuids _utils and a few of the others you want to
+%% keep available on vhosts. You can also use it to make databases 'global'.
+vhost_global( VhostGlobals, MochiReq) ->
+    RawUri = MochiReq:get(raw_path),
+    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
+
+    Front = case couch_httpd:partition(Path) of
+    {"", "", ""} ->
+        "/"; % Special case the root url handler
+    {FirstPart, _, _} ->
+        FirstPart
+    end,
+    [true] == [true||V <- VhostGlobals, V == Front].
+
+%% bind host
+%% first it try to bind the port then the hostname.
+try_bind_vhost([], _HostParts, _Port, _PathParts) ->
+    no_vhost_matched;
+try_bind_vhost([VhostSpec|Rest], HostParts, Port, PathParts) ->
+    {{VHostParts, VPort, VPath}, Path} = VhostSpec,
+    case bind_port(VPort, Port) of
+        ok ->
+            case bind_vhost(lists:reverse(VHostParts), HostParts, []) of
+                {ok, Bindings, Remainings} ->
+                    case bind_path(VPath, PathParts) of
+                        {ok, PathParts1} ->
+                            Path1 = make_target(Path, Bindings, Remainings, []),
+                            {make_path(Path1), make_path(PathParts1)};
+                        fail ->
+                            try_bind_vhost(Rest, HostParts, Port,
+                                PathParts)
+                    end;
+                fail -> try_bind_vhost(Rest, HostParts, Port, PathParts)
+            end;
+        fail ->  try_bind_vhost(Rest, HostParts, Port, PathParts)
+    end.
+
+%% doc: build new patch from bindings. bindings are query args
+%% (+ dynamic query rewritten if needed) and bindings found in
+%% bind_path step.
+%% TODO: merge code with rewrite. But we need to make sure we are
+%% in string here.
+make_target([], _Bindings, _Remaining, Acc) ->
+    lists:reverse(Acc);
+make_target([?MATCH_ALL], _Bindings, Remaining, Acc) ->
+    Acc1 = lists:reverse(Acc) ++ Remaining,
+    Acc1;
+make_target([?MATCH_ALL|_Rest], _Bindings, Remaining, Acc) ->
+    Acc1 = lists:reverse(Acc) ++ Remaining,
+    Acc1;
+make_target([{bind, P}|Rest], Bindings, Remaining, Acc) ->
+    P2 = case couch_util:get_value({bind, P}, Bindings) of
+        undefined ->  "undefined";
+        P1 -> P1
+    end,
+    make_target(Rest, Bindings, Remaining, [P2|Acc]);
+make_target([P|Rest], Bindings, Remaining, Acc) ->
+    make_target(Rest, Bindings, Remaining, [P|Acc]).
+
+%% bind port
+bind_port(Port, Port) -> ok;
+bind_port('*', _) -> ok;
+bind_port(_,_) -> fail.
+
+%% bind bhost
+bind_vhost([],[], Bindings) -> {ok, Bindings, []};
+bind_vhost([?MATCH_ALL], [], _Bindings) -> fail;
+bind_vhost([?MATCH_ALL], Rest, Bindings) -> {ok, Bindings, Rest};
+bind_vhost([], _HostParts, _Bindings) -> fail;
+bind_vhost([{bind, Token}|Rest], [Match|RestHost], Bindings) ->
+    bind_vhost(Rest, RestHost, [{{bind, Token}, Match}|Bindings]);
+bind_vhost([Cname|Rest], [Cname|RestHost], Bindings) ->
+    bind_vhost(Rest, RestHost, Bindings);
+bind_vhost(_, _, _) -> fail.
+
+%% bind path
+bind_path([], PathParts) ->
+    {ok, PathParts};
+bind_path(_VPathParts, []) ->
+    fail;
+bind_path([Path|VRest],[Path|Rest]) ->
+   bind_path(VRest, Rest);
+bind_path(_, _) ->
+    fail.
+
+% utilities
+
+
+%% create vhost list from ini
+
+host(MochiReq) ->
+    XHost = couch_config:get("httpd", "x_forwarded_host",
+                             "X-Forwarded-Host"),
+    case MochiReq:get_header_value(XHost) of
+        undefined ->
+            case MochiReq:get_header_value("Host") of
+                undefined -> [];
+                Value1 -> Value1
+            end;
+        Value -> Value
+    end.
+
+make_vhosts() ->
+    Vhosts = lists:foldl(fun
+                ({_, ""}, Acc) ->
+                    Acc;
+                ({Vhost, Path}, Acc) ->
+                    [{parse_vhost(Vhost), split_path(Path)}|Acc]
+            end, [], couch_config:get("vhosts")),
+
+    lists:reverse(lists:usort(Vhosts)).
+
+
+parse_vhost(Vhost) ->
+    case urlsplit_netloc(Vhost, []) of
+        {[], Path} ->
+            {make_spec("*", []), '*', Path};
+        {HostPort, []} ->
+            {H, P} = split_host_port(HostPort),
+            H1 = make_spec(H, []),
+            {H1, P, []};
+        {HostPort, Path} ->
+            {H, P} = split_host_port(HostPort),
+            H1 = make_spec(H, []),
+            {H1, P, string:tokens(Path, "/")}
+    end.
+
+
+split_host_port(HostAsString) ->
+    case string:rchr(HostAsString, $:) of
+        0 ->
+            {split_host(HostAsString), '*'};
+        N ->
+            HostPart = string:substr(HostAsString, 1, N-1),
+            case (catch erlang:list_to_integer(string:substr(HostAsString,
+                            N+1, length(HostAsString)))) of
+                {'EXIT', _} ->
+                    {split_host(HostAsString), '*'};
+                Port ->
+                    {split_host(HostPart), Port}
+            end
+    end.
+
+split_host(HostAsString) ->
+    string:tokens(HostAsString, "\.").
+
+split_path(Path) ->
+    make_spec(string:tokens(Path, "/"), []).
+
+
+make_spec([], Acc) ->
+    lists:reverse(Acc);
+make_spec([""|R], Acc) ->
+    make_spec(R, Acc);
+make_spec(["*"|R], Acc) ->
+    make_spec(R, [?MATCH_ALL|Acc]);
+make_spec([P|R], Acc) ->
+    P1 = parse_var(P),
+    make_spec(R, [P1|Acc]).
+
+
+parse_var(P) ->
+    case P of
+        ":" ++ Var ->
+            {bind, Var};
+        _ -> P
+    end.
+
+
+% mochiweb doesn't export it.
+urlsplit_netloc("", Acc) ->
+    {lists:reverse(Acc), ""};
+urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
+    {lists:reverse(Acc), Rest};
+urlsplit_netloc([C | Rest], Acc) ->
+    urlsplit_netloc(Rest, [C | Acc]).
+
+make_path(Parts) ->
+     "/" ++ string:join(Parts,[?SEPARATOR]).
+
+init(_) ->
+    ok = couch_config:register(fun ?MODULE:config_change/2),
+
+    %% load configuration
+    {VHostGlobals, VHosts, Fun} = load_conf(),
+    State = #vhosts_state{
+        vhost_globals=VHostGlobals,
+        vhosts=VHosts,
+        vhosts_fun=Fun},
+    {ok, State}.
+
+handle_call(reload, _From, _State) ->
+    {VHostGlobals, VHosts, Fun} = load_conf(),
+    {reply, ok, #vhosts_state{
+            vhost_globals=VHostGlobals,
+            vhosts=VHosts,
+            vhosts_fun=Fun}};
+handle_call(get_state, _From, State) ->
+    {reply, State, State};
+handle_call(_Msg, _From, State) ->
+    {noreply, State}.
+
+handle_cast(_Msg, State) ->
+    {noreply, State}.
+
+handle_info(_Info, State) ->
+    {noreply, State}.
+
+terminate(_Reason, _State) ->
+    ok.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+config_change("httpd", "vhost_global_handlers") ->
+    ?MODULE:reload();
+config_change("httpd", "redirect_vhost_handler") ->
+    ?MODULE:reload();
+config_change("vhosts", _) ->
+    ?MODULE:reload().
+
+load_conf() ->
+    %% get vhost globals
+    VHostGlobals = re:split(couch_config:get("httpd",
+            "vhost_global_handlers",""), "\\s*,\\s*",[{return, list}]),
+
+    %% build vhosts matching rules
+    VHosts = make_vhosts(),
+
+    %% build vhosts handler fun
+    DefaultVHostFun = "{couch_httpd_vhost, redirect_to_vhost}",
+    Fun = couch_httpd:make_arity_2_fun(couch_config:get("httpd",
+            "redirect_vhost_handler", DefaultVHostFun)),
+
+    {VHostGlobals, VHosts, Fun}.

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

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_key_tree.erl
----------------------------------------------------------------------
diff --git a/src/couch_key_tree.erl b/src/couch_key_tree.erl
new file mode 100644
index 0000000..ce45ab8
--- /dev/null
+++ b/src/couch_key_tree.erl
@@ -0,0 +1,422 @@
+% 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 Data structure used to represent document edit histories.
+
+%% A key tree is used to represent the edit history of a document. Each node of
+%% the tree represents a particular version. Relations between nodes represent
+%% the order that these edits were applied. For instance, a set of three edits
+%% would produce a tree of versions A->B->C indicating that edit C was based on
+%% version B which was in turn based on A. In a world without replication (and
+%% no ability to disable MVCC checks), all histories would be forced to be
+%% linear lists of edits due to constraints imposed by MVCC (ie, new edits must
+%% be based on the current version). However, we have replication, so we must
+%% deal with not so easy cases, which lead to trees.
+%%
+%% Consider a document in state A. This doc is replicated to a second node. We
+%% then edit the document on each node leaving it in two different states, B
+%% and C. We now have two key trees, A->B and A->C. When we go to replicate a
+%% second time, the key tree must combine these two trees which gives us
+%% A->(B|C). This is how conflicts are introduced. In terms of the key tree, we
+%% say that we have two leaves (B and C) that are not deleted. The presense of
+%% the multiple leaves indicate conflict. To remove a conflict, one of the
+%% edits (B or C) can be deleted, which results in, A->(B|C->D) where D is an
+%% edit that is specially marked with the a deleted=true flag.
+%%
+%% What makes this a bit more complicated is that there is a limit to the
+%% number of revisions kept, specified in couch_db.hrl (default is 1000). When
+%% this limit is exceeded only the last 1000 are kept. This comes in to play
+%% when branches are merged. The comparison has to begin at the same place in
+%% the branches. A revision id is of the form N-XXXXXXX where N is the current
+%% revision. So each path will have a start number, calculated in
+%% couch_doc:to_path using the formula N - length(RevIds) + 1 So, .eg. if a doc
+%% was edit 1003 times this start number would be 4, indicating that 3
+%% revisions were truncated.
+%%
+%% This comes into play in @see merge_at/3 which recursively walks down one
+%% tree or the other until they begin at the same revision.
+
+-module(couch_key_tree).
+
+-export([merge/3, find_missing/2, get_key_leafs/2, get_full_key_paths/2, get/2]).
+-export([get_all_leafs/1, count_leafs/1, remove_leafs/2, get_all_leafs_full/1, stem/2]).
+-export([map/2, mapfold/3, map_leafs/2, fold/3]).
+
+-include("couch_db.hrl").
+
+%% @doc Merge a path with a list of paths and stem to the given length.
+-spec merge([path()], path(), pos_integer()) -> {[path()],
+    conflicts | no_conflicts}.
+merge(Paths, Path, Depth) ->
+    {Merged, Conflicts} = merge(Paths, Path),
+    {stem(Merged, Depth), Conflicts}.
+
+%% @doc Merge a path with an existing list of paths, returning a new list of
+%% paths. A return of conflicts indicates a new conflict was discovered in this
+%% merge. Conflicts may already exist in the original list of paths.
+-spec merge([path()], path()) -> {[path()], conflicts | no_conflicts}.
+merge(Paths, Path) ->
+    {ok, Merged, HasConflicts} = merge_one(Paths, Path, [], false),
+    if HasConflicts ->
+        Conflicts = conflicts;
+    (length(Merged) =/= length(Paths)) and (length(Merged) =/= 1) ->
+        Conflicts = conflicts;
+    true ->
+        Conflicts = no_conflicts
+    end,
+    {lists:sort(Merged), Conflicts}.
+
+-spec merge_one(Original::[path()], Inserted::path(), [path()], boolean()) ->
+    {ok, Merged::[path()], NewConflicts::boolean()}.
+merge_one([], Insert, OutAcc, ConflictsAcc) ->
+    {ok, [Insert | OutAcc], ConflictsAcc};
+merge_one([{Start, Tree}|Rest], {StartInsert, TreeInsert}, Acc, HasConflicts) ->
+    case merge_at([Tree], StartInsert - Start, [TreeInsert]) of
+    {ok, [Merged], Conflicts} ->
+        MergedStart = lists:min([Start, StartInsert]),
+        {ok, Rest ++ [{MergedStart, Merged} | Acc], Conflicts or HasConflicts};
+    no ->
+        AccOut = [{Start, Tree} | Acc],
+        merge_one(Rest, {StartInsert, TreeInsert}, AccOut, HasConflicts)
+    end.
+
+-spec merge_at(tree(), Place::integer(), tree()) ->
+    {ok, Merged::tree(), HasConflicts::boolean()} | no.
+merge_at(_Ours, _Place, []) ->
+    no;
+merge_at([], _Place, _Insert) ->
+    no;
+merge_at([{Key, Value, SubTree}|Sibs], Place, InsertTree) when Place > 0 ->
+    % inserted starts later than committed, need to drill into committed subtree
+    case merge_at(SubTree, Place - 1, InsertTree) of
+    {ok, Merged, Conflicts} ->
+        {ok, [{Key, Value, Merged} | Sibs], Conflicts};
+    no ->
+        % first branch didn't merge, move to next branch
+        case merge_at(Sibs, Place, InsertTree) of
+        {ok, Merged, Conflicts} ->
+            {ok, [{Key, Value, SubTree} | Merged], Conflicts};
+        no ->
+            no
+        end
+    end;
+merge_at(OurTree, Place, [{Key, Value, SubTree}]) when Place < 0 ->
+    % inserted starts earlier than committed, need to drill into insert subtree
+    case merge_at(OurTree, Place + 1, SubTree) of
+    {ok, Merged, Conflicts} ->
+        {ok, [{Key, Value, Merged}], Conflicts};
+    no ->
+        no
+    end;
+merge_at([{Key, V1, SubTree}|Sibs], 0, [{Key, V2, InsertSubTree}]) ->
+    {Merged, Conflicts} = merge_simple(SubTree, InsertSubTree),
+    {ok, [{Key, value_pref(V1, V2), Merged} | Sibs], Conflicts};
+merge_at([{OurKey, _, _} | _], 0, [{Key, _, _}]) when OurKey > Key ->
+    % siblings keys are ordered, no point in continuing
+    no;
+merge_at([Tree | Sibs], 0, InsertTree) ->
+    case merge_at(Sibs, 0, InsertTree) of
+    {ok, Merged, Conflicts} ->
+        {ok, [Tree | Merged], Conflicts};
+    no ->
+        no
+    end.
+
+% key tree functions
+
+-spec merge_simple(tree(), tree()) -> {Merged::tree(), NewConflicts::boolean()}.
+merge_simple([], B) ->
+    {B, false};
+merge_simple(A, []) ->
+    {A, false};
+merge_simple([{Key, V1, SubA} | NextA], [{Key, V2, SubB} | NextB]) ->
+    {MergedSubTree, Conflict1} = merge_simple(SubA, SubB),
+    {MergedNextTree, Conflict2} = merge_simple(NextA, NextB),
+    Value = value_pref(V1, V2),
+    {[{Key, Value, MergedSubTree} | MergedNextTree], Conflict1 or Conflict2};
+merge_simple([{A, _, _} = Tree | Next], [{B, _, _} | _] = Insert) when A < B ->
+    {Merged, Conflict} = merge_simple(Next, Insert),
+    % if Merged has more branches than the input we added a new conflict
+    {[Tree | Merged], Conflict orelse (length(Merged) > length(Next))};
+merge_simple(Ours, [Tree | Next]) ->
+    {Merged, Conflict} = merge_simple(Ours, Next),
+    {[Tree | Merged], Conflict orelse (length(Merged) > length(Next))}.
+
+find_missing(_Tree, []) ->
+    [];
+find_missing([], SeachKeys) ->
+    SeachKeys;
+find_missing([{Start, {Key, Value, SubTree}} | RestTree], SeachKeys) ->
+    PossibleKeys = [{KeyPos, KeyValue} || {KeyPos, KeyValue} <- SeachKeys, KeyPos >= Start],
+    ImpossibleKeys = [{KeyPos, KeyValue} || {KeyPos, KeyValue} <- SeachKeys, KeyPos < Start],
+    Missing = find_missing_simple(Start, [{Key, Value, SubTree}], PossibleKeys),
+    find_missing(RestTree, ImpossibleKeys ++ Missing).
+
+find_missing_simple(_Pos, _Tree, []) ->
+    [];
+find_missing_simple(_Pos, [], SeachKeys) ->
+    SeachKeys;
+find_missing_simple(Pos, [{Key, _, SubTree} | RestTree], SeachKeys) ->
+    PossibleKeys = [{KeyPos, KeyValue} || {KeyPos, KeyValue} <- SeachKeys, KeyPos >= Pos],
+    ImpossibleKeys = [{KeyPos, KeyValue} || {KeyPos, KeyValue} <- SeachKeys, KeyPos < Pos],
+
+    SrcKeys2 = PossibleKeys -- [{Pos, Key}],
+    SrcKeys3 = find_missing_simple(Pos + 1, SubTree, SrcKeys2),
+    ImpossibleKeys ++ find_missing_simple(Pos, RestTree, SrcKeys3).
+
+
+filter_leafs([], _Keys, FilteredAcc, RemovedKeysAcc) ->
+    {FilteredAcc, RemovedKeysAcc};
+filter_leafs([{Pos, [{LeafKey, _}|_]} = Path |Rest], Keys, FilteredAcc, RemovedKeysAcc) ->
+    FilteredKeys = lists:delete({Pos, LeafKey}, Keys),
+    if FilteredKeys == Keys ->
+        % this leaf is not a key we are looking to remove
+        filter_leafs(Rest, Keys, [Path | FilteredAcc], RemovedKeysAcc);
+    true ->
+        % this did match a key, remove both the node and the input key
+        filter_leafs(Rest, FilteredKeys, FilteredAcc, [{Pos, LeafKey} | RemovedKeysAcc])
+    end.
+
+% Removes any branches from the tree whose leaf node(s) are in the Keys
+remove_leafs(Trees, Keys) ->
+    % flatten each branch in a tree into a tree path
+    Paths = get_all_leafs_full(Trees),
+
+    % filter out any that are in the keys list.
+    {FilteredPaths, RemovedKeys} = filter_leafs(Paths, Keys, [], []),
+
+    SortedPaths = lists:sort(
+        [{Pos + 1 - length(Path), Path} || {Pos, Path} <- FilteredPaths]
+    ),
+
+    % convert paths back to trees
+    NewTree = lists:foldl(
+        fun({StartPos, Path},TreeAcc) ->
+            [SingleTree] = lists:foldl(
+                fun({K,V},NewTreeAcc) -> [{K,V,NewTreeAcc}] end, [], Path),
+            {NewTrees, _} = merge(TreeAcc, {StartPos, SingleTree}),
+            NewTrees
+        end, [], SortedPaths),
+    {NewTree, RemovedKeys}.
+
+
+% get the leafs in the tree matching the keys. The matching key nodes can be
+% leafs or an inner nodes. If an inner node, then the leafs for that node
+% are returned.
+get_key_leafs(Tree, Keys) ->
+    get_key_leafs(Tree, Keys, []).
+
+get_key_leafs(_, [], Acc) ->
+    {Acc, []};
+get_key_leafs([], Keys, Acc) ->
+    {Acc, Keys};
+get_key_leafs([{Pos, Tree}|Rest], Keys, Acc) ->
+    {Gotten, RemainingKeys} = get_key_leafs_simple(Pos, [Tree], Keys, []),
+    get_key_leafs(Rest, RemainingKeys, Gotten ++ Acc).
+
+get_key_leafs_simple(_Pos, _Tree, [], _KeyPathAcc) ->
+    {[], []};
+get_key_leafs_simple(_Pos, [], KeysToGet, _KeyPathAcc) ->
+    {[], KeysToGet};
+get_key_leafs_simple(Pos, [{Key, _Value, SubTree}=Tree | RestTree], KeysToGet, KeyPathAcc) ->
+    case lists:delete({Pos, Key}, KeysToGet) of
+    KeysToGet -> % same list, key not found
+        {LeafsFound, KeysToGet2} = get_key_leafs_simple(Pos + 1, SubTree, KeysToGet, [Key | KeyPathAcc]),
+        {RestLeafsFound, KeysRemaining} = get_key_leafs_simple(Pos, RestTree, KeysToGet2, KeyPathAcc),
+        {LeafsFound ++ RestLeafsFound, KeysRemaining};
+    KeysToGet2 ->
+        LeafsFound = get_all_leafs_simple(Pos, [Tree], KeyPathAcc),
+        LeafKeysFound = [{LeafPos, LeafRev} || {_, {LeafPos, [LeafRev|_]}}
+            <- LeafsFound],
+        KeysToGet3 = KeysToGet2 -- LeafKeysFound,
+        {RestLeafsFound, KeysRemaining} = get_key_leafs_simple(Pos, RestTree, KeysToGet3, KeyPathAcc),
+        {LeafsFound ++ RestLeafsFound, KeysRemaining}
+    end.
+
+get(Tree, KeysToGet) ->
+    {KeyPaths, KeysNotFound} = get_full_key_paths(Tree, KeysToGet),
+    FixedResults = [ {Value, {Pos, [Key0 || {Key0, _} <- Path]}} || {Pos, [{_Key, Value}|_]=Path} <- KeyPaths],
+    {FixedResults, KeysNotFound}.
+
+get_full_key_paths(Tree, Keys) ->
+    get_full_key_paths(Tree, Keys, []).
+
+get_full_key_paths(_, [], Acc) ->
+    {Acc, []};
+get_full_key_paths([], Keys, Acc) ->
+    {Acc, Keys};
+get_full_key_paths([{Pos, Tree}|Rest], Keys, Acc) ->
+    {Gotten, RemainingKeys} = get_full_key_paths(Pos, [Tree], Keys, []),
+    get_full_key_paths(Rest, RemainingKeys, Gotten ++ Acc).
+
+
+get_full_key_paths(_Pos, _Tree, [], _KeyPathAcc) ->
+    {[], []};
+get_full_key_paths(_Pos, [], KeysToGet, _KeyPathAcc) ->
+    {[], KeysToGet};
+get_full_key_paths(Pos, [{KeyId, Value, SubTree} | RestTree], KeysToGet, KeyPathAcc) ->
+    KeysToGet2 = KeysToGet -- [{Pos, KeyId}],
+    CurrentNodeResult =
+    case length(KeysToGet2) =:= length(KeysToGet) of
+    true -> % not in the key list.
+        [];
+    false -> % this node is the key list. return it
+        [{Pos, [{KeyId, Value} | KeyPathAcc]}]
+    end,
+    {KeysGotten, KeysRemaining} = get_full_key_paths(Pos + 1, SubTree, KeysToGet2, [{KeyId, Value} | KeyPathAcc]),
+    {KeysGotten2, KeysRemaining2} = get_full_key_paths(Pos, RestTree, KeysRemaining, KeyPathAcc),
+    {CurrentNodeResult ++ KeysGotten ++ KeysGotten2, KeysRemaining2}.
+
+get_all_leafs_full(Tree) ->
+    get_all_leafs_full(Tree, []).
+
+get_all_leafs_full([], Acc) ->
+    Acc;
+get_all_leafs_full([{Pos, Tree} | Rest], Acc) ->
+    get_all_leafs_full(Rest, get_all_leafs_full_simple(Pos, [Tree], []) ++ Acc).
+
+get_all_leafs_full_simple(_Pos, [], _KeyPathAcc) ->
+    [];
+get_all_leafs_full_simple(Pos, [{KeyId, Value, []} | RestTree], KeyPathAcc) ->
+    [{Pos, [{KeyId, Value} | KeyPathAcc]} | get_all_leafs_full_simple(Pos, RestTree, KeyPathAcc)];
+get_all_leafs_full_simple(Pos, [{KeyId, Value, SubTree} | RestTree], KeyPathAcc) ->
+    get_all_leafs_full_simple(Pos + 1, SubTree, [{KeyId, Value} | KeyPathAcc]) ++ get_all_leafs_full_simple(Pos, RestTree, KeyPathAcc).
+
+get_all_leafs(Trees) ->
+    get_all_leafs(Trees, []).
+
+get_all_leafs([], Acc) ->
+    Acc;
+get_all_leafs([{Pos, Tree}|Rest], Acc) ->
+    get_all_leafs(Rest, get_all_leafs_simple(Pos, [Tree], []) ++ Acc).
+
+get_all_leafs_simple(_Pos, [], _KeyPathAcc) ->
+    [];
+get_all_leafs_simple(Pos, [{KeyId, Value, []} | RestTree], KeyPathAcc) ->
+    [{Value, {Pos, [KeyId | KeyPathAcc]}} | get_all_leafs_simple(Pos, RestTree, KeyPathAcc)];
+get_all_leafs_simple(Pos, [{KeyId, _Value, SubTree} | RestTree], KeyPathAcc) ->
+    get_all_leafs_simple(Pos + 1, SubTree, [KeyId | KeyPathAcc]) ++ get_all_leafs_simple(Pos, RestTree, KeyPathAcc).
+
+
+count_leafs([]) ->
+    0;
+count_leafs([{_Pos,Tree}|Rest]) ->
+    count_leafs_simple([Tree]) + count_leafs(Rest).
+
+count_leafs_simple([]) ->
+    0;
+count_leafs_simple([{_Key, _Value, []} | RestTree]) ->
+    1 + count_leafs_simple(RestTree);
+count_leafs_simple([{_Key, _Value, SubTree} | RestTree]) ->
+    count_leafs_simple(SubTree) + count_leafs_simple(RestTree).
+
+
+fold(_Fun, Acc, []) ->
+    Acc;
+fold(Fun, Acc0, [{Pos, Tree}|Rest]) ->
+    Acc1 = fold_simple(Fun, Acc0, Pos, [Tree]),
+    fold(Fun, Acc1, Rest).
+
+fold_simple(_Fun, Acc, _Pos, []) ->
+    Acc;
+fold_simple(Fun, Acc0, Pos, [{Key, Value, SubTree} | RestTree]) ->
+    Type = if SubTree == [] -> leaf; true -> branch end,
+    Acc1 = Fun({Pos, Key}, Value, Type, Acc0),
+    Acc2 = fold_simple(Fun, Acc1, Pos+1, SubTree),
+    fold_simple(Fun, Acc2, Pos, RestTree).
+
+
+map(_Fun, []) ->
+    [];
+map(Fun, [{Pos, Tree}|Rest]) ->
+    case erlang:fun_info(Fun, arity) of
+    {arity, 2} ->
+        [NewTree] = map_simple(fun(A,B,_C) -> Fun(A,B) end, Pos, [Tree]),
+        [{Pos, NewTree} | map(Fun, Rest)];
+    {arity, 3} ->
+        [NewTree] = map_simple(Fun, Pos, [Tree]),
+        [{Pos, NewTree} | map(Fun, Rest)]
+    end.
+
+map_simple(_Fun, _Pos, []) ->
+    [];
+map_simple(Fun, Pos, [{Key, Value, SubTree} | RestTree]) ->
+    Value2 = Fun({Pos, Key}, Value,
+            if SubTree == [] -> leaf; true -> branch end),
+    [{Key, Value2, map_simple(Fun, Pos + 1, SubTree)} | map_simple(Fun, Pos, RestTree)].
+
+
+mapfold(_Fun, Acc, []) ->
+    {[], Acc};
+mapfold(Fun, Acc, [{Pos, Tree} | Rest]) ->
+    {[NewTree], Acc2} = mapfold_simple(Fun, Acc, Pos, [Tree]),
+    {Rest2, Acc3} = mapfold(Fun, Acc2, Rest),
+    {[{Pos, NewTree} | Rest2], Acc3}.
+
+mapfold_simple(_Fun, Acc, _Pos, []) ->
+    {[], Acc};
+mapfold_simple(Fun, Acc, Pos, [{Key, Value, SubTree} | RestTree]) ->
+    {Value2, Acc2} = Fun({Pos, Key}, Value,
+            if SubTree == [] -> leaf; true -> branch end, Acc),
+    {SubTree2, Acc3} = mapfold_simple(Fun, Acc2, Pos + 1, SubTree),
+    {RestTree2, Acc4} = mapfold_simple(Fun, Acc3, Pos, RestTree),
+    {[{Key, Value2, SubTree2} | RestTree2], Acc4}.
+
+
+map_leafs(_Fun, []) ->
+    [];
+map_leafs(Fun, [{Pos, Tree}|Rest]) ->
+    [NewTree] = map_leafs_simple(Fun, Pos, [Tree]),
+    [{Pos, NewTree} | map_leafs(Fun, Rest)].
+
+map_leafs_simple(_Fun, _Pos, []) ->
+    [];
+map_leafs_simple(Fun, Pos, [{Key, Value, []} | RestTree]) ->
+    Value2 = Fun({Pos, Key}, Value),
+    [{Key, Value2, []} | map_leafs_simple(Fun, Pos, RestTree)];
+map_leafs_simple(Fun, Pos, [{Key, Value, SubTree} | RestTree]) ->
+    [{Key, Value, map_leafs_simple(Fun, Pos + 1, SubTree)} | map_leafs_simple(Fun, Pos, RestTree)].
+
+
+stem(Trees, Limit) ->
+    % flatten each branch in a tree into a tree path, sort by starting rev #
+    Paths = lists:sort(lists:map(fun({Pos, Path}) ->
+        StemmedPath = lists:sublist(Path, Limit),
+        {Pos + 1 - length(StemmedPath), StemmedPath}
+    end, get_all_leafs_full(Trees))),
+
+    % convert paths back to trees
+    lists:foldl(
+        fun({StartPos, Path},TreeAcc) ->
+            [SingleTree] = lists:foldl(
+                fun({K,V},NewTreeAcc) -> [{K,V,NewTreeAcc}] end, [], Path),
+            {NewTrees, _} = merge(TreeAcc, {StartPos, SingleTree}),
+            NewTrees
+        end, [], Paths).
+
+
+value_pref(Tuple, _) when is_tuple(Tuple),
+        (tuple_size(Tuple) == 3 orelse tuple_size(Tuple) == 4) ->
+    Tuple;
+value_pref(_, Tuple) when is_tuple(Tuple),
+        (tuple_size(Tuple) == 3 orelse tuple_size(Tuple) == 4) ->
+    Tuple;
+value_pref(?REV_MISSING, Other) ->
+    Other;
+value_pref(Other, ?REV_MISSING) ->
+    Other;
+value_pref(Last, _) ->
+    Last.
+
+
+% Tests moved to test/etap/06?-*.t
+

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/a6816bff/src/couch_log.erl
----------------------------------------------------------------------
diff --git a/src/couch_log.erl b/src/couch_log.erl
new file mode 100644
index 0000000..cd4bbbb
--- /dev/null
+++ b/src/couch_log.erl
@@ -0,0 +1,254 @@
+% 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_log).
+-behaviour(gen_event).
+
+% public API
+-export([start_link/0, stop/0]).
+-export([debug/2, info/2, warn/2, error/2]).
+-export([debug_on/0, info_on/0, warn_on/0, get_level/0, get_level_integer/0, set_level/1]).
+-export([debug_on/1, info_on/1, warn_on/1, get_level/1, get_level_integer/1, set_level/2]).
+-export([read/2]).
+
+% gen_event callbacks
+-export([init/1, handle_event/2, terminate/2, code_change/3]).
+-export([handle_info/2, handle_call/2]).
+
+-define(LEVEL_ERROR, 4).
+-define(LEVEL_WARN, 3).
+-define(LEVEL_INFO, 2).
+-define(LEVEL_DEBUG, 1).
+
+-record(state, {
+    fd,
+    level,
+    sasl
+}).
+
+debug(Format, Args) ->
+    {ConsoleMsg, FileMsg} = get_log_messages(self(), debug, Format, Args),
+    gen_event:sync_notify(error_logger, {couch_debug, ConsoleMsg, FileMsg}).
+
+info(Format, Args) ->
+    {ConsoleMsg, FileMsg} = get_log_messages(self(), info, Format, Args),
+    gen_event:sync_notify(error_logger, {couch_info, ConsoleMsg, FileMsg}).
+
+warn(Format, Args) ->
+    {ConsoleMsg, FileMsg} = get_log_messages(self(), warn, Format, Args),
+    gen_event:sync_notify(error_logger, {couch_warn, ConsoleMsg, FileMsg}).
+
+error(Format, Args) ->
+    {ConsoleMsg, FileMsg} = get_log_messages(self(), error, Format, Args),
+    gen_event:sync_notify(error_logger, {couch_error, ConsoleMsg, FileMsg}).
+
+
+level_integer(error)    -> ?LEVEL_ERROR;
+level_integer(warn)     -> ?LEVEL_WARN;
+level_integer(info)     -> ?LEVEL_INFO;
+level_integer(debug)    -> ?LEVEL_DEBUG;
+level_integer(_Else)    -> ?LEVEL_ERROR. % anything else default to ERROR level
+
+level_atom(?LEVEL_ERROR) -> error;
+level_atom(?LEVEL_WARN) -> warn;
+level_atom(?LEVEL_INFO) -> info;
+level_atom(?LEVEL_DEBUG) -> debug.
+
+
+start_link() ->
+    couch_event_sup:start_link({local, couch_log}, error_logger, couch_log, []).
+
+stop() ->
+    couch_event_sup:stop(couch_log).
+
+init([]) ->
+    % read config and register for configuration changes
+
+    % just stop if one of the config settings change. couch_server_sup
+    % will restart us and then we will pick up the new settings.
+    ok = couch_config:register(
+        fun("log", "file") ->
+            ?MODULE:stop();
+        ("log", "level") ->
+            ?MODULE:stop();
+        ("log", "include_sasl") ->
+            ?MODULE:stop();
+        ("log_level_by_module", _) ->
+            ?MODULE:stop()
+        end),
+
+    Filename = couch_config:get("log", "file", "couchdb.log"),
+    Level = level_integer(list_to_atom(couch_config:get("log", "level", "info"))),
+    Sasl = couch_config:get("log", "include_sasl", "true") =:= "true",
+    LevelByModule = couch_config:get("log_level_by_module"),
+
+    case ets:info(?MODULE) of
+    undefined -> ets:new(?MODULE, [named_table]);
+    _ -> ok
+    end,
+    ets:insert(?MODULE, {level, Level}),
+    lists:foreach(fun({Module, ModuleLevel}) ->
+        ModuleLevelInteger = level_integer(list_to_atom(ModuleLevel)),
+        ets:insert(?MODULE, {Module, ModuleLevelInteger})
+    end, LevelByModule),
+
+
+    case file:open(Filename, [append]) of
+    {ok, Fd} ->
+        {ok, #state{fd = Fd, level = Level, sasl = Sasl}};
+    {error, Reason} ->
+        ReasonStr = file:format_error(Reason),
+        io:format("Error opening log file ~s: ~s", [Filename, ReasonStr]),
+        {stop, {error, ReasonStr, Filename}}
+    end.
+
+debug_on() ->
+    get_level_integer() =< ?LEVEL_DEBUG.
+
+info_on() ->
+    get_level_integer() =< ?LEVEL_INFO.
+
+warn_on() ->
+    get_level_integer() =< ?LEVEL_WARN.
+
+debug_on(Module) ->
+    get_level_integer(Module) =< ?LEVEL_DEBUG.
+
+info_on(Module) ->
+    get_level_integer(Module) =< ?LEVEL_INFO.
+
+warn_on(Module) ->
+    get_level_integer(Module) =< ?LEVEL_WARN.
+
+set_level(LevelAtom) ->
+    set_level_integer(level_integer(LevelAtom)).
+
+set_level(Module, LevelAtom) ->
+    set_level_integer(Module, level_integer(LevelAtom)).
+
+get_level() ->
+    level_atom(get_level_integer()).
+
+get_level(Module) ->
+    level_atom(get_level_integer(Module)).
+
+get_level_integer() ->
+    try
+        ets:lookup_element(?MODULE, level, 2)
+    catch error:badarg ->
+        ?LEVEL_ERROR
+    end.
+
+get_level_integer(Module0) ->
+    Module = atom_to_list(Module0),
+    try
+        [{_Module, Level}] = ets:lookup(?MODULE, Module),
+        Level
+    catch error:_ ->
+        get_level_integer()
+    end.
+
+set_level_integer(Int) ->
+    gen_event:call(error_logger, couch_log, {set_level_integer, Int}).
+
+set_level_integer(Module, Int) ->
+    gen_event:call(error_logger, couch_log, {set_level_integer, Module, Int}).
+
+handle_event({couch_error, ConMsg, FileMsg}, State) ->
+    log(State, ConMsg, FileMsg),
+    {ok, State};
+handle_event({couch_warn, ConMsg, FileMsg}, State) ->
+    log(State, ConMsg, FileMsg),
+    {ok, State};
+handle_event({couch_info, ConMsg, FileMsg}, State) ->
+    log(State, ConMsg, FileMsg),
+    {ok, State};
+handle_event({couch_debug, ConMsg, FileMsg}, State) ->
+    log(State, ConMsg, FileMsg),
+    {ok, State};
+handle_event({error_report, _, {Pid, _, _}}=Event, #state{sasl = true} = St) ->
+    {ConMsg, FileMsg} = get_log_messages(Pid, error, "~p", [Event]),
+    log(St, ConMsg, FileMsg),
+    {ok, St};
+handle_event({error, _, {Pid, Format, Args}}, #state{sasl = true} = State) ->
+    {ConMsg, FileMsg} = get_log_messages(Pid, error, Format, Args),
+    log(State, ConMsg, FileMsg),
+    {ok, State};
+handle_event(_Event, State) ->
+    {ok, State}.
+
+handle_call({set_level_integer, NewLevel}, State) ->
+    ets:insert(?MODULE, {level, NewLevel}),
+    {ok, ok, State#state{level = NewLevel}};
+
+handle_call({set_level_integer, Module, NewLevel}, State) ->
+    ets:insert(?MODULE, {Module, NewLevel}),
+    {ok, ok, State#state{level = NewLevel}}.
+
+handle_info(_Info, State) ->
+    {ok, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+    {ok, State}.
+
+terminate(_Arg, #state{fd = Fd}) ->
+    file:close(Fd).
+
+log(#state{fd = Fd}, ConsoleMsg, FileMsg) ->
+    ok = io:put_chars(ConsoleMsg),
+    ok = io:put_chars(Fd, FileMsg).
+
+get_log_messages(Pid, Level, Format, Args) ->
+    ConsoleMsg = unicode:characters_to_binary(io_lib:format(
+        "[~s] [~p] " ++ Format ++ "~n", [Level, Pid | Args])),
+    FileMsg = ["[", couch_util:rfc1123_date(), "] ", ConsoleMsg],
+    {ConsoleMsg, iolist_to_binary(FileMsg)}.
+
+
+% Read Bytes bytes from the end of log file, jumping Offset bytes towards
+% the beginning of the file first.
+%
+%  Log File    FilePos
+%  ----------
+% |          |  10
+% |          |  20
+% |          |  30
+% |          |  40
+% |          |  50
+% |          |  60
+% |          |  70 -- Bytes = 20  --
+% |          |  80                 | Chunk
+% |          |  90 -- Offset = 10 --
+% |__________| 100
+
+read(Bytes, Offset) ->
+    LogFileName = couch_config:get("log", "file"),
+    LogFileSize = filelib:file_size(LogFileName),
+    MaxChunkSize = list_to_integer(
+        couch_config:get("httpd", "log_max_chunk_size", "1000000")),
+    case Bytes > MaxChunkSize of
+    true ->
+        throw({bad_request, "'bytes' cannot exceed " ++
+            integer_to_list(MaxChunkSize)});
+    false ->
+        ok
+    end,
+
+    {ok, Fd} = file:open(LogFileName, [read]),
+    Start = lists:max([LogFileSize - Bytes - Offset, 0]),
+
+    % TODO: truncate chopped first line
+    % TODO: make streaming
+
+    {ok, Chunk} = file:pread(Fd, Start, Bytes),
+    ok = file:close(Fd),
+    Chunk.