1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
5 # This code is based on the original CGIWrapper from Mongrel
6 # Copyright (c) 2005 Zed A. Shaw
7 # Copyright (c) 2009 Eric Wong
8 # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
9 # the GPLv2+ (GPLv3+ preferred)
11 # Additional work donated by contributors. See CONTRIBUTORS for more info.
17 # The beginning of a complete wrapper around Unicorn's internal HTTP
18 # processing system but maintaining the original Ruby CGI module. Use
19 # this only as a crutch to get existing CGI based systems working. It
20 # should handle everything, but please notify us if you see special
21 # warnings. This work is still very alpha so we need testers to help
22 # work out the various corner cases.
23 class Unicorn::CGIWrapper < ::CGI
24 undef_method :env_table
25 attr_reader :env_table
28 # these are stripped out of any keys passed to CGIWrapper.header function
29 NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless
30 CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this?
31 CHARSET = 'charset'.freeze # this gets appended to Content-Type
32 COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers
33 STATUS = 'status'.freeze # stored as @status
34 Status = 'Status'.freeze # code + human-readable text, Rails sets this
36 # some of these are common strings, but this is the only module
37 # using them and the reason they're not in Unicorn::Const
38 SET_COOKIE = 'Set-Cookie'.freeze
39 CONTENT_TYPE = 'Content-Type'.freeze
40 CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH
41 RACK_INPUT = 'rack.input'.freeze
42 RACK_ERRORS = 'rack.errors'.freeze
44 # this maps CGI header names to HTTP header names
47 'type' => CONTENT_TYPE,
48 'server' => 'Server'.freeze,
49 'language' => 'Content-Language'.freeze,
50 'expires' => 'Expires'.freeze,
51 'length' => CONTENT_LENGTH,
54 # Takes an a Rackable environment, plus any additional CGI.new
55 # arguments These are used internally to create a wrapper around the
56 # real CGI while maintaining Rack/Unicorn's view of the world. This
57 # this will NOT deal well with large responses that take up a lot of
58 # memory, but neither does the CGI nor the original CGIWrapper from
60 def initialize(rack_env, *args)
64 @headv = Hash.new { |hash,key| hash[key] = [] }
65 @body = StringIO.new("")
69 # finalizes the response in a way Rack applications would expect
71 # @head[CONTENT_LENGTH] ||= @body.size
72 @headv[SET_COOKIE].concat(@output_cookies) if @output_cookies
73 @headv.each_pair do |key,value|
74 @head[key] ||= value.join("\n") unless value.empty?
77 # Capitalized "Status:", with human-readable status code (e.g. "200 OK")
78 @status ||= @head.delete(Status)
80 [ @status || 500, @head, [ @body.string ] ]
83 # The header is typically called to send back the header. In our case we
84 # collect it into a hash for later usage. This can be called multiple
85 # times to set different cookies.
86 def header(options = "text/html")
87 # if they pass in a string then just write the Content-Type
89 @head[CONTENT_TYPE] ||= options
91 HEADER_MAP.each_pair do |from, to|
92 from = options.delete(from) or next
96 @head[CONTENT_TYPE] ||= "text/html"
97 if charset = options.delete(CHARSET)
98 @head[CONTENT_TYPE] << "; charset=#{charset}"
101 # lots of ways to set cookies
102 if cookie = options.delete(COOKIE)
103 set_cookies = @headv[SET_COOKIE]
106 cookie.each { |c| set_cookies << c.to_s }
108 cookie.each_value { |c| set_cookies << c.to_s }
110 set_cookies << cookie.to_s
113 @status ||= options.delete(STATUS) # all lower-case
115 # drop the keys we don't want anymore
117 options.delete(CONNECTION)
119 # finally, set the rest of the headers as-is, allowing duplicates
120 options.each_pair { |k,v| @headv[k] << v }
123 # doing this fakes out the cgi library to think the headers are empty
124 # we then do the real headers in the out function call later
128 # The dumb thing is people can call header or this or both and in
129 # any order. So, we just reuse header and then finalize the
130 # HttpResponse the right way. This will have no effect if called
131 # the second time if the first "outputted" anything.
132 def out(options = "text/html")
134 @body.size == 0 or return
135 @body << yield if block_given?
138 # Used to wrap the normal stdinput variable used inside CGI.
140 @env_table[RACK_INPUT]
143 # return a pointer to the StringIO body since it's STDOUT-like