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.''
16
%% $Id: mod_cgi.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $
19
-export([do/1,env/3,status_code/1,load/2]).
21
%%Exports to the interface for sending chunked data
22
%% to http/1.1 users and full responses to http/1.0
23
-export([send/5,final_send/4, update_status_code/2,get_new_size/2]).
24
-include("httpd.hrl").
26
-define(VMODULE,"CGI").
27
-include("httpd_verbosity.hrl").
29
-define(GATEWAY_INTERFACE,"CGI/1.1").
30
-define(DEFAULT_CGI_TIMEOUT,15000).
36
case httpd_util:key1search(Info#mod.data,status) of
37
%% A status code has been generated!
38
{StatusCode, PhraseArgs, Reason} ->
39
{proceed, Info#mod.data};
40
%% No status code has been generated!
42
?vtrace("do -> no status code has been generated", []),
43
case httpd_util:key1search(Info#mod.data,response) of
44
%% No response has been generated!
46
?vtrace("do -> no response has been generated", []),
48
case httpd_util:key1search(Info#mod.data,
55
?vtrace("do -> RequestURI: ~p", [RequestURI]),
57
httpd_util:multi_lookup(Info#mod.config_db,
59
?vtrace("do -> ScriptAliases: ~p", [ScriptAliases]),
60
case mod_alias:real_script_name(Info#mod.config_db,
63
{Script, AfterScript} ->
64
exec_script(Info, Script, AfterScript, RequestURI);
66
{proceed,Info#mod.data}
68
%% A response has been generated or sent!
70
{proceed,Info#mod.data}
75
%% is_executable(File) ->
76
%% ?DEBUG("is_executable -> entry with~n"
77
%% " File: ~s",[File]),
78
%% Dir = filename:dirname(File),
79
%% FileName = filename:basename(File),
80
%% is_executable(FileName,Dir).
82
%% is_executable(FileName,Dir) ->
83
%% ?DEBUG("is_executable -> entry with~n"
85
%% " FileName: ~s",[Dir,FileName]),
86
%% case os:find_executable(FileName, Dir) of
94
%% -------------------------
95
%% Start temporary (hopefully) fix for win32
99
is_executable(File) ->
100
Dir = filename:dirname(File),
101
FileName = filename:basename(File),
104
is_win32_executable(Dir,FileName);
106
is_other_executable(Dir,FileName)
110
is_win32_executable(D,F) ->
111
case ends_with(F,[".bat",".exe",".com"]) of
113
%% This is why we cant use 'os:find_executable' directly.
114
%% It assumes that executable files is given without extension
115
case os:find_executable(F,D) of
122
case file:read_file_info(D ++ "/" ++ F) of
131
is_other_executable(D,F) ->
132
case os:find_executable(F,D) of
140
ends_with(File,[]) ->
142
ends_with(File,[Ext|Rest]) ->
143
case ends_with1(File,Ext) of
150
ends_with1(S,E) when length(S) >= length(E) ->
151
case to_lower(string:right(S,length(E))) of
161
to_lower(S) -> to_lower(S,[]).
163
to_lower([],L) -> lists:reverse(L);
164
to_lower([H|T],L) -> to_lower(T,[to_lower1(H)|L]).
166
to_lower1(C) when C >= $A, C =< $Z ->
173
%% ---------------------------------
176
env(VarName, Value) ->
179
env(Info, Script, AfterScript) ->
180
?vtrace("env -> entry with"
182
"~n AfterScript: ~p",
183
[Script, AfterScript]),
184
{_, RemoteAddr} = (Info#mod.init_data)#init_data.peername,
185
ServerName = (Info#mod.init_data)#init_data.resolve,
186
PH = parsed_header(Info#mod.parsed_header),
188
[env("SERVER_SOFTWARE",?SERVER_SOFTWARE),
189
env("SERVER_NAME",ServerName),
190
env("GATEWAY_INTERFACE",?GATEWAY_INTERFACE),
191
env("SERVER_PROTOCOL",?SERVER_PROTOCOL),
193
integer_to_list(httpd_util:lookup(Info#mod.config_db,port,80))),
194
env("REQUEST_METHOD",Info#mod.method),
195
env("REMOTE_ADDR",RemoteAddr),
196
env("SCRIPT_NAME",Script)],
198
case Info#mod.method of
202
[env("QUERY_STRING", QueryString)|Env];
204
Aliases = httpd_util:multi_lookup(
205
Info#mod.config_db,alias),
206
{_, PathTranslated, _} =
208
Info#mod.config_db, PathInfo, Aliases),
210
[env("PATH_INFO","/"++httpd_util:decode_hex(PathInfo)),
211
env("PATH_TRANSLATED",PathTranslated)]];
212
{PathInfo, QueryString} ->
213
Aliases = httpd_util:multi_lookup(
214
Info#mod.config_db,alias),
215
{_, PathTranslated, _} =
217
Info#mod.config_db, PathInfo, Aliases),
220
httpd_util:decode_hex(PathInfo)),
221
env("PATH_TRANSLATED",PathTranslated),
222
env("QUERY_STRING", QueryString)]];
227
[env("CONTENT_LENGTH",
228
integer_to_list(httpd_util:flatlength(
229
Info#mod.entity_body)))|Env];
234
case httpd_util:key1search(Info#mod.data,remote_user) of
238
[env("REMOTE_USER",RemoteUser)|Env1] %% OTP-4416
240
lists:flatten([Env2|PH]).
243
parsed_header(List) ->
244
parsed_header(List, []).
246
parsed_header([], SoFar) ->
248
parsed_header([{Name,[Value|R1]}|R2], SoFar) when list(Value)->
249
NewName=lists:map(fun(X) -> if X == $- -> $_; true -> X end end,Name),
250
Env = env("HTTP_"++httpd_util:to_upper(NewName),
251
multi_value([Value|R1])),
252
parsed_header(R2, [Env|SoFar]);
254
parsed_header([{Name,Value}|Rest], SoFar) ->
255
{ok,NewName,_} = regexp:gsub(Name, "-", "_"),
256
Env=env("HTTP_"++httpd_util:to_upper(NewName),Value),
257
parsed_header(Rest, [Env|SoFar]).
262
multi_value([Value]) ->
264
multi_value([Value|Rest]) ->
265
Value++", "++multi_value(Rest).
268
exec_script(Info, Script, AfterScript, RequestURI) ->
269
?vdebug("exec_script -> entry with"
271
"~n AfterScript: ~p",
272
[Script,AfterScript]),
273
exec_script(is_executable(Script),Info,Script,AfterScript,RequestURI).
275
exec_script(true, Info, Script, AfterScript, RequestURI) ->
276
?vtrace("exec_script -> entry when script is executable",[]),
277
process_flag(trap_exit,true),
278
Dir = filename:dirname(Script),
279
[Script_Name|_] = string:tokens(RequestURI, "?"),
280
Env = env(Info, Script_Name, AfterScript),
281
Port = (catch open_port({spawn,Script},[stream,{cd, Dir},{env, Env}])),
282
?vtrace("exec_script -> Port: ~w",[Port]),
285
%% Send entity_body to port.
286
Res = case Info#mod.entity_body of
290
(catch port_command(Port, EntityBody))
294
?vlog("port send failed:"
298
[Port,Info#mod.request_uri,Reason]),
299
exit({open_cmd_failed,Reason,
300
[{mod,?MODULE},{port,Port},
301
{uri,Info#mod.request_uri},
302
{script,Script},{env,Env},{dir,Dir},
303
{ebody_size,sz(Info#mod.entity_body)}]});
308
?vlog("open port failed: exit"
311
[Info#mod.request_uri,Reason]),
312
exit({open_port_failed,Reason,
313
[{mod,?MODULE},{uri,Info#mod.request_uri},{script,Script},
314
{env,Env},{dir,Dir}]});
316
?vlog("open port failed: unknown result"
319
[Info#mod.request_uri,O]),
320
exit({open_port_failed,O,
321
[{mod,?MODULE},{uri,Info#mod.request_uri},{script,Script},
322
{env,Env},{dir,Dir}]})
325
exec_script(false,Info,Script,_AfterScript,_RequestURI) ->
326
?vlog("script ~s not executable",[Script]),
329
{404,Info#mod.request_uri,
330
?NICE("You don't have permission to execute " ++
331
Info#mod.request_uri ++ " on this server")}}|
337
%% Socket <-> Port communication
340
proxy(#mod{config_db = ConfigDb} = Info, Port) ->
341
Timeout = httpd_util:lookup(ConfigDb, cgi_timeout, ?DEFAULT_CGI_TIMEOUT),
342
proxy(Info, Port, 0, undefined,[], Timeout).
344
proxy(Info, Port, Size, StatusCode, AccResponse, Timeout) ->
345
?vdebug("proxy -> entry with"
349
[Size, StatusCode, Timeout]),
351
{Port, {data, Response}} when port(Port) ->
352
?vtrace("proxy -> got some data from the port",[]),
354
NewStatusCode = update_status_code(StatusCode, Response),
356
?vtrace("proxy -> NewStatusCode: ~p",[NewStatusCode]),
357
case send(Info, NewStatusCode, Response, Size, AccResponse) of
359
?vtrace("proxy -> socket closed: kill port",[]),
360
(catch port_close(Port)), % KILL the port !!!!
361
process_flag(trap_exit,false),
363
[{response,{already_sent,200,Size}}|Info#mod.data]};
366
?vtrace("proxy -> head sent: kill port",[]),
367
(catch port_close(Port)), % KILL the port !!!!
368
process_flag(trap_exit,false),
370
[{response,{already_sent,200,Size}}|Info#mod.data]};
372
{http_response, NewAccResponse} ->
373
?vtrace("proxy -> head response: continue",[]),
374
NewSize = get_new_size(Size, Response),
375
proxy(Info, Port, NewSize, NewStatusCode,
376
NewAccResponse, Timeout);
379
?vtrace("proxy -> continue",[]),
380
%% The data is sent and the socket is not closed, continue
381
NewSize = get_new_size(Size, Response),
382
proxy(Info, Port, NewSize, NewStatusCode,
386
{'EXIT', Port, normal} when port(Port) ->
387
?vtrace("proxy -> exit signal from port: normal",[]),
388
NewStatusCode = update_status_code(StatusCode,AccResponse),
389
final_send(Info,NewStatusCode,Size,AccResponse),
390
process_flag(trap_exit,false),
391
{proceed, [{response,{already_sent,200,Size}}|Info#mod.data]};
393
{'EXIT', Port, Reason} when port(Port) ->
394
?vtrace("proxy -> exit signal from port: ~p",[Reason]),
395
process_flag(trap_exit, false),
396
{proceed, [{status,{400,none,reason(Reason)}}|Info#mod.data]};
398
{'EXIT', Pid, Reason} when pid(Pid) ->
399
%% This is the case that a linked process has died,
400
%% It would be nice to response with a server error
401
%% but since the heade alredy is sent
402
?vtrace("proxy -> exit signal from ~p: ~p",[Pid, Reason]),
403
proxy(Info, Port, Size, StatusCode, AccResponse, Timeout);
405
%% This should not happen
407
?vinfo("proxy -> received garbage: ~n~p", [WhatEver]),
408
NewStatusCode = update_status_code(StatusCode, AccResponse),
409
final_send(Info, StatusCode, Size, AccResponse),
410
process_flag(trap_exit, false),
411
{proceed, [{response,{already_sent,200,Size}}|Info#mod.data]}
414
?vlog("proxy -> timeout",[]),
415
(catch port_close(Port)), % KILL the port !!!!
416
httpd_socket:close(Info#mod.socket_type, Info#mod.socket),
417
process_flag(trap_exit,false),
418
{proceed,[{response,{already_sent,200,Size}}|Info#mod.data]}
424
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
426
%% The functions that handles the sending of the data to the client %%
428
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430
%%----------------------------------------------------------------------
431
%% Send the header the first time the size of the body is Zero
432
%%----------------------------------------------------------------------
434
send(#mod{method = "HEAD"} = Info, StatusCode, Response, 0, []) ->
435
first_handle_head_request(Info, StatusCode, Response);
436
send(Info, StatusCode, Response, 0, []) ->
437
first_handle_other_request(Info, StatusCode, Response);
439
%%----------------------------------------------------------------------
440
%% The size of the body is bigger than zero =>
441
%% we have a part of the body to send
442
%%----------------------------------------------------------------------
443
send(Info, StatusCode, Response, Size, AccResponse) ->
444
handle_other_request(Info, StatusCode, Response).
447
%%----------------------------------------------------------------------
448
%% The function is called the last time when the port has closed
449
%%----------------------------------------------------------------------
451
final_send(Info, StatusCode, Size, AccResponse)->
452
final_handle_other_request(Info, StatusCode).
455
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
457
%% The code that handles the head requests %%
459
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460
%%----------------------------------------------------------------------
461
%% The request is a head request if its a HTPT/1.1 request answer to it
462
%% otherwise we must collect the size of hte body before we can answer.
465
%%----------------------------------------------------------------------
466
first_handle_head_request(Info, StatusCode, Response)->
467
case Info#mod.http_version of
469
%% Since we have all we need to create the header create it
470
%% send it and return head_sent.
471
case httpd_util:split(Response,"\r\n\r\n|\n\n",2) of
472
{ok, [HeadEnd, Rest]} ->
473
HeadEnd1 = removeStatus(HeadEnd),
474
httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket,
475
[create_header(Info,StatusCode),
476
HeadEnd1,"\r\n\r\n"]);
478
httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket,
479
[create_header(Info, StatusCode),
480
"Content-Type:text/html\r\n\r\n"])
483
Response1= case regexp:split(Response,"\r\n\r\n|\n\n") of
484
{ok,[HeadEnd|Rest]} ->
485
removeStatus(HeadEnd);
487
["Content-Type:text/html"]
489
H1 = httpd_util:header(StatusCode,Info#mod.connection),
490
httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket,
491
[H1,Response1,"\r\n\r\n"])
496
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
498
%% Handle the requests that is to the other methods %%
500
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
501
%%----------------------------------------------------------------------
502
%% Create the http-response header and send it to the user if it is
503
%% a http/1.1 request otherwise we must accumulate it
504
%%----------------------------------------------------------------------
505
first_handle_other_request(Info,StatusCode,Response)->
506
Header = create_header(Info,StatusCode),
508
case httpd_util:split(Response,"\r\n\r\n|\n\n",2) of
509
{ok,[HeadPart,[]]} ->
510
[Header, removeStatus(HeadPart),"\r\n\r\n"];
512
{ok,[HeadPart,BodyPart]} ->
513
[Header, removeStatus(HeadPart), "\r\n\r\n",
514
httpd_util:integer_to_hexlist(length(BodyPart)),
517
%% No response header field from the cgi-script,
519
[Header, "Content-Type:text/html","\r\n\r\n",
520
httpd_util:integer_to_hexlist(length(Response)),
523
httpd_socket:deliver(Info#mod.socket_type,Info#mod.socket, Response1).
526
handle_other_request(#mod{http_version = "HTTP/1.1",
527
socket_type = Type, socket = Sock} = Info,
528
StatusCode, Response0) ->
529
Response = create_chunk(Info, Response0),
530
httpd_socket:deliver(Type, Sock, Response);
531
handle_other_request(#mod{socket_type = Type, socket = Sock} = Info,
532
StatusCode, Response) ->
533
httpd_socket:deliver(Type, Sock, Response).
536
final_handle_other_request(#mod{http_version = "HTTP/1.1",
537
socket_type = Type, socket = Sock},
539
httpd_socket:deliver(Type, Sock, "0\r\n");
540
final_handle_other_request(#mod{socket_type = Type, socket = Sock},
542
httpd_socket:close(Type, Sock),
546
create_chunk(_Info, Response) ->
547
HEXSize = httpd_util:integer_to_hexlist(length(lists:flatten(Response))),
548
HEXSize++"\r\n"++Response++"\r\n".
551
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
553
%% The various helper functions %%
555
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
557
update_status_code(undefined, Response) ->
558
case status_code(Response) of
562
?vlog("invalid response from script:~n~p", [Response]),
565
update_status_code(StatusCode,_Response)->
569
get_new_size(0,Response)->
570
case httpd_util:split(Response,"\r\n\r\n|\n\n",2) of
572
length(lists:flatten(Body));
574
%%No header in the respone
575
length(lists:flatten(Response))
578
get_new_size(Size,Response)->
579
Size+length(lists:flatten(Response)).
581
%%----------------------------------------------------------------------
582
%% Creates the http-header for a response
583
%%----------------------------------------------------------------------
584
create_header(Info,StatusCode)->
585
Cache=case httpd_util:lookup(Info#mod.config_db,script_nocache,false) of
587
Date=httpd_util:rfc1123_date(),
588
"Cache-Control:no-cache\r\nPragma:no-cache\r\nExpires:"++ Date ++ "\r\n";
592
case Info#mod.http_version of
594
Header=httpd_util:header(StatusCode, Info#mod.connection),
595
Header++"Transfer-encoding:chunked\r\n"++Cache;
597
httpd_util:header(StatusCode,Info#mod.connection)++Cache
604
status_code(Response) ->
605
case httpd_util:split(Response,"\n\n|\r\n\r\n",2) of
606
{ok,[Header,Body]} ->
607
case regexp:split(Header,"\n|\r\n") of
609
{ok,extract_status_code(HeaderFields)};
611
{error, bad_script_output(Response)}
614
%% No header field in the returned data return 200 the standard code
618
bad_script_output(Bad) ->
619
lists:flatten(io_lib:format("Bad script output ~s",[Bad])).
622
extract_status_code([]) ->
624
extract_status_code([[$L,$o,$c,$a,$t,$i,$o,$n,$:,$ |_]|_]) ->
626
extract_status_code([[$S,$t,$a,$t,$u,$s,$:,$ |CodeAndReason]|_]) ->
627
case httpd_util:split(CodeAndReason," ",2) of
629
list_to_integer(Code);
633
extract_status_code([_|Rest]) ->
634
extract_status_code(Rest).
637
sz(B) when binary(B) -> {binary,size(B)};
638
sz(L) when list(L) -> {list,length(L)};
642
%% Convert error to printable string
644
reason({error,emfile}) -> ": To many open files";
645
reason({error,{enfile,_}}) -> ": File/port table overflow";
646
reason({error,enomem}) -> ": Not enough memory";
647
reason({error,eagain}) -> ": No more available OS processes";
651
case httpd_util:split(Head,"Status:.\r\n",2) of
652
{ok,[HeadPart,HeadEnd]}->
660
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
662
%% There are 2 config directives for mod_cgi: %%
663
%% ScriptNoCache true|false, defines whether the server shall add %%
664
%% header fields to stop proxies and %%
665
%% clients from saving the page in history %%
668
%% ScriptTimeout Seconds, The number of seconds that the server %%
669
%% maximum will wait for the script to %%
670
%% generate a part of the document %%
672
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
674
load([$S,$c,$r,$i,$p,$t,$N,$o,$C,$a,$c,$h,$e |CacheArg],[])->
675
case catch list_to_atom(httpd_conf:clean(CacheArg)) of
677
{ok, [], {script_nocache,true}};
679
{ok, [], {script_nocache,false}};
681
{error, ?NICE(httpd_conf:clean(CacheArg)++
682
" is an invalid ScriptNoCache directive")}
685
load([$S,$c,$r,$i,$p,$t,$T,$i,$m,$e,$o,$u,$t,$ |Timeout],[])->
686
case catch list_to_integer(httpd_conf:clean(Timeout)) of
687
TimeoutSec when integer(TimeoutSec) ->
688
{ok, [], {script_timeout,TimeoutSec*1000}};
690
{error, ?NICE(httpd_conf:clean(Timeout)++
691
" is an invalid ScriptTimeout")}