class Rack::CGI

Public Class Methods

new(app, args = {}) click to toggle source
# File lib/rack/cgi.rb, line 11
def initialize app, args = {}
  @app = app
  @opts = args.select {|k, _| k.is_a? Symbol}
  @rules = args.select {|k, _| !k.is_a? Symbol}

  @root = @opts[:cgi_path]
  if @root !~ /^\//
    @root = ::File.join(Dir.pwd, @root)
  end

  @index = @opts[:index] || []
  @index = [@index] if not @index.is_a? Array

  @dir_redirect = @opts[:dir_redirect]
end

Public Instance Methods

call(env) click to toggle source
# File lib/rack/cgi.rb, line 152
def call(env)
  catch :rack_cgi_result do
    path = solve_path env["PATH_INFO"]
    if not path
      return @app.call(env)
    end

    rule = match_cgi_rule path
    if not rule
      return @app.call(env)
    end

    cgi_env = cgi_env(env, path)
    code, out, err = run_cgi rule, path, cgi_env
    if code == 0
      parse_output out
    else
      report_error code, out, err, cgi_env
    end
  end
end
cgi_env(env, path) click to toggle source
# File lib/rack/cgi.rb, line 46
def cgi_env env, path
  env = env.select {|k, _| k =~ /^[A-Z]/}
  env['SCRIPT_FILENAME'] = path
  env['DOCUMENT_ROOT'] = @root
  env['REDIRECT_STATUS'] = "200"
  env
end
match_cgi_rule(path) click to toggle source
# File lib/rack/cgi.rb, line 54
def match_cgi_rule path
  @rules.each do |m, r|
    if m =~ path
      return r
    end
  end
  return nil
end
parse_output(output) click to toggle source
# File lib/rack/cgi.rb, line 109
def parse_output output
  header, content = split_header_content output
 
  h = {}
  header.each_line do |l|
    k, v = l.split ':', 2
    k.strip!
    v.strip!
    h[k.downcase] = [k, v]
  end

  if h['status']
    status = h['status'][1].to_i
    h.delete 'status'
  else
    status = 200
  end

  header_hash = Hash[h.values]

  [status, header_hash, [content]]
end
report_error(code, out, err, cgi_env) click to toggle source
# File lib/rack/cgi.rb, line 132
def report_error code, out, err, cgi_env
  h = {'Content-Type' => 'text/plain'}
  status = 500
  reports = []
  
  reports << "CGI Error!\n\n"
  reports << "stdout output:\n"
  reports << out
  reports << "\n"
  reports << "stderr output:\n"
  reports << err
  reports << "\n"
  reports << "environments:\n"
  cgi_env.each do |k, v|
    reports << "#{k} => #{v}\n"
  end

  [status, h, reports]
end
run(env, rule, path) click to toggle source
# File lib/rack/cgi.rb, line 173
def run(env, rule, path)
  catch :rack_cgi_result do
    cgi_env = cgi_env(env, path)
    code, out, err = run_cgi rule, path, cgi_env
    if code == 0
      parse_output out
    else
      report_error code, out, err, cgi_env
    end
  end
end
run_cgi(rule, path, env) click to toggle source
# File lib/rack/cgi.rb, line 63
def run_cgi rule, path, env
  if rule.empty?
    args = [path]
  else
    args = [rule, path]
  end

  process = ChildProcess.build(*args)
  process.io.stdout = Tempfile.new('rack-cgi-stdout')
  process.io.stderr = Tempfile.new('rack-cgi-stderr')
  env.each do |k, v|
    process.environment[k] = v
  end
  process.cwd = Dir.pwd
  process.start
  process.wait

  cont_out = ::File.read(process.io.stdout.path)
  cont_err = ::File.read(process.io.stderr.path)
  process.io.stdout.unlink
  process.io.stderr.unlink

  [process.exit_code, cont_out, cont_err]
end
solve_path(path) click to toggle source
# File lib/rack/cgi.rb, line 27
def solve_path path
  path = ::File.join(@root, path)
  if ::File.directory? path
    @index.each do |f|
      path2 = ::File.join(path, f)
      if ::File.file? path2
        if @dir_redirect and path !~ %r{/$}
          throw :rack_cgi_result, [302, {'Location' => path + '/'}, [""]]
        else
          return path2
        end
      end
    end
  else
    return path if ::File.file? path
  end
  nil
end
split_header_content(output) click to toggle source
# File lib/rack/cgi.rb, line 88
def split_header_content output
  lines = output.lines.to_a
  header = []
  
  until lines.empty? 
    l = lines.shift
    if l == "\n" or l == "\r\n"
      # find break line between header and content
      return header.join, lines.join
    elsif l =~ /:/
      header << l
    else
      # content break header ruler, so deal as no header
      return "", output
    end
  end

  # deal all content as header
  return output, ""
end