1%%% -*- erlang -*- 2%%% 3%%% This file is part of couchbeam released under the MIT license. 4%%% See the NOTICE for more information. 5 6-module(couchbeam_util). 7 8-include_lib("hackney/include/hackney.hrl"). 9-include_lib("hackney/include/hackney_lib.hrl"). 10 11-export([dbname/1]). 12-export([encode_docid/1, encode_att_name/1]). 13-export([parse_options/1, parse_options/2]). 14-export([to_list/1, to_binary/1, to_integer/1, to_atom/1]). 15-export([encode_query/1, encode_query_value/2]). 16-export([oauth_header/3]). 17-export([propmerge/3, propmerge1/2]). 18-export([get_value/2, get_value/3]). 19-export([deprecated/3, shutdown_sync/1]). 20-export([start_app_deps/1, get_app_env/2]). 21-export([encode_docid1/1, encode_docid_noop/1]). 22-export([force_param/3]). 23-export([proxy_token/2, proxy_header/3]). 24 25-define(PROXY_AUTH_HEADERS,[ 26 {username,<<"X-Auth-CouchDB-UserName">>}, 27 {roles,<<"X-Auth-CouchDB-Roles">>}, 28 {token,<<"X-Auth-CouchDB-Token">>}]). 29 30-define(ENCODE_DOCID_FUNC, encode_docid1). 31 32dbname(DbName) when is_list(DbName) -> 33 list_to_binary(DbName); 34dbname(DbName) when is_binary(DbName) -> 35 DbName; 36dbname(DbName) -> 37 erlang:error({illegal_database_name, DbName}). 38 39encode_att_name(Name) when is_binary(Name) -> 40 encode_att_name(xmerl_ucs:from_utf8(Name)); 41encode_att_name(Name) -> 42 Parts = lists:foldl(fun(P, Att) -> 43 [xmerl_ucs:to_utf8(P)|Att] 44 end, [], string:tokens(Name, "/")), 45 lists:flatten(Parts). 46 47encode_docid(DocId) when is_list(DocId) -> 48 encode_docid(list_to_binary(DocId)); 49encode_docid(DocId)-> 50 ?ENCODE_DOCID_FUNC(DocId). 51 52encode_docid1(DocId) -> 53 case DocId of 54 << "_design/", Rest/binary >> -> 55 Rest1 = hackney_url:urlencode(Rest, [noplus]), 56 <<"_design/", Rest1/binary >>; 57 _ -> 58 hackney_url:urlencode(DocId, [noplus]) 59 end. 60 61encode_docid_noop(DocId) -> 62 DocId. 63 64%% @doc Encode needed value of Query proplists in json 65encode_query([]) -> 66 []; 67encode_query(QSL) when is_list(QSL) -> 68 lists:foldl(fun({K, V}, Acc) -> 69 V1 = encode_query_value(K, V), 70 [{K, V1}|Acc] 71 end, [], QSL); 72encode_query(QSL) -> 73 QSL. 74 75%% @doc Encode value in JSON if needed depending on the key 76encode_query_value(K, V) when is_atom(K) -> 77 encode_query_value(atom_to_list(K), V); 78encode_query_value(K, V) when is_binary(K) -> 79 encode_query_value(binary_to_list(K), V); 80encode_query_value(_K, V) -> V. 81 82% build oauth header 83oauth_header(Url, Action, OauthProps) when is_binary(Url) -> 84 oauth_header(binary_to_list(Url),Action, OauthProps); 85oauth_header(Url, Action, OauthProps) -> 86 #hackney_url{qs=QS} = hackney_url:parse_url(Url), 87 QSL = [{binary_to_list(K), binary_to_list(V)} || {K,V} <- 88 hackney_url:parse_qs(QS)], 89 90 % get oauth paramerers 91 ConsumerKey = to_list(get_value(consumer_key, OauthProps)), 92 Token = to_list(get_value(token, OauthProps)), 93 TokenSecret = to_list(get_value(token_secret, OauthProps)), 94 ConsumerSecret = to_list(get_value(consumer_secret, OauthProps)), 95 SignatureMethodStr = to_list(get_value(signature_method, 96 OauthProps, "HMAC-SHA1")), 97 98 SignatureMethodAtom = case SignatureMethodStr of 99 "PLAINTEXT" -> 100 plaintext; 101 "HMAC-SHA1" -> 102 hmac_sha1; 103 "RSA-SHA1" -> 104 rsa_sha1 105 end, 106 Consumer = {ConsumerKey, ConsumerSecret, SignatureMethodAtom}, 107 Method = case Action of 108 delete -> "DELETE"; 109 get -> "GET"; 110 post -> "POST"; 111 put -> "PUT"; 112 head -> "HEAD" 113 end, 114 Params = oauth:sign(Method, Url, QSL, Consumer, Token, TokenSecret) -- QSL, 115 116 Realm = "OAuth " ++ oauth:header_params_encode(Params), 117 {<<"Authorization">>, list_to_binary(Realm)}. 118 119 120%% @doc merge 2 proplists. All the Key - Value pairs from both proplists 121%% are included in the new proplists. If a key occurs in both dictionaries 122%% then Fun is called with the key and both values to return a new 123%% value. This a wreapper around dict:merge 124propmerge(F, L1, L2) -> 125 dict:to_list(dict:merge(F, dict:from_list(L1), dict:from_list(L2))). 126 127%% @doc Update a proplist with values of the second. In case the same 128%% key is in 2 proplists, the value from the first are kept. 129propmerge1(L1, L2) -> 130 propmerge(fun(_, V1, _) -> V1 end, L1, L2). 131 132%% @doc replace a value in a proplist 133force_param(Key, Value, Options) -> 134 case couchbeam_util:get_value(Key, Options) of 135 undefined -> 136 [{Key, Value} | Options]; 137 _ -> 138 lists:keystore(Key, 1, Options, {Key, Value}) 139 end. 140 141%% @doc emulate proplists:get_value/2,3 but use faster lists:keyfind/3 142-spec get_value(Key :: term(), Prop :: [term()]) -> term(). 143get_value(Key, Prop) -> 144 get_value(Key, Prop, undefined). 145 146-spec get_value(Key :: term(), Prop :: [term()], Default :: term()) -> term(). 147get_value(Key, Prop, Default) -> 148 case lists:keyfind(Key, 1, Prop) of 149 false -> 150 case lists:member(Key, Prop) of 151 true -> true; 152 false -> Default 153 end; 154 {Key, V} -> % only return V if a two-tuple is found 155 V; 156 Other when is_tuple(Other) -> % otherwise return the default 157 Default 158 end. 159 160%% @doc make view options a list 161parse_options(Options) -> 162 parse_options(Options, []). 163 164parse_options([], Acc) -> 165 Acc; 166parse_options([V|Rest], Acc) when is_atom(V) -> 167 parse_options(Rest, [{atom_to_list(V), true}|Acc]); 168parse_options([{K,V}|Rest], Acc) when is_list(K) -> 169 parse_options(Rest, [{K,V}|Acc]); 170parse_options([{K,V}|Rest], Acc) when is_binary(K) -> 171 parse_options(Rest, [{binary_to_list(K),V}|Acc]); 172parse_options([{K,V}|Rest], Acc) when is_atom(K) -> 173 parse_options(Rest, [{atom_to_list(K),V}|Acc]); 174parse_options(_,_) -> 175 fail. 176 177to_binary(V) when is_binary(V) -> 178 V; 179to_binary(V) when is_list(V) -> 180 try 181 list_to_binary(V) 182 catch 183 _ -> 184 list_to_binary(io_lib:format("~p", [V])) 185 end; 186to_binary(V) when is_atom(V) -> 187 list_to_binary(atom_to_list(V)); 188to_binary(V) -> 189 V. 190 191to_integer(V) when is_integer(V) -> 192 V; 193to_integer(V) when is_list(V) -> 194 erlang:list_to_integer(V); 195to_integer(V) when is_binary(V) -> 196 erlang:list_to_integer(binary_to_list(V)). 197 198to_list(V) when is_list(V) -> 199 V; 200to_list(V) when is_binary(V) -> 201 binary_to_list(V); 202to_list(V) when is_atom(V) -> 203 atom_to_list(V); 204to_list(V) -> 205 V. 206 207to_atom(V) when is_atom(V) -> 208 V; 209to_atom(V) when is_list(V) -> 210 list_to_atom(V); 211to_atom(V) when is_binary(V) -> 212 list_to_atom(binary_to_list(V)); 213to_atom(V) -> 214 list_to_atom(lists:flatten(io_lib:format("~p", [V]))). 215 216deprecated(Old, New, When) -> 217 io:format( 218 << 219 "WARNING: function deprecated~n" 220 "Function '~p' has been deprecated~n" 221 "in favor of '~p'.~n" 222 "'~p' will be removed ~s.~n~n" 223 >>, [Old, New, Old, When]). 224 225shutdown_sync(Pid) when not is_pid(Pid)-> 226 ok; 227shutdown_sync(Pid) -> 228 MRef = erlang:monitor(process, Pid), 229 try 230 catch unlink(Pid), 231 catch exit(Pid, shutdown), 232 receive 233 {'DOWN', MRef, _, _, _} -> 234 ok 235 end 236 after 237 erlang:demonitor(MRef, [flush]) 238 end. 239 240%% @spec start_app_deps(App :: atom()) -> ok 241%% @doc Start depedent applications of App. 242start_app_deps(App) -> 243 {ok, DepApps} = application:get_key(App, applications), 244 [ensure_started(A) || A <- DepApps], 245 ok. 246 247%% @spec ensure_started(Application :: atom()) -> ok 248%% @doc Start the named application if not already started. 249ensure_started(App) -> 250 case application:start(App) of 251 ok -> 252 ok; 253 {error, {already_started, App}} -> 254 ok 255 end. 256 257get_app_env(Env, Default) -> 258 case application:get_env(couchbeam, Env) of 259 {ok, Val} -> Val; 260 undefined -> Default 261 end. 262 263proxy_header(UserName,Roles,Secret) -> 264 proxy_header(UserName,Roles,Secret,?PROXY_AUTH_HEADERS). 265 266proxy_header(UserName,Roles,Secret,HeaderNames) -> 267 proxy_header_token(UserName,Roles,proxy_token(Secret,UserName),HeaderNames). 268 269proxy_header_token(UserName,Roles,Token,L) -> 270[ 271 {hgv(username,L), UserName}, 272 {hgv(roles,L), Roles}, 273 {hgv(token,L), Token} 274]. 275 276hgv(N,L) -> 277 get_value(N,L,get_value(N,?PROXY_AUTH_HEADERS)). 278 279proxy_token(Secret,UserName) -> 280 hackney_bstr:to_hex(hmac(sha, Secret, UserName)). 281 282hmac(Alg, Key, Data) -> 283 case {Alg, erlang:function_exported(crypto, hmac, 3)} of 284 {_, true} -> 285 crypto:hmac(Alg, Key, Data); 286 {sha, false} -> 287 crypto:sha_mac(Key, Data); 288 {Alg, false} -> 289 throw({unsupported, Alg}) 290 end. 291