couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rnew...@apache.org
Subject [1/6] Merge remote-tracking branch 'origin/import-master'
Date Tue, 06 May 2014 12:40:56 GMT
Repository: couchdb-couch
Updated Branches:
  refs/heads/master [created] 9d629ff64


http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9d629ff6/src/couch_secondary_sup.erl
----------------------------------------------------------------------
diff --cc src/couch_secondary_sup.erl
index d0ed0c2,0000000..91e87cb
mode 100644,000000..100644
--- a/src/couch_secondary_sup.erl
+++ b/src/couch_secondary_sup.erl
@@@ -1,42 -1,0 +1,49 @@@
 +% 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_secondary_sup).
 +-behaviour(supervisor).
 +-export([init/1, start_link/0]).
 +
 +start_link() ->
 +    supervisor:start_link({local,couch_secondary_services}, ?MODULE, []).
 +
 +init([]) ->
 +    SecondarySupervisors = [
 +        {couch_db_update_notifier_sup,
 +            {couch_db_update_notifier_sup, start_link, []},
 +            permanent,
 +            infinity,
 +            supervisor,
-             [couch_db_update_notifier_sup]}
++            [couch_db_update_notifier_sup]},
++
++        {couch_plugin_event,
++            {gen_event, start_link, [{local, couch_plugin}]},
++            permanent,
++            brutal_kill,
++            worker,
++            dynamic}
 +    ],
 +    Children = SecondarySupervisors ++ [
 +        begin
 +            {ok, {Module, Fun, Args}} = couch_util:parse_term(SpecStr),
 +
 +            {list_to_atom(Name),
 +                {Module, Fun, Args},
 +                permanent,
 +                brutal_kill,
 +                worker,
 +                [Module]}
 +        end
 +        || {Name, SpecStr}
 +        <- config:get("daemons"), SpecStr /= ""],
 +    {ok, {{one_for_one, 50, 3600}, Children}}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9d629ff6/src/couch_server.erl
----------------------------------------------------------------------
diff --cc src/couch_server.erl
index 64c87ef,0000000..e03c1e4
mode 100644,000000..100644
--- a/src/couch_server.erl
+++ b/src/couch_server.erl
@@@ -1,514 -1,0 +1,519 @@@
 +% 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_server).
 +-behaviour(gen_server).
 +-behaviour(config_listener).
 +
- -export([open/2,create/2,delete/2,get_version/0,get_uuid/0]).
++-export([open/2,create/2,delete/2,get_version/0,get_version/1,get_uuid/0]).
 +-export([all_databases/0, all_databases/2]).
 +-export([init/1, handle_call/3,sup_start_link/0]).
 +-export([handle_cast/2,code_change/3,handle_info/2,terminate/2]).
 +-export([dev_start/0,is_admin/2,has_admins/0,get_stats/0]).
 +-export([close_lru/0]).
 +
 +% config_listener api
 +-export([handle_config_change/5]).
 +
 +-include_lib("couch/include/couch_db.hrl").
 +
 +-record(server,{
 +    root_dir = [],
 +    dbname_regexp,
 +    max_dbs_open=100,
 +    dbs_open=0,
 +    start_time="",
 +    lru = couch_lru:new()
 +    }).
 +
 +dev_start() ->
 +    couch:stop(),
 +    up_to_date = make:all([load, debug_info]),
 +    couch:start().
 +
 +get_version() ->
 +    Apps = application:loaded_applications(),
 +    case lists:keysearch(couch, 1, Apps) of
 +    {value, {_, _, Vsn}} ->
 +        Vsn;
 +    false ->
 +        "0.0.0"
 +    end.
