~ubuntu-branches/debian/wheezy/couchdb/wheezy

« back to all changes in this revision

Viewing changes to src/couch_inets/mod_cgi.erl

  • Committer: Bazaar Package Importer
  • Author(s): Noah Slater
  • Date: 2008-02-06 17:03:38 UTC
  • Revision ID: james.westby@ubuntu.com-20080206170338-y411anylx3oplqid
Tags: upstream-0.7.3~svn684
ImportĀ upstreamĀ versionĀ 0.7.3~svn684

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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/.
 
6
%% 
 
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
 
10
%% under the License.
 
11
%% 
 
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.''
 
15
%% 
 
16
%%     $Id$
 
17
%%
 
18
%% Implements  The WWW Common Gateway Interface Version 1.1
 
19
 
 
20
-module(mod_cgi).
 
21
 
 
22
-export([env/3]).
 
23
 
 
24
%%% Callback API
 
25
-export([do/1, load/2]).
 
26
 
 
27
-include("http_internal.hrl").
 
28
-include("httpd.hrl").
 
29
 
 
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}}).
 
33
 
 
34
-define(VMODULE,"CGI").
 
35
 
 
36
-define(DEFAULT_CGI_TIMEOUT, 15000).
 
37
 
 
38
%%%=========================================================================
 
39
%%%  API
 
40
%%%=========================================================================
 
41
%%--------------------------------------------------------------------------
 
42
%% do(ModData, _, AfterScript) ->  [{EnvVariable, Value}]
 
43
%%                
 
44
%%     AfterScript = string()
 
45
%%     ModData = #mod{}
 
46
%%     EnvVariable = string() 
 
47
%%     Value = term()
 
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).
 
53
 
 
54
%%%=========================================================================
 
55
%%%  Callback API
 
56
%%%=========================================================================
 
57
 
 
58
%%--------------------------------------------------------------------------
 
59
%% do(ModData) -> {proceed, OldData} | {proceed, NewData} | {break, NewData} 
 
60
%%                | done
 
61
%%     ModData = #mod{}
 
62
%%
 
63
%% Description:  See httpd(3) ESWAPI CALLBACK FUNCTIONS
 
64
%%-------------------------------------------------------------------------
 
65
do(ModData) ->
 
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!
 
71
        undefined ->
 
72
            case httpd_util:key1search(ModData#mod.data, response) of
 
73
                undefined ->
 
74
                    generate_response(ModData);
 
75
                _Response ->
 
76
                    {proceed, ModData#mod.data}
 
77
            end
 
78
    end.
 
79
 
 
80
%%--------------------------------------------------------------------------
 
81
%% load(Line, Context) ->  eof | ok | {ok, NewContext} | 
 
82
%%                     {ok, NewContext, Directive} | 
 
83
%%                     {ok, NewContext, DirectiveList} | {error, Reason}
 
84
%% Line = string()
 
85
%% Context = NewContext = DirectiveList = [Directive]
 
86
%% Directive = {DirectiveKey , DirectiveValue}
 
87
%% DirectiveKey = DirectiveValue = term()
 
88
%% Reason = term() 
 
89
%%
 
90
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
 
91
%%-------------------------------------------------------------------------
 
92
 
 
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  
 
96
%%                           or cache                                 
 
97
%%                                                                             
 
98
load("ScriptNoCache " ++ CacheArg, [])->
 
99
    case catch list_to_atom(httpd_conf:clean(CacheArg)) of
 
100
        true ->
 
101
            {ok, [], {script_nocache, true}};
 
102
        false ->
 
103
           {ok, [], {script_nocache, false}};
 
104
        _ ->
 
105
           {error, ?NICE(httpd_conf:clean(CacheArg)++
 
106
                         " is an invalid ScriptNoCache directive")}
 
107
    end;
 
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}};
 
115
        _ ->
 
116
           {error, ?NICE(httpd_conf:clean(Timeout)++
 
117
                         " is an invalid ScriptTimeout")}
 
118
    end.
 
119
        
 
120
%%%========================================================================
 
121
%%% Internal functions
 
122
%%%========================================================================
 
123
generate_response(ModData) ->
 
124
    RequestURI =
 
125
        case httpd_util:key1search(ModData#mod.data, new_request_uri) of
 
126
            undefined ->
 
127
                ModData#mod.request_uri;
 
128
            Value ->
 
129
                Value
 
130
        end,
 
131
    ScriptAliases =
 
132
        httpd_util:multi_lookup(ModData#mod.config_db, script_alias),
 
133
    case mod_alias:real_script_name(ModData#mod.config_db, RequestURI,
 
134
                                    ScriptAliases) of
 
135
        {Script, AfterScript} ->
 
136
            exec_script(ModData, Script, AfterScript, 
 
137
                        RequestURI);
 
138
        not_a_script ->
 
139
            {proceed, ModData#mod.data}
 
140
    end.
 
141
 
 
142
is_executable(File) ->
 
143
    Dir      = filename:dirname(File),
 
144
    FileName = filename:basename(File),
 
145
    case os:type() of
 
146
        {win32,_} ->
 
147
            %% temporary (hopefully) fix for win32 OTP-3627
 
148
            is_win32_executable(Dir,FileName);
 
149
        _ ->
 
150
            is_executable(Dir, FileName) 
 
151
    end.
 
152
 
 
153
is_executable(Dir, FilName) ->
 
154
    case os:find_executable(FilName, Dir) of
 
155
        false ->
 
156
            false;
 
157
        _ ->
 
158
            true
 
159
    end.
 
160
 
 
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).
 
166
 
 
167
strip_extention(FileName, []) ->
 
168
    FileName;
 
169
strip_extention(FileName, [Extention | Extentions]) ->
 
170
    case filename:basename(FileName, Extention) of
 
171
        FileName ->
 
172
            strip_extention(FileName, Extentions); 
 
173
        NewFileName ->
 
174
            NewFileName
 
175
    end.
 
176
 
 
177
%% End fix
 
178
%% ---------------------------------
 
179
 
 
180
exec_script(ModData, Script, AfterScript, RequestURI) ->
 
181
    exec_script(is_executable(Script), ModData, Script, 
 
182
                AfterScript, RequestURI).
 
183
 
 
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)),
 
