wycats-merb-core 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +458 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +319 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +340 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. metadata +253 -0
@@ -0,0 +1,107 @@
1
+ module Merb
2
+ class << self
3
+
4
+ # ==== Returns
5
+ # Hash:: The available mime types.
6
+ def available_mime_types
7
+ ResponderMixin::TYPES
8
+ end
9
+
10
+ # Any specific outgoing headers should be included here. These are not
11
+ # the content-type header but anything in addition to it.
12
+ # +transform_method+ should be set to a symbol of the method used to
13
+ # transform a resource into this mime type.
14
+ # For example for the :xml mime type an object might be transformed by
15
+ # calling :to_xml, or for the :js mime type, :to_json.
16
+ # If there is no transform method, use nil.
17
+ #
18
+ # ==== Autogenerated Methods
19
+ # Adding a mime-type adds a render_type method that sets the content
20
+ # type and calls render.
21
+ #
22
+ # By default this does: def render_all, def render_yaml, def render_text,
23
+ # def render_html, def render_xml, def render_js, and def render_yaml
24
+ #
25
+ # ==== Parameters
26
+ # key<Symbol>:: The name of the mime-type. This is used by the provides API
27
+ # transform_method<~to_s>::
28
+ # The associated method to call on objects to convert them to the
29
+ # appropriate mime-type. For instance, :json would use :to_json as its
30
+ # transform_method.
31
+ # mimes<Array[String]>::
32
+ # A list of possible values sent in the Accept header, such as text/html,
33
+ # that should be associated with this content-type.
34
+ # new_response_headers<Hash>::
35
+ # The response headers to set for the the mime type. For example:
36
+ # 'Content-Type' => 'application/json; charset=utf-8'; As a shortcut for
37
+ # the common charset option, use :charset => 'utf-8', which will be
38
+ # correctly appended to the mimetype itself.
39
+ # &block:: a block which recieves the current controller when the format
40
+ # is set (in the controller's #content_type method)
41
+ def add_mime_type(key, transform_method, mimes, new_response_headers = {}, default_quality = 1, &block)
42
+ enforce!(key => Symbol, mimes => Array)
43
+
44
+ content_type = new_response_headers["Content-Type"] || mimes.first
45
+
46
+ if charset = new_response_headers.delete(:charset)
47
+ content_type += "; charset=#{charset}"
48
+ end
49
+
50
+ ResponderMixin::TYPES.update(key =>
51
+ {:accepts => mimes,
52
+ :transform_method => transform_method,
53
+ :content_type => content_type,
54
+ :response_headers => new_response_headers,
55
+ :default_quality => default_quality,
56
+ :response_block => block })
57
+
58
+ mimes.each do |mime|
59
+ ResponderMixin::MIMES.update(mime => key)
60
+ end
61
+
62
+ Merb::RenderMixin.class_eval <<-EOS, __FILE__, __LINE__
63
+ def render_#{key}(thing = nil, opts = {})
64
+ self.content_type = :#{key}
65
+ render thing, opts
66
+ end
67
+ EOS
68
+ end
69
+
70
+ # Removes a MIME-type from the mime-type list.
71
+ #
72
+ # ==== Parameters
73
+ # key<Symbol>:: The key that represents the mime-type to remove.
74
+ #
75
+ # ==== Notes
76
+ # :all is the key for */*; It can't be removed.
77
+ def remove_mime_type(key)
78
+ return false if key == :all
79
+ ResponderMixin::TYPES.delete(key)
80
+ end
81
+
82
+ # ==== Parameters
83
+ # key<Symbol>:: The key that represents the mime-type.
84
+ #
85
+ # ==== Returns
86
+ # Symbol:: The transform method for the mime type, e.g. :to_json.
87
+ #
88
+ # ==== Raises
89
+ # ArgumentError:: The requested mime type is not valid.
90
+ def mime_transform_method(key)
91
+ raise ArgumentError, ":#{key} is not a valid MIME-type" unless ResponderMixin::TYPES.key?(key)
92
+ ResponderMixin::TYPES[key][:transform_method]
93
+ end
94
+
95
+ # The mime-type for a particular inbound Accepts header.
96
+ #
97
+ # ==== Parameters
98
+ # header<String>:: The name of the header to find the mime-type for.
99
+ #
100
+ # ==== Returns
101
+ # Hash:: The mime type information.
102
+ def mime_by_request_header(header)
103
+ available_mime_types.find {|key,info| info[:accepts].include?(header)}.first
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,123 @@
1
+ module Merb::AuthenticationMixin
2
+
3
+ # Attempts to authenticate the user via HTTP Basic authentication. Takes a
4
+ # block with the username and password, if the block yields false the
5
+ # authentication is not accepted and :halt is thrown.
6
+ #
7
+ # If no block is passed, +basic_authentication+, the +request+ and +authenticate+
8
+ # methods can be chained. These can be used to independently request authentication
9
+ # or confirm it, if more control is desired.
10
+ #
11
+ # ==== Parameters
12
+ # realm<~to_s>:: The realm to authenticate against. Defaults to 'Application'.
13
+ # &authenticator:: A block to check if the authentication is valid.
14
+ #
15
+ # ==== Examples
16
+ # class Application < Merb::Controller
17
+ #
18
+ # before :authenticate
19
+ #
20
+ # protected
21
+ #
22
+ # def authenticate
23
+ # basic_authentication("My App") do |username, password|
24
+ # password == "secret"
25
+ # end
26
+ # end
27
+ #
28
+ # end
29
+ #
30
+ # class Application < Merb::Controller
31
+ #
32
+ # before :authenticate
33
+ #
34
+ # def authenticate
35
+ # user = basic_authentication.authenticate do |username, password|
36
+ # User.authenticate(username, password)
37
+ # end
38
+ #
39
+ # if user
40
+ # @current_user = user
41
+ # else
42
+ # basic_authentication.request
43
+ # end
44
+ # end
45
+ #
46
+ # end
47
+ #
48
+ # If you need to request basic authentication inside an action you need to use the request! method.
49
+ #
50
+ # ====Example
51
+ #
52
+ # class Sessions < Application
53
+ #
54
+ # def new
55
+ # case content_type
56
+ # when :html
57
+ # render
58
+ # else
59
+ # basic_authentication.request!
60
+ # end
61
+ # end
62
+ #
63
+ # end
64
+ #
65
+ #---
66
+ # @public
67
+ def basic_authentication(realm = "Application", &authenticator)
68
+ @_basic_authentication ||= BasicAuthentication.new(self, realm, &authenticator)
69
+ end
70
+
71
+ class BasicAuthentication
72
+ # So we can have access to the status codes
73
+ include Merb::ControllerExceptions
74
+
75
+ def initialize(controller, realm = "Application", &authenticator)
76
+ @controller = controller
77
+ @realm = realm
78
+ @auth = Rack::Auth::Basic::Request.new(@controller.request.env)
79
+ authenticate_or_request(&authenticator) if authenticator
80
+ end
81
+
82
+ def authenticate(&authenticator)
83
+ if @auth.provided? and @auth.basic?
84
+ authenticator.call(*@auth.credentials)
85
+ else
86
+ false
87
+ end
88
+ end
89
+
90
+ def request
91
+ request!
92
+ throw :halt, @controller.render("HTTP Basic: Access denied.\n", :status => Unauthorized.status, :layout => false)
93
+ end
94
+
95
+ # This is a special case for use outside a before filter. Use this if you need to
96
+ # request basic authenticaiton as part of an action
97
+ def request!
98
+ @controller.status = Unauthorized.status
99
+ @controller.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
100
+ end
101
+
102
+ # Checks to see if there has been any basic authentication credentials provided
103
+ def provided?
104
+ @auth.provided?
105
+ end
106
+
107
+ def username
108
+ provided? ? @auth.credentials.first : nil
109
+ end
110
+
111
+ def password
112
+ provided? ? @auth.credentials.last : nil
113
+ end
114
+
115
+ protected
116
+
117
+ def authenticate_or_request(&authenticator)
118
+ authenticate(&authenticator) || request
119
+ end
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,83 @@
1
+ # Provides conditional get support in Merb core.
2
+ # Conditional get support is intentionally
3
+ # simple and does not do fancy stuff like making
4
+ # ETag value from Ruby objects for you.
5
+ #
6
+ # The most interesting method for end user is
7
+ # +request_fresh?+ that is used after setting of
8
+ # last modification time or ETag:
9
+ #
10
+ # ==== Example
11
+ #
12
+ # def show
13
+ # self.etag = Digest::SHA1.hexdigest(calculate_cache_key(params))
14
+ #
15
+ # if request_fresh?
16
+ # self.status = 304
17
+ # return ''
18
+ # else
19
+ # @product = Product.get(params[:id])
20
+ # display @product
21
+ # end
22
+ # end
23
+ module Merb::ConditionalGetMixin
24
+
25
+ # Sets ETag response header by calling
26
+ # #to_s on the argument.
27
+ #
28
+ # ==== Parameters
29
+ # tag<~to_s>::
30
+ # value of ETag header enclosed in double quotes
31
+ # as required by the RFC
32
+ def etag=(tag)
33
+ headers[Merb::Const::ETAG] = %("#{tag}")
34
+ end
35
+
36
+ # ==== Returns
37
+ # <String>::
38
+ # Value of ETag response header or nil if it's not set.
39
+ def etag
40
+ headers[Merb::Const::ETAG]
41
+ end
42
+
43
+ # ==== Returns
44
+ # <Boolean>::
45
+ # true if ETag response header equals If-None-Match request header,
46
+ # false otherwise
47
+ def etag_matches?(tag = self.etag)
48
+ tag == self.request.if_none_match
49
+ end
50
+
51
+ # Sets Last-Modified response header.
52
+ #
53
+ # ==== Parameters
54
+ # tag<Time>::
55
+ # resource modification timestamp converted into format
56
+ # required by the RFC
57
+ def last_modified=(time)
58
+ headers[Merb::Const::LAST_MODIFIED] = time.httpdate
59
+ end
60
+
61
+ # ==== Returns
62
+ # <String>::
63
+ # Value of Last-Modified response header or nil if it's not set.
64
+ def last_modified
65
+ Time.rfc2822(headers[Merb::Const::LAST_MODIFIED])
66
+ end
67
+
68
+ # ==== Returns
69
+ # <Boolean>::
70
+ # true if Last-Modified response header is < than
71
+ # If-Modified-Since request header value, false otherwise.
72
+ def not_modified?(time = self.last_modified)
73
+ request.if_modified_since && time && time <= request.if_modified_since
74
+ end
75
+
76
+ # ==== Returns
77
+ # <Boolean>::
78
+ # true if either ETag matches or entity is not modified,
79
+ # so request is fresh; false otherwise
80
+ def request_fresh?
81
+ etag_matches?(self.etag) || not_modified?(self.last_modified)
82
+ end
83
+ end
@@ -0,0 +1,319 @@
1
+ module Merb
2
+ # Module that is mixed in to all implemented controllers.
3
+ module ControllerMixin
4
+
5
+ # Enqueu a block to run in a background thread outside of the request
6
+ # response dispatch
7
+ #
8
+ # ==== Parameters
9
+ # takes a block to run later
10
+ #
11
+ # ==== Example
12
+ # run_later do
13
+ # SomeBackgroundTask.run
14
+ # end
15
+ #
16
+ def run_later(&blk)
17
+ Merb::Dispatcher.work_queue << blk
18
+ end
19
+
20
+ # Renders the block given as a parameter using chunked encoding.
21
+ #
22
+ # ==== Parameters
23
+ # &blk::
24
+ # A block that, when called, will use send_chunks to send chunks of data
25
+ # down to the server. The chunking will terminate once the block returns.
26
+ #
27
+ # ==== Examples
28
+ # def stream
29
+ # prefix = '<p>'
30
+ # suffix = "</p>\r\n"
31
+ # render_chunked do
32
+ # IO.popen("cat /tmp/test.log") do |io|
33
+ # done = false
34
+ # until done
35
+ # sleep 0.3
36
+ # line = io.gets.chomp
37
+ #
38
+ # if line == 'EOF'
39
+ # done = true
40
+ # else
41
+ # send_chunk(prefix + line + suffix)
42
+ # end
43
+ # end
44
+ # end
45
+ # end
46
+ # end
47
+ def render_chunked(&blk)
48
+ must_support_streaming!
49
+ headers['Transfer-Encoding'] = 'chunked'
50
+ Proc.new { |response|
51
+ @response = response
52
+ response.send_status_no_connection_close('')
53
+ response.send_header
54
+ blk.call
55
+ response.write("0\r\n\r\n")
56
+ }
57
+ end
58
+
59
+ # Writes a chunk from +render_chunked+ to the response that is sent back to
60
+ # the client. This should only be called within a +render_chunked+ block.
61
+ #
62
+ # ==== Parameters
63
+ # data<String>:: a chunk of data to return.
64
+ def send_chunk(data)
65
+ @response.write('%x' % data.size + "\r\n")
66
+ @response.write(data + "\r\n")
67
+ end
68
+
69
+ # ==== Parameters
70
+ # &blk::
71
+ # A proc that should get called outside the mutex, and which will return
72
+ # the value to render.
73
+ #
74
+ # ==== Returns
75
+ # Proc::
76
+ # A block that Mongrel can call later, allowing Merb to release the
77
+ # thread lock and render another request.
78
+ def render_deferred(&blk)
79
+ must_support_streaming!
80
+ Proc.new {|response|
81
+ result = blk.call
82
+ response.send_status(result.length)
83
+ response.send_header
84
+ response.write(result)
85
+ }
86
+ end
87
+
88
+ # Renders the passed in string, then calls the block outside the mutex and
89
+ # after the string has been returned to the client.
90
+ #
91
+ # ==== Parameters
92
+ # str<String>:: A +String+ to return to the client.
93
+ # &blk:: A block that should get called once the string has been returned.
94
+ #
95
+ # ==== Returns
96
+ # Proc::
97
+ # A block that Mongrel can call after returning the string to the user.
98
+ def render_then_call(str, &blk)
99
+ must_support_streaming!
100
+ Proc.new {|response|
101
+ response.send_status(str.length)
102
+ response.send_header
103
+ response.write(str)
104
+ blk.call
105
+ }
106
+ end
107
+
108
+ # ==== Parameters
109
+ # url<String>::
110
+ # URL to redirect to. It can be either a relative or fully-qualified URL.
111
+ # opts<Hash>:: An options hash (see below)
112
+ #
113
+ # ==== Options (opts)
114
+ # :message<Hash>::
115
+ # Messages to pass in url query string as value for "_message"
116
+ # :permanent<Boolean>::
117
+ # When true, return status 301 Moved Permanently
118
+ #
119
+ # ==== Returns
120
+ # String:: Explanation of redirect.
121
+ #
122
+ # ==== Examples
123
+ # redirect("/posts/34")
124
+ # redirect("/posts/34", :message => { :notice => 'Post updated successfully!' })
125
+ # redirect("http://www.merbivore.com/")
126
+ # redirect("http://www.merbivore.com/", :permanent => true)
127
+ def redirect(url, opts = {})
128
+ default_redirect_options = { :message => nil, :permanent => false }
129
+ opts = default_redirect_options.merge(opts)
130
+ if opts[:message]
131
+ notice = Merb::Request.escape([Marshal.dump(opts[:message])].pack("m"))
132
+ url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
133
+ end
134
+ self.status = opts[:permanent] ? 301 : 302
135
+ Merb.logger.info("Redirecting to: #{url} (#{self.status})")
136
+ headers['Location'] = url
137
+ "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
138
+ end
139
+
140
+ def message
141
+ @_message = defined?(@_message) ? @_message : request.message
142
+ end
143
+
144
+ # Sends a file over HTTP. When given a path to a file, it will set the
145
+ # right headers so that the static file is served directly.
146
+ #
147
+ # ==== Parameters
148
+ # file<String>:: Path to file to send to the client.
149
+ # opts<Hash>:: Options for sending the file (see below).
150
+ #
151
+ # ==== Options (opts)
152
+ # :disposition<String>::
153
+ # The disposition of the file send. Defaults to "attachment".
154
+ # :filename<String>::
155
+ # The name to use for the file. Defaults to the filename of file.
156
+ # :type<String>:: The content type.
157
+ #
158
+ # ==== Returns
159
+ # IO:: An I/O stream for the file.
160
+ def send_file(file, opts={})
161
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
162
+ disposition = opts[:disposition].dup || 'attachment'
163
+ disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}")
164
+ headers.update(
165
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
166
+ 'Content-Disposition' => disposition,
167
+ 'Content-Transfer-Encoding' => 'binary'
168
+ )
169
+ File.open(file, 'rb')
170
+ end
171
+
172
+ # Send binary data over HTTP to the user as a file download. May set content type,
173
+ # apparent file name, and specify whether to show data inline or download as an attachment.
174
+ #
175
+ # ==== Parameters
176
+ # data<String>:: Path to file to send to the client.
177
+ # opts<Hash>:: Options for sending the data (see below).
178
+ #
179
+ # ==== Options (opts)
180
+ # :disposition<String>::
181
+ # The disposition of the file send. Defaults to "attachment".
182
+ # :filename<String>::
183
+ # The name to use for the file. Defaults to the filename of file.
184
+ # :type<String>:: The content type.
185
+ def send_data(data, opts={})
186
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
187
+ disposition = opts[:disposition].dup || 'attachment'
188
+ disposition << %(; filename="#{opts[:filename]}") if opts[:filename]
189
+ headers.update(
190
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
191
+ 'Content-Disposition' => disposition,
192
+ 'Content-Transfer-Encoding' => 'binary'
193
+ )
194
+ data
195
+ end
196
+
197
+ # Streams a file over HTTP.
198
+ #
199
+ # ==== Parameters
200
+ # opts<Hash>:: Options for the file streaming (see below).
201
+ # &stream::
202
+ # A block that, when called, will return an object that responds to
203
+ # +get_lines+ for streaming.
204
+ #
205
+ # ==== Options
206
+ # :disposition<String>::
207
+ # The disposition of the file send. Defaults to "attachment".
208
+ # :type<String>:: The content type.
209
+ # :content_length<Numeric>:: The length of the content to send.
210
+ # :filename<String>:: The name to use for the streamed file.
211
+ #
212
+ # ==== Examples
213
+ # stream_file({ :filename => file_name, :type => content_type,
214
+ # :content_length => content_length }) do |response|
215
+ # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
216
+ # response.write chunk
217
+ # end
218
+ # end
219
+ def stream_file(opts={}, &stream)
220
+ must_support_streaming!
221
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
222
+ disposition = opts[:disposition].dup || 'attachment'
223
+ disposition << %(; filename="#{opts[:filename]}")
224
+ headers.update(
225
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
226
+ 'Content-Disposition' => disposition,
227
+ 'Content-Transfer-Encoding' => 'binary',
228
+ # Rack specification requires header values to respond to :each
229
+ 'CONTENT-LENGTH' => opts[:content_length].to_s
230
+ )
231
+ Proc.new{|response|
232
+ response.send_status(opts[:content_length])
233
+ response.send_header
234
+ stream.call(response)
235
+ }
236
+ end
237
+
238
+ # Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
239
+ # from nginx. For more information, see the nginx wiki:
240
+ # http://wiki.codemongers.com/NginxXSendfile
241
+ #
242
+ # and the following sample gist:
243
+ # http://gist.github.com/11225
244
+ #
245
+ # there's also example application up on GitHub:
246
+ #
247
+ # http://github.com/michaelklishin/nginx-x-accel-redirect-example-application/tree/master
248
+ #
249
+ # Unless Content-Disposition is set before calling this method,
250
+ # it is set to attachment with streamed file name.
251
+ #
252
+ # ==== Parameters
253
+ # path<String>:: Path to file to send to the client.
254
+ # content_type<String>:: content type header value. By default is set to empty string to let
255
+ # Nginx detect it.
256
+ #
257
+ # ==== Return
258
+ # One space string.
259
+ def nginx_send_file(path, content_type = "")
260
+ # Let Nginx detect content type unless it is explicitly set
261
+ headers['Content-Type'] = content_type
262
+ headers["Content-Disposition"] ||= "attachment; filename=#{path.split('/').last}"
263
+
264
+ headers['X-Accel-Redirect'] = path
265
+
266
+ return ' '
267
+ end
268
+
269
+ # Sets a cookie to be included in the response.
270
+ #
271
+ # If you need to set a cookie, then use the +cookies+ hash.
272
+ #
273
+ # ==== Parameters
274
+ # name<~to_s>:: A name for the cookie.
275
+ # value<~to_s>:: A value for the cookie.
276
+ # expires<~gmtime:~strftime, Hash>:: An expiration time for the cookie, or a hash of cookie options.
277
+ # ---
278
+ # @public
279
+ def set_cookie(name, value, expires)
280
+ options = expires.is_a?(Hash) ? expires : {:expires => expires}
281
+ cookies.set_cookie(name, value, options)
282
+ end
283
+
284
+ # Marks a cookie as deleted and gives it an expires stamp in the past. This
285
+ # method is used primarily internally in Merb.
286
+ #
287
+ # Use the +cookies+ hash to manipulate cookies instead.
288
+ #
289
+ # ==== Parameters
290
+ # name<~to_s>:: A name for the cookie to delete.
291
+ def delete_cookie(name)
292
+ set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
293
+ end
294
+
295
+ # Escapes the string representation of +obj+ and escapes it for use in XML.
296
+ #
297
+ # ==== Parameter
298
+ # obj<~to_s>:: The object to escape for use in XML.
299
+ #
300
+ # ==== Returns
301
+ # String:: The escaped object.
302
+ def escape_xml(obj)
303
+ Erubis::XmlHelper.escape_xml(obj.to_s)
304
+ end
305
+ alias h escape_xml
306
+ alias escape_html escape_xml
307
+
308
+ private
309
+ # Checks whether streaming is supported by the current Rack adapter.
310
+ #
311
+ # ==== Raises
312
+ # NotImplemented:: The Rack adapter doens't support streaming.
313
+ def must_support_streaming!
314
+ unless request.env['rack.streaming']
315
+ raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter does not support streaming")
316
+ end
317
+ end
318
+ end
319
+ end