|
1 | 1 | require 'fileutils'
|
2 |
| -require 'tempfile' |
3 | 2 | require 'shellwords'
|
4 |
| -require 'rack/legacy/error_page' |
5 |
| - |
6 |
| -module Rack |
7 |
| - module Legacy |
8 |
| - class Cgi |
9 |
| - attr_reader :public_dir |
10 |
| - |
11 |
| - # Will setup a new instance of the Cgi middleware executing |
12 |
| - # programs located in the given public_dir |
13 |
| - # |
14 |
| - # use Rack::Legacy::Cgi, 'cgi-bin' |
15 |
| - def initialize(app, public_dir=FileUtils.pwd) |
16 |
| - @app = app |
17 |
| - @public_dir = public_dir |
18 |
| - end |
19 | 3 |
|
20 |
| - # Middleware, so if it looks like we can run it then do so. |
21 |
| - # Otherwise send it on for someone else to handle. |
22 |
| - def call(env) |
23 |
| - if valid? env['PATH_INFO'] |
24 |
| - run env, full_path(env['PATH_INFO']) |
25 |
| - else |
26 |
| - @app.call env |
27 |
| - end |
28 |
| - end |
29 |
| - |
30 |
| - # Check to ensure the path exists and it is a child of the |
31 |
| - # public directory. |
32 |
| - def valid?(path) |
33 |
| - fp = full_path path |
34 |
| - fp.start_with?(::File.expand_path public_dir) && |
35 |
| - ::File.file?(fp) && ::File.executable?(fp) |
36 |
| - end |
37 |
| - |
38 |
| - protected |
| 4 | +class Rack::Legacy::Cgi |
| 5 | + attr_reader :public_dir |
39 | 6 |
|
40 |
| - # Returns the path with the public_dir pre-pended and with the |
41 |
| - # paths expanded (so we can check for security issues) |
42 |
| - def full_path(path) |
43 |
| - ::File.expand_path ::File.join(public_dir, path) |
44 |
| - end |
| 7 | + # Will setup a new instance of the Cgi middleware executing |
| 8 | + # programs located in the given public_dir |
| 9 | + # |
| 10 | + # use Rack::Legacy::Cgi, 'cgi-bin' |
| 11 | + def initialize(app, public_dir=FileUtils.pwd) |
| 12 | + @app = app |
| 13 | + @public_dir = public_dir |
| 14 | + end |
45 | 15 |
|
46 |
| - # Will run the given path with the given environment |
47 |
| - def run(env, *path) |
48 |
| - env['DOCUMENT_ROOT'] = public_dir |
49 |
| - env['SERVER_SOFTWARE'] = 'Rack Legacy' |
50 |
| - status = 200 |
51 |
| - headers = {} |
52 |
| - body = '' |
53 |
| - |
54 |
| - stderr = Tempfile.new 'legacy-rack-stderr' |
55 |
| - IO.popen('-', 'r+') do |io| |
56 |
| - if io.nil? # Child |
57 |
| - $stderr.reopen stderr.path |
58 |
| - env.each do |key, value| |
59 |
| - ENV[key] = value if |
60 |
| - value.respond_to?(:to_str) && key =~ /^[A-Z_]+$/ |
61 |
| - end |
62 |
| - exec *path |
63 |
| - else # Parent |
64 |
| - io.write(env['rack.input'].read) if env['rack.input'] |
65 |
| - io.close_write |
66 |
| - until io.eof? || (line = io.readline.chomp) == '' |
67 |
| - if line =~ /\s*\:\s*/ |
68 |
| - key, value = line.split(/\s*\:\s*/, 2) |
69 |
| - if headers.has_key? key |
70 |
| - headers[key] += "\n" + value |
71 |
| - else |
72 |
| - headers[key] = value |
73 |
| - end |
74 |
| - end |
75 |
| - end |
76 |
| - body = io.read |
77 |
| - stderr.rewind |
78 |
| - stderr = stderr.read |
79 |
| - Process.wait |
80 |
| - unless $?.exitstatus == 0 |
81 |
| - status = 500 |
82 |
| - cmd = env.inject(path) do |assignments, (key, value)| |
83 |
| - assignments.unshift "#{key}=#{value.to_s.shellescape}" if |
84 |
| - value.respond_to?(:to_str) && key =~ /^[A-Z_]+$/ |
85 |
| - assignments |
86 |
| - end * ' ' |
87 |
| - body = ErrorPage.new(env, headers, body, stderr, cmd).to_s |
88 |
| - headers = {'Content-Type' => 'text/html'} |
| 16 | + # Middleware, so if it looks like we can run it then do so. |
| 17 | + # Otherwise send it on for someone else to handle. |
| 18 | + def call(env) |
| 19 | + if valid? env['PATH_INFO'] |
| 20 | + run env, full_path(env['PATH_INFO']) |
| 21 | + else |
| 22 | + @app.call env |
| 23 | + end |
| 24 | + end |
| 25 | + |
| 26 | + # Check to ensure the path exists and it is a child of the |
| 27 | + # public directory. |
| 28 | + def valid?(path) |
| 29 | + fp = full_path path |
| 30 | + fp.start_with?(::File.expand_path public_dir) && |
| 31 | + ::File.file?(fp) && ::File.executable?(fp) |
| 32 | + end |
| 33 | + |
| 34 | + protected |
| 35 | + |
| 36 | + # Returns the path with the public_dir pre-pended and with the |
| 37 | + # paths expanded (so we can check for security issues) |
| 38 | + def full_path(path) |
| 39 | + ::File.expand_path ::File.join(public_dir, path) |
| 40 | + end |
| 41 | + |
| 42 | + # Will run the given path with the given environment |
| 43 | + def run(env, *path) |
| 44 | + env['DOCUMENT_ROOT'] = public_dir |
| 45 | + env['SERVER_SOFTWARE'] = 'Rack Legacy' |
| 46 | + status = 200 |
| 47 | + headers = {} |
| 48 | + body = '' |
| 49 | + |
| 50 | + IO.popen('-', 'r+') do |io| |
| 51 | + if io.nil? # Child |
| 52 | + # Pass on all uppercase environment variables. Only uppercase |
| 53 | + # since Rack uses lower case ones internally. |
| 54 | + env.each do |key, value| |
| 55 | + ENV[key] = value if |
| 56 | + value.respond_to?(:to_str) && key =~ /^[A-Z_]+$/ |
| 57 | + end |
| 58 | + exec *path |
| 59 | + else # Parent |
| 60 | + # Send request to CGI sub-process |
| 61 | + io.write(env['rack.input'].read) if env['rack.input'] |
| 62 | + io.close_write |
| 63 | + |
| 64 | + # Parse headers coming back from sub-process |
| 65 | + until io.eof? || (line = io.readline.chomp) == '' |
| 66 | + if line =~ /\s*\:\s*/ |
| 67 | + key, value = line.split(/\s*\:\s*/, 2) |
| 68 | + if headers.has_key? key |
| 69 | + headers[key] += "\n" + value |
| 70 | + else |
| 71 | + headers[key] = value |
89 | 72 | end
|
90 | 73 | end
|
91 | 74 | end
|
92 | 75 |
|
93 |
| - status = headers.delete('Status').to_i if headers.has_key? 'Status' |
94 |
| - [status, headers, [body]] |
| 76 | + # Get response and wait for process to complete. |
| 77 | + body = io.read |
| 78 | + Process.wait |
| 79 | + |
| 80 | + # If there was an error throw it up the execution stack so |
| 81 | + # someone can rescue to provide info to the right person. |
| 82 | + unless $?.exitstatus == 0 |
| 83 | + # Build full command for easier debugging. Output to |
| 84 | + # stderr to prevent user from getting too much information. |
| 85 | + cmd = env.inject(path) do |assignments, (key, value)| |
| 86 | + assignments.unshift "#{key}=#{value.to_s.shellescape}" if |
| 87 | + value.respond_to?(:to_str) && key =~ /^[A-Z_]+$/ |
| 88 | + assignments |
| 89 | + end * ' ' |
| 90 | + $stderr.puts <<ERROR |
| 91 | +CGI exited with status #{$?.exitstatus}. The full command run was: |
| 92 | +
|
| 93 | +#{cmd} |
| 94 | +
|
| 95 | +ERROR |
| 96 | + raise Rack::Legacy::ExecutionError |
| 97 | + end |
| 98 | + |
95 | 99 | end
|
96 | 100 | end
|
97 | 101 |
|
| 102 | + # Extract status from sub-process if it is doing something other |
| 103 | + # than a 200 response. |
| 104 | + status = headers.delete('Status').to_i if headers.has_key? 'Status' |
98 | 105 |
|
| 106 | + # Send it all back to rack |
| 107 | + [status, headers, [body]] |
99 | 108 | end
|
100 | 109 | end
|
0 commit comments