Current section

Files

Jump to
couchbeam_amuino src couchbeam_util.erl
Raw

src/couchbeam_util.erl

%%% -*- erlang -*-
%%%
%%% This file is part of couchbeam released under the MIT license.
%%% See the NOTICE for more information.
-module(couchbeam_util).
-include_lib("hackney/include/hackney.hrl").
-include_lib("hackney/include/hackney_lib.hrl").
-export([dbname/1]).
-export([encode_docid/1, encode_att_name/1]).
-export([parse_options/1, parse_options/2]).
-export([to_list/1, to_binary/1, to_integer/1, to_atom/1]).
-export([encode_query/1, encode_query_value/2]).
-export([oauth_header/3]).
-export([propmerge/3, propmerge1/2]).
-export([get_value/2, get_value/3]).
-export([deprecated/3, shutdown_sync/1]).
-export([start_app_deps/1, get_app_env/2]).
-export([encode_docid1/1, encode_docid_noop/1]).
-export([force_param/3]).
-export([proxy_token/2, proxy_header/3]).
-define(PROXY_AUTH_HEADERS,[
{username,<<"X-Auth-CouchDB-UserName">>},
{roles,<<"X-Auth-CouchDB-Roles">>},
{token,<<"X-Auth-CouchDB-Token">>}]).
-define(ENCODE_DOCID_FUNC, encode_docid1).
dbname(DbName) when is_list(DbName) ->
list_to_binary(DbName);
dbname(DbName) when is_binary(DbName) ->
DbName;
dbname(DbName) ->
erlang:error({illegal_database_name, DbName}).
encode_att_name(Name) when is_binary(Name) ->
encode_att_name(xmerl_ucs:from_utf8(Name));
encode_att_name(Name) ->
Parts = lists:foldl(fun(P, Att) ->
[xmerl_ucs:to_utf8(P)|Att]
end, [], string:tokens(Name, "/")),
lists:flatten(Parts).
encode_docid(DocId) when is_list(DocId) ->
encode_docid(list_to_binary(DocId));
encode_docid(DocId)->
?ENCODE_DOCID_FUNC(DocId).
encode_docid1(DocId) ->
case DocId of
<< "_design/", Rest/binary >> ->
Rest1 = hackney_url:urlencode(Rest, [noplus]),
<<"_design/", Rest1/binary >>;
_ ->
hackney_url:urlencode(DocId, [noplus])
end.
encode_docid_noop(DocId) ->
DocId.
%% @doc Encode needed value of Query proplists in json
encode_query([]) ->
[];
encode_query(QSL) when is_list(QSL) ->
lists:foldl(fun({K, V}, Acc) ->
V1 = encode_query_value(K, V),
[{K, V1}|Acc]
end, [], QSL);
encode_query(QSL) ->
QSL.
%% @doc Encode value in JSON if needed depending on the key
encode_query_value(K, V) when is_atom(K) ->
encode_query_value(atom_to_list(K), V);
encode_query_value(K, V) when is_binary(K) ->
encode_query_value(binary_to_list(K), V);
encode_query_value(_K, V) -> V.
% build oauth header
oauth_header(Url, Action, OauthProps) when is_binary(Url) ->
oauth_header(binary_to_list(Url),Action, OauthProps);
oauth_header(Url, Action, OauthProps) ->
#hackney_url{qs=QS} = hackney_url:parse_url(Url),
QSL = [{binary_to_list(K), binary_to_list(V)} || {K,V} <-
hackney_url:parse_qs(QS)],
% get oauth paramerers
ConsumerKey = to_list(get_value(consumer_key, OauthProps)),
Token = to_list(get_value(token, OauthProps)),
TokenSecret = to_list(get_value(token_secret, OauthProps)),
ConsumerSecret = to_list(get_value(consumer_secret, OauthProps)),
SignatureMethodStr = to_list(get_value(signature_method,
OauthProps, "HMAC-SHA1")),
SignatureMethodAtom = case SignatureMethodStr of
"PLAINTEXT" ->
plaintext;
"HMAC-SHA1" ->
hmac_sha1;
"RSA-SHA1" ->
rsa_sha1
end,
Consumer = {ConsumerKey, ConsumerSecret, SignatureMethodAtom},
Method = case Action of
delete -> "DELETE";
get -> "GET";
post -> "POST";
put -> "PUT";
head -> "HEAD"
end,
Params = oauth:sign(Method, Url, QSL, Consumer, Token, TokenSecret) -- QSL,
Realm = "OAuth " ++ oauth:header_params_encode(Params),
{<<"Authorization">>, list_to_binary(Realm)}.
%% @doc merge 2 proplists. All the Key - Value pairs from both proplists
%% are included in the new proplists. If a key occurs in both dictionaries
%% then Fun is called with the key and both values to return a new
%% value. This a wreapper around dict:merge
propmerge(F, L1, L2) ->
dict:to_list(dict:merge(F, dict:from_list(L1), dict:from_list(L2))).
%% @doc Update a proplist with values of the second. In case the same
%% key is in 2 proplists, the value from the first are kept.
propmerge1(L1, L2) ->
propmerge(fun(_, V1, _) -> V1 end, L1, L2).
%% @doc replace a value in a proplist
force_param(Key, Value, Options) ->
case couchbeam_util:get_value(Key, Options) of
undefined ->
[{Key, Value} | Options];
_ ->
lists:keystore(Key, 1, Options, {Key, Value})
end.
%% @doc emulate proplists:get_value/2,3 but use faster lists:keyfind/3
-spec get_value(Key :: term(), Prop :: [term()]) -> term().
get_value(Key, Prop) ->
get_value(Key, Prop, undefined).
-spec get_value(Key :: term(), Prop :: [term()], Default :: term()) -> term().
get_value(Key, Prop, Default) ->
case lists:keyfind(Key, 1, Prop) of
false ->
case lists:member(Key, Prop) of
true -> true;
false -> Default
end;
{Key, V} -> % only return V if a two-tuple is found
V;
Other when is_tuple(Other) -> % otherwise return the default
Default
end.
%% @doc make view options a list
parse_options(Options) ->
parse_options(Options, []).
parse_options([], Acc) ->
Acc;
parse_options([V|Rest], Acc) when is_atom(V) ->
parse_options(Rest, [{atom_to_list(V), true}|Acc]);
parse_options([{K,V}|Rest], Acc) when is_list(K) ->
parse_options(Rest, [{K,V}|Acc]);
parse_options([{K,V}|Rest], Acc) when is_binary(K) ->
parse_options(Rest, [{binary_to_list(K),V}|Acc]);
parse_options([{K,V}|Rest], Acc) when is_atom(K) ->
parse_options(Rest, [{atom_to_list(K),V}|Acc]);
parse_options(_,_) ->
fail.
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) ->
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) ->
V.
to_atom(V) when is_atom(V) ->
V;
to_atom(V) when is_list(V) ->
list_to_atom(V);
to_atom(V) when is_binary(V) ->
list_to_atom(binary_to_list(V));
to_atom(V) ->
list_to_atom(lists:flatten(io_lib:format("~p", [V]))).
deprecated(Old, New, When) ->
io:format(
<<
"WARNING: function deprecated~n"
"Function '~p' has been deprecated~n"
"in favor of '~p'.~n"
"'~p' will be removed ~s.~n~n"
>>, [Old, New, Old, When]).
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.
%% @spec start_app_deps(App :: atom()) -> ok
%% @doc Start depedent applications of App.
start_app_deps(App) ->
{ok, DepApps} = application:get_key(App, applications),
[ensure_started(A) || A <- DepApps],
ok.
%% @spec ensure_started(Application :: atom()) -> ok
%% @doc Start the named application if not already started.
ensure_started(App) ->
case application:start(App) of
ok ->
ok;
{error, {already_started, App}} ->
ok
end.
get_app_env(Env, Default) ->
case application:get_env(couchbeam, Env) of
{ok, Val} -> Val;
undefined -> Default
end.
proxy_header(UserName,Roles,Secret) ->
proxy_header(UserName,Roles,Secret,?PROXY_AUTH_HEADERS).
proxy_header(UserName,Roles,Secret,HeaderNames) ->
proxy_header_token(UserName,Roles,proxy_token(Secret,UserName),HeaderNames).
proxy_header_token(UserName,Roles,Token,L) ->
[
{hgv(username,L), UserName},
{hgv(roles,L), Roles},
{hgv(token,L), Token}
].
hgv(N,L) ->
get_value(N,L,get_value(N,?PROXY_AUTH_HEADERS)).
proxy_token(Secret,UserName) ->
hackney_bstr:to_hex(hmac(sha, Secret, UserName)).
hmac(Alg, Key, Data) ->
case {Alg, erlang:function_exported(crypto, hmac, 3)} of
{_, true} ->
crypto:hmac(Alg, Key, Data);
{sha, false} ->
crypto:sha_mac(Key, Data);
{Alg, false} ->
throw({unsupported, Alg})
end.