passenger 2.2.1 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of passenger might be problematic. Click here for more details.

Files changed (216) hide show
  1. data/Rakefile +66 -79
  2. data/bin/passenger-install-nginx-module +1 -1
  3. data/bin/passenger-memory-stats +1 -1
  4. data/bin/passenger-spawn-server +8 -2
  5. data/doc/Users guide Apache.html +33 -49
  6. data/doc/Users guide Apache.txt +28 -27
  7. data/doc/Users guide Nginx.html +9 -19
  8. data/doc/Users guide Nginx.txt +8 -20
  9. data/doc/cxxapi/Bucket_8h-source.html +1 -1
  10. data/doc/cxxapi/Configuration_8h-source.html +297 -300
  11. data/doc/cxxapi/DirectoryMapper_8h-source.html +1 -1
  12. data/doc/cxxapi/Hooks_8h-source.html +1 -1
  13. data/doc/cxxapi/annotated.html +1 -1
  14. data/doc/cxxapi/classHooks-members.html +1 -1
  15. data/doc/cxxapi/classHooks.html +1 -1
  16. data/doc/cxxapi/classPassenger_1_1DirectoryMapper-members.html +1 -1
  17. data/doc/cxxapi/classPassenger_1_1DirectoryMapper.html +1 -1
  18. data/doc/cxxapi/classes.html +1 -1
  19. data/doc/cxxapi/definitions_8h-source.html +1 -1
  20. data/doc/cxxapi/files.html +1 -1
  21. data/doc/cxxapi/functions.html +1 -1
  22. data/doc/cxxapi/functions_func.html +1 -1
  23. data/doc/cxxapi/graph_legend.html +1 -1
  24. data/doc/cxxapi/group__Configuration.html +1 -23
  25. data/doc/cxxapi/group__Core.html +1 -1
  26. data/doc/cxxapi/group__Hooks.html +1 -1
  27. data/doc/cxxapi/group__Support.html +1 -1
  28. data/doc/cxxapi/main.html +1 -1
  29. data/doc/cxxapi/modules.html +1 -1
  30. data/doc/users_guide_snippets/rackup_specifications.txt +4 -2
  31. data/ext/apache2/Configuration.h +0 -3
  32. data/ext/apache2/Hooks.cpp +5 -3
  33. data/ext/common/ApplicationPoolServer.h +1 -0
  34. data/ext/common/ApplicationPoolServerExecutable.cpp +5 -2
  35. data/ext/common/SpawnManager.h +1 -0
  36. data/ext/common/Utils.cpp +22 -23
  37. data/ext/common/Utils.h +32 -21
  38. data/ext/common/Version.h +31 -0
  39. data/ext/nginx/ContentHandler.c +61 -8
  40. data/ext/nginx/HelperServer.cpp +3 -0
  41. data/ext/nginx/HttpStatusExtractor.h +185 -0
  42. data/ext/nginx/StaticContentHandler.c +18 -3
  43. data/ext/nginx/config +2 -1
  44. data/ext/nginx/ngx_http_passenger_module.c +21 -15
  45. data/ext/oxt/backtrace.cpp +4 -2
  46. data/ext/oxt/spin_lock.hpp +3 -3
  47. data/lib/phusion_passenger/abstract_request_handler.rb +5 -3
  48. data/lib/phusion_passenger/admin_tools/control_process.rb +6 -1
  49. data/lib/phusion_passenger/constants.rb +2 -2
  50. data/lib/phusion_passenger/rack/application_spawner.rb +2 -1
  51. data/lib/phusion_passenger/rack/request_handler.rb +45 -35
  52. data/lib/phusion_passenger/templates/nginx/config_snippets.txt.erb +1 -1
  53. data/lib/phusion_passenger/utils.rb +13 -3
  54. data/misc/rake/cplusplus.rb +7 -0
  55. data/test/ApplicationPoolServer_ApplicationPoolTest.cpp +2 -0
  56. data/test/ApplicationPoolTest.cpp +39 -62
  57. data/test/CxxTestMain.cpp +6 -6
  58. data/test/HttpStatusExtractorTest.cpp +17 -0
  59. data/test/StandardApplicationPoolTest.cpp +2 -0
  60. data/test/UtilsTest.cpp +17 -28
  61. data/test/ruby/abstract_request_handler_spec.rb +3 -7
  62. data/test/ruby/utils_spec.rb +18 -13
  63. data/test/ruby/wsgi/application_spawner_spec.rb +5 -9
  64. data/test/stub/railsapp/app/controllers/{bar_controller_1.rb → bar_controller.rb} +0 -0
  65. data/test/stub/railsapp/app/controllers/bar_controller_1.txt +5 -0
  66. data/test/stub/railsapp/app/controllers/{bar_controller_2.rb → bar_controller_2.txt} +0 -0
  67. data/test/support/Support.h +20 -0
  68. data/test/support/config.rb +13 -0
  69. data/vendor/README +4 -3
  70. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/COPYING +1 -1
  71. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/KNOWN-ISSUES +0 -0
  72. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/README +54 -7
  73. data/vendor/rack-1.0.0-git/Rakefile +164 -0
  74. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack.rb +7 -3
  75. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/adapter/camping.rb +0 -0
  76. data/vendor/rack-1.0.0-git/lib/rack/auth/abstract/handler.rb +37 -0
  77. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/abstract/request.rb +0 -0
  78. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/basic.rb +0 -0
  79. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/md5.rb +1 -1
  80. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/nonce.rb +0 -0
  81. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/params.rb +0 -0
  82. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/request.rb +2 -2
  83. data/vendor/rack-1.0.0-git/lib/rack/auth/openid.rb +480 -0
  84. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/builder.rb +1 -5
  85. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/cascade.rb +0 -0
  86. data/vendor/rack-1.0.0-git/lib/rack/chunked.rb +49 -0
  87. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/commonlogger.rb +0 -0
  88. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/conditionalget.rb +4 -0
  89. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/content_length.rb +7 -3
  90. data/vendor/rack-1.0.0-git/lib/rack/content_type.rb +23 -0
  91. data/vendor/rack-1.0.0-git/lib/rack/deflater.rb +96 -0
  92. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/directory.rb +5 -2
  93. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/file.rb +4 -1
  94. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler.rb +22 -1
  95. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/cgi.rb +7 -3
  96. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/evented_mongrel.rb +0 -0
  97. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/fastcgi.rb +26 -24
  98. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/lsws.rb +7 -4
  99. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/mongrel.rb +5 -3
  100. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/scgi.rb +5 -3
  101. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/swiftiplied_mongrel.rb +0 -0
  102. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/thin.rb +3 -0
  103. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/webrick.rb +11 -5
  104. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/head.rb +0 -0
  105. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/lint.rb +138 -66
  106. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/lobster.rb +0 -0
  107. data/vendor/rack-1.0.0-git/lib/rack/lock.rb +16 -0
  108. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/methodoverride.rb +0 -0
  109. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/mime.rb +4 -4
  110. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/mock.rb +42 -5
  111. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/recursive.rb +0 -0
  112. data/vendor/rack-1.0.0-git/lib/rack/reloader.rb +106 -0
  113. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/request.rb +46 -10
  114. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/response.rb +15 -3
  115. data/vendor/rack-1.0.0-git/lib/rack/rewindable_input.rb +98 -0
  116. data/vendor/rack-1.0.0-git/lib/rack/session/abstract/id.rb +142 -0
  117. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/session/cookie.rb +2 -0
  118. data/vendor/rack-1.0.0-git/lib/rack/session/memcache.rb +109 -0
  119. data/vendor/rack-1.0.0-git/lib/rack/session/pool.rb +100 -0
  120. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/showexceptions.rb +2 -1
  121. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/showstatus.rb +1 -1
  122. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/static.rb +0 -0
  123. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/urlmap.rb +12 -5
  124. data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/utils.rb +212 -65
  125. metadata +71 -170
  126. data/doc/rdoc/classes/ConditionVariable.html +0 -194
  127. data/doc/rdoc/classes/Exception.html +0 -120
  128. data/doc/rdoc/classes/GC.html +0 -113
  129. data/doc/rdoc/classes/IO.html +0 -169
  130. data/doc/rdoc/classes/PhusionPassenger.html +0 -238
  131. data/doc/rdoc/classes/PhusionPassenger/AbstractInstaller.html +0 -153
  132. data/doc/rdoc/classes/PhusionPassenger/AbstractRequestHandler.html +0 -506
  133. data/doc/rdoc/classes/PhusionPassenger/AbstractServer.html +0 -692
  134. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerAlreadyStarted.html +0 -97
  135. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerError.html +0 -96
  136. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerNotStarted.html +0 -97
  137. data/doc/rdoc/classes/PhusionPassenger/AbstractServer/UnknownMessage.html +0 -96
  138. data/doc/rdoc/classes/PhusionPassenger/AbstractServerCollection.html +0 -598
  139. data/doc/rdoc/classes/PhusionPassenger/AdminTools.html +0 -140
  140. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess.html +0 -264
  141. data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess/Instance.html +0 -138
  142. data/doc/rdoc/classes/PhusionPassenger/AppInitError.html +0 -154
  143. data/doc/rdoc/classes/PhusionPassenger/Application.html +0 -283
  144. data/doc/rdoc/classes/PhusionPassenger/ConsoleTextTemplate.html +0 -172
  145. data/doc/rdoc/classes/PhusionPassenger/FrameworkInitError.html +0 -145
  146. data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +0 -175
  147. data/doc/rdoc/classes/PhusionPassenger/InitializationError.html +0 -141
  148. data/doc/rdoc/classes/PhusionPassenger/InvalidPath.html +0 -92
  149. data/doc/rdoc/classes/PhusionPassenger/MessageChannel.html +0 -489
  150. data/doc/rdoc/classes/PhusionPassenger/NativeSupport.html +0 -350
  151. data/doc/rdoc/classes/PhusionPassenger/Rack.html +0 -91
  152. data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner.html +0 -185
  153. data/doc/rdoc/classes/PhusionPassenger/Rack/RequestHandler.html +0 -184
  154. data/doc/rdoc/classes/PhusionPassenger/Railz.html +0 -95
  155. data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner.html +0 -419
  156. data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner/Error.html +0 -98
  157. data/doc/rdoc/classes/PhusionPassenger/Railz/CGIFixed.html +0 -200
  158. data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner.html +0 -443
  159. data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner/Error.html +0 -98
  160. data/doc/rdoc/classes/PhusionPassenger/Railz/RequestHandler.html +0 -154
  161. data/doc/rdoc/classes/PhusionPassenger/SpawnManager.html +0 -402
  162. data/doc/rdoc/classes/PhusionPassenger/UnknownError.html +0 -125
  163. data/doc/rdoc/classes/PhusionPassenger/Utils.html +0 -694
  164. data/doc/rdoc/classes/PhusionPassenger/VersionNotFound.html +0 -140
  165. data/doc/rdoc/classes/PhusionPassenger/WSGI.html +0 -89
  166. data/doc/rdoc/classes/PhusionPassenger/WSGI/ApplicationSpawner.html +0 -188
  167. data/doc/rdoc/classes/PlatformInfo.html +0 -831
  168. data/doc/rdoc/classes/RakeExtensions.html +0 -197
  169. data/doc/rdoc/classes/Signal.html +0 -134
  170. data/doc/rdoc/created.rid +0 -1
  171. data/doc/rdoc/files/DEVELOPERS_TXT.html +0 -240
  172. data/doc/rdoc/files/README.html +0 -157
  173. data/doc/rdoc/files/ext/phusion_passenger/native_support_c.html +0 -92
  174. data/doc/rdoc/files/lib/phusion_passenger/abstract_installer_rb.html +0 -129
  175. data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +0 -131
  176. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +0 -126
  177. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +0 -130
  178. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/control_process_rb.html +0 -129
  179. data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +0 -122
  180. data/doc/rdoc/files/lib/phusion_passenger/application_rb.html +0 -127
  181. data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +0 -126
  182. data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +0 -122
  183. data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +0 -134
  184. data/doc/rdoc/files/lib/phusion_passenger/events_rb.html +0 -122
  185. data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +0 -122
  186. data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +0 -126
  187. data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +0 -122
  188. data/doc/rdoc/files/lib/phusion_passenger/packaging_rb.html +0 -122
  189. data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +0 -127
  190. data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +0 -133
  191. data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +0 -126
  192. data/doc/rdoc/files/lib/phusion_passenger/railz/application_spawner_rb.html +0 -143
  193. data/doc/rdoc/files/lib/phusion_passenger/railz/cgi_fixed_rb.html +0 -126
  194. data/doc/rdoc/files/lib/phusion_passenger/railz/framework_spawner_rb.html +0 -145
  195. data/doc/rdoc/files/lib/phusion_passenger/railz/request_handler_rb.html +0 -127
  196. data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +0 -122
  197. data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +0 -161
  198. data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +0 -175
  199. data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +0 -129
  200. data/doc/rdoc/files/misc/rake/extensions_rb.html +0 -130
  201. data/doc/rdoc/fr_class_index.html +0 -90
  202. data/doc/rdoc/fr_file_index.html +0 -76
  203. data/doc/rdoc/fr_method_index.html +0 -195
  204. data/doc/rdoc/index.html +0 -26
  205. data/doc/rdoc/rdoc-style.css +0 -187
  206. data/vendor/rack-0.9.1/AUTHORS +0 -8
  207. data/vendor/rack-0.9.1/ChangeLog +0 -1423
  208. data/vendor/rack-0.9.1/Rakefile +0 -188
  209. data/vendor/rack-0.9.1/SPEC +0 -129
  210. data/vendor/rack-0.9.1/lib/rack/auth/abstract/handler.rb +0 -28
  211. data/vendor/rack-0.9.1/lib/rack/auth/openid.rb +0 -438
  212. data/vendor/rack-0.9.1/lib/rack/deflater.rb +0 -87
  213. data/vendor/rack-0.9.1/lib/rack/reloader.rb +0 -64
  214. data/vendor/rack-0.9.1/lib/rack/session/abstract/id.rb +0 -153
  215. data/vendor/rack-0.9.1/lib/rack/session/memcache.rb +0 -97
  216. data/vendor/rack-0.9.1/lib/rack/session/pool.rb +0 -73
