Skip to content
This repository was archived by the owner on Dec 2, 2018. It is now read-only.

Commit f06b877

Browse files
committed
Remove error page handling.
This software did provide an error page that was designed to help with debugging the CGI script being executed. Although somewhat useful there are a few reasons this needed to be removed: * Security: It might expose sensitive information to the user. While this software is primarily for development purposes there might be valid reasons to use it in production. By providing that useful debugging information to users we are also providing it to potential hackers. * Reduced Redundantancy: Rack provide Rack::ShowExceptions which provides very similar functionality. So we can just use that and provide almost the same info for debugging purposes. * Flexibility: In addition to Rack::ShowExceptions there are other exception handling rack middleware that a project may want to use. By just throwning an exception any library can be used. Or the other middleware may capture the error and do something intelligent. * Simplified Code: By removing our own error page we remove a lot of code. We also remove the temp file hack used to capture the standard out. * Relevancy: I am looking to switch the PHP support off CGI and onto the built-in PHP server. The error page was too specific to CGI. I debated if this change break the API. Obviously if the app assumes that we handle errors and doesn't have some sort of exception capturing it could be considered as an API breakage. But just about every Rack stack has some sort of module of last resort to handle un-excepted exceptions. So overall I felt it should not break any real world apps and therefore isn't an API breaking release.
1 parent 5964f61 commit f06b877

21 files changed

+260
-471
lines changed

README.rdoc

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@
22

33
Rack Legacy tries to provide interaction with legacy environments like
44
PHP and CGI while still getting the Rack portability so you don't need
5-
a full Apache stack.
5+
a full Apache/lighttpd stack.
66

77
This software is currently BETA quality. Use at your own risk.
88

99
= Purpose
1010

11-
The PRIMARY use case for this software is for development of an
12-
application where Ruby is being used but there are also some legacy
13-
PHP or CGI that is running along-side the Ruby application. This
14-
middleware allows you to do that development without the full Apache
15-
stack.
11+
The PRIMARY use case is while developing an application where Ruby is
12+
being used but there are also some legacy PHP or CGI that is running
13+
along-side your Ruby. This middleware allows you to do that development
14+
without the full Apache stack.
1615

1716
When you take the application to a production environment you can either
18-
leave this middleware in or use a full Apache stack to get added
19-
performance and security.
17+
leave this middleware in or use a full Apache stack to get more battle
18+
tested performance and security.
2019

2120
= USAGE
2221

WISHLIST.rdoc

Lines changed: 0 additions & 19 deletions
This file was deleted.

lib/rack-legacy.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
require 'rack/legacy/error_page'
2-
require 'rack/legacy/cgi'
3-
require 'rack/legacy/php'
1+
require 'rack/legacy'

lib/rack/legacy.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Define namespace
2+
module Rack
3+
module Legacy
4+
5+
# Thrown when Rack-Legacy encounters some sort of problem delegating
6+
# the request to the legacy environment.
7+
class ExecutionError < StandardError
8+
end
9+
10+
end
11+
end
12+
13+
require 'rack/legacy/cgi'
14+
require 'rack/legacy/php'

lib/rack/legacy/cgi.rb

Lines changed: 94 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,109 @@
11
require 'fileutils'
2-
require 'tempfile'
32
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
193

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
396

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
4515

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
8972
end
9073
end
9174
end
9275

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+
9599
end
96100
end
97101

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'
98105

106+
# Send it all back to rack
107+
[status, headers, [body]]
99108
end
100109
end

lib/rack/legacy/error_page.rb

Lines changed: 0 additions & 116 deletions
This file was deleted.

0 commit comments

Comments
 (0)