Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

2765 lines (2491 sloc) 104.144 kB
%%%----------------------------------------------------------------------
%%% File : yaws_config.erl
%%% Author : Claes Wikstrom <klacke@bluetail.com>
%%% Purpose :
%%% Created : 16 Jan 2002 by Claes Wikstrom <klacke@bluetail.com>
%%%----------------------------------------------------------------------
-module(yaws_config).
-author('klacke@bluetail.com').
-include("../include/yaws.hrl").
-include("../include/yaws_api.hrl").
-include("yaws_debug.hrl").
-include_lib("kernel/include/file.hrl").
-export([load/1,
make_default_gconf/2, make_default_sconf/0, make_default_sconf/2,
add_sconf/1,
add_yaws_auth/1,
add_yaws_soap_srv/1, add_yaws_soap_srv/2,
load_mime_types_module/2,
search_sconf/2, search_group/2,
update_sconf/2, delete_sconf/2,
eq_sconfs/2, soft_setconf/4, hard_setconf/2,
can_hard_gc/2, can_soft_setconf/4,
can_soft_gc/2, verify_upgrade_args/2, toks/2]).
%% where to look for yaws.conf
paths() ->
case application:get_env(yaws, config) of
undefined ->
case yaws:getuid() of
{ok, "0"} -> %% root
[yaws_generated:etcdir() ++ "/yaws/yaws.conf"];
_ -> %% developer
[filename:join([yaws:home(), "yaws.conf"]),
"./yaws.conf",
yaws_generated:etcdir() ++ "/yaws/yaws.conf"]
end;
{ok, File} ->
[File]
end.
%% load the config
load(E = #env{conf = false}) ->
case yaws:first(fun(F) -> yaws:exists(F) end, paths()) of
false ->
{error, "Can't find config file "};
{ok, _, File} ->
load(E#env{conf = {file, File}})
end;
load(E) ->
{file, File} = E#env.conf,
error_logger:info_msg("Yaws: Using config file ~s~n", [File]),
case file:open(File, [read]) of
{ok, FD} ->
GC = make_default_gconf(E#env.debug, E#env.id),
GC2 = if E#env.traceoutput == undefined ->
GC;
true ->
?gc_set_tty_trace(GC, E#env.traceoutput)
end,
GC3 = ?gc_set_debug(GC2, E#env.debug),
GC4 = GC3#gconf{trace = E#env.trace},
R = (catch fload(FD, globals, GC4, undefined,
[], 1, io:get_line(FD, ''))),
?Debug("FLOAD: ~p", [R]),
case R of
{ok, GC5, Cs} ->
yaws:mkdir(yaws:tmpdir()),
Cs2 = add_yaws_auth(Cs),
add_yaws_soap_srv(GC5),
validate_cs(GC5, Cs2);
Err ->
Err
end;
_ ->
{error, "Can't open config file " ++ File}
end.
add_yaws_soap_srv(GC) when GC#gconf.enable_soap == true ->
add_yaws_soap_srv(GC, true);
add_yaws_soap_srv(_GC) ->
[].
add_yaws_soap_srv(GC, false) when GC#gconf.enable_soap == true ->
[{yaws_soap_srv, {yaws_soap_srv, start_link, [GC#gconf.soap_srv_mods]},
permanent, 5000, worker, [yaws_soap_srv]}];
add_yaws_soap_srv(GC, true) when GC#gconf.enable_soap == true ->
Spec = add_yaws_soap_srv(GC, false),
case whereis(yaws_soap_srv) of
undefined ->
spawn(fun() -> supervisor:start_child(yaws_sup, hd(Spec)) end);
_ ->
ok
end,
Spec;
add_yaws_soap_srv(_GC, _Start) ->
[].
add_yaws_auth(#sconf{}=SC) ->
SC#sconf{authdirs = setup_auth(SC)};
add_yaws_auth(SCs) ->
[SC#sconf{authdirs = setup_auth(SC)} || SC <- SCs].
%% We search and setup www authenticate for each directory
%% specified as an auth directory or containing a .yaws_auth file.
%% These are merged with server conf.
setup_auth(#sconf{docroot = Docroot, xtra_docroots = XtraDocroots,
authdirs = Authdirs}=SC) ->
[begin
Authdirs1 = load_yaws_auth_from_docroot(D, ?sc_auth_skip_docroot(SC)),
Authdirs2 = load_yaws_auth_from_authdirs(Authdirs, D, []),
Authdirs3 = [A || A <- Authdirs1,
not lists:keymember(A#auth.dir,#auth.dir,Authdirs2)],
Authdirs4 = ensure_auth_headers(Authdirs3 ++ Authdirs2),
start_pam(Authdirs4),
{D, Authdirs4}
end || D <- [Docroot|XtraDocroots] ].
load_yaws_auth_from_docroot(_, true) ->
[];
load_yaws_auth_from_docroot(undefined, _) ->
[];
load_yaws_auth_from_docroot(Docroot, _) ->
Fun = fun (Path, Acc) ->
%% Strip Docroot and then filename
SP = string:sub_string(Path, length(Docroot)+1),
Dir = filename:dirname(SP),
A = #auth{docroot=Docroot, dir=Dir},
case catch load_yaws_auth_file(Path, A) of
{ok, L} -> L ++ Acc;
_Other -> Acc
end
end,
filelib:fold_files(Docroot, "^.yaws_auth$", true, Fun, []).
load_yaws_auth_from_authdirs([], _, Acc) ->
lists:reverse(Acc);
load_yaws_auth_from_authdirs([Auth = #auth{dir=Dir}| Rest], Docroot, Acc) ->
if
Auth#auth.docroot /= [] andalso Auth#auth.docroot /= Docroot ->
load_yaws_auth_from_authdirs(Rest, Docroot, Acc);
Auth#auth.docroot == [] ->
Auth1 = Auth#auth{dir=filename:nativename(Dir)},
F = fun(A) ->
(A#auth.docroot == Docroot andalso
A#auth.dir == Auth1#auth.dir)
end,
case lists:any(F, Acc) of
true ->
load_yaws_auth_from_authdirs(Rest, Docroot, Acc);
false ->
Acc1 = Acc ++ load_yaws_auth_from_authdir(Docroot, Auth1),
load_yaws_auth_from_authdirs(Rest, Docroot, Acc1)
end;
true -> %% #auth.docroot == Docroot
Auth1 = Auth#auth{docroot=Docroot, dir=filename:nativename(Dir)},
F = fun(A) ->
not (A#auth.docroot == [] andalso
A#auth.dir == Auth1#auth.dir)
end,
Acc1 = lists:filter(F, Acc),
Acc2 = Acc1 ++ load_yaws_auth_from_authdir(Docroot, Auth1),
load_yaws_auth_from_authdirs(Rest, Docroot, Acc2)
end;
load_yaws_auth_from_authdirs([{Docroot, Auths}|_], Docroot, Acc) ->
load_yaws_auth_from_authdirs(Auths, Docroot, Acc);
load_yaws_auth_from_authdirs([_| Rest], Docroot, Acc) ->
load_yaws_auth_from_authdirs(Rest, Docroot, Acc).
load_yaws_auth_from_authdir(Docroot, Auth) ->
Dir = case Auth#auth.dir of
"/" ++ R -> R;
_ -> Auth#auth.dir
end,
Path = filename:join([Docroot, Dir, ".yaws_auth"]),
case catch load_yaws_auth_file(Path, Auth) of
{ok, Auths} -> Auths;
_ -> [Auth]
end.
load_yaws_auth_file(Path, Auth) ->
case file:consult(Path) of
{ok, TermList} ->
error_logger:info_msg("Reading .yaws_auth ~s~n", [Path]),
parse_yaws_auth_file(TermList, Auth);
{error, enoent} ->
{error, enoent};
Error ->
error_logger:format("Bad .yaws_auth file ~s ~p~n", [Path, Error]),
Error
end.
ensure_auth_headers(Authdirs) ->
[add_auth_headers(Auth) || Auth <- Authdirs].
add_auth_headers(Auth = #auth{headers = []}) ->
%% Headers needs to be set
Realm = Auth#auth.realm,
Headers = yaws:make_www_authenticate_header({realm, Realm}),
Auth#auth{headers = Headers};
add_auth_headers(Auth) ->
Auth.
start_pam([]) ->
ok;
start_pam([#auth{pam = false}|T]) ->
start_pam(T);
start_pam([A|T]) ->
case whereis(yaws_pam) of
undefined -> % pam not started
Spec = {yaws_pam, {yaws_pam, start_link,
[yaws:to_list(A#auth.pam),undefined,undefined]},
permanent, 5000, worker, [yaws_pam]},
spawn(fun() -> supervisor:start_child(yaws_sup, Spec) end);
_ ->
start_pam(T)
end.
parse_yaws_auth_file([], Auth=#auth{files=[]}) ->
{ok, [Auth]};
parse_yaws_auth_file([], Auth=#auth{dir=Dir, files=Files}) ->
{ok, [Auth#auth{dir=filename:join(Dir, F), files=[F]} || F <- Files]};
parse_yaws_auth_file([{realm, Realm}|T], Auth0) ->
parse_yaws_auth_file(T, Auth0#auth{realm = Realm});
parse_yaws_auth_file([{pam, Pam}|T], Auth0)
when is_atom(Pam) ->
parse_yaws_auth_file(T, Auth0#auth{pam = Pam});
parse_yaws_auth_file([{authmod, Authmod0}|T], Auth0)
when is_atom(Authmod0)->
Headers = try
Authmod0:get_header() ++ Auth0#auth.headers
catch
_:_ ->
error_logger:format("Failed to ~p:get_header() \n",
[Authmod0]),
Auth0#auth.headers
end,
parse_yaws_auth_file(T, Auth0#auth{mod = Authmod0, headers = Headers});
parse_yaws_auth_file([{file, File}|T], Auth0) ->
Files = case File of
"/" ++ F -> [F|Auth0#auth.files];
_ -> [File|Auth0#auth.files]
end,
parse_yaws_auth_file(T, Auth0#auth{files=Files});
parse_yaws_auth_file([{User, Password}|T], Auth0)
when is_list(User), is_list(Password) ->
Users = case lists:member({User,Password}, Auth0#auth.users) of
true -> Auth0#auth.users;
false -> [{User, Password} | Auth0#auth.users]
end,
parse_yaws_auth_file(T, Auth0#auth{users = Users});
parse_yaws_auth_file([{allow, all}|T], Auth0) ->
Auth1 = case Auth0#auth.acl of
none -> Auth0#auth{acl={all, [], deny_allow}};
{_,D,O} -> Auth0#auth{acl={all, D, O}}
end,
parse_yaws_auth_file(T, Auth1);
parse_yaws_auth_file([{allow, IPs}|T], Auth0) when is_list(IPs) ->
Auth1 = case Auth0#auth.acl of
none ->
AllowIPs = parse_auth_ips(IPs, []),
Auth0#auth{acl={AllowIPs, [], deny_allow}};
{all, _, _} ->
Auth0;
{AllowIPs, DenyIPs, Order} ->
AllowIPs2 = parse_auth_ips(IPs, []) ++ AllowIPs,
Auth0#auth{acl={AllowIPs2, DenyIPs, Order}}
end,
parse_yaws_auth_file(T, Auth1);
parse_yaws_auth_file([{deny, all}|T], Auth0) ->
Auth1 = case Auth0#auth.acl of
none -> Auth0#auth{acl={[], all, deny_allow}};
{A,_,O} -> Auth0#auth{acl={A, all, O}}
end,
parse_yaws_auth_file(T, Auth1);
parse_yaws_auth_file([{deny, IPs}|T], Auth0) when is_list(IPs) ->
Auth1 = case Auth0#auth.acl of
none ->
DenyIPs = parse_auth_ips(IPs, []),
Auth0#auth{acl={[], DenyIPs, deny_allow}};
{_, all, _} ->
Auth0;
{AllowIPs, DenyIPs, Order} ->
DenyIPs2 = parse_auth_ips(IPs, []) ++ DenyIPs,
Auth0#auth{acl={AllowIPs, DenyIPs2, Order}}
end,
parse_yaws_auth_file(T, Auth1);
parse_yaws_auth_file([{order, O}|T], Auth0)
when O == allow_deny; O == deny_allow ->
Auth1 = case Auth0#auth.acl of
none -> Auth0#auth{acl={[], [], O}};
{A,D,_} -> Auth0#auth{acl={A, D, O}}
end,
parse_yaws_auth_file(T, Auth1).
%% Create mime_types.erl, compile it and load it. If everything is ok,
%% reload groups.
%%
%% If an error occured, the previously-loaded version (the first time, it's the
%% static version) is kept.
load_mime_types_module(GC, Groups) ->
GInfo = GC#gconf.mime_types_info,
SInfos = [{{SC#sconf.servername, SC#sconf.port}, SC#sconf.mime_types_info}
|| SC <- lists:flatten(Groups),
SC#sconf.mime_types_info /= undefined],
case {is_dir(yaws:id_dir(GC#gconf.id)), is_dir(yaws:tmpdir("/tmp"))} of
{true, _} ->
File = filename:join(yaws:id_dir(GC#gconf.id), "mime_types.erl"),
load_mime_types_module(File, GInfo, SInfos);
{_, true} ->
File = filename:join(yaws:tmpdir("/tmp"), "mime_types.erl"),
load_mime_types_module(File, GInfo, SInfos);
_ ->
error_logger:format("Cannot write module mime_types.erl~n"
"Keep the previously-loaded version~n", [])
end,
lists:map(fun(Gp) ->
[begin
F = fun(X) when is_atom(X) -> X;
(X) -> element(1, mime_types:t(SC, X))
end,
TAS = SC#sconf.tilde_allowed_scripts,
AS = SC#sconf.allowed_scripts,
SC#sconf{tilde_allowed_scripts=lists:map(F, TAS),
allowed_scripts=lists:map(F, AS)}
end || SC <- Gp]
end, Groups).
load_mime_types_module(_, undefined, []) ->
ok;
load_mime_types_module(File, undefined, SInfos) ->
load_mime_types_module(File, #mime_types_info{}, SInfos);
load_mime_types_module(File, GInfo, SInfos) ->
case mime_type_c:generate(File, GInfo, SInfos) of
ok ->
case compile:file(File, [binary]) of
{ok, ModName, Binary} ->
case code:load_binary(ModName, [], Binary) of
{module, ModName} ->
ok;
{error, What} ->
error_logger:format(
"Cannot load module '~p': ~p~n"
"Keep the previously-loaded version~n",
[ModName, What]
)
end;
_ ->
error_logger:format("Compilation of '~p' failed~n"
"Keep the previously-loaded version~n",
[File])
end;
{error, Reason} ->
error_logger:format("Cannot write module ~p: ~p~n"
"Keep the previously-loaded version~n",
[File, Reason])
end.
%% This is the function that arranges sconfs into
%% different server groups
validate_cs(GC, Cs) ->
L = lists:map(fun(SC) -> {{SC#sconf.listen, SC#sconf.port}, SC} end, Cs),
L2 = lists:map(fun(X) -> element(2, X) end, lists:keysort(1,L)),
L3 = arrange(L2, start, [], []),
case validate_groups(L3) of
ok ->
{ok, GC, L3};
Err ->
Err
end.
validate_groups([]) ->
ok;
validate_groups([H|T]) ->
case (catch validate_group(H)) of
ok ->
validate_groups(T);
Err ->
Err
end.
validate_group(List) ->
%% all ssl servers with the same IP must share the same ssl configuration
%% check if one ssl server in the group
case lists:any(fun(SC) -> SC#sconf.ssl /= undefined end,List) of
true ->
[SC0|SCs] = List,
%% check if all servers in the group have the same ssl configuration
case lists:filter(
fun(SC) -> SC#sconf.ssl /= SC0#sconf.ssl end,SCs) of
L when length(L) > 1 ->
throw({error, ?F("SSL server per IP must share the "
"same certificate : ~p",
[(hd(L))#sconf.servername])});
_ ->
ok
end;
_ ->
ok
end,
%% second all servernames in a group must be unique
SN = lists:sort([yaws:to_lower(X#sconf.servername) || X <- List]),
no_two_same(SN).
no_two_same([H,H|_]) ->
throw({error,
?F("Two servers in the same group cannot have same name ~p",[H])});
no_two_same([_H|T]) ->
no_two_same(T);
no_two_same([]) ->
ok.
arrange([C|Tail], start, [], B) ->
arrange(Tail, {in, C}, [set_server(C)], B);
arrange([], _, [], B) ->
B;
arrange([], _, A, B) ->
[A | B];
arrange([C1|Tail], {in, C0}, A, B) ->
if
C1#sconf.listen == C0#sconf.listen,
C1#sconf.port == C0#sconf.port ->
arrange(Tail, {in, C0}, [set_server(C1)|A], B);
true ->
arrange(Tail, {in, C1}, [set_server(C1)], [A|B])
end.
set_server(SC) ->
case {SC#sconf.ssl, SC#sconf.port, ?sc_has_add_port(SC)} of
{undefined, 80, _} ->
SC;
{undefined, Port, true} ->
add_port(SC, Port);
{_SSL, 443, _} ->
SC;
{_SSL, Port, true} ->
add_port(SC, Port);
{_,_,_} ->
SC
end.
add_port(SC, Port) ->
case string:tokens(SC#sconf.servername, ":") of
[Srv, Prt] ->
case (catch list_to_integer(Prt)) of
{'EXIT', _} ->
SC#sconf{servername =
Srv ++ [$:|integer_to_list(Port)]};
_Int ->
SC
end;
[Srv] ->
SC#sconf{servername = Srv ++ [$:|integer_to_list(Port)]}
end.
make_default_gconf(Debug, Id) ->
Y = yaws_dir(),
#gconf{yaws_dir = Y,
ebin_dir = [filename:join([Y, "examples/ebin"])],
include_dir = [filename:join([Y, "examples/include"])],
trace = false,
logdir = ".",
cache_refresh_secs = if
Debug == true ->
0;
true ->
30
end,
flags = if Debug ->
?GC_DEBUG bor ?GC_COPY_ERRLOG
bor ?GC_FAIL_ON_BIND_ERR bor
?GC_PICK_FIRST_VIRTHOST_ON_NOMATCH;
true ->
?GC_COPY_ERRLOG bor ?GC_FAIL_ON_BIND_ERR bor
?GC_PICK_FIRST_VIRTHOST_ON_NOMATCH
end,
yaws = "Yaws " ++ yaws_generated:version(),
id = Id
}.
%% Keep this function for backward compatibility. But no one is supposed to use
%% it (yaws_config is an internal module, its api is private).
make_default_sconf() ->
make_default_gconf([], undefined).
make_default_sconf([], Port) ->
make_default_sconf(filename:join([yaws_dir(), "www"]), Port);
make_default_sconf(DocRoot, undefined) ->
make_default_sconf(DocRoot, 8000);
make_default_sconf(DocRoot, Port) ->
set_server(#sconf{port=Port, listen={127,0,0,1}, docroot=DocRoot}).
yaws_dir() ->
%% below, ignore dialyzer warning:
%% "The pattern 'false' can never match the type 'true'"
case yaws_generated:is_local_install() of
true ->
P = filename:split(code:which(?MODULE)),
P1 = del_tail(P),
filename:join(P1);
false ->
code:lib_dir(yaws)
end.
del_tail(Parts) ->
del_tail(Parts,[]).
%% Initial ".." should be preserved
del_tail([".." |Tail], Acc) ->
del_tail(Tail, [".."|Acc]);
del_tail(Parts, Acc) ->
del_tail2(Parts, Acc).
%% Embedded ".." should be removed together with preceding dir
del_tail2([_H, ".." |Tail], Acc) ->
del_tail2(Tail, Acc);
del_tail2([".." |Tail], [_P|Acc]) ->
del_tail2(Tail, Acc);
del_tail2([_X, _Y], Acc) ->
lists:reverse(Acc);
del_tail2([H|T], Acc) ->
del_tail2(T, [H|Acc]).
string_to_host_and_port(String) ->
case string:tokens(String, ":") of
[Host, Port] ->
case string:to_integer(Port) of
{Integer, []} when Integer >= 0, Integer =< 65535 ->
{ok, Host, Integer};
_Else ->
{error, ?F("~p is not a valid port number", [Port])}
end;
_Else ->
{error, ?F("bad host and port specifier, expected HOST:PORT", [])}
end.
string_to_node_mod_fun(String) ->
case string:tokens(String, ":") of
[Node, Mod, Fun] ->
{ok, list_to_atom(Node), list_to_atom(Mod), list_to_atom(Fun)};
[Mod, Fun] ->
{ok, list_to_atom(Mod), list_to_atom(Fun)};
_ ->
{error, ?F("bad external module specifier, "
"expected NODE:MODULE:FUNCTION or MODULE:FUNCTION", [])}
end.
%% two states, global, server
fload(FD, globals, GC, _C, Cs, _Lno, eof) ->
file:close(FD),
{ok, GC, Cs};
fload(FD, _, _GC, _C, _Cs, Lno, eof) ->
file:close(FD),
{error, ?F("Unexpected end-of-file at line ~w", [Lno])};
fload(FD, globals, GC, C, Cs, Lno, Chars) ->
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["subconfig", '=', Name] ->
File = filename:absname(Name),
case is_file(File) of
true ->
error_logger:info_msg(
"Yaws: Using subconfig file ~s~n", [File]),
case file:open(File, [read]) of
{ok, FD1} ->
R = (catch fload(FD1, globals, GC, undefined,
Cs, 1, io:get_line(FD1, ''))),
?Debug("FLOAD: ~p", [R]),
case R of
{ok, GC1, Cs1} ->
fload(FD, globals, GC1, C, Cs1, Lno+1,Next);
Err ->
Err
end;
_ ->
{error, "Can't open config file " ++ File}
end;
false ->
{error, ?F("Expect filename at line ~w", [Lno])}
end;
["subconfigdir", '=', Name] ->
Dir = filename:absname(Name),
case is_dir(Dir) of
true ->
case file:list_dir(Dir) of
{ok, Names} ->
Sorted = lists:sort(Names),
Paths = lists:map(
fun(N) ->
filename:absname(N, Dir)
end, Sorted),
Fold = lists:foldl(
fun subconfigdir_fold/2,
{ok, GC, Cs}, Paths),
case Fold of
{ok, GC1, Cs1} ->
fload(FD, globals, GC1, C, Cs1, Lno+1, Next);
Err ->
Err
end;
{error, Error} ->
{error, ?F("Directory ~s is not readable: ~s",
[Name, Error])}
end;
false ->
{error, ?F("Expect directory at line ~w (subconfdir: ~s)",
[Lno, Dir])}
end;
["trace", '=', Bstr] when GC#gconf.trace == false ->
case Bstr of
"traffic" ->
fload(FD, globals, GC#gconf{trace = {true, traffic}},
C, Cs, Lno+1, Next);
"http" ->
fload(FD, globals, GC#gconf{trace = {true, http}},
C, Cs, Lno+1, Next);
"false" ->
fload(FD, globals, GC#gconf{trace = false},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect false|http|traffic at line ~w",[Lno])}
end;
["trace", '=', _Bstr] ->
%% don't overwrite setting from commandline
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["logdir", '=', Logdir] ->
Dir = filename:absname(Logdir),
case is_dir(Dir) of
true ->
put(logdir, Dir),
fload(FD, globals, GC#gconf{logdir = Dir},
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect directory at line ~w (logdir ~s)", [Lno, Dir])}
end;
["ebin_dir", '=', Ebindir] ->
Dir = filename:absname(Ebindir),
case warn_dir("ebin_dir", Dir) of
true ->
fload(FD, globals, GC#gconf{ebin_dir =
[Dir|GC#gconf.ebin_dir]},
C, Cs, Lno+1, Next);
false ->
fload(FD, globals, GC, C, Cs, Lno+1, Next)
end;
["runmod", '=', Mod0] ->
Mod = list_to_atom(Mod0),
fload(FD, globals, GC#gconf{runmods = [Mod|GC#gconf.runmods]},
C, Cs, Lno+1, Next);
["enable_soap", '=', Bool] ->
if (Bool == "true") ->
fload(FD, globals, GC#gconf{enable_soap = true},
C, Cs, Lno+1, Next);
true ->
fload(FD, globals, GC#gconf{enable_soap = false},
C, Cs, Lno+1, Next)
end;
["soap_srv_mods", '=' | SoapSrvMods] ->
case parse_soap_srv_mods(SoapSrvMods, []) of
{ok, L} ->
fload(FD, globals, GC#gconf{soap_srv_mods = L},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["max_connections", '=', Int] ->
case (catch list_to_integer(Int)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{max_connections = I},
C, Cs, Lno+1, Next);
_ when Int == "nolimit" ->
fload(FD, globals, GC, C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["process_options", '=', POpts] ->
case parse_process_options(POpts) of
{ok, ProcList} ->
fload(FD, globals, GC#gconf{process_options=ProcList},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["acceptor_pool_size", '=', Int] ->
case catch list_to_integer(Int) of
I when is_integer(I), I >= 0 ->
fload(FD, globals, GC#gconf{acceptor_pool_size = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer >= 0 at line ~w", [Lno])}
end;
["log_wrap_size", '=', Int] ->
case (catch list_to_integer(Int)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{log_wrap_size = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["log_resolve_hostname", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
fload(FD, globals, ?gc_log_set_resolve_hostname(GC, Val),
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["fail_on_bind_err", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
fload(FD, globals, ?gc_set_fail_on_bind_err(GC, Val),
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["include_dir", '=', Incdir] ->
Dir = filename:absname(Incdir),
case warn_dir("include_dir", Dir) of
true ->
fload(FD, globals, GC#gconf{include_dir=
[Dir|GC#gconf.include_dir]},
C, Cs, Lno+1, Next);
false ->
fload(FD, globals, GC, C, Cs, Lno+1, Next)
end;
["mnesia_dir", '=', Mnesiadir] ->
Dir = filename:absname(Mnesiadir),
case is_dir(Dir) of
true ->
put(mnesiadir, Dir),
fload(FD, globals, GC#gconf{mnesia_dir = Dir},
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect mnesia directory at line ~w", [Lno])}
end;
["tmpdir", '=', _TmpDir] ->
%% ignore
error_logger:format(
"tmpdir in yaws.conf is no longer supported - ignoring\n",[]),
fload(FD, globals, GC,C, Cs, Lno+1, Next);
%% keep this bugger for backward compat for a while
["keepalive_timeout", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{keepalive_timeout = I},
C, Cs, Lno+1, Next);
_ when Val == "infinity" ->
fload(FD, globals, GC#gconf{keepalive_timeout = infinity},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["keepalive_maxuses", '=', Int] ->
case (catch list_to_integer(Int)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{keepalive_maxuses = I},
C, Cs, Lno+1, Next);
_ when Int == "nolimit" ->
%% nolimit is the default
fload(FD, globals, GC, C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["php_exe_path", '=' , PhpPath] ->
error_logger:format(
"'php_exe_path' is deprecated, use 'php_handler' instead\n",
[]),
case is_file(PhpPath) of
true ->
fload(FD, globals, GC#gconf{phpexe = PhpPath},
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect executable file at line ~w", [Lno])}
end;
%% deprected, don't use
["read_timeout", '=', _Val] ->
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["max_num_cached_files", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{max_num_cached_files = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["max_num_cached_bytes", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{max_num_cached_bytes = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["max_size_cached_file", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
fload(FD, globals, GC#gconf{max_size_cached_file = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["cache_refresh_secs", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I), I >= 0 ->
fload(FD, globals, GC#gconf{cache_refresh_secs = I},
C, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect 0 or positive integer at line ~w",[Lno])}
end;
["copy_error_log", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
fload(FD, globals, ?gc_set_copy_errlog(GC, Val),
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["auth_log", '=', Bool] ->
error_logger:format(
"'auth_log' global variable is deprecated and ignored."
" it is now a per-server variable", []),
case is_bool(Bool) of
{true, _Val} ->
fload(FD, globals, GC, C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["id", '=', String] when GC#gconf.id == undefined;
GC#gconf.id == "default" ->
fload(FD, globals, GC#gconf{id=String},C, Cs, Lno+1, Next);
["id", '=', String] ->
error_logger:format("Ignoring 'id = ~p' setting at line ~p~n",
[String,Lno]),
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["pick_first_virthost_on_nomatch", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
fload(FD, globals,
?gc_set_pick_first_virthost_on_nomatch(GC,Val),
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["use_fdsrv", '=', _Bool] ->
%% feature removed
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["use_old_ssl", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
fload(FD, globals,
?gc_set_use_old_ssl(GC,Val),
C, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["use_large_ssl_pool", '=', _Bool] ->
%% just ignore - not relevant any longer
fload(FD, globals, GC,
C, Cs, Lno+1, Next);
["x_forwarded_for_log_proxy_whitelist", '=' | _] ->
error_logger:info_msg("Warning, x_forwarded_for_log_proxy_whitelist"
" is deprecated and ignored~n", []),
fload(FD, globals, GC, C, Cs, Lno+1, Next);
["ysession_mod", '=', Mod_str] ->
Ysession_mod = list_to_atom(Mod_str),
fload(FD, globals, GC#gconf{ysession_mod = Ysession_mod},
C, Cs, Lno+1, Next);
["server_signature", '=', Signature] ->
fload(FD, globals, GC#gconf{yaws=Signature},C, Cs, Lno+1, Next);
["default_type", '=', MimeType] ->
case parse_mime_types_info(default_type, MimeType,
GC#gconf.mime_types_info,
#mime_types_info{}) of
{ok, Info} ->
fload(FD, globals, GC#gconf{mime_types_info=Info},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["default_charset", '=', Charset] ->
case parse_mime_types_info(default_charset, Charset,
GC#gconf.mime_types_info,
#mime_types_info{}) of
{ok, Info} ->
fload(FD, globals, GC#gconf{mime_types_info=Info},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["mime_types_file", '=', File] ->
case parse_mime_types_info(mime_types_file, File,
GC#gconf.mime_types_info,
#mime_types_info{}) of
{ok, Info} ->
fload(FD, globals, GC#gconf{mime_types_info=Info},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["add_types", '=' | NewTypes] ->
case parse_mime_types_info(add_types, NewTypes,
GC#gconf.mime_types_info,
#mime_types_info{}) of
{ok, Info} ->
fload(FD, globals, GC#gconf{mime_types_info=Info},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["add_charsets", '=' | NewCharsets] ->
case parse_mime_types_info(add_charsets, NewCharsets,
GC#gconf.mime_types_info,
#mime_types_info{}) of
{ok, Info} ->
fload(FD, globals, GC#gconf{mime_types_info=Info},
C, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
['<', "server", Server, '>'] -> %% first server
PhpHandler = {cgi, GC#gconf.phpexe},
fload(FD, server, GC,
#sconf{servername = Server, php_handler = PhpHandler,
listen = []},
Cs, Lno+1, Next);
[H|_] ->
{error, ?F("Unexpected tokens ~p at line ~w", [H, Lno])};
Err ->
Err
end;
fload(FD, server, GC, C, Cs, Lno, Chars) ->
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
["server_signature", '=', Signature] ->
fload(FD, server, GC, C#sconf{yaws=Signature}, Cs, Lno+1, Next);
["access_log", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_access_log(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["auth_log", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_auth_log(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["logger_mod", '=', Module] ->
C2 = C#sconf{logger_mod = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["dir_listings", '=', StrVal] ->
case StrVal of
"true" ->
C2 = ?sc_set_dir_listings(C, true),
C3 = ?sc_set_dir_all_zip(C2, true),
C4 = C3#sconf{appmods = [ {"all.zip", yaws_ls},
{"all.tgz", yaws_ls},
{"all.tbz2", yaws_ls}|
C3#sconf.appmods]},
fload(FD, server, GC, C4, Cs, Lno+1, Next);
"true_nozip" ->
C2 = ?sc_set_dir_listings(C, true),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
"false" ->
C2 = ?sc_set_dir_listings(C, false),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect true|true_nozip|false at line ~w",[Lno])}
end;
["deflate", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C1 = C#sconf{deflate_options=#deflate{}},
C2 = ?sc_set_deflate(C1, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["auth_skip_docroot",'=',Bool] ->
case is_bool(Bool) of
{true,Val} ->
C2 = ?sc_set_auth_skip_docroot(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["dav", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_dav(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["port", '=', Val] ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
C2 = C#sconf{port = I},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["rmethod", '=', Val] ->
case Val of
"http" ->
C2 = C#sconf{rmethod = Val},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
"https" ->
C2 = C#sconf{rmethod = Val},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect http or https at line ~w", [Lno])}
end;
["rhost", '=', Val] ->
C2 = C#sconf{rhost = Val},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["listen", '=', IP] ->
case inet_parse:address(IP) of
{error, _} ->
{error, ?F("Expect IP address at line ~w:", [Lno])};
{ok,Addr} ->
Lstn = C#sconf.listen,
C2 = if
is_list(Lstn) ->
case lists:member(Addr, Lstn) of
false ->
C#sconf{listen = [Addr|Lstn]};
true ->
C
end;
true ->
C#sconf{listen = [Addr, Lstn]}
end,
fload(FD, server, GC, C2, Cs, Lno+1, Next)
end;
["listen_backlog", '=', Val] ->
case (catch list_to_integer(Val)) of
B when is_integer(B) ->
C2 = case proplists:get_value(listen_opts,
C#sconf.soptions) of
undefined ->
C#sconf{soptions =
[{listen_opts, [{backlog, B}]} |
C#sconf.soptions]};
Opts ->
C#sconf{soptions =
[{listen_opts, [{backlog, B} | Opts]} |
C#sconf.soptions]}
end,
fload(FD, server, GC, C2, Cs, Lno+1, Next);
_ ->
{error, ?F("Expect integer at line ~w", [Lno])}
end;
["servername", '=', Name] ->
C2 = ?sc_set_add_port((C#sconf{servername = Name}),false),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["docroot", '=', Rootdir | XtraDirs] ->
RootDirs = lists:map(fun(R) -> filename:absname(R) end,
[Rootdir | XtraDirs]),
case lists:filter(fun(R) -> not is_dir(R) end, RootDirs) of
[] when C#sconf.docroot =:= undefined ->
C2 = C#sconf{docroot = hd(RootDirs),
xtra_docroots = tl(RootDirs)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[] ->
XtraDocroots = RootDirs ++ C#sconf.xtra_docroots,
C2 = C#sconf{xtra_docroots = XtraDocroots},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
NoDirs ->
error_logger:info_msg("Warning, Skip invalid docroots"
" at line ~w : ~s~n",
[Lno, string:join(NoDirs, ", ")]),
case lists:subtract(RootDirs, NoDirs) of
[] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
[H|T] when C#sconf.docroot =:= undefined ->
C2 = C#sconf{docroot = H, xtra_docroots = T},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
Ds ->
XtraDocroots = Ds ++ C#sconf.xtra_docroots,
C2 = C#sconf{xtra_docroots = XtraDocroots},
fload(FD, server, GC, C2, Cs, Lno+1, Next)
end
end;
["partial_post_size",'=',Size] ->
case Size of
"nolimit" ->
C2 = C#sconf{partial_post_size = nolimit},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
Val ->
case (catch list_to_integer(Val)) of
I when is_integer(I) ->
C2 = C#sconf{partial_post_size = I},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
_ ->
{error,
?F("Expect integer or 'nolimit' at line ~w",
[Lno])}
end
end;
['<', "auth", '>'] ->
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, #auth{});
['<', "redirect", '>'] ->
fload(FD, server_redirect, GC, C, Cs, Lno+1, Next, []);
['<', "deflate", '>'] ->
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next,
#deflate{mime_types=[]});
%% noop
["default_server_on_this_ip", '=', _Bool] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
[ '<', "ssl", '>'] ->
ssl_start(),
fload(FD, ssl, GC, C#sconf{ssl = #ssl{}}, Cs, Lno+1, Next);
["appmods", '=' | AppMods] ->
case parse_appmods(AppMods, []) of
{ok, L} ->
C2 = C#sconf{appmods = L ++ C#sconf.appmods},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["dispatchmod", '=', DispatchMod] ->
C2 = C#sconf{dispatch_mod = list_to_atom(DispatchMod)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["expires", '=' | Expires] ->
case parse_expires(Expires, []) of
{ok, L} ->
C2 = C#sconf{expires = L ++ C#sconf.expires},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["errormod_404", '=' , Module] ->
C2 = C#sconf{errormod_404 = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["errormod_crash", '=', Module] ->
C2 = C#sconf{errormod_crash = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["errormod_401", '=' , Module] ->
C2 = C#sconf{errormod_401 = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["arg_rewrite_mod", '=', Module] ->
C2 = C#sconf{arg_rewrite_mod = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["tilde_expand", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_tilde_expand(C,Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
['<', "/server", '>'] ->
HasDocroot =
case C#sconf.docroot of
undefined ->
Tests = [fun() ->
lists:keymember("/", #proxy_cfg.prefix, C#sconf.revproxy)
end,
fun() ->
lists:keymember("/", 1, C#sconf.redirect_map)
end,
fun() ->
lists:foldl(fun(_, true) -> true;
({"/", _}, _Acc) -> true;
(_, Acc) -> Acc
end, false, C#sconf.appmods)
end,
fun() ->
?sc_forward_proxy(C)
end],
lists:any(fun(T) -> T() end, Tests);
_ ->
true
end,
case HasDocroot of
true ->
case C#sconf.listen of
[] ->
C2 = C#sconf{listen = {127,0,0,1}},
fload(FD, globals, GC, undefined, [C2|Cs], Lno+1, Next);
Ls ->
Cs2 = [C#sconf{listen=L} || L <- Ls] ++ Cs,
fload(FD, globals, GC, undefined, Cs2, Lno+1, Next)
end;
false ->
{error,
?F("No valid docroot configured for virthost '~s' (port: ~w)",
[C#sconf.servername, C#sconf.port])}
end;
['<', "opaque", '>'] ->
fload(FD, opaque, GC, C, Cs, Lno+1, Next);
["start_mod", '=' , Module] ->
C2 = C#sconf{start_mod = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
['<', "rss", '>'] ->
erase(rss_id),
put(rss, []),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["tilde_allowed_scripts", '=' | Suffixes] ->
C2 = C#sconf{tilde_allowed_scripts=Suffixes},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["allowed_scripts", '=' | Suffixes] ->
C2 = C#sconf{allowed_scripts=Suffixes},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["index_files", '=' | Files] ->
case parse_index_files(Files) of
ok ->
C2 = C#sconf{index_files = Files},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["revproxy", '=' | Tail] ->
case parse_revproxy(Tail) of
{ok, RevProxy} ->
C2 = C#sconf{revproxy = [RevProxy | C#sconf.revproxy]},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, url} ->
{error, ?F("Bad url at line ~p",[Lno])};
{error, syntax} ->
{error, ?F("Bad revproxy syntax at line ~p",[Lno])};
Error ->
Error
end;
["fwdproxy", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_forward_proxy(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
['<', "extra_cgi_vars", "dir", '=', Dir, '>'] ->
fload(FD, extra_cgi_vars, GC, C, Cs, Lno+1, Next, {Dir, []});
["statistics", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_statistics(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["fcgi_app_server", '=', Val] ->
case string_to_host_and_port(Val) of
{ok, Host, Port} ->
C2 = C#sconf{fcgi_app_server = {Host, Port}},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Reason} ->
{error, ?F("Invalid fcgi_app_server ~p at line ~w: ~s",
[Val, Lno, Reason])}
end;
["fcgi_trace_protocol", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_fcgi_trace_protocol(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["fcgi_log_app_error", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
C2 = ?sc_set_fcgi_log_app_error(C, Val),
fload(FD, server, GC, C2, Cs, Lno+1, Next);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
["phpfcgi", '=', HostPortSpec] ->
error_logger:format(
"'phpfcgi' is deprecated, use 'php_handler' instead\n", []),
case string_to_host_and_port(HostPortSpec) of
{ok, Host, Port} ->
C2 = C#sconf{php_handler = {fcgi, {Host, Port}}},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Reason} ->
{error,
?F("Invalid php fcgi server ~p at line ~w: ~s",
[HostPortSpec, Lno, Reason])}
end;
["php_handler", '=' | PhpMod] ->
case parse_phpmod(PhpMod, GC#gconf.phpexe) of
{ok, I} ->
C2 = C#sconf{php_handler = I},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
{error, Reason} ->
{error,
?F("Invalide php_handler configuration at line ~w: ~s",
[Lno, Reason])}
end;
["shaper", '=', Module] ->
C2 = C#sconf{shaper = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
["default_type", '=', MimeType] ->
case parse_mime_types_info(default_type, MimeType,
C#sconf.mime_types_info,
GC#gconf.mime_types_info) of
{ok, Info} ->
fload(FD, server, GC, C#sconf{mime_types_info=Info},
Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["default_charset", '=', Charset] ->
case parse_mime_types_info(default_charset, Charset,
C#sconf.mime_types_info,
GC#gconf.mime_types_info) of
{ok, Info} ->
fload(FD, server, GC, C#sconf{mime_types_info=Info},
Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["mime_types_file", '=', File] ->
case parse_mime_types_info(mime_types_file, File,
C#sconf.mime_types_info,
GC#gconf.mime_types_info) of
{ok, Info} ->
fload(FD, server, GC, C#sconf{mime_types_info=Info},
Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["add_types", '=' | NewTypes] ->
case parse_mime_types_info(add_types, NewTypes,
C#sconf.mime_types_info,
GC#gconf.mime_types_info) of
{ok, Info} ->
fload(FD, server, GC, C#sconf{mime_types_info=Info},
Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["add_charsets", '=' | NewCharsets] ->
case parse_mime_types_info(add_charsets, NewCharsets,
C#sconf.mime_types_info,
GC#gconf.mime_types_info) of
{ok, Info} ->
fload(FD, server, GC, C#sconf{mime_types_info=Info},
Cs, Lno+1, Next);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, ssl, GC, C, Cs, Lno, Chars) ->
Next = io_get_line(FD, '', []),
case toks(Lno, Chars) of
[] ->
fload(FD, ssl, GC, C, Cs, Lno+1, Next);
%% A bunch of ssl options
["keyfile", '=', Val] ->
case is_file(Val) of
true when is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{keyfile = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])};
_ ->
{error, ?F("Expect existing file at line ~w", [Lno])}
end;
["certfile", '=', Val] ->
case is_file(Val) of
true when is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{certfile = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])};
_ ->
{error, ?F("Expect existing file at line ~w", [Lno])}
end;
["cacertfile", '=', Val] ->
case is_file(Val) of
true when is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{cacertfile = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])};
_ ->
{error, ?F("Expect existing file at line ~w", [Lno])}
end;
["verify", '=', Val0] ->
Val =
try
list_to_integer(Val0)
catch error:badarg ->
list_to_atom(Val0)
end,
case lists:member(Val, [0,1,2,verify_peer,verify_none]) of
true when is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{verify = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])};
_ ->
{error, ?F("Expect integer or verify_none, "
"verify_peer at line ~w", [Lno])}
end;
["fail_if_no_peer_cert", '=', Val0] ->
Val = (catch list_to_atom(Val0)),
if
is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{
fail_if_no_peer_cert = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option fail_if_no_peer_cert "
"to true before line ~w",
[Lno])}
end;
["depth", '=', Val0] ->
Val = (catch list_to_integer(Val0)),
case lists:member(Val, [0, 1,2,3,4,5,6,7]) of
true when is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{depth = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])};
_ ->
{error, ?F("Expect integer 0..7 at line ~w", [Lno])}
end;
["password", '=', Val] ->
if
is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{password = Val}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to true before line ~w",
[Lno])}
end;
["ciphers", '=', Val] ->
try
L = str2term(Val),
io:format("L = ~p~n",[L]),
Ciphers = ssl:cipher_suites(),
case check_ciphers(L, Ciphers) of
ok ->
if
is_record(C#sconf.ssl, ssl) ->
C2 = C#sconf{ssl = (C#sconf.ssl)#ssl{
ciphers = L}},
fload(FD, ssl, GC, C2, Cs, Lno+1, Next);
true ->
{error, ?F("Need to set option ssl to "
"true before line ~w",
[Lno])}
end;
Err ->
Err
end
catch _:Err2 ->
io:format("~p~n", [Err2]),
{error, ?F("Bad cipherspec at line ~w", [Lno])}
end;
['<', "/ssl", '>'] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, rss, GC, C, Cs, Lno, Chars) ->
%%?Debug("Chars: ~s", [Chars]),
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, rss, GC, C, Cs, Lno+1, Next);
['<', "/rss", '>'] ->
case get(rss_id) of
undefined ->
{error, ?F("No rss_id specified at line ~w", [Lno])};
RSSid ->
yaws_rss:open(RSSid, get(rss)),
fload(FD, server, GC, C, Cs, Lno+1, Next)
end;
["rss_id", '=', Value] -> % mandatory !!
put(rss_id, list_to_atom(Value)),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["rss_dir", '=', Value] -> % mandatory !!
put(rss, [{db_dir, Value} | get(rss)]),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["rss_expire", '=', Value] ->
put(rss, [{expire, Value} | get(rss)]),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["rss_days", '=', Value] ->
put(rss, [{days, Value} | get(rss)]),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["rss_rm_exp", '=', Value] ->
put(rss, [{rm_exp, Value} | get(rss)]),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
["rss_max", '=', Value] ->
put(rss, [{rm_max, Value} | get(rss)]),
fload(FD, rss, GC, C, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, opaque, GC, C, Cs, Lno, Chars) ->
%%?Debug("Chars: ~s", [Chars]),
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, opaque, GC, C, Cs, Lno+1, Next);
['<', "/opaque", '>'] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
[Key, '=', Value] ->
C2 = C#sconf{opaque = [{Key,Value} | C#sconf.opaque]},
fload(FD, opaque, GC, C2, Cs, Lno+1, Next);
[Key, '='| Value] ->
String_value = lists:flatten(
lists:map(
fun(Item) when is_atom(Item) ->
atom_to_list(Item);
(Item) ->
Item
end, Value)),
C2 = C#sconf{opaque = [{Key, String_value} | C#sconf.opaque]},
fload(FD, opaque, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end.
fload(FD, server_auth, _GC, _C, _Cs, Lno, eof, _Auth) ->
file:close(FD),
{error, ?F("Unexpected end of file at line ~w", [Lno])};
fload(FD, server_auth, GC, C, Cs, Lno, Chars, Auth) ->
%%?Debug("Chars: ~s", [Chars]),
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, Auth);
["docroot", '=', Docroot] ->
A2 = Auth#auth{docroot = filename:absname(Docroot)},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["dir", '=', Authdir] ->
case file:list_dir(Authdir) of
{ok,_} when Authdir /= "/" ->
error_logger:info_msg("Warning, authdir must be set "
"relative docroot ~n",[]);
_ ->
ok
end,
Dir = yaws_api:path_norm(Authdir),
A2 = Auth#auth{dir = [Dir | Auth#auth.dir]},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["realm", '=', Realm] ->
A2 = Auth#auth{realm = Realm},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["authmod", '=', Mod] ->
Mod2 = list_to_atom(Mod),
code:ensure_loaded(Mod2),
%% Add the auth header for the mod
H = try
Mod2:get_header() ++ Auth#auth.headers
catch _:_ ->
error_logger:format("Failed to ~p:get_header() \n",
[Mod2]),
Auth#auth.headers
end,
A2 = Auth#auth{mod = Mod2, headers = H},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["user", '=', User] ->
case (catch string:tokens(User, ":")) of
[Name, Passwd] ->
A2 = Auth#auth{users = [{Name, Passwd}|Auth#auth.users]},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
_ ->
{error, ?F("Invalid user at line ~w", [Lno])}
end;
["allow", '=', "all"] ->
A2 = case Auth#auth.acl of
none -> Auth#auth{acl={all, [], deny_allow}};
{_,D,O} -> Auth#auth{acl={all, D, O}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["allow", '=' | IPs] ->
A2 = case Auth#auth.acl of
none ->
AllowIPs = parse_auth_ips(IPs, []),
Auth#auth{acl={AllowIPs, [], deny_allow}};
{all, _, _} ->
Auth;
{AllowIPs, DenyIPs, Order} ->
AllowIPs2 = parse_auth_ips(IPs, []) ++ AllowIPs,
Auth#auth{acl={AllowIPs2, DenyIPs, Order}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["deny", '=', "all"] ->
A2 = case Auth#auth.acl of
none -> Auth#auth{acl={[], all, deny_allow}};
{A,_,O} -> Auth#auth{acl={A, all, O}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["deny", '=' | IPs] ->
A2 = case Auth#auth.acl of
none ->
DenyIPs = parse_auth_ips(IPs, []),
Auth#auth{acl={[], DenyIPs, deny_allow}};
{_, all, _} ->
Auth;
{AllowIPs, DenyIPs, Order} ->
DenyIPs2 = parse_auth_ips(IPs, []) ++ DenyIPs,
Auth#auth{acl={AllowIPs, DenyIPs2, Order}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["order", '=', "allow", ',', "deny"] ->
A2 = case Auth#auth.acl of
none -> Auth#auth{acl={[], [], allow_deny}};
{A,D,_} -> Auth#auth{acl={A, D, allow_deny}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["order", '=', "deny", ',', "allow"] ->
A2 = case Auth#auth.acl of
none -> Auth#auth{acl={[], [], deny_allow}};
{A,D,_} -> Auth#auth{acl={A, D, deny_allow}}
end,
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["pam", "service", '=', Serv] ->
A2 = Auth#auth{pam=Serv},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
['<', "/auth", '>'] ->
Pam = Auth#auth.pam,
Users = Auth#auth.users,
Realm = Auth#auth.realm,
A2 = case {Pam, Users} of
{false, []} ->
Auth;
_ ->
H = Auth#auth.headers ++
yaws:make_www_authenticate_header({realm, Realm}),
Auth#auth{headers = H}
end,
Authdirs = case A2#auth.dir of
[] -> [A2#auth{dir="/"}];
Ds -> [A2#auth{dir=D} || D <- Ds]
end,
C2 = C#sconf{authdirs = Authdirs ++ C#sconf.authdirs},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, server_redirect, _GC, _C, _Cs, Lno, eof, _RedirMap) ->
file:close(FD),
{error, ?F("Unexpected end of file at line ~w", [Lno])};
fload(FD, server_redirect, GC, C, Cs, Lno, Chars, RedirMap) ->
%%?Debug("Chars: ~s", [Chars]),
Next = io:get_line(FD, ''),
Toks = toks(Lno, Chars),
case Toks of
[] ->
fload(FD, server_redirect, GC, C, Cs, Lno+1, Next, RedirMap);
[Path, '=', URL] ->
try yaws_api:parse_url(URL, sloppy) of
U when is_record(U, url) ->
fload(FD, server_redirect, GC, C, Cs, Lno+1, Next,
[{Path, U, append}|RedirMap])
catch _:_ ->
{error, ?F("bad redir ~p at line ~w", [URL, Lno])}
end;
[Path, '=', '=', URL] ->
try yaws_api:parse_url(URL, sloppy) of
U when is_record(U, url) ->
fload(FD, server_redirect, GC, C, Cs, Lno+1, Next,
[{Path, U, noappend}|RedirMap])
catch _:_ ->
{error, ?F("Bad redir ~p at line ~w", [URL, Lno])}
end;
['<', "/redirect", '>'] ->
C2 = C#sconf{redirect_map = lists:reverse(RedirMap)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, server_deflate, _GC, _C, _Cs, Lno, eof, _Deflate) ->
file:close(FD),
{error, ?F("Unexpected end of file at line ~w", [Lno])};
fload(FD, server_deflate, GC, C, Cs, Lno, Chars, Deflate) ->
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, Deflate);
["min_compress_size", '=', CSize] ->
case (catch list_to_integer(CSize)) of
I when is_integer(I), I > 0 ->
D2 = Deflate#deflate{min_compress_size=I},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
_ when CSize == "nolimit" ->
D2 = Deflate#deflate{min_compress_size=nolimit},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
_ ->
{error, ?F("Expect integer > 0 at line ~w", [Lno])}
end;
["mime_types", '=' | MimeTypes] ->
case parse_compressible_mime_types(MimeTypes,
Deflate#deflate.mime_types) of
{ok, L} ->
D2 = Deflate#deflate{mime_types=L},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
["compression_level", '=', CLevel] ->
L = try
list_to_integer(CLevel)
catch error:badarg ->
list_to_atom(CLevel)
end,
if
L =:= none; L =:= default;
L =:= best_compression; L =:= best_speed ->
D2 = Deflate#deflate{compression_level=L},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
is_integer(L), L >= 0, L =< 9 ->
D2 = Deflate#deflate{compression_level=L},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
true ->
{error, ?F("Bad compression level at line ~w", [Lno])}
end;
["window_size", '=', WSize] ->
case (catch list_to_integer(WSize)) of
I when is_integer(I), I > 8, I < 16 ->
D2 = Deflate#deflate{window_size=I * -1},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
_ ->
{error,
?F("Expect integer between 9..15 at line ~w",
[Lno])}
end;
["mem_level", '=', MLevel] ->
case (catch list_to_integer(MLevel)) of
I when is_integer(I), I >= 1, I =< 9 ->
D2 = Deflate#deflate{mem_level=I},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
_ ->
{error, ?F("Expect integer between 1..9 at line ~w", [Lno])}
end;
["strategy", '=', Strategy] ->
if
Strategy =:= "default";
Strategy =:= "filtered";
Strategy =:= "huffman_only" ->
D2 = Deflate#deflate{strategy=list_to_atom(Strategy)},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
true ->
{error,
?F("Unknown strategy ~p at line ~w", [Strategy, Lno])}
end;
["use_gzip_static", '=', Bool] ->
case is_bool(Bool) of
{true, Val} ->
D2 = Deflate#deflate{use_gzip_static=Val},
fload(FD, server_deflate, GC, C, Cs, Lno+1, Next, D2);
false ->
{error, ?F("Expect true|false at line ~w", [Lno])}
end;
['<', "/deflate", '>'] ->
D2 = case Deflate#deflate.mime_types of
[] ->
Deflate#deflate{
mime_types = ?DEFAULT_COMPRESSIBLE_MIME_TYPES
};
_ ->
Deflate
end,
C2 = C#sconf{deflate_options = D2},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end;
fload(FD, extra_cgi_vars, _GC, _C, _Cs, Lno, eof, _EVars) ->
file:close(FD),
{error, ?F("Unexpected end of file at line ~w", [Lno])};
fload(FD, extra_cgi_vars, GC, C, Cs, Lno, Chars, EVars = {Dir, Vars}) ->
Next = io:get_line(FD, ''),
case toks(Lno, Chars) of
[] ->
fload(FD, extra_cgi_vars, GC, C, Cs, Lno+1, Next, EVars);
[Var, '=', Val] ->
fload(FD, extra_cgi_vars, GC, C, Cs, Lno+1, Next,
{Dir, [{Var, Val} | Vars]});
['<', "/extra_cgi_vars", '>'] ->
C2 = C#sconf{extra_cgi_vars = [EVars | C#sconf.extra_cgi_vars]},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])};
Err ->
Err
end.
is_bool("true") ->
{true, true};
is_bool("false") ->
{true, false};
is_bool(_) ->
false.
warn_dir(Type, Dir) ->
case is_dir(Dir) of
true ->
true;
false ->
error_logger:format("Config Warning: Directory ~s "
"for ~s doesn't exist~n",
[Dir, Type]),
false
end.
is_dir(Val) ->
case file:read_file_info(Val) of
{ok, FI} when FI#file_info.type == directory ->
true;
_ ->
false
end.
is_file(Val) ->
case file:read_file_info(Val) of
{ok, FI} when FI#file_info.type == regular ->
true;
_ ->
false
end.
%% tokenizer
toks(Lno, Chars) ->
toks(Lno, Chars, free, [], []). % two accumulators
toks(Lno, [$#|_T], Mode, Ack, Tack) ->
toks(Lno, [], Mode, Ack, Tack);
toks(Lno, [H|T], free, Ack, Tack) ->
%%?Debug("Char=~p", [H]),
case {is_quote(H), is_string_char([H|T]),is_special(H), yaws:is_space(H)} of
{_,_, _, true} ->
toks(Lno, T, free, Ack, Tack);
{_,_, true, _} ->
toks(Lno, T, free, [], [list_to_atom([H]) | Tack]);
{_,true, _,_} ->
toks(Lno, T, string, [H], Tack);
{_,utf8, _,_} ->
toks(Lno, tl(T), string, [H, hd(T)], Tack);
{true,_, _,_} ->
toks(Lno, T, quote, [], Tack);
{false, false, false, false} ->
{error, ?F("Unexpected character <~p / ~c> at line ~w",
[H,H, Lno])}
end;
toks(Lno, [C|T], string, Ack, Tack) ->
case {is_backquote(C), is_quote(C), is_string_char([C|T]), is_special(C),
yaws:is_space(C)} of
{true, _, _, _,_} ->
toks(Lno, T, [backquote,string], Ack, Tack);
{_, _, true, _,_} ->
toks(Lno, T, string, [C|Ack], Tack);
{_, _, utf8, _,_} ->
toks(Lno, tl(T), string, [C, hd(T)|Ack], Tack);
{_, _, _, true, _} ->
toks(Lno, T, free, [], [list_to_atom([C]),lists:reverse(Ack)|Tack]);
{_, true, _, _, _} ->
toks(Lno, T, quote, [], [lists:reverse(Ack)|Tack]);
{_, _, _, _, true} ->
toks(Lno, T, free, [], [lists:reverse(Ack)|Tack]);
{false, false, false, false, false} ->
{error, ?F("Unexpected character <~p / ~c> at line ~w",
[C, C, Lno])}
end;
toks(Lno, [C|T], quote, Ack, Tack) ->
case {is_quote(C), is_backquote(C)} of
{true, _} ->
toks(Lno, T, free, [], [lists:reverse(Ack)|Tack]);
{_, true} ->
toks(Lno, T, [backquote,quote], [C|Ack], Tack);
{false, false} ->
toks(Lno, T, quote, [C|Ack], Tack)
end;
toks(Lno, [C|T], [backquote,Mode], Ack, Tack) ->
toks(Lno, T, Mode, [C|Ack], Tack);
toks(_Lno, [], string, Ack, Tack) ->
lists:reverse([lists:reverse(Ack) | Tack]);
toks(_Lno, [], free, _,Tack) ->
lists:reverse(Tack).
is_quote(34) -> true ; %% $" but emacs mode can't handle it
is_quote(_) -> false.
is_backquote($\\) -> true ;
is_backquote(_) -> false.
is_string_char([C|T]) ->
if
$a =< C, C =< $z ->
true;
$A =< C, C =< $Z ->
true;
$0 =< C, C =< $9 ->
true;
C == 195 , T /= [] ->
%% FIXME check that [C, hd(T)] really is a char ?? how
utf8;
true ->
lists:member(C, [$., $/, $:, $_, $-, $+, $~, $@, $*])
end.
is_special(C) ->
lists:member(C, [$=, $[, $], ${, $}, $, ,$<, $>, $,]).
%% parse the argument string PLString which can either be the undefined
%% atom or a proplist. Currently the only supported keys are
%% fullsweep_after, min_heap_size, and min_bin_vheap_size. Any other
%% key/values are ignored.
parse_process_options(PLString) ->
case erl_scan:string(PLString ++ ".") of
{ok, PLTokens, _} ->
case erl_parse:parse_term(PLTokens) of
{ok, undefined} ->
{ok, []};
{ok, []} ->
{ok, []};
{ok, [Hd|_Tl]=PList} when is_atom(Hd); is_tuple(Hd) ->
%% create new safe proplist of desired options
{ok, proplists_int_copy([], PList, [fullsweep_after,
min_heap_size,
min_bin_vheap_size])};
_ ->
{error, "Expect undefined or proplist"}
end;
_ ->
{error, "Expect undefined or proplist"}
end.
%% copy proplist integer values for the given keys from the
%% Src proplist to the Dest proplist. Ignored keys that are not
%% found or have non-integer values. Returns the new Dest proplist.
proplists_int_copy(Dest, _Src, []) ->
Dest;
proplists_int_copy(Dest, Src, [Key|NextKeys]) ->
case proplists:get_value(Key, Src) of
Val when is_integer(Val) ->
proplists_int_copy([{Key, Val}|Dest], Src, NextKeys);
_ ->
proplists_int_copy(Dest, Src, NextKeys)
end.
parse_soap_srv_mods(['<', Module, ',' , Handler, ',', WsdlFile, '>' | Tail],
Ack) ->
case is_file(WsdlFile) of
true ->
S = { {list_to_atom(Module), list_to_atom(Handler)}, WsdlFile},
parse_soap_srv_mods(Tail, [S |Ack]);
false ->
{error, ?F("Bad wsdl file ~p", [WsdlFile])}
end;
parse_soap_srv_mods(['<', Module, ',' , Handler, ',', WsdlFile, ',',
Prefix, '>' | Tail], Ack) ->
case is_file(WsdlFile) of
true ->
S = { {list_to_atom(Module), list_to_atom(Handler)},
WsdlFile, Prefix},
parse_soap_srv_mods(Tail, [S |Ack]);
false ->
{error, ?F("Bad wsdl file ~p", [WsdlFile])}
end;
parse_soap_srv_mods([ SoapSrvMod | _Tail], _Ack) ->
{error, ?F("Bad soap_srv_mods syntax: ~p", [SoapSrvMod])};
parse_soap_srv_mods([], Ack) ->
{ok, Ack}.
parse_appmods(['<', PathElem, ',' , AppMod, '>' | Tail], Ack) ->
S = {PathElem , list_to_atom(AppMod)},
parse_appmods(Tail, [S |Ack]);
parse_appmods(['<', PathElem, ',' , AppMod, "exclude_paths" |Tail], Ack)->
Paths = lists:takewhile(fun(X) -> X /= '>' end,
Tail),
Tail2 = lists:dropwhile(fun(X) -> X /= '>' end,
Tail),
Tail3 = tl(Tail2),
S = {PathElem , list_to_atom(AppMod), lists:map(
fun(Str) ->
string:tokens(Str, "/")
end, Paths)},
parse_appmods(Tail3, [S |Ack]);
parse_appmods([AppMod | Tail], Ack) ->
%% just some simpleminded test to catch syntax errors in the config
case AppMod of
[Char] ->
case is_special(Char) of
true ->
{error, "Bad appmod syntax"};
false ->
S = {AppMod, list_to_atom(AppMod)},
parse_appmods(Tail, [S | Ack])
end;
_ ->
S = {AppMod, list_to_atom(AppMod)},
parse_appmods(Tail, [S | Ack])
end;
parse_appmods([], Ack) ->
{ok, Ack}.
parse_revproxy([Prefix, Url]) ->
parse_revproxy_url(Prefix, Url);
parse_revproxy([Prefix, Url, "intercept_mod", InterceptMod]) ->
case parse_revproxy_url(Prefix, Url) of
{ok, RP} ->
{ok, RP#proxy_cfg{intercept_mod = list_to_atom(InterceptMod)}};
Error ->
Error
end;
parse_revproxy(_Other) ->
{error, syntax}.
parse_revproxy_url(Prefix, Url) ->
case (catch yaws_api:parse_url(Url)) of
{'EXIT', _} ->
{error, url};
URL when URL#url.path == "/" ->
P = case lists:reverse(Prefix) of
[$/|_Tail] ->
Prefix;
Other ->
lists:reverse(Other)
end,
{ok, #proxy_cfg{prefix=P, url=URL}};
_URL ->
{error, "Can't revproxy to a URL with a path "}
end.
parse_expires(['<', MimeType, ',' , Expire, '>' | Tail], Ack) ->
{Type, Value} =
case string:tokens(Expire, "+") of
[Secs] ->
{access, (catch list_to_integer(Secs))};
["access", Secs] ->
{access, (catch list_to_integer(Secs))};
["modify", Secs] ->
{modify, (catch list_to_integer(Secs))};
_ ->
{error, "Bad expires syntax"}
end,
if
Type =:= error ->
{Type, Value};
not is_integer(Value) ->
{error, "Bad expires syntax"};
true ->
E = {MimeType, Type, Value},
parse_expires(Tail, [E |Ack])
end;
parse_expires([], Ack)->
{ok, Ack}.
parse_phpmod(['<', "cgi", ',', DefaultPhpPath, '>'], DefaultPhpPath) ->
{ok, {cgi, DefaultPhpPath}};
parse_phpmod(['<', "cgi", ',', PhpPath, '>'], _) ->
case is_file(PhpPath) of
true ->
{ok, {cgi, PhpPath}};
false ->
{error, ?F("~s is not a regular file", [PhpPath])}
end;
parse_phpmod(['<', "fcgi", ',', HostPortSpec, '>'], _) ->
case string_to_host_and_port(HostPortSpec) of
{ok, Host, Port} ->
{ok, {fcgi, {Host, Port}}};
{error, Reason} ->
{error, Reason}
end;
parse_phpmod(['<', "extern", ',', NodeModFunSpec, '>'], _) ->
case string_to_node_mod_fun(NodeModFunSpec) of
{ok, Node, Mod, Fun} ->
{ok, {extern, {Node,Mod,Fun}}};
{ok, Mod, Fun} ->
{ok, {extern, {Mod,Fun}}};
{error, Reason} ->
{error, Reason}
end.
parse_compressible_mime_types(_, all) ->
{ok, all};
parse_compressible_mime_types(["all"|_], _Acc) ->
{ok, all};
parse_compressible_mime_types(["*/*"|_], _Acc) ->
{ok, all};
parse_compressible_mime_types(["defaults"|Rest], Acc) ->
parse_compressible_mime_types(Rest, ?DEFAULT_COMPRESSIBLE_MIME_TYPES++Acc);
parse_compressible_mime_types([',' | Rest], Acc) ->
parse_compressible_mime_types(Rest, Acc);
parse_compressible_mime_types([MimeType | Rest], Acc) ->
Res = re:run(MimeType, "^([-\\w\+]+)/([-\\w\+\.]+|\\*)$",
[{capture, all_but_first, list}]),
case Res of
{match, [Type,"*"]} ->
parse_compressible_mime_types(Rest, [{Type, all}|Acc]);
{match, [Type,SubType]} ->
parse_compressible_mime_types(Rest, [{Type, SubType}|Acc]);
nomatch ->
{error, "Invalid MimeType"}
end;
parse_compressible_mime_types([], Acc) ->
{ok, Acc}.
parse_index_files([]) ->
ok;
parse_index_files([Idx|Rest]) ->
case Idx of
[$/|_] when Rest /= [] ->
{error, "Only the last index should be absolute"};
_ ->
parse_index_files(Rest)
end.
is_valid_mime_type(MimeType) ->
case re:run(MimeType, "^[-\\w\+]+/[-\\w\+\.]+$", [{capture, none}]) of
match -> true;
nomatch -> false
end.
parse_mime_types(['<', MimeType, ',' | Tail], Acc0) ->
Exts = lists:takewhile(fun(X) -> X /= '>' end, Tail),
[_|Tail2] = lists:dropwhile(fun(X) -> X /= '>' end, Tail),
Acc1 = lists:foldl(fun(E, Acc) ->
lists:keystore(E, 1, Acc, {E, MimeType})
end, Acc0, Exts),
case is_valid_mime_type(MimeType) of
true -> parse_mime_types(Tail2, Acc1);
false -> {error, ?F("Invalid mime-type '~p'", [MimeType])}
end;
parse_mime_types([], Acc)->
{ok, lists:reverse(Acc)};
parse_mime_types(_, _) ->
{error, "Unexpected tokens"}.
parse_charsets(['<', Charset, ',' | Tail], Acc0) ->
Exts = lists:takewhile(fun(X) -> X /= '>' end, Tail),
[_|Tail2] = lists:dropwhile(fun(X) -> X /= '>' end, Tail),
Acc1 = lists:foldl(fun(E, Acc) ->
lists:keystore(E, 1, Acc, {E, Charset})
end, Acc0, Exts),
parse_charsets(Tail2, Acc1);
parse_charsets([], Acc)->
{ok, lists:reverse(Acc)};
parse_charsets(_, _) ->
{error, "Unexpected tokens"}.
parse_mime_types_info(Directive, Type, undefined, undefined) ->
parse_mime_types_info(Directive, Type, #mime_types_info{});
parse_mime_types_info(Directive, Type, undefined, DefaultInfo) ->
parse_mime_types_info(Directive, Type, DefaultInfo);
parse_mime_types_info(Directive, Type, Info, _) ->
parse_mime_types_info(Directive, Type, Info).
parse_mime_types_info(default_type, Type, Info) ->
case is_valid_mime_type(Type) of
true -> {ok, Info#mime_types_info{default_type=Type}};
false -> {error, ?F("Invalid mime-type '~p'", [Type])}
end;
parse_mime_types_info(default_charset, Charset, Info) ->
{ok, Info#mime_types_info{default_charset=Charset}};
parse_mime_types_info(mime_types_file, File, Info) ->
{ok, Info#mime_types_info{mime_types_file=File}};
parse_mime_types_info(add_types, NewTypes, Info) ->
case parse_mime_types(NewTypes, Info#mime_types_info.types) of
{ok, Types} -> {ok, Info#mime_types_info{types=Types}};
Error -> Error
end;
parse_mime_types_info(add_charsets, NewCharsets, Info) ->
case parse_charsets(NewCharsets, Info#mime_types_info.charsets) of
{ok, Charsets} -> {ok, Info#mime_types_info{charsets=Charsets}};
Error -> Error
end.
ssl_start() ->
case catch ssl:start() of
ok ->
ok;
{error,{already_started,ssl}} ->
ok;
Err ->
error_logger:format("Failed to start ssl: ~p~n", [Err])
end.
%% search for an SC within Pairs that have the same, listen,port,ssl,severname
%% Return {Pid, SC} or false
%% Pairs is the pairs in yaws_server #state{}
search_sconf(NewSC, Pairs) ->
case lists:zf(
fun({Pid, Scs = [SC|_]}) ->
case same_virt_srv(NewSC, SC) of
true ->
case lists:keysearch(NewSC#sconf.servername,
#sconf.servername, Scs) of
{value, Found} ->
{true, {Pid, Found, Scs}};
false ->
false
end;
false ->
false
end
end, Pairs) of
[] ->
false;
[{Pid, Found, Scs}] ->
{Pid, Found, Scs};
_Other ->
error_logger:format("Fatal error, no two sconfs should "
" ever be considered equal ..",[]),
erlang:error(fatal_conf)
end.
%% find the group a new SC would belong to
search_group(SC, Pairs) ->
Fun = fun({Pid, [S|Ss]}) ->
case same_virt_srv(S, SC) of
true ->
{true, {Pid, [S|Ss]}};
false ->
false
end
end,
lists:zf(Fun, Pairs).
%% Return a new Pairs list with one SC updated
update_sconf(NewSC, Pairs) ->
lists:map(
fun({Pid, Scs}) ->
{Pid,
lists:map(fun(SC) ->
case same_sconf(SC, NewSC) of
true ->
NewSC;
false ->
SC
end
end, Scs)
}
end, Pairs).
%% return a new pairs list with SC removed
delete_sconf(OldSc, Pairs) ->
lists:zf(
fun({Pid, Scs}) ->
case same_virt_srv(hd(Scs), OldSc) of
true ->
L2 = lists:keydelete(OldSc#sconf.servername,
#sconf.servername, Scs),
{true, {Pid, L2}};
false ->
{true, {Pid, Scs}}
end
end, Pairs).
same_virt_srv(S, NewSc) when
S#sconf.listen == NewSc#sconf.listen,
S#sconf.port == NewSc#sconf.port,
S#sconf.ssl == NewSc#sconf.ssl ->
true;
same_virt_srv(_,_) ->
false.
same_sconf(S, NewSc) ->
same_virt_srv(S, NewSc) andalso
S#sconf.servername == NewSc#sconf.servername.
eq_sconfs(S1,S2) ->
(S1#sconf.port == S2#sconf.port andalso
S1#sconf.flags == S2#sconf.flags andalso
S1#sconf.redirect_map == S2#sconf.redirect_map andalso
S1#sconf.rhost == S2#sconf.rhost andalso
S1#sconf.rmethod == S2#sconf.rmethod andalso
S1#sconf.docroot == S2#sconf.docroot andalso
S1#sconf.xtra_docroots == S2#sconf.xtra_docroots andalso
S1#sconf.listen == S2#sconf.listen andalso
S1#sconf.servername == S2#sconf.servername andalso
S1#sconf.yaws == S2#sconf.yaws andalso
S1#sconf.ssl == S2#sconf.ssl andalso
S1#sconf.authdirs == S2#sconf.authdirs andalso
S1#sconf.partial_post_size == S2#sconf.partial_post_size andalso
S1#sconf.appmods == S2#sconf.appmods andalso
S1#sconf.expires == S2#sconf.expires andalso
S1#sconf.errormod_401 == S2#sconf.errormod_401 andalso
S1#sconf.errormod_404 == S2#sconf.errormod_404 andalso
S1#sconf.errormod_crash == S2#sconf.errormod_crash andalso
S1#sconf.arg_rewrite_mod == S2#sconf.arg_rewrite_mod andalso
S1#sconf.logger_mod == S2#sconf.logger_mod andalso
S1#sconf.opaque == S2#sconf.opaque andalso
S1#sconf.start_mod == S2#sconf.start_mod andalso
S1#sconf.allowed_scripts == S2#sconf.allowed_scripts andalso
S1#sconf.tilde_allowed_scripts == S2#sconf.tilde_allowed_scripts andalso
S1#sconf.index_files == S2#sconf.index_files andalso
S1#sconf.revproxy == S2#sconf.revproxy andalso
S1#sconf.soptions == S2#sconf.soptions andalso
S1#sconf.extra_cgi_vars == S2#sconf.extra_cgi_vars andalso
S1#sconf.stats == S2#sconf.stats andalso
S1#sconf.fcgi_app_server == S2#sconf.fcgi_app_server andalso
S1#sconf.php_handler == S2#sconf.php_handler andalso
S1#sconf.shaper == S2#sconf.shaper andalso
S1#sconf.deflate_options == S2#sconf.deflate_options andalso
S1#sconf.mime_types_info == S2#sconf.mime_types_info).
%% This the version of setconf that perform a
%% soft reconfig, it requires the args to be checked.
soft_setconf(GC, Groups, OLDGC, OldGroups) ->
if
GC /= OLDGC ->
yaws_trace:setup(GC),
update_gconf(GC);
true ->
ok
end,
Grps = load_mime_types_module(GC, Groups),
Rems = remove_old_scs(lists:flatten(OldGroups), Grps),
Adds = soft_setconf_scs(lists:flatten(Grps), OldGroups),
lists:foreach(
fun({delete_sconf, SC}) ->
delete_sconf(SC);
({add_sconf, SC}) ->
add_sconf(SC);
({update_sconf, SC}) ->
update_sconf(SC)
end, Rems ++ Adds).
hard_setconf(GC, Groups) ->
case gen_server:call(yaws_server,{setconf, GC, Groups},infinity) of
ok ->
yaws_log:setup(GC, Groups),
yaws_trace:setup(GC);
E ->
erlang:error(E)
end.
remove_old_scs([Sc|Scs], NewGroups) ->
case find_group(Sc, NewGroups) of
false ->
[{delete_sconf, Sc} |remove_old_scs(Scs, NewGroups)];
{true, G} ->
case find_sc(Sc, G) of
false ->
[{delete_sconf, Sc} | remove_old_scs(Scs, NewGroups)];
_ ->
remove_old_scs(Scs, NewGroups)
end
end;
remove_old_scs([],_) ->
[].
soft_setconf_scs([Sc|Scs], OldGroups) ->
case find_group(Sc, OldGroups) of
false ->
[{add_sconf,Sc} | soft_setconf_scs(Scs, OldGroups)];
{true, G} ->
case find_sc(Sc, G) of
false ->
[{add_sconf, Sc} | soft_setconf_scs(Scs, OldGroups)];
{true, _OldSc} ->
[{update_sconf,Sc} | soft_setconf_scs(Scs, OldGroups)]
end
end;
soft_setconf_scs([],_) ->
[].
%% checking code
can_hard_gc(New, Old) ->
if
Old == undefined ->
true;
New#gconf.yaws_dir == Old#gconf.yaws_dir,
New#gconf.runmods == Old#gconf.runmods,
New#gconf.logdir == Old#gconf.logdir ->
true;
true ->
false
end.
can_soft_setconf(NEWGC, NewGroups, OLDGC, OldGroups) ->
can_soft_gc(NEWGC, OLDGC) andalso
can_soft_sconf(lists:flatten(NewGroups), OldGroups).
can_soft_gc(G1, G2) ->
if
G1#gconf.flags == G2#gconf.flags,
G1#gconf.logdir == G2#gconf.logdir,
G1#gconf.log_wrap_size == G2#gconf.log_wrap_size,
G1#gconf.id == G2#gconf.id ->
true;
true ->
false
end.
can_soft_sconf([Sc|Scs], OldGroups) ->
case find_group(Sc, OldGroups) of
false ->
can_soft_sconf(Scs, OldGroups);
{true, G} ->
case find_sc(Sc, G) of
false ->
can_soft_sconf(Scs, OldGroups);
{true, Old} when Old#sconf.start_mod /= Sc#sconf.start_mod ->
false;
{true, Old} ->
case
{proplists:get_value(listen_opts, Old#sconf.soptions),
proplists:get_value(listen_opts, Sc#sconf.soptions)} of
{Opts, Opts} ->
can_soft_sconf(Scs, OldGroups);
_ ->
false
end
end
end;
can_soft_sconf([], _) ->
true.
find_group(SC, [G|Gs]) ->
case same_virt_srv(SC, hd(G)) of
true ->
{true, G};
false ->
find_group(SC, Gs)
end;
find_group(_,[]) ->
false.
find_sc(SC, [S|Ss]) ->
if SC#sconf.servername == S#sconf.servername ->
{true, S};
true ->
find_sc(SC, Ss)
end;
find_sc(_SC,[]) ->
false.
verify_upgrade_args(GC, Groups0) when is_record(GC, gconf) ->
case is_groups(Groups0) of
true ->
%% Embedded code may give appmods as a list of strings, or
%% appmods can be {StringPathElem,ModAtom} or
%% {StringPathElem,ModAtom,ExcludePathsList} tuples. Handle
%% all possible variants here.
Groups = yaws:deepmap(
fun(SC) ->
SC#sconf{appmods =
lists:map(
fun({PE, Mod}) ->
{PE, Mod};
({PE,Mod,Ex}) ->
{PE,Mod,Ex};
(AM) when is_list(AM) ->
{AM,list_to_atom(AM)};
(AM) when is_atom(AM) ->
{atom_to_list(AM), AM}
end,
SC#sconf.appmods)}
end, Groups0),
{GC, Groups};
_ ->
erlang:error(badgroups)
end.
%% verify args to setconf
is_groups([H|T]) ->
case is_list_of_scs(H) of
true ->
is_groups(T);
false ->
false
end;
is_groups([]) ->
true.
is_list_of_scs([H|T]) when is_record(H, sconf) ->
is_list_of_scs(T);
is_list_of_scs([]) ->
true;
is_list_of_scs(_) ->
false.
add_sconf(SC) ->
ok= gen_server:call(yaws_server, {add_sconf, SC}, infinity),
ok = yaws_log:add_sconf(SC).
update_sconf(SC) ->
ok = gen_server:call(yaws_server, {update_sconf, SC}, infinity).
delete_sconf(SC) ->
ok = gen_server:call(yaws_server, {delete_sconf, SC}, infinity),
ok = yaws_log:del_sconf(SC).
update_gconf(GC) ->
ok = gen_server:call(yaws_server, {update_gconf, GC}, infinity).
parse_auth_ips([], Result) ->
Result;
parse_auth_ips([Str|Rest], Result) ->
try
parse_auth_ips(Rest, [yaws:parse_ipmask(Str)|Result])
catch
_:_ -> parse_auth_ips(Rest, Result)
end.
subconfigdir_fold(_File, {error, _Err}=Acc) ->
Acc;
subconfigdir_fold(File, {ok, GCp, Csp}=Acc) ->
case is_file(File) of
true ->
error_logger:info_msg("Yaws: Using subconfig file ~s~n", [File]),
case file:open(File, [read]) of
{ok, FD1} ->
R = (catch fload(FD1,
globals,
GCp,
undefined,
Csp,
1,
io:get_line(FD1, ''))),
?Debug("FLOAD: ~p", [R]),
R;
_ ->
{error, "Can't open config file " ++ File}
end;
false ->
%% Ignore subdirectories
Acc
end.
str2term(Str0) ->
Str=Str0++".",
{ok,Tokens,_EndLine} = erl_scan:string(Str),
{ok,AbsForm} = erl_parse:parse_exprs(Tokens),
{value,Value,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
Value.
check_ciphers([], _) ->
ok;
check_ciphers([Spec|Specs], L) ->
case lists:member(Spec, L) of
true ->
check_ciphers(Specs, L);
false ->
{error, ?F("Bad cipherspec ~p",[Spec])}
end;
check_ciphers(X,_) ->
{error, ?F("Bad cipherspec ~p",[X])}.
io_get_line(FD, Prompt, Acc) ->
Next = io:get_line(FD, Prompt),
if
is_list(Next) ->
case lists:reverse(Next) of
[$\n, $\\ |More] ->
io_get_line(FD, Prompt, Acc ++ lists:reverse(More));
_ ->
Acc ++ Next
end;
true ->
Next
end.
Jump to Line
Something went wrong with that request. Please try again.