edgar-rack 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +21 -0
  3. data/README +401 -0
  4. data/Rakefile +101 -0
  5. data/SPEC +171 -0
  6. data/bin/rackup +4 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/example/lobster.ru +4 -0
  9. data/example/protectedlobster.rb +14 -0
  10. data/example/protectedlobster.ru +8 -0
  11. data/lib/rack.rb +81 -0
  12. data/lib/rack/auth/abstract/handler.rb +37 -0
  13. data/lib/rack/auth/abstract/request.rb +43 -0
  14. data/lib/rack/auth/basic.rb +58 -0
  15. data/lib/rack/auth/digest/md5.rb +124 -0
  16. data/lib/rack/auth/digest/nonce.rb +51 -0
  17. data/lib/rack/auth/digest/params.rb +53 -0
  18. data/lib/rack/auth/digest/request.rb +40 -0
  19. data/lib/rack/builder.rb +80 -0
  20. data/lib/rack/cascade.rb +41 -0
  21. data/lib/rack/chunked.rb +52 -0
  22. data/lib/rack/commonlogger.rb +49 -0
  23. data/lib/rack/conditionalget.rb +63 -0
  24. data/lib/rack/config.rb +15 -0
  25. data/lib/rack/content_length.rb +29 -0
  26. data/lib/rack/content_type.rb +23 -0
  27. data/lib/rack/deflater.rb +96 -0
  28. data/lib/rack/directory.rb +157 -0
  29. data/lib/rack/etag.rb +59 -0
  30. data/lib/rack/file.rb +118 -0
  31. data/lib/rack/handler.rb +88 -0
  32. data/lib/rack/handler/cgi.rb +61 -0
  33. data/lib/rack/handler/evented_mongrel.rb +8 -0
  34. data/lib/rack/handler/fastcgi.rb +90 -0
  35. data/lib/rack/handler/lsws.rb +61 -0
  36. data/lib/rack/handler/mongrel.rb +90 -0
  37. data/lib/rack/handler/scgi.rb +59 -0
  38. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  39. data/lib/rack/handler/thin.rb +17 -0
  40. data/lib/rack/handler/webrick.rb +73 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +567 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +44 -0
  45. data/lib/rack/logger.rb +18 -0
  46. data/lib/rack/methodoverride.rb +27 -0
  47. data/lib/rack/mime.rb +210 -0
  48. data/lib/rack/mock.rb +185 -0
  49. data/lib/rack/nulllogger.rb +18 -0
  50. data/lib/rack/recursive.rb +61 -0
  51. data/lib/rack/reloader.rb +109 -0
  52. data/lib/rack/request.rb +307 -0
  53. data/lib/rack/response.rb +151 -0
  54. data/lib/rack/rewindable_input.rb +104 -0
  55. data/lib/rack/runtime.rb +27 -0
  56. data/lib/rack/sendfile.rb +139 -0
  57. data/lib/rack/server.rb +289 -0
  58. data/lib/rack/session/abstract/id.rb +348 -0
  59. data/lib/rack/session/cookie.rb +152 -0
  60. data/lib/rack/session/memcache.rb +93 -0
  61. data/lib/rack/session/pool.rb +79 -0
  62. data/lib/rack/showexceptions.rb +378 -0
  63. data/lib/rack/showstatus.rb +113 -0
  64. data/lib/rack/static.rb +53 -0
  65. data/lib/rack/urlmap.rb +55 -0
  66. data/lib/rack/utils.rb +698 -0
  67. data/rack.gemspec +39 -0
  68. data/test/cgi/lighttpd.conf +25 -0
  69. data/test/cgi/rackup_stub.rb +6 -0
  70. data/test/cgi/sample_rackup.ru +5 -0
  71. data/test/cgi/test +9 -0
  72. data/test/cgi/test.fcgi +8 -0
  73. data/test/cgi/test.ru +5 -0
  74. data/test/gemloader.rb +6 -0
  75. data/test/multipart/bad_robots +259 -0
  76. data/test/multipart/binary +0 -0
  77. data/test/multipart/empty +10 -0
  78. data/test/multipart/fail_16384_nofile +814 -0
  79. data/test/multipart/file1.txt +1 -0
  80. data/test/multipart/filename_and_modification_param +7 -0
  81. data/test/multipart/filename_with_escaped_quotes +6 -0
  82. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  83. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  84. data/test/multipart/filename_with_unescaped_quotes +6 -0
  85. data/test/multipart/ie +6 -0
  86. data/test/multipart/nested +10 -0
  87. data/test/multipart/none +9 -0
  88. data/test/multipart/semicolon +6 -0
  89. data/test/multipart/text +15 -0
  90. data/test/rackup/config.ru +31 -0
  91. data/test/spec_auth_basic.rb +70 -0
  92. data/test/spec_auth_digest.rb +241 -0
  93. data/test/spec_builder.rb +123 -0
  94. data/test/spec_cascade.rb +45 -0
  95. data/test/spec_cgi.rb +102 -0
  96. data/test/spec_chunked.rb +60 -0
  97. data/test/spec_commonlogger.rb +56 -0
  98. data/test/spec_conditionalget.rb +86 -0
  99. data/test/spec_config.rb +23 -0
  100. data/test/spec_content_length.rb +36 -0
  101. data/test/spec_content_type.rb +29 -0
  102. data/test/spec_deflater.rb +125 -0
  103. data/test/spec_directory.rb +57 -0
  104. data/test/spec_etag.rb +75 -0
  105. data/test/spec_fastcgi.rb +107 -0
  106. data/test/spec_file.rb +92 -0
  107. data/test/spec_handler.rb +49 -0
  108. data/test/spec_head.rb +30 -0
  109. data/test/spec_lint.rb +515 -0
  110. data/test/spec_lobster.rb +43 -0
  111. data/test/spec_lock.rb +142 -0
  112. data/test/spec_logger.rb +28 -0
  113. data/test/spec_methodoverride.rb +58 -0
  114. data/test/spec_mock.rb +241 -0
  115. data/test/spec_mongrel.rb +182 -0
  116. data/test/spec_nulllogger.rb +12 -0
  117. data/test/spec_recursive.rb +69 -0
  118. data/test/spec_request.rb +774 -0
  119. data/test/spec_response.rb +245 -0
  120. data/test/spec_rewindable_input.rb +118 -0
  121. data/test/spec_runtime.rb +39 -0
  122. data/test/spec_sendfile.rb +83 -0
  123. data/test/spec_server.rb +8 -0
  124. data/test/spec_session_abstract_id.rb +43 -0
  125. data/test/spec_session_cookie.rb +171 -0
  126. data/test/spec_session_memcache.rb +289 -0
  127. data/test/spec_session_pool.rb +200 -0
  128. data/test/spec_showexceptions.rb +87 -0
  129. data/test/spec_showstatus.rb +79 -0
  130. data/test/spec_static.rb +48 -0
  131. data/test/spec_thin.rb +86 -0
  132. data/test/spec_urlmap.rb +213 -0
  133. data/test/spec_utils.rb +678 -0
  134. data/test/spec_webrick.rb +141 -0
  135. data/test/testrequest.rb +78 -0
  136. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  137. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  138. metadata +329 -0
