about summary refs log tree commit homepage
path: root/lib/mongrel/cgi.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mongrel/cgi.rb')
-rw-r--r--lib/mongrel/cgi.rb147
1 files changed, 147 insertions, 0 deletions
diff --git a/lib/mongrel/cgi.rb b/lib/mongrel/cgi.rb
new file mode 100644
index 0000000..3d7d25a
--- /dev/null
+++ b/lib/mongrel/cgi.rb
@@ -0,0 +1,147 @@
+require 'cgi'
+
+module Mongrel
+    # The beginning of a complete wrapper around Mongrel's internal HTTP processing
+  # system but maintaining the original Ruby CGI module.  Use this only as a crutch
+  # to get existing CGI based systems working.  It should handle everything, but please
+  # notify me if you see special warnings.  This work is still very alpha so I need
+  # testers to help work out the various corner cases.
+  class CGIWrapper < ::CGI
+    public :env_table
+    attr_reader :options
+
+    # these are stripped out of any keys passed to CGIWrapper.header function
+    REMOVED_KEYS = [ "nph","status","server","connection","type",
+                     "charset","length","language","expires"]
+
+    # Takes an HttpRequest and HttpResponse object, plus any additional arguments
+    # normally passed to CGI.  These are used internally to create a wrapper around
+    # the real CGI while maintaining Mongrel's view of the world.
+    def initialize(request, response, *args)
+      @request = request
+      @response = response
+      @args = *args
+      @input = StringIO.new(request.body)
+      @head = {}
+      @out_called = false
+      super(*args)
+    end
+    
+    # The header is typically called to send back the header.  In our case we
+    # collect it into a hash for later usage.
+    #
+    # nph -- Mostly ignored.  It'll output the date.
+    # connection -- Completely ignored.  Why is CGI doing this?
+    # length -- Ignored since Mongrel figures this out from what you write to output.
+    #
+    def header(options = "text/html")
+      # if they pass in a string then just write the Content-Type
+      if options.class == String
+        @head['Content-Type'] = options unless @head['Content-Type']
+      else
+        # convert the given options into what Mongrel wants
+        @head['Content-Type'] = options['type'] || "text/html"
+        @head['Content-Type'] += "; charset=" + options['charset'] if options.has_key? "charset" if options['charset']
+        
+        # setup date only if they use nph
+        @head['Date'] = CGI::rfc1123_date(Time.now) if options['nph']
+
+        # setup the server to use the default or what they set
+        @head['Server'] = options['server'] || env_table['SERVER_SOFTWARE']
+
+        # remaining possible options they can give
+        @head['Status'] = options['status'] if options['status']
+        @head['Content-Language'] = options['language'] if options['language']
+        @head['Expires'] = options['expires'] if options['expires']
+
+        # drop the keys we don't want anymore
+        REMOVED_KEYS.each {|k| options.delete(k) }
+
+        # finally just convert the rest raw (which puts 'cookie' directly)
+        # 'cookie' is translated later as we write the header out
+        options.each{|k,v| @head[k] = v}
+      end
+
+      # doing this fakes out the cgi library to think the headers are empty
+      # we then do the real headers in the out function call later
+      ""
+    end
+
+    # Takes any 'cookie' setting and sends it over the Mongrel header,
+    # then removes the setting from the options. If cookie is an
+    # Array or Hash then it sends those on with .to_s, otherwise
+    # it just calls .to_s on it and hopefully your "cookie" can
+    # write itself correctly.
+    def send_cookies(to)
+      # convert the cookies based on the myriad of possible ways to set a cookie
+      if @head['cookie']
+        cookie = @head['cookie']
+        case cookie
+        when Array
+          cookie.each {|c| to['Set-Cookie'] = c.to_s }
+        when Hash
+          cookie.each_value {|c| to['Set-Cookie'] = c.to_s}
+        else
+          to['Set-Cookie'] = options['cookie'].to_s
+        end
+        
+        @head.delete('cookie')
+
+        # @output_cookies seems to never be used, but we'll process it just in case
+        @output_cookies.each {|c| to['Set-Cookie'] = c.to_s } if @output_cookies
+      end
+    end
+    
+    # The dumb thing is people can call header or this or both and in any order.
+    # So, we just reuse header and then finalize the HttpResponse the right way.
+    # Status is taken from the various options and converted to what Mongrel needs
+    # via the CGIWrapper.status function.
+    def out(options = "text/html")
+      return if @out_called  # don't do this more than once
+
+      header(options)
+
+      @response.start status do |head, out|
+        send_cookies(head)
+        
+        @head.each {|k,v| head[k] = v}
+        out.write(yield || "")
+      end
+    end
+    
+    # Computes the status once, but lazily so that people who call header twice
+    # don't get penalized.  Because CGI insists on including the options status
+    # message in the status we have to do a bit of parsing.
+    def status
+      if not @status
+        stat = @head["Status"]
+        stat = stat.split(' ')[0] if stat
+
+        @status = stat || "200"
+      end
+
+      @status
+    end
+
+    # Used to wrap the normal args variable used inside CGI.
+    def args
+      @args
+    end
+    
+    # Used to wrap the normal env_table variable used inside CGI.
+    def env_table
+      @request.params
+    end
+    
+    # Used to wrap the normal stdinput variable used inside CGI.
+    def stdinput
+      @input
+    end
+    
+    # The stdoutput should be completely bypassed but we'll drop a warning just in case
+    def stdoutput
+      STDERR.puts "WARNING: Your program is doing something not expected.  Please tell Zed that stdoutput was used and what software you are running.  Thanks."
+      @response.body
+    end    
+  end
+end