1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
21
-export([is_directory/1, is_file/1, make_integer/1, clean/1,
22
custom_clean/3, check_enum/2]).
24
%% Application internal API
25
-export([load/1, load/2, load_mime_types/1, store/1, store/2,
26
remove/1, remove_all/1, config/1]).
28
-define(VMODULE,"CONF").
29
-include("httpd.hrl").
31
%%%=========================================================================
33
%%%=========================================================================
34
%%-------------------------------------------------------------------------
35
%% is_directory(FilePath) -> Result
36
%% FilePath = string()
37
%% Result = {ok,Directory} | {error,Reason}
38
%% Directory = string()
39
%% Reason = string() | enoent | eaccess | enotdir | FileInfo
40
%% FileInfo = File info record
42
%% Description: Checks if FilePath is a directory in which case it is
44
%%-------------------------------------------------------------------------
45
is_directory(Directory) ->
46
case file:read_file_info(Directory) of
48
#file_info{type = Type, access = Access} = FileInfo,
49
is_directory(Type,Access,FileInfo,Directory);
53
is_directory(directory,read,_FileInfo,Directory) ->
55
is_directory(directory,read_write,_FileInfo,Directory) ->
57
is_directory(_Type,_Access,FileInfo,_Directory) ->
59
%%-------------------------------------------------------------------------
60
%% is_file(FilePath) -> Result
61
%% FilePath = string()
62
%% Result = {ok,File} | {error,Reason}
64
%% Reason = string() | enoent | eaccess | enotdir | FileInfo
65
%% FileInfo = File info record
67
%% Description: Checks if FilePath is a regular file in which case it
69
%%-------------------------------------------------------------------------
71
case file:read_file_info(File) of
73
#file_info{type = Type, access = Access} = FileInfo,
74
is_file(Type,Access,FileInfo,File);
78
is_file(regular,read,_FileInfo,File) ->
80
is_file(regular,read_write,_FileInfo,File) ->
82
is_file(_Type,_Access,FileInfo,_File) ->
84
%%-------------------------------------------------------------------------
85
%% make_integer(String) -> Result
87
%% Result = {ok,integer()} | {error,nomatch}
89
%% Description: make_integer/1 returns an integer representation of String.
90
%%-------------------------------------------------------------------------
91
make_integer(String) ->
92
case regexp:match(clean(String),"[0-9]+") of
94
{ok, list_to_integer(clean(String))};
98
%%-------------------------------------------------------------------------
99
%% clean(String) -> Stripped
100
%% String = Stripped = string()
102
%% Description:clean/1 removes leading and/or trailing white spaces
104
%%-------------------------------------------------------------------------
106
{ok,CleanedString,_} =
107
regexp:gsub(String, "^[ \t\n\r\f]*|[ \t\n\r\f]*\$",""),
109
%%-------------------------------------------------------------------------
110
%% custom_clean(String,Before,After) -> Stripped
111
%% Before = After = regexp()
112
%% String = Stripped = string()
114
%% Description: custom_clean/3 removes leading and/or trailing white
115
%% spaces and custom characters from String.
116
%%-------------------------------------------------------------------------
117
custom_clean(String,MoreBefore,MoreAfter) ->
118
{ok,CleanedString,_} = regexp:gsub(String,"^[ \t\n\r\f"++MoreBefore++
119
"]*|[ \t\n\r\f"++MoreAfter++"]*\$",""),
121
%%-------------------------------------------------------------------------
122
%% check_enum(EnumString,ValidEnumStrings) -> Result
123
%% EnumString = string()
124
%% ValidEnumStrings = [string()]
125
%% Result = {ok,atom()} | {error,not_valid}
127
%% Description: check_enum/2 checks if EnumString is a valid
128
%% enumeration of ValidEnumStrings in which case it is returned as an
130
%%-------------------------------------------------------------------------
131
check_enum(_Enum,[]) ->
133
check_enum(Enum,[Enum|_Rest]) ->
134
{ok, list_to_atom(Enum)};
135
check_enum(Enum, [_NotValid|Rest]) ->
136
check_enum(Enum, Rest).
138
%%%=========================================================================
139
%%% Application internal API
140
%%%=========================================================================
141
%% The configuration data is handled in three (3) phases:
142
%% 1. Parse the config file and put all directives into a key-vale
143
%% tuple list (load/1).
144
%% 2. Traverse the key-value tuple list store it into an ETS table.
145
%% Directives depending on other directives are taken care of here
147
%% 3. Traverse the ETS table and do a complete clean-up (remove/1).
151
case read_config_file(ConfigFile) of
153
case bootstrap(Config) of
157
load_config(Config, lists:append(Modules, [?MODULE]))
160
{error, ?NICE("Error while reading config file: "++Reason)}
165
load("MaxHeaderSize " ++ MaxHeaderSize, []) ->
166
case make_integer(MaxHeaderSize) of
168
{ok, [], {max_header_size,Integer}};
170
{error, ?NICE(clean(MaxHeaderSize)++
171
" is an invalid number of MaxHeaderSize")}
173
load("MaxHeaderAction " ++ Action, []) ->
174
{ok, [], {max_header_action,list_to_atom(clean(Action))}};
175
load("MaxBodySize " ++ MaxBodySize, []) ->
176
case make_integer(MaxBodySize) of
178
{ok, [], {max_body_size,Integer}};
180
{error, ?NICE(clean(MaxBodySize)++
181
" is an invalid number of MaxBodySize")}
183
load("MaxBodyAction " ++ Action, []) ->
184
{ok, [], {max_body_action,list_to_atom(clean(Action))}};
185
load("ServerName " ++ ServerName, []) ->
186
{ok,[],{server_name,clean(ServerName)}};
187
load("SocketType " ++ SocketType, []) ->
188
case check_enum(clean(SocketType),["ssl","ip_comm"]) of
189
{ok, ValidSocketType} ->
190
{ok, [], {com_type,ValidSocketType}};
192
{error, ?NICE(clean(SocketType) ++ " is an invalid SocketType")}
194
load("Port " ++ Port, []) ->
195
case make_integer(Port) of
197
{ok, [], {port,Integer}};
199
{error, ?NICE(clean(Port)++" is an invalid Port")}
201
load("BindAddress " ++ Address, []) ->
202
%% If an ipv6 address is provided in URL-syntax strip the
203
%% url specific part e.i. "[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]"
204
%% -> "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"
205
NewAddress = string:strip(string:strip(clean(Address),
210
{ok, [], {bind_address,any}};
212
case (catch inet:getaddr(CAddress,inet6)) of
213
{ok, {0, 0, 0, 0, 0, 16#ffff, _, _}} ->
214
case inet:getaddr(CAddress, inet) of
216
{ok, [], {bind_address,IPAddr}};
218
{error, ?NICE(CAddress++" is an invalid address")}
221
{ok, [], {bind_address, IPAddr}};
223
case inet:getaddr(CAddress, inet) of
225
{ok, [], {bind_address,IPAddr}};
227
{error, ?NICE(CAddress++" is an invalid address")}
231
load("KeepAlive " ++ OnorOff, []) ->
232
case list_to_atom(clean(OnorOff)) of
234
{ok, [], {persistent_conn, false}};
236
{ok, [], {persistent_conn, true}}
238
load("MaxKeepAliveRequests " ++ MaxRequests, []) ->
239
case make_integer(MaxRequests) of
241
{ok, [], {max_keep_alive_request, Integer}};
243
{error, ?NICE(clean(MaxRequests) ++
244
" is an invalid MaxKeepAliveRequests")}
246
%% This clause is keept for backwards compability
247
load("MaxKeepAliveRequest " ++ MaxRequests, []) ->
248
case make_integer(MaxRequests) of
250
{ok, [], {max_keep_alive_request, Integer}};
252
{error, ?NICE(clean(MaxRequests) ++
253
" is an invalid MaxKeepAliveRequest")}
255
load("KeepAliveTimeout " ++ Timeout, []) ->
256
case make_integer(Timeout) of
258
{ok, [], {keep_alive_timeout, Integer*1000}};
260
{error, ?NICE(clean(Timeout)++" is an invalid KeepAliveTimeout")}
262
load("Modules " ++ Modules, []) ->
263
{ok, ModuleList} = regexp:split(Modules," "),
264
{ok, [], {modules,[list_to_atom(X) || X <- ModuleList]}};
265
load("ServerAdmin " ++ ServerAdmin, []) ->
266
{ok, [], {server_admin,clean(ServerAdmin)}};
267
load("ServerRoot " ++ ServerRoot, []) ->
268
case is_directory(clean(ServerRoot)) of
271
filename:join([clean(ServerRoot),"conf", "mime.types"]),
272
case load_mime_types(MimeTypesFile) of
273
{ok, MimeTypesList} ->
274
{ok, [], [{server_root,string:strip(Directory,right,$/)},
275
{mime_types,MimeTypesList}]};
280
{error, ?NICE(clean(ServerRoot)++" is an invalid ServerRoot")}
282
load("MaxClients " ++ MaxClients, []) ->
283
case make_integer(MaxClients) of
285
{ok, [], {max_clients,Integer}};
287
{error, ?NICE(clean(MaxClients) ++
288
" is an invalid number of MaxClients")}
290
load("DocumentRoot " ++ DocumentRoot,[]) ->
291
case is_directory(clean(DocumentRoot)) of
293
{ok, [], {document_root,string:strip(Directory,right,$/)}};
295
{error, ?NICE(clean(DocumentRoot)++"is an invalid DocumentRoot")}
297
load("DefaultType " ++ DefaultType, []) ->
298
{ok, [], {default_type,clean(DefaultType)}};
299
load("SSLCertificateFile " ++ SSLCertificateFile, []) ->
300
case is_file(clean(SSLCertificateFile)) of
302
{ok, [], {ssl_certificate_file,File}};
304
{error, ?NICE(clean(SSLCertificateFile)++
305
" is an invalid SSLCertificateFile")}
307
load("SSLCertificateKeyFile " ++ SSLCertificateKeyFile, []) ->
308
case is_file(clean(SSLCertificateKeyFile)) of
310
{ok, [], {ssl_certificate_key_file,File}};
312
{error, ?NICE(clean(SSLCertificateKeyFile)++
313
" is an invalid SSLCertificateKeyFile")}
315
load("SSLVerifyClient " ++ SSLVerifyClient, []) ->
316
case make_integer(clean(SSLVerifyClient)) of
317
{ok, Integer} when Integer >=0,Integer =< 2 ->
318
{ok, [], {ssl_verify_client,Integer}};
320
{error,?NICE(clean(SSLVerifyClient) ++
321
" is an invalid SSLVerifyClient")};
323
{error,?NICE(clean(SSLVerifyClient) ++
324
" is an invalid SSLVerifyClient")}
326
load("SSLVerifyDepth " ++ SSLVerifyDepth, []) ->
327
case make_integer(clean(SSLVerifyDepth)) of
328
{ok, Integer} when Integer > 0 ->
329
{ok, [], {ssl_verify_client_depth,Integer}};
331
{error,?NICE(clean(SSLVerifyDepth) ++
332
" is an invalid SSLVerifyDepth")};
334
{error,?NICE(clean(SSLVerifyDepth) ++
335
" is an invalid SSLVerifyDepth")}
337
load("SSLCiphers " ++ SSLCiphers, []) ->
338
{ok, [], {ssl_ciphers, clean(SSLCiphers)}};
339
load("SSLCACertificateFile " ++ SSLCACertificateFile, []) ->
340
case is_file(clean(SSLCACertificateFile)) of
342
{ok, [], {ssl_ca_certificate_file,File}};
344
{error, ?NICE(clean(SSLCACertificateFile)++
345
" is an invalid SSLCACertificateFile")}
347
load("SSLPasswordCallbackModule " ++ SSLPasswordCallbackModule, []) ->
348
{ok, [], {ssl_password_callback_module,
349
list_to_atom(clean(SSLPasswordCallbackModule))}};
350
load("SSLPasswordCallbackFunction " ++ SSLPasswordCallbackFunction, []) ->
351
{ok, [], {ssl_password_callback_function,
352
list_to_atom(clean(SSLPasswordCallbackFunction))}};
353
load("DisableChunkedTransferEncodingSend " ++ TrueOrFalse, []) ->
354
case list_to_atom(clean(TrueOrFalse)) of
356
{ok, [], {disable_chunked_transfer_encoding_send, true}};
358
{ok, [], {disable_chunked_transfer_encoding_send, false}}
362
%% load_mime_types/1 -> {ok, MimeTypes} | {error, Reason}
364
load_mime_types(MimeTypesFile) ->
365
case file:open(MimeTypesFile, read) of
367
parse_mime_types(Stream, []);
369
{error, ?NICE("Can't open " ++ MimeTypesFile)}
374
Modules = httpd_util:key1search(ConfigList, modules, []),
375
Port = httpd_util:key1search(ConfigList, port),
376
Addr = httpd_util:key1search(ConfigList,bind_address),
377
Name = httpd_util:make_name("httpd_conf",Addr,Port),
378
ConfigDB = ets:new(Name, [named_table, bag, protected]),
379
store(ConfigDB, ConfigList, lists:append(Modules,[?MODULE]),ConfigList).
381
store({mime_types,MimeTypesList},ConfigList) ->
382
Port = httpd_util:key1search(ConfigList, port),
383
Addr = httpd_util:key1search(ConfigList, bind_address),
384
Name = httpd_util:make_name("httpd_mime",Addr,Port),
385
{ok, MimeTypesDB} = store_mime_types(Name,MimeTypesList),
386
{ok, {mime_types,MimeTypesDB}};
387
store(ConfigListEntry, _ConfigList) ->
388
{ok, ConfigListEntry}.
391
remove_all(ConfigDB) ->
392
Modules = httpd_util:lookup(ConfigDB,modules,[]),
393
remove_traverse(ConfigDB, lists:append(Modules,[?MODULE])).
396
ets:delete(ConfigDB),
400
case httpd_util:lookup(ConfigDB,com_type,ip_comm) of
402
case ssl_certificate_file(ConfigDB) of
405
"Directive SSLCertificateFile "
406
"not found in the config file"};
407
SSLCertificateFile ->
410
ssl_certificate_key_file(ConfigDB)++
411
ssl_verify_client(ConfigDB)++
412
ssl_ciphers(ConfigDB)++
413
ssl_password(ConfigDB)++
414
ssl_verify_depth(ConfigDB)++
415
ssl_ca_certificate_file(ConfigDB)}
421
%%%========================================================================
422
%%% Internal functions
423
%%%========================================================================
426
{error, ?NICE("Modules must be specified in the config file")};
427
bootstrap([Line|Config]) ->
429
"Modules " ++ Modules ->
430
{ok, ModuleList} = regexp:split(Modules," "),
431
TheMods = [list_to_atom(X) || X <- ModuleList],
432
case verify_modules(TheMods) of
442
load_config(Config, Modules) ->
443
%% Create default contexts for all modules
444
Contexts = lists:duplicate(length(Modules), []),
445
load_config(Config, Modules, Contexts, []).
446
load_config([], _Modules, _Contexts, ConfigList) ->
447
case a_must(ConfigList, [server_name,port,server_root,document_root]) of
450
{missing, Directive} ->
451
{error, ?NICE(atom_to_list(Directive)++
452
" must be specified in the config file")}
454
load_config([Line|Config], Modules, Contexts, ConfigList) ->
455
case load_traverse(Line, Contexts, Modules, [], ConfigList, no) of
456
{ok, NewContexts, NewConfigList} ->
457
load_config(Config, Modules, NewContexts, NewConfigList);
463
%% This loads the config file into each module specified by Modules
464
%% Each module has its own context that is passed to and (optionally)
465
%% returned by the modules load function. The module can also return
466
%% a ConfigEntry, which will be added to the global configuration
468
%% All configuration directives are guaranteed to be passed to all
469
%% modules. Each module only implements the function clauses of
470
%% the load function for the configuration directives it supports,
471
%% it's ok if an apply returns {'EXIT', {function_clause, ..}}.
472
load_traverse(Line, [], [], _NewContexts, _ConfigList, no) ->
473
{error, ?NICE("Configuration directive not recognized: "++Line)};
474
load_traverse(_Line, [], [], NewContexts, ConfigList, yes) ->
475
{ok, lists:reverse(NewContexts), ConfigList};
476
load_traverse(Line, [Context|Contexts], [Module|Modules], NewContexts,
477
ConfigList, State) ->
478
case catch apply(Module, load, [Line, Context]) of
479
{'EXIT', {function_clause, _}} ->
480
load_traverse(Line, Contexts, Modules,
481
[Context|NewContexts], ConfigList, State);
482
{'EXIT',{undef, _}} ->
483
load_traverse(Line, Contexts, Modules,
484
[Context|NewContexts], ConfigList,yes);
486
error_logger:error_report({'EXIT', Reason}),
487
load_traverse(Line, Contexts, Modules,
488
[Context|NewContexts], ConfigList, State);
490
load_traverse(Line, Contexts, Modules,
491
[NewContext|NewContexts], ConfigList,yes);
492
{ok, NewContext, ConfigEntry} when tuple(ConfigEntry) ->
493
load_traverse(Line, Contexts, Modules, [NewContext|NewContexts],
494
[ConfigEntry|ConfigList], yes);
495
{ok, NewContext, ConfigEntry} when list(ConfigEntry) ->
496
load_traverse(Line, Contexts, Modules, [NewContext|NewContexts],
497
lists:append(ConfigEntry, ConfigList), yes);
502
%% Verifies that all specified modules are available.
503
verify_modules([]) ->
505
verify_modules([Mod|Rest]) ->
506
case code:which(Mod) of
508
{error, ?NICE(atom_to_list(Mod)++" does not exist")};
513
%% Reads the entire configuration file and returns list of strings or
515
read_config_file(FileName) ->
516
case file:open(FileName, read) of
518
read_config_file(Stream, []);
520
{error, ?NICE("Cannot open "++FileName)}
522
read_config_file(Stream, SoFar) ->
523
case io:get_line(Stream, []) of
526
{ok, lists:reverse(SoFar)};
531
%% Ignore commented lines for efficiency later ..
532
read_config_file(Stream, SoFar);
534
{ok, NewLine, _}=regexp:sub(clean(Line),"[\t\r\f ]"," "),
537
%% Also ignore empty lines ..
538
read_config_file(Stream, SoFar);
540
read_config_file(Stream, [NewLine|SoFar])
544
parse_mime_types(Stream,MimeTypesList) ->
546
case io:get_line(Stream,'') of
552
parse_mime_types(Stream, MimeTypesList, Line).
553
parse_mime_types(Stream, MimeTypesList, eof) ->
556
parse_mime_types(Stream, MimeTypesList, "") ->
557
parse_mime_types(Stream, MimeTypesList);
558
parse_mime_types(Stream, MimeTypesList, [$#|_]) ->
559
parse_mime_types(Stream, MimeTypesList);
560
parse_mime_types(Stream, MimeTypesList, Line) ->
561
case regexp:split(Line, " ") of
562
{ok, [NewMimeType|Suffixes]} ->
563
parse_mime_types(Stream,
564
lists:append(suffixes(NewMimeType,Suffixes),
570
suffixes(_MimeType,[]) ->
572
suffixes(MimeType,[Suffix|Rest]) ->
573
[{Suffix,MimeType}|suffixes(MimeType,Rest)].
575
a_must(_ConfigList,[]) ->
577
a_must(ConfigList,[Directive|Rest]) ->
578
case httpd_util:key1search(ConfigList,Directive) of
582
a_must(ConfigList,Rest)
586
store(ConfigDB, _ConfigList, _Modules,[]) ->
588
store(ConfigDB, ConfigList, Modules, [ConfigListEntry|Rest]) ->
589
case store_traverse(ConfigListEntry,ConfigList,Modules) of
590
{ok, ConfigDBEntry} when tuple(ConfigDBEntry) ->
591
ets:insert(ConfigDB,ConfigDBEntry),
592
store(ConfigDB,ConfigList,Modules,Rest);
593
{ok, ConfigDBEntry} when list(ConfigDBEntry) ->
594
lists:foreach(fun(Entry) ->
595
ets:insert(ConfigDB,Entry)
597
store(ConfigDB,ConfigList,Modules,Rest);
602
store_traverse(_ConfigListEntry, _ConfigList,[]) ->
603
{error,?NICE("Unable to store configuration...")};
604
store_traverse(ConfigListEntry, ConfigList, [Module|Rest]) ->
605
case catch apply(Module,store,[ConfigListEntry, ConfigList]) of
606
{'EXIT',{function_clause,_}} ->
607
store_traverse(ConfigListEntry,ConfigList,Rest);
608
{'EXIT',{undef, _}} ->
609
store_traverse(ConfigListEntry,ConfigList,Rest);
611
error_logger:error_report({'EXIT',Reason}),
612
store_traverse(ConfigListEntry,ConfigList,Rest);
617
store_mime_types(Name,MimeTypesList) ->
618
%% Make sure that the ets table is not duplicated
619
%% when reloading configuration
620
catch ets:delete(Name),
621
MimeTypesDB = ets:new(Name, [named_table, set, protected]),
622
store_mime_types1(MimeTypesDB, MimeTypesList).
623
store_mime_types1(MimeTypesDB,[]) ->
625
store_mime_types1(MimeTypesDB,[Type|Rest]) ->
626
ets:insert(MimeTypesDB, Type),
627
store_mime_types1(MimeTypesDB, Rest).
631
remove_traverse(_ConfigDB,[]) ->
633
remove_traverse(ConfigDB,[Module|Rest]) ->
634
case (catch apply(Module,remove,[ConfigDB])) of
635
{'EXIT',{undef,_}} ->
636
remove_traverse(ConfigDB,Rest);
637
{'EXIT',{function_clause,_}} ->
638
remove_traverse(ConfigDB,Rest);
640
error_logger:error_report({'EXIT',Reason}),
641
remove_traverse(ConfigDB,Rest);
643
error_logger:error_report(Reason),
644
remove_traverse(ConfigDB,Rest);
646
remove_traverse(ConfigDB,Rest)
649
ssl_certificate_file(ConfigDB) ->
650
case httpd_util:lookup(ConfigDB,ssl_certificate_file) of
653
SSLCertificateFile ->
654
[{certfile,SSLCertificateFile}]
657
ssl_certificate_key_file(ConfigDB) ->
658
case httpd_util:lookup(ConfigDB,ssl_certificate_key_file) of
661
SSLCertificateKeyFile ->
662
[{keyfile,SSLCertificateKeyFile}]
665
ssl_verify_client(ConfigDB) ->
666
case httpd_util:lookup(ConfigDB,ssl_verify_client) of
670
[{verify,SSLVerifyClient}]
673
ssl_ciphers(ConfigDB) ->
674
case httpd_util:lookup(ConfigDB,ssl_ciphers) of
681
ssl_password(ConfigDB) ->
682
case httpd_util:lookup(ConfigDB,ssl_password_callback_module) of
686
case httpd_util:lookup(ConfigDB,
687
ssl_password_callback_function) of
691
case catch apply(Module, Function, []) of
692
Password when list(Password) ->
693
[{password, Password}];
695
error_report(ssl_password,Module,Function,Error),
701
ssl_verify_depth(ConfigDB) ->
702
case httpd_util:lookup(ConfigDB, ssl_verify_client_depth) of
709
ssl_ca_certificate_file(ConfigDB) ->
710
case httpd_util:lookup(ConfigDB, ssl_ca_certificate_file) of
717
error_report(Where,M,F,Error) ->
718
error_logger:error_report([{?MODULE, Where},
719
{apply, {M, F, []}}, Error]).