@@ -0,0 +1,65 @@
1
+ require 'zlib'
2
+
3
+ require 'rack/request'
4
+ require 'rack/response'
5
+
6
+ module Rack
7
+ # Paste has a Pony, Rack has a Lobster!
8
+ class Lobster
9
+ LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2
10
+ P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0
11
+ t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ
12
+ I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0])
13
+
14
+ LambdaLobster = lambda { |env|
15
+ if env["QUERY_STRING"].include?("flip")
16
+ lobster = LobsterString.split("\n").
17
+ map { |line| line.ljust(42).reverse }.
18
+ join("\n")
19
+ href = "?"
20
+ else
21
+ lobster = LobsterString
22
+ href = "?flip"
23
+ end
24
+
25
+ content = ["<title>Lobstericious!</title>",
26
+ "<pre>", lobster, "</pre>",
27
+ "<a href='#{href}'>flip!</a>"]
28
+ length = content.inject(0) { |a,e| a+e.size }.to_s
29
+ [200, {"Content-Type" => "text/html", "Content-Length" => length}, content]
30
+ }
31
+
32
+ def call(env)
33
+ req = Request.new(env)
34
+ if req.GET["flip"] == "left"
35
+ lobster = LobsterString.split("\n").
36
+ map { |line| line.ljust(42).reverse }.
37
+ join("\n")
38
+ href = "?flip=right"
39
+ elsif req.GET["flip"] == "crash"
40
+ raise "Lobster crashed"
41
+ else
42
+ lobster = LobsterString
43
+ href = "?flip=left"
44
+ end
45
+
46
+ res = Response.new
47
+ res.write "<title>Lobstericious!</title>"
48
+ res.write "<pre>"
49
+ res.write lobster
50
+ res.write "</pre>"
51
+ res.write "<p><a href='#{href}'>flip!</a></p>"
52
+ res.write "<p><a href='?flip=crash'>crash!</a></p>"
53
+ res.finish
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ if $0 == __FILE__
60
+ require 'rack'
61
+ require 'rack/showexceptions'
62
+ Rack::Handler::WEBrick.run \
63
+ Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
64
+ :Port => 9292
65
+ end
@@ -0,0 +1,44 @@
1
+ require 'thread'
2
+
3
+ module Rack
4
+ class Lock
5
+ class Proxy < Struct.new(:target, :mutex) # :nodoc:
6
+ def each
7
+ target.each { |x| yield x }
8
+ end
9
+
10
+ def close
11
+ target.close if target.respond_to?(:close)
12
+ ensure
13
+ mutex.unlock
14
+ end
15
+
16
+ def to_path
17
+ target.to_path
18
+ end
19
+
20
+ def respond_to?(sym)
21
+ sym.to_sym == :close || target.respond_to?(sym)
22
+ end
23
+ end
24
+
25
+ FLAG = 'rack.multithread'.freeze
26
+
27
+ def initialize(app, mutex = Mutex.new)
28
+ @app, @mutex = app, mutex
29
+ end
30
+
31
+ def call(env)
32
+ old, env[FLAG] = env[FLAG], false
33
+ @mutex.lock
34
+ response = @app.call(env)
35
+ response[2] = Proxy.new(response[2], @mutex)
36
+ response
37
+ rescue Exception
38
+ @mutex.unlock
39
+ raise
40
+ ensure
41
+ env[FLAG] = old
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require 'logger'
2
+
3
+ module Rack
4
+ # Sets up rack.logger to write to rack.errors stream
5
+ class Logger
6
+ def initialize(app, level = ::Logger::INFO)
7
+ @app, @level = app, level
8
+ end
9
+
10
+ def call(env)
11
+ logger = ::Logger.new(env['rack.errors'])
12
+ logger.level = @level
13
+
14
+ env['rack.logger'] = logger
15
+ @app.call(env)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ module Rack
2
+ class MethodOverride
3
+ HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
4
+
5
+ METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
6
+ HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ if env["REQUEST_METHOD"] == "POST"
14
+ req = Request.new(env)
15
+ method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
16
+ env[HTTP_METHOD_OVERRIDE_HEADER]
17
+ method = method.to_s.upcase
18
+ if HTTP_METHODS.include?(method)
19
+ env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
20
+ env["REQUEST_METHOD"] = method
21
+ end
22
+ end
23
+
24
+ @app.call(env)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,210 @@
1
+ module Rack
2
+ module Mime
3
+ # Returns String with mime type if found, otherwise use +fallback+.
4
+ # +ext+ should be filename extension in the '.ext' format that
5
+ # File.extname(file) returns.
6
+ # +fallback+ may be any object
7
+ #
8
+ # Also see the documentation for MIME_TYPES
9
+ #
10
+ # Usage:
11
+ # Rack::Mime.mime_type('.foo')
12
+ #
13
+ # This is a shortcut for:
14
+ # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream')
15
+
16
+ def mime_type(ext, fallback='application/octet-stream')
17
+ MIME_TYPES.fetch(ext.to_s.downcase, fallback)
18
+ end
19
+ module_function :mime_type
20
+
21
+ # List of most common mime-types, selected various sources
22
+ # according to their usefulness in a webserving scope for Ruby
23
+ # users.
24
+ #
25
+ # To amend this list with your local mime.types list you can use:
26
+ #
27
+ # require 'webrick/httputils'
28
+ # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types')
29
+ # Rack::Mime::MIME_TYPES.merge!(list)
30
+ #
31
+ # To add the list mongrel provides, use:
32
+ #
33
+ # require 'mongrel/handlers'
34
+ # Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES)
35
+
36
+ MIME_TYPES = {
37
+ ".3gp" => "video/3gpp",
38
+ ".a" => "application/octet-stream",
39
+ ".ai" => "application/postscript",
40
+ ".aif" => "audio/x-aiff",
41
+ ".aiff" => "audio/x-aiff",
42
+ ".asc" => "application/pgp-signature",
43
+ ".asf" => "video/x-ms-asf",
44
+ ".asm" => "text/x-asm",
45
+ ".asx" => "video/x-ms-asf",
46
+ ".atom" => "application/atom+xml",
47
+ ".au" => "audio/basic",
48
+ ".avi" => "video/x-msvideo",
49
+ ".bat" => "application/x-msdownload",
50
+ ".bin" => "application/octet-stream",
51
+ ".bmp" => "image/bmp",
52
+ ".bz2" => "application/x-bzip2",
53
+ ".c" => "text/x-c",
54
+ ".cab" => "application/vnd.ms-cab-compressed",
55
+ ".cc" => "text/x-c",
56
+ ".chm" => "application/vnd.ms-htmlhelp",
57
+ ".class" => "application/octet-stream",
58
+ ".com" => "application/x-msdownload",
59
+ ".conf" => "text/plain",
60
+ ".cpp" => "text/x-c",
61
+ ".crt" => "application/x-x509-ca-cert",
62
+ ".css" => "text/css",
63
+ ".csv" => "text/csv",
64
+ ".cxx" => "text/x-c",
65
+ ".deb" => "application/x-debian-package",
66
+ ".der" => "application/x-x509-ca-cert",
67
+ ".diff" => "text/x-diff",
68
+ ".djv" => "image/vnd.djvu",
69
+ ".djvu" => "image/vnd.djvu",
70
+ ".dll" => "application/x-msdownload",
71
+ ".dmg" => "application/octet-stream",
72
+ ".doc" => "application/msword",
73
+ ".dot" => "application/msword",
74
+ ".dtd" => "application/xml-dtd",
75
+ ".dvi" => "application/x-dvi",
76
+ ".ear" => "application/java-archive",
77
+ ".eml" => "message/rfc822",
78
+ ".eps" => "application/postscript",
79
+ ".exe" => "application/x-msdownload",
80
+ ".f" => "text/x-fortran",
81
+ ".f77" => "text/x-fortran",
82
+ ".f90" => "text/x-fortran",
83
+ ".flv" => "video/x-flv",
84
+ ".for" => "text/x-fortran",
85
+ ".gem" => "application/octet-stream",
86
+ ".gemspec" => "text/x-script.ruby",
87
+ ".gif" => "image/gif",
88
+ ".gz" => "application/x-gzip",
89
+ ".h" => "text/x-c",
90
+ ".htc" => "text/x-component",
91
+ ".hh" => "text/x-c",
92
+ ".htm" => "text/html",
93
+ ".html" => "text/html",
94
+ ".ico" => "image/vnd.microsoft.icon",
95
+ ".ics" => "text/calendar",
96
+ ".ifb" => "text/calendar",
97
+ ".iso" => "application/octet-stream",
98
+ ".jar" => "application/java-archive",
99
+ ".java" => "text/x-java-source",
100
+ ".jnlp" => "application/x-java-jnlp-file",
101
+ ".jpeg" => "image/jpeg",
102
+ ".jpg" => "image/jpeg",
103
+ ".js" => "application/javascript",
104
+ ".json" => "application/json",
105
+ ".log" => "text/plain",
106
+ ".m3u" => "audio/x-mpegurl",
107
+ ".m4v" => "video/mp4",
108
+ ".man" => "text/troff",
109
+ ".manifest"=> "text/cache-manifest",
110
+ ".mathml" => "application/mathml+xml",
111
+ ".mbox" => "application/mbox",
112
+ ".mdoc" => "text/troff",
113
+ ".me" => "text/troff",
114
+ ".mid" => "audio/midi",
115
+ ".midi" => "audio/midi",
116
+ ".mime" => "message/rfc822",
117
+ ".mml" => "application/mathml+xml",
118
+ ".mng" => "video/x-mng",
119
+ ".mov" => "video/quicktime",
120
+ ".mp3" => "audio/mpeg",
121
+ ".mp4" => "video/mp4",
122
+ ".mp4v" => "video/mp4",
123
+ ".mpeg" => "video/mpeg",
124
+ ".mpg" => "video/mpeg",
125
+ ".ms" => "text/troff",
126
+ ".msi" => "application/x-msdownload",
127
+ ".odp" => "application/vnd.oasis.opendocument.presentation",
128
+ ".ods" => "application/vnd.oasis.opendocument.spreadsheet",
129
+ ".odt" => "application/vnd.oasis.opendocument.text",
130
+ ".ogg" => "application/ogg",
131
+ ".ogv" => "video/ogg",
132
+ ".p" => "text/x-pascal",
133
+ ".pas" => "text/x-pascal",
134
+ ".pbm" => "image/x-portable-bitmap",
135
+ ".pdf" => "application/pdf",
136
+ ".pem" => "application/x-x509-ca-cert",
137
+ ".pgm" => "image/x-portable-graymap",
138
+ ".pgp" => "application/pgp-encrypted",
139
+ ".pkg" => "application/octet-stream",
140
+ ".pl" => "text/x-script.perl",
141
+ ".pm" => "text/x-script.perl-module",
142
+ ".png" => "image/png",
143
+ ".pnm" => "image/x-portable-anymap",
144
+ ".ppm" => "image/x-portable-pixmap",
145
+ ".pps" => "application/vnd.ms-powerpoint",
146
+ ".ppt" => "application/vnd.ms-powerpoint",
147
+ ".ps" => "application/postscript",
148
+ ".psd" => "image/vnd.adobe.photoshop",
149
+ ".py" => "text/x-script.python",
150
+ ".qt" => "video/quicktime",
151
+ ".ra" => "audio/x-pn-realaudio",
152
+ ".rake" => "text/x-script.ruby",
153
+ ".ram" => "audio/x-pn-realaudio",
154
+ ".rar" => "application/x-rar-compressed",
155
+ ".rb" => "text/x-script.ruby",
156
+ ".rdf" => "application/rdf+xml",
157
+ ".roff" => "text/troff",
158
+ ".rpm" => "application/x-redhat-package-manager",
159
+ ".rss" => "application/rss+xml",
160
+ ".rtf" => "application/rtf",
161
+ ".ru" => "text/x-script.ruby",
162
+ ".s" => "text/x-asm",
163
+ ".sgm" => "text/sgml",
164
+ ".sgml" => "text/sgml",
165
+ ".sh" => "application/x-sh",
166
+ ".sig" => "application/pgp-signature",
167
+ ".snd" => "audio/basic",
168
+ ".so" => "application/octet-stream",
169
+ ".svg" => "image/svg+xml",
170
+ ".svgz" => "image/svg+xml",
171
+ ".swf" => "application/x-shockwave-flash",
172
+ ".t" => "text/troff",
173
+ ".tar" => "application/x-tar",
174
+ ".tbz" => "application/x-bzip-compressed-tar",
175
+ ".tcl" => "application/x-tcl",
176
+ ".tex" => "application/x-tex",
177
+ ".texi" => "application/x-texinfo",
178
+ ".texinfo" => "application/x-texinfo",
179
+ ".text" => "text/plain",
180
+ ".tif" => "image/tiff",
181
+ ".tiff" => "image/tiff",
182
+ ".torrent" => "application/x-bittorrent",
183
+ ".tr" => "text/troff",
184
+ ".ttf" => "application/octet-stream",
185
+ ".txt" => "text/plain",
186
+ ".vcf" => "text/x-vcard",
187
+ ".vcs" => "text/x-vcalendar",
188
+ ".vrml" => "model/vrml",
189
+ ".war" => "application/java-archive",
190
+ ".wav" => "audio/x-wav",
191
+ ".webm" => "video/webm",
192
+ ".wma" => "audio/x-ms-wma",
193
+ ".wmv" => "video/x-ms-wmv",
194
+ ".wmx" => "video/x-ms-wmx",
195
+ ".woff" => "application/octet-stream",
196
+ ".wrl" => "model/vrml",
197
+ ".wsdl" => "application/wsdl+xml",
198
+ ".xbm" => "image/x-xbitmap",
199
+ ".xhtml" => "application/xhtml+xml",
200
+ ".xls" => "application/vnd.ms-excel",
201
+ ".xml" => "application/xml",
202
+ ".xpm" => "image/x-xpixmap",
203
+ ".xsl" => "application/xml",
204
+ ".xslt" => "application/xslt+xml",
205
+ ".yaml" => "text/yaml",
206
+ ".yml" => "text/yaml",
207
+ ".zip" => "application/zip",
208
+ }
209
+ end
210
+ end
@@ -0,0 +1,185 @@
1
+ require 'uri'
2
+ require 'stringio'
3
+ require 'rack'
4
+ require 'rack/lint'
5
+ require 'rack/utils'
6
+ require 'rack/response'
7
+
8
+ module Rack
9
+ # Rack::MockRequest helps testing your Rack application without
10
+ # actually using HTTP.
11
+ #
12
+ # After performing a request on a URL with get/post/put/delete, it
13
+ # returns a MockResponse with useful helper methods for effective
14
+ # testing.
15
+ #
16
+ # You can pass a hash with additional configuration to the
17
+ # get/post/put/delete.
18
+ # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
19
+ # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
20
+ # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
21
+
22
+ class MockRequest
23
+ class FatalWarning < RuntimeError
24
+ end
25
+
26
+ class FatalWarner
27
+ def puts(warning)
28
+ raise FatalWarning, warning
29
+ end
30
+
31
+ def write(warning)
32
+ raise FatalWarning, warning
33
+ end
34
+
35
+ def flush
36
+ end
37
+
38
+ def string
39
+ ""
40
+ end
41
+ end
42
+
43
+ DEFAULT_ENV = {
44
+ "rack.version" => Rack::VERSION,
45
+ "rack.input" => StringIO.new,
46
+ "rack.errors" => StringIO.new,
47
+ "rack.multithread" => true,
48
+ "rack.multiprocess" => true,
49
+ "rack.run_once" => false,
50
+ }
51
+
52
+ def initialize(app)
53
+ @app = app
54
+ end
55
+
56
+ def get(uri, opts={}) request("GET", uri, opts) end
57
+ def post(uri, opts={}) request("POST", uri, opts) end
58
+ def put(uri, opts={}) request("PUT", uri, opts) end
59
+ def delete(uri, opts={}) request("DELETE", uri, opts) end
60
+
61
+ def request(method="GET", uri="", opts={})
62
+ env = self.class.env_for(uri, opts.merge(:method => method))
63
+
64
+ if opts[:lint]
65
+ app = Rack::Lint.new(@app)
66
+ else
67
+ app = @app
68
+ end
69
+
70
+ errors = env["rack.errors"]
71
+ MockResponse.new(*(app.call(env) + [errors]))
72
+ end
73
+
74
+ # Return the Rack environment used for a request to +uri+.
75
+ def self.env_for(uri="", opts={})
76
+ uri = URI(uri)
77
+ uri.path = "/#{uri.path}" unless uri.path[0] == ?/
78
+
79
+ env = DEFAULT_ENV.dup
80
+
81
+ env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
82
+ env["SERVER_NAME"] = uri.host || "example.org"
83
+ env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
84
+ env["QUERY_STRING"] = uri.query.to_s
85
+ env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
86
+ env["rack.url_scheme"] = uri.scheme || "http"
87
+ env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
88
+
89
+ env["SCRIPT_NAME"] = opts[:script_name] || ""
90
+
91
+ if opts[:fatal]
92
+ env["rack.errors"] = FatalWarner.new
93
+ else
94
+ env["rack.errors"] = StringIO.new
95
+ end
96
+
97
+ if params = opts[:params]
98
+ if env["REQUEST_METHOD"] == "GET"
99
+ params = Utils.parse_nested_query(params) if params.is_a?(String)
100
+ params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
101
+ env["QUERY_STRING"] = Utils.build_nested_query(params)
102
+ elsif !opts.has_key?(:input)
103
+ opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
104
+ if params.is_a?(Hash)
105
+ if data = Utils::Multipart.build_multipart(params)
106
+ opts[:input] = data
107
+ opts["CONTENT_LENGTH"] ||= data.length.to_s
108
+ opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
109
+ else
110
+ opts[:input] = Utils.build_nested_query(params)
111
+ end
112
+ else
113
+ opts[:input] = params
114
+ end
115
+ end
116
+ end
117
+
118
+ empty_str = ""
119
+ empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
120
+ opts[:input] ||= empty_str
121
+ if String === opts[:input]
122
+ rack_input = StringIO.new(opts[:input])
123
+ else
124
+ rack_input = opts[:input]
125
+ end
126
+
127
+ rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
128
+ env['rack.input'] = rack_input
129
+
130
+ env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
131
+
132
+ opts.each { |field, value|
133
+ env[field] = value if String === field
134
+ }
135
+
136
+ env
137
+ end
138
+ end
139
+
140
+ # Rack::MockResponse provides useful helpers for testing your apps.
141
+ # Usually, you don't create the MockResponse on your own, but use
142
+ # MockRequest.
143
+
144
+ class MockResponse < Rack::Response
145
+ # Headers
146
+ attr_reader :original_headers
147
+
148
+ # Errors
149
+ attr_accessor :errors
150
+
151
+ def initialize(status, headers, body, errors=StringIO.new(""))
152
+ @original_headers = headers
153
+ @errors = errors.string if errors.respond_to?(:string)
154
+ @body_string = nil
155
+
156
+ super(body, status, headers)
157
+ end
158
+
159
+ def =~(other)
160
+ body =~ other
161
+ end
162
+
163
+ def match(other)
164
+ body.match other
165
+ end
166
+
167
+ def body
168
+ # FIXME: apparently users of MockResponse expect the return value of
169
+ # MockResponse#body to be a string. However, the real response object
170
+ # returns the body as a list.
171
+ #
172
+ # See spec_showstatus.rb:
173
+ #
174
+ # should "not replace existing messages" do
175
+ # ...
176
+ # res.body.should == "foo!"
177
+ # end
178
+ super.join
179
+ end
180
+
181
+ def empty?
182
+ [201, 204, 304].include? status
183
+ end
184
+ end
185
+ end