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