++get_version(short) ->
++  %% strip git hash from version string
++  [Version|_Rest] = string:tokens(get_version(), "+"),
++  Version.
++
 +
 +get_uuid() ->
 +    case config:get("couchdb", "uuid", nil) of
 +        nil ->
 +            UUID = couch_uuids:random(),
 +            config:set("couchdb", "uuid", ?b2l(UUID)),
 +            UUID;
 +        UUID -> ?l2b(UUID)
 +    end.
 +
 +get_stats() ->
 +    {ok, #server{start_time=Time,dbs_open=Open}} =
 +            gen_server:call(couch_server, get_server),
 +    [{start_time, ?l2b(Time)}, {dbs_open, Open}].
 +
 +sup_start_link() ->
 +    gen_server:start_link({local, couch_server}, couch_server, [], []).
 +
 +
 +open(DbName, Options0) ->
 +    Options = maybe_add_sys_db_callbacks(DbName, Options0),
 +    Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
 +    case ets:lookup(couch_dbs, DbName) of
 +    [#db{fd=Fd, fd_monitor=Lock} = Db] when Lock =/= locked ->
 +        update_lru(DbName, Options),
 +        {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}};
 +    _ ->
 +        Timeout = couch_util:get_value(timeout, Options, infinity),
 +        case gen_server:call(couch_server, {open, DbName, Options}, Timeout) of
 +        {ok, #db{fd=Fd} = Db} ->
 +            update_lru(DbName, Options),
 +            {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}};
 +        Error ->
 +            Error
 +        end
 +    end.
 +
 +update_lru(DbName, Options) ->
 +    case lists:member(sys_db, Options) of
 +        false -> gen_server:cast(couch_server, {update_lru, DbName});
 +        true -> ok
 +    end.
 +
 +close_lru() ->
 +    gen_server:call(couch_server, close_lru).
 +
 +create(DbName, Options0) ->
 +    Options = maybe_add_sys_db_callbacks(DbName, Options0),
 +    case gen_server:call(couch_server, {create, DbName, Options}, infinity) of
 +    {ok, #db{fd=Fd} = Db} ->
 +        Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
 +        {ok, Db#db{user_ctx=Ctx, fd_monitor=erlang:monitor(process,Fd)}};
 +    Error ->
 +        Error
 +    end.
 +
 +delete(DbName, Options) ->
 +    gen_server:call(couch_server, {delete, DbName, Options}, infinity).
 +
 +maybe_add_sys_db_callbacks(DbName, Options) when is_binary(DbName) ->
 +    maybe_add_sys_db_callbacks(?b2l(DbName), Options);
 +maybe_add_sys_db_callbacks(DbName, Options) ->
 +    ReplicatorDbName = config:get("replicator", "db", "_replicator"),
 +    ReplicatorDbOptions = [
 +        {before_doc_update, fun couch_replicator_manager:before_doc_update/2},
 +        {after_doc_read, fun couch_replicator_manager:after_doc_read/2},
 +        sys_db
 +    ] ++ Options,
 +    UsersDbName = config:get("couch_httpd_auth", "authentication_db", "_users"),
 +    UsersDbOptions = [
 +        {before_doc_update, fun couch_users_db:before_doc_update/2},
 +        {after_doc_read, fun couch_users_db:after_doc_read/2},
 +        sys_db
 +    ] ++ Options,
 +    DbsDbName = config:get("mem3", "shard_db", "dbs"),
 +    DbsDbOptions = [sys_db | Options],
 +    NodesDbName = config:get("mem3", "node_db", "nodes"),
 +    NodesDbOptions = [sys_db | Options],
 +    KnownSysDbs = [
 +        {ReplicatorDbName, ReplicatorDbOptions},
 +        {UsersDbName, UsersDbOptions},
 +        {DbsDbName, DbsDbOptions},
 +        {NodesDbName, NodesDbOptions}
 +    ],
 +    case lists:keyfind(DbName, 1, KnownSysDbs) of
 +        {DbName, SysOptions} ->
 +            SysOptions;
 +        false ->
 +            Options
 +    end.
 +
 +check_dbname(#server{dbname_regexp=RegExp}, DbName) ->
 +    case re:run(DbName, RegExp, [{capture, none}]) of
 +    nomatch ->
 +        case DbName of
 +            "_users" -> ok;
 +            "_replicator" -> ok;
 +            _Else ->
 +                {error, illegal_database_name, DbName}
 +            end;
 +    match ->
 +        ok
 +    end.
 +
 +is_admin(User, ClearPwd) ->
 +    case config:get("admins", User) of
 +    "-hashed-" ++ HashedPwdAndSalt ->
 +        [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","),
 +        couch_util:to_hex(crypto:sha(ClearPwd ++ Salt)) == HashedPwd;
 +    _Else ->
 +        false
 +    end.
 +
 +has_admins() ->
 +    config:get("admins") /= [].
 +
 +get_full_filename(Server, DbName) ->
 +    filename:join([Server#server.root_dir, "./" ++ DbName ++ ".couch"]).
 +
 +hash_admin_passwords() ->
 +    hash_admin_passwords(true).
 +
 +hash_admin_passwords(Persist) ->
 +    lists:foreach(
 +        fun({User, ClearPassword}) ->
 +            HashedPassword = couch_passwords:hash_admin_password(ClearPassword),
 +            config:set("admins", User, ?b2l(HashedPassword), Persist)
 +        end, couch_passwords:get_unhashed_admins()).
 +
 +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.
 +
 +    RootDir = config:get("couchdb", "database_dir", "."),
 +    MaxDbsOpen = list_to_integer(
 +            config:get("couchdb", "max_dbs_open")),
 +    ok = config:listen_for_changes(?MODULE, nil),
 +    ok = couch_file:init_delete_dir(RootDir),
 +    hash_admin_passwords(),
 +    {ok, RegExp} = re:compile(
 +        "^[a-z][a-z0-9\\_\\$()\\+\\-\\/]*" % use the stock CouchDB regex
 +        "(\\.[0-9]{10,})?$" % but allow an optional shard timestamp at the end
 +    ),
 +    ets:new(couch_dbs, [set, protected, named_table, {keypos, #db.name}]),
 +    process_flag(trap_exit, true),
 +    {ok, #server{root_dir=RootDir,
 +                dbname_regexp=RegExp,
 +                max_dbs_open=MaxDbsOpen,
 +                start_time=couch_util:rfc1123_date()}}.
 +
 +terminate(_Reason, _Srv) ->
 +    ets:foldl(fun(#db{main_pid=Pid}, _) -> couch_util:shutdown_sync(Pid) end,
 +        nil, couch_dbs),
 +    ok.
 +
 +handle_config_change("couchdb", "database_dir", _, _, _) ->
 +    exit(whereis(couch_server), config_change),
 +    remove_handler;
 +handle_config_change("couchdb", "max_dbs_open", Max, _, _) ->
 +    {ok, gen_server:call(couch_server,{set_max_dbs_open,list_to_integer(Max)})};
 +handle_config_change("admins", _, _, Persist, _) ->
 +    % spawn here so couch event manager doesn't deadlock
 +    {ok, spawn(fun() -> hash_admin_passwords(Persist) end)};
 +handle_config_change("httpd", "authentication_handlers", _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd", "bind_address", _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd", "port", _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd", "max_connections", _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd", "default_handler", _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd_global_handlers", _, _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change("httpd_db_handlers", _, _, _, _) ->
 +    {ok, couch_httpd:stop()};
 +handle_config_change(_, _, _, _, _) ->
 +    {ok, nil}.
 +
 +
 +all_databases() ->
 +    {ok, DbList} = all_databases(
 +        fun(DbName, Acc) -> {ok, [DbName | Acc]} end, []),
 +    {ok, lists:usort(DbList)}.
 +
 +all_databases(Fun, Acc0) ->
 +    {ok, #server{root_dir=Root}} = gen_server:call(couch_server, get_server),
 +    NormRoot = couch_util:normpath(Root),
 +    FinalAcc = try
 +    filelib:fold_files(Root,
 +        "^[a-z0-9\\_\\$()\\+\\-]*" % stock CouchDB name regex
 +        "(\\.[0-9]{10,})?"         % optional shard timestamp
 +        "\\.couch$",               % filename extension
 +        true,
 +            fun(Filename, AccIn) ->
 +                NormFilename = couch_util:normpath(Filename),
 +                case NormFilename -- NormRoot of
 +                [$/ | RelativeFilename] -> ok;
 +                RelativeFilename -> ok
 +                end,
 +                case Fun(?l2b(filename:rootname(RelativeFilename, ".couch")), AccIn) of
 +                {ok, NewAcc} -> NewAcc;
 +                {stop, NewAcc} -> throw({stop, Fun, NewAcc})
 +                end
 +            end, Acc0)
 +    catch throw:{stop, Fun, Acc1} ->
 +         Acc1
 +    end,
 +    {ok, FinalAcc}.
 +
 +
 +make_room(Server, Options) ->
 +    case lists:member(sys_db, Options) of
 +        false -> maybe_close_lru_db(Server);
 +        true -> {ok, Server}
 +    end.
 +
 +maybe_close_lru_db(#server{dbs_open=NumOpen, max_dbs_open=MaxOpen}=Server)
 +        when NumOpen < MaxOpen ->
 +    {ok, Server};
 +maybe_close_lru_db(#server{lru=Lru}=Server) ->
 +    try
 +        {ok, db_closed(Server#server{lru = couch_lru:close(Lru)}, [])}
 +    catch error:all_dbs_active ->
 +        {error, all_dbs_active}
 +    end.
 +
 +open_async(Server, From, DbName, Filepath, Options) ->
 +    Parent = self(),
 +    put({async_open, DbName}, now()),
 +    Opener = spawn_link(fun() ->
 +        Res = couch_db:start_link(DbName, Filepath, Options),
 +        case {Res, lists:member(create, Options)} of
 +            {{ok, _Db}, true} ->
 +                couch_db_update_notifier:notify({created, DbName});
 +            _ ->
 +                ok
 +        end,
 +        gen_server:call(Parent, {open_result, DbName, Res}, infinity),
 +        unlink(Parent)
 +    end),
 +    ReqType = case lists:member(create, Options) of
 +        true -> create;
 +        false -> open
 +    end,
 +    % icky hack of field values - compactor_pid used to store clients
 +    % and fd used for opening request info
 +    true = ets:insert(couch_dbs, #db{
 +        name = DbName,
 +        fd = ReqType,
 +        main_pid = Opener,
 +        compactor_pid = [From],
 +        fd_monitor = locked,
 +        options = Options
 +    }),
 +    db_opened(Server, Options).
 +
 +handle_call(close_lru, _From, #server{lru=Lru} = Server) ->
 +    try
 +        {reply, ok, db_closed(Server#server{lru = couch_lru:close(Lru)}, [])}
 +    catch error:all_dbs_active ->
 +        {reply, {error, all_dbs_active}, Server}
 +    end;
 +handle_call(open_dbs_count, _From, Server) ->
 +    {reply, Server#server.dbs_open, Server};
 +handle_call({set_max_dbs_open, Max}, _From, Server) ->
 +    {reply, ok, Server#server{max_dbs_open=Max}};
 +handle_call(get_server, _From, Server) ->
 +    {reply, {ok, Server}, Server};
 +handle_call({open_result, DbName, {ok, Db}}, _From, Server) ->
 +    link(Db#db.main_pid),
 +    case erase({async_open, DbName}) of undefined -> ok; T0 ->
 +        ?LOG_INFO("needed ~p ms to open new ~s", [timer:now_diff(now(),T0)/1000,
 +            DbName])
 +    end,
 +    % icky hack of field values - compactor_pid used to store clients
 +    % and fd used to possibly store a creation request
 +    [#db{fd=ReqType, compactor_pid=Froms}] = ets:lookup(couch_dbs, DbName),
 +    [gen_server:reply(From, {ok, Db}) || From <- Froms],
 +    % Cancel the creation request if it exists.
 +    case ReqType of
 +        {create, DbName, _Filepath, _Options, CrFrom} ->
 +            gen_server:reply(CrFrom, file_exists);
 +        _ ->
 +            ok
 +    end,
 +    true = ets:insert(couch_dbs, Db),
 +    Lru = case couch_db:is_system_db(Db) of
 +        false ->
 +            Stat = {couchdb, open_databases},
 +            couch_stats_collector:track_process_count(Db#db.main_pid, Stat),
 +            couch_lru:insert(DbName, Server#server.lru);
 +        true ->
 +            Server#server.lru
 +    end,
 +    {reply, ok, Server#server{lru = Lru}};
 +handle_call({open_result, DbName, {error, eexist}}, From, Server) ->
 +    handle_call({open_result, DbName, file_exists}, From, Server);
 +handle_call({open_result, DbName, Error}, _From, Server) ->
 +    % icky hack of field values - compactor_pid used to store clients
 +    [#db{fd=ReqType, compactor_pid=Froms}=Db] = ets:lookup(couch_dbs, DbName),
 +    [gen_server:reply(From, Error) || From <- Froms],
 +    ?LOG_INFO("open_result error ~p for ~s", [Error, DbName]),
 +    true = ets:delete(couch_dbs, DbName),
 +    NewServer = case ReqType of
 +        {create, DbName, Filepath, Options, CrFrom} ->
 +            open_async(Server, CrFrom, DbName, Filepath, Options);
 +        _ ->
 +            Server
 +    end,
 +    {reply, ok, db_closed(NewServer, Db#db.options)};
 +handle_call({open, DbName, Options}, From, Server) ->
 +    case ets:lookup(couch_dbs, DbName) of
 +    [] ->
 +        DbNameList = binary_to_list(DbName),
 +        case check_dbname(Server, DbNameList) of
 +        ok ->
 +            case make_room(Server, Options) of
 +            {ok, Server2} ->
 +                Filepath = get_full_filename(Server, DbNameList),
 +                {noreply, open_async(Server2, From, DbName, Filepath, Options)};
 +            CloseError ->
 +                {reply, CloseError, Server}
 +            end;
 +        Error ->
 +            {reply, Error, Server}
 +        end;
 +    [#db{compactor_pid = Froms} = Db] when is_list(Froms) ->
 +        % icky hack of field values - compactor_pid used to store clients
 +        true = ets:insert(couch_dbs, Db#db{compactor_pid = [From|Froms]}),
 +        if length(Froms) =< 10 -> ok; true ->
 +            Fmt = "~b clients waiting to open db ~s",
 +            ?LOG_INFO(Fmt, [length(Froms), DbName])
 +        end,
 +        {noreply, Server};
 +    [#db{} = Db] ->
 +        {reply, {ok, Db}, Server}
 +    end;
 +handle_call({create, DbName, Options}, From, Server) ->
 +    DbNameList = binary_to_list(DbName),
 +    Filepath = get_full_filename(Server, DbNameList),
 +    case check_dbname(Server, DbNameList) of
 +    ok ->
 +        case ets:lookup(couch_dbs, DbName) of
 +        [] ->
 +            case make_room(Server, Options) of
 +            {ok, Server2} ->
 +                {noreply, open_async(Server2, From, DbName, Filepath,
 +                        [create | Options])};
 +            CloseError ->
 +                {reply, CloseError, Server}
 +            end;
 +        [#db{fd=open}=Db] ->
 +            % We're trying to create a database while someone is in
 +            % the middle of trying to open it. We allow one creator
 +            % to wait while we figure out if it'll succeed.
 +            % icky hack of field values - fd used to store create request
 +            CrOptions = [create | Options],
 +            NewDb = Db#db{fd={create, DbName, Filepath, CrOptions, From}},
 +            true = ets:insert(couch_dbs, NewDb),
 +            {noreply, Server};
 +        [_AlreadyRunningDb] ->
 +            {reply, file_exists, Server}
 +        end;
 +    Error ->
 +        {reply, Error, Server}
 +    end;
 +handle_call({delete, DbName, Options}, _From, Server) ->
 +    DbNameList = binary_to_list(DbName),
 +    case check_dbname(Server, DbNameList) of
 +    ok ->
 +        FullFilepath = get_full_filename(Server, DbNameList),
 +        Server2 =
 +        case ets:lookup(couch_dbs, DbName) of
 +        [] -> Server;
 +        [#db{main_pid=Pid, compactor_pid=Froms} = Db] when is_list(Froms) ->
 +            % icky hack of field values - compactor_pid used to store clients
 +            true = ets:delete(couch_dbs, DbName),
 +            exit(Pid, kill),
 +            [gen_server:reply(F, not_found) || F <- Froms],
 +            db_closed(Server, Db#db.options);
 +        [#db{main_pid=Pid} = Db] ->
 +            true = ets:delete(couch_dbs, DbName),
 +            exit(Pid, kill),
 +            db_closed(Server, Db#db.options)
 +        end,
 +
 +        %% Delete any leftover compaction files. If we don't do this a
 +        %% subsequent request for this DB will try to open them to use
 +        %% as a recovery.
 +        lists:foreach(fun(Ext) ->
 +            couch_file:delete(Server#server.root_dir, FullFilepath ++ Ext)
 +        end, [".compact", ".compact.data", ".compact.meta"]),
 +        couch_file:delete(Server#server.root_dir, FullFilepath ++ ".compact"),
 +
 +        Async = not lists:member(sync, Options),
 +
 +        case couch_file:delete(Server#server.root_dir, FullFilepath, Async) of
 +        ok ->
 +            couch_db_update_notifier:notify({deleted, DbName}),
 +            {reply, ok, Server2};
 +        {error, enoent} ->
 +            {reply, not_found, Server2};
 +        Else ->
 +            {reply, Else, Server2}
 +        end;
 +    Error ->
 +        {reply, Error, Server}
 +    end;
 +handle_call({db_updated, #db{name = DbName} = Db}, _From, Server) ->
 +    true = ets:insert(couch_dbs, Db),
 +    Lru = case couch_db:is_system_db(Db) of
 +        false -> couch_lru:update(DbName, Server#server.lru);
 +        true -> Server#server.lru
 +    end,
 +    {reply, ok, Server#server{lru = Lru}}.
 +
 +handle_cast({update_lru, DbName}, #server{lru = Lru} = Server) ->
 +    {noreply, Server#server{lru = couch_lru:update(DbName, Lru)}};
 +handle_cast(Msg, Server) ->
 +    {stop, {unknown_cast_message, Msg}, Server}.
 +
 +code_change(_, State, _) ->
 +    {ok, State}.
 +
 +handle_info({'EXIT', _Pid, config_change}, Server) ->
 +    {stop, config_change, Server};
 +handle_info({'EXIT', Pid, Reason}, Server) ->
 +    case ets:match_object(couch_dbs, #db{main_pid=Pid, _='_'}) of
 +    [#db{name = DbName, compactor_pid=Froms} = Db] ->
 +        if Reason /= snappy_nif_not_loaded -> ok; true ->
 +            Msg = io_lib:format("To open the database `~s`, Apache CouchDB "
 +                "must be built with Erlang OTP R13B04 or higher.", [DbName]),
 +            ?LOG_ERROR(Msg, [])
 +        end,
 +        ?LOG_INFO("db ~s died with reason ~p", [DbName, Reason]),
 +        % icky hack of field values - compactor_pid used to store clients
 +        if is_list(Froms) ->
 +            [gen_server:reply(From, Reason) || From <- Froms];
 +        true ->
 +            ok
 +        end,
 +        true = ets:delete(couch_dbs, DbName),
 +        {noreply, db_closed(Server, Db#db.options)};
 +    [] ->
 +        {noreply, Server}
 +    end;
 +handle_info(Info, Server) ->
 +    {stop, {unknown_message, Info}, Server}.
 +
 +db_opened(Server, Options) ->
 +    case lists:member(sys_db, Options) of
 +        false -> Server#server{dbs_open=Server#server.dbs_open + 1};
 +        true -> Server
 +    end.
 +
 +db_closed(Server, Options) ->
 +    case lists:member(sys_db, Options) of
 +        false -> Server#server{dbs_open=Server#server.dbs_open - 1};
 +        true -> Server
 +    end.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9d629ff6/src/couch_users_db.erl
----------------------------------------------------------------------
diff --cc src/couch_users_db.erl
index 76acfee,0000000..3b76862
mode 100644,000000..100644
--- a/src/couch_users_db.erl
+++ b/src/couch_users_db.erl
@@@ -1,110 -1,0 +1,121 @@@
 +% 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_users_db).
 +
- -export([before_doc_update/2, after_doc_read/2]).
++-export([before_doc_update/2, after_doc_read/2, strip_non_public_fields/1]).
 +
 +-include_lib("couch/include/couch_db.hrl").
 +
 +-define(NAME, <<"name">>).
 +-define(PASSWORD, <<"password">>).
 +-define(DERIVED_KEY, <<"derived_key">>).
 +-define(PASSWORD_SCHEME, <<"password_scheme">>).
 +-define(PBKDF2, <<"pbkdf2">>).
 +-define(ITERATIONS, <<"iterations">>).
 +-define(SALT, <<"salt">>).
 +-define(replace(L, K, V), lists:keystore(K, 1, L, {K, V})).
 +
 +% If the request's userCtx identifies an admin
 +%   -> save_doc (see below)
 +%
 +% If the request's userCtx.name is null:
 +%   -> save_doc
 +%   // this is an anonymous user registering a new document
 +%   // in case a user doc with the same id already exists, the anonymous
 +%   // user will get a regular doc update conflict.
 +% If the request's userCtx.name doesn't match the doc's name
 +%   -> 404 // Not Found
 +% Else
 +%   -> save_doc
 +before_doc_update(Doc, #db{user_ctx = UserCtx} = Db) ->
 +    #user_ctx{name=Name} = UserCtx,
 +    DocName = get_doc_name(Doc),
 +    case (catch couch_db:check_is_admin(Db)) of
 +    ok ->
 +        save_doc(Doc);
 +    _ when Name =:= DocName orelse Name =:= null ->
 +        save_doc(Doc);
 +    _ ->
 +        throw(not_found)
 +    end.
 +
 +% If newDoc.password == null || newDoc.password == undefined:
 +%   ->
 +%   noop
 +% Else -> // calculate password hash server side
 +%    newDoc.password_sha = hash_pw(newDoc.password + salt)
 +%    newDoc.salt = salt
 +%    newDoc.password = null
 +save_doc(#doc{body={Body}} = Doc) ->
 +    case couch_util:get_value(?PASSWORD, Body) of
 +    null -> % server admins don't have a user-db password entry
 +        Doc;
 +    undefined ->
 +        Doc;
 +    ClearPassword ->
 +        Iterations = list_to_integer(config:get("couch_httpd_auth", "iterations", "1000")),
 +        Salt = couch_uuids:random(),
 +        DerivedKey = couch_passwords:pbkdf2(ClearPassword, Salt, Iterations),
 +        Body0 = [{?PASSWORD_SCHEME, ?PBKDF2}, {?ITERATIONS, Iterations}|Body],
 +        Body1 = ?replace(Body0, ?DERIVED_KEY, DerivedKey),
 +        Body2 = ?replace(Body1, ?SALT, Salt),
 +        Body3 = proplists:delete(?PASSWORD, Body2),
 +        Doc#doc{body={Body3}}
 +    end.
 +
 +% If the doc is a design doc
 +%   If the request's userCtx identifies an admin
 +%     -> return doc
 +%   Else
 +%     -> 403 // Forbidden
 +% If the request's userCtx identifies an admin
 +%   -> return doc
 +% If the request's userCtx.name doesn't match the doc's name
 +%   -> 404 // Not Found
 +% Else
 +%   -> return doc
 +after_doc_read(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, Db) ->
 +    case (catch couch_db:check_is_admin(Db)) of
 +    ok ->
 +        Doc;
 +    _ ->
 +        throw({forbidden,
 +        <<"Only administrators can view design docs in the users database.">>})
 +    end;
 +after_doc_read(Doc, #db{user_ctx = UserCtx} = Db) ->
 +    #user_ctx{name=Name} = UserCtx,
 +    DocName = get_doc_name(Doc),
 +    case (catch couch_db:check_is_admin(Db)) of
 +    ok ->
 +        Doc;
 +    _ when Name =:= DocName ->
 +        Doc;
 +    _ ->
-         throw(not_found)
++        Doc1 = strip_non_public_fields(Doc),
++        case Doc1 of
++          #doc{body={[]}} ->
++              throw(not_found);
++          _ ->
++              Doc1
++        end
 +    end.
 +
 +get_doc_name(#doc{id= <<"org.couchdb.user:", Name/binary>>}) ->
 +    Name;
 +get_doc_name(_) ->
 +    undefined.
++
++strip_non_public_fields(#doc{body={Props}}=Doc) ->
++    Public = re:split(config:get("couch_httpd_auth", "public_fields", ""),
++                      "\\s*,\\s*", [{return, binary}]),
++    Doc#doc{body={[{K, V} || {K, V} <- Props, lists:member(K, Public)]}}.

http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/9d629ff6/src/couch_util.erl
----------------------------------------------------------------------
diff --cc src/couch_util.erl
index beb9622,0000000..20dfdaf
mode 100644,000000..100644
--- a/src/couch_util.erl
+++ b/src/couch_util.erl
@@@ -1,512 -1,0 +1,544 @@@
 +% 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_util).
 +
 +-export([priv_dir/0, normpath/1]).
 +-export([should_flush/0, should_flush/1, to_existing_atom/1]).
 +-export([rand32/0, implode/2, collate/2, collate/3]).
 +-export([abs_pathname/1,abs_pathname/2, trim/1]).
 +-export([encodeBase64Url/1, decodeBase64Url/1]).
 +-export([validate_utf8/1, to_hex/1, parse_term/1, dict_find/3]).
 +-export([get_nested_json_value/2, json_user_ctx/1]).
 +-export([proplist_apply_field/2, json_apply_field/2]).
 +-export([to_binary/1, to_integer/1, to_list/1, url_encode/1]).
 +-export([json_encode/1, json_decode/1]).
 +-export([verify/2,simple_call/2,shutdown_sync/1]).
 +-export([get_value/2, get_value/3]).
 +-export([md5/1, md5_init/0, md5_update/2, md5_final/1]).
 +-export([reorder_results/2]).
 +-export([url_strip_password/1]).
 +-export([encode_doc_id/1]).
 +-export([with_db/2]).
 +-export([rfc1123_date/0, rfc1123_date/1]).
 +-export([integer_to_boolean/1, boolean_to_integer/1]).
++-export([find_in_binary/2]).
 +
 +-include_lib("couch/include/couch_db.hrl").
 +
 +% arbitrarily chosen amount of memory to use before flushing to disk
 +-define(FLUSH_MAX_MEM, 10000000).
 +
 +priv_dir() ->
 +    case code:priv_dir(couch) of
 +        {error, bad_name} ->
 +            % small hack, in dev mode "app" is couchdb. Fixing requires
 +            % renaming src/couch to src/couch. Not really worth the hassle.
 +            % -Damien
 +            code:priv_dir(couchdb);
 +        Dir -> Dir
 +    end.
 +
 +% Normalize a pathname by removing .. and . components.
 +normpath(Path) ->
 +    normparts(filename:split(Path), []).
 +
 +normparts([], Acc) ->
 +    filename:join(lists:reverse(Acc));
 +normparts([".." | RestParts], [_Drop | RestAcc]) ->
 +    normparts(RestParts, RestAcc);
 +normparts(["." | RestParts], Acc) ->
 +    normparts(RestParts, Acc);
 +normparts([Part | RestParts], Acc) ->
 +    normparts(RestParts, [Part | Acc]).
 +
 +% works like list_to_existing_atom, except can be list or binary and it
 +% gives you the original value instead of an error if no existing atom.
 +to_existing_atom(V) when is_list(V) ->
 +    try list_to_existing_atom(V) catch _:_ -> V end;
 +to_existing_atom(V) when is_binary(V) ->
 +    try list_to_existing_atom(?b2l(V)) catch _:_ -> V end;
 +to_existing_atom(V) when is_atom(V) ->
 +    V.
 +
 +shutdown_sync(Pid) when not is_pid(Pid)->
 +    ok;
 +shutdown_sync(Pid) ->
 +    MRef = erlang:monitor(process, Pid),
 +    try
 +        catch unlink(Pid),
 +        catch exit(Pid, shutdown),
 +        receive
 +        {'DOWN', MRef, _, _, _} ->
 +            ok
 +        end
 +    after
 +        erlang:demonitor(MRef, [flush])
 +    end.
 +
 +
 +simple_call(Pid, Message) ->
 +    MRef = erlang:monitor(process, Pid),
 +    try
 +        Pid ! {self(), Message},
 +        receive
 +        {Pid, Result} ->
 +            Result;
 +        {'DOWN', MRef, _, _, Reason} ->
 +            exit(Reason)
 +        end
 +    after
 +        erlang:demonitor(MRef, [flush])
 +    end.
 +
 +validate_utf8(Data) when is_list(Data) ->
 +    validate_utf8(?l2b(Data));
 +validate_utf8(Bin) when is_binary(Bin) ->
 +    validate_utf8_fast(Bin, 0).
 +
 +validate_utf8_fast(B, O) ->
 +    case B of
 +        <<_:O/binary>> ->
 +            true;
 +        <<_:O/binary, C1, _/binary>> when
 +                C1 < 128 ->
 +            validate_utf8_fast(B, 1 + O);
 +        <<_:O/binary, C1, C2, _/binary>> when
 +                C1 >= 194, C1 =< 223,
 +                C2 >= 128, C2 =< 191 ->
 +            validate_utf8_fast(B, 2 + O);
 +        <<_:O/binary, C1, C2, C3, _/binary>> when
 +                C1 >= 224, C1 =< 239,
 +                C2 >= 128, C2 =< 191,
 +                C3 >= 128, C3 =< 191 ->
 +            validate_utf8_fast(B, 3 + O);
 +        <<_:O/binary, C1, C2, C3, C4, _/binary>> when
 +                C1 >= 240, C1 =< 244,
 +                C2 >= 128, C2 =< 191,
 +                C3 >= 128, C3 =< 191,
 +                C4 >= 128, C4 =< 191 ->
 +            validate_utf8_fast(B, 4 + O);
 +        _ ->
 +            false
 +    end.
 +
 +to_hex([]) ->
 +    [];
 +to_hex(Bin) when is_binary(Bin) ->
 +    to_hex(binary_to_list(Bin));
 +to_hex([H|T]) ->
 +    [to_digit(H div 16), to_digit(H rem 16) | to_hex(T)].
 +
 +to_digit(N) when N < 10 -> $0 + N;
 +to_digit(N)             -> $a + N-10.
 +
 +
 +parse_term(Bin) when is_binary(Bin) ->
 +    parse_term(binary_to_list(Bin));
 +parse_term(List) ->
 +    {ok, Tokens, _} = erl_scan:string(List ++ "."),
 +    erl_parse:parse_term(Tokens).
 +
 +get_value(Key, List) ->
 +    get_value(Key, List, undefined).
 +
 +get_value(Key, List, Default) ->
 +    case lists:keysearch(Key, 1, List) of
 +    {value, {Key,Value}} ->
 +        Value;
 +    false ->
 +        Default
 +    end.
 +
 +get_nested_json_value({Props}, [Key|Keys]) ->
 +    case couch_util:get_value(Key, Props, nil) of
 +    nil -> throw({not_found, <<"missing json key: ", Key/binary>>});
 +    Value -> get_nested_json_value(Value, Keys)
 +    end;
 +get_nested_json_value(Value, []) ->
 +    Value;
 +get_nested_json_value(_NotJSONObj, _) ->
 +    throw({not_found, json_mismatch}).
 +
 +proplist_apply_field(H, L) ->
 +    {R} = json_apply_field(H, {L}),
 +    R.
 +
 +json_apply_field(H, {L}) ->
 +    json_apply_field(H, L, []).
 +json_apply_field({Key, NewValue}, [{Key, _OldVal} | Headers], Acc) ->
 +    json_apply_field({Key, NewValue}, Headers, Acc);
 +json_apply_field({Key, NewValue}, [{OtherKey, OtherVal} | Headers], Acc) ->
 +    json_apply_field({Key, NewValue}, Headers, [{OtherKey, OtherVal} | Acc]);
 +json_apply_field({Key, NewValue}, [], Acc) ->
 +    {[{Key, NewValue}|Acc]}.
 +
 +json_user_ctx(#db{name=ShardName, user_ctx=Ctx}) ->
 +    {[{<<"db">>, mem3:dbname(ShardName)},
 +            {<<"name">>,Ctx#user_ctx.name},
 +            {<<"roles">>,Ctx#user_ctx.roles}]}.
 +
 +
 +% returns a random integer
 +rand32() ->
 +    crypto:rand_uniform(0, 16#100000000).
 +
 +% given a pathname "../foo/bar/" it gives back the fully qualified
 +% absolute pathname.
 +abs_pathname(" " ++ Filename) ->
 +    % strip leading whitspace
 +    abs_pathname(Filename);
 +abs_pathname([$/ |_]=Filename) ->
 +    Filename;
 +abs_pathname(Filename) ->
 +    {ok, Cwd} = file:get_cwd(),
 +    {Filename2, Args} = separate_cmd_args(Filename, ""),
 +    abs_pathname(Filename2, Cwd) ++ Args.
 +
 +abs_pathname(Filename, Dir) ->
 +    Name = filename:absname(Filename, Dir ++ "/"),
 +    OutFilename = filename:join(fix_path_list(filename:split(Name), [])),
 +    % If the filename is a dir (last char slash, put back end slash
 +    case string:right(Filename,1) of
 +    "/" ->
 +        OutFilename ++ "/";
 +    "\\" ->
 +        OutFilename ++ "/";
 +    _Else->
 +        OutFilename
 +    end.
 +
 +% if this as an executable with arguments, seperate out the arguments
 +% ""./foo\ bar.sh -baz=blah" -> {"./foo\ bar.sh", " -baz=blah"}
 +separate_cmd_args("", CmdAcc) ->
 +    {lists:reverse(CmdAcc), ""};
 +separate_cmd_args("\\ " ++ Rest, CmdAcc) -> % handle skipped value
 +    separate_cmd_args(Rest, " \\" ++ CmdAcc);
 +separate_cmd_args(" " ++ Rest, CmdAcc) ->
 +    {lists:reverse(CmdAcc), " " ++ Rest};
 +separate_cmd_args([Char|Rest], CmdAcc) ->
 +    separate_cmd_args(Rest, [Char | CmdAcc]).
 +
 +% Is a character whitespace?
 +is_whitespace($\s) -> true;
 +is_whitespace($\t) -> true;
 +is_whitespace($\n) -> true;
 +is_whitespace($\r) -> true;
 +is_whitespace(_Else) -> false.
 +
 +
 +% removes leading and trailing whitespace from a string
 +trim(String) ->
 +    String2 = lists:dropwhile(fun is_whitespace/1, String),
 +    lists:reverse(lists:dropwhile(fun is_whitespace/1, lists:reverse(String2))).
 +
 +% takes a heirarchical list of dirs and removes the dots ".", double dots
 +% ".." and the corresponding parent dirs.
 +fix_path_list([], Acc) ->
 +    lists:reverse(Acc);
 +fix_path_list([".."|Rest], [_PrevAcc|RestAcc]) ->
 +    fix_path_list(Rest, RestAcc);
 +fix_path_list(["."|Rest], Acc) ->
 +    fix_path_list(Rest, Acc);
 +fix_path_list([Dir | Rest], Acc) ->
 +    fix_path_list(Rest, [Dir | Acc]).
 +
 +
 +implode(List, Sep) ->
 +    implode(List, Sep, []).
 +
 +implode([], _Sep, Acc) ->
 +    lists:flatten(lists:reverse(Acc));
 +implode([H], Sep, Acc) ->
 +    implode([], Sep, [H|Acc]);
 +implode([H|T], Sep, Acc) ->
 +    implode(T, Sep, [Sep,H|Acc]).
 +
 +
 +drv_port() ->
 +    case get(couch_drv_port) of
 +    undefined ->
 +        Port = open_port({spawn, "couch_icu_driver"}, []),
 +        put(couch_drv_port, Port),
 +        Port;
 +    Port ->
 +        Port
 +    end.
 +
 +collate(A, B) ->
 +    collate(A, B, []).
 +
 +collate(A, B, Options) when is_binary(A), is_binary(B) ->
 +    Operation =
 +    case lists:member(nocase, Options) of
 +        true -> 1; % Case insensitive
 +        false -> 0 % Case sensitive
 +    end,
 +    SizeA = byte_size(A),
 +    SizeB = byte_size(B),
 +    Bin = <<SizeA:32/native, A/binary, SizeB:32/native, B/binary>>,
 +    [Result] = erlang:port_control(drv_port(), Operation, Bin),
 +    % Result is 0 for lt, 1 for eq and 2 for gt. Subtract 1 to return the
 +    % expected typical -1, 0, 1
 +    Result - 1.
 +
 +should_flush() ->
 +    should_flush(?FLUSH_MAX_MEM).
 +
 +should_flush(MemThreshHold) ->
 +    {memory, ProcMem} = process_info(self(), memory),
 +    BinMem = lists:foldl(fun({_Id, Size, _NRefs}, Acc) -> Size+Acc end,
 +        0, element(2,process_info(self(), binary))),
 +    if ProcMem+BinMem > 2*MemThreshHold ->
 +        garbage_collect(),
 +        {memory, ProcMem2} = process_info(self(), memory),
 +        BinMem2 = lists:foldl(fun({_Id, Size, _NRefs}, Acc) -> Size+Acc end,
 +            0, element(2,process_info(self(), binary))),
 +        ProcMem2+BinMem2 > MemThreshHold;
 +    true -> false end.
 +
 +encodeBase64Url(Url) ->
 +    Url1 = re:replace(base64:encode(Url), ["=+", $$], ""),
 +    Url2 = re:replace(Url1, "/", "_", [global]),
 +    re:replace(Url2, "\\+", "-", [global, {return, binary}]).
 +
 +decodeBase64Url(Url64) ->
 +    Url1 = re:replace(Url64, "-", "+", [global]),
 +    Url2 = re:replace(Url1, "_", "/", [global]),
 +    Padding = lists:duplicate((4 - iolist_size(Url2) rem 4) rem 4, $=),
 +    base64:decode(iolist_to_binary([Url2, Padding])).
 +
 +dict_find(Key, Dict, DefaultValue) ->
 +    case dict:find(Key, Dict) of
 +    {ok, Value} ->
 +        Value;
 +    error ->
 +        DefaultValue
 +    end.
 +
 +to_binary(V) when is_binary(V) ->
 +    V;
 +to_binary(V) when is_list(V) ->
 +    try
 +        list_to_binary(V)
 +    catch
 +        _:_ ->
 +            list_to_binary(io_lib:format("~p", [V]))
 +    end;
 +to_binary(V) when is_atom(V) ->
 +    list_to_binary(atom_to_list(V));
 +to_binary(V) ->
 +    list_to_binary(io_lib:format("~p", [V])).
 +
 +to_integer(V) when is_integer(V) ->
 +    V;
 +to_integer(V) when is_list(V) ->
 +    erlang:list_to_integer(V);
 +to_integer(V) when is_binary(V) ->
 +    erlang:list_to_integer(binary_to_list(V)).
 +
 +to_list(V) when is_list(V) ->
 +    V;
 +to_list(V) when is_binary(V) ->
 +    binary_to_list(V);
 +to_list(V) when is_atom(V) ->
 +    atom_to_list(V);
 +to_list(V) ->
 +    lists:flatten(io_lib:format("~p", [V])).
 +
 +url_encode(Bin) when is_binary(Bin) ->
 +    url_encode(binary_to_list(Bin));
 +url_encode([H|T]) ->
 +    if
 +    H >= $a, $z >= H ->
 +        [H|url_encode(T)];
 +    H >= $A, $Z >= H ->
 +        [H|url_encode(T)];
 +    H >= $0, $9 >= H ->
 +        [H|url_encode(T)];
 +    H == $_; H == $.; H == $-; H == $: ->
 +        [H|url_encode(T)];
 +    true ->
 +        case lists:flatten(io_lib:format("~.16.0B", [H])) of
 +        [X, Y] ->
 +            [$%, X, Y | url_encode(T)];
 +        [X] ->
 +            [$%, $0, X | url_encode(T)]
 +        end
 +    end;
 +url_encode([]) ->
 +    [].
 +
 +json_encode(V) ->
 +    jiffy:encode(V, [force_utf8]).
 +
 +json_decode(V) ->
 +    try
 +        jiffy:decode(V)
 +    catch
 +        throw:Error ->
 +            throw({invalid_json, Error})
 +    end.
 +
 +verify([X|RestX], [Y|RestY], Result) ->
 +    verify(RestX, RestY, (X bxor Y) bor Result);
 +verify([], [], Result) ->
 +    Result == 0.
 +
 +verify(<<X/binary>>, <<Y/binary>>) ->
 +    verify(?b2l(X), ?b2l(Y));
 +verify(X, Y) when is_list(X) and is_list(Y) ->
 +    case length(X) == length(Y) of
 +        true ->
 +            verify(X, Y, 0);
 +        false ->
 +            false
 +    end;
 +verify(_X, _Y) -> false.
 +
 +-spec md5(Data::(iolist() | binary())) -> Digest::binary().
 +md5(Data) ->
 +    try crypto:md5(Data) catch error:_ -> erlang:md5(Data) end.
 +
 +-spec md5_init() -> Context::binary().
 +md5_init() ->
 +    try crypto:md5_init() catch error:_ -> erlang:md5_init() end.
 +
 +-spec md5_update(Context::binary(), Data::(iolist() | binary())) ->
 +    NewContext::binary().
 +md5_update(Ctx, D) ->
 +    try crypto:md5_update(Ctx,D) catch error:_ -> erlang:md5_update(Ctx,D) end.
 +
 +-spec md5_final(Context::binary()) -> Digest::binary().
 +md5_final(Ctx) ->
 +    try crypto:md5_final(Ctx) catch error:_ -> erlang:md5_final(Ctx) end.
 +
 +% linear search is faster for small lists, length() is 0.5 ms for 100k list
 +reorder_results(Keys, SortedResults) when length(Keys) < 100 ->
 +    [couch_util:get_value(Key, SortedResults) || Key <- Keys];
 +reorder_results(Keys, SortedResults) ->
 +    KeyDict = dict:from_list(SortedResults),
 +    [dict:fetch(Key, KeyDict) || Key <- Keys].
 +
 +url_strip_password(Url) ->
 +    re:replace(Url,
 +        "http(s)?://([^:]+):[^@]+@(.*)$",
 +        "http\\1://\\2:*****@\\3",
 +        [{return, list}]).
 +
 +encode_doc_id(#doc{id = Id}) ->
 +    encode_doc_id(Id);
 +encode_doc_id(Id) when is_list(Id) ->
 +    encode_doc_id(?l2b(Id));
 +encode_doc_id(<<"_design/", Rest/binary>>) ->
 +    "_design/" ++ url_encode(Rest);
 +encode_doc_id(<<"_local/", Rest/binary>>) ->
 +    "_local/" ++ url_encode(Rest);
 +encode_doc_id(Id) ->
 +    url_encode(Id).
 +
 +
 +with_db(Db, Fun) when is_record(Db, db) ->
 +    Fun(Db);
 +with_db(DbName, Fun) ->
 +    case couch_db:open_int(DbName, [{user_ctx, #user_ctx{roles=[<<"_admin">>]}}])
of
 +        {ok, Db} ->
 +            try
 +                Fun(Db)
 +            after
 +                catch couch_db:close(Db)
 +            end;
 +        Else ->
 +            throw(Else)
 +    end.
 +
 +rfc1123_date() ->
 +    {{YYYY,MM,DD},{Hour,Min,Sec}} = calendar:universal_time(),
 +    DayNumber = calendar:day_of_the_week({YYYY,MM,DD}),
 +    lists:flatten(
 +      io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT",
 +            [day(DayNumber),DD,month(MM),YYYY,Hour,Min,Sec])).
 +
 +rfc1123_date(undefined) ->
 +    undefined;
 +rfc1123_date(UniversalTime) ->
 +    {{YYYY,MM,DD},{Hour,Min,Sec}} = UniversalTime,
 +    DayNumber = calendar:day_of_the_week({YYYY,MM,DD}),
 +    lists:flatten(
 +      io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT",
 +            [day(DayNumber),DD,month(MM),YYYY,Hour,Min,Sec])).
 +
 +%% day
 +
 +day(1) -> "Mon";
 +day(2) -> "Tue";
 +day(3) -> "Wed";
 +day(4) -> "Thu";
 +day(5) -> "Fri";
 +day(6) -> "Sat";
 +day(7) -> "Sun".
 +
 +%% month
 +
 +month(1) -> "Jan";
 +month(2) -> "Feb";
 +month(3) -> "Mar";
 +month(4) -> "Apr";
 +month(5) -> "May";
 +month(6) -> "Jun";
 +month(7) -> "Jul";
 +month(8) -> "Aug";
 +month(9) -> "Sep";
 +month(10) -> "Oct";
 +month(11) -> "Nov";
 +month(12) -> "Dec".
 +
 +integer_to_boolean(1) ->
 +    true;
 +integer_to_boolean(0) ->
 +    false.
 +
 +boolean_to_integer(true) ->
 +    1;
 +boolean_to_integer(false) ->
 +    0.
++
++
++find_in_binary(_B, <<>>) ->
++    not_found;
++
++find_in_binary(B, Data) ->
++    case binary:match(Data, [B], []) of
++    nomatch ->
++        MatchLength = erlang:min(byte_size(B), byte_size(Data)),
++        match_prefix_at_end(binary:part(B, {0, MatchLength}),
++                            binary:part(Data, {byte_size(Data), -MatchLength}),
++                            MatchLength, byte_size(Data) - MatchLength);
++    {Pos, _Len} ->
++        {exact, Pos}
++    end.
++
++match_prefix_at_end(Prefix, Data, PrefixLength, N) ->
++    FirstCharMatches = binary:matches(Data, [binary:part(Prefix, {0, 1})], []),
++    match_rest_of_prefix(FirstCharMatches, Prefix, Data, PrefixLength, N).
++
++match_rest_of_prefix([], _Prefix, _Data, _PrefixLength, _N) ->
++    not_found;
++
++match_rest_of_prefix([{Pos, _Len} | Rest], Prefix, Data, PrefixLength, N) ->
++    case binary:match(binary:part(Data, {PrefixLength, Pos - PrefixLength}),
++                      [binary:part(Prefix, {0, PrefixLength - Pos})], []) of
++        nomatch ->
++            match_rest_of_prefix(Rest, Prefix, Data, PrefixLength, N);
++        {_Pos, _Len1} ->
++            {partial, N + Pos}
++    end.


Mime
View raw message