%%%----------------------------------------------------------------------
%%% 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 any 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;
        Err ->
            {error, ?F("Can't open config file ~s: ~p", [File, Err])}
    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_sconf([], 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) ->
    AbsDocRoot = filename:absname(DocRoot),
    case is_dir(AbsDocRoot) of
        true ->
            set_server(#sconf{port=Port,listen={127,0,0,1},docroot=AbsDocRoot});
        false ->
            throw({error, ?F("Invalid docroot: directory ~s does not exist",
                             [AbsDocRoot])})
    end.


yaws_dir() ->
    yaws:get_app_dir().

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;
                        Err ->
                            {error, ?F("Can't open config file ~s:~p",
                                       [File,Err])}
                    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, '=', '=' | Rest] ->
            %% "Normalize" Path
            Path1 = filename:join([yaws_api:path_norm(Path)]),
            case parse_redirect(Path1, Rest, noappend, Lno) of
                {error, Str} ->
                    {error, Str};
                Redir ->
                    fload(FD, server_redirect, GC, C, Cs, Lno+1, Next,
                          [Redir|RedirMap])
            end;
        [Path, '=' | Rest] ->
            %% "Normalize" Path
            Path1 = filename:join([yaws_api:path_norm(Path)]),
            case parse_redirect(Path1, Rest, append, Lno) of
                {error, Str} ->
                    {error, Str};
                Redir ->
                    fload(FD, server_redirect, GC, C, Cs, Lno+1, Next,
                          [Redir|RedirMap])
            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.


parse_redirect(Path, [Code, URL], Mode, Lno) ->
    case catch list_to_integer(Code) of
        I when is_integer(I), I >= 300, I =< 399 ->
            try yaws_api:parse_url(URL, sloppy) of
                U when is_record(U, url) ->
                    {Path, I, U, Mode}
            catch _:_ ->
                    {error, ?F("Bad redirect URL ~p at line ~w", [URL, Lno])}
            end;
        I when is_integer(I), I >= 100, I =< 599 ->
            %% Only relative path are authorized here
            try yaws_api:parse_url(URL, sloppy) of
                #url{scheme=undefined, host=[], port=undefined, path=P} ->
                    {Path, I, P, Mode};
                #url{} ->
                    {error, ?F("Bad redirect rule at line ~w: "
                               " Absolute URL is forbidden here", [Lno])}
            catch _:_ ->
                    {error, ?F("Bad redirect URL ~p at line ~w", [URL, Lno])}
            end;
        _ ->
            {error, ?F("Bad status code ~p at line ~w", [Code, Lno])}
    end;
parse_redirect(Path, [CodeOrUrl], Mode, Lno) ->
    case catch list_to_integer(CodeOrUrl) of
        I when is_integer(I), I >= 300, I =< 399 ->
            {error, ?F("Bad redirect rule at line ~w: "
                       "URL to redirect to is missing ", [Lno])};
        I when is_integer(I), I >= 100, I =< 599 ->
            {Path, I, undefined, Mode};
        I when is_integer(I) ->
            {error, ?F("Bad status code ~p at line ~w", [CodeOrUrl, Lno])};
        _ ->
            try yaws_api:parse_url(CodeOrUrl, sloppy) of
                #url{}=U ->
                    {Path, 302, U, Mode}
            catch _:_ ->
                    {error, ?F("Bad redirect URL ~p at line ~w",
                               [CodeOrUrl, Lno])}
            end
    end;
parse_redirect(_Path, _, _Mode, Lno) ->
    {error, ?F("Bad redirect rule at line ~w", [Lno])}.



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.
