diff options
author | Evgeny Khramtsov <ekhramtsov@process-one.net> | 2019-06-14 12:33:26 +0300 |
---|---|---|
committer | Evgeny Khramtsov <ekhramtsov@process-one.net> | 2019-06-14 12:33:26 +0300 |
commit | a02cff0e780bb735531594c4ece81e8628f79782 (patch) | |
tree | 6fe7d8219d14f58183be1741fcea262c216db447 /src/ejabberd_old_config.erl | |
parent | Return jid_malformed error when sending presence without nick to conference (diff) |
Use new configuration validator
Diffstat (limited to '')
-rw-r--r-- | src/ejabberd_old_config.erl | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/src/ejabberd_old_config.erl b/src/ejabberd_old_config.erl new file mode 100644 index 000000000..13a006055 --- /dev/null +++ b/src/ejabberd_old_config.erl @@ -0,0 +1,655 @@ +%%%---------------------------------------------------------------------- +%%% Purpose: Transform old-style Erlang config to YAML config +%%% +%%% ejabberd, Copyright (C) 2002-2019 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- +-module(ejabberd_old_config). + +%% API +-export([read_file/1]). + +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +read_file(File) -> + case consult(File) of + {ok, Terms1} -> + ?INFO_MSG("Converting from old configuration format", []), + Terms2 = strings_to_binary(Terms1), + Terms3 = transform(Terms2), + Terms4 = transform_certfiles(Terms3), + Terms5 = transform_host_config(Terms4), + {ok, collect_options(Terms5)}; + {error, Reason} -> + {error, {old_config, File, Reason}} + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +collect_options(Opts) -> + {D, InvalidOpts} = + lists:foldl( + fun({K, V}, {D, Os}) when is_list(V) -> + {orddict:append_list(K, V, D), Os}; + ({K, V}, {D, Os}) -> + {orddict:store(K, V, D), Os}; + (Opt, {D, Os}) -> + {D, [Opt|Os]} + end, {orddict:new(), []}, Opts), + InvalidOpts ++ orddict:to_list(D). + +transform(Opts) -> + Opts1 = transform_register(Opts), + Opts2 = transform_s2s(Opts1), + Opts3 = transform_listeners(Opts2), + Opts4 = transform_sql(Opts3), + Opts5 = transform_riak(Opts4), + Opts6 = transform_shaper(Opts5), + Opts7 = transform_s2s_out(Opts6), + Opts8 = transform_acl(Opts7), + Opts9 = transform_modules(Opts8), + Opts10 = transform_globals(Opts9), + collect_options(Opts10). + +%%%=================================================================== +%%% mod_register +%%%=================================================================== +transform_register(Opts) -> + try + {value, {modules, ModOpts}, Opts1} = lists:keytake(modules, 1, Opts), + {value, {?MODULE, RegOpts}, ModOpts1} = lists:keytake(?MODULE, 1, ModOpts), + {value, {ip_access, L}, RegOpts1} = lists:keytake(ip_access, 1, RegOpts), + true = is_list(L), + ?WARNING_MSG("Old 'ip_access' format detected. " + "The old format is still supported " + "but it is better to fix your config: " + "use access rules instead.", []), + ACLs = lists:flatmap( + fun({Action, S}) -> + ACLName = misc:binary_to_atom( + iolist_to_binary( + ["ip_", S])), + [{Action, ACLName}, + {acl, ACLName, {ip, S}}] + end, L), + Access = {access, mod_register_networks, + [{Action, ACLName} || {Action, ACLName} <- ACLs]}, + [ACL || {acl, _, _} = ACL <- ACLs] ++ + [Access, + {modules, + [{mod_register, + [{ip_access, mod_register_networks}|RegOpts1]} + | ModOpts1]}|Opts1] + catch error:{badmatch, false} -> + Opts + end. + +%%%=================================================================== +%%% ejabberd_s2s +%%%=================================================================== +transform_s2s(Opts) -> + lists:foldl(fun transform_s2s/2, [], Opts). + +transform_s2s({{s2s_host, Host}, Action}, Opts) -> + ?WARNING_MSG("Option 's2s_host' is deprecated.", []), + ACLName = misc:binary_to_atom( + iolist_to_binary(["s2s_access_", Host])), + [{acl, ACLName, {server, Host}}, + {access, s2s, [{Action, ACLName}]}, + {s2s_access, s2s} | + Opts]; +transform_s2s({s2s_default_policy, Action}, Opts) -> + ?WARNING_MSG("Option 's2s_default_policy' is deprecated. " + "The option is still supported but it is better to " + "fix your config: " + "use 's2s_access' with an access rule.", []), + [{access, s2s, [{Action, all}]}, + {s2s_access, s2s} | + Opts]; +transform_s2s(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% ejabberd_s2s_out +%%%=================================================================== +transform_s2s_out(Opts) -> + lists:foldl(fun transform_s2s_out/2, [], Opts). + +transform_s2s_out({outgoing_s2s_options, Families, Timeout}, Opts) -> + ?WARNING_MSG("Option 'outgoing_s2s_options' is deprecated. " + "The option is still supported " + "but it is better to fix your config: " + "use 'outgoing_s2s_timeout' and " + "'outgoing_s2s_families' instead.", []), + [{outgoing_s2s_families, Families}, + {outgoing_s2s_timeout, Timeout} + | Opts]; +transform_s2s_out({s2s_dns_options, S2SDNSOpts}, AllOpts) -> + ?WARNING_MSG("Option 's2s_dns_options' is deprecated. " + "The option is still supported " + "but it is better to fix your config: " + "use 's2s_dns_timeout' and " + "'s2s_dns_retries' instead", []), + lists:foldr( + fun({timeout, T}, AccOpts) -> + [{s2s_dns_timeout, T}|AccOpts]; + ({retries, R}, AccOpts) -> + [{s2s_dns_retries, R}|AccOpts]; + (_, AccOpts) -> + AccOpts + end, AllOpts, S2SDNSOpts); +transform_s2s_out(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% ejabberd_listener +%%%=================================================================== +transform_listeners(Opts) -> + lists:foldl(fun transform_listeners/2, [], Opts). + +transform_listeners({listen, LOpts}, Opts) -> + [{listen, lists:map(fun transform_listener/1, LOpts)} | Opts]; +transform_listeners(Opt, Opts) -> + [Opt|Opts]. + +transform_listener({{Port, IP, Transport}, Mod, Opts}) -> + IPStr = if is_tuple(IP) -> + list_to_binary(inet_parse:ntoa(IP)); + true -> + IP + end, + Opts1 = lists:map( + fun({ip, IPT}) when is_tuple(IPT) -> + {ip, list_to_binary(inet_parse:ntoa(IP))}; + (ssl) -> {tls, true}; + (A) when is_atom(A) -> {A, true}; + (Opt) -> Opt + end, Opts), + Opts2 = lists:foldl( + fun(Opt, Acc) -> + transform_listen_option(Mod, Opt, Acc) + end, [], Opts1), + TransportOpt = if Transport == tcp -> []; + true -> [{transport, Transport}] + end, + IPOpt = if IPStr == <<"0.0.0.0">> -> []; + true -> [{ip, IPStr}] + end, + IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; +transform_listener({{Port, Transport}, Mod, Opts}) + when Transport == tcp orelse Transport == udp -> + transform_listener({{Port, all_zero_ip(Opts), Transport}, Mod, Opts}); +transform_listener({{Port, IP}, Mod, Opts}) -> + transform_listener({{Port, IP, tcp}, Mod, Opts}); +transform_listener({Port, Mod, Opts}) -> + transform_listener({{Port, all_zero_ip(Opts), tcp}, Mod, Opts}); +transform_listener(Opt) -> + Opt. + +transform_listen_option(ejabberd_http, captcha, Opts) -> + [{captcha, true}|Opts]; +transform_listen_option(ejabberd_http, register, Opts) -> + [{register, true}|Opts]; +transform_listen_option(ejabberd_http, web_admin, Opts) -> + [{web_admin, true}|Opts]; +transform_listen_option(ejabberd_http, http_bind, Opts) -> + [{http_bind, true}|Opts]; +transform_listen_option(ejabberd_http, http_poll, Opts) -> + [{http_poll, true}|Opts]; +transform_listen_option(ejabberd_http, {request_handlers, Hs}, Opts) -> + Hs1 = lists:map( + fun({PList, Mod}) when is_list(PList) -> + Path = iolist_to_binary([[$/, P] || P <- PList]), + {Path, Mod}; + (Opt) -> + Opt + end, Hs), + [{request_handlers, Hs1} | Opts]; +transform_listen_option(ejabberd_service, {hosts, Hosts, O}, Opts) -> + case lists:keyfind(hosts, 1, Opts) of + {_, PrevHostOpts} -> + NewHostOpts = + lists:foldl( + fun(H, Acc) -> + dict:append_list(H, O, Acc) + end, dict:from_list(PrevHostOpts), Hosts), + [{hosts, dict:to_list(NewHostOpts)}| + lists:keydelete(hosts, 1, Opts)]; + _ -> + [{hosts, [{H, O} || H <- Hosts]}|Opts] + end; +transform_listen_option(ejabberd_service, {host, Host, Os}, Opts) -> + transform_listen_option(ejabberd_service, {hosts, [Host], Os}, Opts); +transform_listen_option(ejabberd_xmlrpc, {access_commands, ACOpts}, Opts) -> + NewACOpts = lists:map( + fun({AName, ACmds, AOpts}) -> + {AName, [{commands, ACmds}, {options, AOpts}]}; + (Opt) -> + Opt + end, ACOpts), + [{access_commands, NewACOpts}|Opts]; +transform_listen_option(_, Opt, Opts) -> + [Opt|Opts]. + +-spec all_zero_ip([proplists:property()]) -> inet:ip_address(). +all_zero_ip(Opts) -> + case proplists:get_bool(inet6, Opts) of + true -> {0,0,0,0,0,0,0,0}; + false -> {0,0,0,0} + end. + +%%%=================================================================== +%%% ejabberd_shaper +%%%=================================================================== +transform_shaper(Opts) -> + lists:foldl(fun transform_shaper/2, [], Opts). + +transform_shaper({shaper, Name, {maxrate, N}}, Opts) -> + [{shaper, [{Name, N}]} | Opts]; +transform_shaper({shaper, Name, none}, Opts) -> + [{shaper, [{Name, none}]} | Opts]; +transform_shaper({shaper, List}, Opts) when is_list(List) -> + R = lists:map( + fun({Name, Args}) when is_list(Args) -> + MaxRate = proplists:get_value(rate, Args, 1000), + BurstSize = proplists:get_value(burst_size, Args, MaxRate), + {Name, MaxRate, BurstSize}; + ({Name, Val}) -> + {Name, Val, Val} + end, List), + [{shaper, R} | Opts]; +transform_shaper(Opt, Opts) -> + [Opt | Opts]. + +%%%=================================================================== +%%% acl +%%%=================================================================== +transform_acl(Opts) -> + Opts1 = lists:foldl(fun transform_acl/2, [], Opts), + {ACLOpts, Opts2} = lists:mapfoldl( + fun({acl, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts1), + {AccessOpts, Opts3} = lists:mapfoldl( + fun({access, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts2), + {NewAccessOpts, Opts4} = lists:mapfoldl( + fun({access_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts3), + {ShaperOpts, Opts5} = lists:mapfoldl( + fun({shaper_rules, Os}, Acc) -> + {Os, Acc}; + (O, Acc) -> + {[], [O|Acc]} + end, [], Opts4), + ACLOpts1 = collect_options(lists:flatten(ACLOpts)), + AccessOpts1 = case collect_options(lists:flatten(AccessOpts)) of + [] -> []; + L1 -> [{access, L1}] + end, + ACLOpts2 = case lists:map( + fun({ACLName, Os}) -> + {ACLName, collect_options(Os)} + end, ACLOpts1) of + [] -> []; + L2 -> [{acl, L2}] + end, + NewAccessOpts1 = case lists:map( + fun({NAName, Os}) -> + {NAName, transform_access_rules_config(Os)} + end, lists:flatten(NewAccessOpts)) of + [] -> []; + L3 -> [{access_rules, L3}] + end, + ShaperOpts1 = case lists:map( + fun({SName, Ss}) -> + {SName, transform_access_rules_config(Ss)} + end, lists:flatten(ShaperOpts)) of + [] -> []; + L4 -> [{shaper_rules, L4}] + end, + ACLOpts2 ++ AccessOpts1 ++ NewAccessOpts1 ++ ShaperOpts1 ++ Opts5. + +transform_acl({acl, Name, Type}, Opts) -> + T = case Type of + all -> all; + none -> none; + {user, U} -> {user, [b(U)]}; + {user, U, S} -> {user, [[{b(U), b(S)}]]}; + {shared_group, G} -> {shared_group, [b(G)]}; + {shared_group, G, H} -> {shared_group, [[{b(G), b(H)}]]}; + {user_regexp, UR} -> {user_regexp, [b(UR)]}; + {user_regexp, UR, S} -> {user_regexp, [[{b(UR), b(S)}]]}; + {node_regexp, UR, SR} -> {node_regexp, [[{b(UR), b(SR)}]]}; + {user_glob, UR} -> {user_glob, [b(UR)]}; + {user_glob, UR, S} -> {user_glob, [[{b(UR), b(S)}]]}; + {node_glob, UR, SR} -> {node_glob, [[{b(UR), b(SR)}]]}; + {server, S} -> {server, [b(S)]}; + {resource, R} -> {resource, [b(R)]}; + {server_regexp, SR} -> {server_regexp, [b(SR)]}; + {server_glob, S} -> {server_glob, [b(S)]}; + {ip, S} -> {ip, [b(S)]}; + {resource_glob, R} -> {resource_glob, [b(R)]}; + {resource_regexp, R} -> {resource_regexp, [b(R)]} + end, + [{acl, [{Name, [T]}]}|Opts]; +transform_acl({access, Name, Rules}, Opts) -> + NewRules = [{ACL, Action} || {Action, ACL} <- Rules], + [{access, [{Name, NewRules}]}|Opts]; +transform_acl(Opt, Opts) -> + [Opt|Opts]. + +transform_access_rules_config(Config) when is_list(Config) -> + lists:map(fun transform_access_rules_config2/1, lists:flatten(Config)); +transform_access_rules_config(Config) -> + transform_access_rules_config([Config]). + +transform_access_rules_config2(Type) when is_integer(Type); is_atom(Type) -> + {Type, [all]}; +transform_access_rules_config2({Type, ACL}) when is_atom(ACL) -> + {Type, [{acl, ACL}]}; +transform_access_rules_config2({Res, Rules}) when is_list(Rules) -> + T = lists:map(fun({Type, Args}) when is_list(Args) -> + {Type, hd(lists:flatten(Args))}; + (V) -> + V + end, lists:flatten(Rules)), + {Res, T}; +transform_access_rules_config2({Res, Rule}) -> + {Res, [Rule]}. + +%%%=================================================================== +%%% SQL +%%%=================================================================== +-define(PGSQL_PORT, 5432). +-define(MYSQL_PORT, 3306). + +transform_sql(Opts) -> + lists:foldl(fun transform_sql/2, [], Opts). + +transform_sql({odbc_server, {Type, Server, Port, DB, User, Pass}}, Opts) -> + [{sql_type, Type}, + {sql_server, Server}, + {sql_port, Port}, + {sql_database, DB}, + {sql_username, User}, + {sql_password, Pass}|Opts]; +transform_sql({odbc_server, {mysql, Server, DB, User, Pass}}, Opts) -> + transform_sql({odbc_server, {mysql, Server, ?MYSQL_PORT, DB, User, Pass}}, Opts); +transform_sql({odbc_server, {pgsql, Server, DB, User, Pass}}, Opts) -> + transform_sql({odbc_server, {pgsql, Server, ?PGSQL_PORT, DB, User, Pass}}, Opts); +transform_sql({odbc_server, {sqlite, DB}}, Opts) -> + [{sql_type, sqlite}, + {sql_database, DB}|Opts]; +transform_sql({odbc_pool_size, N}, Opts) -> + [{sql_pool_size, N}|Opts]; +transform_sql(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Riak +%%%=================================================================== +transform_riak(Opts) -> + lists:foldl(fun transform_riak/2, [], Opts). + +transform_riak({riak_server, {S, P}}, Opts) -> + [{riak_server, S}, {riak_port, P}|Opts]; +transform_riak(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% modules +%%%=================================================================== +transform_modules(Opts) -> + lists:foldl(fun transform_modules/2, [], Opts). + +transform_modules({modules, ModOpts}, Opts) -> + [{modules, lists:map( + fun({Mod, Opts1}) -> + {Mod, transform_module(Mod, Opts1)}; + (Other) -> + Other + end, ModOpts)}|Opts]; +transform_modules(Opt, Opts) -> + [Opt|Opts]. + +transform_module(mod_disco, Opts) -> + lists:map( + fun({server_info, Infos}) -> + NewInfos = lists:map( + fun({Modules, Name, URLs}) -> + [[{modules, Modules}, + {name, Name}, + {urls, URLs}]]; + (Opt) -> + Opt + end, Infos), + {server_info, NewInfos}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_muc_log, Opts) -> + lists:map( + fun({top_link, {S1, S2}}) -> + {top_link, [{S1, S2}]}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_proxy65, Opts) -> + lists:map( + fun({ip, IP}) when is_tuple(IP) -> + {ip, misc:ip_to_list(IP)}; + ({hostname, IP}) when is_tuple(IP) -> + {hostname, misc:ip_to_list(IP)}; + (Opt) -> + Opt + end, Opts); +transform_module(mod_register, Opts) -> + lists:flatmap( + fun({welcome_message, {Subj, Body}}) -> + [{welcome_message, [{subject, Subj}, {body, Body}]}]; + (Opt) -> + [Opt] + end, Opts); +transform_module(_Mod, Opts) -> + Opts. + +%%%=================================================================== +%%% Host config +%%%=================================================================== +transform_host_config(Opts) -> + Opts1 = lists:foldl(fun transform_host_config/2, [], Opts), + {HOpts, Opts2} = lists:mapfoldl( + fun({host_config, O}, Os) -> + {[O], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], Opts1), + {AHOpts, Opts3} = lists:mapfoldl( + fun({append_host_config, O}, Os) -> + {[O], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], Opts2), + HOpts1 = case collect_options(lists:flatten(HOpts)) of + [] -> + []; + HOs -> + [{host_config, + [{H, transform(O)} || {H, O} <- HOs]}] + end, + AHOpts1 = case collect_options(lists:flatten(AHOpts)) of + [] -> + []; + AHOs -> + [{append_host_config, + [{H, transform(O)} || {H, O} <- AHOs]}] + end, + HOpts1 ++ AHOpts1 ++ Opts3. + +transform_host_config({host_config, Host, HOpts}, Opts) -> + {AddOpts, HOpts1} = + lists:mapfoldl( + fun({{add, Opt}, Val}, Os) -> + {[{Opt, Val}], Os}; + (O, Os) -> + {[], [O|Os]} + end, [], HOpts), + [{append_host_config, [{Host, lists:flatten(AddOpts)}]}, + {host_config, [{Host, HOpts1}]}|Opts]; +transform_host_config(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Top-level options +%%%=================================================================== +transform_globals(Opts) -> + lists:foldl(fun transform_globals/2, [], Opts). + +transform_globals(Opt, Opts) when Opt == override_global; + Opt == override_local; + Opt == override_acls -> + ?WARNING_MSG("Option '~s' has no effect anymore", [Opt]), + Opts; +transform_globals({node_start, _}, Opts) -> + ?WARNING_MSG("Option 'node_start' has no effect anymore", []), + Opts; +transform_globals({iqdisc, {queues, N}}, Opts) -> + [{iqdisc, N}|Opts]; +transform_globals({define_macro, Macro, Val}, Opts) -> + [{define_macro, [{Macro, Val}]}|Opts]; +transform_globals(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Certfiles +%%%=================================================================== +transform_certfiles(Opts) -> + lists:foldl(fun transform_certfiles/2, [], Opts). + +transform_certfiles({domain_certfile, Domain, CertFile}, Opts) -> + [{host_config, [{Domain, [{domain_certfile, CertFile}]}]}|Opts]; +transform_certfiles(Opt, Opts) -> + [Opt|Opts]. + +%%%=================================================================== +%%% Consult file +%%%=================================================================== +consult(File) -> + case file:consult(File) of + {ok, Terms} -> + include_config_files(Terms); + Err -> + Err + end. + +include_config_files(Terms) -> + include_config_files(Terms, []). + +include_config_files([], Res) -> + {ok, Res}; +include_config_files([{include_config_file, Filename} | Terms], Res) -> + include_config_files([{include_config_file, Filename, []} | Terms], Res); +include_config_files([{include_config_file, Filename, Options} | Terms], Res) -> + case consult(Filename) of + {ok, Included_terms} -> + Disallow = proplists:get_value(disallow, Options, []), + Included_terms2 = delete_disallowed(Disallow, Included_terms), + Allow_only = proplists:get_value(allow_only, Options, all), + Included_terms3 = keep_only_allowed(Allow_only, Included_terms2), + include_config_files(Terms, Res ++ Included_terms3); + Err -> + Err + end; +include_config_files([Term | Terms], Res) -> + include_config_files(Terms, Res ++ [Term]). + +delete_disallowed(Disallowed, Terms) -> + lists:foldl( + fun(Dis, Ldis) -> + delete_disallowed2(Dis, Ldis) + end, + Terms, + Disallowed). + +delete_disallowed2(Disallowed, [H|T]) -> + case element(1, H) of + Disallowed -> + delete_disallowed2(Disallowed, T); + _ -> + [H|delete_disallowed2(Disallowed, T)] + end; +delete_disallowed2(_, []) -> + []. + +keep_only_allowed(all, Terms) -> + Terms; +keep_only_allowed(Allowed, Terms) -> + {As, _NAs} = lists:partition( + fun(Term) -> + lists:member(element(1, Term), Allowed) + end, Terms), + As. + +%%%=================================================================== +%%% Aux functions +%%%=================================================================== +strings_to_binary([]) -> + []; +strings_to_binary(L) when is_list(L) -> + case is_string(L) of + true -> + list_to_binary(L); + false -> + strings_to_binary1(L) + end; +strings_to_binary({A, B, C, D}) when + is_integer(A), is_integer(B), is_integer(C), is_integer(D) -> + {A, B, C ,D}; +strings_to_binary(T) when is_tuple(T) -> + list_to_tuple(strings_to_binary1(tuple_to_list(T))); +strings_to_binary(X) -> + X. + +strings_to_binary1([El|L]) -> + [strings_to_binary(El)|strings_to_binary1(L)]; +strings_to_binary1([]) -> + []; +strings_to_binary1(T) -> + T. + +is_string([C|T]) when (C >= 0) and (C =< 255) -> + is_string(T); +is_string([]) -> + true; +is_string(_) -> + false. + +b(S) -> + iolist_to_binary(S). |