189
 
 
190
    %% Run script
 
191
    Port = (catch open_port({spawn, Script},[binary, stream,
 
192
                                             {cd, Dir}, {env, Env}])),
 
193
    case Port of
 
194
        Port when is_port(Port) ->
 
195
            send_request_body_to_script(ModData, Port),
 
196
            deliver_webpage(ModData, Port); % Take care of script output
 
197
        Error ->
 
198
            exit({open_port_failed, Error,
 
199
                  [{mod,?MODULE},
 
200
                   {uri,ModData#mod.request_uri}, {script,Script},
 
201
                   {env,Env},{dir,Dir}]})
 
202
    end;
 
203
 
 
204
exec_script(false, ModData, _Script, _AfterScript, _RequestURI) ->
 
205
    {proceed,
 
206
     [{status,
 
207
       {404,ModData#mod.request_uri,
 
208
        ?NICE("You don't have permission to execute " ++
 
209
              ModData#mod.request_uri ++ " on this server")}}|
 
210
      ModData#mod.data]}.
 
211
    
 
212
send_request_body_to_script(ModData, Port) ->
 
213
    case ModData#mod.entity_body of
 
214
        [] ->
 
215
            ok;
 
216
        EntityBody ->
 
217
            port_command(Port, EntityBody)
 
218
    end.        
 
219
           
 
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
 
224
        {Headers, Body} ->
 
225
            case httpd_cgi:handle_headers(Headers) of
 
226
                {proceed, AbsPath} ->
 
227
                    {proceed, [{real_name, 
 
228
                                httpd_util:split_path(AbsPath)} | 
 
229
                               ModData#mod.data]};
 
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
 
235
                        true ->
 
236
                            send_headers(ModData, Status, 
 
237
                                         [{"connection", "close"}
 
238
                                           | HTTPHeaders]);
 
239
                        false ->
 
240
                            send_headers(ModData, Status,
 
241
                                         [{"transfer-encoding",
 
242
                                           "chunked"} | HTTPHeaders])
 
243
                    end,
 
244
                    handle_body(Port, ModData, Body, Timeout, size(Body),
 
245
                                IsDisableChunkedSend)
 
246
            end;
 
247
        {'EXIT', Port, Reason} ->
 
248
            process_flag(trap_exit, false),
 
249
            {proceed, [{status, {400, none, reason(Reason)}} |
 
250
                       ModData#mod.data]};
 
251
        timeout ->
 
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]}
 
257
    end.
 
258
            
 
259
receive_headers(Port, Module, Function, Args, Timeout) ->
 
260
      receive
 
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}} ->
 
267
                      {Headers, Body}
 
268
              end;
 
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})
 
273
      after Timeout ->
 
274
              timeout
 
275
      end.
 
276
 
 
277
send_headers(ModData, {StatusCode, _}, HTTPHeaders) ->
 
278
    ExtraHeaders = httpd_response:cache_headers(ModData),
 
279
    httpd_response:send_header(ModData, StatusCode, 
 
280
                               ExtraHeaders ++ HTTPHeaders).
 
281
 
 
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]};
 
286
 
 
287
handle_body(Port, ModData, Body, Timeout, Size, IsDisableChunkedSend) ->
 
288
    httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend),
 
289
    receive 
 
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}} |
 
297
                       ModData#mod.data]};
 
298
        {'EXIT', Port, Reason} when is_port(Port) ->
 
299
            process_flag(trap_exit, false),
 
300
            {proceed, [{status, {400, none, reason(Reason)}} | 
 
301
                       ModData#mod.data]};
 
302
        {'EXIT', Pid, Reason} when is_pid(Pid) ->
 
303
            exit({mod_cgi_linked_process_died, Pid, Reason})
 
304
    after Timeout ->
 
305
            (catch port_close(Port)), % KILL the port !!!!
 
306
            process_flag(trap_exit,false),
 
307
            {proceed,[{response, {already_sent, 200, Size}} |
 
308
                      ModData#mod.data]}
 
309
    end.
 
310
 
 
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(_, _) ->
 
320
    [].
 
321
 
 
322
cgi_timeout(Db) ->
 
323
    httpd_util:lookup(Db, cgi_timeout, ?DEFAULT_CGI_TIMEOUT).
 
324
 
 
325
%% Convert error to printable string
 
326
%%
 
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])).