]> source.dussan.org Git - sonarqube.git/blob
ff1702e845eab9b4056bf5a13562ba0f336894d3
[sonarqube.git] /
1 require 'digest/md5'
2
3 module ActionController # :nodoc:
4   # Represents an HTTP response generated by a controller action. One can use
5   # an ActionController::Response object to retrieve the current state
6   # of the response, or customize the response. An Response object can
7   # either represent a "real" HTTP response (i.e. one that is meant to be sent
8   # back to the web browser) or a test response (i.e. one that is generated
9   # from integration tests). See CgiResponse and TestResponse, respectively.
10   #
11   # Response is mostly a Ruby on Rails framework implement detail, and
12   # should never be used directly in controllers. Controllers should use the
13   # methods defined in ActionController::Base instead. For example, if you want
14   # to set the HTTP response's content MIME type, then use
15   # ActionControllerBase#headers instead of Response#headers.
16   #
17   # Nevertheless, integration tests may want to inspect controller responses in
18   # more detail, and that's when Response can be useful for application
19   # developers. Integration test methods such as
20   # ActionController::Integration::Session#get and
21   # ActionController::Integration::Session#post return objects of type
22   # TestResponse (which are of course also of type Response).
23   #
24   # For example, the following demo integration "test" prints the body of the
25   # controller response to the console:
26   #
27   #  class DemoControllerTest < ActionController::IntegrationTest
28   #    def test_print_root_path_to_console
29   #      get('/')
30   #      puts @response.body
31   #    end
32   #  end
33   class Response < Rack::Response
34     DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
35     attr_accessor :request
36
37     attr_accessor :session, :assigns, :template, :layout
38     attr_accessor :redirected_to, :redirected_to_method_params
39
40     delegate :default_charset, :to => 'ActionController::Base'
41
42     def initialize
43       @status = 200
44       @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
45
46       @writer = lambda { |x| @body << x }
47       @block = nil
48
49       @body = "",
50       @session = []
51       @assigns = []
52     end
53
54     def location; headers['Location'] end
55     def location=(url) headers['Location'] = url end
56
57
58     # Sets the HTTP response's content MIME type. For example, in the controller
59     # you could write this:
60     #
61     #  response.content_type = "text/plain"
62     #
63     # If a character set has been defined for this response (see charset=) then
64     # the character set information will also be included in the content type
65     # information.
66     def content_type=(mime_type)
67       new_content_type =
68         if mime_type =~ /charset/ || (c = charset).nil?
69           mime_type.to_s
70         else
71           "#{mime_type}; charset=#{c}"
72         end
73       self.headers["Content-Type"] = URI.escape(new_content_type, "\r\n")
74     end
75
76     # Returns the response's content MIME type, or nil if content type has been set.
77     def content_type
78       content_type = String(headers["Content-Type"] || headers["type"]).split(";")[0]
79       content_type.blank? ? nil : content_type
80     end
81
82     # Set the charset of the Content-Type header. Set to nil to remove it.
83     # If no content type is set, it defaults to HTML.
84     def charset=(charset)
85       headers["Content-Type"] =
86         if charset
87           "#{content_type || Mime::HTML}; charset=#{charset}"
88         else
89           content_type || Mime::HTML.to_s
90         end
91     end
92
93     def charset
94       charset = String(headers["Content-Type"] || headers["type"]).split(";")[1]
95       charset.blank? ? nil : charset.strip.split("=")[1]
96     end
97
98     def last_modified
99       if last = headers['Last-Modified']
100         Time.httpdate(last)
101       end
102     end
103
104     def last_modified?
105       headers.include?('Last-Modified')
106     end
107
108     def last_modified=(utc_time)
109       headers['Last-Modified'] = utc_time.httpdate
110     end
111
112     def etag
113       headers['ETag']
114     end
115
116     def etag?
117       headers.include?('ETag')
118     end
119
120     def etag=(etag)
121       headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
122     end
123
124     def redirect(url, status)
125       self.status = status
126       self.location = url.gsub(/[\r\n]/, '')
127       self.body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>"
128     end
129
130     def sending_file?
131       headers["Content-Transfer-Encoding"] == "binary"
132     end
133
134     def assign_default_content_type_and_charset!
135       self.content_type ||= Mime::HTML
136       self.charset ||= default_charset unless sending_file?
137     end
138
139     def prepare!
140       assign_default_content_type_and_charset!
141       handle_conditional_get!
142       set_content_length!
143       convert_content_type!
144       convert_language!
145       convert_cookies!
146     end
147
148     def each(&callback)
149       if @body.respond_to?(:call)
150         @writer = lambda { |x| callback.call(x) }
151         @body.call(self, self)
152       elsif @body.respond_to?(:to_str)
153         yield @body
154       else
155         @body.each(&callback)
156       end
157
158       @writer = callback
159       @block.call(self) if @block
160     end
161
162     def write(str)
163       @writer.call str.to_s
164       str
165     end
166
167     def flush #:nodoc:
168       ActiveSupport::Deprecation.warn(
169         'Calling output.flush is no longer needed for streaming output ' +
170         'because ActionController::Response automatically handles it', caller)
171     end
172
173     def set_cookie(key, value)
174       if value.has_key?(:http_only)
175         ActiveSupport::Deprecation.warn(
176           "The :http_only option in ActionController::Response#set_cookie " +
177           "has been renamed. Please use :httponly instead.", caller)
178         value[:httponly] ||= value.delete(:http_only)
179       end
180
181       super(key, value)
182     end
183
184     private
185       def handle_conditional_get!
186         if etag? || last_modified?
187           set_conditional_cache_control!
188         elsif nonempty_ok_response?
189           self.etag = body
190
191           if request && request.etag_matches?(etag)
192             self.status = '304 Not Modified'
193             self.body = ''
194           end
195
196           set_conditional_cache_control!
197         end
198       end
199
200       def nonempty_ok_response?
201         ok = !status || status.to_s[0..2] == '200'
202         ok && body.is_a?(String) && !body.blank?
203       end
204
205       def set_conditional_cache_control!
206         if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
207           headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
208         end
209       end
210
211       def convert_content_type!
212         headers['Content-Type'] ||= "text/html"
213         headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
214       end
215
216       # Don't set the Content-Length for block-based bodies as that would mean
217       # reading it all into memory. Not nice for, say, a 2GB streaming file.
218       def set_content_length!
219         if status && status.to_s[0..2] == '204'
220           headers.delete('Content-Length')
221         elsif length = headers['Content-Length']
222           headers['Content-Length'] = length.to_s
223         elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304')
224           headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
225         end
226       end
227
228       def convert_language!
229         headers["Content-Language"] = headers.delete("language") if headers["language"]
230       end
231
232       def convert_cookies!
233         cookies = Array(headers['Set-Cookie']).compact
234         headers['Set-Cookie'] = cookies unless cookies.empty?
235       end
236   end
237 end