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
%% Functions provided to help erl scheme alias programmer to
22
%% Create dynamic webpages that are sent back to the user during
27
-export([do/1, load/2]).
29
-include("httpd.hrl").
31
-define(VMODULE,"ESI").
32
-define(DEFAULT_ERL_TIMEOUT,15000).
34
%%%=========================================================================
36
%%%=========================================================================
37
%%--------------------------------------------------------------------------
38
%% deliver(SessionID, Data) -> ok | {error, bad_sessionID}
40
%% Data = string() | io_list() (first call must send a string that
41
%% contains all header information including "\r\n\r\n", unless there
42
%% is no header information at all.)
44
%% Description: Send <Data> (Html page generated sofar) to the server
45
%% request handling process so it can forward it to the client.
46
%%-------------------------------------------------------------------------
47
deliver(SessionID, Data) when pid(SessionID) ->
48
SessionID ! {ok, Data},
50
deliver(_SessionID, _Data) ->
51
{error, bad_sessionID}.
53
%%%=========================================================================
55
%%%=========================================================================
56
%%--------------------------------------------------------------------------
57
%% do(ModData) -> {proceed, OldData} | {proceed, NewData} | {break, NewData}
61
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
62
%%-------------------------------------------------------------------------
64
case httpd_util:key1search(ModData#mod.data, status) of
65
{_StatusCode, _PhraseArgs, _Reason} ->
66
{proceed, ModData#mod.data};
68
case httpd_util:key1search(ModData#mod.data, response) of
70
generate_response(ModData);
72
{proceed, ModData#mod.data}
75
%%--------------------------------------------------------------------------
76
%% load(Line, Context) -> eof | ok | {ok, NewContext} |
77
%% {ok, NewContext, Directive} |
78
%% {ok, NewContext, DirectiveList} | {error, Reason}
80
%% Context = NewContext = DirectiveList = [Directive]
81
%% Directive = {DirectiveKey , DirectiveValue}
82
%% DirectiveKey = DirectiveValue = term()
85
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
86
%%-------------------------------------------------------------------------
87
load("ErlScriptAlias " ++ ErlScriptAlias, []) ->
88
case regexp:split(ErlScriptAlias," ") of
89
{ok, [ErlName | Modules]} ->
90
{ok, [], {erl_script_alias, {ErlName,Modules}}};
92
{error, ?NICE(httpd_conf:clean(ErlScriptAlias) ++
93
" is an invalid ErlScriptAlias")}
95
load("EvalScriptAlias " ++ EvalScriptAlias, []) ->
96
case regexp:split(EvalScriptAlias, " ") of
97
{ok, [EvalName|Modules]} ->
98
{ok, [], {eval_script_alias, {EvalName, Modules}}};
100
{error, ?NICE(httpd_conf:clean(EvalScriptAlias) ++
101
" is an invalid EvalScriptAlias")}
103
load("ErlScriptTimeout " ++ Timeout, [])->
104
case catch list_to_integer(httpd_conf:clean(Timeout)) of
105
TimeoutSec when integer(TimeoutSec) ->
106
{ok, [], {erl_script_timeout, TimeoutSec * 1000}};
108
{error, ?NICE(httpd_conf:clean(Timeout) ++
109
" is an invalid ErlScriptTimeout")}
111
load("ErlScriptNoCache " ++ CacheArg, [])->
112
case catch list_to_atom(httpd_conf:clean(CacheArg)) of
114
{ok, [], {erl_script_nocache, true}};
116
{ok, [], {erl_script_nocache, false}};
118
{error, ?NICE(httpd_conf:clean(CacheArg)++
119
" is an invalid ErlScriptNoCache directive")}
122
%%%========================================================================
123
%%% Internal functions
124
%%%========================================================================
125
generate_response(ModData) ->
126
case scheme(ModData#mod.request_uri, ModData#mod.config_db) of
127
{eval, ESIBody, Modules} ->
128
eval(ModData, ESIBody, Modules);
129
{erl, ESIBody, Modules} ->
130
erl(ModData, ESIBody, Modules);
132
{proceed, ModData#mod.data}
135
scheme(RequestURI, ConfigDB) ->
136
case match_script(RequestURI, ConfigDB, erl_script_alias) of
138
case match_script(RequestURI, ConfigDB, eval_script_alias) of
141
{EsiBody, ScriptModules} ->
142
{eval, EsiBody, ScriptModules}
144
{EsiBody, ScriptModules} ->
145
{erl, EsiBody, ScriptModules}
148
match_script(RequestURI, ConfigDB, AliasType) ->
149
case httpd_util:multi_lookup(ConfigDB, AliasType) of
153
match_esi_script(RequestURI, AliasAndMods, AliasType)
156
match_esi_script(_, [], _) ->
158
match_esi_script(RequestURI, [{Alias,Modules} | Rest], AliasType) ->
159
AliasMatchStr = alias_match_str(Alias, AliasType),
160
case regexp:first_match(RequestURI, AliasMatchStr) of
161
{match, 1, Length} ->
162
{string:substr(RequestURI, Length + 1), Modules};
164
match_esi_script(RequestURI, Rest, AliasType)
167
alias_match_str(Alias, erl_script_alias) ->
169
alias_match_str(Alias, eval_script_alias) ->
170
"^" ++ Alias ++ "\\?".
173
%%------------------------ Erl mechanism --------------------------------
175
erl(#mod{method = Method} = ModData, ESIBody, Modules)
176
when Method == "GET"; Method == "HEAD"->
177
case httpd_util:split(ESIBody,":|%3A|/",2) of
178
{ok, [Module, FuncAndInput]} ->
179
case httpd_util:split(FuncAndInput,"[\?/]",2) of
180
{ok, [FunctionName, Input]} ->
181
generate_webpage(ModData, ESIBody, Modules,
182
Module, FunctionName, Input,
183
script_elements(FunctionName, Input));
184
{ok, [FunctionName]} ->
185
generate_webpage(ModData, ESIBody, Modules,
186
Module, FunctionName, "",
187
script_elements(FunctionName, ""));
189
{proceed,[{status,{400,none, BadRequest}} |
193
{proceed, [{status,{400, none, BadRequest}} | ModData#mod.data]}
196
erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) ->
197
case httpd_util:split(ESIBody,":|%3A|/",2) of
198
{ok,[Module, Function]} ->
199
generate_webpage(ModData, ESIBody, Modules, Module,
200
Function, Body, [{entity_body, Body}]);
202
{proceed,[{status, {400, none, BadRequest}} | ModData#mod.data]}
205
generate_webpage(ModData, ESIBody, ["all"], ModuleName, FunctionName,
206
Input, ScriptElements) ->
207
generate_webpage(ModData, ESIBody, [ModuleName], ModuleName,
208
FunctionName, Input, ScriptElements);
209
generate_webpage(ModData, ESIBody, Modules, ModuleName, FunctionName,
210
Input, ScriptElements) ->
211
case lists:member(ModuleName, Modules) of
213
Env = httpd_script_env:create_env(esi, ModData, ScriptElements),
214
Module = list_to_atom(ModuleName),
215
Function = list_to_atom(FunctionName),
216
case erl_scheme_webpage_chunk(Module, Function,
217
Env, Input, ModData) of
218
{error, erl_scheme_webpage_chunk_undefined} ->
219
erl_scheme_webpage_whole(Module, Function, Env, Input,
225
{proceed, [{status, {403, ModData#mod.request_uri,
226
?NICE("Client not authorized to evaluate: "
227
++ ESIBody)}} | ModData#mod.data]}
230
%% Old API that waits for the dymnamic webpage to be totally generated
231
%% before anythig is sent back to the client.
232
erl_scheme_webpage_whole(Module, Function, Env, Input, ModData) ->
233
case (catch Module:Function(Env, Input)) of
235
{proceed, [{status, {500, none, Reason}} |
239
httpd_esi:parse_headers(lists:flatten(Response)),
240
Length = httpd_util:flatlength(Body),
241
case httpd_esi:handle_headers(Headers) of
242
{proceed, AbsPath} ->
243
{proceed, [{real_name, httpd_util:split_path(AbsPath)}
244
| ModData#mod.data]};
245
{ok, NewHeaders, StatusCode} ->
246
send_headers(ModData, StatusCode,
248
integer_to_list(Length)}| NewHeaders]),
249
case ModData#mod.method of
251
{proceed, [{response, {already_sent, 200, 0}} |
254
httpd_response:send_body(ModData,
256
{proceed, [{response, {already_sent, 200,
263
%% New API that allows the dynamic wepage to be sent back to the client
264
%% in small chunks at the time during generation.
265
erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) ->
266
process_flag(trap_exit, true),
268
%% Spawn worker that generates the webpage.
269
%% It would be nicer to use erlang:function_exported/3 but if the
270
%% Module isn't loaded the function says that it is not loaded
273
case catch Mod:Func(Self, Env, Input) of
274
{'EXIT',{undef,_}} ->
275
%% Will force fallback on the old API
276
exit(erl_scheme_webpage_chunk_undefined);
282
Response = deliver_webpage_chunk(ModData, Pid),
284
process_flag(trap_exit,false),
287
deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid) ->
288
Timeout = erl_script_timeout(Db),
289
deliver_webpage_chunk(ModData, Pid, Timeout).
291
deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
292
case receive_headers(Timeout) of
294
%% Happens when webpage generator callback/3 is undefined
297
case httpd_esi:handle_headers(Headers) of
298
{proceed, AbsPath} ->
299
{proceed, [{real_name, httpd_util:split_path(AbsPath)}
300
| ModData#mod.data]};
301
{ok, NewHeaders, StatusCode} ->
302
IsDisableChunkedSend =
303
httpd_response:is_disable_chunked_send(Db),
304
case (ModData#mod.http_version =/= "HTTP/1.1") or
305
(IsDisableChunkedSend) of
307
send_headers(ModData, StatusCode,
308
[{"connection", "close"} |
311
send_headers(ModData, StatusCode,
312
[{"transfer-encoding",
313
"chunked"} | NewHeaders])
315
handle_body(Pid, ModData, Body, Timeout, length(Body),
316
IsDisableChunkedSend)
319
send_headers(ModData, {504, "Timeout"},[{"connection", "close"}]),
320
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket),
321
process_flag(trap_exit,false),
322
{proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]}
325
receive_headers(Timeout) ->
328
httpd_esi:parse_headers(lists:flatten(Chunk));
329
{'EXIT', Pid, erl_scheme_webpage_chunk_undefined} when is_pid(Pid) ->
330
{error, erl_scheme_webpage_chunk_undefined};
331
{'EXIT', Pid, Reason} when is_pid(Pid) ->
332
exit({mod_esi_linked_process_died, Pid, Reason})
337
send_headers(ModData, StatusCode, HTTPHeaders) ->
338
ExtraHeaders = httpd_response:cache_headers(ModData),
339
httpd_response:send_header(ModData, StatusCode,
340
ExtraHeaders ++ HTTPHeaders).
342
handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) ->
343
process_flag(trap_exit,false),
344
{proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]};
346
handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) ->
347
httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend),
350
handle_body(Pid, ModData, Data, Timeout, Size + length(Data),
351
IsDisableChunkedSend);
352
{'EXIT', Pid, normal} when is_pid(Pid) ->
353
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
354
{proceed, [{response, {already_sent, 200, Size}} |
356
{'EXIT', Pid, Reason} when is_pid(Pid) ->
357
exit({mod_esi_linked_process_died, Pid, Reason})
359
process_flag(trap_exit,false),
360
{proceed,[{response, {already_sent, 200, Size}} |
364
erl_script_timeout(Db) ->
365
httpd_util:lookup(Db, erl_script_timeout, ?DEFAULT_ERL_TIMEOUT).
367
script_elements(FuncAndInput, Input) ->
368
case input_type(FuncAndInput) of
370
[{path_info, Input}];
372
[{query_string, Input}];
379
input_type([$/|_Rest]) ->
381
input_type([$?|_Rest]) ->
383
input_type([_First|Rest]) ->
386
%%------------------------ Eval mechanism --------------------------------
388
eval(#mod{request_uri = ReqUri, method = "POST",
389
http_version = Version, data = Data}, _ESIBody, _Modules) ->
390
{proceed,[{status,{501,{"POST", ReqUri, Version},
391
?NICE("Eval mechanism doesn't support method POST")}}|
394
eval(#mod{method = Method} = ModData, ESIBody, Modules)
395
when Method == "GET"; Method == "HEAD" ->
396
case is_authorized(ESIBody, Modules) of
398
case generate_webpage(ESIBody) of
400
{proceed, [{status, {500, none, Reason}} |
404
httpd_esi:parse_headers(lists:flatten(Response)),
405
case httpd_esi:handle_headers(Headers) of
406
{ok, _, StatusCode} ->
407
{proceed,[{response, {StatusCode, Response}} |
409
{proceed, AbsPath} ->
410
{proceed, [{real_name, AbsPath} |
413
{proceed, [{status, {400, none, Reason}} |
419
{403, ModData#mod.request_uri,
420
?NICE("Client not authorized to evaluate: "
421
++ ESIBody)}} | ModData#mod.data]}
424
generate_webpage(ESIBody) ->
425
(catch lib:eval_str(string:concat(ESIBody,". "))).
427
is_authorized(_ESIBody, ["all"]) ->
429
is_authorized(ESIBody, Modules) ->
430
case regexp:match(ESIBody, "^[^\:(%3A)]*") of
431
{match, Start, Length} ->
432
lists:member(string:substr(ESIBody, Start, Length), Modules);