joe-merb-core 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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 +456 -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 +648 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +889 -0
  13. data/lib/merb-core/config.rb +380 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +620 -0
  16. data/lib/merb-core/controller/exceptions.rb +302 -0
  17. data/lib/merb-core/controller/merb_controller.rb +283 -0
  18. data/lib/merb-core/controller/mime.rb +111 -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 +316 -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 +345 -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 +200 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +77 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +98 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +172 -0
  34. data/lib/merb-core/dispatch/request.rb +718 -0
  35. data/lib/merb-core/dispatch/router.rb +228 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +610 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +220 -0
  39. data/lib/merb-core/dispatch/router/route.rb +560 -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 +215 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +27 -0
  51. data/lib/merb-core/rack/adapter.rb +47 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +24 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +13 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +119 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +33 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +14 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +40 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +17 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +72 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +96 -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 +321 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +252 -0
  75. data/lib/merb-core/tasks/merb.rb +2 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +51 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +17 -0
  79. data/lib/merb-core/test/helpers.rb +10 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +176 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +61 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +47 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +10 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +108 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +393 -0
  89. data/lib/merb-core/test/run_specs.rb +141 -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,111 @@
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
+ def available_accepts
11
+ ResponderMixin::MIMES
12
+ end
13
+
14
+ # Any specific outgoing headers should be included here. These are not
15
+ # the content-type header but anything in addition to it.
16
+ # +transform_method+ should be set to a symbol of the method used to
17
+ # transform a resource into this mime type.
18
+ # For example for the :xml mime type an object might be transformed by
19
+ # calling :to_xml, or for the :js mime type, :to_json.
20
+ # If there is no transform method, use nil.
21
+ #
22
+ # ==== Autogenerated Methods
23
+ # Adding a mime-type adds a render_type method that sets the content
24
+ # type and calls render.
25
+ #
26
+ # By default this does: def render_all, def render_yaml, def render_text,
27
+ # def render_html, def render_xml, def render_js, and def render_yaml
28
+ #
29
+ # ==== Parameters
30
+ # key<Symbol>:: The name of the mime-type. This is used by the provides API
31
+ # transform_method<~to_s>::
32
+ # The associated method to call on objects to convert them to the
33
+ # appropriate mime-type. For instance, :json would use :to_json as its
34
+ # transform_method.
35
+ # mimes<Array[String]>::
36
+ # A list of possible values sent in the Accept header, such as text/html,
37
+ # that should be associated with this content-type.
38
+ # new_response_headers<Hash>::
39
+ # The response headers to set for the the mime type. For example:
40
+ # 'Content-Type' => 'application/json; charset=utf-8'; As a shortcut for
41
+ # the common charset option, use :charset => 'utf-8', which will be
42
+ # correctly appended to the mimetype itself.
43
+ # &block:: a block which recieves the current controller when the format
44
+ # is set (in the controller's #content_type method)
45
+ def add_mime_type(key, transform_method, mimes, new_response_headers = {}, default_quality = 1, &block)
46
+ enforce!(key => Symbol, mimes => Array)
47
+
48
+ content_type = new_response_headers["Content-Type"] || mimes.first
49
+
50
+ if charset = new_response_headers.delete(:charset)
51
+ content_type += "; charset=#{charset}"
52
+ end
53
+
54
+ ResponderMixin::TYPES.update(key =>
55
+ {:accepts => mimes,
56
+ :transform_method => transform_method,
57
+ :content_type => content_type,
58
+ :response_headers => new_response_headers,
59
+ :default_quality => default_quality,
60
+ :response_block => block })
61
+
62
+ mimes.each do |mime|
63
+ ResponderMixin::MIMES.update(mime => key)
64
+ end
65
+
66
+ Merb::RenderMixin.class_eval <<-EOS, __FILE__, __LINE__
67
+ def render_#{key}(thing = nil, opts = {})
68
+ self.content_type = :#{key}
69
+ render thing, opts
70
+ end
71
+ EOS
72
+ end
73
+
74
+ # Removes a MIME-type from the mime-type list.
75
+ #
76
+ # ==== Parameters
77
+ # key<Symbol>:: The key that represents the mime-type to remove.
78
+ #
79
+ # ==== Notes
80
+ # :all is the key for */*; It can't be removed.
81
+ def remove_mime_type(key)
82
+ return false if key == :all
83
+ ResponderMixin::TYPES.delete(key)
84
+ end
85
+
86
+ # ==== Parameters
87
+ # key<Symbol>:: The key that represents the mime-type.
88
+ #
89
+ # ==== Returns
90
+ # Symbol:: The transform method for the mime type, e.g. :to_json.
91
+ #
92
+ # ==== Raises
93
+ # ArgumentError:: The requested mime type is not valid.
94
+ def mime_transform_method(key)
95
+ raise ArgumentError, ":#{key} is not a valid MIME-type" unless ResponderMixin::TYPES.key?(key)
96
+ ResponderMixin::TYPES[key][:transform_method]
97
+ end
98
+
99
+ # The mime-type for a particular inbound Accepts header.
100
+ #
101
+ # ==== Parameters
102
+ # header<String>:: The name of the header to find the mime-type for.
103
+ #
104
+ # ==== Returns
105
+ # Hash:: The mime type information.
106
+ def mime_by_request_header(header)
107
+ available_mime_types.find {|key,info| info[:accepts].include?(header)}.first
108
+ end
109
+
110
+ end
111
+ 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,316 @@
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
+ only_runs_on_mongrel!
66
+ @response.write('%x' % data.size + "\r\n")
67
+ @response.write(data + "\r\n")
68
+ end
69
+
70
+ # ==== Parameters
71
+ # &blk::
72
+ # A proc that should get called outside the mutex, and which will return
73
+ # the value to render.
74
+ #
75
+ # ==== Returns
76
+ # Proc::
77
+ # A block that Mongrel can call later, allowing Merb to release the
78
+ # thread lock and render another request.
79
+ def render_deferred(&blk)
80
+ Proc.new {|response|
81
+ response.write(blk.call)
82
+ }
83
+ end
84
+
85
+ # Renders the passed in string, then calls the block outside the mutex and
86
+ # after the string has been returned to the client.
87
+ #
88
+ # ==== Parameters
89
+ # str<String>:: A +String+ to return to the client.
90
+ # &blk:: A block that should get called once the string has been returned.
91
+ #
92
+ # ==== Returns
93
+ # Proc::
94
+ # A block that Mongrel can call after returning the string to the user.
95
+ def render_then_call(str, &blk)
96
+ Proc.new {|response|
97
+ response.write(str)
98
+ blk.call
99
+ }
100
+ end
101
+
102
+ # ==== Parameters
103
+ # url<String>::
104
+ # URL to redirect to. It can be either a relative or fully-qualified URL.
105
+ # opts<Hash>:: An options hash (see below)
106
+ #
107
+ # ==== Options (opts)
108
+ # :message<Hash>::
109
+ # Messages to pass in url query string as value for "_message"
110
+ # :permanent<Boolean>::
111
+ # When true, return status 301 Moved Permanently
112
+ #
113
+ # ==== Returns
114
+ # String:: Explanation of redirect.
115
+ #
116
+ # ==== Examples
117
+ # redirect("/posts/34")
118
+ # redirect("/posts/34", :message => { :notice => 'Post updated successfully!' })
119
+ # redirect("http://www.merbivore.com/")
120
+ # redirect("http://www.merbivore.com/", :permanent => true)
121
+ def redirect(url, opts = {})
122
+ default_redirect_options = { :message => nil, :permanent => false }
123
+ opts = default_redirect_options.merge(opts)
124
+ if opts[:message]
125
+ notice = Merb::Request.escape([Marshal.dump(opts[:message])].pack("m"))
126
+ url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
127
+ end
128
+ self.status = opts[:permanent] ? 301 : 302
129
+ Merb.logger.info("Redirecting to: #{url} (#{self.status})")
130
+ headers['Location'] = url
131
+ "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
132
+ end
133
+
134
+ def message
135
+ @_message = defined?(@_message) ? @_message : request.message
136
+ end
137
+
138
+ # Sends a file over HTTP. When given a path to a file, it will set the
139
+ # right headers so that the static file is served directly.
140
+ #
141
+ # ==== Parameters
142
+ # file<String>:: Path to file to send to the client.
143
+ # opts<Hash>:: Options for sending the file (see below).
144
+ #
145
+ # ==== Options (opts)
146
+ # :disposition<String>::
147
+ # The disposition of the file send. Defaults to "attachment".
148
+ # :filename<String>::
149
+ # The name to use for the file. Defaults to the filename of file.
150
+ # :type<String>:: The content type.
151
+ #
152
+ # ==== Returns
153
+ # IO:: An I/O stream for the file.
154
+ def send_file(file, opts={})
155
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
156
+ disposition = opts[:disposition].dup || 'attachment'
157
+ disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}")
158
+ headers.update(
159
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
160
+ 'Content-Disposition' => disposition,
161
+ 'Content-Transfer-Encoding' => 'binary'
162
+ )
163
+ Proc.new {|response|
164
+ file = File.open(file, 'rb')
165
+ while chunk = file.read(16384)
166
+ response.write chunk
167
+ end
168
+ file.close
169
+ }
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
+ opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
221
+ disposition = opts[:disposition].dup || 'attachment'
222
+ disposition << %(; filename="#{opts[:filename]}")
223
+ headers.update(
224
+ 'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
225
+ 'Content-Disposition' => disposition,
226
+ 'Content-Transfer-Encoding' => 'binary',
227
+ # Rack specification requires header values to respond to :each
228
+ 'CONTENT-LENGTH' => opts[:content_length].to_s
229
+ )
230
+ Proc.new{|response|
231
+ stream.call(response)
232
+ }
233
+ end
234
+
235
+ # Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
236
+ # from nginx. For more information, see the nginx wiki:
237
+ # http://wiki.codemongers.com/NginxXSendfile
238
+ #
239
+ # and the following sample gist:
240
+ # http://gist.github.com/11225
241
+ #
242
+ # there's also example application up on GitHub:
243
+ #
244
+ # http://github.com/michaelklishin/nginx-x-accel-redirect-example-application/tree/master
245
+ #
246
+ # Unless Content-Disposition is set before calling this method,
247
+ # it is set to attachment with streamed file name.
248
+ #
249
+ # ==== Parameters
250
+ # path<String>:: Path to file to send to the client.
251
+ # content_type<String>:: content type header value. By default is set to empty string to let
252
+ # Nginx detect it.
253
+ #
254
+ # ==== Return
255
+ # One space string.
256
+ def nginx_send_file(path, content_type = "")
257
+ # Let Nginx detect content type unless it is explicitly set
258
+ headers['Content-Type'] = content_type
259
+ headers["Content-Disposition"] ||= "attachment; filename=#{path.split('/').last}"
260
+
261
+ headers['X-Accel-Redirect'] = path
262
+
263
+ return ' '
264
+ end
265
+
266
+ # Sets a cookie to be included in the response.
267
+ #
268
+ # If you need to set a cookie, then use the +cookies+ hash.
269
+ #
270
+ # ==== Parameters
271
+ # name<~to_s>:: A name for the cookie.
272
+ # value<~to_s>:: A value for the cookie.
273
+ # expires<~gmtime:~strftime, Hash>:: An expiration time for the cookie, or a hash of cookie options.
274
+ # ---
275
+ # @public
276
+ def set_cookie(name, value, expires)
277
+ options = expires.is_a?(Hash) ? expires : {:expires => expires}
278
+ cookies.set_cookie(name, value, options)
279
+ end
280
+
281
+ # Marks a cookie as deleted and gives it an expires stamp in the past. This
282
+ # method is used primarily internally in Merb.
283
+ #
284
+ # Use the +cookies+ hash to manipulate cookies instead.
285
+ #
286
+ # ==== Parameters
287
+ # name<~to_s>:: A name for the cookie to delete.
288
+ def delete_cookie(name)
289
+ set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
290
+ end
291
+
292
+ # Escapes the string representation of +obj+ and escapes it for use in XML.
293
+ #
294
+ # ==== Parameter
295
+ # obj<~to_s>:: The object to escape for use in XML.
296
+ #
297
+ # ==== Returns
298
+ # String:: The escaped object.
299
+ def escape_xml(obj)
300
+ Erubis::XmlHelper.escape_xml(obj.to_s)
301
+ end
302
+ alias h escape_xml
303
+ alias escape_html escape_xml
304
+
305
+ private
306
+ # Marks an output method that only runs on the mongrel webserver.
307
+ #
308
+ # ==== Raises
309
+ # NotImplemented:: The Rack adapter is not mongrel.
310
+ def only_runs_on_mongrel!
311
+ unless Merb::Config[:log_stream] == 'mongrel'
312
+ raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter is not mongrel. cannot support this feature")
313
+ end
314
+ end
315
+ end
316
+ end