/src/support/z_utils.erl

https://code.google.com/p/zotonic/ · Erlang · 876 lines · 654 code · 140 blank · 82 comment · 29 complexity · 257609fd86ecd1da896b04fd319922f8 MD5 · raw file

  1. %% @author Marc Worrell
  2. %% @copyright 2009 Marc Worrell
  3. %%
  4. %% Parts are from wf_utils.erl which is Copyright (c) 2008-2009 Rusty Klophaus
  5. %%
  6. %% @doc Misc utility functions for zotonic
  7. %% Copyright 2009 Marc Worrell
  8. %%
  9. %% Licensed under the Apache License, Version 2.0 (the "License");
  10. %% you may not use this file except in compliance with the License.
  11. %% You may obtain a copy of the License at
  12. %%
  13. %% http://www.apache.org/licenses/LICENSE-2.0
  14. %%
  15. %% Unless required by applicable law or agreed to in writing, software
  16. %% distributed under the License is distributed on an "AS IS" BASIS,
  17. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. %% See the License for the specific language governing permissions and
  19. %% limitations under the License.
  20. -module(z_utils).
  21. -include("zotonic.hrl").
  22. -export ([
  23. are_equal/2,
  24. assert/2,
  25. encode_value/2,
  26. decode_value/2,
  27. encode_value_expire/3,
  28. decode_value_expire/2,
  29. checksum/2,
  30. checksum_assert/3,
  31. coalesce/1,
  32. combine/2,
  33. combine_defined/2,
  34. decode/2,
  35. depickle/2,
  36. encode/2,
  37. f/1,
  38. f/2,
  39. get_seconds/0,
  40. group_by/3,
  41. group_proplists/2,
  42. hex_decode/1,
  43. hex_encode/1,
  44. index_proplist/2,
  45. nested_proplist/1,
  46. nested_proplist/2,
  47. get_nth/2,
  48. set_nth/3,
  49. is_empty/1,
  50. is_process_alive/1,
  51. is_true/1,
  52. js_escape/1,
  53. js_array/1,
  54. js_object/1,
  55. js_object/2,
  56. lib_dir/0,
  57. lib_dir/1,
  58. list_dir_recursive/1,
  59. name_for_host/2,
  60. only_digits/1,
  61. only_letters/1,
  62. is_iolist/1,
  63. is_proplist/1,
  64. os_escape/1,
  65. os_filename/1,
  66. pickle/2,
  67. prefix/2,
  68. prop_delete/2,
  69. prop_replace/3,
  70. randomize/1,
  71. randomize/2,
  72. replace1/3,
  73. split/2,
  74. split_in/2,
  75. url_path_encode/1,
  76. url_encode/1,
  77. url_decode/1,
  78. vsplit_in/2,
  79. now/0,
  80. now_msec/0,
  81. tempfile/0,
  82. url_reserved_char/1,
  83. url_unreserved_char/1,
  84. url_valid_char/1,
  85. flush_message/1,
  86. ensure_existing_module/1
  87. ]).
  88. %%% FORMAT %%%
  89. f(S) -> f(S, []).
  90. f(S, Args) -> lists:flatten(io_lib:format(S, Args)).
  91. %% @doc Return an abspath to a directory relative to the application root.
  92. %% This is used to prevent that we have to name the root dir "zotonic".
  93. lib_dir() ->
  94. {ok, Path} = zotonic_app:get_path(),
  95. Path.
  96. lib_dir(Dir) ->
  97. {ok, Path} = zotonic_app:get_path(),
  98. filename:join([Path, z_convert:to_list(Dir)]).
  99. %% @doc Return the current tick count
  100. now() ->
  101. {M,S,_M} = erlang:now(),
  102. M*1000000 + S.
  103. now_msec() ->
  104. {M,S,Micro} = erlang:now(),
  105. M*1000000000 + S*1000 + Micro div 1000.
  106. %% @doc Return the current universal time in seconds
  107. get_seconds() -> calendar:datetime_to_gregorian_seconds(calendar:universal_time()).
  108. %% @doc Multinode is_process_alive check
  109. is_process_alive(Pid) ->
  110. case is_pid(Pid) of
  111. true ->
  112. % If node(Pid) is down, rpc:call returns something other than
  113. % true or false.
  114. case rpc:call(node(Pid), erlang, is_process_alive, [Pid]) of
  115. true -> true;
  116. _ -> false
  117. end;
  118. _ -> false
  119. end.
  120. %%% HEX ENCODE and HEX DECODE
  121. hex_encode(Data) -> encode(Data, 16).
  122. hex_decode(Data) -> decode(Data, 16).
  123. encode(Data, Base) when is_binary(Data) -> encode(binary_to_list(Data), Base);
  124. encode(Data, Base) when is_list(Data) ->
  125. F = fun(C) when is_integer(C) ->
  126. case erlang:integer_to_list(C, Base) of
  127. [C1, C2] -> [C1, C2];
  128. [C1] -> [$0, C1]
  129. end
  130. end,
  131. [F(I) || I <- Data].
  132. decode(Data, Base) when is_binary(Data) -> decode(binary_to_list(Data), Base);
  133. decode(Data, Base) when is_list(Data) ->
  134. inner_decode(Data, Base).
  135. inner_decode(Data, Base) when is_list(Data) ->
  136. case Data of
  137. [C1, C2|Rest] ->
  138. I = erlang:list_to_integer([C1, C2], Base),
  139. [I|inner_decode(Rest, Base)];
  140. [] ->
  141. []
  142. end.
  143. %% Encode value securely, for use in cookies.
  144. %% 50 usec on core2duo 2GHz
  145. encode_value(Value, Context) ->
  146. Salt = z_ids:id(),
  147. Secret = z_ids:sign_key(Context),
  148. base64:encode(
  149. term_to_binary({Value, Salt, crypto:sha_mac(Secret, term_to_binary([Value, Salt]))})
  150. ).
  151. %% 23 usec on core2duo 2GHz
  152. decode_value(Data, Context) ->
  153. Secret = z_ids:sign_key(Context),
  154. {Value, Salt, Sign} = binary_to_term(base64:decode(Data)),
  155. Sign = crypto:sha_mac(Secret, term_to_binary([Value, Salt])),
  156. Value.
  157. encode_value_expire(Value, Date, Context) ->
  158. encode_value({Value, Date}, Context).
  159. decode_value_expire(Data, Context) ->
  160. {Value, Expire} = decode_value(Data, Context),
  161. case Expire >= calendar:local_time() of
  162. false -> {error, expired};
  163. true -> {ok, Value}
  164. end.
  165. %%% CHECKSUM %%%
  166. checksum(Data, Context) ->
  167. Sign = z_ids:sign_key_simple(Context),
  168. z_utils:hex_encode(erlang:md5([Sign,Data])).
  169. checksum_assert(Data, Checksum, Context) ->
  170. Sign = z_ids:sign_key_simple(Context),
  171. assert(list_to_binary(z_utils:hex_decode(Checksum)) == erlang:md5([Sign,Data]), checksum_invalid).
  172. %%% PICKLE / UNPICKLE %%%
  173. pickle(Data, Context) ->
  174. BData = erlang:term_to_binary(Data),
  175. Nonce = z_ids:number(1 bsl 31),
  176. Sign = z_ids:sign_key(Context),
  177. SData = <<BData/binary, Nonce:32, Sign/binary>>,
  178. <<C1:64,C2:64>> = erlang:md5(SData),
  179. base64:encode(<<C1:64, C2:64, Nonce:32, BData/binary>>).
  180. depickle(Data, Context) ->
  181. try
  182. <<C1:64, C2:64, Nonce:32, BData/binary>> = base64:decode(Data),
  183. Sign = z_ids:sign_key(Context),
  184. SData = <<BData/binary, Nonce:32, Sign/binary>>,
  185. <<C1:64, C2:64>> = erlang:md5(SData),
  186. erlang:binary_to_term(BData)
  187. catch
  188. _M:_E -> erlang:throw("Postback data invalid, could not depickle: "++Data)
  189. end.
  190. %%% URL ENCODE %%%
  191. url_encode(S) ->
  192. %% @todo possible speedups for binaries
  193. mochiweb_util:quote_plus(S).
  194. % hexdigit is from Mochiweb.
  195. -define(PERCENT, 37). % $\%
  196. hexdigit(C) when C < 10 -> $0 + C;
  197. hexdigit(C) when C < 16 -> $A + (C - 10).
  198. %%% URL PATH ENCODE %%%
  199. %% url spec for path part
  200. url_path_encode(L) when is_list(L) ->
  201. url_path_encode(L, []);
  202. url_path_encode(L) ->
  203. url_path_encode(z_convert:to_list(L)).
  204. url_path_encode([], Acc) ->
  205. lists:reverse(Acc);
  206. url_path_encode([$/|R], Acc) ->
  207. url_path_encode(R, [$/|Acc]);
  208. url_path_encode([C|R], Acc) when (C==$: orelse C==$@ orelse C==$& orelse C==$= orelse C==$+ orelse C==$$ orelse C==$ orelse C==$;) ->
  209. url_path_encode(R, [C|Acc]);
  210. url_path_encode([C|R], Acc)->
  211. case url_unreserved_char(C) of
  212. true ->
  213. url_path_encode(R, [C|Acc]);
  214. false ->
  215. <<Hi:4, Lo:4>> = <<C>>,
  216. url_path_encode(R, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc])
  217. end.
  218. %% @spec os_filename(String) -> String
  219. %% @doc Simple escape function for filenames as commandline arguments.
  220. %% foo/"bar.jpg -> "foo/\"bar.jpg"; on windows "foo\\\"bar.jpg" (both including quotes!)
  221. os_filename(A) when is_binary(A) ->
  222. os_filename(binary_to_list(A));
  223. os_filename(A) when is_list(A) ->
  224. os_filename(lists:flatten(A), []).
  225. os_filename([], Acc) ->
  226. filename:nativename([$"] ++ lists:reverse(Acc) ++ [$"]);
  227. os_filename([$\\|Rest], Acc) ->
  228. os_filename_bs(Rest, Acc);
  229. os_filename([$"|Rest], Acc) ->
  230. os_filename(Rest, [$", $\\ | Acc]);
  231. os_filename([C|Rest], Acc) ->
  232. os_filename(Rest, [C|Acc]).
  233. os_filename_bs([$\\|Rest], Acc) ->
  234. os_filename(Rest, [$\\,$\\|Acc]);
  235. os_filename_bs([$"|Rest], Acc) ->
  236. os_filename(Rest, [$",$\\,$\\,$\\|Acc]);
  237. os_filename_bs([C|Rest], Acc) ->
  238. os_filename(Rest, [C,$\\|Acc]).
  239. %% @spec os_escape(String) -> String
  240. %% @doc Simple escape function for command line arguments
  241. os_escape(undefined) ->
  242. [];
  243. os_escape(A) when is_binary(A) ->
  244. os_escape(binary_to_list(A));
  245. os_escape(A) when is_list(A) ->
  246. {Family, _} = os:type(),
  247. os_escape(Family, lists:flatten(A), []).
  248. os_escape(_, [], Acc) ->
  249. lists:reverse(Acc);
  250. os_escape(unix, [C|Rest], Acc) when
  251. (C >= $A andalso C =< $Z)
  252. orelse (C >= $a andalso C =< $z)
  253. orelse (C >= $0 andalso C =< $9)
  254. orelse C == $_
  255. orelse C == $.
  256. orelse C == $-
  257. orelse C == $+
  258. orelse C == $/
  259. ->
  260. os_escape(unix, Rest, [C|Acc]);
  261. os_escape(unix, [C|Rest], Acc) when
  262. C >= 32
  263. orelse C == $\r
  264. orelse C == $\n
  265. orelse C == $\t
  266. ->
  267. os_escape(unix, Rest, [C,$\\|Acc]);
  268. %% Win32 escaping, see: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/ntcmds_shelloverview.mspx
  269. os_escape(win32, [C|Rest], Acc) when C == $&
  270. orelse C == $|
  271. orelse C == $;
  272. orelse C == $,
  273. orelse C == $%
  274. orelse C == $(
  275. orelse C == $)
  276. orelse C == $"
  277. orelse C == $'
  278. orelse C == $=
  279. orelse C == $^
  280. orelse C == 32
  281. ->
  282. os_escape(win32, Rest, [C,$^|Acc]);
  283. os_escape(win32, [C|Rest], Acc) ->
  284. os_escape(win32, Rest, [C|Acc]).
  285. url_decode(S) ->
  286. lists:reverse(url_decode(S, [])).
  287. url_decode([], Acc) ->
  288. Acc;
  289. url_decode([$%, A, B|Rest], Acc) ->
  290. Ch = erlang:list_to_integer([A, B], 16),
  291. url_decode(Rest, [Ch|Acc]);
  292. url_decode([$+|Rest], Acc) ->
  293. url_decode(Rest, [32|Acc]);
  294. url_decode([Ch|Rest], Acc) ->
  295. url_decode(Rest, [Ch|Acc]).
  296. %% VALID URL CHARACTERS
  297. %% RFC 3986
  298. url_valid_char(Char) ->
  299. url_reserved_char(Char) orelse url_unreserved_char(Char).
  300. url_reserved_char($!) -> true;
  301. url_reserved_char($*) -> true;
  302. url_reserved_char($") -> true;
  303. url_reserved_char($') -> true;
  304. url_reserved_char($() -> true;
  305. url_reserved_char($)) -> true;
  306. url_reserved_char($;) -> true;
  307. url_reserved_char($:) -> true;
  308. url_reserved_char($@) -> true;
  309. url_reserved_char($&) -> true;
  310. url_reserved_char($=) -> true;
  311. url_reserved_char($+) -> true;
  312. url_reserved_char($$) -> true;
  313. url_reserved_char($,) -> true;
  314. url_reserved_char($/) -> true;
  315. url_reserved_char($?) -> true;
  316. url_reserved_char($%) -> true;
  317. url_reserved_char($#) -> true;
  318. url_reserved_char($[) -> true;
  319. url_reserved_char($]) -> true;
  320. url_reserved_char(_) -> false.
  321. url_unreserved_char(Ch) when Ch >= $A andalso Ch < $Z + 1 -> true;
  322. url_unreserved_char(Ch) when Ch >= $a andalso Ch < $z + 1 -> true;
  323. url_unreserved_char(Ch) when Ch >= $0 andalso Ch < $9 + 1 -> true;
  324. url_unreserved_char($-) -> true;
  325. url_unreserved_char($_) -> true;
  326. url_unreserved_char($.) -> true;
  327. url_unreserved_char($~) -> true;
  328. url_unreserved_char(_) -> false.
  329. %%% ESCAPE JAVASCRIPT %%%
  330. %% @doc Javascript escape, see also: http://code.google.com/p/doctype/wiki/ArticleXSSInJavaScript
  331. js_escape(undefined) -> [];
  332. js_escape([]) -> [];
  333. js_escape(<<>>) -> [];
  334. js_escape(Value) when is_integer(Value) -> integer_to_list(Value);
  335. js_escape(Value) when is_atom(Value) -> js_escape(atom_to_list(Value), []);
  336. js_escape(Value) when is_binary(Value) -> js_escape(binary_to_list(Value), []);
  337. js_escape(Value) -> js_escape(Value, []).
  338. js_escape([], Acc) -> lists:reverse(Acc);
  339. js_escape([$\\|T], Acc) -> js_escape(T, [$\\,$\\|Acc]);
  340. js_escape([$\n|T], Acc) -> js_escape(T, [$n,$\\|Acc]);
  341. js_escape([$\r|T], Acc) -> js_escape(T, [$r,$\\|Acc]);
  342. js_escape([$\t|T], Acc) -> js_escape(T, [$t,$\\|Acc]);
  343. js_escape([$'|T], Acc) -> js_escape(T, [$7,$2,$x,$\\|Acc]);
  344. js_escape([$"|T], Acc) -> js_escape(T, [$2,$2,$x,$\\|Acc]);
  345. js_escape([$<|T], Acc) -> js_escape(T, [$c,$3,$x,$\\|Acc]);
  346. js_escape([$>|T], Acc) -> js_escape(T, [$e,$3,$x,$\\|Acc]);
  347. js_escape([$=|T], Acc) -> js_escape(T, [$d,$3,$x,$\\|Acc]);
  348. js_escape([$&|T], Acc) -> js_escape(T, [$6,$2,$x,$\\|Acc]);
  349. %% js_escape([16#85,C|T], Acc) when C >= 16#80 -> js_escape(T, [C,16#85|Acc]);
  350. %% js_escape([16#85|T], Acc) -> js_escape(T, [$5,$8,$0,$0,$u,$\\|Acc]);
  351. js_escape([16#2028|T],Acc)-> js_escape(T, [$8,$2,$0,$2,$u,$\\|Acc]);
  352. js_escape([16#2029|T],Acc)-> js_escape(T, [$9,$2,$0,$2,$u,$\\|Acc]);
  353. js_escape([16#e2,16#80,16#a8|T],Acc)-> js_escape(T, [$8,$2,$0,$2,$u,$\\|Acc]);
  354. js_escape([16#e2,16#80,16#a9|T],Acc)-> js_escape(T, [$9,$2,$0,$2,$u,$\\|Acc]);
  355. js_escape([H|T], Acc) when is_integer(H) ->
  356. js_escape(T, [H|Acc]);
  357. js_escape([H|T], Acc) ->
  358. H1 = js_escape(H),
  359. js_escape(T, [H1|Acc]).
  360. %% js_escape(<<"<script", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "<scr\" + \"ipt">>);
  361. %% js_escape(<<"script>", Rest/binary>>, Acc) -> js_escape(Rest, <<Acc/binary, "scr\" + \"ipt>">>);
  362. js_array(L) ->
  363. [ $[, combine($,,[ js_prop_value(undefined, V) || V <- L ]), $] ].
  364. %% @doc Create a javascript object from a proplist
  365. js_object([]) -> <<"{}">>;
  366. js_object(L) -> js_object(L,[]).
  367. js_object(L, []) -> js_object1(L, []);
  368. js_object(L, [Key|T]) -> js_object(proplists:delete(Key,L), T).
  369. %% recursively add all properties as object properties
  370. js_object1([], Acc) ->
  371. [${, combine($,,lists:reverse(Acc)), $}];
  372. js_object1([{Key,Value}|T], Acc) ->
  373. Prop = [atom_to_list(Key), $:, js_prop_value(Key, Value)],
  374. js_object1(T, [Prop|Acc]).
  375. js_prop_value(_, undefined) -> <<"null">>;
  376. js_prop_value(_, true) -> <<"true">>;
  377. js_prop_value(_, false) -> <<"true">>;
  378. js_prop_value(_, Atom) when is_atom(Atom) -> [$",js_escape(erlang:atom_to_list(Atom)), $"];
  379. js_prop_value(pattern, [$/|T]=List) ->
  380. %% Check for regexp
  381. case length(T) of
  382. Len when Len =< 2 ->
  383. [$",js_escape(List),$"];
  384. _Len ->
  385. case string:rchr(T, $/) of
  386. 0 ->
  387. [$",js_escape(List),$"];
  388. N ->
  389. {_Re, [$/|Options]} = lists:split(N-1,T),
  390. case only_letters(Options) of
  391. true -> List;
  392. false -> [$",js_escape(List),$"]
  393. end
  394. end
  395. end;
  396. js_prop_value(_, Int) when is_integer(Int) -> integer_to_list(Int);
  397. js_prop_value(_, Value) -> [$",js_escape(Value),$"].
  398. only_letters([]) ->
  399. true;
  400. only_letters([C|T]) when (C >= $a andalso C =< $z) orelse (C >= $A andalso C =< $Z) ->
  401. only_letters(T);
  402. only_letters(_) ->
  403. false.
  404. only_digits([]) ->
  405. false;
  406. only_digits(L) when is_list(L) ->
  407. only_digits1(L);
  408. only_digits(B) when is_binary(B) ->
  409. only_digits(binary_to_list(B)).
  410. only_digits1([]) ->
  411. true;
  412. only_digits1([C|R]) when C >= $0 andalso C =< $9 ->
  413. only_digits1(R);
  414. only_digits1(_) ->
  415. false.
  416. is_iolist(C) when is_integer(C) andalso C >= 0 andalso C =< 255 -> true;
  417. is_iolist(B) when is_binary(B) -> true;
  418. is_iolist([H|L]) -> is_iolist(H) andalso is_iolist(L);
  419. is_iolist(_) -> false.
  420. is_proplist([]) -> true;
  421. is_proplist([{K,_}|R]) when is_atom(K) -> is_proplist(R);
  422. is_proplist(_) -> false.
  423. combine_defined(Sep, List) ->
  424. List2 = lists:filter(fun(X) -> X /= undefined end, List),
  425. combine(Sep, List2).
  426. combine(_Sep, []) -> [];
  427. combine(_Sep, [A]) -> [A];
  428. combine(Sep, [H|T]) -> [H, prefix(Sep, T)].
  429. prefix(Sep, List) -> prefix(Sep,List,[]).
  430. prefix(_Sep, [], Acc) -> lists:reverse(Acc);
  431. prefix(Sep, [H|T], Acc) -> prefix(Sep, T, [H,Sep|Acc]).
  432. %%% COALESCE %%%
  433. coalesce([]) -> undefined;
  434. coalesce([H]) -> H;
  435. coalesce([undefined|T]) -> coalesce(T);
  436. coalesce([[]|T]) -> coalesce(T);
  437. coalesce([H|_]) -> H.
  438. %% @doc Check if a value is 'empty'
  439. is_empty(undefined) -> true;
  440. is_empty([]) -> true;
  441. is_empty(<<>>) -> true;
  442. is_empty(_) -> false.
  443. %% @doc Check if the parameter could represent the logical value of "true"
  444. is_true([$t|_T]) -> true;
  445. is_true([$y|_T]) -> true;
  446. is_true([$T|_T]) -> true;
  447. is_true([$Y|_T]) -> true;
  448. is_true("on") -> true;
  449. is_true("ON") -> true;
  450. is_true("1") -> true;
  451. is_true(<<"true">>) -> true;
  452. is_true(<<"yes">>) -> true;
  453. is_true(<<"on">>) -> true;
  454. is_true(<<"TRUE">>) -> true;
  455. is_true(<<"YES">>) -> true;
  456. is_true(<<"ON">>) -> true;
  457. is_true(<<"1">>) -> true;
  458. is_true(true) -> true;
  459. is_true(yes) -> true;
  460. is_true(on) -> true;
  461. is_true(N) when is_integer(N) andalso N /= 0 -> true;
  462. is_true(_) -> false.
  463. %% @spec assert(bool(), error) -> none()
  464. %% @doc Check if an assertion is ok or failed
  465. assert(false, Error) -> erlang:error(Error);
  466. assert(_, _) -> ok.
  467. %% @doc Replace a property in a proplist
  468. prop_replace(Prop, Value, List) ->
  469. [{Prop,Value} | lists:keydelete(Prop,1,List)].
  470. prop_delete(Prop, List) ->
  471. lists:keydelete(Prop, 1, List).
  472. %% @doc Given a list of proplists, make it a nested list with respect to a property, combining elements
  473. %% with the same property. Assumes the list is sorted on the property you are splitting on
  474. %% For example: [[{a,b}{x}], [{a,b}{z}], [{a,c}{y}]] gives:
  475. %% [ {b, [[{a,b}{x}], [{a,b}{z}]]}, {c, [[{a,c}{y}]]} ]
  476. %% @spec group_proplists(Property, [PropList]) -> PropList
  477. group_proplists(_Prop, []) ->
  478. [];
  479. group_proplists(Prop, [Item|Rest]) ->
  480. PropValue = proplists:get_value(Prop, Item),
  481. group_proplists(Prop, PropValue, Rest, [Item], []).
  482. group_proplists(_Prop, _PropValue, [], [], Result) ->
  483. lists:reverse(Result);
  484. group_proplists(Prop, PropValue, [], Acc, Result) ->
  485. lists:reverse(Acc),
  486. group_proplists(Prop, PropValue, [], [], [{z_convert:to_atom(PropValue),Acc}|Result]);
  487. group_proplists(Prop, PropValue, [C|Rest], Acc, Result) ->
  488. case proplists:get_value(Prop, C) of
  489. PropValue ->
  490. group_proplists(Prop, PropValue, Rest, [C|Acc], Result);
  491. Other ->
  492. group_proplists(Prop, Other, Rest, [C], [{z_convert:to_atom(PropValue),Acc}|Result])
  493. end.
  494. %% @doc Make a property list based on the value of a property
  495. %% For example: [ [{a,b}], [{a,c}] ] gives [{a, [{a,b}]}, {c, [[{a,c}]]}]
  496. %% @spec index_proplist(Property, [PropList]) -> PropList
  497. index_proplist(_Prop, []) ->
  498. [];
  499. index_proplist(Prop, List) ->
  500. index_proplist(Prop, List, []).
  501. index_proplist(_Prop, [], Acc) ->
  502. lists:reverse(Acc);
  503. index_proplist(Prop, [L|Rest], Acc) ->
  504. index_proplist(Prop, Rest, [{z_convert:to_atom(proplists:get_value(Prop,L)),L}|Acc]).
  505. %% @doc Scan the props of a proplist, when the prop is a list with a $. characters in it then split the prop.
  506. nested_proplist(Props) ->
  507. nested_proplist(Props, []).
  508. nested_proplist([], Acc) ->
  509. lists:reverse(Acc);
  510. nested_proplist([{K,V}|T], Acc) when is_list(K) ->
  511. case string:tokens(K, ".") of
  512. [K0] -> nested_proplist(T, [{K0,V}|Acc]);
  513. List -> nested_proplist(T, nested_props_assign(List, V, Acc))
  514. end;
  515. nested_proplist([H|T], Acc) ->
  516. nested_proplist(T, [H|Acc]).
  517. nested_props_assign([K], V, Acc) ->
  518. case only_digits(K) of
  519. true -> set_nth(list_to_integer(K), V, Acc);
  520. false -> prop_replace(z_convert:to_atom(K), V, Acc)
  521. end;
  522. nested_props_assign([H|T], V, Acc) ->
  523. case only_digits(H) of
  524. true ->
  525. Index = list_to_integer(H),
  526. NewV = case get_nth(Index, Acc) of
  527. L when is_list(L) -> nested_props_assign(T, V, L);
  528. _ -> nested_props_assign(T, V, [])
  529. end,
  530. set_nth(Index, NewV, Acc);
  531. false ->
  532. K = z_convert:to_atom(H),
  533. NewV = case proplists:get_value(K, Acc) of
  534. L when is_list(L) -> nested_props_assign(T, V, L);
  535. _ -> nested_props_assign(T, V, [])
  536. end,
  537. prop_replace(K, NewV, Acc)
  538. end.
  539. get_nth(N, L) when N >= 1 ->
  540. try lists:nth(N, L) catch _:_ -> undefined end.
  541. set_nth(N, V, L) when N >= 1 ->
  542. try
  543. case lists:split(N-1, L) of
  544. {Pre, []} -> Pre ++ [V];
  545. {Pre, [_|T]} -> Pre ++ [V|T]
  546. end
  547. catch _:_ ->
  548. set_nth(N, V, L ++ [undefined])
  549. end.
  550. %% @doc Simple randomize of a list. Not good quality, but good enough for us
  551. randomize(List) ->
  552. {A1,A2,A3} = erlang:now(),
  553. random:seed(A1, A2, A3),
  554. D = lists:map(fun(A) ->
  555. {random:uniform(), A}
  556. end, List),
  557. {_, D1} = lists:unzip(lists:keysort(1, D)),
  558. D1.
  559. randomize(N, List) ->
  560. split(N, randomize(List)).
  561. split(N, L) ->
  562. split(N,L,[]).
  563. split(_N, [], Acc) ->
  564. {lists:reverse(Acc), []};
  565. split(0, Rest, Acc) ->
  566. {lists:reverse(Acc), Rest};
  567. split(N, [A|Rest], Acc) ->
  568. split(N-1, Rest, [A|Acc]).
  569. split_in(L, N) when N =< 1 ->
  570. L;
  571. split_in(L, N) when is_binary(L) ->
  572. split_in(binary_to_list(L), N);
  573. split_in(L, N) when is_list(L) ->
  574. [ lists:reverse(SubList) || SubList <- split_in(L, [], split_in_acc0(N, [])) ].
  575. split_in_acc0(0, Acc) -> Acc;
  576. split_in_acc0(N, Acc) -> split_in_acc0(N-1, [[] | Acc]).
  577. split_in([], Acc1, Acc0) ->
  578. lists:reverse(Acc1) ++ Acc0;
  579. split_in(L, Acc1, []) ->
  580. split_in(L, [], lists:reverse(Acc1));
  581. split_in([H|T], Acc1, [HA|HT]) ->
  582. split_in(T, [[H|HA]|Acc1], HT).
  583. vsplit_in(L, N) when N =< 1 ->
  584. L;
  585. vsplit_in(L, N) when is_binary(L) ->
  586. vsplit_in(binary_to_list(L), N);
  587. vsplit_in(L, N) ->
  588. Len = length(L),
  589. RunLength = case Len rem N of
  590. 0 -> Len div N;
  591. _ -> Len div N + 1
  592. end,
  593. vsplit_in(N, L, RunLength, []).
  594. vsplit_in(1, L, _, Acc) ->
  595. lists:reverse([L|Acc]);
  596. vsplit_in(N, [], RunLength, Acc) ->
  597. vsplit_in(N-1, [], RunLength, [[]|Acc]);
  598. vsplit_in(N, L, RunLength, Acc) ->
  599. {Row,Rest} = lists:split(RunLength, L),
  600. vsplit_in(N-1, Rest, RunLength, [Row|Acc]).
  601. %% @doc Group by a property or m_rsc property, keeps the input list in the same order.
  602. group_by([], _, _Context) ->
  603. [];
  604. group_by(L, Prop, Context) ->
  605. LP = [ group_by_addprop(H, Prop, Context) || H <- L ],
  606. Dict1 = group_by_dict(LP, dict:new()),
  607. group_by_fetch_in_order(LP, [], Dict1, []).
  608. group_by_fetch_in_order([], _, _, Acc) ->
  609. lists:reverse(Acc);
  610. group_by_fetch_in_order([{Key,_}|T], Ks, Dict, Acc) ->
  611. case lists:member(Key, Ks) of
  612. true -> group_by_fetch_in_order(T, Ks, Dict, Acc);
  613. false -> group_by_fetch_in_order(T, [Key|Ks], Dict, [dict:fetch(Key, Dict)|Acc])
  614. end.
  615. group_by_dict([], Dict) ->
  616. Dict;
  617. group_by_dict([{Key,V}|T], Dict) ->
  618. case dict:is_key(Key, Dict) of
  619. true -> group_by_dict(T, dict:append(Key, V, Dict));
  620. false -> group_by_dict(T, dict:store(Key, [V], Dict))
  621. end.
  622. group_by_addprop(Id, Prop, Context) when is_integer(Id) ->
  623. {m_rsc:p(Id, Prop, Context), Id};
  624. group_by_addprop(L, Prop, _Context) when is_list(L) ->
  625. {proplists:get_value(Prop, L), L};
  626. group_by_addprop(N, _Prop, _Context) ->
  627. {undefined, N}.
  628. replace1(F, T, L) ->
  629. replace1(F, T, L, []).
  630. replace1(_F, _T, [], Acc) ->
  631. lists:reverse(Acc);
  632. replace1(F, T, [F|R], Acc) ->
  633. replace1(F, T, R, [T|Acc]);
  634. replace1(F, T, [C|R], Acc) ->
  635. replace1(F, T, R, [C|Acc]).
  636. %% @doc Return a list of all files in a directory, recursive depth first search for files not starting with a '.'
  637. list_dir_recursive(Dir) ->
  638. case file:list_dir(Dir) of
  639. {ok, Files} ->
  640. list_dir_recursive(Files, Dir, []);
  641. {error, _} ->
  642. []
  643. end.
  644. list_dir_recursive([], _BaseDir, Acc) ->
  645. Acc;
  646. list_dir_recursive([[$.|_]|OtherFiles], BaseDir, Acc) ->
  647. list_dir_recursive(OtherFiles, BaseDir, Acc);
  648. list_dir_recursive([File|OtherFiles], BaseDir, Acc) ->
  649. Path = filename:join(BaseDir, File),
  650. case filelib:is_regular(Path) of
  651. true ->
  652. list_dir_recursive(OtherFiles, BaseDir, [File|Acc]);
  653. false ->
  654. case filelib:is_dir(Path) of
  655. true ->
  656. Acc1 = case file:list_dir(Path) of
  657. {ok, Files} ->
  658. NonDotFiles = lists:filter(fun([$.|_]) -> false; (_) -> true end, Files),
  659. RelFiles = [ filename:join(File, F) || F <- NonDotFiles],
  660. list_dir_recursive(RelFiles, BaseDir, Acc);
  661. {error, _} ->
  662. Acc
  663. end,
  664. list_dir_recursive(OtherFiles, BaseDir, Acc1);
  665. false ->
  666. list_dir_recursive(OtherFiles, BaseDir, Acc)
  667. end
  668. end.
  669. %% @doc Check if two arguments are equal, optionally converting them
  670. are_equal(Arg1, Arg2) when Arg1 =:= Arg2 ->
  671. true;
  672. are_equal(Arg1, Arg2) when is_boolean(Arg1) ->
  673. Arg1 == z_convert:to_bool(Arg2);
  674. are_equal(Arg1, Arg2) when is_boolean(Arg2) ->
  675. Arg2 == z_convert:to_bool(Arg1);
  676. are_equal(Arg1, Arg2) when is_atom(Arg1) ->
  677. are_equal(atom_to_list(Arg1), Arg2);
  678. are_equal(Arg1, Arg2) when is_atom(Arg2) ->
  679. are_equal(Arg1, atom_to_list(Arg2));
  680. are_equal(Arg1, Arg2) when is_binary(Arg1) ->
  681. are_equal(binary_to_list(Arg1), Arg2);
  682. are_equal(Arg1, Arg2) when is_binary(Arg2) ->
  683. are_equal(Arg1, binary_to_list(Arg2));
  684. are_equal(Arg1, Arg2) when is_integer(Arg1) ->
  685. are_equal(integer_to_list(Arg1), Arg2);
  686. are_equal(Arg1, Arg2) when is_integer(Arg2) ->
  687. are_equal(Arg1, integer_to_list(Arg2));
  688. are_equal(_Arg1, _Arg2) ->
  689. false.
  690. %% @doc Return the name used in the context of a hostname
  691. %% @spec name_for_host(atom(), atom()) -> atom()
  692. name_for_host(Name, Host) ->
  693. z_convert:to_atom(z_convert:to_list(Name) ++ [$$, z_convert:to_list(Host)]).
  694. %% @doc Ensure that the given string matches an existing module. Used to prevent
  695. %% a denial of service attack where we exhaust the atom space.
  696. ensure_existing_module(ModuleName) when is_list(ModuleName) ->
  697. case catch list_to_existing_atom(ModuleName) of
  698. {'EXIT', {badarg, _Traceback}} ->
  699. case code:where_is_file(ensure_valid_modulename(ModuleName) ++ ".beam") of
  700. non_existing -> {error, not_found};
  701. Absname ->
  702. {module, Module} = code:load_abs(filename:rootname(Absname)),
  703. {ok, Module}
  704. end;
  705. M ->
  706. ensure_existing_module(M)
  707. end;
  708. ensure_existing_module(ModuleName) when is_atom(ModuleName) ->
  709. case module_loaded(ModuleName) of
  710. true ->
  711. {ok, ModuleName};
  712. false ->
  713. {module, Module} = code:ensure_loaded(ModuleName),
  714. {ok, Module}
  715. end;
  716. ensure_existing_module(ModuleName) when is_binary(ModuleName) ->
  717. ensure_existing_module(binary_to_list(ModuleName)).
  718. % Crash on a modulename that is not valid.
  719. ensure_valid_modulename(Name) ->
  720. lists:filter(fun ensure_valid_modulechar/1, Name).
  721. ensure_valid_modulechar(C) when C >= $0, C =< $9 -> true;
  722. ensure_valid_modulechar(C) when C >= $a, C =< $z -> true;
  723. ensure_valid_modulechar(C) when C >= $A, C =< $Z -> true;
  724. ensure_valid_modulechar(C) when C == $_ -> true.
  725. %% @doc return a unique temporary filename.
  726. %% @spec tempfile() -> string()
  727. tempfile() ->
  728. {A,B,C}=erlang:now(),
  729. lists:flatten(io_lib:format("/tmp/ztmp-~p-~p.~p.~p",[node(),A,B,C])).
  730. %% @doc Flush all incoming messages, used when receiving timer ticks to prevent multiple ticks.
  731. flush_message(Msg) ->
  732. receive
  733. Msg -> flush_message(Msg)
  734. after 0 ->
  735. ok
  736. end.