passenger 2.2.7 → 2.2.8

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 (160) hide show
  1. data/NEWS +40 -0
  2. data/Rakefile +1 -1
  3. data/bin/passenger-install-apache2-module +1 -0
  4. data/bin/passenger-install-nginx-module +2 -0
  5. data/doc/Architectural overview.html +173 -138
  6. data/doc/Security of user switching support.html +147 -109
  7. data/doc/Users guide Apache.html +4 -43
  8. data/doc/Users guide Nginx.html +4 -43
  9. data/doc/cxxapi/Bucket_8h-source.html +1 -1
  10. data/doc/cxxapi/Configuration_8h-source.html +1 -1
  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 -1
  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/rdoc/classes/GC.html +4 -4
  31. data/doc/rdoc/classes/PhusionPassenger.html +2 -1
  32. data/doc/rdoc/classes/PhusionPassenger/AbstractInstaller.html +8 -8
  33. data/doc/rdoc/classes/PhusionPassenger/AbstractRequestHandler.html +26 -26
  34. data/doc/rdoc/classes/PhusionPassenger/AbstractServer.html +98 -98
  35. data/doc/rdoc/classes/PhusionPassenger/AbstractServerCollection.html +61 -61
  36. data/doc/rdoc/classes/PhusionPassenger/AppInitError.html +4 -4
  37. data/doc/rdoc/classes/PhusionPassenger/Application.html +14 -14
  38. data/doc/rdoc/classes/PhusionPassenger/ConsoleTextTemplate.html +12 -12
  39. data/doc/rdoc/classes/PhusionPassenger/FrameworkInitError.html +4 -4
  40. data/doc/rdoc/classes/PhusionPassenger/HTMLTemplate.html +12 -12
  41. data/doc/rdoc/classes/PhusionPassenger/InitializationError.html +5 -5
  42. data/doc/rdoc/classes/PhusionPassenger/MessageChannel.html +42 -42
  43. data/doc/rdoc/classes/PhusionPassenger/Rack/ApplicationSpawner.html +33 -33
  44. data/doc/rdoc/classes/PhusionPassenger/Rack/RequestHandler.html +59 -59
  45. data/doc/rdoc/classes/PhusionPassenger/Railz/ApplicationSpawner.html +3 -3
  46. data/doc/rdoc/classes/PhusionPassenger/Railz/FrameworkSpawner.html +7 -7
  47. data/doc/rdoc/classes/PhusionPassenger/SpawnManager.html +23 -23
  48. data/doc/rdoc/classes/PhusionPassenger/UnknownError.html +4 -4
  49. data/doc/rdoc/classes/PhusionPassenger/Utils.html +2 -0
  50. data/doc/rdoc/classes/PhusionPassenger/Utils/PseudoIO.html +16 -16
  51. data/doc/rdoc/classes/PhusionPassenger/VersionNotFound.html +4 -4
  52. data/doc/rdoc/classes/PlatformInfo.html +1 -1
  53. data/doc/rdoc/classes/Signal.html +23 -15
  54. data/doc/rdoc/created.rid +1 -1
  55. data/doc/rdoc/files/DEVELOPERS_TXT.html +1 -1
  56. data/doc/rdoc/files/README.html +1 -1
  57. data/doc/rdoc/files/ext/phusion_passenger/native_support_c.html +1 -1
  58. data/doc/rdoc/files/lib/phusion_passenger/abstract_installer_rb.html +1 -1
  59. data/doc/rdoc/files/lib/phusion_passenger/abstract_request_handler_rb.html +1 -1
  60. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_collection_rb.html +1 -1
  61. data/doc/rdoc/files/lib/phusion_passenger/abstract_server_rb.html +1 -1
  62. data/doc/rdoc/files/lib/phusion_passenger/admin_tools/control_process_rb.html +1 -1
  63. data/doc/rdoc/files/lib/phusion_passenger/admin_tools_rb.html +1 -1
  64. data/doc/rdoc/files/lib/phusion_passenger/application_rb.html +1 -1
  65. data/doc/rdoc/files/lib/phusion_passenger/console_text_template_rb.html +1 -1
  66. data/doc/rdoc/files/lib/phusion_passenger/constants_rb.html +1 -1
  67. data/doc/rdoc/files/lib/phusion_passenger/dependencies_rb.html +1 -1
  68. data/doc/rdoc/files/lib/phusion_passenger/events_rb.html +1 -1
  69. data/doc/rdoc/files/lib/phusion_passenger/exceptions_rb.html +1 -1
  70. data/doc/rdoc/files/lib/phusion_passenger/html_template_rb.html +1 -1
  71. data/doc/rdoc/files/lib/phusion_passenger/message_channel_rb.html +1 -1
  72. data/doc/rdoc/files/lib/phusion_passenger/packaging_rb.html +1 -1
  73. data/doc/rdoc/files/lib/phusion_passenger/platform_info_rb.html +1 -1
  74. data/doc/rdoc/files/lib/phusion_passenger/rack/application_spawner_rb.html +2 -2
  75. data/doc/rdoc/files/lib/phusion_passenger/rack/request_handler_rb.html +2 -2
  76. data/doc/rdoc/files/lib/phusion_passenger/railz/application_spawner_rb.html +1 -1
  77. data/doc/rdoc/files/lib/phusion_passenger/railz/cgi_fixed_rb.html +1 -1
  78. data/doc/rdoc/files/lib/phusion_passenger/railz/framework_spawner_rb.html +1 -1
  79. data/doc/rdoc/files/lib/phusion_passenger/railz/request_handler_rb.html +1 -1
  80. data/doc/rdoc/files/lib/phusion_passenger/simple_benchmarking_rb.html +1 -1
  81. data/doc/rdoc/files/lib/phusion_passenger/spawn_manager_rb.html +1 -1
  82. data/doc/rdoc/files/lib/phusion_passenger/utils_rb.html +9 -9
  83. data/doc/rdoc/files/lib/phusion_passenger/wsgi/application_spawner_rb.html +1 -1
  84. data/doc/rdoc/files/misc/rake/extensions_rb.html +1 -1
  85. data/doc/rdoc/fr_class_index.html +2 -0
  86. data/doc/rdoc/fr_file_index.html +1 -0
  87. data/doc/rdoc/fr_method_index.html +79 -72
  88. data/ext/apache2/Hooks.cpp +4 -2
  89. data/ext/common/StandardApplicationPool.h +4 -2
  90. data/ext/common/Version.h +1 -1
  91. data/ext/nginx/Configuration.c +1 -1
  92. data/ext/oxt/system_calls.cpp +11 -0
  93. data/ext/oxt/system_calls.hpp +2 -1
  94. data/ext/oxt/thread.hpp +97 -1
  95. data/lib/phusion_passenger/constants.rb +1 -1
  96. data/lib/phusion_passenger/dependencies.rb +32 -0
  97. data/lib/phusion_passenger/platform_info.rb +1 -1
  98. data/lib/phusion_passenger/rack/application_spawner.rb +4 -4
  99. data/lib/phusion_passenger/rack/request_handler.rb +2 -5
  100. data/lib/phusion_passenger/railz/application_spawner.rb +13 -2
  101. data/lib/phusion_passenger/utils.rb +12 -4
  102. data/{vendor/rack-1.0.0-git/lib/rack → lib/phusion_passenger/utils}/rewindable_input.rb +19 -3
  103. data/test/ApplicationPoolTest.cpp +1 -1
  104. metadata +13 -59
  105. data/vendor/README +0 -13
  106. data/vendor/README_FOR_PACKAGERS +0 -1
  107. data/vendor/rack-1.0.0-git/COPYING +0 -18
  108. data/vendor/rack-1.0.0-git/KNOWN-ISSUES +0 -18
  109. data/vendor/rack-1.0.0-git/README +0 -353
  110. data/vendor/rack-1.0.0-git/Rakefile +0 -164
  111. data/vendor/rack-1.0.0-git/lib/rack.rb +0 -90
  112. data/vendor/rack-1.0.0-git/lib/rack/adapter/camping.rb +0 -22
  113. data/vendor/rack-1.0.0-git/lib/rack/auth/abstract/handler.rb +0 -37
  114. data/vendor/rack-1.0.0-git/lib/rack/auth/abstract/request.rb +0 -37
  115. data/vendor/rack-1.0.0-git/lib/rack/auth/basic.rb +0 -58
  116. data/vendor/rack-1.0.0-git/lib/rack/auth/digest/md5.rb +0 -124
  117. data/vendor/rack-1.0.0-git/lib/rack/auth/digest/nonce.rb +0 -51
  118. data/vendor/rack-1.0.0-git/lib/rack/auth/digest/params.rb +0 -55
  119. data/vendor/rack-1.0.0-git/lib/rack/auth/digest/request.rb +0 -40
  120. data/vendor/rack-1.0.0-git/lib/rack/auth/openid.rb +0 -487
  121. data/vendor/rack-1.0.0-git/lib/rack/builder.rb +0 -63
  122. data/vendor/rack-1.0.0-git/lib/rack/cascade.rb +0 -41
  123. data/vendor/rack-1.0.0-git/lib/rack/chunked.rb +0 -49
  124. data/vendor/rack-1.0.0-git/lib/rack/commonlogger.rb +0 -52
  125. data/vendor/rack-1.0.0-git/lib/rack/conditionalget.rb +0 -47
  126. data/vendor/rack-1.0.0-git/lib/rack/content_length.rb +0 -29
  127. data/vendor/rack-1.0.0-git/lib/rack/content_type.rb +0 -23
  128. data/vendor/rack-1.0.0-git/lib/rack/deflater.rb +0 -96
  129. data/vendor/rack-1.0.0-git/lib/rack/directory.rb +0 -153
  130. data/vendor/rack-1.0.0-git/lib/rack/file.rb +0 -88
  131. data/vendor/rack-1.0.0-git/lib/rack/handler.rb +0 -69
  132. data/vendor/rack-1.0.0-git/lib/rack/handler/cgi.rb +0 -61
  133. data/vendor/rack-1.0.0-git/lib/rack/handler/evented_mongrel.rb +0 -8
  134. data/vendor/rack-1.0.0-git/lib/rack/handler/fastcgi.rb +0 -88
  135. data/vendor/rack-1.0.0-git/lib/rack/handler/lsws.rb +0 -55
  136. data/vendor/rack-1.0.0-git/lib/rack/handler/mongrel.rb +0 -84
  137. data/vendor/rack-1.0.0-git/lib/rack/handler/scgi.rb +0 -59
  138. data/vendor/rack-1.0.0-git/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  139. data/vendor/rack-1.0.0-git/lib/rack/handler/thin.rb +0 -18
  140. data/vendor/rack-1.0.0-git/lib/rack/handler/webrick.rb +0 -67
  141. data/vendor/rack-1.0.0-git/lib/rack/head.rb +0 -19
  142. data/vendor/rack-1.0.0-git/lib/rack/lint.rb +0 -537
  143. data/vendor/rack-1.0.0-git/lib/rack/lobster.rb +0 -65
  144. data/vendor/rack-1.0.0-git/lib/rack/lock.rb +0 -16
  145. data/vendor/rack-1.0.0-git/lib/rack/methodoverride.rb +0 -27
  146. data/vendor/rack-1.0.0-git/lib/rack/mime.rb +0 -204
  147. data/vendor/rack-1.0.0-git/lib/rack/mock.rb +0 -184
  148. data/vendor/rack-1.0.0-git/lib/rack/recursive.rb +0 -57
  149. data/vendor/rack-1.0.0-git/lib/rack/reloader.rb +0 -106
  150. data/vendor/rack-1.0.0-git/lib/rack/request.rb +0 -248
  151. data/vendor/rack-1.0.0-git/lib/rack/response.rb +0 -183
  152. data/vendor/rack-1.0.0-git/lib/rack/session/abstract/id.rb +0 -142
  153. data/vendor/rack-1.0.0-git/lib/rack/session/cookie.rb +0 -91
  154. data/vendor/rack-1.0.0-git/lib/rack/session/memcache.rb +0 -109
  155. data/vendor/rack-1.0.0-git/lib/rack/session/pool.rb +0 -100
  156. data/vendor/rack-1.0.0-git/lib/rack/showexceptions.rb +0 -349
  157. data/vendor/rack-1.0.0-git/lib/rack/showstatus.rb +0 -106
  158. data/vendor/rack-1.0.0-git/lib/rack/static.rb +0 -38
  159. data/vendor/rack-1.0.0-git/lib/rack/urlmap.rb +0 -55
  160. data/vendor/rack-1.0.0-git/lib/rack/utils.rb +0 -522