@@ -0,0 +1,142 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # bugrep: Andreas Zehnder
3
+
4
+ require 'time'
5
+ require 'rack/request'
6
+ require 'rack/response'
7
+
8
+ module Rack
9
+
10
+ module Session
11
+
12
+ module Abstract
13
+
14
+ # ID sets up a basic framework for implementing an id based sessioning
15
+ # service. Cookies sent to the client for maintaining sessions will only
16
+ # contain an id reference. Only #get_session and #set_session are
17
+ # required to be overwritten.
18
+ #
19
+ # All parameters are optional.
20
+ # * :key determines the name of the cookie, by default it is
21
+ # 'rack.session'
22
+ # * :path, :domain, :expire_after, :secure, and :httponly set the related
23
+ # cookie options as by Rack::Response#add_cookie
24
+ # * :defer will not set a cookie in the response.
25
+ # * :renew (implementation dependent) will prompt the generation of a new
26
+ # session id, and migration of data to be referenced at the new id. If
27
+ # :defer is set, it will be overridden and the cookie will be set.
28
+ # * :sidbits sets the number of bits in length that a generated session
29
+ # id will be.
30
+ #
31
+ # These options can be set on a per request basis, at the location of
32
+ # env['rack.session.options']. Additionally the id of the session can be
33
+ # found within the options hash at the key :id. It is highly not
34
+ # recommended to change its value.
35
+ #
36
+ # Is Rack::Utils::Context compatible.
37
+
38
+ class ID
39
+ DEFAULT_OPTIONS = {
40
+ :path => '/',
41
+ :domain => nil,
42
+ :expire_after => nil,
43
+ :secure => false,
44
+ :httponly => true,
45
+ :defer => false,
46
+ :renew => false,
47
+ :sidbits => 128
48
+ }
49
+
50
+ attr_reader :key, :default_options
51
+ def initialize(app, options={})
52
+ @app = app
53
+ @key = options[:key] || "rack.session"
54
+ @default_options = self.class::DEFAULT_OPTIONS.merge(options)
55
+ end
56
+
57
+ def call(env)
58
+ context(env)
59
+ end
60
+
61
+ def context(env, app=@app)
62
+ load_session(env)
63
+ status, headers, body = app.call(env)
64
+ commit_session(env, status, headers, body)
65
+ end
66
+
67
+ private
68
+
69
+ # Generate a new session id using Ruby #rand. The size of the
70
+ # session id is controlled by the :sidbits option.
71
+ # Monkey patch this to use custom methods for session id generation.
72
+
73
+ def generate_sid
74
+ "%0#{@default_options[:sidbits] / 4}x" %
75
+ rand(2**@default_options[:sidbits] - 1)
76
+ end
77
+
78
+ # Extracts the session id from provided cookies and passes it and the
79
+ # environment to #get_session. It then sets the resulting session into
80
+ # 'rack.session', and places options and session metadata into
81
+ # 'rack.session.options'.
82
+
83
+ def load_session(env)
84
+ request = Rack::Request.new(env)
85
+ session_id = request.cookies[@key]
86
+
87
+ begin
88
+ session_id, session = get_session(env, session_id)
89
+ env['rack.session'] = session
90
+ rescue
91
+ env['rack.session'] = Hash.new
92
+ end
93
+
94
+ env['rack.session.options'] = @default_options.
95
+ merge(:id => session_id)
96
+ end
97
+
98
+ # Acquires the session from the environment and the session id from
99
+ # the session options and passes them to #set_session. If successful
100
+ # and the :defer option is not true, a cookie will be added to the
101
+ # response with the session's id.
102
+
103
+ def commit_session(env, status, headers, body)
104
+ session = env['rack.session']
105
+ options = env['rack.session.options']
106
+ session_id = options[:id]
107
+
108
+ if not session_id = set_session(env, session_id, session, options)
109
+ env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
110
+ [status, headers, body]
111
+ elsif options[:defer] and not options[:renew]
112
+ env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
113
+ [status, headers, body]
114
+ else
115
+ cookie = Hash.new
116
+ cookie[:value] = session_id
117
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
118
+ response = Rack::Response.new(body, status, headers)
119
+ response.set_cookie(@key, cookie.merge(options))
120
+ response.to_a
121
+ end
122
+ end
123
+
124
+ # All thread safety and session retrival proceedures should occur here.
125
+ # Should return [session_id, session].
126
+ # If nil is provided as the session id, generation of a new valid id
127
+ # should occur within.
128
+
129
+ def get_session(env, sid)
130
+ raise '#get_session not implemented.'
131
+ end
132
+
133
+ # All thread safety and session storage proceedures should occur here.
134
+ # Should return true or false dependant on whether or not the session
135
+ # was saved or not.
136
+ def set_session(env, sid, session, options)
137
+ raise '#set_session not implemented.'
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -1,4 +1,6 @@
1
1
  require 'openssl'
