1# 2# cgi.rb -- Yet another CGI library 3# 4# Author: IPR -- Internet Programming with Ruby -- writers 5# Copyright (c) 2003 Internet Programming with Ruby writers. All rights 6# reserved. 7# 8# $Id: cgi.rb 38945 2013-01-26 01:12:54Z drbrain $ 9 10require "webrick/httprequest" 11require "webrick/httpresponse" 12require "webrick/config" 13require "stringio" 14 15module WEBrick 16 17 # A CGI library using WEBrick requests and responses. 18 # 19 # Example: 20 # 21 # class MyCGI < WEBrick::CGI 22 # def do_GET req, res 23 # res.body = 'it worked!' 24 # res.status = 200 25 # end 26 # end 27 # 28 # MyCGI.new.start 29 30 class CGI 31 32 # The CGI error exception class 33 34 CGIError = Class.new(StandardError) 35 36 ## 37 # The CGI configuration. This is based on WEBrick::Config::HTTP 38 39 attr_reader :config 40 41 ## 42 # The CGI logger 43 44 attr_reader :logger 45 46 ## 47 # Creates a new CGI interface. 48 # 49 # The first argument in +args+ is a configuration hash which would update 50 # WEBrick::Config::HTTP. 51 # 52 # Any remaining arguments are stored in the <code>@options</code> instance 53 # variable for use by a subclass. 54 55 def initialize(*args) 56 if defined?(MOD_RUBY) 57 unless ENV.has_key?("GATEWAY_INTERFACE") 58 Apache.request.setup_cgi_env 59 end 60 end 61 if %r{HTTP/(\d+\.\d+)} =~ ENV["SERVER_PROTOCOL"] 62 httpv = $1 63 end 64 @config = WEBrick::Config::HTTP.dup.update( 65 :ServerSoftware => ENV["SERVER_SOFTWARE"] || "null", 66 :HTTPVersion => HTTPVersion.new(httpv || "1.0"), 67 :RunOnCGI => true, # to detect if it runs on CGI. 68 :NPH => false # set true to run as NPH script. 69 ) 70 if config = args.shift 71 @config.update(config) 72 end 73 @config[:Logger] ||= WEBrick::BasicLog.new($stderr) 74 @logger = @config[:Logger] 75 @options = args 76 end 77 78 ## 79 # Reads +key+ from the configuration 80 81 def [](key) 82 @config[key] 83 end 84 85 ## 86 # Starts the CGI process with the given environment +env+ and standard 87 # input and output +stdin+ and +stdout+. 88 89 def start(env=ENV, stdin=$stdin, stdout=$stdout) 90 sock = WEBrick::CGI::Socket.new(@config, env, stdin, stdout) 91 req = HTTPRequest.new(@config) 92 res = HTTPResponse.new(@config) 93 unless @config[:NPH] or defined?(MOD_RUBY) 94 def res.setup_header 95 unless @header["status"] 96 phrase = HTTPStatus::reason_phrase(@status) 97 @header["status"] = "#{@status} #{phrase}" 98 end 99 super 100 end 101 def res.status_line 102 "" 103 end 104 end 105 106 begin 107 req.parse(sock) 108 req.script_name = (env["SCRIPT_NAME"] || File.expand_path($0)).dup 109 req.path_info = (env["PATH_INFO"] || "").dup 110 req.query_string = env["QUERY_STRING"] 111 req.user = env["REMOTE_USER"] 112 res.request_method = req.request_method 113 res.request_uri = req.request_uri 114 res.request_http_version = req.http_version 115 res.keep_alive = req.keep_alive? 116 self.service(req, res) 117 rescue HTTPStatus::Error => ex 118 res.set_error(ex) 119 rescue HTTPStatus::Status => ex 120 res.status = ex.code 121 rescue Exception => ex 122 @logger.error(ex) 123 res.set_error(ex, true) 124 ensure 125 req.fixup 126 if defined?(MOD_RUBY) 127 res.setup_header 128 Apache.request.status_line = "#{res.status} #{res.reason_phrase}" 129 Apache.request.status = res.status 130 table = Apache.request.headers_out 131 res.header.each{|key, val| 132 case key 133 when /^content-encoding$/i 134 Apache::request.content_encoding = val 135 when /^content-type$/i 136 Apache::request.content_type = val 137 else 138 table[key] = val.to_s 139 end 140 } 141 res.cookies.each{|cookie| 142 table.add("Set-Cookie", cookie.to_s) 143 } 144 Apache.request.send_http_header 145 res.send_body(sock) 146 else 147 res.send_response(sock) 148 end 149 end 150 end 151 152 ## 153 # Services the request +req+ which will fill in the response +res+. See 154 # WEBrick::HTTPServlet::AbstractServlet#service for details. 155 156 def service(req, res) 157 method_name = "do_" + req.request_method.gsub(/-/, "_") 158 if respond_to?(method_name) 159 __send__(method_name, req, res) 160 else 161 raise HTTPStatus::MethodNotAllowed, 162 "unsupported method `#{req.request_method}'." 163 end 164 end 165 166 ## 167 # Provides HTTP socket emulation from the CGI environment 168 169 class Socket # :nodoc: 170 include Enumerable 171 172 private 173 174 def initialize(config, env, stdin, stdout) 175 @config = config 176 @env = env 177 @header_part = StringIO.new 178 @body_part = stdin 179 @out_port = stdout 180 @out_port.binmode 181 182 @server_addr = @env["SERVER_ADDR"] || "0.0.0.0" 183 @server_name = @env["SERVER_NAME"] 184 @server_port = @env["SERVER_PORT"] 185 @remote_addr = @env["REMOTE_ADDR"] 186 @remote_host = @env["REMOTE_HOST"] || @remote_addr 187 @remote_port = @env["REMOTE_PORT"] || 0 188 189 begin 190 @header_part << request_line << CRLF 191 setup_header 192 @header_part << CRLF 193 @header_part.rewind 194 rescue Exception 195 raise CGIError, "invalid CGI environment" 196 end 197 end 198 199 def request_line 200 meth = @env["REQUEST_METHOD"] || "GET" 201 unless url = @env["REQUEST_URI"] 202 url = (@env["SCRIPT_NAME"] || File.expand_path($0)).dup 203 url << @env["PATH_INFO"].to_s 204 url = WEBrick::HTTPUtils.escape_path(url) 205 if query_string = @env["QUERY_STRING"] 206 unless query_string.empty? 207 url << "?" << query_string 208 end 209 end 210 end 211 # we cannot get real HTTP version of client ;) 212 httpv = @config[:HTTPVersion] 213 return "#{meth} #{url} HTTP/#{httpv}" 214 end 215 216 def setup_header 217 @env.each{|key, value| 218 case key 219 when "CONTENT_TYPE", "CONTENT_LENGTH" 220 add_header(key.gsub(/_/, "-"), value) 221 when /^HTTP_(.*)/ 222 add_header($1.gsub(/_/, "-"), value) 223 end 224 } 225 end 226 227 def add_header(hdrname, value) 228 unless value.empty? 229 @header_part << hdrname << ": " << value << CRLF 230 end 231 end 232 233 def input 234 @header_part.eof? ? @body_part : @header_part 235 end 236 237 public 238 239 def peeraddr 240 [nil, @remote_port, @remote_host, @remote_addr] 241 end 242 243 def addr 244 [nil, @server_port, @server_name, @server_addr] 245 end 246 247 def gets(eol=LF, size=nil) 248 input.gets(eol, size) 249 end 250 251 def read(size=nil) 252 input.read(size) 253 end 254 255 def each 256 input.each{|line| yield(line) } 257 end 258 259 def eof? 260 input.eof? 261 end 262 263 def <<(data) 264 @out_port << data 265 end 266 267 def cert 268 return nil unless defined?(OpenSSL) 269 if pem = @env["SSL_SERVER_CERT"] 270 OpenSSL::X509::Certificate.new(pem) unless pem.empty? 271 end 272 end 273 274 def peer_cert 275 return nil unless defined?(OpenSSL) 276 if pem = @env["SSL_CLIENT_CERT"] 277 OpenSSL::X509::Certificate.new(pem) unless pem.empty? 278 end 279 end 280 281 def peer_cert_chain 282 return nil unless defined?(OpenSSL) 283 if @env["SSL_CLIENT_CERT_CHAIN_0"] 284 keys = @env.keys 285 certs = keys.sort.collect{|k| 286 if /^SSL_CLIENT_CERT_CHAIN_\d+$/ =~ k 287 if pem = @env[k] 288 OpenSSL::X509::Certificate.new(pem) unless pem.empty? 289 end 290 end 291 } 292 certs.compact 293 end 294 end 295 296 def cipher 297 return nil unless defined?(OpenSSL) 298 if cipher = @env["SSL_CIPHER"] 299 ret = [ cipher ] 300 ret << @env["SSL_PROTOCOL"] 301 ret << @env["SSL_CIPHER_USEKEYSIZE"] 302 ret << @env["SSL_CIPHER_ALGKEYSIZE"] 303 ret 304 end 305 end 306 end 307 end 308end 309