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.
- data/Rakefile +66 -79
- data/bin/passenger-install-nginx-module +1 -1
- data/bin/passenger-memory-stats +1 -1
- data/bin/passenger-spawn-server +8 -2
- data/doc/Users guide Apache.html +33 -49
- data/doc/Users guide Apache.txt +28 -27
- data/doc/Users guide Nginx.html +9 -19
- data/doc/Users guide Nginx.txt +8 -20
- data/doc/cxxapi/Bucket_8h-source.html +1 -1
- data/doc/cxxapi/Configuration_8h-source.html +297 -300
- data/doc/cxxapi/DirectoryMapper_8h-source.html +1 -1
- data/doc/cxxapi/Hooks_8h-source.html +1 -1
- data/doc/cxxapi/annotated.html +1 -1
- data/doc/cxxapi/classHooks-members.html +1 -1
- data/doc/cxxapi/classHooks.html +1 -1
- data/doc/cxxapi/classPassenger_1_1DirectoryMapper-members.html +1 -1
- data/doc/cxxapi/classPassenger_1_1DirectoryMapper.html +1 -1
- data/doc/cxxapi/classes.html +1 -1
- data/doc/cxxapi/definitions_8h-source.html +1 -1
- data/doc/cxxapi/files.html +1 -1
- data/doc/cxxapi/functions.html +1 -1
- data/doc/cxxapi/functions_func.html +1 -1
- data/doc/cxxapi/graph_legend.html +1 -1
- data/doc/cxxapi/group__Configuration.html +1 -23
- data/doc/cxxapi/group__Core.html +1 -1
- data/doc/cxxapi/group__Hooks.html +1 -1
- data/doc/cxxapi/group__Support.html +1 -1
- data/doc/cxxapi/main.html +1 -1
- data/doc/cxxapi/modules.html +1 -1
- data/doc/users_guide_snippets/rackup_specifications.txt +4 -2
- data/ext/apache2/Configuration.h +0 -3
- data/ext/apache2/Hooks.cpp +5 -3
- data/ext/common/ApplicationPoolServer.h +1 -0
- data/ext/common/ApplicationPoolServerExecutable.cpp +5 -2
- data/ext/common/SpawnManager.h +1 -0
- data/ext/common/Utils.cpp +22 -23
- data/ext/common/Utils.h +32 -21
- data/ext/common/Version.h +31 -0
- data/ext/nginx/ContentHandler.c +61 -8
- data/ext/nginx/HelperServer.cpp +3 -0
- data/ext/nginx/HttpStatusExtractor.h +185 -0
- data/ext/nginx/StaticContentHandler.c +18 -3
- data/ext/nginx/config +2 -1
- data/ext/nginx/ngx_http_passenger_module.c +21 -15
- data/ext/oxt/backtrace.cpp +4 -2
- data/ext/oxt/spin_lock.hpp +3 -3
- data/lib/phusion_passenger/abstract_request_handler.rb +5 -3
- data/lib/phusion_passenger/admin_tools/control_process.rb +6 -1
- data/lib/phusion_passenger/constants.rb +2 -2
- data/lib/phusion_passenger/rack/application_spawner.rb +2 -1
- data/lib/phusion_passenger/rack/request_handler.rb +45 -35
- data/lib/phusion_passenger/templates/nginx/config_snippets.txt.erb +1 -1
- data/lib/phusion_passenger/utils.rb +13 -3
- data/misc/rake/cplusplus.rb +7 -0
- data/test/ApplicationPoolServer_ApplicationPoolTest.cpp +2 -0
- data/test/ApplicationPoolTest.cpp +39 -62
- data/test/CxxTestMain.cpp +6 -6
- data/test/HttpStatusExtractorTest.cpp +17 -0
- data/test/StandardApplicationPoolTest.cpp +2 -0
- data/test/UtilsTest.cpp +17 -28
- data/test/ruby/abstract_request_handler_spec.rb +3 -7
- data/test/ruby/utils_spec.rb +18 -13
- data/test/ruby/wsgi/application_spawner_spec.rb +5 -9
- data/test/stub/railsapp/app/controllers/{bar_controller_1.rb → bar_controller.rb} +0 -0
- data/test/stub/railsapp/app/controllers/bar_controller_1.txt +5 -0
- data/test/stub/railsapp/app/controllers/{bar_controller_2.rb → bar_controller_2.txt} +0 -0
- data/test/support/Support.h +20 -0
- data/test/support/config.rb +13 -0
- data/vendor/README +4 -3
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/COPYING +1 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/KNOWN-ISSUES +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/README +54 -7
- data/vendor/rack-1.0.0-git/Rakefile +164 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack.rb +7 -3
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/adapter/camping.rb +0 -0
- data/vendor/rack-1.0.0-git/lib/rack/auth/abstract/handler.rb +37 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/abstract/request.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/basic.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/md5.rb +1 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/nonce.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/params.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/auth/digest/request.rb +2 -2
- data/vendor/rack-1.0.0-git/lib/rack/auth/openid.rb +480 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/builder.rb +1 -5
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/cascade.rb +0 -0
- data/vendor/rack-1.0.0-git/lib/rack/chunked.rb +49 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/commonlogger.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/conditionalget.rb +4 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/content_length.rb +7 -3
- data/vendor/rack-1.0.0-git/lib/rack/content_type.rb +23 -0
- data/vendor/rack-1.0.0-git/lib/rack/deflater.rb +96 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/directory.rb +5 -2
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/file.rb +4 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler.rb +22 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/cgi.rb +7 -3
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/evented_mongrel.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/fastcgi.rb +26 -24
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/lsws.rb +7 -4
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/mongrel.rb +5 -3
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/scgi.rb +5 -3
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/swiftiplied_mongrel.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/thin.rb +3 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/handler/webrick.rb +11 -5
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/head.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/lint.rb +138 -66
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/lobster.rb +0 -0
- data/vendor/rack-1.0.0-git/lib/rack/lock.rb +16 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/methodoverride.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/mime.rb +4 -4
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/mock.rb +42 -5
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/recursive.rb +0 -0
- data/vendor/rack-1.0.0-git/lib/rack/reloader.rb +106 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/request.rb +46 -10
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/response.rb +15 -3
- data/vendor/rack-1.0.0-git/lib/rack/rewindable_input.rb +98 -0
- data/vendor/rack-1.0.0-git/lib/rack/session/abstract/id.rb +142 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/session/cookie.rb +2 -0
- data/vendor/rack-1.0.0-git/lib/rack/session/memcache.rb +109 -0
- data/vendor/rack-1.0.0-git/lib/rack/session/pool.rb +100 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/showexceptions.rb +2 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/showstatus.rb +1 -1
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/static.rb +0 -0
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/urlmap.rb +12 -5
- data/vendor/{rack-0.9.1 → rack-1.0.0-git}/lib/rack/utils.rb +212 -65
- metadata +71 -170
- data/doc/rdoc/classes/ConditionVariable.html +0 -194
- data/doc/rdoc/classes/Exception.html +0 -120
- data/doc/rdoc/classes/GC.html +0 -113
- data/doc/rdoc/classes/IO.html +0 -169
- data/doc/rdoc/classes/PhusionPassenger.html +0 -238
- data/doc/rdoc/classes/PhusionPassenger/AbstractInstaller.html +0 -153
- data/doc/rdoc/classes/PhusionPassenger/AbstractRequestHandler.html +0 -506
- data/doc/rdoc/classes/PhusionPassenger/AbstractServer.html +0 -692
- data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerAlreadyStarted.html +0 -97
- data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerError.html +0 -96
- data/doc/rdoc/classes/PhusionPassenger/AbstractServer/ServerNotStarted.html +0 -97
- data/doc/rdoc/classes/PhusionPassenger/AbstractServer/UnknownMessage.html +0 -96
- data/doc/rdoc/classes/PhusionPassenger/AbstractServerCollection.html +0 -598
- data/doc/rdoc/classes/PhusionPassenger/AdminTools.html +0 -140
- data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess.html +0 -264
- data/doc/rdoc/classes/PhusionPassenger/AdminTools/ControlProcess/Instance.html +0 -138
- data/doc/rdoc/classes/PhusionPassenger/AppInitError.html +0 -154
- data/doc/rdoc/classes/PhusionPassenger/Application.html +0 -283
- data/doc/rdoc/classes/PhusionPassenger/ConsoleTextTemplate.html +0 -172
- data/doc/rdoc/classes/PhusionPassenger/FrameworkInitError.html +0 -145
- data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +0 -175
- data/doc/rdoc/classes/PhusionPassenger/InitializationError.html +0 -141
- data/doc/rdoc/classes/PhusionPassenger/InvalidPath.html +0 -92
- data/doc/rdoc/classes/PhusionPassenger/MessageChannel.html +0 -489
- data/doc/rdoc/classes/PhusionPassenger/NativeSupport.html +0 -350
- data/doc/rdoc/classes/PhusionPassenger/Rack.html +0 -91
- data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner.html +0 -185
- data/doc/rdoc/classes/PhusionPassenger/Rack/RequestHandler.html +0 -184
- data/doc/rdoc/classes/PhusionPassenger/Railz.html +0 -95
- data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner.html +0 -419
- data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner/Error.html +0 -98
- data/doc/rdoc/classes/PhusionPassenger/Railz/CGIFixed.html +0 -200
- data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner.html +0 -443
- data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner/Error.html +0 -98
- data/doc/rdoc/classes/PhusionPassenger/Railz/RequestHandler.html +0 -154
- data/doc/rdoc/classes/PhusionPassenger/SpawnManager.html +0 -402
- data/doc/rdoc/classes/PhusionPassenger/UnknownError.html +0 -125
- data/doc/rdoc/classes/PhusionPassenger/Utils.html +0 -694
- data/doc/rdoc/classes/PhusionPassenger/VersionNotFound.html +0 -140
- data/doc/rdoc/classes/PhusionPassenger/WSGI.html +0 -89
- data/doc/rdoc/classes/PhusionPassenger/WSGI/ApplicationSpawner.html +0 -188
- data/doc/rdoc/classes/PlatformInfo.html +0 -831
- data/doc/rdoc/classes/RakeExtensions.html +0 -197
- data/doc/rdoc/classes/Signal.html +0 -134
- data/doc/rdoc/created.rid +0 -1
- data/doc/rdoc/files/DEVELOPERS_TXT.html +0 -240
- data/doc/rdoc/files/README.html +0 -157
- data/doc/rdoc/files/ext/phusion_passenger/native_support_c.html +0 -92
- data/doc/rdoc/files/lib/phusion_passenger/abstract_installer_rb.html +0 -129
- data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +0 -131
- data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +0 -126
- data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +0 -130
- data/doc/rdoc/files/lib/phusion_passenger/admin_tools/control_process_rb.html +0 -129
- data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/application_rb.html +0 -127
- data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +0 -126
- data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +0 -134
- data/doc/rdoc/files/lib/phusion_passenger/events_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +0 -126
- data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/packaging_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +0 -127
- data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +0 -133
- data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +0 -126
- data/doc/rdoc/files/lib/phusion_passenger/railz/application_spawner_rb.html +0 -143
- data/doc/rdoc/files/lib/phusion_passenger/railz/cgi_fixed_rb.html +0 -126
- data/doc/rdoc/files/lib/phusion_passenger/railz/framework_spawner_rb.html +0 -145
- data/doc/rdoc/files/lib/phusion_passenger/railz/request_handler_rb.html +0 -127
- data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +0 -122
- data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +0 -161
- data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +0 -175
- data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +0 -129
- data/doc/rdoc/files/misc/rake/extensions_rb.html +0 -130
- data/doc/rdoc/fr_class_index.html +0 -90
- data/doc/rdoc/fr_file_index.html +0 -76
- data/doc/rdoc/fr_method_index.html +0 -195
- data/doc/rdoc/index.html +0 -26
- data/doc/rdoc/rdoc-style.css +0 -187
- data/vendor/rack-0.9.1/AUTHORS +0 -8
- data/vendor/rack-0.9.1/ChangeLog +0 -1423
- data/vendor/rack-0.9.1/Rakefile +0 -188
- data/vendor/rack-0.9.1/SPEC +0 -129
- data/vendor/rack-0.9.1/lib/rack/auth/abstract/handler.rb +0 -28
- data/vendor/rack-0.9.1/lib/rack/auth/openid.rb +0 -438
- data/vendor/rack-0.9.1/lib/rack/deflater.rb +0 -87
- data/vendor/rack-0.9.1/lib/rack/reloader.rb +0 -64
- data/vendor/rack-0.9.1/lib/rack/session/abstract/id.rb +0 -153
- data/vendor/rack-0.9.1/lib/rack/session/memcache.rb +0 -97
- 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
|
@@ -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::
|
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 =
|
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]
|
File without changes
|
@@ -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)| [-
|
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
|
-
|
41
|
-
|
42
|
-
|
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("&", "&").
|
@@ -102,57 +164,42 @@ module Rack
|
|
102
164
|
end
|
103
165
|
module_function :select_best_encoding
|
104
166
|
|
105
|
-
#
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
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
|
-
|
131
|
-
|
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
|
-
|
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
|
-
|
139
|
-
|
192
|
+
|
193
|
+
def call(env)
|
194
|
+
@for.context(env, @app)
|
140
195
|
end
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@for.context app_r
|
196
|
+
|
197
|
+
def recontext(app)
|
198
|
+
self.class.new(@for, app)
|
145
199
|
end
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
{}
|
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
|
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
|
-
|
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(
|
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: (.*)
|
293
|
-
name = head[/Content-Disposition
|
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
|
-
|
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
|