2
+ require 'rack/request'
3
+ require 'rack/response'
2
4
 
3
5
  module Rack
4
6
 
@@ -0,0 +1,109 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+
3
+ require 'rack/session/abstract/id'
4
+ require 'memcache'
5
+
6
+ module Rack
7
+ module Session
8
+ # Rack::Session::Memcache provides simple cookie based session management.
9
+ # Session data is stored in memcached. The corresponding session key is
10
+ # maintained in the cookie.
11
+ # You may treat Session::Memcache as you would Session::Pool with the
12
+ # following caveats.
13
+ #
14
+ # * Setting :expire_after to 0 would note to the Memcache server to hang
15
+ # onto the session data until it would drop it according to it's own
16
+ # specifications. However, the cookie sent to the client would expire
17
+ # immediately.
18
+ #
19
+ # Note that memcache does drop data before it may be listed to expire. For
20
+ # a full description of behaviour, please see memcache's documentation.
21
+
22
+ class Memcache < Abstract::ID
23
+ attr_reader :mutex, :pool
24
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
25
+ :namespace => 'rack:session',
26
+ :memcache_server => 'localhost:11211'
27
+
28
+ def initialize(app, options={})
29
+ super
30
+
31
+ @mutex = Mutex.new
32
+ @pool = MemCache.
33
+ new @default_options[:memcache_server], @default_options
34
+ raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?}
35
+ end
36
+
37
+ def generate_sid
38
+ loop do
39
+ sid = super
40
+ break sid unless @pool.get(sid, true)
41
+ end
42
+ end
43
+
44
+ def get_session(env, sid)
45
+ session = @pool.get(sid) if sid
46
+ @mutex.lock if env['rack.multithread']
47
+ unless sid and session
48
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
+ session = {}
50
+ sid = generate_sid
51
+ ret = @pool.add sid, session
52
+ raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
53
+ end
54
+ session.instance_variable_set('@old', {}.merge(session))
55
+ return [sid, session]
56
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
57
+ warn "#{self} is unable to find server."
58
+ warn $!.inspect
59
+ return [ nil, {} ]
60
+ ensure
61
+ @mutex.unlock if env['rack.multithread']
62
+ end
63
+
64
+ def set_session(env, session_id, new_session, options)
65
+ expiry = options[:expire_after]
66
+ expiry = expiry.nil? ? 0 : expiry + 1
67
+
68
+ @mutex.lock if env['rack.multithread']
69
+ session = @pool.get(session_id) || {}
70
+ if options[:renew] or options[:drop]
71
+ @pool.delete session_id
72
+ return false if options[:drop]
73
+ session_id = generate_sid
74
+ @pool.add session_id, 0 # so we don't worry about cache miss on #set
75
+ end
76
+ old_session = new_session.instance_variable_get('@old') || {}
77
+ session = merge_sessions session_id, old_session, new_session, session
78
+ @pool.set session_id, session, expiry
79
+ return session_id
80
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
81
+ warn "#{self} is unable to find server."
82
+ warn $!.inspect
83
+ return false
84
+ ensure
85
+ @mutex.unlock if env['rack.multithread']
86
+ end
87
+
88
+ private
89
+
90
+ def merge_sessions sid, old, new, cur=nil
91
+ cur ||= {}
92
+ unless Hash === old and Hash === new
93
+ warn 'Bad old or new sessions provided.'
94
+ return cur
95
+ end
96
+
97
+ delete = old.keys - new.keys
98
+ warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
99
+ delete.each{|k| cur.delete k }
100
+
101
+ update = new.keys.select{|k| new[k] != old[k] }
102
+ warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
103
+ update.each{|k| cur[k] = new[k] }
104
+
105
+ cur
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,100 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # THANKS:
3
+ # apeiros, for session id generation, expiry setup, and threadiness
4
+ # sergio, threadiness and bugreps
5
+
6
+ require 'rack/session/abstract/id'
7
+ require 'thread'
8
+
9
+ module Rack
10
+ module Session
11
+ # Rack::Session::Pool provides simple cookie based session management.
12
+ # Session data is stored in a hash held by @pool.
13
+ # In the context of a multithreaded environment, sessions being
14
+ # committed to the pool is done in a merging manner.
15
+ #
16
+ # The :drop option is available in rack.session.options if you with to
17
+ # explicitly remove the session from the session cache.
18
+ #
19
+ # Example:
20
+ # myapp = MyRackApp.new
21
+ # sessioned = Rack::Session::Pool.new(myapp,
22
+ # :domain => 'foo.com',
23
+ # :expire_after => 2592000
24
+ # )
25
+ # Rack::Handler::WEBrick.run sessioned
26
+
27
+ class Pool < Abstract::ID
28
+ attr_reader :mutex, :pool
29
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
30
+
31
+ def initialize(app, options={})
32
+ super
33
+ @pool = Hash.new
34
+ @mutex = Mutex.new
35
+ end
36
+
37
+ def generate_sid
38
+ loop do
39
+ sid = super
40
+ break sid unless @pool.key? sid
41
+ end
42
+ end
43
+
44
+ def get_session(env, sid)
45
+ session = @pool[sid] if sid
46
+ @mutex.lock if env['rack.multithread']
47
+ unless sid and session
48
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
+ session = {}
50
+ sid = generate_sid
51
+ @pool.store sid, session
52
+ end
53
+ session.instance_variable_set('@old', {}.merge(session))
54
+ return [sid, session]
55
+ ensure
56
+ @mutex.unlock if env['rack.multithread']
57
+ end
58
+
59
+ def set_session(env, session_id, new_session, options)
60
+ @mutex.lock if env['rack.multithread']
61
+ session = @pool[session_id]
62
+ if options[:renew] or options[:drop]
63
+ @pool.delete session_id
64
+ return false if options[:drop]
65
+ session_id = generate_sid
66
+ @pool.store session_id, 0
67
+ end
68
+ old_session = new_session.instance_variable_get('@old') || {}
69
+ session = merge_sessions session_id, old_session, new_session, session
70
+ @pool.store session_id, session
71
+ return session_id
72
+ rescue
73
+ warn "#{new_session.inspect} has been lost."
74
+ warn $!.inspect
75
+ ensure
76
+ @mutex.unlock if env['rack.multithread']
77
+ end
78
+
79
+ private
80
+
81
+ def merge_sessions sid, old, new, cur=nil
82
+ cur ||= {}
83
+ unless Hash === old and Hash === new
84
+ warn 'Bad old or new sessions provided.'
85
+ return cur
86
+ end
87
+
88
+ delete = old.keys - new.keys
89
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
90
+ delete.each{|k| cur.delete k }
91
+
92
+ update = new.keys.select{|k| new[k] != old[k] }
93
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
94
+ update.each{|k| cur[k] = new[k] }
95
+
96
+ cur
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,6 +1,7 @@
1
1
  require 'ostruct'