@@ -1,106 +0,0 @@
1
- require 'erb'
2
- require 'rack/request'
3
- require 'rack/utils'
4
-
5
- module Rack
6
- # Rack::ShowStatus catches all empty responses the app it wraps and
7
- # replaces them with a site explaining the error.
8
- #
9
- # Additional details can be put into <tt>rack.showstatus.detail</tt>
10
- # and will be shown as HTML. If such details exist, the error page
11
- # is always rendered, even if the reply was not empty.
12
-
13
- class ShowStatus
14
- def initialize(app)
15
- @app = app
16
- @template = ERB.new(TEMPLATE)
17
- end
18
-
19
- def call(env)
20
- status, headers, body = @app.call(env)
21
- headers = Utils::HeaderHash.new(headers)
22
- empty = headers['Content-Length'].to_i <= 0
23
-
24
- # client or server error, or explicit message
25
- if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
26
- req = Rack::Request.new(env)
27
- message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
28
- detail = env["rack.showstatus.detail"] || message
29
- body = @template.result(binding)
30
- size = Rack::Utils.bytesize(body)
31
- [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
32
- else
33
- [status, headers, body]
34
- end
35
- end
36
-
37
- def h(obj) # :nodoc:
38
- case obj
39
- when String
40
- Utils.escape_html(obj)
41
- else
42
- Utils.escape_html(obj.inspect)
43
- end
44
- end
45
-
46
- # :stopdoc:
47
-
48
- # adapted from Django <djangoproject.com>
49
- # Copyright (c) 2005, the Lawrence Journal-World
50
- # Used under the modified BSD license:
51
- # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
52
- TEMPLATE = <<'HTML'
53
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
54
- <html lang="en">
55
- <head>
56
- <meta http-equiv="content-type" content="text/html; charset=utf-8" />
57
- <title><%=h message %> at <%=h req.script_name + req.path_info %></title>
58
- <meta name="robots" content="NONE,NOARCHIVE" />
59
- <style type="text/css">
60
- html * { padding:0; margin:0; }
61
- body * { padding:10px 20px; }
62
- body * * { padding:0; }
63
- body { font:small sans-serif; background:#eee; }
64
- body>div { border-bottom:1px solid #ddd; }
65
- h1 { font-weight:normal; margin-bottom:.4em; }
66
- h1 span { font-size:60%; color:#666; font-weight:normal; }
67
- table { border:none; border-collapse: collapse; width:100%; }
68
- td, th { vertical-align:top; padding:2px 3px; }
69
- th { width:12em; text-align:right; color:#666; padding-right:.5em; }
70
- #info { background:#f6f6f6; }
71
- #info ol { margin: 0.5em 4em; }
72
- #info ol li { font-family: monospace; }
73
- #summary { background: #ffc; }
74
- #explanation { background:#eee; border-bottom: 0px none; }
75
- </style>
76
- </head>
77
- <body>
78
- <div id="summary">
79
- <h1><%=h message %> <span>(<%= status.to_i %>)</span></h1>
80
- <table class="meta">
81
- <tr>
82
- <th>Request Method:</th>
83
- <td><%=h req.request_method %></td>
84
- </tr>
85
- <tr>
86
- <th>Request URL:</th>
87
- <td><%=h req.url %></td>
88
- </tr>
89
- </table>
90
- </div>
91
- <div id="info">
92
- <p><%= detail %></p>
93
- </div>
94
-
95
- <div id="explanation">
96
- <p>
97
- You're seeing this error because you use <code>Rack::ShowStatus</code>.
98
- </p>
99
- </div>
100
- </body>
101
- </html>
102
- HTML
103
-
104
- # :startdoc:
105
- end
106
- end
@@ -1,38 +0,0 @@
1
- module Rack
2
-
3
- # The Rack::Static middleware intercepts requests for static files
4
- # (javascript files, images, stylesheets, etc) based on the url prefixes
5
- # passed in the options, and serves them using a Rack::File object. This
6
- # allows a Rack stack to serve both static and dynamic content.
7
- #
8
- # Examples:
9
- # use Rack::Static, :urls => ["/media"]
10
- # will serve all requests beginning with /media from the "media" folder
11
- # located in the current directory (ie media/*).
12
- #
13
- # use Rack::Static, :urls => ["/css", "/images"], :root => "public"
14
- # will serve all requests beginning with /css or /images from the folder
15
- # "public" in the current directory (ie public/css/* and public/images/*)
16
-
17
- class Static
18
-
19
- def initialize(app, options={})
20
- @app = app
21
- @urls = options[:urls] || ["/favicon.ico"]
22
- root = options[:root] || Dir.pwd
23
- @file_server = Rack::File.new(root)
24
- end
25
-
26
- def call(env)
27
- path = env["PATH_INFO"]
28
- can_serve = @urls.any? { |url| path.index(url) == 0 }
29
-
30
- if can_serve
31
- @file_server.call(env)
32
- else
33
- @app.call(env)
34
- end
35
- end
36
-
37
- end
38
- end
@@ -1,55 +0,0 @@
1
- module Rack
2
- # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
- # dispatches accordingly. Support for HTTP/1.1 host names exists if
4
- # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
5
- #
6
- # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
7
- # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
8
- # PATH_INFO. This should be taken care of when you need to
9
- # reconstruct the URL in order to create links.
10
- #
11
- # URLMap dispatches in such a way that the longest paths are tried
12
- # first, since they are most specific.
13
-
14
- class URLMap
15
- def initialize(map = {})
16
- remap(map)
17
- end
18
-
19
- def remap(map)
20
- @mapping = map.map { |location, app|
21
- if location =~ %r{\Ahttps?://(.*?)(/.*)}
22
- host, location = $1, $2
23
- else
24
- host = nil
25
- end
26
-
27
- unless location[0] == ?/
28
- raise ArgumentError, "paths need to start with /"
29
- end
30
- location = location.chomp('/')
31
-
32
- [host, location, app]
33
- }.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
34
- end
35
-
36
- def call(env)
37
- path = env["PATH_INFO"].to_s.squeeze("/")
38
- script_name = env['SCRIPT_NAME']
39
- hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
40
- @mapping.each { |host, location, app|
41
- next unless (hHost == host || sName == host \
42
- || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
43
- next unless location == path[0, location.size]
44
- next unless path[location.size] == nil || path[location.size] == ?/
45
-
46
- return app.call(
47
- env.merge(
48
- 'SCRIPT_NAME' => (script_name + location),
49
- 'PATH_INFO' => path[location.size..-1]))
50
- }
51
- [404, {"Content-Type" => "text/plain"}, ["Not Found: #{path}"]]
52
- end
53
- end
54
- end
55
-
@@ -1,522 +0,0 @@
1
- # -*- encoding: binary -*-
2
-
3
- require 'set'
4
- require 'tempfile'
5
-
6
- module Rack
7
- # Rack::Utils contains a grab-bag of useful methods for writing web
8
- # applications adopted from all kinds of Ruby libraries.
9
-
10
- module Utils
11
- # Performs URI escaping so that you can construct proper
12
- # query strings faster. Use this rather than the cgi.rb
13
- # version since it's faster. (Stolen from Camping).
14
- def escape(s)
15
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
16
- '%'+$1.unpack('H2'*$1.size).join('%').upcase
17
- }.tr(' ', '+')
18
- end
19
- module_function :escape
20
-
21
- # Unescapes a URI escaped string. (Stolen from Camping).
22
- def unescape(s)
23
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
24
- [$1.delete('%')].pack('H*')
25
- }
26
- end
27
- module_function :unescape
28
-
29
- DEFAULT_SEP = /[&;] */n
30
-
31
- # Stolen from Mongrel, with some small modifications:
32
- # Parses a query string by breaking it up at the '&'
33
- # and ';' characters. You can also use this to parse
34
- # cookies by changing the characters used in the second
35
- # parameter (which defaults to '&;').
36
- def parse_query(qs, d = nil)
37
- params = {}
38
-
39
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
40
- k, v = p.split('=', 2).map { |x| unescape(x) }
41
-
42
- if cur = params[k]
43
- if cur.class == Array
44
- params[k] << v
45
- else
46
- params[k] = [cur, v]
47
- end
48
- else
49
- params[k] = v
50
- end
51
- end
52
-
53
- return params
54
- end
55
- module_function :parse_query
56
-
57
- def parse_nested_query(qs, d = nil)
58
- params = {}
59
-
60
- (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
61
- k, v = unescape(p).split('=', 2)
62
- normalize_params(params, k, v)
63
- end
64
-
65
- return params
66
- end
67
- module_function :parse_nested_query
68
-
69
- def normalize_params(params, name, v = nil)
70
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
71
- k = $1 || ''
72
- after = $' || ''
73
-
74
- return if k.empty?
75
-
76
- if after == ""
77
- params[k] = v
78
- elsif after == "[]"
79
- params[k] ||= []
80
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
81
- params[k] << v
82
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
83
- child_key = $1
84
- params[k] ||= []
85
- raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
86
- if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
87
- normalize_params(params[k].last, child_key, v)
88
- else
89
- params[k] << normalize_params({}, child_key, v)
90
- end
91
- else
92
- params[k] ||= {}
93
- raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
94
- params[k] = normalize_params(params[k], after, v)
95
- end
96
-
97
- return params
98
- end
99
- module_function :normalize_params
100
-
101
- def build_query(params)
102
- params.map { |k, v|
103
- if v.class == Array
104
- build_query(v.map { |x| [k, x] })
105
- else
106
- "#{escape(k)}=#{escape(v)}"
107
- end
108
- }.join("&")
109
- end
110
- module_function :build_query
111
-
112
- def build_nested_query(value, prefix = nil)
113
- case value
114
- when Array
115
- value.map { |v|
116
- build_nested_query(v, "#{prefix}[]")
117
- }.join("&")
118
- when Hash
119
- value.map { |k, v|
120
- build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
121
- }.join("&")
122
- when String
123
- raise ArgumentError, "value must be a Hash" if prefix.nil?
124
- "#{prefix}=#{escape(value)}"
125
- else
126
- prefix
127
- end
128
- end
129
- module_function :build_nested_query
130
-
131
- # Escape ampersands, brackets and quotes to their HTML/XML entities.
132
- def escape_html(string)
133
- string.to_s.gsub("&", "&amp;").
134
- gsub("<", "&lt;").
135
- gsub(">", "&gt;").
136
- gsub("'", "&#39;").
137
- gsub('"', "&quot;")
138
- end
139
- module_function :escape_html
140
-
141
- def select_best_encoding(available_encodings, accept_encoding)
142
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
143
-
144
- expanded_accept_encoding =
145
- accept_encoding.map { |m, q|
146
- if m == "*"
147
- (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] }
148
- else
149
- [[m, q]]
150
- end
151
- }.inject([]) { |mem, list|
152
- mem + list
153
- }
154
-
155
- encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m }
156
-
157
- unless encoding_candidates.include?("identity")
158
- encoding_candidates.push("identity")
159
- end
160
-
161
- expanded_accept_encoding.find_all { |m, q|
162
- q == 0.0
163
- }.each { |m, _|
164
- encoding_candidates.delete(m)
165
- }
166
-
167
- return (encoding_candidates & available_encodings)[0]
168
- end
169
- module_function :select_best_encoding
170
-
171
- # Return the bytesize of String; uses String#length under Ruby 1.8 and
172
- # String#bytesize under 1.9.
173
- if ''.respond_to?(:bytesize)
174
- def bytesize(string)
175
- string.bytesize
176
- end
177
- else
178
- def bytesize(string)
179
- string.size
180
- end
181
- end
182
- module_function :bytesize
183
-
184
- # Context allows the use of a compatible middleware at different points
185
- # in a request handling stack. A compatible middleware must define
186
- # #context which should take the arguments env and app. The first of which
187
- # would be the request environment. The second of which would be the rack
188
- # application that the request would be forwarded to.
189
- class Context
190
- attr_reader :for, :app
191
-
192
- def initialize(app_f, app_r)
193
- raise 'running context does not respond to #context' unless app_f.respond_to? :context
194
- @for, @app = app_f, app_r
195
- end
196
-
197
- def call(env)
198
- @for.context(env, @app)
199
- end
200
-
201
- def recontext(app)
202
- self.class.new(@for, app)
203
- end
204
-
205
- def context(env, app=@app)
206
- recontext(app).call(env)
207
- end
208
- end
209
-
210
- # A case-insensitive Hash that preserves the original case of a
211
- # header when set.
212
- class HeaderHash < Hash
213
- def initialize(hash={})
214
- super()
215
- @names = {}
216
- hash.each { |k, v| self[k] = v }
217
- end
218
-
219
- def to_hash
220
- inject({}) do |hash, (k,v)|
221
- if v.respond_to? :to_ary
222
- hash[k] = v.to_ary.join("\n")
223
- else
224
- hash[k] = v
225
- end
226
- hash
227
- end
228
- end
229
-
230
- def [](k)
231
- super(@names[k] ||= @names[k.downcase])
232
- end
233
-
234
- def []=(k, v)
235
- delete k
236
- @names[k] = @names[k.downcase] = k
237
- super k, v
238
- end
239
-
240
- def delete(k)
241
- canonical = k.downcase
242
- result = super @names.delete(canonical)
243
- @names.delete_if { |name,| name.downcase == canonical }
244
- result
245
- end
246
-
247
- def include?(k)
248
- @names.include?(k) || @names.include?(k.downcase)
249
- end
250
-
251
- alias_method :has_key?, :include?
252
- alias_method :member?, :include?
253
- alias_method :key?, :include?
254
-
255
- def merge!(other)
256
- other.each { |k, v| self[k] = v }
257
- self
258
- end
259
-
260
- def merge(other)
261
- hash = dup
262
- hash.merge! other
263
- end
264
- end
265
-
266
- # Every standard HTTP code mapped to the appropriate message.
267
- # Stolen from Mongrel.
268
- HTTP_STATUS_CODES = {
269
- 100 => 'Continue',
270
- 101 => 'Switching Protocols',
271
- 200 => 'OK',
272
- 201 => 'Created',
273
- 202 => 'Accepted',
274
- 203 => 'Non-Authoritative Information',
275
- 204 => 'No Content',
276
- 205 => 'Reset Content',
277
- 206 => 'Partial Content',
278
- 300 => 'Multiple Choices',
279
- 301 => 'Moved Permanently',
280
- 302 => 'Found',
281
- 303 => 'See Other',
282
- 304 => 'Not Modified',
283
- 305 => 'Use Proxy',
284
- 307 => 'Temporary Redirect',
285
- 400 => 'Bad Request',
286
- 401 => 'Unauthorized',
287
- 402 => 'Payment Required',
288
- 403 => 'Forbidden',
289
- 404 => 'Not Found',
290
- 405 => 'Method Not Allowed',
291
- 406 => 'Not Acceptable',
292
- 407 => 'Proxy Authentication Required',
293
- 408 => 'Request Timeout',
294
- 409 => 'Conflict',
295
- 410 => 'Gone',
296
- 411 => 'Length Required',
297
- 412 => 'Precondition Failed',
298
- 413 => 'Request Entity Too Large',
299
- 414 => 'Request-URI Too Large',
300
- 415 => 'Unsupported Media Type',
301
- 416 => 'Requested Range Not Satisfiable',
302
- 417 => 'Expectation Failed',
303
- 500 => 'Internal Server Error',
304
- 501 => 'Not Implemented',
305
- 502 => 'Bad Gateway',
306
- 503 => 'Service Unavailable',
307
- 504 => 'Gateway Timeout',
308
- 505 => 'HTTP Version Not Supported'
309
- }
310
-
311
- # Responses with HTTP status codes that should not have an entity body
312
- STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
313
-
314
- # A multipart form data parser, adapted from IOWA.
315
- #
316
- # Usually, Rack::Request#POST takes care of calling this.
317
-
318
- module Multipart
319
- class UploadedFile
320
- # The filename, *not* including the path, of the "uploaded" file
321
- attr_reader :original_filename
322
-
323
- # The content type of the "uploaded" file
324
- attr_accessor :content_type
325
-
326
- def initialize(path, content_type = "text/plain", binary = false)
327
- raise "#{path} file does not exist" unless ::File.exist?(path)
328
- @content_type = content_type
329
- @original_filename = ::File.basename(path)
330
- @tempfile = Tempfile.new(@original_filename)
331
- @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
332
- @tempfile.binmode if binary
333
- FileUtils.copy_file(path, @tempfile.path)
334
- end
335
-
336
- def path
337
- @tempfile.path
338
- end
339
- alias_method :local_path, :path
340
-
341
- def method_missing(method_name, *args, &block) #:nodoc:
342
- @tempfile.__send__(method_name, *args, &block)
343
- end
344
- end
345
-
346
- EOL = "\r\n"
347
- MULTIPART_BOUNDARY = "AaB03x"
348
-
349
- def self.parse_multipart(env)
350
- unless env['CONTENT_TYPE'] =~
351
- %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
352
- nil
353
- else
354
- boundary = "--#{$1}"
355
-
356
- params = {}
357
- buf = ""
358
- content_length = env['CONTENT_LENGTH'].to_i
359
- input = env['rack.input']
360
- input.rewind
361
-
362
- boundary_size = Utils.bytesize(boundary) + EOL.size
363
- bufsize = 16384
364
-
365
- content_length -= boundary_size
366
-
367
- read_buffer = ''
368
-
369
- status = input.read(boundary_size, read_buffer)
370
- raise EOFError, "bad content body" unless status == boundary + EOL
371
-
372
- rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
373
-
374
- loop {
375
- head = nil
376
- body = ''
377
- filename = content_type = name = nil
378
-
379
- until head && buf =~ rx
380
- if !head && i = buf.index(EOL+EOL)
381
- head = buf.slice!(0, i+2) # First \r\n
382
- buf.slice!(0, 2) # Second \r\n
383
-
384
- filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
385
- content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
386
- name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
387
-
388
- if content_type || filename
389
- body = Tempfile.new("RackMultipart")
390
- body.binmode if body.respond_to?(:binmode)
391
- end
392
-
393
- next
394
- end
395
-
396
- # Save the read body part.
397
- if head && (boundary_size+4 < buf.size)
398
- body << buf.slice!(0, buf.size - (boundary_size+4))
399
- end
400
-
401
- c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
402
- raise EOFError, "bad content body" if c.nil? || c.empty?
403
- buf << c
404
- content_length -= c.size
405
- end
406
-
407
- # Save the rest.
408
- if i = buf.index(rx)
409
- body << buf.slice!(0, i)
410
- buf.slice!(0, boundary_size+2)
411
-
412
- content_length = -1 if $1 == "--"
413
- end
414
-
415
- if filename == ""
416
- # filename is blank which means no file has been selected
417
- data = nil
418
- elsif filename
419
- body.rewind
420
-
421
- # Take the basename of the upload's original filename.
422
- # This handles the full Windows paths given by Internet Explorer
423
- # (and perhaps other broken user agents) without affecting
424
- # those which give the lone filename.
425
- filename =~ /^(?:.*[:\\\/])?(.*)/m
426
- filename = $1
427
-
428
- data = {:filename => filename, :type => content_type,
429
- :name => name, :tempfile => body, :head => head}
430
- elsif !filename && content_type
431
- body.rewind
432
-
433
- # Generic multipart cases, not coming from a form
434
- data = {:type => content_type,
435
- :name => name, :tempfile => body, :head => head}
436
- else
437
- data = body
438
- end
439
-
440
- Utils.normalize_params(params, name, data) unless data.nil?
441
-
442
- break if buf.empty? || content_length == -1
443
- }
444
-
445
- input.rewind
446
-
447
- params
448
- end
449
- end
450
-
451
- def self.build_multipart(params, first = true)
452
- if first
453
- unless params.is_a?(Hash)
454
- raise ArgumentError, "value must be a Hash"
455
- end
456
-
457
- multipart = false
458
- query = lambda { |value|
459
- case value
460
- when Array
461
- value.each(&query)
462
- when Hash
463
- value.values.each(&query)
464
- when UploadedFile
465
- multipart = true
466
- end
467
- }
468
- params.values.each(&query)
469
- return nil unless multipart
470
- end
471
-
472
- flattened_params = Hash.new
473
-
474
- params.each do |key, value|
475
- k = first ? key.to_s : "[#{key}]"
476
-
477
- case value
478
- when Array
479
- value.map { |v|
480
- build_multipart(v, false).each { |subkey, subvalue|
481
- flattened_params["#{k}[]#{subkey}"] = subvalue
482
- }
483
- }
484
- when Hash
485
- build_multipart(value, false).each { |subkey, subvalue|
486
- flattened_params[k + subkey] = subvalue
487
- }
488
- else
489
- flattened_params[k] = value
490
- end
491
- end
492
-
493
- if first
494
- flattened_params.map { |name, file|
495
- if file.respond_to?(:original_filename)
496
- ::File.open(file.path, "rb") do |f|
497
- f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
498
- <<-EOF
499
- --#{MULTIPART_BOUNDARY}\r
500
- Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
501
- Content-Type: #{file.content_type}\r
502
- Content-Length: #{::File.stat(file.path).size}\r
503
- \r
504
- #{f.read}\r
505
- EOF
506
- end
507
- else
508
- <<-EOF
509
- --#{MULTIPART_BOUNDARY}\r
510
- Content-Disposition: form-data; name="#{name}"\r
511
- \r
512
- #{file}\r
513
- EOF
514
- end
515
- }.join + "--#{MULTIPART_BOUNDARY}--\r"
516
- else
517
- flattened_params
518
- end
519
- end
520
- end
521
- end
522
- end