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.''
18
%% Implements The WWW Common Gateway Interface Version 1.1
25
-export([do/1, load/2]).
27
-include("http_internal.hrl").
28
-include("httpd.hrl").
30
%% We will not make the change to use base64 in stdlib in inets just yet.
31
%% it will be included in the next major release of inets.
32
-compile({nowarn_deprecated_function, {http_base_64, encode, 1}}).
34
-define(VMODULE,"CGI").
36
-define(DEFAULT_CGI_TIMEOUT, 15000).
38
%%%=========================================================================
40
%%%=========================================================================
41
%%--------------------------------------------------------------------------
42
%% do(ModData, _, AfterScript) -> [{EnvVariable, Value}]
44
%% AfterScript = string()
46
%% EnvVariable = string()
48
%% Description: Keep for now as it is documented in the man page
49
%%-------------------------------------------------------------------------
50
env(ModData, _Script, AfterScript) ->
51
ScriptElements = script_elements(ModData, AfterScript),
52
httpd_script_env:create_env(cgi, ModData, ScriptElements).
54
%%%=========================================================================
56
%%%=========================================================================
58
%%--------------------------------------------------------------------------
59
%% do(ModData) -> {proceed, OldData} | {proceed, NewData} | {break, NewData}
63
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
64
%%-------------------------------------------------------------------------
66
case httpd_util:key1search(ModData#mod.data, status) of
67
%% A status code has been generated!
68
{_StatusCode, _PhraseArgs, _Reason} ->
69
{proceed, ModData#mod.data};
70
%% No status code has been generated!
72
case httpd_util:key1search(ModData#mod.data, response) of
74
generate_response(ModData);
76
{proceed, ModData#mod.data}
80
%%--------------------------------------------------------------------------
81
%% load(Line, Context) -> eof | ok | {ok, NewContext} |
82
%% {ok, NewContext, Directive} |
83
%% {ok, NewContext, DirectiveList} | {error, Reason}
85
%% Context = NewContext = DirectiveList = [Directive]
86
%% Directive = {DirectiveKey , DirectiveValue}
87
%% DirectiveKey = DirectiveValue = term()
90
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
91
%%-------------------------------------------------------------------------
93
%% ScriptNoCache true|false, defines whether the server shall add
94
%% header fields to stop proxies and
95
%% clients from saving the page in history
98
load("ScriptNoCache " ++ CacheArg, [])->
99
case catch list_to_atom(httpd_conf:clean(CacheArg)) of
101
{ok, [], {script_nocache, true}};
103
{ok, [], {script_nocache, false}};
105
{error, ?NICE(httpd_conf:clean(CacheArg)++
106
" is an invalid ScriptNoCache directive")}
108
%% ScriptTimeout Seconds, The number of seconds that the server
109
%% maximum will wait for the script to
110
%% generate a part of the document
111
load("ScriptTimeout " ++ Timeout, [])->
112
case catch list_to_integer(httpd_conf:clean(Timeout)) of
113
TimeoutSec when integer(TimeoutSec) ->
114
{ok, [], {script_timeout,TimeoutSec*1000}};
116
{error, ?NICE(httpd_conf:clean(Timeout)++
117
" is an invalid ScriptTimeout")}
120
%%%========================================================================
121
%%% Internal functions
122
%%%========================================================================
123
generate_response(ModData) ->
125
case httpd_util:key1search(ModData#mod.data, new_request_uri) of
127
ModData#mod.request_uri;
132
httpd_util:multi_lookup(ModData#mod.config_db, script_alias),
133
case mod_alias:real_script_name(ModData#mod.config_db, RequestURI,
135
{Script, AfterScript} ->
136
exec_script(ModData, Script, AfterScript,
139
{proceed, ModData#mod.data}
142
is_executable(File) ->
143
Dir = filename:dirname(File),
144
FileName = filename:basename(File),
147
%% temporary (hopefully) fix for win32 OTP-3627
148
is_win32_executable(Dir,FileName);
150
is_executable(Dir, FileName)
153
is_executable(Dir, FilName) ->
154
case os:find_executable(FilName, Dir) of
161
%% Start temporary (hopefully) fix for win32 OTP-3627
162
%% ---------------------------------
163
is_win32_executable(Dir, FileName) ->
164
NewFileName = strip_extention(FileName, [".bat",".exe",".com", ".cmd"]),
165
is_executable(Dir, NewFileName).
167
strip_extention(FileName, []) ->
169
strip_extention(FileName, [Extention | Extentions]) ->
170
case filename:basename(FileName, Extention) of
172
strip_extention(FileName, Extentions);
178
%% ---------------------------------
180
exec_script(ModData, Script, AfterScript, RequestURI) ->
181
exec_script(is_executable(Script), ModData, Script,
182
AfterScript, RequestURI).
184
exec_script(true, ModData, Script, AfterScript, _RequestURI) ->
185
process_flag(trap_exit,true),
186
Dir = filename:dirname(Script),
187
ScriptElements = script_elements(ModData, AfterScript),
188
Env = (catch httpd_script_env:create_env(cgi, ModData, ScriptElements)),
191
Port = (catch open_port({spawn, Script},[binary, stream,
192
{cd, Dir}, {env, Env}])),
194
Port when is_port(Port) ->
195
send_request_body_to_script(ModData, Port),
196
deliver_webpage(ModData, Port); % Take care of script output
198
exit({open_port_failed, Error,
200
{uri,ModData#mod.request_uri}, {script,Script},
201
{env,Env},{dir,Dir}]})
204
exec_script(false, ModData, _Script, _AfterScript, _RequestURI) ->
207
{404,ModData#mod.request_uri,
208
?NICE("You don't have permission to execute " ++
209
ModData#mod.request_uri ++ " on this server")}}|
212
send_request_body_to_script(ModData, Port) ->
213
case ModData#mod.entity_body of
217
port_command(Port, EntityBody)
220
deliver_webpage(#mod{config_db = Db} = ModData, Port) ->
221
Timeout = cgi_timeout(Db),
222
case receive_headers(Port, httpd_cgi, parse_headers,
223
[<<>>, [], []], Timeout) of
225
case httpd_cgi:handle_headers(Headers) of
226
{proceed, AbsPath} ->
227
{proceed, [{real_name,
228
httpd_util:split_path(AbsPath)} |
230
{ok, HTTPHeaders, Status} ->
231
IsDisableChunkedSend =
232
httpd_response:is_disable_chunked_send(Db),
233
case (ModData#mod.http_version =/= "HTTP/1.1") or
234
(IsDisableChunkedSend) of
236
send_headers(ModData, Status,
237
[{"connection", "close"}
240
send_headers(ModData, Status,
241
[{"transfer-encoding",
242
"chunked"} | HTTPHeaders])
244
handle_body(Port, ModData, Body, Timeout, size(Body),
245
IsDisableChunkedSend)
247
{'EXIT', Port, Reason} ->
248
process_flag(trap_exit, false),
249
{proceed, [{status, {400, none, reason(Reason)}} |
252
(catch port_close(Port)), % KILL the port !!!!
253
send_headers(ModData, {504, "Timeout"}, []),
254
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket),
255
process_flag(trap_exit,false),
256
{proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]}
259
receive_headers(Port, Module, Function, Args, Timeout) ->
261
{Port, {data, Response}} when is_port(Port) ->
262
case Module:Function([Response | Args]) of
263
{NewModule, NewFunction, NewArgs} ->
264
receive_headers(Port, NewModule,
265
NewFunction, NewArgs, Timeout);
266
{ok, {Headers, Body}} ->
269
{'EXIT', Port, Reason} when is_port(Port) ->
270
{'EXIT', Port, Reason};
271
{'EXIT', Pid, Reason} when is_pid(Pid) ->
272
exit({linked_process_died, Pid, Reason})
277
send_headers(ModData, {StatusCode, _}, HTTPHeaders) ->
278
ExtraHeaders = httpd_response:cache_headers(ModData),
279
httpd_response:send_header(ModData, StatusCode,
280
ExtraHeaders ++ HTTPHeaders).
282
handle_body(Port, #mod{method = "HEAD"} = ModData, _, _, Size, _) ->
283
(catch port_close(Port)), % KILL the port !!!!
284
process_flag(trap_exit,false),
285
{proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]};
287
handle_body(Port, ModData, Body, Timeout, Size, IsDisableChunkedSend) ->
288
httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend),
290
{Port, {data, Data}} when port(Port) ->
291
handle_body(Port, ModData, Data, Timeout, Size + size(Data),
292
IsDisableChunkedSend);
293
{'EXIT', Port, normal} when is_port(Port) ->
294
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
295
process_flag(trap_exit,false),
296
{proceed, [{response, {already_sent, 200, Size}} |
298
{'EXIT', Port, Reason} when is_port(Port) ->
299
process_flag(trap_exit, false),
300
{proceed, [{status, {400, none, reason(Reason)}} |
302
{'EXIT', Pid, Reason} when is_pid(Pid) ->
303
exit({mod_cgi_linked_process_died, Pid, Reason})
305
(catch port_close(Port)), % KILL the port !!!!
306
process_flag(trap_exit,false),
307
{proceed,[{response, {already_sent, 200, Size}} |
311
script_elements(#mod{method = "GET"}, {[], QueryString}) ->
312
[{query_string, QueryString}];
313
script_elements(#mod{method = "GET"}, {PathInfo, []}) ->
314
[{path_info, PathInfo}];
315
script_elements(#mod{method = "GET"}, {PathInfo, QueryString}) ->
316
[{query_string, QueryString}, {path_info, PathInfo}];
317
script_elements(#mod{method = "POST", entity_body = Body}, _) ->
318
[{entity_body, Body}];
319
script_elements(_, _) ->
323
httpd_util:lookup(Db, cgi_timeout, ?DEFAULT_CGI_TIMEOUT).
325
%% Convert error to printable string
327
reason({error,emfile}) -> ": To many open files";
328
reason({error,{enfile,_}}) -> ": File/port table overflow";
329
reason({error,enomem}) -> ": Not enough memory";
330
reason({error,eagain}) -> ": No more available OS processes";
331
reason(Reason) -> lists:flatten(io_lib:format("Reason: ~p~n", [Reason])).