2
2
  require 'erb'
3
3
  require 'rack/request'
4
+ require 'rack/utils'
4
5
 
5
6
  module Rack
6
7
  # Rack::ShowExceptions catches all exceptions raised from the app it
@@ -335,7 +336,7 @@ TEMPLATE = <<'HTML'
335
336
 
336
337
  <div id="explanation">
337
338
  <p>
338
- You're seeing this error because you use <code>Rack::ShowException</code>.
339
+ You're seeing this error because you use <code>Rack::ShowExceptions</code>.
339
340
  </p>
340
341
  </div>
341
342
 
@@ -27,7 +27,7 @@ module Rack
27
27
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
28
28
  detail = env["rack.showstatus.detail"] || message
29
29
  body = @template.result(binding)
30
- size = body.respond_to?(:bytesize) ? body.bytesize : body.size
30
+ size = Rack::Utils.bytesize(body)
31
31
  [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
32
32
  else
33
33
  [status, headers, body]
@@ -12,7 +12,11 @@ module Rack
12
12
  # first, since they are most specific.
13
13
 
14
14
  class URLMap
15
- def initialize(map)
15
+ def initialize(map = {})
16
+ remap(map)
17
+ end
18
+
19
+ def remap(map)
16
20
  @mapping = map.map { |location, app|
17
21
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
18
22
  host, location = $1, $2
@@ -26,20 +30,23 @@ module Rack
26
30
  location = location.chomp('/')
27
31
 
28
32
  [host, location, app]
29
- }.sort_by { |(h, l, a)| [-l.size, h.to_s.size] } # Longest path first
33
+ }.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
30
34
  end
31
35
 
32
36
  def call(env)
33
37
  path = env["PATH_INFO"].to_s.squeeze("/")
38
+ script_name = env['SCRIPT_NAME']
34
39
  hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
35
40
  @mapping.each { |host, location, app|
36
41
  next unless (hHost == host || sName == host \
37
42
  || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
38
43
  next unless location == path[0, location.size]
39
44
  next unless path[location.size] == nil || path[location.size] == ?/
40
- env["SCRIPT_NAME"] += location
41
- env["PATH_INFO"] = path[location.size..-1]
42
- return app.call(env)
45
+
46
+ return app.call(
47
+ env.merge(
48
+ 'SCRIPT_NAME' => (script_name + location),
49
+ 'PATH_INFO' => path[location.size..-1]))
43
50
  }
44
51
  [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
45
52
  end
@@ -29,7 +29,6 @@ module Rack
29
29
  # and ';' characters. You can also use this to parse
30
30
  # cookies by changing the characters used in the second
31
31
  # parameter (which defaults to '&;').
32
-
33
32
  def parse_query(qs, d = '&;')
34
33
  params = {}
35
34
 
@@ -51,6 +50,50 @@ module Rack
51
50
  end
52
51
  module_function :parse_query
53
52
 
53
+ def parse_nested_query(qs, d = '&;')
54
+ params = {}
55
+
56
+ (qs || '').split(/[#{d}] */n).each do |p|
57
+ k, v = unescape(p).split('=', 2)
58
+ normalize_params(params, k, v)
59
+ end
60
+
61
+ return params
62
+ end
63
+ module_function :parse_nested_query
64
+
65
+ def normalize_params(params, name, v = nil)
66
+ name =~ %r([\[\]]*([^\[\]]+)\]*)
67
+ k = $1 || ''
68
+ after = $' || ''
69
+
70
+ return if k.empty?
71
+
72
+ if after == ""
73
+ params[k] = v
74
+ elsif after == "[]"
75
+ params[k] ||= []
76
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
77
+ params[k] << v
78
+ elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
79
+ child_key = $1
80
+ params[k] ||= []
81
+ raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
82
+ if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
83
+ normalize_params(params[k].last, child_key, v)
84
+ else
85
+ params[k] << normalize_params({}, child_key, v)
86
+ end
87
+ else
88
+ params[k] ||= {}
89
+ raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
90
+ params[k] = normalize_params(params[k], after, v)
91
+ end
92
+
93
+ return params
94
+ end
95
+ module_function :normalize_params
96
+
54
97
  def build_query(params)
55
98
  params.map { |k, v|
56
99
  if v.class == Array
@@ -62,6 +105,25 @@ module Rack
62
105
  end
63
106
  module_function :build_query
64
107
 
108
+ def build_nested_query(value, prefix = nil)
109
+ case value
110
+ when Array
111
+ value.map { |v|
112
+ build_nested_query(v, "#{prefix}[]")
113
+ }.join("&")
114
+ when Hash
115
+ value.map { |k, v|
116
+ build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
117
+ }.join("&")
118
+ when String
119
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
120
+ "#{prefix}=#{escape(value)}"
121
+ else
122
+ prefix
123
+ end
124
+ end
125
+ module_function :build_nested_query
126
+
65
127
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
66
128
  def escape_html(string)
67
129
  string.to_s.gsub("&", "&amp;").
@@ -102,57 +164,42 @@ module Rack
102
164
  end
103
165
  module_function :select_best_encoding
104
166
 
105
- # The recommended manner in which to implement a contexting application
106
- # is to define a method #context in which a new Context is instantiated.
107
- #
108
- # As a Context is a glorified block, it is highly recommended that you
109
- # define the contextual block within the application's operational scope.
110
- # This would typically the application as you're place into Rack's stack.
111
- #
112
- # class MyObject
113
- # ...
114
- # def context app
115
- # Rack::Utils::Context.new app do |env|
116
- # do_stuff
117
- # response = app.call(env)
118
- # do_more_stuff
119
- # end
120
- # end
121
- # ...
122
- # end
123
- #
124
- # mobj = MyObject.new
125
- # app = mobj.context other_app
126
- # Rack::Handler::Mongrel.new app
127
- class Context < Proc
128
- alias_method :old_inspect, :inspect
167
+ # Return the bytesize of String; uses String#length under Ruby 1.8 and
168
+ # String#bytesize under 1.9.
169
+ if ''.respond_to?(:bytesize)
170
+ def bytesize(string)
171
+ string.bytesize
172
+ end
173
+ else
174
+ def bytesize(string)
175
+ string.size
176
+ end
177
+ end
178
+ module_function :bytesize
179
+
180
+ # Context allows the use of a compatible middleware at different points
181
+ # in a request handling stack. A compatible middleware must define
182
+ # #context which should take the arguments env and app. The first of which
183
+ # would be the request environment. The second of which would be the rack
184
+ # application that the request would be forwarded to.
185
+ class Context
129
186
  attr_reader :for, :app
130
- def initialize app_f, app_r
131
- raise 'running context not provided' unless app_f
187
+
188
+ def initialize(app_f, app_r)
132
189
  raise 'running context does not respond to #context' unless app_f.respond_to? :context
133
- raise 'application context not provided' unless app_r
134
- raise 'application context does not respond to #call' unless app_r.respond_to? :call
135
- @for = app_f
136
- @app = app_r
190
+ @for, @app = app_f, app_r
137
191
  end
138
- def inspect
139
- "#{old_inspect} ==> #{@for.inspect} ==> #{@app.inspect}"
192
+
193
+ def call(env)
194
+ @for.context(env, @app)
140
195
  end
141
- def context app_r
142
- raise 'new application context not provided' unless app_r
143
- raise 'new application context does not respond to #call' unless app_r.respond_to? :call
144
- @for.context app_r
196
+
197
+ def recontext(app)
198
+ self.class.new(@for, app)
145
199
  end
146
- def pretty_print pp
147
- pp.text old_inspect
148
- pp.nest 1 do
149
- pp.breakable
150
- pp.text '=for> '
151
- pp.pp @for
152
- pp.breakable
153
- pp.text '=app> '
154
- pp.pp @app
155
- end
200
+
201
+ def context(env, app=@app)
202
+ recontext(app).call(env)
156
203
  end
157
204
  end
158
205
 
@@ -165,7 +212,14 @@ module Rack
165
212
  end
166
213
 
167
214
  def to_hash
168
- {}.replace(self)
215
+ inject({}) do |hash, (k,v)|
216
+ if v.respond_to? :to_ary
217
+ hash[k] = v.to_ary.join("\n")
218
+ else
219
+ hash[k] = v
220
+ end
221
+ hash
222
+ end
169
223
  end
170
224
 
171
225
  def [](k)
@@ -254,11 +308,39 @@ module Rack
254
308
  # Usually, Rack::Request#POST takes care of calling this.
255
309
 
256
310
  module Multipart
311
+ class UploadedFile
312
+ # The filename, *not* including the path, of the "uploaded" file
313
+ attr_reader :original_filename
314
+
315
+ # The content type of the "uploaded" file
316
+ attr_accessor :content_type
317
+
318
+ def initialize(path, content_type = "text/plain", binary = false)
319
+ raise "#{path} file does not exist" unless ::File.exist?(path)
320
+ @content_type = content_type
321
+ @original_filename = ::File.basename(path)
322
+ @tempfile = Tempfile.new(@original_filename)
323
+ @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
324
+ @tempfile.binmode if binary
325
+ FileUtils.copy_file(path, @tempfile.path)
326
+ end
327
+
328
+ def path
329
+ @tempfile.path
330
+ end
331
+ alias_method :local_path, :path
332
+
333
+ def method_missing(method_name, *args, &block) #:nodoc:
334
+ @tempfile.__send__(method_name, *args, &block)
335
+ end
336
+ end
337
+
257
338
  EOL = "\r\n"
339
+ MULTIPART_BOUNDARY = "AaB03x"
258
340
 
259
341
  def self.parse_multipart(env)
260
342
  unless env['CONTENT_TYPE'] =~
261
- %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
343
+ %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
262
344
  nil
263
345
  else
264
346
  boundary = "--#{$1}"
@@ -267,16 +349,19 @@ module Rack
267
349
  buf = ""
268
350
  content_length = env['CONTENT_LENGTH'].to_i
269
351
  input = env['rack.input']
352
+ input.rewind
270
353
 
271
354
  boundary_size = boundary.size + EOL.size
272
355
  bufsize = 16384
273
356
 
274
357
  content_length -= boundary_size
275
358
 
276
- status = input.read(boundary_size)
359
+ read_buffer = ''
360
+
361
+ status = input.read(boundary_size, read_buffer)
277
362
  raise EOFError, "bad content body" unless status == boundary + EOL
278
363
 
279
- rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/
364
+ rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
280
365
 
281
366
  loop {
282
367
  head = nil
@@ -284,15 +369,15 @@ module Rack
284
369
  filename = content_type = name = nil
285
370
 
286
371
  until head && buf =~ rx
287
- if !head && i = buf.index("\r\n\r\n")
372
+ if !head && i = buf.index(EOL+EOL)
288
373
  head = buf.slice!(0, i+2) # First \r\n
289
374
  buf.slice!(0, 2) # Second \r\n
290
375
 
291
376
  filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
292
- content_type = head[/Content-Type: (.*)\r\n/ni, 1]
293
- name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
377
+ content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
378
+ name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
294
379
 
295
- if filename
380
+ if content_type || filename
296
381
  body = Tempfile.new("RackMultipart")
297
382
  body.binmode if body.respond_to?(:binmode)
298
383
  end
@@ -305,7 +390,7 @@ module Rack
305
390
  body << buf.slice!(0, buf.size - (boundary_size+4))
306
391
  end
307
392
 
308
- c = input.read(bufsize < content_length ? bufsize : content_length)
393
+ c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
309
394
  raise EOFError, "bad content body" if c.nil? || c.empty?
310
395
  buf << c
311
396
  content_length -= c.size
@@ -319,29 +404,91 @@ module Rack
319
404
  content_length = -1 if $1 == "--"
320
405
  end
321
406
 
322
- if filename
407
+ if filename == ""
408
+ # filename is blank which means no file has been selected
409
+ data = nil
410
+ elsif filename
323
411
  body.rewind
412
+
413
+ # Take the basename of the upload's original filename.
414
+ # This handles the full Windows paths given by Internet Explorer
415
+ # (and perhaps other broken user agents) without affecting
416
+ # those which give the lone filename.
417
+ filename =~ /^(?:.*[:\\\/])?(.*)/m
418
+ filename = $1
419
+
324
420
  data = {:filename => filename, :type => content_type,
325
421
  :name => name, :tempfile => body, :head => head}
422
+ elsif !filename && content_type
423
+ body.rewind
424
+
425
+ # Generic multipart cases, not coming from a form
426
+ data = {:type => content_type,
427
+ :name => name, :tempfile => body, :head => head}
326
428
  else
327
429
  data = body
328
430
  end
329
431
 
330
- if name
331
- if name =~ /\[\]\z/
332
- params[name] ||= []
333
- params[name] << data
334
- else
335
- params[name] = data
336
- end
337
- end
432
+ Utils.normalize_params(params, name, data) unless data.nil?
338
433
 
339
434
  break if buf.empty? || content_length == -1
340
435
  }
341
436
 
437
+ input.rewind
438
+
342
439
  params
343
440
  end
344
441
  end
442
+
443
+ def self.build_multipart(params, first = true)
444
+ flattened_params = Hash.new
445
+
446
+ params.each do |key, value|
447
+ k = first ? key.to_s : "[#{key}]"
448
+
449
+ case value
450
+ when Array
451
+ value.map { |v|
452
+ build_multipart(v, false).each { |subkey, subvalue|
453
+ flattened_params["#{k}[]#{subkey}"] = subvalue
454
+ }
455
+ }
456
+ when Hash
457
+ build_multipart(value, false).each { |subkey, subvalue|
458
+ flattened_params[k + subkey] = subvalue
459
+ }
460
+ else
461
+ flattened_params[k] = value
462
+ end
463
+ end
464
+
465
+ if first
466
+ flattened_params.map { |name, file|
467
+ if file.respond_to?(:original_filename)
468
+ ::File.open(file.path, "rb") do |f|
469
+ f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
470
+ <<-EOF
471
+ --#{MULTIPART_BOUNDARY}\r
472
+ Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
473
+ Content-Type: #{file.content_type}\r
474
+ Content-Length: #{::File.stat(file.path).size}\r
475
+ \r
476
+ #{f.read}\r
477
+ EOF
478
+ end
479
+ else
480
+ <<-EOF
481
+ --#{MULTIPART_BOUNDARY}\r
482
+ Content-Disposition: form-data; name="#{name}"\r
483
+ \r
484
+ #{file}\r
485
+ EOF
486
+ end
487
+ }.join + "--#{MULTIPART_BOUNDARY}--\r"
488
+ else
489
+ flattened_params
490
+ end
491
+ end
345
492
  end
346
493
  end
347
494
  end