From febdfbbdcddba331c04b353bb857a40283f20814 Mon Sep 17 00:00:00 2001 From: Daniel Huckstep Date: Sun, 21 Feb 2010 16:29:30 -0700 Subject: [PATCH] Upgrade vendored sinatra to 1.0.a and include rack 1.1.0 Don't shrink variables when javascript file includes 'eval(' to prevent breaking eval calls --- lib/sinatra/bundles.rb | 118 +--- lib/sinatra/bundles/bundle.rb | 36 + lib/sinatra/bundles/helpers.rb | 22 + lib/sinatra/bundles/javascript_bundle.rb | 37 ++ lib/sinatra/bundles/stylesheet_bundle.rb | 36 + spec/app.rb | 2 +- spec/production_app.rb | 2 +- spec/public/javascripts/eval.js | 5 + spec/sinatra-bundles_spec.rb | 11 +- vendor/bin/rackup | 19 + vendor/cache/rack-1.1.0.gem | Bin 0 -> 104448 bytes vendor/cache/sinatra-1.0.a.gem | Bin 0 -> 117760 bytes vendor/gems/rack-1.1.0/COPYING | 18 + vendor/gems/rack-1.1.0/KNOWN-ISSUES | 21 + vendor/gems/rack-1.1.0/RDOX | 0 vendor/gems/rack-1.1.0/README | 399 +++++++++++ vendor/gems/rack-1.1.0/SPEC | 171 +++++ vendor/gems/rack-1.1.0/bin/rackup | 4 + vendor/gems/rack-1.1.0/contrib/rack_logo.svg | 111 ++++ vendor/gems/rack-1.1.0/example/lobster.ru | 4 + .../rack-1.1.0/example/protectedlobster.rb | 14 + .../rack-1.1.0/example/protectedlobster.ru | 8 + vendor/gems/rack-1.1.0/lib/rack.rb | 92 +++ .../rack-1.1.0/lib/rack/adapter/camping.rb | 22 + .../lib/rack/auth/abstract/handler.rb | 37 ++ .../lib/rack/auth/abstract/request.rb | 37 ++ vendor/gems/rack-1.1.0/lib/rack/auth/basic.rb | 58 ++ .../rack-1.1.0/lib/rack/auth/digest/md5.rb | 124 ++++ .../rack-1.1.0/lib/rack/auth/digest/nonce.rb | 51 ++ .../rack-1.1.0/lib/rack/auth/digest/params.rb | 55 ++ .../lib/rack/auth/digest/request.rb | 40 ++ vendor/gems/rack-1.1.0/lib/rack/builder.rb | 80 +++ vendor/gems/rack-1.1.0/lib/rack/cascade.rb | 41 ++ vendor/gems/rack-1.1.0/lib/rack/chunked.rb | 49 ++ .../gems/rack-1.1.0/lib/rack/commonlogger.rb | 49 ++ .../rack-1.1.0/lib/rack/conditionalget.rb | 47 ++ vendor/gems/rack-1.1.0/lib/rack/config.rb | 15 + .../rack-1.1.0/lib/rack/content_length.rb | 29 + .../gems/rack-1.1.0/lib/rack/content_type.rb | 23 + vendor/gems/rack-1.1.0/lib/rack/deflater.rb | 96 +++ vendor/gems/rack-1.1.0/lib/rack/directory.rb | 157 +++++ vendor/gems/rack-1.1.0/lib/rack/etag.rb | 23 + vendor/gems/rack-1.1.0/lib/rack/file.rb | 90 +++ vendor/gems/rack-1.1.0/lib/rack/handler.rb | 88 +++ .../gems/rack-1.1.0/lib/rack/handler/cgi.rb | 61 ++ .../lib/rack/handler/evented_mongrel.rb | 8 + .../rack-1.1.0/lib/rack/handler/fastcgi.rb | 89 +++ .../gems/rack-1.1.0/lib/rack/handler/lsws.rb | 63 ++ .../rack-1.1.0/lib/rack/handler/mongrel.rb | 90 +++ .../gems/rack-1.1.0/lib/rack/handler/scgi.rb | 62 ++ .../lib/rack/handler/swiftiplied_mongrel.rb | 8 + .../gems/rack-1.1.0/lib/rack/handler/thin.rb | 18 + .../rack-1.1.0/lib/rack/handler/webrick.rb | 69 ++ vendor/gems/rack-1.1.0/lib/rack/head.rb | 19 + vendor/gems/rack-1.1.0/lib/rack/lint.rb | 575 ++++++++++++++++ vendor/gems/rack-1.1.0/lib/rack/lobster.rb | 65 ++ vendor/gems/rack-1.1.0/lib/rack/lock.rb | 16 + vendor/gems/rack-1.1.0/lib/rack/logger.rb | 20 + .../rack-1.1.0/lib/rack/methodoverride.rb | 27 + vendor/gems/rack-1.1.0/lib/rack/mime.rb | 206 ++++++ vendor/gems/rack-1.1.0/lib/rack/mock.rb | 189 ++++++ vendor/gems/rack-1.1.0/lib/rack/nulllogger.rb | 18 + vendor/gems/rack-1.1.0/lib/rack/recursive.rb | 57 ++ vendor/gems/rack-1.1.0/lib/rack/reloader.rb | 109 +++ vendor/gems/rack-1.1.0/lib/rack/request.rb | 271 ++++++++ vendor/gems/rack-1.1.0/lib/rack/response.rb | 149 +++++ .../rack-1.1.0/lib/rack/rewindable_input.rb | 100 +++ vendor/gems/rack-1.1.0/lib/rack/runtime.rb | 27 + vendor/gems/rack-1.1.0/lib/rack/sendfile.rb | 142 ++++ vendor/gems/rack-1.1.0/lib/rack/server.rb | 212 ++++++ .../lib/rack/session/abstract/id.rb | 140 ++++ .../rack-1.1.0/lib/rack/session/cookie.rb | 90 +++ .../rack-1.1.0/lib/rack/session/memcache.rb | 119 ++++ .../gems/rack-1.1.0/lib/rack/session/pool.rb | 100 +++ .../rack-1.1.0/lib/rack/showexceptions.rb | 349 ++++++++++ vendor/gems/rack-1.1.0/lib/rack/showstatus.rb | 106 +++ vendor/gems/rack-1.1.0/lib/rack/static.rb | 38 ++ vendor/gems/rack-1.1.0/lib/rack/urlmap.rb | 56 ++ vendor/gems/rack-1.1.0/lib/rack/utils.rb | 620 ++++++++++++++++++ vendor/gems/rack-1.1.0/rack.gemspec | 38 ++ .../rack-1.1.0/test/spec_rack_auth_basic.rb | 73 +++ .../rack-1.1.0/test/spec_rack_auth_digest.rb | 226 +++++++ .../gems/rack-1.1.0/test/spec_rack_builder.rb | 84 +++ .../gems/rack-1.1.0/test/spec_rack_camping.rb | 51 ++ .../gems/rack-1.1.0/test/spec_rack_cascade.rb | 48 ++ vendor/gems/rack-1.1.0/test/spec_rack_cgi.rb | 89 +++ .../gems/rack-1.1.0/test/spec_rack_chunked.rb | 62 ++ .../rack-1.1.0/test/spec_rack_commonlogger.rb | 61 ++ .../test/spec_rack_conditionalget.rb | 41 ++ .../gems/rack-1.1.0/test/spec_rack_config.rb | 24 + .../test/spec_rack_content_length.rb | 43 ++ .../rack-1.1.0/test/spec_rack_content_type.rb | 30 + .../rack-1.1.0/test/spec_rack_deflater.rb | 127 ++++ .../rack-1.1.0/test/spec_rack_directory.rb | 61 ++ vendor/gems/rack-1.1.0/test/spec_rack_etag.rb | 17 + .../gems/rack-1.1.0/test/spec_rack_fastcgi.rb | 89 +++ vendor/gems/rack-1.1.0/test/spec_rack_file.rb | 75 +++ .../gems/rack-1.1.0/test/spec_rack_handler.rb | 43 ++ vendor/gems/rack-1.1.0/test/spec_rack_head.rb | 30 + vendor/gems/rack-1.1.0/test/spec_rack_lint.rb | 528 +++++++++++++++ .../gems/rack-1.1.0/test/spec_rack_lobster.rb | 45 ++ vendor/gems/rack-1.1.0/test/spec_rack_lock.rb | 38 ++ .../gems/rack-1.1.0/test/spec_rack_logger.rb | 21 + .../test/spec_rack_methodoverride.rb | 60 ++ vendor/gems/rack-1.1.0/test/spec_rack_mock.rb | 243 +++++++ .../gems/rack-1.1.0/test/spec_rack_mongrel.rb | 189 ++++++ .../rack-1.1.0/test/spec_rack_nulllogger.rb | 13 + .../rack-1.1.0/test/spec_rack_recursive.rb | 77 +++ .../gems/rack-1.1.0/test/spec_rack_request.rb | 545 +++++++++++++++ .../rack-1.1.0/test/spec_rack_response.rb | 221 +++++++ .../test/spec_rack_rewindable_input.rb | 118 ++++ .../gems/rack-1.1.0/test/spec_rack_runtime.rb | 35 + .../rack-1.1.0/test/spec_rack_sendfile.rb | 86 +++ .../test/spec_rack_session_cookie.rb | 73 +++ .../test/spec_rack_session_memcache.rb | 273 ++++++++ .../rack-1.1.0/test/spec_rack_session_pool.rb | 172 +++++ .../test/spec_rack_showexceptions.rb | 21 + .../rack-1.1.0/test/spec_rack_showstatus.rb | 72 ++ .../gems/rack-1.1.0/test/spec_rack_static.rb | 37 ++ vendor/gems/rack-1.1.0/test/spec_rack_thin.rb | 91 +++ .../gems/rack-1.1.0/test/spec_rack_urlmap.rb | 215 ++++++ .../gems/rack-1.1.0/test/spec_rack_utils.rb | 552 ++++++++++++++++ .../gems/rack-1.1.0/test/spec_rack_webrick.rb | 130 ++++ vendor/gems/rack-1.1.0/test/spec_rackup.rb | 154 +++++ vendor/gems/sinatra-0.10.1/Rakefile | 129 ---- .../{sinatra-0.10.1 => sinatra-1.0.a}/AUTHORS | 0 .../{sinatra-0.10.1 => sinatra-1.0.a}/CHANGES | 13 +- .../{sinatra-0.10.1 => sinatra-1.0.a}/LICENSE | 0 .../README.jp.rdoc | 0 .../README.rdoc | 0 vendor/gems/sinatra-1.0.a/Rakefile | 113 ++++ .../lib/sinatra.rb | 0 .../lib/sinatra/base.rb | 76 ++- .../lib/sinatra/images/404.png | Bin .../lib/sinatra/images/500.png | Bin .../lib/sinatra/main.rb | 2 +- .../lib/sinatra/showexceptions.rb | 18 +- .../lib/sinatra/tilt.rb | 0 .../sinatra.gemspec | 11 +- .../test/base_test.rb | 0 .../test/builder_test.rb | 0 .../test/contest.rb | 0 .../test/erb_test.rb | 0 .../test/erubis_test.rb | 0 .../test/extensions_test.rb | 0 .../test/filter_test.rb | 26 + .../test/haml_test.rb | 0 .../test/helper.rb | 0 .../test/helpers_test.rb | 0 .../test/mapped_error_test.rb | 0 .../test/middleware_test.rb | 0 .../sinatra-1.0.a/test/public/favicon.ico | 0 .../test/request_test.rb | 0 .../test/response_test.rb | 0 .../test/result_test.rb | 0 .../test/route_added_hook_test.rb | 0 .../test/routing_test.rb | 18 - .../test/sass_test.rb | 0 .../test/server_test.rb | 0 .../gems/sinatra-1.0.a/test/settings_test.rb | 383 +++++++++++ .../test/sinatra_test.rb | 0 .../test/static_test.rb | 6 + .../test/templates_test.rb | 0 .../test/views/error.builder | 0 .../test/views/error.erb | 0 .../test/views/error.erubis | 0 .../test/views/error.haml | 0 .../test/views/error.sass | 0 .../test/views/foo/hello.test | 0 .../test/views/hello.builder | 0 .../test/views/hello.erb | 0 .../test/views/hello.erubis | 0 .../test/views/hello.haml | 0 .../test/views/hello.sass | 0 .../test/views/hello.test | 0 .../test/views/layout2.builder | 0 .../test/views/layout2.erb | 0 .../test/views/layout2.erubis | 0 .../test/views/layout2.haml | 0 .../test/views/layout2.test | 0 vendor/specifications/rack-1.1.0.gemspec | 57 ++ vendor/specifications/sinatra-1.0.a.gemspec | 49 ++ 182 files changed, 12537 insertions(+), 314 deletions(-) create mode 100644 lib/sinatra/bundles/bundle.rb create mode 100644 lib/sinatra/bundles/helpers.rb create mode 100644 lib/sinatra/bundles/javascript_bundle.rb create mode 100644 lib/sinatra/bundles/stylesheet_bundle.rb create mode 100644 spec/public/javascripts/eval.js create mode 100755 vendor/bin/rackup create mode 100644 vendor/cache/rack-1.1.0.gem create mode 100644 vendor/cache/sinatra-1.0.a.gem create mode 100644 vendor/gems/rack-1.1.0/COPYING create mode 100644 vendor/gems/rack-1.1.0/KNOWN-ISSUES create mode 100644 vendor/gems/rack-1.1.0/RDOX create mode 100644 vendor/gems/rack-1.1.0/README create mode 100644 vendor/gems/rack-1.1.0/SPEC create mode 100755 vendor/gems/rack-1.1.0/bin/rackup create mode 100644 vendor/gems/rack-1.1.0/contrib/rack_logo.svg create mode 100644 vendor/gems/rack-1.1.0/example/lobster.ru create mode 100644 vendor/gems/rack-1.1.0/example/protectedlobster.rb create mode 100644 vendor/gems/rack-1.1.0/example/protectedlobster.ru create mode 100644 vendor/gems/rack-1.1.0/lib/rack.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/adapter/camping.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/abstract/handler.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/abstract/request.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/basic.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/digest/md5.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/digest/nonce.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/digest/params.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/auth/digest/request.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/builder.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/cascade.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/chunked.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/commonlogger.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/conditionalget.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/config.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/content_length.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/content_type.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/deflater.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/directory.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/etag.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/file.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/cgi.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/evented_mongrel.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/fastcgi.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/lsws.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/mongrel.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/scgi.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/swiftiplied_mongrel.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/thin.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/handler/webrick.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/head.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/lint.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/lobster.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/lock.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/logger.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/methodoverride.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/mime.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/mock.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/nulllogger.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/recursive.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/reloader.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/request.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/response.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/rewindable_input.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/runtime.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/sendfile.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/server.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/session/abstract/id.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/session/cookie.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/session/memcache.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/session/pool.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/showexceptions.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/showstatus.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/static.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/urlmap.rb create mode 100644 vendor/gems/rack-1.1.0/lib/rack/utils.rb create mode 100644 vendor/gems/rack-1.1.0/rack.gemspec create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_auth_basic.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_auth_digest.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_builder.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_camping.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_cascade.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_cgi.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_chunked.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_commonlogger.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_conditionalget.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_config.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_content_length.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_content_type.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_deflater.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_directory.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_etag.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_fastcgi.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_file.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_handler.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_head.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_lint.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_lobster.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_lock.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_logger.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_methodoverride.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_mock.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_mongrel.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_nulllogger.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_recursive.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_request.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_response.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_rewindable_input.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_runtime.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_sendfile.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_session_cookie.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_session_memcache.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_session_pool.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_showexceptions.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_showstatus.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_static.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_thin.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_urlmap.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_utils.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rack_webrick.rb create mode 100644 vendor/gems/rack-1.1.0/test/spec_rackup.rb delete mode 100644 vendor/gems/sinatra-0.10.1/Rakefile rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/AUTHORS (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/CHANGES (98%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/LICENSE (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/README.jp.rdoc (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/README.rdoc (100%) create mode 100644 vendor/gems/sinatra-1.0.a/Rakefile rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/base.rb (95%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/images/404.png (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/images/500.png (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/main.rb (96%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/showexceptions.rb (95%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/lib/sinatra/tilt.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/sinatra.gemspec (89%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/base_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/builder_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/contest.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/erb_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/erubis_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/extensions_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/filter_test.rb (84%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/haml_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/helper.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/helpers_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/mapped_error_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/middleware_test.rb (100%) create mode 100644 vendor/gems/sinatra-1.0.a/test/public/favicon.ico rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/request_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/response_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/result_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/route_added_hook_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/routing_test.rb (98%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/sass_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/server_test.rb (100%) create mode 100644 vendor/gems/sinatra-1.0.a/test/settings_test.rb rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/sinatra_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/static_test.rb (90%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/templates_test.rb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/error.builder (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/error.erb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/error.erubis (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/error.haml (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/error.sass (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/foo/hello.test (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.builder (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.erb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.erubis (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.haml (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.sass (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/hello.test (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/layout2.builder (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/layout2.erb (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/layout2.erubis (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/layout2.haml (100%) rename vendor/gems/{sinatra-0.10.1 => sinatra-1.0.a}/test/views/layout2.test (100%) create mode 100644 vendor/specifications/rack-1.1.0.gemspec create mode 100644 vendor/specifications/sinatra-1.0.a.gemspec diff --git a/lib/sinatra/bundles.rb b/lib/sinatra/bundles.rb index 97db598..ffe30cc 100644 --- a/lib/sinatra/bundles.rb +++ b/lib/sinatra/bundles.rb @@ -1,120 +1,10 @@ -require 'rainpress' -require 'packr' - module Sinatra # Main Bundles Module module Bundles - # The base class for a bundle of files. - # The developer user sinatra-bundles should - # never have to deal with this directly - class Bundle - def initialize(app, files) - @app = app - @files = files - end - - # Since we pass Bundles back as the body, - # this follows Rack standards and supports an each method - # to yield parts of the body, in our case, the files. - def each - @files.each do |f| - content = File.read(path(f)) - content = compress(content) if @app.compress_bundles - # Include a new line to prevent weirdness at file boundaries - yield("#{content}\n") - end - end - - private - - # The timestamp of the bundle, which is the newest file in the bundle. - # - # @return [Integer] The timestamp of the bundle - def stamp - @files.map do |f| - File.mtime(path(f)) - end.sort.first.to_i - end - end - - # Bundle for stylesheets - class StylesheetBundle < Bundle - # Generate the HTML tag for the stylesheet - # - # @param [String] name The name of a bundle - # @return [String] The HTML that can be inserted into the doc - def to_html(name) - "" - end - - protected - - # Compress CSS - # - # @param [String] css The CSS to compress - # @return [String] Compressed CSS - def compress(css) - Rainpress.compress(css) - end - - # Get the path of the file on disk - # - # @param [String] filename The name of sheet, - # assumed to be in the public directory, under 'stylesheets' - # @return [String] The full path to the file - def path(filename) - File.join(@app.public, 'stylesheets', "#{filename}.css") - end - end - - # Bundle for javascripts - class JavascriptBundle < Bundle - # Generate the HTML tag for the script file - # - # @param [String] name The name of a bundle - # @return [String] The HTML that can be inserted into the doc - def to_html(name) - "" - end - - protected - - # Compress Javascript - # - # @param [String] js The Javascript to compress - # @return [String] Compressed Javascript - def compress(js) - Packr.pack(js, :shrink_vars => true) - end - - # Get the path of the file on disk - # - # @param [String] filename The name of sheet, - # assumed to be in the public directory, under 'javascripts' - # @return [String] The full path to the file - def path(filename) - File.join(@app.public, 'javascripts', "#{filename}.js") - end - end - - # View helpers - module Helpers - # Emit a script tag for a javascript bundle - # - # @param [Symbol,String] bundle The bundle name - # @return [String] HTML script tag - def javascript_bundle_include_tag(bundle) - options.javascript_bundles[bundle].to_html(bundle) - end - - # Emit a script tag for a stylesheet bundle - # - # @param [Symbol,String] bundle The bundle name - # @return [String] HTML link tag - def stylesheet_bundle_link_tag(bundle) - options.stylesheet_bundles[bundle].to_html(bundle) - end - end + autoload :Helpers, 'sinatra/bundles/helpers' + autoload :Bundle, 'sinatra/bundles/bundle' + autoload :JavascriptBundle, 'sinatra/bundles/javascript_bundle' + autoload :StylesheetBundle, 'sinatra/bundles/stylesheet_bundle' # Set a Javascript bundle # javascript_bundle(:all, %w(jquery lightbox)) diff --git a/lib/sinatra/bundles/bundle.rb b/lib/sinatra/bundles/bundle.rb new file mode 100644 index 0000000..d77035d --- /dev/null +++ b/lib/sinatra/bundles/bundle.rb @@ -0,0 +1,36 @@ +module Sinatra + module Bundles + # The base class for a bundle of files. + # The developer user sinatra-bundles should + # never have to deal with this directly + class Bundle + def initialize(app, files) + @app = app + @files = files + end + + # Since we pass Bundles back as the body, + # this follows Rack standards and supports an each method + # to yield parts of the body, in our case, the files. + def each + @files.each do |f| + content = File.read(path(f)) + content = compress(content) if @app.compress_bundles + # Include a new line to prevent weirdness at file boundaries + yield("#{content}\n") + end + end + + private + + # The timestamp of the bundle, which is the newest file in the bundle. + # + # @return [Integer] The timestamp of the bundle + def stamp + @files.map do |f| + File.mtime(path(f)) + end.sort.first.to_i + end + end + end +end \ No newline at end of file diff --git a/lib/sinatra/bundles/helpers.rb b/lib/sinatra/bundles/helpers.rb new file mode 100644 index 0000000..7fcd344 --- /dev/null +++ b/lib/sinatra/bundles/helpers.rb @@ -0,0 +1,22 @@ +module Sinatra + module Bundles + # View helpers + module Helpers + # Emit a script tag for a javascript bundle + # + # @param [Symbol,String] bundle The bundle name + # @return [String] HTML script tag + def javascript_bundle_include_tag(bundle) + options.javascript_bundles[bundle].to_html(bundle) + end + + # Emit a script tag for a stylesheet bundle + # + # @param [Symbol,String] bundle The bundle name + # @return [String] HTML link tag + def stylesheet_bundle_link_tag(bundle) + options.stylesheet_bundles[bundle].to_html(bundle) + end + end + end +end \ No newline at end of file diff --git a/lib/sinatra/bundles/javascript_bundle.rb b/lib/sinatra/bundles/javascript_bundle.rb new file mode 100644 index 0000000..727f3d8 --- /dev/null +++ b/lib/sinatra/bundles/javascript_bundle.rb @@ -0,0 +1,37 @@ +require 'sinatra/bundles/bundle' +require 'packr' + +module Sinatra + module Bundles + # Bundle for javascripts + class JavascriptBundle < Bundle + # Generate the HTML tag for the script file + # + # @param [String] name The name of a bundle + # @return [String] The HTML that can be inserted into the doc + def to_html(name) + "" + end + + protected + + # Compress Javascript + # + # @param [String] js The Javascript to compress + # @return [String] Compressed Javascript + def compress(js) + # Don't shrink variables if the file includes a call to `eval` + Packr.pack(js, :shrink_vars => !js.include?('eval(')) + end + + # Get the path of the file on disk + # + # @param [String] filename The name of sheet, + # assumed to be in the public directory, under 'javascripts' + # @return [String] The full path to the file + def path(filename) + File.join(@app.public, 'javascripts', "#{filename}.js") + end + end + end +end \ No newline at end of file diff --git a/lib/sinatra/bundles/stylesheet_bundle.rb b/lib/sinatra/bundles/stylesheet_bundle.rb new file mode 100644 index 0000000..62b805d --- /dev/null +++ b/lib/sinatra/bundles/stylesheet_bundle.rb @@ -0,0 +1,36 @@ +require 'sinatra/bundles/bundle' +require 'rainpress' + +module Sinatra + module Bundles + # Bundle for stylesheets + class StylesheetBundle < Bundle + # Generate the HTML tag for the stylesheet + # + # @param [String] name The name of a bundle + # @return [String] The HTML that can be inserted into the doc + def to_html(name) + "" + end + + protected + + # Compress CSS + # + # @param [String] css The CSS to compress + # @return [String] Compressed CSS + def compress(css) + Rainpress.compress(css) + end + + # Get the path of the file on disk + # + # @param [String] filename The name of sheet, + # assumed to be in the public directory, under 'stylesheets' + # @return [String] The full path to the file + def path(filename) + File.join(@app.public, 'stylesheets', "#{filename}.css") + end + end + end +end \ No newline at end of file diff --git a/spec/app.rb b/spec/app.rb index 3f68f78..ef7c2c6 100644 --- a/spec/app.rb +++ b/spec/app.rb @@ -7,5 +7,5 @@ class TestApp < Sinatra::Base register Sinatra::Bundles stylesheet_bundle(:test, %w(test1 test2)) - javascript_bundle(:test, %w(test1 test2)) + javascript_bundle(:test, %w(test1 test2 eval)) end \ No newline at end of file diff --git a/spec/production_app.rb b/spec/production_app.rb index e08278d..d8eaffd 100644 --- a/spec/production_app.rb +++ b/spec/production_app.rb @@ -1,4 +1,4 @@ -gem 'sinatra', '>= 0.10.1' +gem 'sinatra', '>= 1.0.a' require 'sinatra/base' require 'sinatra/bundles' diff --git a/spec/public/javascripts/eval.js b/spec/public/javascripts/eval.js new file mode 100644 index 0000000..29d9ff3 --- /dev/null +++ b/spec/public/javascripts/eval.js @@ -0,0 +1,5 @@ +function something(name) { + eval('alert(name);'); +} + +something('bob'); \ No newline at end of file diff --git a/spec/sinatra-bundles_spec.rb b/spec/sinatra-bundles_spec.rb index cef06cd..5d4a295 100644 --- a/spec/sinatra-bundles_spec.rb +++ b/spec/sinatra-bundles_spec.rb @@ -29,7 +29,7 @@ def css_stamp(names) end before do - @scripts = %w(test1 test2).map do |name| + @scripts = %w(test1 test2 eval).map do |name| File.expand_path(File.join(File.dirname(__FILE__), 'public', 'javascripts', "#{name}.js")) end @@ -111,6 +111,15 @@ def css_stamp(names) last_response.headers['Vary'].should == 'Accept-Encoding' last_response.headers['Cache-Control'].should == 'public, must-revalidate, max-age=31536000' end + + it 'should not shrink vars on javascript files that use eval' do + app.enable(:compress_bundles) + get '/javascripts/bundles/test.js' + last_response.should be_ok + js = File.read(File.join(File.dirname(__FILE__), 'public/javascripts/eval.js')) + last_response.body.include?(Packr.pack(js)).should be_true + last_response.body.include?(Packr.pack(js, :shrink_vars => true)).should be_false + end end context 'stylesheet bundles' do diff --git a/vendor/bin/rackup b/vendor/bin/rackup new file mode 100755 index 0000000..d36c6ee --- /dev/null +++ b/vendor/bin/rackup @@ -0,0 +1,19 @@ +#!/Users/darkhelmet/local/ree/bin/ruby +# +# This file was generated by RubyGems. +# +# The application 'rack' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0" + +if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then + version = $1 + ARGV.shift +end + +gem 'rack', version +load Gem.bin_path('rack', 'rackup', version) diff --git a/vendor/cache/rack-1.1.0.gem b/vendor/cache/rack-1.1.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..4a73511e827d6010f64edc7ea7229e27c55e974d GIT binary patch literal 104448 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%0AL&aAwrzt^4z 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�!eB6CwAH$?ngA0R*I&Z^B`T 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 literal 0 HcmV?d00001 diff --git a/vendor/cache/sinatra-1.0.a.gem b/vendor/cache/sinatra-1.0.a.gem new file mode 100644 index 0000000000000000000000000000000000000000..64c026ae9aaee25ceafbf2d6ac15f2150b8c86a5 GIT binary patch literal 117760 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

pMfSOE@p+Fr9I+c|22BOtCdsEhz=7MzwZ4O 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 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/gems/rack-1.1.0/KNOWN-ISSUES b/vendor/gems/rack-1.1.0/KNOWN-ISSUES new file mode 100644 index 0000000..a1af5dc --- /dev/null +++ b/vendor/gems/rack-1.1.0/KNOWN-ISSUES @@ -0,0 +1,21 @@ += Known issues with Rack and Web servers + +* Lighttpd sets wrong SCRIPT_NAME and PATH_INFO if you mount your + FastCGI app at "/". This can be fixed by using this middleware: + + class LighttpdScriptNameFix + def initialize(app) + @app = app + end + + def call(env) + env["PATH_INFO"] = env["SCRIPT_NAME"].to_s + env["PATH_INFO"].to_s + env["SCRIPT_NAME"] = "" + @app.call(env) + end + end + + Of course, use this only when your app runs at "/". + + Since lighttpd 1.4.23, you also can use the "fix-root-scriptname" flag + in fastcgi.server. diff --git a/vendor/gems/rack-1.1.0/RDOX b/vendor/gems/rack-1.1.0/RDOX new file mode 100644 index 0000000..e69de29 diff --git a/vendor/gems/rack-1.1.0/README b/vendor/gems/rack-1.1.0/README new file mode 100644 index 0000000..777b12d --- /dev/null +++ b/vendor/gems/rack-1.1.0/README @@ -0,0 +1,399 @@ += Rack, a modular Ruby webserver interface + +Rack provides a minimal, modular and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +The exact details of this are described in the Rack specification, +which all Rack applications should conform to. + +== Specification changes in this release + +With Rack 1.1, the Rack specification (found in SPEC) changed in the +following backward-incompatible ways. + +* Rack::VERSION has been pushed to [1,1]. +* rack.logger is now specified. +* The SPEC now allows subclasses of the required types. +* rack.input has to be opened in binary mode. + +== Supported web servers + +The included *handlers* connect all kinds of web servers to Rack: +* Mongrel +* EventedMongrel +* SwiftipliedMongrel +* WEBrick +* FCGI +* CGI +* SCGI +* LiteSpeed +* Thin + +These web servers include Rack handlers in their distributions: +* Ebb +* Fuzed +* Glassfish v3 +* Phusion Passenger (which is mod_rack for Apache and for nginx) +* Rainbows! +* Unicorn +* Zbatery + +Any valid Rack app will run the same on all these handlers, without +changing anything. + +== Supported web frameworks + +The included *adapters* connect Rack with existing Ruby web frameworks: +* Camping + +These frameworks include Rack adapters in their distributions: +* Camping +* Coset +* Halcyon +* Mack +* Maveric +* Merb +* Racktools::SimpleApplication +* Ramaze +* Ruby on Rails +* Rum +* Sinatra +* Sin +* Vintage +* Waves +* Wee +* ... and many others. + +Current links to these projects can be found at +http://wiki.ramaze.net/Home#other-frameworks + +== Available middleware + +Between the server and the framework, Rack can be customized to your +applications needs using middleware, for example: +* Rack::URLMap, to route to multiple applications inside the same process. +* Rack::CommonLogger, for creating Apache-style logfiles. +* Rack::ShowException, for catching unhandled exceptions and + presenting them in a nice and helpful way with clickable backtrace. +* Rack::File, for serving static files. +* ...many others! + +All these components use the same interface, which is described in +detail in the Rack specification. These optional components can be +used in any way you wish. + +== Convenience + +If you want to develop outside of existing frameworks, implement your +own ones, or develop middleware, Rack provides many helpers to create +Rack applications quickly and without doing the same web stuff all +over: +* Rack::Request, which also provides query string parsing and + multipart handling. +* Rack::Response, for convenient generation of HTTP replies and + cookie handling. +* Rack::MockRequest and Rack::MockResponse for efficient and quick + testing of Rack application without real HTTP round-trips. + +== rack-contrib + +The plethora of useful middleware created the need for a project that +collects fresh Rack middleware. rack-contrib includes a variety of +add-on components for Rack and it is easy to contribute new modules. + +* http://github.com/rack/rack-contrib + +== rackup + +rackup is a useful tool for running Rack applications, which uses the +Rack::Builder DSL to configure middleware and build up applications +easily. + +rackup automatically figures out the environment it is run in, and +runs your application as FastCGI, CGI, or standalone with Mongrel or +WEBrick---all from the same configuration. + +== Quick start + +Try the lobster! + +Either with the embedded WEBrick starter: + + ruby -Ilib lib/rack/lobster.rb + +Or with rackup: + + bin/rackup -Ilib example/lobster.ru + +By default, the lobster is found at http://localhost:9292. + +== Installing with RubyGems + +A Gem of Rack is available at gemcutter.org. You can install it with: + + gem install rack + +I also provide a local mirror of the gems (and development snapshots) +at my site: + + gem install rack --source http://chneukirchen.org/releases/gems/ + +== Running the tests + +Testing Rack requires the test/spec testing framework: + + gem install test-spec + +There are two rake-based test tasks: + + rake test tests all the fast tests (no Handlers or Adapters) + rake fulltest runs all the tests + +The fast testsuite has no dependencies outside of the core Ruby +installation and test-spec. + +To run the test suite completely, you need: + + * camping + * fcgi + * memcache-client + * mongrel + * thin + +The full set of tests test FCGI access with lighttpd (on port +9203) so you will need lighttpd installed as well as the FCGI +libraries and the fcgi gem: + +Download and install lighttpd: + + http://www.lighttpd.net/download + +Installing the FCGI libraries: + + curl -O http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz + tar xzvf fcgi-2.4.0.tar.gz + cd fcgi-2.4.0 + ./configure --prefix=/usr/local + make + sudo make install + cd .. + +Installing the Ruby fcgi gem: + + gem install fcgi + +Furthermore, to test Memcache sessions, you need memcached (will be +run on port 11211) and memcache-client installed. + +== History + +* March 3rd, 2007: First public release 0.1. + +* May 16th, 2007: Second public release 0.2. + * HTTP Basic authentication. + * Cookie Sessions. + * Static file handler. + * Improved Rack::Request. + * Improved Rack::Response. + * Added Rack::ShowStatus, for better default error messages. + * Bug fixes in the Camping adapter. + * Removed Rails adapter, was too alpha. + +* February 26th, 2008: Third public release 0.3. + * LiteSpeed handler, by Adrian Madrid. + * SCGI handler, by Jeremy Evans. + * Pool sessions, by blink. + * OpenID authentication, by blink. + * :Port and :File options for opening FastCGI sockets, by blink. + * Last-Modified HTTP header for Rack::File, by blink. + * Rack::Builder#use now accepts blocks, by Corey Jewett. + (See example/protectedlobster.ru) + * HTTP status 201 can contain a Content-Type and a body now. + * Many bugfixes, especially related to Cookie handling. + +* August 21st, 2008: Fourth public release 0.4. + * New middleware, Rack::Deflater, by Christoffer Sawicki. + * OpenID authentication now needs ruby-openid 2. + * New Memcache sessions, by blink. + * Explicit EventedMongrel handler, by Joshua Peek + * Rack::Reloader is not loaded in rackup development mode. + * rackup can daemonize with -D. + * Many bugfixes, especially for pool sessions, URLMap, thread safety + and tempfile handling. + * Improved tests. + * Rack moved to Git. + +* January 6th, 2009: Fifth public release 0.9. + * Rack is now managed by the Rack Core Team. + * Rack::Lint is stricter and follows the HTTP RFCs more closely. + * Added ConditionalGet middleware. + * Added ContentLength middleware. + * Added Deflater middleware. + * Added Head middleware. + * Added MethodOverride middleware. + * Rack::Mime now provides popular MIME-types and their extension. + * Mongrel Header now streams. + * Added Thin handler. + * Official support for swiftiplied Mongrel. + * Secure cookies. + * Made HeaderHash case-preserving. + * Many bugfixes and small improvements. + +* January 9th, 2009: Sixth public release 0.9.1. + * Fix directory traversal exploits in Rack::File and Rack::Directory. + +* April 25th, 2009: Seventh public release 1.0.0. + * SPEC change: Rack::VERSION has been pushed to [1,0]. + * SPEC change: header values must be Strings now, split on "\n". + * SPEC change: Content-Length can be missing, in this case chunked transfer + encoding is used. + * SPEC change: rack.input must be rewindable and support reading into + a buffer, wrap with Rack::RewindableInput if it isn't. + * SPEC change: rack.session is now specified. + * SPEC change: Bodies can now additionally respond to #to_path with + a filename to be served. + * NOTE: String bodies break in 1.9, use an Array consisting of a + single String instead. + * New middleware Rack::Lock. + * New middleware Rack::ContentType. + * Rack::Reloader has been rewritten. + * Major update to Rack::Auth::OpenID. + * Support for nested parameter parsing in Rack::Response. + * Support for redirects in Rack::Response. + * HttpOnly cookie support in Rack::Response. + * The Rakefile has been rewritten. + * Many bugfixes and small improvements. + +* October 18th, 2009: Eighth public release 1.0.1. + * Bump remainder of rack.versions. + * Support the pure Ruby FCGI implementation. + * Fix for form names containing "=": split first then unescape components + * Fixes the handling of the filename parameter with semicolons in names. + * Add anchor to nested params parsing regexp to prevent stack overflows + * Use more compatible gzip write api instead of "<<". + * Make sure that Reloader doesn't break when executed via ruby -e + * Make sure WEBrick respects the :Host option + * Many Ruby 1.9 fixes. + +* January 3rd, 2009: Ninth public release 1.1.0. + * Moved Auth::OpenID to rack-contrib. + * SPEC change that relaxes Lint slightly to allow subclasses of the + required types + * SPEC change to document rack.input binary mode in greator detail + * SPEC define optional rack.logger specification + * File servers support X-Cascade header + * Imported Config middleware + * Imported ETag middleware + * Imported Runtime middleware + * Imported Sendfile middleware + * New Logger and NullLogger middlewares + * Added mime type for .ogv and .manifest. + * Don't squeeze PATH_INFO slashes + * Use Content-Type to determine POST params parsing + * Update Rack::Utils::HTTP_STATUS_CODES hash + * Add status code lookup utility + * Response should call #to_i on the status + * Add Request#user_agent + * Request#host knows about forwared host + * Return an empty string for Request#host if HTTP_HOST and + SERVER_NAME are both missing + * Allow MockRequest to accept hash params + * Optimizations to HeaderHash + * Refactored rackup into Rack::Server + * Added Utils.build_nested_query to complement Utils.parse_nested_query + * Added Utils::Multipart.build_multipart to complement + Utils::Multipart.parse_multipart + * Extracted set and delete cookie helpers into Utils so they can be + used outside Response + * Extract parse_query and parse_multipart in Request so subclasses + can change their behavior + * Enforce binary encoding in RewindableInput + * Set correct external_encoding for handlers that don't use RewindableInput + +== Contact + +Please post bugs, suggestions and patches to +the bug tracker at . + +Mailing list archives are available at +. + +Git repository (send Git patches to the mailing list): +* http://github.com/rack/rack +* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git + +You are also welcome to join the #rack channel on irc.freenode.net. + +== Thanks + +The Rack Core Team, consisting of + +* Christian Neukirchen (chneukirchen) +* James Tucker (raggi) +* Josh Peek (josh) +* Michael Fellinger (manveru) +* Ryan Tomayko (rtomayko) +* Scytrin dai Kinthra (scytrin) + +would like to thank: + +* Adrian Madrid, for the LiteSpeed handler. +* Christoffer Sawicki, for the first Rails adapter and Rack::Deflater. +* Tim Fletcher, for the HTTP authentication code. +* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes. +* Armin Ronacher, for the logo and racktools. +* Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, + Tom Robinson, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic, + Daniel Rodríguez Troitiño, Genki Takiuchi, Geoffrey Grosenbach, + Julien Sanchez, Kamal Fariz Mahyuddin, Masayoshi Takahashi, Patrick + Aljordm, Mig, and Kazuhiro Nishiyama for bug fixing and other + improvements. +* Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support + and API improvements. +* Yehuda Katz and Carl Lerche for refactoring rackup. +* Brian Candler, for Rack::ContentType. +* Graham Batty, for improved handler loading. +* Stephen Bannasch, for bug reports and documentation. +* Gary Wright, for proposing a better Rack::Response interface. +* Jonathan Buch, for improvements regarding Rack::Response. +* Armin Röhrl, for tracking down bugs in the Cookie generator. +* Alexander Kellett for testing the Gem and reviewing the announcement. +* Marcus Rückert, for help with configuring and debugging lighttpd. +* The WSGI team for the well-done and documented work they've done and + Rack builds up on. +* All bug reporters and patch contributers not mentioned above. + +== Copyright + +Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +== Links + +Rack:: +Rack's Rubyforge project:: +Official Rack repositories:: +Rack Lighthouse Bug Tracking:: +rack-devel mailing list:: + +Christian Neukirchen:: + diff --git a/vendor/gems/rack-1.1.0/SPEC b/vendor/gems/rack-1.1.0/SPEC new file mode 100644 index 0000000..d2260cb --- /dev/null +++ b/vendor/gems/rack-1.1.0/SPEC @@ -0,0 +1,171 @@ +This specification aims to formalize the Rack protocol. You +can (and should) use Rack::Lint to enforce it. +When you develop middleware, be sure to add a Lint before and +after to catch all mistakes. += Rack applications +A Rack application is an Ruby object (not a class) that +responds to +call+. +It takes exactly one argument, the *environment* +and returns an Array of exactly three values: +The *status*, +the *headers*, +and the *body*. +== The Environment +The environment must be an true instance of Hash (no +subclassing allowed) that includes CGI-like headers. +The application is free to modify the environment. +The environment is required to include these variables +(adopted from PEP333), except when they'd be empty, but see +below. +REQUEST_METHOD:: The HTTP request method, such as + "GET" or "POST". This cannot ever + be an empty string, and so is + always required. +SCRIPT_NAME:: The initial portion of the request + URL's "path" that corresponds to the + application object, so that the + application knows its virtual + "location". This may be an empty + string, if the application corresponds + to the "root" of the server. +PATH_INFO:: The remainder of the request URL's + "path", designating the virtual + "location" of the request's target + within the application. This may be an + empty string, if the request URL targets + the application root and does not have a + trailing slash. This value may be + percent-encoded when I originating from + a URL. +QUERY_STRING:: The portion of the request URL that + follows the ?, if any. May be + empty, but is always required! +SERVER_NAME, SERVER_PORT:: When combined with SCRIPT_NAME and PATH_INFO, these variables can be used to complete the URL. Note, however, that HTTP_HOST, if present, should be used in preference to SERVER_NAME for reconstructing the request URL. SERVER_NAME and SERVER_PORT can never be empty strings, and so are always required. +HTTP_ Variables:: Variables corresponding to the + client-supplied HTTP request + headers (i.e., variables whose + names begin with HTTP_). The + presence or absence of these + variables should correspond with + the presence or absence of the + appropriate HTTP header in the + request. +In addition to this, the Rack environment must include these +Rack-specific variables: +rack.version:: The Array [1,1], representing this version of Rack. +rack.url_scheme:: +http+ or +https+, depending on the request URL. +rack.input:: See below, the input stream. +rack.errors:: See below, the error stream. +rack.multithread:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise. +rack.multiprocess:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise. +rack.run_once:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar). +Additional environment specifications have approved to +standardized middleware APIs. None of these are required to +be implemented by the server. +rack.session:: A hash like interface for storing request session data. + The store must implement: + store(key, value) (aliased as []=); + fetch(key, default = nil) (aliased as []); + delete(key); + clear; +rack.logger:: A common object interface for logging messages. + The object must implement: + info(message, &block) + debug(message, &block) + warn(message, &block) + error(message, &block) + fatal(message, &block) +The server or the application can store their own data in the +environment, too. The keys must contain at least one dot, +and should be prefixed uniquely. The prefix rack. +is reserved for use with the Rack core distribution and other +accepted specifications and must not be used otherwise. +The environment must not contain the keys +HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH +(use the versions without HTTP_). +The CGI keys (named without a period) must have String values. +There are the following restrictions: +* rack.version must be an array of Integers. +* rack.url_scheme must either be +http+ or +https+. +* There must be a valid input stream in rack.input. +* There must be a valid error stream in rack.errors. +* The REQUEST_METHOD must be a valid token. +* The SCRIPT_NAME, if non-empty, must start with / +* The PATH_INFO, if non-empty, must start with / +* The CONTENT_LENGTH, if given, must consist of digits only. +* One of SCRIPT_NAME or PATH_INFO must be + set. PATH_INFO should be / if + SCRIPT_NAME is empty. + SCRIPT_NAME never should be /, but instead be empty. +=== The Input Stream +The input stream is an IO-like object which contains the raw HTTP +POST data. +When applicable, its external encoding must be "ASCII-8BIT" and it +must be opened in binary mode, for Ruby 1.9 compatibility. +The input stream must respond to +gets+, +each+, +read+ and +rewind+. +* +gets+ must be called without arguments and return a string, + or +nil+ on EOF. +* +read+ behaves like IO#read. Its signature is read([length, [buffer]]). + If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must + be a String and may not be nil. If +length+ is given and not nil, then this method + reads at most +length+ bytes from the input stream. If +length+ is not given or nil, + then this method reads all data until EOF. + When EOF is reached, this method returns nil if +length+ is given and not nil, or "" + if +length+ is not given or is nil. + If +buffer+ is given, then the read data will be placed into +buffer+ instead of a + newly created String object. +* +each+ must be called without arguments and only yield Strings. +* +rewind+ must be called without arguments. It rewinds the input + stream back to the beginning. It must not raise Errno::ESPIPE: + that is, it may not be a pipe or a socket. Therefore, handler + developers must buffer the input data into some rewindable object + if the underlying input stream is not rewindable. +* +close+ must never be called on the input stream. +=== The Error Stream +The error stream must respond to +puts+, +write+ and +flush+. +* +puts+ must be called with a single argument that responds to +to_s+. +* +write+ must be called with a single argument that is a String. +* +flush+ must be called without arguments and must be called + in order to make the error appear for sure. +* +close+ must never be called on the error stream. +== The Response +=== The Status +This is an HTTP status. When parsed as integer (+to_i+), it must be +greater than or equal to 100. +=== The Headers +The header must respond to +each+, and yield values of key and value. +The header keys must be Strings. +The header must not contain a +Status+ key, +contain keys with : or newlines in their name, +contain keys names that end in - or _, +but only contain keys that consist of +letters, digits, _ or - and start with a letter. +The values of the header must be Strings, +consisting of lines (for multiple header values, e.g. multiple +Set-Cookie values) seperated by "\n". +The lines must not contain characters below 037. +=== The Content-Type +There must be a Content-Type, except when the ++Status+ is 1xx, 204 or 304, in which case there must be none +given. +=== The Content-Length +There must not be a Content-Length header when the ++Status+ is 1xx, 204 or 304. +=== The Body +The Body must respond to +each+ +and must only yield String values. +The Body itself should not be an instance of String, as this will +break in Ruby 1.9. +If the Body responds to +close+, it will be called after iteration. +If the Body responds to +to_path+, it must return a String +identifying the location of a file whose contents are identical +to that produced by calling +each+; this may be used by the +server as an alternative, possibly more efficient way to +transport the response. +The Body commonly is an Array of Strings, the application +instance itself, or a File-like object. +== Thanks +Some parts of this specification are adopted from PEP333: Python +Web Server Gateway Interface +v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank +everyone involved in that effort. diff --git a/vendor/gems/rack-1.1.0/bin/rackup b/vendor/gems/rack-1.1.0/bin/rackup new file mode 100755 index 0000000..ad94af4 --- /dev/null +++ b/vendor/gems/rack-1.1.0/bin/rackup @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require "rack" +Rack::Server.start diff --git a/vendor/gems/rack-1.1.0/contrib/rack_logo.svg b/vendor/gems/rack-1.1.0/contrib/rack_logo.svg new file mode 100644 index 0000000..905dcd3 --- /dev/null +++ b/vendor/gems/rack-1.1.0/contrib/rack_logo.svg @@ -0,0 +1,111 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/vendor/gems/rack-1.1.0/example/lobster.ru b/vendor/gems/rack-1.1.0/example/lobster.ru new file mode 100644 index 0000000..cc7ffca --- /dev/null +++ b/vendor/gems/rack-1.1.0/example/lobster.ru @@ -0,0 +1,4 @@ +require 'rack/lobster' + +use Rack::ShowExceptions +run Rack::Lobster.new diff --git a/vendor/gems/rack-1.1.0/example/protectedlobster.rb b/vendor/gems/rack-1.1.0/example/protectedlobster.rb new file mode 100644 index 0000000..108b9d0 --- /dev/null +++ b/vendor/gems/rack-1.1.0/example/protectedlobster.rb @@ -0,0 +1,14 @@ +require 'rack' +require 'rack/lobster' + +lobster = Rack::Lobster.new + +protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password| + 'secret' == password +end + +protected_lobster.realm = 'Lobster 2.0' + +pretty_protected_lobster = Rack::ShowStatus.new(Rack::ShowExceptions.new(protected_lobster)) + +Rack::Handler::WEBrick.run pretty_protected_lobster, :Port => 9292 diff --git a/vendor/gems/rack-1.1.0/example/protectedlobster.ru b/vendor/gems/rack-1.1.0/example/protectedlobster.ru new file mode 100644 index 0000000..b0da62f --- /dev/null +++ b/vendor/gems/rack-1.1.0/example/protectedlobster.ru @@ -0,0 +1,8 @@ +require 'rack/lobster' + +use Rack::ShowExceptions +use Rack::Auth::Basic, "Lobster 2.0" do |username, password| + 'secret' == password +end + +run Rack::Lobster.new diff --git a/vendor/gems/rack-1.1.0/lib/rack.rb b/vendor/gems/rack-1.1.0/lib/rack.rb new file mode 100644 index 0000000..c118fc0 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack.rb @@ -0,0 +1,92 @@ +# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen +# +# Rack is freely distributable under the terms of an MIT-style license. +# See COPYING or http://www.opensource.org/licenses/mit-license.php. + +# The Rack main module, serving as a namespace for all core Rack +# modules and classes. +# +# All modules meant for use in your application are autoloaded here, +# so it should be enough just to require rack.rb in your code. + +module Rack + # The Rack protocol version number implemented. + VERSION = [1,1] + + # Return the Rack protocol version as a dotted string. + def self.version + VERSION.join(".") + end + + # Return the Rack release as a dotted string. + def self.release + "1.1" + end + + autoload :Builder, "rack/builder" + autoload :Cascade, "rack/cascade" + autoload :Chunked, "rack/chunked" + autoload :CommonLogger, "rack/commonlogger" + autoload :ConditionalGet, "rack/conditionalget" + autoload :Config, "rack/config" + autoload :ContentLength, "rack/content_length" + autoload :ContentType, "rack/content_type" + autoload :ETag, "rack/etag" + autoload :File, "rack/file" + autoload :Deflater, "rack/deflater" + autoload :Directory, "rack/directory" + autoload :ForwardRequest, "rack/recursive" + autoload :Handler, "rack/handler" + autoload :Head, "rack/head" + autoload :Lint, "rack/lint" + autoload :Lock, "rack/lock" + autoload :Logger, "rack/logger" + autoload :MethodOverride, "rack/methodoverride" + autoload :Mime, "rack/mime" + autoload :NullLogger, "rack/nulllogger" + autoload :Recursive, "rack/recursive" + autoload :Reloader, "rack/reloader" + autoload :Runtime, "rack/runtime" + autoload :Sendfile, "rack/sendfile" + autoload :Server, "rack/server" + autoload :ShowExceptions, "rack/showexceptions" + autoload :ShowStatus, "rack/showstatus" + autoload :Static, "rack/static" + autoload :URLMap, "rack/urlmap" + autoload :Utils, "rack/utils" + + autoload :MockRequest, "rack/mock" + autoload :MockResponse, "rack/mock" + + autoload :Request, "rack/request" + autoload :Response, "rack/response" + + module Auth + autoload :Basic, "rack/auth/basic" + autoload :AbstractRequest, "rack/auth/abstract/request" + autoload :AbstractHandler, "rack/auth/abstract/handler" + module Digest + autoload :MD5, "rack/auth/digest/md5" + autoload :Nonce, "rack/auth/digest/nonce" + autoload :Params, "rack/auth/digest/params" + autoload :Request, "rack/auth/digest/request" + end + end + + module Session + autoload :Cookie, "rack/session/cookie" + autoload :Pool, "rack/session/pool" + autoload :Memcache, "rack/session/memcache" + end + + # *Adapters* connect Rack with third party web frameworks. + # + # Rack includes an adapter for Camping, see README for other + # frameworks supporting Rack in their code bases. + # + # Refer to the submodules for framework-specific calling details. + + module Adapter + autoload :Camping, "rack/adapter/camping" + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/adapter/camping.rb b/vendor/gems/rack-1.1.0/lib/rack/adapter/camping.rb new file mode 100644 index 0000000..63bc787 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/adapter/camping.rb @@ -0,0 +1,22 @@ +module Rack + module Adapter + class Camping + def initialize(app) + @app = app + end + + def call(env) + env["PATH_INFO"] ||= "" + env["SCRIPT_NAME"] ||= "" + controller = @app.run(env['rack.input'], env) + h = controller.headers + h.each_pair do |k,v| + if v.kind_of? URI + h[k] = v.to_s + end + end + [controller.status, controller.headers, [controller.body.to_s]] + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/handler.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/handler.rb new file mode 100644 index 0000000..214df62 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/handler.rb @@ -0,0 +1,37 @@ +module Rack + module Auth + # Rack::Auth::AbstractHandler implements common authentication functionality. + # + # +realm+ should be set for all handlers. + + class AbstractHandler + + attr_accessor :realm + + def initialize(app, realm=nil, &authenticator) + @app, @realm, @authenticator = app, realm, authenticator + end + + + private + + def unauthorized(www_authenticate = challenge) + return [ 401, + { 'Content-Type' => 'text/plain', + 'Content-Length' => '0', + 'WWW-Authenticate' => www_authenticate.to_s }, + [] + ] + end + + def bad_request + return [ 400, + { 'Content-Type' => 'text/plain', + 'Content-Length' => '0' }, + [] + ] + end + + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/request.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/request.rb new file mode 100644 index 0000000..1d9ccec --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/abstract/request.rb @@ -0,0 +1,37 @@ +module Rack + module Auth + class AbstractRequest + + def initialize(env) + @env = env + end + + def provided? + !authorization_key.nil? + end + + def parts + @parts ||= @env[authorization_key].split(' ', 2) + end + + def scheme + @scheme ||= parts.first.downcase.to_sym + end + + def params + @params ||= parts.last + end + + + private + + AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION'] + + def authorization_key + @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) } + end + + end + + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/basic.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/basic.rb new file mode 100644 index 0000000..9557224 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/basic.rb @@ -0,0 +1,58 @@ +require 'rack/auth/abstract/handler' +require 'rack/auth/abstract/request' + +module Rack + module Auth + # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617. + # + # Initialize with the Rack application that you want protecting, + # and a block that checks if a username and password pair are valid. + # + # See also: example/protectedlobster.rb + + class Basic < AbstractHandler + + def call(env) + auth = Basic::Request.new(env) + + return unauthorized unless auth.provided? + + return bad_request unless auth.basic? + + if valid?(auth) + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + + unauthorized + end + + + private + + def challenge + 'Basic realm="%s"' % realm + end + + def valid?(auth) + @authenticator.call(*auth.credentials) + end + + class Request < Auth::AbstractRequest + def basic? + :basic == scheme + end + + def credentials + @credentials ||= params.unpack("m*").first.split(/:/, 2) + end + + def username + credentials.first + end + end + + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/digest/md5.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/md5.rb new file mode 100644 index 0000000..e579dc9 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/md5.rb @@ -0,0 +1,124 @@ +require 'rack/auth/abstract/handler' +require 'rack/auth/digest/request' +require 'rack/auth/digest/params' +require 'rack/auth/digest/nonce' +require 'digest/md5' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::MD5 implements the MD5 algorithm version of + # HTTP Digest Authentication, as per RFC 2617. + # + # Initialize with the [Rack] application that you want protecting, + # and a block that looks up a plaintext password for a given username. + # + # +opaque+ needs to be set to a constant base64/hexadecimal string. + # + class MD5 < AbstractHandler + + attr_accessor :opaque + + attr_writer :passwords_hashed + + def initialize(*args) + super + @passwords_hashed = nil + end + + def passwords_hashed? + !!@passwords_hashed + end + + def call(env) + auth = Request.new(env) + + unless auth.provided? + return unauthorized + end + + if !auth.digest? || !auth.correct_uri? || !valid_qop?(auth) + return bad_request + end + + if valid?(auth) + if auth.nonce.stale? + return unauthorized(challenge(:stale => true)) + else + env['REMOTE_USER'] = auth.username + + return @app.call(env) + end + end + + unauthorized + end + + + private + + QOP = 'auth'.freeze + + def params(hash = {}) + Params.new do |params| + params['realm'] = realm + params['nonce'] = Nonce.new.to_s + params['opaque'] = H(opaque) + params['qop'] = QOP + + hash.each { |k, v| params[k] = v } + end + end + + def challenge(hash = {}) + "Digest #{params(hash)}" + end + + def valid?(auth) + valid_opaque?(auth) && valid_nonce?(auth) && valid_digest?(auth) + end + + def valid_qop?(auth) + QOP == auth.qop + end + + def valid_opaque?(auth) + H(opaque) == auth.opaque + end + + def valid_nonce?(auth) + auth.nonce.valid? + end + + def valid_digest?(auth) + digest(auth, @authenticator.call(auth.username)) == auth.response + end + + def md5(data) + ::Digest::MD5.hexdigest(data) + end + + alias :H :md5 + + def KD(secret, data) + H([secret, data] * ':') + end + + def A1(auth, password) + [ auth.username, auth.realm, password ] * ':' + end + + def A2(auth) + [ auth.method, auth.uri ] * ':' + end + + def digest(auth, password) + password_hash = passwords_hashed? ? password : H(A1(auth, password)) + + KD(password_hash, [ auth.nonce, auth.nc, auth.cnonce, QOP, H(A2(auth)) ] * ':') + end + + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/digest/nonce.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/nonce.rb new file mode 100644 index 0000000..dbe109f --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/nonce.rb @@ -0,0 +1,51 @@ +require 'digest/md5' + +module Rack + module Auth + module Digest + # Rack::Auth::Digest::Nonce is the default nonce generator for the + # Rack::Auth::Digest::MD5 authentication handler. + # + # +private_key+ needs to set to a constant string. + # + # +time_limit+ can be optionally set to an integer (number of seconds), + # to limit the validity of the generated nonces. + + class Nonce + + class << self + attr_accessor :private_key, :time_limit + end + + def self.parse(string) + new(*string.unpack("m*").first.split(' ', 2)) + end + + def initialize(timestamp = Time.now, given_digest = nil) + @timestamp, @given_digest = timestamp.to_i, given_digest + end + + def to_s + [([ @timestamp, digest ] * ' ')].pack("m*").strip + end + + def digest + ::Digest::MD5.hexdigest([ @timestamp, self.class.private_key ] * ':') + end + + def valid? + digest == @given_digest + end + + def stale? + !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit + end + + def fresh? + !stale? + end + + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/digest/params.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/params.rb new file mode 100644 index 0000000..730e2ef --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/params.rb @@ -0,0 +1,55 @@ +module Rack + module Auth + module Digest + class Params < Hash + + def self.parse(str) + split_header_value(str).inject(new) do |header, param| + k, v = param.split('=', 2) + header[k] = dequote(v) + header + end + end + + def self.dequote(str) # From WEBrick::HTTPUtils + ret = (/\A"(.*)"\Z/ =~ str) ? $1 : str.dup + ret.gsub!(/\\(.)/, "\\1") + ret + end + + def self.split_header_value(str) + str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] } + end + + def initialize + super + + yield self if block_given? + end + + def [](k) + super k.to_s + end + + def []=(k, v) + super k.to_s, v.to_s + end + + UNQUOTED = ['qop', 'nc', 'stale'] + + def to_s + inject([]) do |parts, (k, v)| + parts << "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v)) + parts + end.join(', ') + end + + def quote(str) # From WEBrick::HTTPUtils + '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' + end + + end + end + end +end + diff --git a/vendor/gems/rack-1.1.0/lib/rack/auth/digest/request.rb b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/request.rb new file mode 100644 index 0000000..a8aa3bf --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/auth/digest/request.rb @@ -0,0 +1,40 @@ +require 'rack/auth/abstract/request' +require 'rack/auth/digest/params' +require 'rack/auth/digest/nonce' + +module Rack + module Auth + module Digest + class Request < Auth::AbstractRequest + + def method + @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD'] + end + + def digest? + :digest == scheme + end + + def correct_uri? + (@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri + end + + def nonce + @nonce ||= Nonce.parse(params['nonce']) + end + + def params + @params ||= Params.parse(parts.last) + end + + def method_missing(sym) + if params.has_key? key = sym.to_s + return params[key] + end + super + end + + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/builder.rb b/vendor/gems/rack-1.1.0/lib/rack/builder.rb new file mode 100644 index 0000000..530f0aa --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/builder.rb @@ -0,0 +1,80 @@ +module Rack + # Rack::Builder implements a small DSL to iteratively construct Rack + # applications. + # + # Example: + # + # app = Rack::Builder.new { + # use Rack::CommonLogger + # use Rack::ShowExceptions + # map "/lobster" do + # use Rack::Lint + # run Rack::Lobster.new + # end + # } + # + # Or + # + # app = Rack::Builder.app do + # use Rack::CommonLogger + # lambda { |env| [200, {'Content-Type' => 'text/plain'}, 'OK'] } + # end + # + # +use+ adds a middleware to the stack, +run+ dispatches to an application. + # You can use +map+ to construct a Rack::URLMap in a convenient way. + + class Builder + def self.parse_file(config, opts = Server::Options.new) + options = {} + if config =~ /\.ru$/ + cfgfile = ::File.read(config) + if cfgfile[/^#\\(.*)/] && opts + options = opts.parse! $1.split(/\s+/) + end + cfgfile.sub!(/^__END__\n.*/, '') + app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", + TOPLEVEL_BINDING, config + else + require config + app = Object.const_get(::File.basename(config, '.rb').capitalize) + end + return app, options + end + + def initialize(&block) + @ins = [] + instance_eval(&block) if block_given? + end + + def self.app(&block) + self.new(&block).to_app + end + + def use(middleware, *args, &block) + @ins << lambda { |app| middleware.new(app, *args, &block) } + end + + def run(app) + @ins << app #lambda { |nothing| app } + end + + def map(path, &block) + if @ins.last.kind_of? Hash + @ins.last[path] = self.class.new(&block).to_app + else + @ins << {} + map(path, &block) + end + end + + def to_app + @ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last + inner_app = @ins.last + @ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) } + end + + def call(env) + to_app.call(env) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/cascade.rb b/vendor/gems/rack-1.1.0/lib/rack/cascade.rb new file mode 100644 index 0000000..14c3e54 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/cascade.rb @@ -0,0 +1,41 @@ +module Rack + # Rack::Cascade tries an request on several apps, and returns the + # first response that is not 404 (or in a list of configurable + # status codes). + + class Cascade + NotFound = [404, {}, []] + + attr_reader :apps + + def initialize(apps, catch=404) + @apps = []; @has_app = {} + apps.each { |app| add app } + + @catch = {} + [*catch].each { |status| @catch[status] = true } + end + + def call(env) + result = NotFound + + @apps.each do |app| + result = app.call(env) + break unless @catch.include?(result[0].to_i) + end + + result + end + + def add app + @has_app[app] = true + @apps << app + end + + def include? app + @has_app.include? app + end + + alias_method :<<, :add + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/chunked.rb b/vendor/gems/rack-1.1.0/lib/rack/chunked.rb new file mode 100644 index 0000000..dddf969 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/chunked.rb @@ -0,0 +1,49 @@ +require 'rack/utils' + +module Rack + + # Middleware that applies chunked transfer encoding to response bodies + # when the response does not include a Content-Length header. + class Chunked + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if env['HTTP_VERSION'] == 'HTTP/1.0' || + STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Content-Length'] || + headers['Transfer-Encoding'] + [status, headers, body] + else + dup.chunk(status, headers, body) + end + end + + def chunk(status, headers, body) + @body = body + headers.delete('Content-Length') + headers['Transfer-Encoding'] = 'chunked' + [status, headers, self] + end + + def each + term = "\r\n" + @body.each do |chunk| + size = bytesize(chunk) + next if size == 0 + yield [size.to_s(16), term, chunk, term].join + end + yield ["0", term, "", term].join + end + + def close + @body.close if @body.respond_to?(:close) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/commonlogger.rb b/vendor/gems/rack-1.1.0/lib/rack/commonlogger.rb new file mode 100644 index 0000000..1edc9b8 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/commonlogger.rb @@ -0,0 +1,49 @@ +module Rack + # Rack::CommonLogger forwards every request to an +app+ given, and + # logs a line in the Apache common log format to the +logger+, or + # rack.errors by default. + class CommonLogger + # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common + # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 - + # %{%s - %s [%s] "%s %s%s %s" %d %s\n} % + FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n} + + def initialize(app, logger=nil) + @app = app + @logger = logger + end + + def call(env) + began_at = Time.now + status, header, body = @app.call(env) + header = Utils::HeaderHash.new(header) + log(env, status, header, began_at) + [status, header, body] + end + + private + + def log(env, status, header, began_at) + now = Time.now + length = extract_content_length(header) + + logger = @logger || env['rack.errors'] + logger.write FORMAT % [ + env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", + env["REMOTE_USER"] || "-", + now.strftime("%d/%b/%Y %H:%M:%S"), + env["REQUEST_METHOD"], + env["PATH_INFO"], + env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"], + env["HTTP_VERSION"], + status.to_s[0..3], + length, + now - began_at ] + end + + def extract_content_length(headers) + value = headers['Content-Length'] or return '-' + value.to_s == '0' ? '-' : value + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/conditionalget.rb b/vendor/gems/rack-1.1.0/lib/rack/conditionalget.rb new file mode 100644 index 0000000..046ebdb --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/conditionalget.rb @@ -0,0 +1,47 @@ +require 'rack/utils' + +module Rack + + # Middleware that enables conditional GET using If-None-Match and + # If-Modified-Since. The application should set either or both of the + # Last-Modified or Etag response headers according to RFC 2616. When + # either of the conditions is met, the response body is set to be zero + # length and the response status is set to 304 Not Modified. + # + # Applications that defer response body generation until the body's each + # message is received will avoid response body generation completely when + # a conditional GET matches. + # + # Adapted from Michael Klishin's Merb implementation: + # http://github.com/wycats/merb-core/tree/master/lib/merb-core/rack/middleware/conditional_get.rb + class ConditionalGet + def initialize(app) + @app = app + end + + def call(env) + return @app.call(env) unless %w[GET HEAD].include?(env['REQUEST_METHOD']) + + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + if etag_matches?(env, headers) || modified_since?(env, headers) + status = 304 + headers.delete('Content-Type') + headers.delete('Content-Length') + body = [] + end + [status, headers, body] + end + + private + def etag_matches?(env, headers) + etag = headers['Etag'] and etag == env['HTTP_IF_NONE_MATCH'] + end + + def modified_since?(env, headers) + last_modified = headers['Last-Modified'] and + last_modified == env['HTTP_IF_MODIFIED_SINCE'] + end + end + +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/config.rb b/vendor/gems/rack-1.1.0/lib/rack/config.rb new file mode 100644 index 0000000..c6d446c --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/config.rb @@ -0,0 +1,15 @@ +module Rack + # Rack::Config modifies the environment using the block given during + # initialization. + class Config + def initialize(app, &block) + @app = app + @block = block + end + + def call(env) + @block.call(env) + @app.call(env) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/content_length.rb b/vendor/gems/rack-1.1.0/lib/rack/content_length.rb new file mode 100644 index 0000000..1e56d43 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/content_length.rb @@ -0,0 +1,29 @@ +require 'rack/utils' + +module Rack + # Sets the Content-Length header on responses with fixed-length bodies. + class ContentLength + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if !STATUS_WITH_NO_ENTITY_BODY.include?(status) && + !headers['Content-Length'] && + !headers['Transfer-Encoding'] && + (body.respond_to?(:to_ary) || body.respond_to?(:to_str)) + + body = [body] if body.respond_to?(:to_str) # rack 0.4 compat + length = body.to_ary.inject(0) { |len, part| len + bytesize(part) } + headers['Content-Length'] = length.to_s + end + + [status, headers, body] + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/content_type.rb b/vendor/gems/rack-1.1.0/lib/rack/content_type.rb new file mode 100644 index 0000000..874c28c --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/content_type.rb @@ -0,0 +1,23 @@ +require 'rack/utils' + +module Rack + + # Sets the Content-Type header on responses which don't have one. + # + # Builder Usage: + # use Rack::ContentType, "text/plain" + # + # When no content type argument is provided, "text/html" is assumed. + class ContentType + def initialize(app, content_type = "text/html") + @app, @content_type = app, content_type + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + headers['Content-Type'] ||= @content_type + [status, headers, body] + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/deflater.rb b/vendor/gems/rack-1.1.0/lib/rack/deflater.rb new file mode 100644 index 0000000..ad0f531 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/deflater.rb @@ -0,0 +1,96 @@ +require "zlib" +require "stringio" +require "time" # for Time.httpdate +require 'rack/utils' + +module Rack + class Deflater + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + + # Skip compressing empty entity body responses and responses with + # no-transform set. + if Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Cache-Control'].to_s =~ /\bno-transform\b/ + return [status, headers, body] + end + + request = Request.new(env) + + encoding = Utils.select_best_encoding(%w(gzip deflate identity), + request.accept_encoding) + + # Set the Vary HTTP header. + vary = headers["Vary"].to_s.split(",").map { |v| v.strip } + unless vary.include?("*") || vary.include?("Accept-Encoding") + headers["Vary"] = vary.push("Accept-Encoding").join(",") + end + + case encoding + when "gzip" + headers['Content-Encoding'] = "gzip" + headers.delete('Content-Length') + mtime = headers.key?("Last-Modified") ? + Time.httpdate(headers["Last-Modified"]) : Time.now + [status, headers, GzipStream.new(body, mtime)] + when "deflate" + headers['Content-Encoding'] = "deflate" + headers.delete('Content-Length') + [status, headers, DeflateStream.new(body)] + when "identity" + [status, headers, body] + when nil + message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found." + [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]] + end + end + + class GzipStream + def initialize(body, mtime) + @body = body + @mtime = mtime + end + + def each(&block) + @writer = block + gzip =::Zlib::GzipWriter.new(self) + gzip.mtime = @mtime + @body.each { |part| gzip.write(part) } + @body.close if @body.respond_to?(:close) + gzip.close + @writer = nil + end + + def write(data) + @writer.call(data) + end + end + + class DeflateStream + DEFLATE_ARGS = [ + Zlib::DEFAULT_COMPRESSION, + # drop the zlib header which causes both Safari and IE to choke + -Zlib::MAX_WBITS, + Zlib::DEF_MEM_LEVEL, + Zlib::DEFAULT_STRATEGY + ] + + def initialize(body) + @body = body + end + + def each + deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS) + @body.each { |part| yield deflater.deflate(part) } + @body.close if @body.respond_to?(:close) + yield deflater.finish + nil + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/directory.rb b/vendor/gems/rack-1.1.0/lib/rack/directory.rb new file mode 100644 index 0000000..927ac0c --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/directory.rb @@ -0,0 +1,157 @@ +require 'time' +require 'rack/utils' +require 'rack/mime' + +module Rack + # Rack::Directory serves entries below the +root+ given, according to the + # path info of the Rack request. If a directory is found, the file's contents + # will be presented in an html based index. If a file is found, the env will + # be passed to the specified +app+. + # + # If +app+ is not specified, a Rack::File of the same +root+ will be used. + + class Directory + DIR_FILE = "%s%s%s%s" + DIR_PAGE = <<-PAGE + + %s + + + +

%s

+
+ + + + + + + +%s +
NameSizeTypeLast Modified
+
+ + PAGE + + attr_reader :files + attr_accessor :root, :path + + def initialize(root, app=nil) + @root = F.expand_path(root) + @app = app || Rack::File.new(@root) + end + + def call(env) + dup._call(env) + end + + F = ::File + + def _call(env) + @env = env + @script_name = env['SCRIPT_NAME'] + @path_info = Utils.unescape(env['PATH_INFO']) + + if forbidden = check_forbidden + forbidden + else + @path = F.join(@root, @path_info) + list_path + end + end + + def check_forbidden + return unless @path_info.include? ".." + + body = "Forbidden\n" + size = Rack::Utils.bytesize(body) + return [403, {"Content-Type" => "text/plain", + "Content-Length" => size.to_s, + "X-Cascade" => "pass"}, [body]] + end + + def list_directory + @files = [['../','Parent Directory','','','']] + glob = F.join(@path, '*') + + Dir[glob].sort.each do |node| + stat = stat(node) + next unless stat + basename = F.basename(node) + ext = F.extname(node) + + url = F.join(@script_name, @path_info, basename) + size = stat.size + type = stat.directory? ? 'directory' : Mime.mime_type(ext) + size = stat.directory? ? '-' : filesize_format(size) + mtime = stat.mtime.httpdate + url << '/' if stat.directory? + basename << '/' if stat.directory? + + @files << [ url, basename, size, type, mtime ] + end + + return [ 200, {'Content-Type'=>'text/html; charset=utf-8'}, self ] + end + + def stat(node, max = 10) + F.stat(node) + rescue Errno::ENOENT, Errno::ELOOP + return nil + end + + # TODO: add correct response if not readable, not sure if 404 is the best + # option + def list_path + @stat = F.stat(@path) + + if @stat.readable? + return @app.call(@env) if @stat.file? + return list_directory if @stat.directory? + else + raise Errno::ENOENT, 'No such file or directory' + end + + rescue Errno::ENOENT, Errno::ELOOP + return entity_not_found + end + + def entity_not_found + body = "Entity not found: #{@path_info}\n" + size = Rack::Utils.bytesize(body) + return [404, {"Content-Type" => "text/plain", + "Content-Length" => size.to_s, + "X-Cascade" => "pass"}, [body]] + end + + def each + show_path = @path.sub(/^#{@root}/,'') + files = @files.map{|f| DIR_FILE % f }*"\n" + page = DIR_PAGE % [ show_path, show_path , files ] + page.each_line{|l| yield l } + end + + # Stolen from Ramaze + + FILESIZE_FORMAT = [ + ['%.1fT', 1 << 40], + ['%.1fG', 1 << 30], + ['%.1fM', 1 << 20], + ['%.1fK', 1 << 10], + ] + + def filesize_format(int) + FILESIZE_FORMAT.each do |format, size| + return format % (int.to_f / size) if int >= size + end + + int.to_s + 'B' + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/etag.rb b/vendor/gems/rack-1.1.0/lib/rack/etag.rb new file mode 100644 index 0000000..06dbc6a --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/etag.rb @@ -0,0 +1,23 @@ +require 'digest/md5' + +module Rack + # Automatically sets the ETag header on all String bodies + class ETag + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + if !headers.has_key?('ETag') + parts = [] + body.each { |part| parts << part.to_s } + headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}") + [status, headers, parts] + else + [status, headers, body] + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/file.rb b/vendor/gems/rack-1.1.0/lib/rack/file.rb new file mode 100644 index 0000000..14af7b3 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/file.rb @@ -0,0 +1,90 @@ +require 'time' +require 'rack/utils' +require 'rack/mime' + +module Rack + # Rack::File serves files below the +root+ given, according to the + # path info of the Rack request. + # + # Handlers can detect if bodies are a Rack::File, and use mechanisms + # like sendfile on the +path+. + + class File + attr_accessor :root + attr_accessor :path + + alias :to_path :path + + def initialize(root) + @root = root + end + + def call(env) + dup._call(env) + end + + F = ::File + + def _call(env) + @path_info = Utils.unescape(env["PATH_INFO"]) + return forbidden if @path_info.include? ".." + + @path = F.join(@root, @path_info) + + begin + if F.file?(@path) && F.readable?(@path) + serving + else + raise Errno::EPERM + end + rescue SystemCallError + not_found + end + end + + def forbidden + body = "Forbidden\n" + [403, {"Content-Type" => "text/plain", + "Content-Length" => body.size.to_s, + "X-Cascade" => "pass"}, + [body]] + end + + # NOTE: + # We check via File::size? whether this file provides size info + # via stat (e.g. /proc files often don't), otherwise we have to + # figure it out by reading the whole file into memory. And while + # we're at it we also use this as body then. + + def serving + if size = F.size?(@path) + body = self + else + body = [F.read(@path)] + size = Utils.bytesize(body.first) + end + + [200, { + "Last-Modified" => F.mtime(@path).httpdate, + "Content-Type" => Mime.mime_type(F.extname(@path), 'text/plain'), + "Content-Length" => size.to_s + }, body] + end + + def not_found + body = "File not found: #{@path_info}\n" + [404, {"Content-Type" => "text/plain", + "Content-Length" => body.size.to_s, + "X-Cascade" => "pass"}, + [body]] + end + + def each + F.open(@path, "rb") { |file| + while part = file.read(8192) + yield part + end + } + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler.rb b/vendor/gems/rack-1.1.0/lib/rack/handler.rb new file mode 100644 index 0000000..3c09883 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler.rb @@ -0,0 +1,88 @@ +module Rack + # *Handlers* connect web servers with Rack. + # + # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI + # and LiteSpeed. + # + # Handlers usually are activated by calling MyHandler.run(myapp). + # A second optional hash can be passed to include server-specific + # configuration. + module Handler + def self.get(server) + return unless server + server = server.to_s + + if klass = @handlers[server] + obj = Object + klass.split("::").each { |x| obj = obj.const_get(x) } + obj + else + try_require('rack/handler', server) + const_get(server) + end + end + + def self.default(options = {}) + # Guess. + if ENV.include?("PHP_FCGI_CHILDREN") + # We already speak FastCGI + options.delete :File + options.delete :Port + + Rack::Handler::FastCGI + elsif ENV.include?("REQUEST_METHOD") + Rack::Handler::CGI + else + begin + Rack::Handler::Mongrel + rescue LoadError => e + Rack::Handler::WEBrick + end + end + end + + # Transforms server-name constants to their canonical form as filenames, + # then tries to require them but silences the LoadError if not found + # + # Naming convention: + # + # Foo # => 'foo' + # FooBar # => 'foo_bar.rb' + # FooBAR # => 'foobar.rb' + # FOObar # => 'foobar.rb' + # FOOBAR # => 'foobar.rb' + # FooBarBaz # => 'foo_bar_baz.rb' + def self.try_require(prefix, const_name) + file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }. + gsub(/[A-Z]+[^A-Z]/, '_\&').downcase + + require(::File.join(prefix, file)) + rescue LoadError + end + + def self.register(server, klass) + @handlers ||= {} + @handlers[server] = klass + end + + autoload :CGI, "rack/handler/cgi" + autoload :FastCGI, "rack/handler/fastcgi" + autoload :Mongrel, "rack/handler/mongrel" + autoload :EventedMongrel, "rack/handler/evented_mongrel" + autoload :SwiftipliedMongrel, "rack/handler/swiftiplied_mongrel" + autoload :WEBrick, "rack/handler/webrick" + autoload :LSWS, "rack/handler/lsws" + autoload :SCGI, "rack/handler/scgi" + autoload :Thin, "rack/handler/thin" + + register 'cgi', 'Rack::Handler::CGI' + register 'fastcgi', 'Rack::Handler::FastCGI' + register 'mongrel', 'Rack::Handler::Mongrel' + register 'emongrel', 'Rack::Handler::EventedMongrel' + register 'smongrel', 'Rack::Handler::SwiftipliedMongrel' + register 'webrick', 'Rack::Handler::WEBrick' + register 'lsws', 'Rack::Handler::LSWS' + register 'scgi', 'Rack::Handler::SCGI' + register 'thin', 'Rack::Handler::Thin' + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/cgi.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/cgi.rb new file mode 100644 index 0000000..c6903f1 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/cgi.rb @@ -0,0 +1,61 @@ +require 'rack/content_length' + +module Rack + module Handler + class CGI + def self.run(app, options=nil) + serve app + end + + def self.serve(app) + app = ContentLength.new(app) + + env = ENV.to_hash + env.delete "HTTP_CONTENT_LENGTH" + + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + + env.update({"rack.version" => [1,1], + "rack.input" => $stdin, + "rack.errors" => $stderr, + + "rack.multithread" => false, + "rack.multiprocess" => true, + "rack.run_once" => true, + + "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" + }) + + env["QUERY_STRING"] ||= "" + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env["REQUEST_PATH"] ||= "/" + + status, headers, body = app.call(env) + begin + send_headers status, headers + send_body body + ensure + body.close if body.respond_to? :close + end + end + + def self.send_headers(status, headers) + STDOUT.print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + STDOUT.print "#{k}: #{v}\r\n" + } + } + STDOUT.print "\r\n" + STDOUT.flush + end + + def self.send_body(body) + body.each { |part| + STDOUT.print part + STDOUT.flush + } + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/evented_mongrel.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/evented_mongrel.rb new file mode 100644 index 0000000..0f5cbf7 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/evented_mongrel.rb @@ -0,0 +1,8 @@ +require 'swiftcore/evented_mongrel' + +module Rack + module Handler + class EventedMongrel < Handler::Mongrel + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/fastcgi.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/fastcgi.rb new file mode 100644 index 0000000..b992a5f --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/fastcgi.rb @@ -0,0 +1,89 @@ +require 'fcgi' +require 'socket' +require 'rack/content_length' +require 'rack/rewindable_input' + +if defined? FCGI::Stream + class FCGI::Stream + alias _rack_read_without_buffer read + + def read(n, buffer=nil) + buf = _rack_read_without_buffer n + buffer.replace(buf.to_s) if buffer + buf + end + end +end + +module Rack + module Handler + class FastCGI + def self.run(app, options={}) + file = options[:File] and STDIN.reopen(UNIXServer.new(file)) + port = options[:Port] and STDIN.reopen(TCPServer.new(port)) + FCGI.each { |request| + serve request, app + } + end + + def self.serve(request, app) + app = Rack::ContentLength.new(app) + + env = request.env + env.delete "HTTP_CONTENT_LENGTH" + + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + + rack_input = RewindableInput.new(request.in) + + env.update({"rack.version" => [1,1], + "rack.input" => rack_input, + "rack.errors" => request.err, + + "rack.multithread" => false, + "rack.multiprocess" => true, + "rack.run_once" => false, + + "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" + }) + + env["QUERY_STRING"] ||= "" + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env["REQUEST_PATH"] ||= "/" + env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == "" + env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == "" + + begin + status, headers, body = app.call(env) + begin + send_headers request.out, status, headers + send_body request.out, body + ensure + body.close if body.respond_to? :close + end + ensure + rack_input.close + request.finish + end + end + + def self.send_headers(out, status, headers) + out.print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + out.print "#{k}: #{v}\r\n" + } + } + out.print "\r\n" + out.flush + end + + def self.send_body(out, body) + body.each { |part| + out.print part + out.flush + } + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/lsws.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/lsws.rb new file mode 100644 index 0000000..eabc0bc --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/lsws.rb @@ -0,0 +1,63 @@ +require 'lsapi' +require 'rack/content_length' +require 'rack/rewindable_input' + +module Rack + module Handler + class LSWS + def self.run(app, options=nil) + while LSAPI.accept != nil + serve app + end + end + def self.serve(app) + app = Rack::ContentLength.new(app) + + env = ENV.to_hash + env.delete "HTTP_CONTENT_LENGTH" + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + + rack_input = RewindableInput.new($stdin.read.to_s) + + env.update( + "rack.version" => [1,1], + "rack.input" => rack_input, + "rack.errors" => $stderr, + "rack.multithread" => false, + "rack.multiprocess" => true, + "rack.run_once" => false, + "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" + ) + + env["QUERY_STRING"] ||= "" + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env["REQUEST_PATH"] ||= "/" + status, headers, body = app.call(env) + begin + send_headers status, headers + send_body body + ensure + body.close if body.respond_to? :close + end + ensure + rack_input.close + end + def self.send_headers(status, headers) + print "Status: #{status}\r\n" + headers.each { |k, vs| + vs.split("\n").each { |v| + print "#{k}: #{v}\r\n" + } + } + print "\r\n" + STDOUT.flush + end + def self.send_body(body) + body.each { |part| + print part + STDOUT.flush + } + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/mongrel.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/mongrel.rb new file mode 100644 index 0000000..b6b775e --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/mongrel.rb @@ -0,0 +1,90 @@ +require 'mongrel' +require 'stringio' +require 'rack/content_length' +require 'rack/chunked' + +module Rack + module Handler + class Mongrel < ::Mongrel::HttpHandler + def self.run(app, options={}) + server = ::Mongrel::HttpServer.new( + options[:Host] || '0.0.0.0', + options[:Port] || 8080, + options[:num_processors] || 950, + options[:throttle] || 0, + options[:timeout] || 60) + # Acts like Rack::URLMap, utilizing Mongrel's own path finding methods. + # Use is similar to #run, replacing the app argument with a hash of + # { path=>app, ... } or an instance of Rack::URLMap. + if options[:map] + if app.is_a? Hash + app.each do |path, appl| + path = '/'+path unless path[0] == ?/ + server.register(path, Rack::Handler::Mongrel.new(appl)) + end + elsif app.is_a? URLMap + app.instance_variable_get(:@mapping).each do |(host, path, appl)| + next if !host.nil? && !options[:Host].nil? && options[:Host] != host + path = '/'+path unless path[0] == ?/ + server.register(path, Rack::Handler::Mongrel.new(appl)) + end + else + raise ArgumentError, "first argument should be a Hash or URLMap" + end + else + server.register('/', Rack::Handler::Mongrel.new(app)) + end + yield server if block_given? + server.run.join + end + + def initialize(app) + @app = Rack::Chunked.new(Rack::ContentLength.new(app)) + end + + def process(request, response) + env = {}.replace(request.params) + env.delete "HTTP_CONTENT_TYPE" + env.delete "HTTP_CONTENT_LENGTH" + + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + + rack_input = request.body || StringIO.new('') + rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) + + env.update({"rack.version" => [1,1], + "rack.input" => rack_input, + "rack.errors" => $stderr, + + "rack.multithread" => true, + "rack.multiprocess" => false, # ??? + "rack.run_once" => false, + + "rack.url_scheme" => "http", + }) + env["QUERY_STRING"] ||= "" + + status, headers, body = @app.call(env) + + begin + response.status = status.to_i + response.send_status(nil) + + headers.each { |k, vs| + vs.split("\n").each { |v| + response.header[k] = v + } + } + response.send_header + + body.each { |part| + response.write part + response.socket.flush + } + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/scgi.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/scgi.rb new file mode 100644 index 0000000..79a6b2b --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/scgi.rb @@ -0,0 +1,62 @@ +require 'scgi' +require 'stringio' +require 'rack/content_length' +require 'rack/chunked' + +module Rack + module Handler + class SCGI < ::SCGI::Processor + attr_accessor :app + + def self.run(app, options=nil) + new(options.merge(:app=>app, + :host=>options[:Host], + :port=>options[:Port], + :socket=>options[:Socket])).listen + end + + def initialize(settings = {}) + @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) + @log = Object.new + def @log.info(*args); end + def @log.error(*args); end + super(settings) + end + + def process_request(request, input_body, socket) + env = {}.replace(request) + env.delete "HTTP_CONTENT_TYPE" + env.delete "HTTP_CONTENT_LENGTH" + env["REQUEST_PATH"], env["QUERY_STRING"] = env["REQUEST_URI"].split('?', 2) + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env["PATH_INFO"] = env["REQUEST_PATH"] + env["QUERY_STRING"] ||= "" + env["SCRIPT_NAME"] = "" + + rack_input = StringIO.new(input_body) + rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) + + env.update({"rack.version" => [1,1], + "rack.input" => rack_input, + "rack.errors" => $stderr, + "rack.multithread" => true, + "rack.multiprocess" => true, + "rack.run_once" => false, + + "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http" + }) + status, headers, body = app.call(env) + begin + socket.write("Status: #{status}\r\n") + headers.each do |k, vs| + vs.split("\n").each { |v| socket.write("#{k}: #{v}\r\n")} + end + socket.write("\r\n") + body.each {|s| socket.write(s)} + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/swiftiplied_mongrel.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/swiftiplied_mongrel.rb new file mode 100644 index 0000000..4bafd0b --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/swiftiplied_mongrel.rb @@ -0,0 +1,8 @@ +require 'swiftcore/swiftiplied_mongrel' + +module Rack + module Handler + class SwiftipliedMongrel < Handler::Mongrel + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/thin.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/thin.rb new file mode 100644 index 0000000..3d4fedf --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/thin.rb @@ -0,0 +1,18 @@ +require "thin" +require "rack/content_length" +require "rack/chunked" + +module Rack + module Handler + class Thin + def self.run(app, options={}) + app = Rack::Chunked.new(Rack::ContentLength.new(app)) + server = ::Thin::Server.new(options[:Host] || '0.0.0.0', + options[:Port] || 8080, + app) + yield server if block_given? + server.start + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/handler/webrick.rb b/vendor/gems/rack-1.1.0/lib/rack/handler/webrick.rb new file mode 100644 index 0000000..8d7f572 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/handler/webrick.rb @@ -0,0 +1,69 @@ +require 'webrick' +require 'stringio' +require 'rack/content_length' + +module Rack + module Handler + class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet + def self.run(app, options={}) + options[:BindAddress] = options.delete(:Host) if options[:Host] + server = ::WEBrick::HTTPServer.new(options) + server.mount "/", Rack::Handler::WEBrick, app + trap(:INT) { server.shutdown } + yield server if block_given? + server.start + end + + def initialize(server, app) + super server + @app = Rack::ContentLength.new(app) + end + + def service(req, res) + env = req.meta_vars + env.delete_if { |k, v| v.nil? } + + rack_input = StringIO.new(req.body.to_s) + rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) + + env.update({"rack.version" => [1,1], + "rack.input" => rack_input, + "rack.errors" => $stderr, + + "rack.multithread" => true, + "rack.multiprocess" => false, + "rack.run_once" => false, + + "rack.url_scheme" => ["yes", "on", "1"].include?(ENV["HTTPS"]) ? "https" : "http" + }) + + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env["QUERY_STRING"] ||= "" + env["REQUEST_PATH"] ||= "/" + unless env["PATH_INFO"] == "" + path, n = req.request_uri.path, env["SCRIPT_NAME"].length + env["PATH_INFO"] = path[n, path.length-n] + end + + status, headers, body = @app.call(env) + begin + res.status = status.to_i + headers.each { |k, vs| + if k.downcase == "set-cookie" + res.cookies.concat vs.split("\n") + else + vs.split("\n").each { |v| + res[k] = v + } + end + } + body.each { |part| + res.body << part + } + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/head.rb b/vendor/gems/rack-1.1.0/lib/rack/head.rb new file mode 100644 index 0000000..deab822 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/head.rb @@ -0,0 +1,19 @@ +module Rack + +class Head + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + if env["REQUEST_METHOD"] == "HEAD" + [status, headers, []] + else + [status, headers, body] + end + end +end + +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/lint.rb b/vendor/gems/rack-1.1.0/lib/rack/lint.rb new file mode 100644 index 0000000..534375b --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/lint.rb @@ -0,0 +1,575 @@ +require 'rack/utils' + +module Rack + # Rack::Lint validates your application and the requests and + # responses according to the Rack spec. + + class Lint + def initialize(app) + @app = app + end + + # :stopdoc: + + class LintError < RuntimeError; end + module Assertion + def assert(message, &block) + unless block.call + raise LintError, message + end + end + end + include Assertion + + ## This specification aims to formalize the Rack protocol. You + ## can (and should) use Rack::Lint to enforce it. + ## + ## When you develop middleware, be sure to add a Lint before and + ## after to catch all mistakes. + + ## = Rack applications + + ## A Rack application is an Ruby object (not a class) that + ## responds to +call+. + def call(env=nil) + dup._call(env) + end + + def _call(env) + ## It takes exactly one argument, the *environment* + assert("No env given") { env } + check_env env + + env['rack.input'] = InputWrapper.new(env['rack.input']) + env['rack.errors'] = ErrorWrapper.new(env['rack.errors']) + + ## and returns an Array of exactly three values: + status, headers, @body = @app.call(env) + ## The *status*, + check_status status + ## the *headers*, + check_headers headers + ## and the *body*. + check_content_type status, headers + check_content_length status, headers, env + [status, headers, self] + end + + ## == The Environment + def check_env(env) + ## The environment must be an true instance of Hash (no + ## subclassing allowed) that includes CGI-like headers. + ## The application is free to modify the environment. + assert("env #{env.inspect} is not a Hash, but #{env.class}") { + env.kind_of? Hash + } + + ## + ## The environment is required to include these variables + ## (adopted from PEP333), except when they'd be empty, but see + ## below. + + ## REQUEST_METHOD:: The HTTP request method, such as + ## "GET" or "POST". This cannot ever + ## be an empty string, and so is + ## always required. + + ## SCRIPT_NAME:: The initial portion of the request + ## URL's "path" that corresponds to the + ## application object, so that the + ## application knows its virtual + ## "location". This may be an empty + ## string, if the application corresponds + ## to the "root" of the server. + + ## PATH_INFO:: The remainder of the request URL's + ## "path", designating the virtual + ## "location" of the request's target + ## within the application. This may be an + ## empty string, if the request URL targets + ## the application root and does not have a + ## trailing slash. This value may be + ## percent-encoded when I originating from + ## a URL. + + ## QUERY_STRING:: The portion of the request URL that + ## follows the ?, if any. May be + ## empty, but is always required! + + ## SERVER_NAME, SERVER_PORT:: When combined with SCRIPT_NAME and PATH_INFO, these variables can be used to complete the URL. Note, however, that HTTP_HOST, if present, should be used in preference to SERVER_NAME for reconstructing the request URL. SERVER_NAME and SERVER_PORT can never be empty strings, and so are always required. + + ## HTTP_ Variables:: Variables corresponding to the + ## client-supplied HTTP request + ## headers (i.e., variables whose + ## names begin with HTTP_). The + ## presence or absence of these + ## variables should correspond with + ## the presence or absence of the + ## appropriate HTTP header in the + ## request. + + ## In addition to this, the Rack environment must include these + ## Rack-specific variables: + + ## rack.version:: The Array [1,1], representing this version of Rack. + ## rack.url_scheme:: +http+ or +https+, depending on the request URL. + ## rack.input:: See below, the input stream. + ## rack.errors:: See below, the error stream. + ## rack.multithread:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise. + ## rack.multiprocess:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise. + ## rack.run_once:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar). + ## + + ## Additional environment specifications have approved to + ## standardized middleware APIs. None of these are required to + ## be implemented by the server. + + ## rack.session:: A hash like interface for storing request session data. + ## The store must implement: + if session = env['rack.session'] + ## store(key, value) (aliased as []=); + assert("session #{session.inspect} must respond to store and []=") { + session.respond_to?(:store) && session.respond_to?(:[]=) + } + + ## fetch(key, default = nil) (aliased as []); + assert("session #{session.inspect} must respond to fetch and []") { + session.respond_to?(:fetch) && session.respond_to?(:[]) + } + + ## delete(key); + assert("session #{session.inspect} must respond to delete") { + session.respond_to?(:delete) + } + + ## clear; + assert("session #{session.inspect} must respond to clear") { + session.respond_to?(:clear) + } + end + + ## rack.logger:: A common object interface for logging messages. + ## The object must implement: + if logger = env['rack.logger'] + ## info(message, &block) + assert("logger #{logger.inspect} must respond to info") { + logger.respond_to?(:info) + } + + ## debug(message, &block) + assert("logger #{logger.inspect} must respond to debug") { + logger.respond_to?(:debug) + } + + ## warn(message, &block) + assert("logger #{logger.inspect} must respond to warn") { + logger.respond_to?(:warn) + } + + ## error(message, &block) + assert("logger #{logger.inspect} must respond to error") { + logger.respond_to?(:error) + } + + ## fatal(message, &block) + assert("logger #{logger.inspect} must respond to fatal") { + logger.respond_to?(:fatal) + } + end + + ## The server or the application can store their own data in the + ## environment, too. The keys must contain at least one dot, + ## and should be prefixed uniquely. The prefix rack. + ## is reserved for use with the Rack core distribution and other + ## accepted specifications and must not be used otherwise. + ## + + %w[REQUEST_METHOD SERVER_NAME SERVER_PORT + QUERY_STRING + rack.version rack.input rack.errors + rack.multithread rack.multiprocess rack.run_once].each { |header| + assert("env missing required key #{header}") { env.include? header } + } + + ## The environment must not contain the keys + ## HTTP_CONTENT_TYPE or HTTP_CONTENT_LENGTH + ## (use the versions without HTTP_). + %w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header| + assert("env contains #{header}, must use #{header[5,-1]}") { + not env.include? header + } + } + + ## The CGI keys (named without a period) must have String values. + env.each { |key, value| + next if key.include? "." # Skip extensions + assert("env variable #{key} has non-string value #{value.inspect}") { + value.kind_of? String + } + } + + ## + ## There are the following restrictions: + + ## * rack.version must be an array of Integers. + assert("rack.version must be an Array, was #{env["rack.version"].class}") { + env["rack.version"].kind_of? Array + } + ## * rack.url_scheme must either be +http+ or +https+. + assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") { + %w[http https].include? env["rack.url_scheme"] + } + + ## * There must be a valid input stream in rack.input. + check_input env["rack.input"] + ## * There must be a valid error stream in rack.errors. + check_error env["rack.errors"] + + ## * The REQUEST_METHOD must be a valid token. + assert("REQUEST_METHOD unknown: #{env["REQUEST_METHOD"]}") { + env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/ + } + + ## * The SCRIPT_NAME, if non-empty, must start with / + assert("SCRIPT_NAME must start with /") { + !env.include?("SCRIPT_NAME") || + env["SCRIPT_NAME"] == "" || + env["SCRIPT_NAME"] =~ /\A\// + } + ## * The PATH_INFO, if non-empty, must start with / + assert("PATH_INFO must start with /") { + !env.include?("PATH_INFO") || + env["PATH_INFO"] == "" || + env["PATH_INFO"] =~ /\A\// + } + ## * The CONTENT_LENGTH, if given, must consist of digits only. + assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") { + !env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/ + } + + ## * One of SCRIPT_NAME or PATH_INFO must be + ## set. PATH_INFO should be / if + ## SCRIPT_NAME is empty. + assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") { + env["SCRIPT_NAME"] || env["PATH_INFO"] + } + ## SCRIPT_NAME never should be /, but instead be empty. + assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") { + env["SCRIPT_NAME"] != "/" + } + end + + ## === The Input Stream + ## + ## The input stream is an IO-like object which contains the raw HTTP + ## POST data. + def check_input(input) + ## When applicable, its external encoding must be "ASCII-8BIT" and it + ## must be opened in binary mode, for Ruby 1.9 compatibility. + assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") { + input.external_encoding.name == "ASCII-8BIT" + } if input.respond_to?(:external_encoding) + assert("rack.input #{input} is not opened in binary mode") { + input.binmode? + } if input.respond_to?(:binmode?) + + ## The input stream must respond to +gets+, +each+, +read+ and +rewind+. + [:gets, :each, :read, :rewind].each { |method| + assert("rack.input #{input} does not respond to ##{method}") { + input.respond_to? method + } + } + end + + class InputWrapper + include Assertion + + def initialize(input) + @input = input + end + + def size + @input.size + end + + ## * +gets+ must be called without arguments and return a string, + ## or +nil+ on EOF. + def gets(*args) + assert("rack.input#gets called with arguments") { args.size == 0 } + v = @input.gets + assert("rack.input#gets didn't return a String") { + v.nil? or v.kind_of? String + } + v + end + + ## * +read+ behaves like IO#read. Its signature is read([length, [buffer]]). + ## If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must + ## be a String and may not be nil. If +length+ is given and not nil, then this method + ## reads at most +length+ bytes from the input stream. If +length+ is not given or nil, + ## then this method reads all data until EOF. + ## When EOF is reached, this method returns nil if +length+ is given and not nil, or "" + ## if +length+ is not given or is nil. + ## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a + ## newly created String object. + def read(*args) + assert("rack.input#read called with too many arguments") { + args.size <= 2 + } + if args.size >= 1 + assert("rack.input#read called with non-integer and non-nil length") { + args.first.kind_of?(Integer) || args.first.nil? + } + assert("rack.input#read called with a negative length") { + args.first.nil? || args.first >= 0 + } + end + if args.size >= 2 + assert("rack.input#read called with non-String buffer") { + args[1].kind_of?(String) + } + end + + v = @input.read(*args) + + assert("rack.input#read didn't return nil or a String") { + v.nil? or v.kind_of? String + } + if args[0].nil? + assert("rack.input#read(nil) returned nil on EOF") { + !v.nil? + } + end + + v + end + + ## * +each+ must be called without arguments and only yield Strings. + def each(*args) + assert("rack.input#each called with arguments") { args.size == 0 } + @input.each { |line| + assert("rack.input#each didn't yield a String") { + line.kind_of? String + } + yield line + } + end + + ## * +rewind+ must be called without arguments. It rewinds the input + ## stream back to the beginning. It must not raise Errno::ESPIPE: + ## that is, it may not be a pipe or a socket. Therefore, handler + ## developers must buffer the input data into some rewindable object + ## if the underlying input stream is not rewindable. + def rewind(*args) + assert("rack.input#rewind called with arguments") { args.size == 0 } + assert("rack.input#rewind raised Errno::ESPIPE") { + begin + @input.rewind + true + rescue Errno::ESPIPE + false + end + } + end + + ## * +close+ must never be called on the input stream. + def close(*args) + assert("rack.input#close must not be called") { false } + end + end + + ## === The Error Stream + def check_error(error) + ## The error stream must respond to +puts+, +write+ and +flush+. + [:puts, :write, :flush].each { |method| + assert("rack.error #{error} does not respond to ##{method}") { + error.respond_to? method + } + } + end + + class ErrorWrapper + include Assertion + + def initialize(error) + @error = error + end + + ## * +puts+ must be called with a single argument that responds to +to_s+. + def puts(str) + @error.puts str + end + + ## * +write+ must be called with a single argument that is a String. + def write(str) + assert("rack.errors#write not called with a String") { str.kind_of? String } + @error.write str + end + + ## * +flush+ must be called without arguments and must be called + ## in order to make the error appear for sure. + def flush + @error.flush + end + + ## * +close+ must never be called on the error stream. + def close(*args) + assert("rack.errors#close must not be called") { false } + end + end + + ## == The Response + + ## === The Status + def check_status(status) + ## This is an HTTP status. When parsed as integer (+to_i+), it must be + ## greater than or equal to 100. + assert("Status must be >=100 seen as integer") { status.to_i >= 100 } + end + + ## === The Headers + def check_headers(header) + ## The header must respond to +each+, and yield values of key and value. + assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") { + header.respond_to? :each + } + header.each { |key, value| + ## The header keys must be Strings. + assert("header key must be a string, was #{key.class}") { + key.kind_of? String + } + ## The header must not contain a +Status+ key, + assert("header must not contain Status") { key.downcase != "status" } + ## contain keys with : or newlines in their name, + assert("header names must not contain : or \\n") { key !~ /[:\n]/ } + ## contain keys names that end in - or _, + assert("header names must not end in - or _") { key !~ /[-_]\z/ } + ## but only contain keys that consist of + ## letters, digits, _ or - and start with a letter. + assert("invalid header name: #{key}") { key =~ /\A[a-zA-Z][a-zA-Z0-9_-]*\z/ } + + ## The values of the header must be Strings, + assert("a header value must be a String, but the value of " + + "'#{key}' is a #{value.class}") { value.kind_of? String } + ## consisting of lines (for multiple header values, e.g. multiple + ## Set-Cookie values) seperated by "\n". + value.split("\n").each { |item| + ## The lines must not contain characters below 037. + assert("invalid header value #{key}: #{item.inspect}") { + item !~ /[\000-\037]/ + } + } + } + end + + ## === The Content-Type + def check_content_type(status, headers) + headers.each { |key, value| + ## There must be a Content-Type, except when the + ## +Status+ is 1xx, 204 or 304, in which case there must be none + ## given. + if key.downcase == "content-type" + assert("Content-Type header found in #{status} response, not allowed") { + not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i + } + return + end + } + assert("No Content-Type header found") { + Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i + } + end + + ## === The Content-Length + def check_content_length(status, headers, env) + headers.each { |key, value| + if key.downcase == 'content-length' + ## There must not be a Content-Length header when the + ## +Status+ is 1xx, 204 or 304. + assert("Content-Length header found in #{status} response, not allowed") { + not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i + } + + bytes = 0 + string_body = true + + if @body.respond_to?(:to_ary) + @body.each { |part| + unless part.kind_of?(String) + string_body = false + break + end + + bytes += Rack::Utils.bytesize(part) + } + + if env["REQUEST_METHOD"] == "HEAD" + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 + } + else + if string_body + assert("Content-Length header was #{value}, but should be #{bytes}") { + value == bytes.to_s + } + end + end + end + + return + end + } + end + + ## === The Body + def each + @closed = false + ## The Body must respond to +each+ + @body.each { |part| + ## and must only yield String values. + assert("Body yielded non-string value #{part.inspect}") { + part.kind_of? String + } + yield part + } + ## + ## The Body itself should not be an instance of String, as this will + ## break in Ruby 1.9. + ## + ## If the Body responds to +close+, it will be called after iteration. + # XXX howto: assert("Body has not been closed") { @closed } + + + ## + ## If the Body responds to +to_path+, it must return a String + ## identifying the location of a file whose contents are identical + ## to that produced by calling +each+; this may be used by the + ## server as an alternative, possibly more efficient way to + ## transport the response. + + if @body.respond_to?(:to_path) + assert("The file identified by body.to_path does not exist") { + ::File.exist? @body.to_path + } + end + + ## + ## The Body commonly is an Array of Strings, the application + ## instance itself, or a File-like object. + end + + def close + @closed = true + @body.close if @body.respond_to?(:close) + end + + # :startdoc: + + end +end + +## == Thanks +## Some parts of this specification are adopted from PEP333: Python +## Web Server Gateway Interface +## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank +## everyone involved in that effort. diff --git a/vendor/gems/rack-1.1.0/lib/rack/lobster.rb b/vendor/gems/rack-1.1.0/lib/rack/lobster.rb new file mode 100644 index 0000000..f63f419 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/lobster.rb @@ -0,0 +1,65 @@ +require 'zlib' + +require 'rack/request' +require 'rack/response' + +module Rack + # Paste has a Pony, Rack has a Lobster! + class Lobster + LobsterString = Zlib::Inflate.inflate("eJx9kEEOwyAMBO99xd7MAcytUhPlJyj2 + P6jy9i4k9EQyGAnBarEXeCBqSkntNXsi/ZCvC48zGQoZKikGrFMZvgS5ZHd+aGWVuWwhVF0 + t1drVmiR42HcWNz5w3QanT+2gIvTVCiE1lm1Y0eU4JGmIIbaKwextKn8rvW+p5PIwFl8ZWJ + I8jyiTlhTcYXkekJAzTyYN6E08A+dk8voBkAVTJQ==".delete("\n ").unpack("m*")[0]) + + LambdaLobster = lambda { |env| + if env["QUERY_STRING"].include?("flip") + lobster = LobsterString.split("\n"). + map { |line| line.ljust(42).reverse }. + join("\n") + href = "?" + else + lobster = LobsterString + href = "?flip" + end + + content = ["Lobstericious!", + "
", lobster, "
", + "flip!"] + length = content.inject(0) { |a,e| a+e.size }.to_s + [200, {"Content-Type" => "text/html", "Content-Length" => length}, content] + } + + def call(env) + req = Request.new(env) + if req.GET["flip"] == "left" + lobster = LobsterString.split("\n"). + map { |line| line.ljust(42).reverse }. + join("\n") + href = "?flip=right" + elsif req.GET["flip"] == "crash" + raise "Lobster crashed" + else + lobster = LobsterString + href = "?flip=left" + end + + res = Response.new + res.write "Lobstericious!" + res.write "
"
+      res.write lobster
+      res.write "
" + res.write "

flip!

" + res.write "

crash!

" + res.finish + end + + end +end + +if $0 == __FILE__ + require 'rack' + require 'rack/showexceptions' + Rack::Handler::WEBrick.run \ + Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), + :Port => 9292 +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/lock.rb b/vendor/gems/rack-1.1.0/lib/rack/lock.rb new file mode 100644 index 0000000..9323852 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/lock.rb @@ -0,0 +1,16 @@ +module Rack + class Lock + FLAG = 'rack.multithread'.freeze + + def initialize(app, lock = Mutex.new) + @app, @lock = app, lock + end + + def call(env) + old, env[FLAG] = env[FLAG], false + @lock.synchronize { @app.call(env) } + ensure + env[FLAG] = old + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/logger.rb b/vendor/gems/rack-1.1.0/lib/rack/logger.rb new file mode 100644 index 0000000..d67d8ce --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/logger.rb @@ -0,0 +1,20 @@ +require 'logger' + +module Rack + # Sets up rack.logger to write to rack.errors stream + class Logger + def initialize(app, level = ::Logger::INFO) + @app, @level = app, level + end + + def call(env) + logger = ::Logger.new(env['rack.errors']) + logger.level = @level + + env['rack.logger'] = logger + @app.call(env) + ensure + logger.close + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/methodoverride.rb b/vendor/gems/rack-1.1.0/lib/rack/methodoverride.rb new file mode 100644 index 0000000..0eed29f --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/methodoverride.rb @@ -0,0 +1,27 @@ +module Rack + class MethodOverride + HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS) + + METHOD_OVERRIDE_PARAM_KEY = "_method".freeze + HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze + + def initialize(app) + @app = app + end + + def call(env) + if env["REQUEST_METHOD"] == "POST" + req = Request.new(env) + method = req.POST[METHOD_OVERRIDE_PARAM_KEY] || + env[HTTP_METHOD_OVERRIDE_HEADER] + method = method.to_s.upcase + if HTTP_METHODS.include?(method) + env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"] + env["REQUEST_METHOD"] = method + end + end + + @app.call(env) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/mime.rb b/vendor/gems/rack-1.1.0/lib/rack/mime.rb new file mode 100644 index 0000000..1414d19 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/mime.rb @@ -0,0 +1,206 @@ +module Rack + module Mime + # Returns String with mime type if found, otherwise use +fallback+. + # +ext+ should be filename extension in the '.ext' format that + # File.extname(file) returns. + # +fallback+ may be any object + # + # Also see the documentation for MIME_TYPES + # + # Usage: + # Rack::Mime.mime_type('.foo') + # + # This is a shortcut for: + # Rack::Mime::MIME_TYPES.fetch('.foo', 'application/octet-stream') + + def mime_type(ext, fallback='application/octet-stream') + MIME_TYPES.fetch(ext.to_s.downcase, fallback) + end + module_function :mime_type + + # List of most common mime-types, selected various sources + # according to their usefulness in a webserving scope for Ruby + # users. + # + # To amend this list with your local mime.types list you can use: + # + # require 'webrick/httputils' + # list = WEBrick::HTTPUtils.load_mime_types('/etc/mime.types') + # Rack::Mime::MIME_TYPES.merge!(list) + # + # To add the list mongrel provides, use: + # + # require 'mongrel/handlers' + # Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES) + + MIME_TYPES = { + ".3gp" => "video/3gpp", + ".a" => "application/octet-stream", + ".ai" => "application/postscript", + ".aif" => "audio/x-aiff", + ".aiff" => "audio/x-aiff", + ".asc" => "application/pgp-signature", + ".asf" => "video/x-ms-asf", + ".asm" => "text/x-asm", + ".asx" => "video/x-ms-asf", + ".atom" => "application/atom+xml", + ".au" => "audio/basic", + ".avi" => "video/x-msvideo", + ".bat" => "application/x-msdownload", + ".bin" => "application/octet-stream", + ".bmp" => "image/bmp", + ".bz2" => "application/x-bzip2", + ".c" => "text/x-c", + ".cab" => "application/vnd.ms-cab-compressed", + ".cc" => "text/x-c", + ".chm" => "application/vnd.ms-htmlhelp", + ".class" => "application/octet-stream", + ".com" => "application/x-msdownload", + ".conf" => "text/plain", + ".cpp" => "text/x-c", + ".crt" => "application/x-x509-ca-cert", + ".css" => "text/css", + ".csv" => "text/csv", + ".cxx" => "text/x-c", + ".deb" => "application/x-debian-package", + ".der" => "application/x-x509-ca-cert", + ".diff" => "text/x-diff", + ".djv" => "image/vnd.djvu", + ".djvu" => "image/vnd.djvu", + ".dll" => "application/x-msdownload", + ".dmg" => "application/octet-stream", + ".doc" => "application/msword", + ".dot" => "application/msword", + ".dtd" => "application/xml-dtd", + ".dvi" => "application/x-dvi", + ".ear" => "application/java-archive", + ".eml" => "message/rfc822", + ".eps" => "application/postscript", + ".exe" => "application/x-msdownload", + ".f" => "text/x-fortran", + ".f77" => "text/x-fortran", + ".f90" => "text/x-fortran", + ".flv" => "video/x-flv", + ".for" => "text/x-fortran", + ".gem" => "application/octet-stream", + ".gemspec" => "text/x-script.ruby", + ".gif" => "image/gif", + ".gz" => "application/x-gzip", + ".h" => "text/x-c", + ".hh" => "text/x-c", + ".htm" => "text/html", + ".html" => "text/html", + ".ico" => "image/vnd.microsoft.icon", + ".ics" => "text/calendar", + ".ifb" => "text/calendar", + ".iso" => "application/octet-stream", + ".jar" => "application/java-archive", + ".java" => "text/x-java-source", + ".jnlp" => "application/x-java-jnlp-file", + ".jpeg" => "image/jpeg", + ".jpg" => "image/jpeg", + ".js" => "application/javascript", + ".json" => "application/json", + ".log" => "text/plain", + ".m3u" => "audio/x-mpegurl", + ".m4v" => "video/mp4", + ".man" => "text/troff", + ".manifest"=> "text/cache-manifest", + ".mathml" => "application/mathml+xml", + ".mbox" => "application/mbox", + ".mdoc" => "text/troff", + ".me" => "text/troff", + ".mid" => "audio/midi", + ".midi" => "audio/midi", + ".mime" => "message/rfc822", + ".mml" => "application/mathml+xml", + ".mng" => "video/x-mng", + ".mov" => "video/quicktime", + ".mp3" => "audio/mpeg", + ".mp4" => "video/mp4", + ".mp4v" => "video/mp4", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".ms" => "text/troff", + ".msi" => "application/x-msdownload", + ".odp" => "application/vnd.oasis.opendocument.presentation", + ".ods" => "application/vnd.oasis.opendocument.spreadsheet", + ".odt" => "application/vnd.oasis.opendocument.text", + ".ogg" => "application/ogg", + ".ogv" => "video/ogg", + ".p" => "text/x-pascal", + ".pas" => "text/x-pascal", + ".pbm" => "image/x-portable-bitmap", + ".pdf" => "application/pdf", + ".pem" => "application/x-x509-ca-cert", + ".pgm" => "image/x-portable-graymap", + ".pgp" => "application/pgp-encrypted", + ".pkg" => "application/octet-stream", + ".pl" => "text/x-script.perl", + ".pm" => "text/x-script.perl-module", + ".png" => "image/png", + ".pnm" => "image/x-portable-anymap", + ".ppm" => "image/x-portable-pixmap", + ".pps" => "application/vnd.ms-powerpoint", + ".ppt" => "application/vnd.ms-powerpoint", + ".ps" => "application/postscript", + ".psd" => "image/vnd.adobe.photoshop", + ".py" => "text/x-script.python", + ".qt" => "video/quicktime", + ".ra" => "audio/x-pn-realaudio", + ".rake" => "text/x-script.ruby", + ".ram" => "audio/x-pn-realaudio", + ".rar" => "application/x-rar-compressed", + ".rb" => "text/x-script.ruby", + ".rdf" => "application/rdf+xml", + ".roff" => "text/troff", + ".rpm" => "application/x-redhat-package-manager", + ".rss" => "application/rss+xml", + ".rtf" => "application/rtf", + ".ru" => "text/x-script.ruby", + ".s" => "text/x-asm", + ".sgm" => "text/sgml", + ".sgml" => "text/sgml", + ".sh" => "application/x-sh", + ".sig" => "application/pgp-signature", + ".snd" => "audio/basic", + ".so" => "application/octet-stream", + ".svg" => "image/svg+xml", + ".svgz" => "image/svg+xml", + ".swf" => "application/x-shockwave-flash", + ".t" => "text/troff", + ".tar" => "application/x-tar", + ".tbz" => "application/x-bzip-compressed-tar", + ".tcl" => "application/x-tcl", + ".tex" => "application/x-tex", + ".texi" => "application/x-texinfo", + ".texinfo" => "application/x-texinfo", + ".text" => "text/plain", + ".tif" => "image/tiff", + ".tiff" => "image/tiff", + ".torrent" => "application/x-bittorrent", + ".tr" => "text/troff", + ".txt" => "text/plain", + ".vcf" => "text/x-vcard", + ".vcs" => "text/x-vcalendar", + ".vrml" => "model/vrml", + ".war" => "application/java-archive", + ".wav" => "audio/x-wav", + ".wma" => "audio/x-ms-wma", + ".wmv" => "video/x-ms-wmv", + ".wmx" => "video/x-ms-wmx", + ".wrl" => "model/vrml", + ".wsdl" => "application/wsdl+xml", + ".xbm" => "image/x-xbitmap", + ".xhtml" => "application/xhtml+xml", + ".xls" => "application/vnd.ms-excel", + ".xml" => "application/xml", + ".xpm" => "image/x-xpixmap", + ".xsl" => "application/xml", + ".xslt" => "application/xslt+xml", + ".yaml" => "text/yaml", + ".yml" => "text/yaml", + ".zip" => "application/zip", + } + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/mock.rb b/vendor/gems/rack-1.1.0/lib/rack/mock.rb new file mode 100644 index 0000000..23ecba1 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/mock.rb @@ -0,0 +1,189 @@ +require 'uri' +require 'stringio' +require 'rack/lint' +require 'rack/utils' +require 'rack/response' + +module Rack + # Rack::MockRequest helps testing your Rack application without + # actually using HTTP. + # + # After performing a request on a URL with get/post/put/delete, it + # returns a MockResponse with useful helper methods for effective + # testing. + # + # You can pass a hash with additional configuration to the + # get/post/put/delete. + # :input:: A String or IO-like to be used as rack.input. + # :fatal:: Raise a FatalWarning if the app writes to rack.errors. + # :lint:: If true, wrap the application in a Rack::Lint. + + class MockRequest + class FatalWarning < RuntimeError + end + + class FatalWarner + def puts(warning) + raise FatalWarning, warning + end + + def write(warning) + raise FatalWarning, warning + end + + def flush + end + + def string + "" + end + end + + DEFAULT_ENV = { + "rack.version" => [1,1], + "rack.input" => StringIO.new, + "rack.errors" => StringIO.new, + "rack.multithread" => true, + "rack.multiprocess" => true, + "rack.run_once" => false, + } + + def initialize(app) + @app = app + end + + def get(uri, opts={}) request("GET", uri, opts) end + def post(uri, opts={}) request("POST", uri, opts) end + def put(uri, opts={}) request("PUT", uri, opts) end + def delete(uri, opts={}) request("DELETE", uri, opts) end + + def request(method="GET", uri="", opts={}) + env = self.class.env_for(uri, opts.merge(:method => method)) + + if opts[:lint] + app = Rack::Lint.new(@app) + else + app = @app + end + + errors = env["rack.errors"] + MockResponse.new(*(app.call(env) + [errors])) + end + + # Return the Rack environment used for a request to +uri+. + def self.env_for(uri="", opts={}) + uri = URI(uri) + uri.path = "/#{uri.path}" unless uri.path[0] == ?/ + + env = DEFAULT_ENV.dup + + env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET" + env["SERVER_NAME"] = uri.host || "example.org" + env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80" + env["QUERY_STRING"] = uri.query.to_s + env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path + env["rack.url_scheme"] = uri.scheme || "http" + env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off" + + env["SCRIPT_NAME"] = opts[:script_name] || "" + + if opts[:fatal] + env["rack.errors"] = FatalWarner.new + else + env["rack.errors"] = StringIO.new + end + + if params = opts[:params] + if env["REQUEST_METHOD"] == "GET" + params = Utils.parse_nested_query(params) if params.is_a?(String) + params.update(Utils.parse_nested_query(env["QUERY_STRING"])) + env["QUERY_STRING"] = Utils.build_nested_query(params) + elsif !opts.has_key?(:input) + opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded" + if params.is_a?(Hash) + if data = Utils::Multipart.build_multipart(params) + opts[:input] = data + opts["CONTENT_LENGTH"] ||= data.length.to_s + opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}" + else + opts[:input] = Utils.build_nested_query(params) + end + else + opts[:input] = params + end + end + end + + empty_str = "" + empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding + opts[:input] ||= empty_str + if String === opts[:input] + rack_input = StringIO.new(opts[:input]) + else + rack_input = opts[:input] + end + + rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) + env['rack.input'] = rack_input + + env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s + + opts.each { |field, value| + env[field] = value if String === field + } + + env + end + end + + # Rack::MockResponse provides useful helpers for testing your apps. + # Usually, you don't create the MockResponse on your own, but use + # MockRequest. + + class MockResponse + def initialize(status, headers, body, errors=StringIO.new("")) + @status = status.to_i + + @original_headers = headers + @headers = Rack::Utils::HeaderHash.new + headers.each { |field, values| + @headers[field] = values + @headers[field] = "" if values.empty? + } + + @body = "" + body.each { |part| @body << part } + + @errors = errors.string if errors.respond_to?(:string) + end + + # Status + attr_reader :status + + # Headers + attr_reader :headers, :original_headers + + def [](field) + headers[field] + end + + + # Body + attr_reader :body + + def =~(other) + @body =~ other + end + + def match(other) + @body.match other + end + + + # Errors + attr_accessor :errors + + + include Response::Helpers + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/nulllogger.rb b/vendor/gems/rack-1.1.0/lib/rack/nulllogger.rb new file mode 100644 index 0000000..77fb637 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/nulllogger.rb @@ -0,0 +1,18 @@ +module Rack + class NullLogger + def initialize(app) + @app = app + end + + def call(env) + env['rack.logger'] = self + @app.call(env) + end + + def info(progname = nil, &block); end + def debug(progname = nil, &block); end + def warn(progname = nil, &block); end + def error(progname = nil, &block); end + def fatal(progname = nil, &block); end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/recursive.rb b/vendor/gems/rack-1.1.0/lib/rack/recursive.rb new file mode 100644 index 0000000..bf8b965 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/recursive.rb @@ -0,0 +1,57 @@ +require 'uri' + +module Rack + # Rack::ForwardRequest gets caught by Rack::Recursive and redirects + # the current request to the app at +url+. + # + # raise ForwardRequest.new("/not-found") + # + + class ForwardRequest < Exception + attr_reader :url, :env + + def initialize(url, env={}) + @url = URI(url) + @env = env + + @env["PATH_INFO"] = @url.path + @env["QUERY_STRING"] = @url.query if @url.query + @env["HTTP_HOST"] = @url.host if @url.host + @env["HTTP_PORT"] = @url.port if @url.port + @env["rack.url_scheme"] = @url.scheme if @url.scheme + + super "forwarding to #{url}" + end + end + + # Rack::Recursive allows applications called down the chain to + # include data from other applications (by using + # rack['rack.recursive.include'][...] or raise a + # ForwardRequest to redirect internally. + + class Recursive + def initialize(app) + @app = app + end + + def call(env) + @script_name = env["SCRIPT_NAME"] + @app.call(env.merge('rack.recursive.include' => method(:include))) + rescue ForwardRequest => req + call(env.merge(req.env)) + end + + def include(env, path) + unless path.index(@script_name) == 0 && (path[@script_name.size] == ?/ || + path[@script_name.size].nil?) + raise ArgumentError, "can only include below #{@script_name}, not #{path}" + end + + env = env.merge("PATH_INFO" => path, "SCRIPT_NAME" => @script_name, + "REQUEST_METHOD" => "GET", + "CONTENT_LENGTH" => "0", "CONTENT_TYPE" => "", + "rack.input" => StringIO.new("")) + @app.call(env) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/reloader.rb b/vendor/gems/rack-1.1.0/lib/rack/reloader.rb new file mode 100644 index 0000000..a06de23 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/reloader.rb @@ -0,0 +1,109 @@ +# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com +# Rack::Reloader is subject to the terms of an MIT-style license. +# See COPYING or http://www.opensource.org/licenses/mit-license.php. + +require 'pathname' + +module Rack + + # High performant source reloader + # + # This class acts as Rack middleware. + # + # What makes it especially suited for use in a production environment is that + # any file will only be checked once and there will only be made one system + # call stat(2). + # + # Please note that this will not reload files in the background, it does so + # only when actively called. + # + # It is performing a check/reload cycle at the start of every request, but + # also respects a cool down time, during which nothing will be done. + class Reloader + def initialize(app, cooldown = 10, backend = Stat) + @app = app + @cooldown = cooldown + @last = (Time.now - cooldown) + @cache = {} + @mtimes = {} + + extend backend + end + + def call(env) + if @cooldown and Time.now > @last + @cooldown + if Thread.list.size > 1 + Thread.exclusive{ reload! } + else + reload! + end + + @last = Time.now + end + + @app.call(env) + end + + def reload!(stderr = $stderr) + rotation do |file, mtime| + previous_mtime = @mtimes[file] ||= mtime + safe_load(file, mtime, stderr) if mtime > previous_mtime + end + end + + # A safe Kernel::load, issuing the hooks depending on the results + def safe_load(file, mtime, stderr = $stderr) + load(file) + stderr.puts "#{self.class}: reloaded `#{file}'" + file + rescue LoadError, SyntaxError => ex + stderr.puts ex + ensure + @mtimes[file] = mtime + end + + module Stat + def rotation + files = [$0, *$LOADED_FEATURES].uniq + paths = ['./', *$LOAD_PATH].uniq + + files.map{|file| + next if file =~ /\.(so|bundle)$/ # cannot reload compiled files + + found, stat = figure_path(file, paths) + next unless found && stat && mtime = stat.mtime + + @cache[file] = found + + yield(found, mtime) + }.compact + end + + # Takes a relative or absolute +file+ name, a couple possible +paths+ that + # the +file+ might reside in. Returns the full path and File::Stat for the + # path. + def figure_path(file, paths) + found = @cache[file] + found = file if !found and Pathname.new(file).absolute? + found, stat = safe_stat(found) + return found, stat if found + + paths.find do |possible_path| + path = ::File.join(possible_path, file) + found, stat = safe_stat(path) + return ::File.expand_path(found), stat if found + end + + return false, false + end + + def safe_stat(file) + return unless file + stat = ::File.stat(file) + return file, stat if stat.file? + rescue Errno::ENOENT, Errno::ENOTDIR + @cache.delete(file) and false + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/request.rb b/vendor/gems/rack-1.1.0/lib/rack/request.rb new file mode 100644 index 0000000..b3de1ce --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/request.rb @@ -0,0 +1,271 @@ +require 'rack/utils' + +module Rack + # Rack::Request provides a convenient interface to a Rack + # environment. It is stateless, the environment +env+ passed to the + # constructor will be directly modified. + # + # req = Rack::Request.new(env) + # req.post? + # req.params["data"] + # + # The environment hash passed will store a reference to the Request object + # instantiated so that it will only instantiate if an instance of the Request + # object doesn't already exist. + + class Request + # The environment of the request. + attr_reader :env + + def initialize(env) + @env = env + end + + def body; @env["rack.input"] end + def scheme; @env["rack.url_scheme"] end + def script_name; @env["SCRIPT_NAME"].to_s end + def path_info; @env["PATH_INFO"].to_s end + def port; @env["SERVER_PORT"].to_i end + def request_method; @env["REQUEST_METHOD"] end + def query_string; @env["QUERY_STRING"].to_s end + def content_length; @env['CONTENT_LENGTH'] end + def content_type; @env['CONTENT_TYPE'] end + def session; @env['rack.session'] ||= {} end + def session_options; @env['rack.session.options'] ||= {} end + def logger; @env['rack.logger'] end + + # The media type (type/subtype) portion of the CONTENT_TYPE header + # without any media type parameters. e.g., when CONTENT_TYPE is + # "text/plain;charset=utf-8", the media-type is "text/plain". + # + # For more information on the use of media types in HTTP, see: + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 + def media_type + content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase + end + + # The media type parameters provided in CONTENT_TYPE as a Hash, or + # an empty Hash if no CONTENT_TYPE or media-type parameters were + # provided. e.g., when the CONTENT_TYPE is "text/plain;charset=utf-8", + # this method responds with the following Hash: + # { 'charset' => 'utf-8' } + def media_type_params + return {} if content_type.nil? + content_type.split(/\s*[;,]\s*/)[1..-1]. + collect { |s| s.split('=', 2) }. + inject({}) { |hash,(k,v)| hash[k.downcase] = v ; hash } + end + + # The character set of the request body if a "charset" media type + # parameter was given, or nil if no "charset" was specified. Note + # that, per RFC2616, text/* media types that specify no explicit + # charset are to be considered ISO-8859-1. + def content_charset + media_type_params['charset'] + end + + def host_with_port + if forwarded = @env["HTTP_X_FORWARDED_HOST"] + forwarded.split(/,\s?/).last + else + @env['HTTP_HOST'] || "#{@env['SERVER_NAME'] || @env['SERVER_ADDR']}:#{@env['SERVER_PORT']}" + end + end + + def host + # Remove port number. + host_with_port.to_s.gsub(/:\d+\z/, '') + end + + def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end + def path_info=(s); @env["PATH_INFO"] = s.to_s end + + def get?; request_method == "GET" end + def post?; request_method == "POST" end + def put?; request_method == "PUT" end + def delete?; request_method == "DELETE" end + def head?; request_method == "HEAD" end + + # The set of form-data media-types. Requests that do not indicate + # one of the media types presents in this list will not be eligible + # for form-data / param parsing. + FORM_DATA_MEDIA_TYPES = [ + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ] + + # The set of media-types. Requests that do not indicate + # one of the media types presents in this list will not be eligible + # for param parsing like soap attachments or generic multiparts + PARSEABLE_DATA_MEDIA_TYPES = [ + 'multipart/related', + 'multipart/mixed' + ] + + # Determine whether the request body contains form-data by checking + # the request Content-Type for one of the media-types: + # "application/x-www-form-urlencoded" or "multipart/form-data". The + # list of form-data media types can be modified through the + # +FORM_DATA_MEDIA_TYPES+ array. + # + # A request body is also assumed to contain form-data when no + # Content-Type header is provided and the request_method is POST. + def form_data? + type = media_type + meth = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD'] + (meth == 'POST' && type.nil?) || FORM_DATA_MEDIA_TYPES.include?(type) + end + + # Determine whether the request body contains data by checking + # the request media_type against registered parse-data media-types + def parseable_data? + PARSEABLE_DATA_MEDIA_TYPES.include?(media_type) + end + + # Returns the data recieved in the query string. + def GET + if @env["rack.request.query_string"] == query_string + @env["rack.request.query_hash"] + else + @env["rack.request.query_string"] = query_string + @env["rack.request.query_hash"] = parse_query(query_string) + end + end + + # Returns the data recieved in the request body. + # + # This method support both application/x-www-form-urlencoded and + # multipart/form-data. + def POST + if @env["rack.input"].nil? + raise "Missing rack.input" + elsif @env["rack.request.form_input"].eql? @env["rack.input"] + @env["rack.request.form_hash"] + elsif form_data? || parseable_data? + @env["rack.request.form_input"] = @env["rack.input"] + unless @env["rack.request.form_hash"] = parse_multipart(env) + form_vars = @env["rack.input"].read + + # Fix for Safari Ajax postings that always append \0 + form_vars.sub!(/\0\z/, '') + + @env["rack.request.form_vars"] = form_vars + @env["rack.request.form_hash"] = parse_query(form_vars) + + @env["rack.input"].rewind + end + @env["rack.request.form_hash"] + else + {} + end + end + + # The union of GET and POST data. + def params + self.GET.update(self.POST) + rescue EOFError => e + self.GET + end + + # shortcut for request.params[key] + def [](key) + params[key.to_s] + end + + # shortcut for request.params[key] = value + def []=(key, value) + params[key.to_s] = value + end + + # like Hash#values_at + def values_at(*keys) + keys.map{|key| params[key] } + end + + # the referer of the client or '/' + def referer + @env['HTTP_REFERER'] || '/' + end + alias referrer referer + + def user_agent + @env['HTTP_USER_AGENT'] + end + + def cookies + return {} unless @env["HTTP_COOKIE"] + + if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"] + @env["rack.request.cookie_hash"] + else + @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"] + # According to RFC 2109: + # If multiple cookies satisfy the criteria above, they are ordered in + # the Cookie header such that those with more specific Path attributes + # precede those with less specific. Ordering with respect to other + # attributes (e.g., Domain) is unspecified. + @env["rack.request.cookie_hash"] = + Utils.parse_query(@env["rack.request.cookie_string"], ';,').inject({}) {|h,(k,v)| + h[k] = Array === v ? v.first : v + h + } + end + end + + def xhr? + @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" + end + + # Tries to return a remake of the original request URL as a string. + def url + url = scheme + "://" + url << host + + if scheme == "https" && port != 443 || + scheme == "http" && port != 80 + url << ":#{port}" + end + + url << fullpath + + url + end + + def path + script_name + path_info + end + + def fullpath + query_string.empty? ? path : "#{path}?#{query_string}" + end + + def accept_encoding + @env["HTTP_ACCEPT_ENCODING"].to_s.split(/,\s*/).map do |part| + m = /^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$/.match(part) # From WEBrick + + if m + [m[1], (m[2] || 1.0).to_f] + else + raise "Invalid value for Accept-Encoding: #{part.inspect}" + end + end + end + + def ip + if addr = @env['HTTP_X_FORWARDED_FOR'] + addr.split(',').last.strip + else + @env['REMOTE_ADDR'] + end + end + + protected + def parse_query(qs) + Utils.parse_nested_query(qs) + end + + def parse_multipart(env) + Utils::Multipart.parse_multipart(env) + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/response.rb b/vendor/gems/rack-1.1.0/lib/rack/response.rb new file mode 100644 index 0000000..a7f9bf2 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/response.rb @@ -0,0 +1,149 @@ +require 'rack/request' +require 'rack/utils' + +module Rack + # Rack::Response provides a convenient interface to create a Rack + # response. + # + # It allows setting of headers and cookies, and provides useful + # defaults (a OK response containing HTML). + # + # You can use Response#write to iteratively generate your response, + # but note that this is buffered by Rack::Response until you call + # +finish+. +finish+ however can take a block inside which calls to + # +write+ are syncronous with the Rack response. + # + # Your application's +call+ should end returning Response#finish. + + class Response + attr_accessor :length + + def initialize(body=[], status=200, header={}, &block) + @status = status.to_i + @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. + merge(header)) + + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 + + @body = [] + + if body.respond_to? :to_str + write body.to_str + elsif body.respond_to?(:each) + body.each { |part| + write part.to_s + } + else + raise TypeError, "stringable or iterable required" + end + + yield self if block_given? + end + + attr_reader :header + attr_accessor :status, :body + + def [](key) + header[key] + end + + def []=(key, value) + header[key] = value + end + + def set_cookie(key, value) + Utils.set_cookie_header!(header, key, value) + end + + def delete_cookie(key, value={}) + Utils.delete_cookie_header!(header, key, value) + end + + def redirect(target, status=302) + self.status = status + self["Location"] = target + end + + def finish(&block) + @block = block + + if [204, 304].include?(status.to_i) + header.delete "Content-Type" + [status.to_i, header, []] + else + [status.to_i, header, self] + end + end + alias to_a finish # For *response + + def each(&callback) + @body.each(&callback) + @writer = callback + @block.call(self) if @block + end + + # Append to body and update Content-Length. + # + # NOTE: Do not mix #write and direct #body access! + # + def write(str) + s = str.to_s + @length += Rack::Utils.bytesize(s) + @writer.call s + + header["Content-Length"] = @length.to_s + str + end + + def close + body.close if body.respond_to?(:close) + end + + def empty? + @block == nil && @body.empty? + end + + alias headers header + + module Helpers + def invalid?; @status < 100 || @status >= 600; end + + def informational?; @status >= 100 && @status < 200; end + def successful?; @status >= 200 && @status < 300; end + def redirection?; @status >= 300 && @status < 400; end + def client_error?; @status >= 400 && @status < 500; end + def server_error?; @status >= 500 && @status < 600; end + + def ok?; @status == 200; end + def forbidden?; @status == 403; end + def not_found?; @status == 404; end + + def redirect?; [301, 302, 303, 307].include? @status; end + def empty?; [201, 204, 304].include? @status; end + + # Headers + attr_reader :headers, :original_headers + + def include?(header) + !!headers[header] + end + + def content_type + headers["Content-Type"] + end + + def content_length + cl = headers["Content-Length"] + cl ? cl.to_i : cl + end + + def location + headers["Location"] + end + end + + include Helpers + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/rewindable_input.rb b/vendor/gems/rack-1.1.0/lib/rack/rewindable_input.rb new file mode 100644 index 0000000..accd96b --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/rewindable_input.rb @@ -0,0 +1,100 @@ +require 'tempfile' + +module Rack + # Class which can make any IO object rewindable, including non-rewindable ones. It does + # this by buffering the data into a tempfile, which is rewindable. + # + # rack.input is required to be rewindable, so if your input stream IO is non-rewindable + # by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class + # to easily make it rewindable. + # + # Don't forget to call #close when you're done. This frees up temporary resources that + # RewindableInput uses, though it does *not* close the original IO object. + class RewindableInput + def initialize(io) + @io = io + @rewindable_io = nil + @unlinked = false + end + + def gets + make_rewindable unless @rewindable_io + @rewindable_io.gets + end + + def read(*args) + make_rewindable unless @rewindable_io + @rewindable_io.read(*args) + end + + def each(&block) + make_rewindable unless @rewindable_io + @rewindable_io.each(&block) + end + + def rewind + make_rewindable unless @rewindable_io + @rewindable_io.rewind + end + + # Closes this RewindableInput object without closing the originally + # wrapped IO oject. Cleans up any temporary resources that this RewindableInput + # has created. + # + # This method may be called multiple times. It does nothing on subsequent calls. + def close + if @rewindable_io + if @unlinked + @rewindable_io.close + else + @rewindable_io.close! + end + @rewindable_io = nil + end + end + + private + + # Ruby's Tempfile class has a bug. Subclass it and fix it. + class Tempfile < ::Tempfile + def _close + @tmpfile.close if @tmpfile + @data[1] = nil if @data + @tmpfile = nil + end + end + + def make_rewindable + # Buffer all data into a tempfile. Since this tempfile is private to this + # RewindableInput object, we chmod it so that nobody else can read or write + # it. On POSIX filesystems we also unlink the file so that it doesn't + # even have a file entry on the filesystem anymore, though we can still + # access it because we have the file handle open. + @rewindable_io = Tempfile.new('RackRewindableInput') + @rewindable_io.chmod(0000) + @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding) + @rewindable_io.binmode + if filesystem_has_posix_semantics? + @rewindable_io.unlink + @unlinked = true + end + + buffer = "" + while @io.read(1024 * 4, buffer) + entire_buffer_written_out = false + while !entire_buffer_written_out + written = @rewindable_io.write(buffer) + entire_buffer_written_out = written == buffer.size + if !entire_buffer_written_out + buffer.slice!(0 .. written - 1) + end + end + end + @rewindable_io.rewind + end + + def filesystem_has_posix_semantics? + RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/ + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/runtime.rb b/vendor/gems/rack-1.1.0/lib/rack/runtime.rb new file mode 100644 index 0000000..1bd411f --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/runtime.rb @@ -0,0 +1,27 @@ +module Rack + # Sets an "X-Runtime" response header, indicating the response + # time of the request, in seconds + # + # You can put it right before the application to see the processing + # time, or before all the other middlewares to include time for them, + # too. + class Runtime + def initialize(app, name = nil) + @app = app + @header_name = "X-Runtime" + @header_name << "-#{name}" if name + end + + def call(env) + start_time = Time.now + status, headers, body = @app.call(env) + request_time = Time.now - start_time + + if !headers.has_key?(@header_name) + headers[@header_name] = "%0.6f" % request_time + end + + [status, headers, body] + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/sendfile.rb b/vendor/gems/rack-1.1.0/lib/rack/sendfile.rb new file mode 100644 index 0000000..4fa8294 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/sendfile.rb @@ -0,0 +1,142 @@ +require 'rack/file' + +module Rack + class File #:nodoc: + alias :to_path :path + end + + # = Sendfile + # + # The Sendfile middleware intercepts responses whose body is being + # served from a file and replaces it with a server specific X-Sendfile + # header. The web server is then responsible for writing the file contents + # to the client. This can dramatically reduce the amount of work required + # by the Ruby backend and takes advantage of the web servers optimized file + # delivery code. + # + # In order to take advantage of this middleware, the response body must + # respond to +to_path+ and the request must include an X-Sendfile-Type + # header. Rack::File and other components implement +to_path+ so there's + # rarely anything you need to do in your application. The X-Sendfile-Type + # header is typically set in your web servers configuration. The following + # sections attempt to document + # + # === Nginx + # + # Nginx supports the X-Accel-Redirect header. This is similar to X-Sendfile + # but requires parts of the filesystem to be mapped into a private URL + # hierarachy. + # + # The following example shows the Nginx configuration required to create + # a private "/files/" area, enable X-Accel-Redirect, and pass the special + # X-Sendfile-Type and X-Accel-Mapping headers to the backend: + # + # location /files/ { + # internal; + # alias /var/www/; + # } + # + # location / { + # proxy_redirect false; + # + # proxy_set_header Host $host; + # proxy_set_header X-Real-IP $remote_addr; + # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + # + # proxy_set_header X-Sendfile-Type X-Accel-Redirect + # proxy_set_header X-Accel-Mapping /files/=/var/www/; + # + # proxy_pass http://127.0.0.1:8080/; + # } + # + # Note that the X-Sendfile-Type header must be set exactly as shown above. The + # X-Accel-Mapping header should specify the name of the private URL pattern, + # followed by an equals sign (=), followed by the location on the file system + # that it maps to. The middleware performs a simple substitution on the + # resulting path. + # + # See Also: http://wiki.codemongers.com/NginxXSendfile + # + # === lighttpd + # + # Lighttpd has supported some variation of the X-Sendfile header for some + # time, although only recent version support X-Sendfile in a reverse proxy + # configuration. + # + # $HTTP["host"] == "example.com" { + # proxy-core.protocol = "http" + # proxy-core.balancer = "round-robin" + # proxy-core.backends = ( + # "127.0.0.1:8000", + # "127.0.0.1:8001", + # ... + # ) + # + # proxy-core.allow-x-sendfile = "enable" + # proxy-core.rewrite-request = ( + # "X-Sendfile-Type" => (".*" => "X-Sendfile") + # ) + # } + # + # See Also: http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModProxyCore + # + # === Apache + # + # X-Sendfile is supported under Apache 2.x using a separate module: + # + # http://tn123.ath.cx/mod_xsendfile/ + # + # Once the module is compiled and installed, you can enable it using + # XSendFile config directive: + # + # RequestHeader Set X-Sendfile-Type X-Sendfile + # ProxyPassReverse / http://localhost:8001/ + # XSendFile on + + class Sendfile + F = ::File + + def initialize(app, variation=nil) + @app = app + @variation = variation + end + + def call(env) + status, headers, body = @app.call(env) + if body.respond_to?(:to_path) + case type = variation(env) + when 'X-Accel-Redirect' + path = F.expand_path(body.to_path) + if url = map_accel_path(env, path) + headers[type] = url + body = [] + else + env['rack.errors'] << "X-Accel-Mapping header missing" + end + when 'X-Sendfile', 'X-Lighttpd-Send-File' + path = F.expand_path(body.to_path) + headers[type] = path + body = [] + when '', nil + else + env['rack.errors'] << "Unknown x-sendfile variation: '#{variation}'.\n" + end + end + [status, headers, body] + end + + private + def variation(env) + @variation || + env['sendfile.type'] || + env['HTTP_X_SENDFILE_TYPE'] + end + + def map_accel_path(env, file) + if mapping = env['HTTP_X_ACCEL_MAPPING'] + internal, external = mapping.split('=', 2).map{ |p| p.strip } + file.sub(/^#{internal}/i, external) + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/server.rb b/vendor/gems/rack-1.1.0/lib/rack/server.rb new file mode 100644 index 0000000..2bb20aa --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/server.rb @@ -0,0 +1,212 @@ +require 'optparse' + +module Rack + class Server + class Options + def parse!(args) + options = {} + opt_parser = OptionParser.new("", 24, ' ') do |opts| + opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]" + + opts.separator "" + opts.separator "Ruby options:" + + lineno = 1 + opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line| + eval line, TOPLEVEL_BINDING, "-e", lineno + lineno += 1 + } + + opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") { + options[:debug] = true + } + opts.on("-w", "--warn", "turn warnings on for your script") { + options[:warn] = true + } + + opts.on("-I", "--include PATH", + "specify $LOAD_PATH (may be used more than once)") { |path| + options[:include] = path.split(":") + } + + opts.on("-r", "--require LIBRARY", + "require the library, before executing your script") { |library| + options[:require] = library + } + + opts.separator "" + opts.separator "Rack options:" + opts.on("-s", "--server SERVER", "serve using SERVER (webrick/mongrel)") { |s| + options[:server] = s + } + + opts.on("-o", "--host HOST", "listen on HOST (default: 0.0.0.0)") { |host| + options[:Host] = host + } + + opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port| + options[:Port] = port + } + + opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e| + options[:environment] = e + } + + opts.on("-D", "--daemonize", "run daemonized in the background") { |d| + options[:daemonize] = d ? true : false + } + + opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f| + options[:pid] = f + } + + opts.separator "" + opts.separator "Common options:" + + opts.on_tail("-h", "--help", "Show this message") do + puts opts + exit + end + + opts.on_tail("--version", "Show version") do + puts "Rack #{Rack.version}" + exit + end + end + opt_parser.parse! args + options[:config] = args.last if args.last + options + end + end + + def self.start + new.start + end + + attr_accessor :options + + def initialize(options = nil) + @options = options + end + + def options + @options ||= parse_options(ARGV) + end + + def default_options + { + :environment => "development", + :pid => nil, + :Port => 9292, + :Host => "0.0.0.0", + :AccessLog => [], + :config => "config.ru" + } + end + + def app + @app ||= begin + if !::File.exist? options[:config] + abort "configuration #{options[:config]} not found" + end + + app, options = Rack::Builder.parse_file(self.options[:config], opt_parser) + self.options.merge! options + app + end + end + + def self.middleware + @middleware ||= begin + m = Hash.new {|h,k| h[k] = []} + m["deployment"].concat [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }] + m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]] + m + end + end + + def middleware + self.class.middleware + end + + def start + if options[:debug] + $DEBUG = true + require 'pp' + p options[:server] + pp wrapped_app + pp app + end + + if options[:warn] + $-w = true + end + + if includes = options[:include] + $LOAD_PATH.unshift *includes + end + + if library = options[:require] + require library + end + + daemonize_app if options[:daemonize] + write_pid if options[:pid] + server.run wrapped_app, options + end + + def server + @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default + end + + private + def parse_options(args) + options = default_options + + # Don't evaluate CGI ISINDEX parameters. + # http://hoohoo.ncsa.uiuc.edu/cgi/cl.html + args.clear if ENV.include?("REQUEST_METHOD") + + options.merge! opt_parser.parse! args + options + end + + def opt_parser + Options.new + end + + def build_app(app) + middleware[options[:environment]].reverse_each do |middleware| + middleware = middleware.call(self) if middleware.respond_to?(:call) + next unless middleware + klass = middleware.shift + app = klass.new(app, *middleware) + end + app + end + + def wrapped_app + @wrapped_app ||= build_app app + end + + def daemonize_app + if RUBY_VERSION < "1.9" + exit if fork + Process.setsid + exit if fork + Dir.chdir "/" + ::File.umask 0000 + STDIN.reopen "/dev/null" + STDOUT.reopen "/dev/null", "a" + STDERR.reopen "/dev/null", "a" + else + Process.daemon + end + end + + def write_pid + ::File.open(options[:pid], 'w'){ |f| f.write("#{Process.pid}") } + at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) } + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/session/abstract/id.rb b/vendor/gems/rack-1.1.0/lib/rack/session/abstract/id.rb new file mode 100644 index 0000000..9874670 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/session/abstract/id.rb @@ -0,0 +1,140 @@ +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net +# bugrep: Andreas Zehnder + +require 'time' +require 'rack/request' +require 'rack/response' + +module Rack + + module Session + + module Abstract + + # ID sets up a basic framework for implementing an id based sessioning + # service. Cookies sent to the client for maintaining sessions will only + # contain an id reference. Only #get_session and #set_session are + # required to be overwritten. + # + # All parameters are optional. + # * :key determines the name of the cookie, by default it is + # 'rack.session' + # * :path, :domain, :expire_after, :secure, and :httponly set the related + # cookie options as by Rack::Response#add_cookie + # * :defer will not set a cookie in the response. + # * :renew (implementation dependent) will prompt the generation of a new + # session id, and migration of data to be referenced at the new id. If + # :defer is set, it will be overridden and the cookie will be set. + # * :sidbits sets the number of bits in length that a generated session + # id will be. + # + # These options can be set on a per request basis, at the location of + # env['rack.session.options']. Additionally the id of the session can be + # found within the options hash at the key :id. It is highly not + # recommended to change its value. + # + # Is Rack::Utils::Context compatible. + + class ID + DEFAULT_OPTIONS = { + :path => '/', + :domain => nil, + :expire_after => nil, + :secure => false, + :httponly => true, + :defer => false, + :renew => false, + :sidbits => 128 + } + + attr_reader :key, :default_options + def initialize(app, options={}) + @app = app + @key = options[:key] || "rack.session" + @default_options = self.class::DEFAULT_OPTIONS.merge(options) + end + + def call(env) + context(env) + end + + def context(env, app=@app) + load_session(env) + status, headers, body = app.call(env) + commit_session(env, status, headers, body) + end + + private + + # Generate a new session id using Ruby #rand. The size of the + # session id is controlled by the :sidbits option. + # Monkey patch this to use custom methods for session id generation. + + def generate_sid + "%0#{@default_options[:sidbits] / 4}x" % + rand(2**@default_options[:sidbits] - 1) + end + + # Extracts the session id from provided cookies and passes it and the + # environment to #get_session. It then sets the resulting session into + # 'rack.session', and places options and session metadata into + # 'rack.session.options'. + + def load_session(env) + request = Rack::Request.new(env) + session_id = request.cookies[@key] + + begin + session_id, session = get_session(env, session_id) + env['rack.session'] = session + rescue + env['rack.session'] = Hash.new + end + + env['rack.session.options'] = @default_options. + merge(:id => session_id) + end + + # Acquires the session from the environment and the session id from + # the session options and passes them to #set_session. If successful + # and the :defer option is not true, a cookie will be added to the + # response with the session's id. + + def commit_session(env, status, headers, body) + session = env['rack.session'] + options = env['rack.session.options'] + session_id = options[:id] + + if not session_id = set_session(env, session_id, session, options) + env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.") + elsif options[:defer] and not options[:renew] + env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE + else + cookie = Hash.new + cookie[:value] = session_id + cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? + Utils.set_cookie_header!(headers, @key, cookie.merge(options)) + end + + [status, headers, body] + end + + # All thread safety and session retrival proceedures should occur here. + # Should return [session_id, session]. + # If nil is provided as the session id, generation of a new valid id + # should occur within. + + def get_session(env, sid) + raise '#get_session not implemented.' + end + + # All thread safety and session storage proceedures should occur here. + # Should return true or false dependant on whether or not the session + # was saved or not. + def set_session(env, sid, session, options) + raise '#set_session not implemented.' + end + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/session/cookie.rb b/vendor/gems/rack-1.1.0/lib/rack/session/cookie.rb new file mode 100644 index 0000000..240e6c8 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/session/cookie.rb @@ -0,0 +1,90 @@ +require 'openssl' +require 'rack/request' +require 'rack/response' + +module Rack + + module Session + + # Rack::Session::Cookie provides simple cookie based session management. + # The session is a Ruby Hash stored as base64 encoded marshalled data + # set to :key (default: rack.session). + # When the secret key is set, cookie data is checked for data integrity. + # + # Example: + # + # use Rack::Session::Cookie, :key => 'rack.session', + # :domain => 'foo.com', + # :path => '/', + # :expire_after => 2592000, + # :secret => 'change_me' + # + # All parameters are optional. + + class Cookie + + def initialize(app, options={}) + @app = app + @key = options[:key] || "rack.session" + @secret = options[:secret] + @default_options = {:domain => nil, + :path => "/", + :expire_after => nil}.merge(options) + end + + def call(env) + load_session(env) + status, headers, body = @app.call(env) + commit_session(env, status, headers, body) + end + + private + + def load_session(env) + request = Rack::Request.new(env) + session_data = request.cookies[@key] + + if @secret && session_data + session_data, digest = session_data.split("--") + session_data = nil unless digest == generate_hmac(session_data) + end + + begin + session_data = session_data.unpack("m*").first + session_data = Marshal.load(session_data) + env["rack.session"] = session_data + rescue + env["rack.session"] = Hash.new + end + + env["rack.session.options"] = @default_options.dup + end + + def commit_session(env, status, headers, body) + session_data = Marshal.dump(env["rack.session"]) + session_data = [session_data].pack("m*") + + if @secret + session_data = "#{session_data}--#{generate_hmac(session_data)}" + end + + if session_data.size > (4096 - @key.size) + env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.") + else + options = env["rack.session.options"] + cookie = Hash.new + cookie[:value] = session_data + cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil? + Utils.set_cookie_header!(headers, @key, cookie.merge(options)) + end + + [status, headers, body] + end + + def generate_hmac(data) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data) + end + + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/session/memcache.rb b/vendor/gems/rack-1.1.0/lib/rack/session/memcache.rb new file mode 100644 index 0000000..44629da --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/session/memcache.rb @@ -0,0 +1,119 @@ +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net + +require 'rack/session/abstract/id' +require 'memcache' + +module Rack + module Session + # Rack::Session::Memcache provides simple cookie based session management. + # Session data is stored in memcached. The corresponding session key is + # maintained in the cookie. + # You may treat Session::Memcache as you would Session::Pool with the + # following caveats. + # + # * Setting :expire_after to 0 would note to the Memcache server to hang + # onto the session data until it would drop it according to it's own + # specifications. However, the cookie sent to the client would expire + # immediately. + # + # Note that memcache does drop data before it may be listed to expire. For + # a full description of behaviour, please see memcache's documentation. + + class Memcache < Abstract::ID + attr_reader :mutex, :pool + DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \ + :namespace => 'rack:session', + :memcache_server => 'localhost:11211' + + def initialize(app, options={}) + super + + @mutex = Mutex.new + mserv = @default_options[:memcache_server] + mopts = @default_options. + reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k } + @pool = MemCache.new mserv, mopts + unless @pool.active? and @pool.servers.any?{|c| c.alive? } + raise 'No memcache servers' + end + end + + def generate_sid + loop do + sid = super + break sid unless @pool.get(sid, true) + end + end + + def get_session(env, session_id) + @mutex.lock if env['rack.multithread'] + unless session_id and session = @pool.get(session_id) + session_id, session = generate_sid, {} + unless /^STORED/ =~ @pool.add(session_id, session) + raise "Session collision on '#{session_id.inspect}'" + end + end + session.instance_variable_set '@old', @pool.get(session_id, true) + return [session_id, session] + rescue MemCache::MemCacheError, Errno::ECONNREFUSED + # MemCache server cannot be contacted + warn "#{self} is unable to find memcached server." + warn $!.inspect + return [ nil, {} ] + ensure + @mutex.unlock if @mutex.locked? + end + + def set_session(env, session_id, new_session, options) + expiry = options[:expire_after] + expiry = expiry.nil? ? 0 : expiry + 1 + + @mutex.lock if env['rack.multithread'] + if options[:renew] or options[:drop] + @pool.delete session_id + return false if options[:drop] + session_id = generate_sid + @pool.add session_id, {} # so we don't worry about cache miss on #set + end + + session = @pool.get(session_id) || {} + old_session = new_session.instance_variable_get '@old' + old_session = old_session ? Marshal.load(old_session) : {} + + unless Hash === old_session and Hash === new_session + env['rack.errors']. + puts 'Bad old_session or new_session sessions provided.' + else # merge sessions + # alterations are either update or delete, making as few changes as + # possible to prevent possible issues. + + # removed keys + delete = old_session.keys - new_session.keys + if $VERBOSE and not delete.empty? + env['rack.errors']. + puts "//@#{session_id}: delete #{delete*','}" + end + delete.each{|k| session.delete k } + + # added or altered keys + update = new_session.keys. + select{|k| new_session[k] != old_session[k] } + if $VERBOSE and not update.empty? + env['rack.errors'].puts "//@#{session_id}: update #{update*','}" + end + update.each{|k| session[k] = new_session[k] } + end + + @pool.set session_id, session, expiry + return session_id + rescue MemCache::MemCacheError, Errno::ECONNREFUSED + # MemCache server cannot be contacted + warn "#{self} is unable to find memcached server." + warn $!.inspect + return false + ensure + @mutex.unlock if @mutex.locked? + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/session/pool.rb b/vendor/gems/rack-1.1.0/lib/rack/session/pool.rb new file mode 100644 index 0000000..b3f8bd7 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/session/pool.rb @@ -0,0 +1,100 @@ +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net +# THANKS: +# apeiros, for session id generation, expiry setup, and threadiness +# sergio, threadiness and bugreps + +require 'rack/session/abstract/id' +require 'thread' + +module Rack + module Session + # Rack::Session::Pool provides simple cookie based session management. + # Session data is stored in a hash held by @pool. + # In the context of a multithreaded environment, sessions being + # committed to the pool is done in a merging manner. + # + # The :drop option is available in rack.session.options if you wish to + # explicitly remove the session from the session cache. + # + # Example: + # myapp = MyRackApp.new + # sessioned = Rack::Session::Pool.new(myapp, + # :domain => 'foo.com', + # :expire_after => 2592000 + # ) + # Rack::Handler::WEBrick.run sessioned + + class Pool < Abstract::ID + attr_reader :mutex, :pool + DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false + + def initialize(app, options={}) + super + @pool = Hash.new + @mutex = Mutex.new + end + + def generate_sid + loop do + sid = super + break sid unless @pool.key? sid + end + end + + def get_session(env, sid) + session = @pool[sid] if sid + @mutex.lock if env['rack.multithread'] + unless sid and session + env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil? + session = {} + sid = generate_sid + @pool.store sid, session + end + session.instance_variable_set('@old', {}.merge(session)) + return [sid, session] + ensure + @mutex.unlock if env['rack.multithread'] + end + + def set_session(env, session_id, new_session, options) + @mutex.lock if env['rack.multithread'] + session = @pool[session_id] + if options[:renew] or options[:drop] + @pool.delete session_id + return false if options[:drop] + session_id = generate_sid + @pool.store session_id, 0 + end + old_session = new_session.instance_variable_get('@old') || {} + session = merge_sessions session_id, old_session, new_session, session + @pool.store session_id, session + return session_id + rescue + warn "#{new_session.inspect} has been lost." + warn $!.inspect + ensure + @mutex.unlock if env['rack.multithread'] + end + + private + + def merge_sessions sid, old, new, cur=nil + cur ||= {} + unless Hash === old and Hash === new + warn 'Bad old or new sessions provided.' + return cur + end + + delete = old.keys - new.keys + warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty? + delete.each{|k| cur.delete k } + + update = new.keys.select{|k| new[k] != old[k] } + warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty? + update.each{|k| cur[k] = new[k] } + + cur + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/showexceptions.rb b/vendor/gems/rack-1.1.0/lib/rack/showexceptions.rb new file mode 100644 index 0000000..697bc41 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/showexceptions.rb @@ -0,0 +1,349 @@ +require 'ostruct' +require 'erb' +require 'rack/request' +require 'rack/utils' + +module Rack + # Rack::ShowExceptions catches all exceptions raised from the app it + # wraps. It shows a useful backtrace with the sourcefile and + # clickable context, the whole Rack environment and the request + # data. + # + # Be careful when you use this on public-facing sites as it could + # reveal information helpful to attackers. + + class ShowExceptions + CONTEXT = 7 + + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + @app.call(env) + rescue StandardError, LoadError, SyntaxError => e + backtrace = pretty(env, e) + [500, + {"Content-Type" => "text/html", + "Content-Length" => backtrace.join.size.to_s}, + backtrace] + end + + def pretty(env, exception) + req = Rack::Request.new(env) + path = (req.script_name + req.path_info).squeeze("/") + + frames = exception.backtrace.map { |line| + frame = OpenStruct.new + if line =~ /(.*?):(\d+)(:in `(.*)')?/ + frame.filename = $1 + frame.lineno = $2.to_i + frame.function = $4 + + begin + lineno = frame.lineno-1 + lines = ::File.readlines(frame.filename) + frame.pre_context_lineno = [lineno-CONTEXT, 0].max + frame.pre_context = lines[frame.pre_context_lineno...lineno] + frame.context_line = lines[lineno].chomp + frame.post_context_lineno = [lineno+CONTEXT, lines.size].min + frame.post_context = lines[lineno+1..frame.post_context_lineno] + rescue + end + + frame + else + nil + end + }.compact + + env["rack.errors"].puts "#{exception.class}: #{exception.message}" + env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l } + env["rack.errors"].flush + + [@template.result(binding)] + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + +# adapted from Django +# Copyright (c) 2005, the Lawrence Journal-World +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +TEMPLATE = <<'HTML' + + + + + + <%=h exception.class %> at <%=h path %> + + + + + +
+

<%=h exception.class %> at <%=h path %>

+

<%=h exception.message %>

+ + + + + + +
Ruby<%=h frames.first.filename %>: in <%=h frames.first.function %>, line <%=h frames.first.lineno %>
Web<%=h req.request_method %> <%=h(req.host + path)%>
+ +

Jump to:

+ +
+ +
+

Traceback (innermost first)

+
    +<% frames.each { |frame| %> +
  • + <%=h frame.filename %>: in <%=h frame.function %> + + <% if frame.context_line %> +
    + <% if frame.pre_context %> +
      + <% frame.pre_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> + +
      +
    1. <%=h frame.context_line %>...
    + + <% if frame.post_context %> +
      + <% frame.post_context.each { |line| %> +
    1. <%=h line %>
    2. + <% } %> +
    + <% end %> +
    + <% end %> +
  • +<% } %> +
+
+ +
+

Request information

+ +

GET

+ <% unless req.GET.empty? %> + + + + + + + + + <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No GET data.

+ <% end %> + +

POST

+ <% unless req.POST.empty? %> + + + + + + + + + <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No POST data.

+ <% end %> + + + + <% unless req.cookies.empty? %> + + + + + + + + + <% req.cookies.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val.inspect %>
+ <% else %> +

No cookie data.

+ <% end %> + +

Rack ENV

+ + + + + + + + + <% env.sort_by { |k, v| k.to_s }.each { |key, val| %> + + + + + <% } %> + +
VariableValue
<%=h key %>
<%=h val %>
+ +
+ +
+

+ You're seeing this error because you use Rack::ShowExceptions. +

+
+ + + +HTML + + # :startdoc: + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/showstatus.rb b/vendor/gems/rack-1.1.0/lib/rack/showstatus.rb new file mode 100644 index 0000000..28258c7 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/showstatus.rb @@ -0,0 +1,106 @@ +require 'erb' +require 'rack/request' +require 'rack/utils' + +module Rack + # Rack::ShowStatus catches all empty responses the app it wraps and + # replaces them with a site explaining the error. + # + # Additional details can be put into rack.showstatus.detail + # and will be shown as HTML. If such details exist, the error page + # is always rendered, even if the reply was not empty. + + class ShowStatus + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + empty = headers['Content-Length'].to_i <= 0 + + # client or server error, or explicit message + if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"] + req = Rack::Request.new(env) + message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s + detail = env["rack.showstatus.detail"] || message + body = @template.result(binding) + size = Rack::Utils.bytesize(body) + [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] + else + [status, headers, body] + end + end + + def h(obj) # :nodoc: + case obj + when String + Utils.escape_html(obj) + else + Utils.escape_html(obj.inspect) + end + end + + # :stopdoc: + +# adapted from Django +# Copyright (c) 2005, the Lawrence Journal-World +# Used under the modified BSD license: +# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5 +TEMPLATE = <<'HTML' + + + + + <%=h message %> at <%=h req.script_name + req.path_info %> + + + + +
+

<%=h message %> (<%= status.to_i %>)

+ + + + + + + + + +
Request Method:<%=h req.request_method %>
Request URL:<%=h req.url %>
+
+
+

<%= detail %>

+
+ +
+

+ You're seeing this error because you use Rack::ShowStatus. +

+
+ + +HTML + + # :startdoc: + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/static.rb b/vendor/gems/rack-1.1.0/lib/rack/static.rb new file mode 100644 index 0000000..168e8f8 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/static.rb @@ -0,0 +1,38 @@ +module Rack + + # The Rack::Static middleware intercepts requests for static files + # (javascript files, images, stylesheets, etc) based on the url prefixes + # passed in the options, and serves them using a Rack::File object. This + # allows a Rack stack to serve both static and dynamic content. + # + # Examples: + # use Rack::Static, :urls => ["/media"] + # will serve all requests beginning with /media from the "media" folder + # located in the current directory (ie media/*). + # + # use Rack::Static, :urls => ["/css", "/images"], :root => "public" + # will serve all requests beginning with /css or /images from the folder + # "public" in the current directory (ie public/css/* and public/images/*) + + class Static + + def initialize(app, options={}) + @app = app + @urls = options[:urls] || ["/favicon.ico"] + root = options[:root] || Dir.pwd + @file_server = Rack::File.new(root) + end + + def call(env) + path = env["PATH_INFO"] + can_serve = @urls.any? { |url| path.index(url) == 0 } + + if can_serve + @file_server.call(env) + else + @app.call(env) + end + end + + end +end diff --git a/vendor/gems/rack-1.1.0/lib/rack/urlmap.rb b/vendor/gems/rack-1.1.0/lib/rack/urlmap.rb new file mode 100644 index 0000000..b699d35 --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/urlmap.rb @@ -0,0 +1,56 @@ +module Rack + # Rack::URLMap takes a hash mapping urls or paths to apps, and + # dispatches accordingly. Support for HTTP/1.1 host names exists if + # the URLs start with http:// or https://. + # + # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part + # relevant for dispatch is in the SCRIPT_NAME, and the rest in the + # PATH_INFO. This should be taken care of when you need to + # reconstruct the URL in order to create links. + # + # URLMap dispatches in such a way that the longest paths are tried + # first, since they are most specific. + + class URLMap + def initialize(map = {}) + remap(map) + end + + def remap(map) + @mapping = map.map { |location, app| + if location =~ %r{\Ahttps?://(.*?)(/.*)} + host, location = $1, $2 + else + host = nil + end + + unless location[0] == ?/ + raise ArgumentError, "paths need to start with /" + end + location = location.chomp('/') + match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n') + + [host, location, match, app] + }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first + end + + def call(env) + path = env["PATH_INFO"].to_s + script_name = env['SCRIPT_NAME'] + hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') + @mapping.each { |host, location, match, app| + next unless (hHost == host || sName == host \ + || (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) + next unless path =~ match && rest = $1 + next unless rest.empty? || rest[0] == ?/ + + return app.call( + env.merge( + 'SCRIPT_NAME' => (script_name + location), + 'PATH_INFO' => rest)) + } + [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] + end + end +end + diff --git a/vendor/gems/rack-1.1.0/lib/rack/utils.rb b/vendor/gems/rack-1.1.0/lib/rack/utils.rb new file mode 100644 index 0000000..68fd6ac --- /dev/null +++ b/vendor/gems/rack-1.1.0/lib/rack/utils.rb @@ -0,0 +1,620 @@ +# -*- encoding: binary -*- + +require 'set' +require 'tempfile' + +module Rack + # Rack::Utils contains a grab-bag of useful methods for writing web + # applications adopted from all kinds of Ruby libraries. + + module Utils + # Performs URI escaping so that you can construct proper + # query strings faster. Use this rather than the cgi.rb + # version since it's faster. (Stolen from Camping). + def escape(s) + s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { + '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase + }.tr(' ', '+') + end + module_function :escape + + # Unescapes a URI escaped string. (Stolen from Camping). + def unescape(s) + s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){ + [$1.delete('%')].pack('H*') + } + end + module_function :unescape + + DEFAULT_SEP = /[&;] */n + + # Stolen from Mongrel, with some small modifications: + # Parses a query string by breaking it up at the '&' + # and ';' characters. You can also use this to parse + # cookies by changing the characters used in the second + # parameter (which defaults to '&;'). + def parse_query(qs, d = nil) + params = {} + + (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map { |x| unescape(x) } + if v =~ /^("|')(.*)\1$/ + v = $2.gsub('\\'+$1, $1) + end + if cur = params[k] + if cur.class == Array + params[k] << v + else + params[k] = [cur, v] + end + else + params[k] = v + end + end + + return params + end + module_function :parse_query + + def parse_nested_query(qs, d = nil) + params = {} + + (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| + k, v = unescape(p).split('=', 2) + normalize_params(params, k, v) + end + + return params + end + module_function :parse_nested_query + + def normalize_params(params, name, v = nil) + if v and v =~ /^("|')(.*)\1$/ + v = $2.gsub('\\'+$1, $1) + end + name =~ %r(\A[\[\]]*([^\[\]]+)\]*) + k = $1 || '' + after = $' || '' + + return if k.empty? + + if after == "" + params[k] = v + elsif after == "[]" + params[k] ||= [] + raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + params[k] << v + elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$) + child_key = $1 + params[k] ||= [] + raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key) + normalize_params(params[k].last, child_key, v) + else + params[k] << normalize_params({}, child_key, v) + end + else + params[k] ||= {} + raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash) + params[k] = normalize_params(params[k], after, v) + end + + return params + end + module_function :normalize_params + + def build_query(params) + params.map { |k, v| + if v.class == Array + build_query(v.map { |x| [k, x] }) + else + "#{escape(k)}=#{escape(v)}" + end + }.join("&") + end + module_function :build_query + + def build_nested_query(value, prefix = nil) + case value + when Array + value.map { |v| + build_nested_query(v, "#{prefix}[]") + }.join("&") + when Hash + value.map { |k, v| + build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k)) + }.join("&") + when String + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{escape(value)}" + else + prefix + end + end + module_function :build_nested_query + + # Escape ampersands, brackets and quotes to their HTML/XML entities. + def escape_html(string) + string.to_s.gsub("&", "&"). + gsub("<", "<"). + gsub(">", ">"). + gsub("'", "'"). + gsub('"', """) + end + module_function :escape_html + + def select_best_encoding(available_encodings, accept_encoding) + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + + expanded_accept_encoding = + accept_encoding.map { |m, q| + if m == "*" + (available_encodings - accept_encoding.map { |m2, _| m2 }).map { |m2| [m2, q] } + else + [[m, q]] + end + }.inject([]) { |mem, list| + mem + list + } + + encoding_candidates = expanded_accept_encoding.sort_by { |_, q| -q }.map { |m, _| m } + + unless encoding_candidates.include?("identity") + encoding_candidates.push("identity") + end + + expanded_accept_encoding.find_all { |m, q| + q == 0.0 + }.each { |m, _| + encoding_candidates.delete(m) + } + + return (encoding_candidates & available_encodings)[0] + end + module_function :select_best_encoding + + def set_cookie_header!(header, key, value) + case value + when Hash + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + # According to RFC 2109, we need dashes here. + # N.B.: cgi.rb uses spaces... + expires = "; expires=" + value[:expires].clone.gmtime. + strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if value[:httponly] + value = value[:value] + end + value = [value] unless Array === value + cookie = escape(key) + "=" + + value.map { |v| escape v }.join("&") + + "#{domain}#{path}#{expires}#{secure}#{httponly}" + + case header["Set-Cookie"] + when Array + header["Set-Cookie"] << cookie + when String + header["Set-Cookie"] = [header["Set-Cookie"], cookie] + when nil + header["Set-Cookie"] = cookie + end + + nil + end + module_function :set_cookie_header! + + def delete_cookie_header!(header, key, value = {}) + unless Array === header["Set-Cookie"] + header["Set-Cookie"] = [header["Set-Cookie"]].compact + end + + header["Set-Cookie"].reject! { |cookie| + cookie =~ /\A#{escape(key)}=/ + } + + set_cookie_header!(header, key, + {:value => '', :path => nil, :domain => nil, + :expires => Time.at(0) }.merge(value)) + + nil + end + module_function :delete_cookie_header! + + # Return the bytesize of String; uses String#length under Ruby 1.8 and + # String#bytesize under 1.9. + if ''.respond_to?(:bytesize) + def bytesize(string) + string.bytesize + end + else + def bytesize(string) + string.size + end + end + module_function :bytesize + + # Context allows the use of a compatible middleware at different points + # in a request handling stack. A compatible middleware must define + # #context which should take the arguments env and app. The first of which + # would be the request environment. The second of which would be the rack + # application that the request would be forwarded to. + class Context + attr_reader :for, :app + + def initialize(app_f, app_r) + raise 'running context does not respond to #context' unless app_f.respond_to? :context + @for, @app = app_f, app_r + end + + def call(env) + @for.context(env, @app) + end + + def recontext(app) + self.class.new(@for, app) + end + + def context(env, app=@app) + recontext(app).call(env) + end + end + + # A case-insensitive Hash that preserves the original case of a + # header when set. + class HeaderHash < Hash + def self.new(hash={}) + HeaderHash === hash ? hash : super(hash) + end + + def initialize(hash={}) + super() + @names = {} + hash.each { |k, v| self[k] = v } + end + + def each + super do |k, v| + yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v) + end + end + + def to_hash + inject({}) do |hash, (k,v)| + if v.respond_to? :to_ary + hash[k] = v.to_ary.join("\n") + else + hash[k] = v + end + hash + end + end + + def [](k) + super(@names[k] ||= @names[k.downcase]) + end + + def []=(k, v) + delete k + @names[k] = @names[k.downcase] = k + super k, v + end + + def delete(k) + canonical = k.downcase + result = super @names.delete(canonical) + @names.delete_if { |name,| name.downcase == canonical } + result + end + + def include?(k) + @names.include?(k) || @names.include?(k.downcase) + end + + alias_method :has_key?, :include? + alias_method :member?, :include? + alias_method :key?, :include? + + def merge!(other) + other.each { |k, v| self[k] = v } + self + end + + def merge(other) + hash = dup + hash.merge! other + end + + def replace(other) + clear + other.each { |k, v| self[k] = v } + self + end + end + + # Every standard HTTP code mapped to the appropriate message. + # Generated with: + # curl -s http://www.iana.org/assignments/http-status-codes | \ + # ruby -ane 'm = /^(\d{3}) +(\S[^\[(]+)/.match($_) and + # puts " #{m[1]} => \x27#{m[2].strip}x27,"' + HTTP_STATUS_CODES = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 426 => 'Upgrade Required', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 510 => 'Not Extended', + } + + # Responses with HTTP status codes that should not have an entity body + STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304) + + SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)| + hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code + hash + } + + def status_code(status) + if status.is_a?(Symbol) + SYMBOL_TO_STATUS_CODE[status] || 500 + else + status.to_i + end + end + module_function :status_code + + # A multipart form data parser, adapted from IOWA. + # + # Usually, Rack::Request#POST takes care of calling this. + + module Multipart + class UploadedFile + # The filename, *not* including the path, of the "uploaded" file + attr_reader :original_filename + + # The content type of the "uploaded" file + attr_accessor :content_type + + def initialize(path, content_type = "text/plain", binary = false) + raise "#{path} file does not exist" unless ::File.exist?(path) + @content_type = content_type + @original_filename = ::File.basename(path) + @tempfile = Tempfile.new(@original_filename) + @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding) + @tempfile.binmode if binary + FileUtils.copy_file(path, @tempfile.path) + end + + def path + @tempfile.path + end + alias_method :local_path, :path + + def method_missing(method_name, *args, &block) #:nodoc: + @tempfile.__send__(method_name, *args, &block) + end + end + + EOL = "\r\n" + MULTIPART_BOUNDARY = "AaB03x" + + def self.parse_multipart(env) + unless env['CONTENT_TYPE'] =~ + %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n + nil + else + boundary = "--#{$1}" + + params = {} + buf = "" + content_length = env['CONTENT_LENGTH'].to_i + input = env['rack.input'] + input.rewind + + boundary_size = Utils.bytesize(boundary) + EOL.size + bufsize = 16384 + + content_length -= boundary_size + + read_buffer = '' + + status = input.read(boundary_size, read_buffer) + raise EOFError, "bad content body" unless status == boundary + EOL + + rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n + + loop { + head = nil + body = '' + filename = content_type = name = nil + + until head && buf =~ rx + if !head && i = buf.index(EOL+EOL) + head = buf.slice!(0, i+2) # First \r\n + buf.slice!(0, 2) # Second \r\n + + filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1] + content_type = head[/Content-Type: (.*)#{EOL}/ni, 1] + name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1] + + if content_type || filename + body = Tempfile.new("RackMultipart") + body.binmode if body.respond_to?(:binmode) + end + + next + end + + # Save the read body part. + if head && (boundary_size+4 < buf.size) + body << buf.slice!(0, buf.size - (boundary_size+4)) + end + + c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer) + raise EOFError, "bad content body" if c.nil? || c.empty? + buf << c + content_length -= c.size + end + + # Save the rest. + if i = buf.index(rx) + body << buf.slice!(0, i) + buf.slice!(0, boundary_size+2) + + content_length = -1 if $1 == "--" + end + + if filename == "" + # filename is blank which means no file has been selected + data = nil + elsif filename + body.rewind + + # Take the basename of the upload's original filename. + # This handles the full Windows paths given by Internet Explorer + # (and perhaps other broken user agents) without affecting + # those which give the lone filename. + filename =~ /^(?:.*[:\\\/])?(.*)/m + filename = $1 + + data = {:filename => filename, :type => content_type, + :name => name, :tempfile => body, :head => head} + elsif !filename && content_type + body.rewind + + # Generic multipart cases, not coming from a form + data = {:type => content_type, + :name => name, :tempfile => body, :head => head} + else + data = body + end + + Utils.normalize_params(params, name, data) unless data.nil? + + # break if we're at the end of a buffer, but not if it is the end of a field + break if (buf.empty? && $1 != EOL) || content_length == -1 + } + + input.rewind + + params + end + end + + def self.build_multipart(params, first = true) + if first + unless params.is_a?(Hash) + raise ArgumentError, "value must be a Hash" + end + + multipart = false + query = lambda { |value| + case value + when Array + value.each(&query) + when Hash + value.values.each(&query) + when UploadedFile + multipart = true + end + } + params.values.each(&query) + return nil unless multipart + end + + flattened_params = Hash.new + + params.each do |key, value| + k = first ? key.to_s : "[#{key}]" + + case value + when Array + value.map { |v| + build_multipart(v, false).each { |subkey, subvalue| + flattened_params["#{k}[]#{subkey}"] = subvalue + } + } + when Hash + build_multipart(value, false).each { |subkey, subvalue| + flattened_params[k + subkey] = subvalue + } + else + flattened_params[k] = value + end + end + + if first + flattened_params.map { |name, file| + if file.respond_to?(:original_filename) + ::File.open(file.path, "rb") do |f| + f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding) +<<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r +Content-Type: #{file.content_type}\r +Content-Length: #{::File.stat(file.path).size}\r +\r +#{f.read}\r +EOF + end + else +<<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{name}"\r +\r +#{file}\r +EOF + end + }.join + "--#{MULTIPART_BOUNDARY}--\r" + else + flattened_params + end + end + end + end +end diff --git a/vendor/gems/rack-1.1.0/rack.gemspec b/vendor/gems/rack-1.1.0/rack.gemspec new file mode 100644 index 0000000..e28b9bb --- /dev/null +++ b/vendor/gems/rack-1.1.0/rack.gemspec @@ -0,0 +1,38 @@ +Gem::Specification.new do |s| + s.name = "rack" + s.version = "1.1.0" + s.platform = Gem::Platform::RUBY + s.summary = "a modular Ruby webserver interface" + + s.description = <<-EOF +Rack provides minimal, modular and adaptable interface for developing +web applications in Ruby. By wrapping HTTP requests and responses in +the simplest way possible, it unifies and distills the API for web +servers, web frameworks, and software in between (the so-called +middleware) into a single method call. + +Also see http://rack.rubyforge.org. +EOF + + s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] + + %w(COPYING KNOWN-ISSUES rack.gemspec RDOX README SPEC) + s.bindir = 'bin' + s.executables << 'rackup' + s.require_path = 'lib' + s.has_rdoc = true + s.extra_rdoc_files = ['README', 'SPEC', 'KNOWN-ISSUES'] + s.test_files = Dir['test/{test,spec}_*.rb'] + + s.author = 'Christian Neukirchen' + s.email = 'chneukirchen@gmail.com' + s.homepage = 'http://rack.rubyforge.org' + s.rubyforge_project = 'rack' + + s.add_development_dependency 'test-spec' + + s.add_development_dependency 'camping' + s.add_development_dependency 'fcgi' + s.add_development_dependency 'memcache-client' + s.add_development_dependency 'mongrel' + s.add_development_dependency 'thin' +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_auth_basic.rb b/vendor/gems/rack-1.1.0/test/spec_rack_auth_basic.rb new file mode 100644 index 0000000..0176efc --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_auth_basic.rb @@ -0,0 +1,73 @@ +require 'test/spec' + +require 'rack/auth/basic' +require 'rack/mock' + +context 'Rack::Auth::Basic' do + + def realm + 'WallysWorld' + end + + def unprotected_app + lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] } + end + + def protected_app + app = Rack::Auth::Basic.new(unprotected_app) { |username, password| 'Boss' == username } + app.realm = realm + app + end + + setup do + @request = Rack::MockRequest.new(protected_app) + end + + def request_with_basic_auth(username, password, &block) + request 'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:#{password}"].pack("m*"), &block + end + + def request(headers = {}) + yield @request.get('/', headers) + end + + def assert_basic_auth_challenge(response) + response.should.be.a.client_error + response.status.should.equal 401 + response.should.include 'WWW-Authenticate' + response.headers['WWW-Authenticate'].should =~ /Basic realm="#{Regexp.escape(realm)}"/ + response.body.should.be.empty + end + + specify 'should challenge correctly when no credentials are specified' do + request do |response| + assert_basic_auth_challenge response + end + end + + specify 'should rechallenge if incorrect credentials are specified' do + request_with_basic_auth 'joe', 'password' do |response| + assert_basic_auth_challenge response + end + end + + specify 'should return application output if correct credentials are specified' do + request_with_basic_auth 'Boss', 'password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Boss' + end + end + + specify 'should return 400 Bad Request if different auth scheme used' do + request 'HTTP_AUTHORIZATION' => 'Digest params' do |response| + response.should.be.a.client_error + response.status.should.equal 400 + response.should.not.include 'WWW-Authenticate' + end + end + + specify 'realm as optional constructor arg' do + app = Rack::Auth::Basic.new(unprotected_app, realm) { true } + assert_equal realm, app.realm + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_auth_digest.rb b/vendor/gems/rack-1.1.0/test/spec_rack_auth_digest.rb new file mode 100644 index 0000000..a980acc --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_auth_digest.rb @@ -0,0 +1,226 @@ +require 'test/spec' + +require 'rack/auth/digest/md5' +require 'rack/mock' + +context 'Rack::Auth::Digest::MD5' do + + def realm + 'WallysWorld' + end + + def unprotected_app + lambda do |env| + [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] + end + end + + def protected_app + app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username| + { 'Alice' => 'correct-password' }[username] + end + app.realm = realm + app.opaque = 'this-should-be-secret' + app + end + + def protected_app_with_hashed_passwords + app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username| + username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil + end + app.realm = realm + app.opaque = 'this-should-be-secret' + app.passwords_hashed = true + app + end + + def partially_protected_app + Rack::URLMap.new({ + '/' => unprotected_app, + '/protected' => protected_app + }) + end + + def protected_app_with_method_override + Rack::MethodOverride.new(protected_app) + end + + setup do + @request = Rack::MockRequest.new(protected_app) + end + + def request(method, path, headers = {}, &block) + response = @request.request(method, path, headers) + block.call(response) if block + return response + end + + class MockDigestRequest + def initialize(params) + @params = params + end + def method_missing(sym) + if @params.has_key? k = sym.to_s + return @params[k] + end + super + end + def method + @params['method'] + end + def response(password) + Rack::Auth::Digest::MD5.new(nil).send :digest, self, password + end + end + + def request_with_digest_auth(method, path, username, password, options = {}, &block) + request_options = {} + request_options[:input] = options.delete(:input) if options.include? :input + + response = request(method, path, request_options) + + return response unless response.status == 401 + + if wait = options.delete(:wait) + sleep wait + end + + challenge = response['WWW-Authenticate'].split(' ', 2).last + + params = Rack::Auth::Digest::Params.parse(challenge) + + params['username'] = username + params['nc'] = '00000001' + params['cnonce'] = 'nonsensenonce' + params['uri'] = path + + params['method'] = method + + params.update options + + params['response'] = MockDigestRequest.new(params).response(password) + + request(method, path, request_options.merge('HTTP_AUTHORIZATION' => "Digest #{params}"), &block) + end + + def assert_digest_auth_challenge(response) + response.should.be.a.client_error + response.status.should.equal 401 + response.should.include 'WWW-Authenticate' + response.headers['WWW-Authenticate'].should =~ /^Digest / + response.body.should.be.empty + end + + def assert_bad_request(response) + response.should.be.a.client_error + response.status.should.equal 400 + response.should.not.include 'WWW-Authenticate' + end + + specify 'should challenge when no credentials are specified' do + request 'GET', '/' do |response| + assert_digest_auth_challenge response + end + end + + specify 'should return application output if correct credentials given' do + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + end + end + + specify 'should return application output if correct credentials given (hashed passwords)' do + @request = Rack::MockRequest.new(protected_app_with_hashed_passwords) + + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + end + end + + specify 'should rechallenge if incorrect username given' do + request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response| + assert_digest_auth_challenge response + end + end + + specify 'should rechallenge if incorrect password given' do + request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response| + assert_digest_auth_challenge response + end + end + + specify 'should rechallenge with stale parameter if nonce is stale' do + begin + Rack::Auth::Digest::Nonce.time_limit = 1 + + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 2 do |response| + assert_digest_auth_challenge response + response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/ + end + ensure + Rack::Auth::Digest::Nonce.time_limit = nil + end + end + + specify 'should return 400 Bad Request if incorrect qop given' do + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response| + assert_bad_request response + end + end + + specify 'should return 400 Bad Request if incorrect uri given' do + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response| + assert_bad_request response + end + end + + specify 'should return 400 Bad Request if different auth scheme used' do + request 'GET', '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response| + assert_bad_request response + end + end + + specify 'should not require credentials for unprotected path' do + @request = Rack::MockRequest.new(partially_protected_app) + request 'GET', '/' do |response| + response.should.be.ok + end + end + + specify 'should challenge when no credentials are specified for protected path' do + @request = Rack::MockRequest.new(partially_protected_app) + request 'GET', '/protected' do |response| + assert_digest_auth_challenge response + end + end + + specify 'should return application output if correct credentials given for protected path' do + @request = Rack::MockRequest.new(partially_protected_app) + request_with_digest_auth 'GET', '/protected', 'Alice', 'correct-password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + end + end + + specify 'should return application output if correct credentials given for POST' do + request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + end + end + + specify 'should return application output if correct credentials given for PUT (using method override of POST)' do + @request = Rack::MockRequest.new(protected_app_with_method_override) + request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + end + end + + specify 'realm as optional constructor arg' do + app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true } + assert_equal realm, app.realm + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_builder.rb b/vendor/gems/rack-1.1.0/test/spec_rack_builder.rb new file mode 100644 index 0000000..3fad981 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_builder.rb @@ -0,0 +1,84 @@ +require 'test/spec' + +require 'rack/builder' +require 'rack/mock' +require 'rack/showexceptions' +require 'rack/auth/basic' + +context "Rack::Builder" do + specify "chains apps by default" do + app = Rack::Builder.new do + use Rack::ShowExceptions + run lambda { |env| raise "bzzzt" } + end.to_app + + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + end + + specify "has implicit #to_app" do + app = Rack::Builder.new do + use Rack::ShowExceptions + run lambda { |env| raise "bzzzt" } + end + + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + end + + specify "supports blocks on use" do + app = Rack::Builder.new do + use Rack::ShowExceptions + use Rack::Auth::Basic do |username, password| + 'secret' == password + end + + run lambda { |env| [200, {}, ['Hi Boss']] } + end + + response = Rack::MockRequest.new(app).get("/") + response.should.be.client_error + response.status.should.equal 401 + + # with auth... + response = Rack::MockRequest.new(app).get("/", + 'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*")) + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Boss' + end + + specify "has explicit #to_app" do + app = Rack::Builder.app do + use Rack::ShowExceptions + run lambda { |env| raise "bzzzt" } + end + + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + Rack::MockRequest.new(app).get("/").should.be.server_error + end + + specify "apps are initialized once" do + app = Rack::Builder.new do + class AppClass + def initialize + @called = 0 + end + def call(env) + raise "bzzzt" if @called > 0 + @called += 1 + [200, {'Content-Type' => 'text/plain'}, ['OK']] + end + end + + use Rack::ShowExceptions + run AppClass.new + end + + Rack::MockRequest.new(app).get("/").status.should.equal 200 + Rack::MockRequest.new(app).get("/").should.be.server_error + end + +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_camping.rb b/vendor/gems/rack-1.1.0/test/spec_rack_camping.rb new file mode 100644 index 0000000..bed1171 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_camping.rb @@ -0,0 +1,51 @@ +require 'test/spec' +require 'stringio' +require 'uri' + +begin + require 'rack/mock' + + $-w, w = nil, $-w # yuck + require 'camping' + require 'rack/adapter/camping' + + Camping.goes :CampApp + module CampApp + module Controllers + class HW < R('/') + def get + @headers["X-Served-By"] = URI("http://rack.rubyforge.org") + "Camping works!" + end + + def post + "Data: #{input.foo}" + end + end + end + end + $-w = w + + context "Rack::Adapter::Camping" do + specify "works with GET" do + res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)). + get("/") + + res.should.be.ok + res["Content-Type"].should.equal "text/html" + res["X-Served-By"].should.equal "http://rack.rubyforge.org" + + res.body.should.equal "Camping works!" + end + + specify "works with POST" do + res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)). + post("/", :input => "foo=bar") + + res.should.be.ok + res.body.should.equal "Data: bar" + end + end +rescue LoadError + $stderr.puts "Skipping Rack::Adapter::Camping tests (Camping is required). `gem install camping` and try again." +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_cascade.rb b/vendor/gems/rack-1.1.0/test/spec_rack_cascade.rb new file mode 100644 index 0000000..cf3c29b --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_cascade.rb @@ -0,0 +1,48 @@ +require 'test/spec' + +require 'rack/cascade' +require 'rack/mock' + +require 'rack/urlmap' +require 'rack/file' + +context "Rack::Cascade" do + docroot = File.expand_path(File.dirname(__FILE__)) + app1 = Rack::File.new(docroot) + + app2 = Rack::URLMap.new("/crash" => lambda { |env| raise "boom" }) + + app3 = Rack::URLMap.new("/foo" => lambda { |env| + [200, { "Content-Type" => "text/plain"}, [""]]}) + + specify "should dispatch onward on 404 by default" do + cascade = Rack::Cascade.new([app1, app2, app3]) + Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok + Rack::MockRequest.new(cascade).get("/foo").should.be.ok + Rack::MockRequest.new(cascade).get("/toobad").should.be.not_found + Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden + end + + specify "should dispatch onward on whatever is passed" do + cascade = Rack::Cascade.new([app1, app2, app3], [404, 403]) + Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found + end + + specify "should return 404 if empty" do + Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found + end + + specify "should append new app" do + cascade = Rack::Cascade.new([], [404, 403]) + Rack::MockRequest.new(cascade).get('/').should.be.not_found + cascade << app2 + Rack::MockRequest.new(cascade).get('/cgi/test').should.be.not_found + Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.not_found + cascade << app1 + Rack::MockRequest.new(cascade).get('/cgi/test').should.be.ok + Rack::MockRequest.new(cascade).get('/cgi/../bla').should.be.forbidden + Rack::MockRequest.new(cascade).get('/foo').should.be.not_found + cascade << app3 + Rack::MockRequest.new(cascade).get('/foo').should.be.ok + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_cgi.rb b/vendor/gems/rack-1.1.0/test/spec_rack_cgi.rb new file mode 100644 index 0000000..59500cd --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_cgi.rb @@ -0,0 +1,89 @@ +require 'test/spec' +require 'testrequest' + +context "Rack::Handler::CGI" do + include TestRequest::Helpers + + setup do + @host = '0.0.0.0' + @port = 9203 + end + + # Keep this first. + specify "startup" do + $pid = fork { + Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi")) + exec "lighttpd -D -f lighttpd.conf" + } + end + + specify "should respond" do + sleep 1 + lambda { + GET("/test") + }.should.not.raise + end + + specify "should be a lighttpd" do + GET("/test") + status.should.be 200 + response["SERVER_SOFTWARE"].should =~ /lighttpd/ + response["HTTP_VERSION"].should.equal "HTTP/1.1" + response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" + response["SERVER_PORT"].should.equal @port.to_s + response["SERVER_NAME"].should =~ @host + end + + specify "should have rack headers" do + GET("/test") + response["rack.version"].should.equal [1,1] + response["rack.multithread"].should.be false + response["rack.multiprocess"].should.be true + response["rack.run_once"].should.be true + end + + specify "should have CGI headers on GET" do + GET("/test") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "" + response["QUERY_STRING"].should.equal "" + response["test.postdata"].should.equal "" + + GET("/test/foo?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "/foo" + response["QUERY_STRING"].should.equal "quux=1" + end + + specify "should have CGI headers on POST" do + POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) + status.should.equal 200 + response["REQUEST_METHOD"].should.equal "POST" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["QUERY_STRING"].should.equal "" + response["HTTP_X_TEST_HEADER"].should.equal "42" + response["test.postdata"].should.equal "rack-form-data=23" + end + + specify "should support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + specify "should set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + # Keep this last. + specify "shutdown" do + Process.kill 15, $pid + Process.wait($pid).should.equal $pid + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_chunked.rb b/vendor/gems/rack-1.1.0/test/spec_rack_chunked.rb new file mode 100644 index 0000000..39eea48 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_chunked.rb @@ -0,0 +1,62 @@ +require 'rack/mock' +require 'rack/chunked' +require 'rack/utils' + +context "Rack::Chunked" do + + before do + @env = Rack::MockRequest. + env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET') + end + + specify 'chunks responses with no Content-Length' do + app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] } + response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env)) + response.headers.should.not.include 'Content-Length' + response.headers['Transfer-Encoding'].should.equal 'chunked' + response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n" + end + + specify 'chunks empty bodies properly' do + app = lambda { |env| [200, {}, []] } + response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env)) + response.headers.should.not.include 'Content-Length' + response.headers['Transfer-Encoding'].should.equal 'chunked' + response.body.should.equal "0\r\n\r\n" + end + + specify 'does not modify response when Content-Length header present' do + app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] } + status, headers, body = Rack::Chunked.new(app).call(@env) + status.should.equal 200 + headers.should.not.include 'Transfer-Encoding' + headers.should.include 'Content-Length' + body.join.should.equal 'Hello World!' + end + + specify 'does not modify response when client is HTTP/1.0' do + app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] } + @env['HTTP_VERSION'] = 'HTTP/1.0' + status, headers, body = Rack::Chunked.new(app).call(@env) + status.should.equal 200 + headers.should.not.include 'Transfer-Encoding' + body.join.should.equal 'Hello World!' + end + + specify 'does not modify response when Transfer-Encoding header already present' do + app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] } + status, headers, body = Rack::Chunked.new(app).call(@env) + status.should.equal 200 + headers['Transfer-Encoding'].should.equal 'identity' + body.join.should.equal 'Hello World!' + end + + [100, 204, 304].each do |status_code| + specify "does not modify response when status code is #{status_code}" do + app = lambda { |env| [status_code, {}, []] } + status, headers, body = Rack::Chunked.new(app).call(@env) + status.should.equal status_code + headers.should.not.include 'Transfer-Encoding' + end + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_commonlogger.rb b/vendor/gems/rack-1.1.0/test/spec_rack_commonlogger.rb new file mode 100644 index 0000000..46a72e8 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_commonlogger.rb @@ -0,0 +1,61 @@ +require 'test/spec' +require 'stringio' + +require 'rack/commonlogger' +require 'rack/lobster' +require 'rack/mock' + +context "Rack::CommonLogger" do + app = lambda { |env| + [200, + {"Content-Type" => "text/html", "Content-Length" => length.to_s}, + [obj]]} + app_without_length = lambda { |env| + [200, + {"Content-Type" => "text/html"}, + []]} + app_with_zero_length = lambda { |env| + [200, + {"Content-Type" => "text/html", "Content-Length" => "0"}, + []]} + + specify "should log to rack.errors by default" do + res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/") + + res.errors.should.not.be.empty + res.errors.should =~ /"GET \/ " 200 #{length} / + end + + specify "should log to anything with +write+" do + log = StringIO.new + res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") + + log.string.should =~ /"GET \/ " 200 #{length} / + end + + specify "should log - content length if header is missing" do + res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/") + + res.errors.should.not.be.empty + res.errors.should =~ /"GET \/ " 200 - / + end + + specify "should log - content length if header is zero" do + res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/") + + res.errors.should.not.be.empty + res.errors.should =~ /"GET \/ " 200 - / + end + + def length + self.class.length + end + + def self.length + 123 + end + + def self.obj + "hello world" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_conditionalget.rb b/vendor/gems/rack-1.1.0/test/spec_rack_conditionalget.rb new file mode 100644 index 0000000..ca34cc9 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_conditionalget.rb @@ -0,0 +1,41 @@ +require 'test/spec' +require 'time' + +require 'rack/mock' +require 'rack/conditionalget' + +context "Rack::ConditionalGet" do + specify "should set a 304 status and truncate body when If-Modified-Since hits" do + timestamp = Time.now.httpdate + app = Rack::ConditionalGet.new(lambda { |env| + [200, {'Last-Modified'=>timestamp}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp) + + response.status.should.equal 304 + response.body.should.be.empty + end + + specify "should set a 304 status and truncate body when If-None-Match hits" do + app = Rack::ConditionalGet.new(lambda { |env| + [200, {'Etag'=>'1234'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_NONE_MATCH' => '1234') + + response.status.should.equal 304 + response.body.should.be.empty + end + + specify "should not affect non-GET/HEAD requests" do + app = Rack::ConditionalGet.new(lambda { |env| + [200, {'Etag'=>'1234'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + post("/", 'HTTP_IF_NONE_MATCH' => '1234') + + response.status.should.equal 200 + response.body.should.equal 'TEST' + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_config.rb b/vendor/gems/rack-1.1.0/test/spec_rack_config.rb new file mode 100644 index 0000000..a508ea4 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_config.rb @@ -0,0 +1,24 @@ +require 'test/spec' +require 'rack/mock' +require 'rack/builder' +require 'rack/content_length' +require 'rack/config' + +context "Rack::Config" do + + specify "should accept a block that modifies the environment" do + app = Rack::Builder.new do + use Rack::Lint + use Rack::ContentLength + use Rack::Config do |env| + env['greeting'] = 'hello' + end + run lambda { |env| + [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']] + } + end + response = Rack::MockRequest.new(app).get('/') + response.body.should.equal('hello') + end + +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_content_length.rb b/vendor/gems/rack-1.1.0/test/spec_rack_content_length.rb new file mode 100644 index 0000000..7db9345 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_content_length.rb @@ -0,0 +1,43 @@ +require 'rack/mock' +require 'rack/content_length' + +context "Rack::ContentLength" do + specify "sets Content-Length on String bodies if none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.equal '13' + end + + specify "sets Content-Length on Array bodies if none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.equal '13' + end + + specify "does not set Content-Length on variable length bodies" do + body = lambda { "Hello World!" } + def body.each ; yield call ; end + + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.be.nil + end + + specify "does not change Content-Length if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.equal '1' + end + + specify "does not set Content-Length on 304 responses" do + app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.equal nil + end + + specify "does not set Content-Length when Transfer-Encoding is chunked" do + app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] } + response = Rack::ContentLength.new(app).call({}) + response[1]['Content-Length'].should.equal nil + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_content_type.rb b/vendor/gems/rack-1.1.0/test/spec_rack_content_type.rb new file mode 100644 index 0000000..9975b94 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_content_type.rb @@ -0,0 +1,30 @@ +require 'rack/mock' +require 'rack/content_type' + +context "Rack::ContentType" do + specify "sets Content-Type to default text/html if none is set" do + app = lambda { |env| [200, {}, "Hello, World!"] } + status, headers, body = Rack::ContentType.new(app).call({}) + headers['Content-Type'].should.equal 'text/html' + end + + specify "sets Content-Type to chosen default if none is set" do + app = lambda { |env| [200, {}, "Hello, World!"] } + status, headers, body = + Rack::ContentType.new(app, 'application/octet-stream').call({}) + headers['Content-Type'].should.equal 'application/octet-stream' + end + + specify "does not change Content-Type if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] } + status, headers, body = Rack::ContentType.new(app).call({}) + headers['Content-Type'].should.equal 'foo/bar' + end + + specify "case insensitive detection of Content-Type" do + app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] } + status, headers, body = Rack::ContentType.new(app).call({}) + headers.to_a.select { |k,v| k.downcase == "content-type" }. + should.equal [["CONTENT-Type","foo/bar"]] + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_deflater.rb b/vendor/gems/rack-1.1.0/test/spec_rack_deflater.rb new file mode 100644 index 0000000..c9bb318 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_deflater.rb @@ -0,0 +1,127 @@ +require 'test/spec' + +require 'rack/mock' +require 'rack/deflater' +require 'stringio' +require 'time' # for Time#httpdate + +context "Rack::Deflater" do + def build_response(status, body, accept_encoding, headers = {}) + body = [body] if body.respond_to? :to_str + app = lambda { |env| [status, {}, body] } + request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding)) + response = Rack::Deflater.new(app).call(request) + + return response + end + + specify "should be able to deflate bodies that respond to each" do + body = Object.new + class << body; def each; yield("foo"); yield("bar"); end; end + + response = build_response(200, body, "deflate") + + response[0].should.equal(200) + response[1].should.equal({ + "Content-Encoding" => "deflate", + "Vary" => "Accept-Encoding" + }) + buf = '' + response[2].each { |part| buf << part } + buf.should.equal("K\313\317OJ,\002\000") + end + + # TODO: This is really just a special case of the above... + specify "should be able to deflate String bodies" do + response = build_response(200, "Hello world!", "deflate") + + response[0].should.equal(200) + response[1].should.equal({ + "Content-Encoding" => "deflate", + "Vary" => "Accept-Encoding" + }) + buf = '' + response[2].each { |part| buf << part } + buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000") + end + + specify "should be able to gzip bodies that respond to each" do + body = Object.new + class << body; def each; yield("foo"); yield("bar"); end; end + + response = build_response(200, body, "gzip") + + response[0].should.equal(200) + response[1].should.equal({ + "Content-Encoding" => "gzip", + "Vary" => "Accept-Encoding", + }) + + buf = '' + response[2].each { |part| buf << part } + io = StringIO.new(buf) + gz = Zlib::GzipReader.new(io) + gz.read.should.equal("foobar") + gz.close + end + + specify "should be able to fallback to no deflation" do + response = build_response(200, "Hello world!", "superzip") + + response[0].should.equal(200) + response[1].should.equal({ "Vary" => "Accept-Encoding" }) + response[2].should.equal(["Hello world!"]) + end + + specify "should be able to skip when there is no response entity body" do + response = build_response(304, [], "gzip") + + response[0].should.equal(304) + response[1].should.equal({}) + response[2].should.equal([]) + end + + specify "should handle the lack of an acceptable encoding" do + response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/") + response1[0].should.equal(406) + response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"}) + response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."]) + + response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar") + response2[0].should.equal(406) + response2[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "78"}) + response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."]) + end + + specify "should handle gzip response with Last-Modified header" do + last_modified = Time.now.httpdate + + app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] } + request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") + response = Rack::Deflater.new(app).call(request) + + response[0].should.equal(200) + response[1].should.equal({ + "Content-Encoding" => "gzip", + "Vary" => "Accept-Encoding", + "Last-Modified" => last_modified + }) + + buf = '' + response[2].each { |part| buf << part } + io = StringIO.new(buf) + gz = Zlib::GzipReader.new(io) + gz.read.should.equal("Hello World!") + gz.close + end + + specify "should do nothing when no-transform Cache-Control directive present" do + app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] } + request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") + response = Rack::Deflater.new(app).call(request) + + response[0].should.equal(200) + response[1].should.not.include "Content-Encoding" + response[2].join.should.equal("Hello World!") + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_directory.rb b/vendor/gems/rack-1.1.0/test/spec_rack_directory.rb new file mode 100644 index 0000000..d255c91 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_directory.rb @@ -0,0 +1,61 @@ +require 'test/spec' + +require 'rack/directory' +require 'rack/lint' + +require 'rack/mock' + +context "Rack::Directory" do + DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT + FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] } + app = Rack::Directory.new DOCROOT, FILE_CATCH + + specify "serves directory indices" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/") + + res.should.be.ok + res.should =~ // + end + + specify "passes to app if file found" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/test") + + res.should.be.ok + res.should =~ /passed!/ + end + + specify "serves uri with URL encoded filenames" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/%63%67%69/") # "/cgi/test" + + res.should.be.ok + res.should =~ // + + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/%74%65%73%74") # "/cgi/test" + + res.should.be.ok + res.should =~ /passed!/ + end + + specify "does not allow directory traversal" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/../test") + + res.should.be.forbidden + + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/%2E%2E/test") + + res.should.be.forbidden + end + + specify "404s if it can't find the file" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/blubb") + + res.should.be.not_found + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_etag.rb b/vendor/gems/rack-1.1.0/test/spec_rack_etag.rb new file mode 100644 index 0000000..73cd31a --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_etag.rb @@ -0,0 +1,17 @@ +require 'test/spec' +require 'rack/mock' +require 'rack/etag' + +context "Rack::ETag" do + specify "sets ETag if none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = Rack::ETag.new(app).call({}) + response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\"" + end + + specify "does not change ETag if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] } + response = Rack::ETag.new(app).call({}) + response[1]['ETag'].should.equal "\"abc\"" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_fastcgi.rb b/vendor/gems/rack-1.1.0/test/spec_rack_fastcgi.rb new file mode 100644 index 0000000..1ae55ac --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_fastcgi.rb @@ -0,0 +1,89 @@ +require 'test/spec' +require 'testrequest' + +context "Rack::Handler::FastCGI" do + include TestRequest::Helpers + + setup do + @host = '0.0.0.0' + @port = 9203 + end + + # Keep this first. + specify "startup" do + $pid = fork { + Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi")) + exec "lighttpd -D -f lighttpd.conf" + } + end + + specify "should respond" do + sleep 1 + lambda { + GET("/test.fcgi") + }.should.not.raise + end + + specify "should be a lighttpd" do + GET("/test.fcgi") + status.should.be 200 + response["SERVER_SOFTWARE"].should =~ /lighttpd/ + response["HTTP_VERSION"].should.equal "HTTP/1.1" + response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" + response["SERVER_PORT"].should.equal @port.to_s + response["SERVER_NAME"].should =~ @host + end + + specify "should have rack headers" do + GET("/test.fcgi") + response["rack.version"].should.equal [1,1] + response["rack.multithread"].should.be false + response["rack.multiprocess"].should.be true + response["rack.run_once"].should.be false + end + + specify "should have CGI headers on GET" do + GET("/test.fcgi") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test.fcgi" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "" + response["QUERY_STRING"].should.equal "" + response["test.postdata"].should.equal "" + + GET("/test.fcgi/foo?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test.fcgi" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "/foo" + response["QUERY_STRING"].should.equal "quux=1" + end + + specify "should have CGI headers on POST" do + POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) + status.should.equal 200 + response["REQUEST_METHOD"].should.equal "POST" + response["SCRIPT_NAME"].should.equal "/test.fcgi" + response["REQUEST_PATH"].should.equal "/" + response["QUERY_STRING"].should.equal "" + response["HTTP_X_TEST_HEADER"].should.equal "42" + response["test.postdata"].should.equal "rack-form-data=23" + end + + specify "should support HTTP auth" do + GET("/test.fcgi", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + specify "should set status" do + GET("/test.fcgi?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + # Keep this last. + specify "shutdown" do + Process.kill 15, $pid + Process.wait($pid).should.equal $pid + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_file.rb b/vendor/gems/rack-1.1.0/test/spec_rack_file.rb new file mode 100644 index 0000000..0a2f8ee --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_file.rb @@ -0,0 +1,75 @@ +require 'test/spec' + +require 'rack/file' +require 'rack/lint' + +require 'rack/mock' + +context "Rack::File" do + DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT + + specify "serves files" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi/test") + + res.should.be.ok + res.should =~ /ruby/ + end + + specify "sets Last-Modified header" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi/test") + + path = File.join(DOCROOT, "/cgi/test") + + res.should.be.ok + res["Last-Modified"].should.equal File.mtime(path).httpdate + end + + specify "serves files with URL encoded filenames" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi/%74%65%73%74") # "/cgi/test" + + res.should.be.ok + res.should =~ /ruby/ + end + + specify "does not allow directory traversal" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi/../test") + + res.should.be.forbidden + end + + specify "does not allow directory traversal with encoded periods" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/%2E%2E/README") + + res.should.be.forbidden + end + + specify "404s if it can't find the file" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi/blubb") + + res.should.be.not_found + end + + specify "detects SystemCallErrors" do + res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). + get("/cgi") + + res.should.be.not_found + end + + specify "returns bodies that respond to #to_path" do + env = Rack::MockRequest.env_for("/cgi/test") + status, headers, body = Rack::File.new(DOCROOT).call(env) + + path = File.join(DOCROOT, "/cgi/test") + + status.should.equal 200 + body.should.respond_to :to_path + body.to_path.should.equal path + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_handler.rb b/vendor/gems/rack-1.1.0/test/spec_rack_handler.rb new file mode 100644 index 0000000..fcf19b7 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_handler.rb @@ -0,0 +1,43 @@ +require 'test/spec' + +require 'rack/handler' + +class Rack::Handler::Lobster; end +class RockLobster; end + +context "Rack::Handler" do + specify "has registered default handlers" do + Rack::Handler.get('cgi').should.equal Rack::Handler::CGI + Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI + Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel + Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick + end + + specify "handler that doesn't exist should raise a NameError" do + lambda { + Rack::Handler.get('boom') + }.should.raise(NameError) + end + + specify "should get unregistered, but already required, handler by name" do + Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster + end + + specify "should register custom handler" do + Rack::Handler.register('rock_lobster', 'RockLobster') + Rack::Handler.get('rock_lobster').should.equal RockLobster + end + + specify "should not need registration for properly coded handlers even if not already required" do + begin + $:.push "test/unregistered_handler" + Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered + lambda { + Rack::Handler.get('UnRegistered') + }.should.raise(NameError) + Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne + ensure + $:.delete "test/unregistered_handler" + end + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_head.rb b/vendor/gems/rack-1.1.0/test/spec_rack_head.rb new file mode 100644 index 0000000..48d3f81 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_head.rb @@ -0,0 +1,30 @@ +require 'rack/head' +require 'rack/mock' + +context "Rack::Head" do + def test_response(headers = {}) + app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] } + request = Rack::MockRequest.env_for("/", headers) + response = Rack::Head.new(app).call(request) + + return response + end + + specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do + %w[GET POST PUT DELETE OPTIONS TRACE].each do |type| + resp = test_response("REQUEST_METHOD" => type) + + resp[0].should.equal(200) + resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"}) + resp[2].should.equal(["foo"]) + end + end + + specify "removes body from HEAD requests" do + resp = test_response("REQUEST_METHOD" => "HEAD") + + resp[0].should.equal(200) + resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"}) + resp[2].should.equal([]) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_lint.rb b/vendor/gems/rack-1.1.0/test/spec_rack_lint.rb new file mode 100644 index 0000000..bbf75c1 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_lint.rb @@ -0,0 +1,528 @@ +require 'test/spec' +require 'stringio' + +require 'rack/lint' +require 'rack/mock' + +context "Rack::Lint" do + def env(*args) + Rack::MockRequest.env_for("/", *args) + end + + specify "passes valid request" do + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] + }).call(env({})) + }.should.not.raise + end + + specify "notices fatal errors" do + lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError). + message.should.match(/No env given/) + end + + specify "notices environment errors" do + lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError). + message.should.match(/not a Hash/) + + lambda { + e = env + e.delete("REQUEST_METHOD") + Rack::Lint.new(nil).call(e) + }.should.raise(Rack::Lint::LintError). + message.should.match(/missing required key REQUEST_METHOD/) + + lambda { + e = env + e.delete("SERVER_NAME") + Rack::Lint.new(nil).call(e) + }.should.raise(Rack::Lint::LintError). + message.should.match(/missing required key SERVER_NAME/) + + + lambda { + Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/contains HTTP_CONTENT_TYPE/) + + lambda { + Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/contains HTTP_CONTENT_LENGTH/) + + lambda { + Rack::Lint.new(nil).call(env("FOO" => Object.new)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/non-string value/) + + lambda { + Rack::Lint.new(nil).call(env("rack.version" => "0.2")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must be an Array/) + + lambda { + Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/url_scheme unknown/) + + lambda { + Rack::Lint.new(nil).call(env("rack.session" => [])) + }.should.raise(Rack::Lint::LintError). + message.should.equal("session [] must respond to store and []=") + + lambda { + Rack::Lint.new(nil).call(env("rack.logger" => [])) + }.should.raise(Rack::Lint::LintError). + message.should.equal("logger [] must respond to info") + + lambda { + Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/REQUEST_METHOD/) + + lambda { + Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must start with/) + + lambda { + Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must start with/) + + lambda { + Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/Invalid CONTENT_LENGTH/) + + lambda { + e = env + e.delete("PATH_INFO") + e.delete("SCRIPT_NAME") + Rack::Lint.new(nil).call(e) + }.should.raise(Rack::Lint::LintError). + message.should.match(/One of .* must be set/) + + lambda { + Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/cannot be .* make it ''/) + end + + specify "notices input errors" do + lambda { + Rack::Lint.new(nil).call(env("rack.input" => "")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/does not respond to #gets/) + + lambda { + input = Object.new + def input.binmode? + false + end + Rack::Lint.new(nil).call(env("rack.input" => input)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/is not opened in binary mode/) + + lambda { + input = Object.new + def input.external_encoding + result = Object.new + def result.name + "US-ASCII" + end + result + end + Rack::Lint.new(nil).call(env("rack.input" => input)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/does not have ASCII-8BIT as its external encoding/) + end + + specify "notices error errors" do + lambda { + Rack::Lint.new(nil).call(env("rack.errors" => "")) + }.should.raise(Rack::Lint::LintError). + message.should.match(/does not respond to #puts/) + end + + specify "notices status errors" do + lambda { + Rack::Lint.new(lambda { |env| + ["cc", {}, ""] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must be >=100 seen as integer/) + + lambda { + Rack::Lint.new(lambda { |env| + [42, {}, ""] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must be >=100 seen as integer/) + end + + specify "notices header errors" do + lambda { + Rack::Lint.new(lambda { |env| + [200, Object.new, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)") + + lambda { + Rack::Lint.new(lambda { |env| + [200, {true=>false}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.equal("header key must be a string, was TrueClass") + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Status" => "404"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must not contain Status/) + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-Type:" => "text/plain"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must not contain :/) + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-" => "text/plain"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/must not end/) + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"..%%quark%%.." => "text/plain"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.equal("invalid header name: ..%%quark%%..") + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Foo" => Object.new}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.equal("a header value must be a String, but the value of 'Foo' is a Object") + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Foo" => [1, 2, 3]}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.equal("a header value must be a String, but the value of 'Foo' is a Array") + + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Foo-Bar" => "text\000plain"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/invalid header/) + + # line ends (010) should be allowed in header values. + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []] + }).call(env({})) + }.should.not.raise(Rack::Lint::LintError) + end + + specify "notices content-type errors" do + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/No Content-Type/) + + [100, 101, 204, 304].each do |status| + lambda { + Rack::Lint.new(lambda { |env| + [status, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/Content-Type header found/) + end + end + + specify "notices content-length errors" do + [100, 101, 204, 304].each do |status| + lambda { + Rack::Lint.new(lambda { |env| + [status, {"Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/Content-Length header found/) + end + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/Content-Length header was 1, but should be 0/) + end + + specify "notices body errors" do + lambda { + status, header, body = Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]] + }).call(env({})) + body.each { |part| } + }.should.raise(Rack::Lint::LintError). + message.should.match(/yielded non-string/) + end + + specify "notices input handling errors" do + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].gets("\r\n") + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/gets called with arguments/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(1, 2, 3) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read called with too many arguments/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read("foo") + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read called with non-integer and non-nil length/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(-1) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read called with a negative length/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(nil, nil) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read called with non-String buffer/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(nil, 1) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read called with non-String buffer/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].rewind(0) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/rewind called with arguments/) + + weirdio = Object.new + class << weirdio + def gets + 42 + end + + def read + 23 + end + + def each + yield 23 + yield 42 + end + + def rewind + raise Errno::ESPIPE, "Errno::ESPIPE" + end + end + + eof_weirdio = Object.new + class << eof_weirdio + def gets + nil + end + + def read(*args) + nil + end + + def each + end + + def rewind + end + end + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].gets + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env("rack.input" => weirdio)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/gets didn't return a String/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].each { |x| } + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env("rack.input" => weirdio)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/each didn't yield a String/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env("rack.input" => weirdio)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read didn't return nil or a String/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env("rack.input" => eof_weirdio)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/read\(nil\) returned nil on EOF/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].rewind + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env("rack.input" => weirdio)) + }.should.raise(Rack::Lint::LintError). + message.should.match(/rewind raised Errno::ESPIPE/) + + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].close + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/close must not be called/) + end + + specify "notices error handling errors" do + lambda { + Rack::Lint.new(lambda { |env| + env["rack.errors"].write(42) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/write not called with a String/) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.errors"].close + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/close must not be called/) + end + + specify "notices HEAD errors" do + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []] + }).call(env({"REQUEST_METHOD" => "HEAD"})) + }.should.not.raise + + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] + }).call(env({"REQUEST_METHOD" => "HEAD"})) + }.should.raise(Rack::Lint::LintError). + message.should.match(/body was given for HEAD/) + end + + specify "passes valid read calls" do + hello_str = "hello world" + hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(0) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(1) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(nil) + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(nil, '') + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + + lambda { + Rack::Lint.new(lambda { |env| + env["rack.input"].read(1, '') + [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []] + }).call(env({"rack.input" => StringIO.new(hello_str)})) + }.should.not.raise(Rack::Lint::LintError) + end +end + +context "Rack::Lint::InputWrapper" do + specify "delegates :size to underlying IO object" do + class IOMock + def size + 101 + end + end + + wrapper = Rack::Lint::InputWrapper.new(IOMock.new) + wrapper.size.should == 101 + end + + specify "delegates :rewind to underlying IO object" do + io = StringIO.new("123") + wrapper = Rack::Lint::InputWrapper.new(io) + wrapper.read.should.equal "123" + wrapper.read.should.equal "" + wrapper.rewind + wrapper.read.should.equal "123" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_lobster.rb b/vendor/gems/rack-1.1.0/test/spec_rack_lobster.rb new file mode 100644 index 0000000..7be267a --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_lobster.rb @@ -0,0 +1,45 @@ +require 'test/spec' + +require 'rack/lobster' +require 'rack/mock' + +context "Rack::Lobster::LambdaLobster" do + specify "should be a single lambda" do + Rack::Lobster::LambdaLobster.should.be.kind_of Proc + end + + specify "should look like a lobster" do + res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/") + res.should.be.ok + res.body.should.include "(,(,,(,,,(" + res.body.should.include "?flip" + end + + specify "should be flippable" do + res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/?flip") + res.should.be.ok + res.body.should.include "(,,,(,,(,(" + end +end + +context "Rack::Lobster" do + specify "should look like a lobster" do + res = Rack::MockRequest.new(Rack::Lobster.new).get("/") + res.should.be.ok + res.body.should.include "(,(,,(,,,(" + res.body.should.include "?flip" + res.body.should.include "crash" + end + + specify "should be flippable" do + res = Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=left") + res.should.be.ok + res.body.should.include "(,,,(,,(,(" + end + + specify "should provide crashing for testing purposes" do + lambda { + Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=crash") + }.should.raise + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_lock.rb b/vendor/gems/rack-1.1.0/test/spec_rack_lock.rb new file mode 100644 index 0000000..18af2b2 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_lock.rb @@ -0,0 +1,38 @@ +require 'test/spec' + +require 'rack/mock' +require 'rack/lock' + +context "Rack::Lock" do + class Lock + attr_reader :synchronized + + def initialize + @synchronized = false + end + + def synchronize + @synchronized = true + yield + end + end + + specify "should call synchronize on lock" do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + app = Rack::Lock.new(lambda { |env| }, lock) + lock.synchronized.should.equal false + app.call(env) + lock.synchronized.should.equal true + end + + specify "should set multithread flag to false" do + app = Rack::Lock.new(lambda { |env| env['rack.multithread'] }) + app.call(Rack::MockRequest.env_for("/")).should.equal false + end + + specify "should reset original multithread flag when exiting lock" do + app = Rack::Lock.new(lambda { |env| env }) + app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_logger.rb b/vendor/gems/rack-1.1.0/test/spec_rack_logger.rb new file mode 100644 index 0000000..d55b9c7 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_logger.rb @@ -0,0 +1,21 @@ +require 'rack/logger' +require 'rack/lint' +require 'stringio' + +context "Rack::Logger" do + specify "logs to rack.errors" do + app = lambda { |env| + log = env['rack.logger'] + log.debug("Created logger") + log.info("Program started") + log.warn("Nothing to do!") + + [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] + } + + errors = StringIO.new + Rack::Logger.new(app).call({'rack.errors' => errors}) + errors.string.should.match "INFO -- : Program started" + errors.string.should.match "WARN -- : Nothing to do" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_methodoverride.rb b/vendor/gems/rack-1.1.0/test/spec_rack_methodoverride.rb new file mode 100644 index 0000000..5745239 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_methodoverride.rb @@ -0,0 +1,60 @@ +require 'test/spec' + +require 'rack/mock' +require 'rack/methodoverride' +require 'stringio' + +context "Rack::MethodOverride" do + specify "should not affect GET requests" do + env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET") + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["REQUEST_METHOD"].should.equal "GET" + end + + specify "_method parameter should modify REQUEST_METHOD for POST requests" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put") + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["REQUEST_METHOD"].should.equal "PUT" + end + + specify "X-HTTP-Method-Override header should modify REQUEST_METHOD for POST requests" do + env = Rack::MockRequest.env_for("/", + :method => "POST", + "HTTP_X_HTTP_METHOD_OVERRIDE" => "PUT" + ) + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["REQUEST_METHOD"].should.equal "PUT" + end + + specify "should not modify REQUEST_METHOD if the method is unknown" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo") + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["REQUEST_METHOD"].should.equal "POST" + end + + specify "should not modify REQUEST_METHOD when _method is nil" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar") + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["REQUEST_METHOD"].should.equal "POST" + end + + specify "should store the original REQUEST_METHOD prior to overriding" do + env = Rack::MockRequest.env_for("/", + :method => "POST", + :input => "_method=options") + app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) + req = app.call(env) + + req.env["rack.methodoverride.original_method"].should.equal "POST" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_mock.rb b/vendor/gems/rack-1.1.0/test/spec_rack_mock.rb new file mode 100644 index 0000000..a03bedc --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_mock.rb @@ -0,0 +1,243 @@ +require 'yaml' +require 'rack/mock' +require 'rack/request' +require 'rack/response' + +app = lambda { |env| + req = Rack::Request.new(env) + + env["mock.postdata"] = env["rack.input"].read + if req.GET["error"] + env["rack.errors"].puts req.GET["error"] + env["rack.errors"].flush + end + + Rack::Response.new(env.to_yaml, + req.GET["status"] || 200, + "Content-Type" => "text/yaml").finish +} + +context "Rack::MockRequest" do + specify "should return a MockResponse" do + res = Rack::MockRequest.new(app).get("") + res.should.be.kind_of Rack::MockResponse + end + + specify "should be able to only return the environment" do + env = Rack::MockRequest.env_for("") + env.should.be.kind_of Hash + env.should.include "rack.version" + end + + specify "should provide sensible defaults" do + res = Rack::MockRequest.new(app).request + + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["SERVER_NAME"].should.equal "example.org" + env["SERVER_PORT"].should.equal "80" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/" + env["SCRIPT_NAME"].should.equal "" + env["rack.url_scheme"].should.equal "http" + env["mock.postdata"].should.be.empty + end + + specify "should allow GET/POST/PUT/DELETE" do + res = Rack::MockRequest.new(app).get("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + + res = Rack::MockRequest.new(app).post("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "POST" + + res = Rack::MockRequest.new(app).put("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "PUT" + + res = Rack::MockRequest.new(app).delete("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "DELETE" + + Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"]. + should.equal "OPTIONS" + end + + specify "should set content length" do + env = Rack::MockRequest.env_for("/", :input => "foo") + env["CONTENT_LENGTH"].should.equal "3" + end + + specify "should allow posting" do + res = Rack::MockRequest.new(app).get("", :input => "foo") + env = YAML.load(res.body) + env["mock.postdata"].should.equal "foo" + + res = Rack::MockRequest.new(app).post("", :input => StringIO.new("foo")) + env = YAML.load(res.body) + env["mock.postdata"].should.equal "foo" + end + + specify "should use all parts of an URL" do + res = Rack::MockRequest.new(app). + get("https://bla.example.org:9292/meh/foo?bar") + res.should.be.kind_of Rack::MockResponse + + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["SERVER_NAME"].should.equal "bla.example.org" + env["SERVER_PORT"].should.equal "9292" + env["QUERY_STRING"].should.equal "bar" + env["PATH_INFO"].should.equal "/meh/foo" + env["rack.url_scheme"].should.equal "https" + end + + specify "should set SSL port and HTTP flag on when using https" do + res = Rack::MockRequest.new(app). + get("https://example.org/foo") + res.should.be.kind_of Rack::MockResponse + + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["SERVER_NAME"].should.equal "example.org" + env["SERVER_PORT"].should.equal "443" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/foo" + env["rack.url_scheme"].should.equal "https" + env["HTTPS"].should.equal "on" + end + + specify "should prepend slash to uri path" do + res = Rack::MockRequest.new(app). + get("foo") + res.should.be.kind_of Rack::MockResponse + + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["SERVER_NAME"].should.equal "example.org" + env["SERVER_PORT"].should.equal "80" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/foo" + env["rack.url_scheme"].should.equal "http" + end + + specify "should properly convert method name to an uppercase string" do + res = Rack::MockRequest.new(app).request(:get) + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + end + + specify "should accept params and build query string for GET requests" do + res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}}) + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["QUERY_STRING"].should.match "baz=2" + env["QUERY_STRING"].should.match "foo[bar]=1" + env["PATH_INFO"].should.equal "/foo" + env["mock.postdata"].should.equal "" + end + + specify "should accept raw input in params for GET requests" do + res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "GET" + env["QUERY_STRING"].should.match "baz=2" + env["QUERY_STRING"].should.match "foo[bar]=1" + env["PATH_INFO"].should.equal "/foo" + env["mock.postdata"].should.equal "" + end + + specify "should accept params and build url encoded params for POST requests" do + res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}}) + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "POST" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/foo" + env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded" + env["mock.postdata"].should.equal "foo[bar]=1" + end + + specify "should accept raw input in params for POST requests" do + res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "POST" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/foo" + env["CONTENT_TYPE"].should.equal "application/x-www-form-urlencoded" + env["mock.postdata"].should.equal "foo[bar]=1" + end + + specify "should accept params and build multipart encoded params for POST requests" do + files = Rack::Utils::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt")) + res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files }) + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "POST" + env["QUERY_STRING"].should.equal "" + env["PATH_INFO"].should.equal "/foo" + env["CONTENT_TYPE"].should.equal "multipart/form-data; boundary=AaB03x" + env["mock.postdata"].length.should.equal 206 + end + + specify "should behave valid according to the Rack spec" do + lambda { + res = Rack::MockRequest.new(app). + get("https://bla.example.org:9292/meh/foo?bar", :lint => true) + }.should.not.raise(Rack::Lint::LintError) + end +end + +context "Rack::MockResponse" do + specify "should provide access to the HTTP status" do + res = Rack::MockRequest.new(app).get("") + res.should.be.successful + res.should.be.ok + + res = Rack::MockRequest.new(app).get("/?status=404") + res.should.not.be.successful + res.should.be.client_error + res.should.be.not_found + + res = Rack::MockRequest.new(app).get("/?status=501") + res.should.not.be.successful + res.should.be.server_error + + res = Rack::MockRequest.new(app).get("/?status=307") + res.should.be.redirect + + res = Rack::MockRequest.new(app).get("/?status=201", :lint => true) + res.should.be.empty + end + + specify "should provide access to the HTTP headers" do + res = Rack::MockRequest.new(app).get("") + res.should.include "Content-Type" + res.headers["Content-Type"].should.equal "text/yaml" + res.original_headers["Content-Type"].should.equal "text/yaml" + res["Content-Type"].should.equal "text/yaml" + res.content_type.should.equal "text/yaml" + res.content_length.should.be 414 # needs change often. + res.location.should.be.nil + end + + specify "should provide access to the HTTP body" do + res = Rack::MockRequest.new(app).get("") + res.body.should =~ /rack/ + res.should =~ /rack/ + res.should.match(/rack/) + res.should.satisfy { |r| r.match(/rack/) } + end + + specify "should provide access to the Rack errors" do + res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true) + res.should.be.ok + res.errors.should.not.be.empty + res.errors.should.include "foo" + end + + specify "should optionally make Rack errors fatal" do + lambda { + Rack::MockRequest.new(app).get("/?error=foo", :fatal => true) + }.should.raise(Rack::MockRequest::FatalWarning) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_mongrel.rb b/vendor/gems/rack-1.1.0/test/spec_rack_mongrel.rb new file mode 100644 index 0000000..4b38689 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_mongrel.rb @@ -0,0 +1,189 @@ +require 'test/spec' + +begin +require 'rack/handler/mongrel' +require 'rack/urlmap' +require 'rack/lint' +require 'testrequest' +require 'timeout' + +Thread.abort_on_exception = true +$tcp_defer_accept_opts = nil +$tcp_cork_opts = nil + +context "Rack::Handler::Mongrel" do + include TestRequest::Helpers + + setup do + server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201) + server.register('/test', + Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new))) + server.register('/stream', + Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest))) + @acc = server.run + end + + specify "should respond" do + lambda { + GET("/test") + }.should.not.raise + end + + specify "should be a Mongrel" do + GET("/test") + status.should.be 200 + response["SERVER_SOFTWARE"].should =~ /Mongrel/ + response["HTTP_VERSION"].should.equal "HTTP/1.1" + response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" + response["SERVER_PORT"].should.equal "9201" + response["SERVER_NAME"].should.equal "0.0.0.0" + end + + specify "should have rack headers" do + GET("/test") + response["rack.version"].should.equal [1,1] + response["rack.multithread"].should.be true + response["rack.multiprocess"].should.be false + response["rack.run_once"].should.be false + end + + specify "should have CGI headers on GET" do + GET("/test") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/test" + response["PATH_INFO"].should.be.equal "" + response["QUERY_STRING"].should.equal "" + response["test.postdata"].should.equal "" + + GET("/test/foo?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/test/foo" + response["PATH_INFO"].should.equal "/foo" + response["QUERY_STRING"].should.equal "quux=1" + end + + specify "should have CGI headers on POST" do + POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) + status.should.equal 200 + response["REQUEST_METHOD"].should.equal "POST" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/test" + response["QUERY_STRING"].should.equal "" + response["HTTP_X_TEST_HEADER"].should.equal "42" + response["test.postdata"].should.equal "rack-form-data=23" + end + + specify "should support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + specify "should set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + specify "should provide a .run" do + block_ran = false + Thread.new { + Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server| + server.should.be.kind_of Mongrel::HttpServer + block_ran = true + } + } + sleep 1 + block_ran.should.be true + end + + specify "should provide a .run that maps a hash" do + block_ran = false + Thread.new { + map = {'/'=>lambda{},'/foo'=>lambda{}} + Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server| + server.should.be.kind_of Mongrel::HttpServer + server.classifier.uris.size.should.be 2 + server.classifier.uris.should.not.include '/arf' + server.classifier.uris.should.include '/' + server.classifier.uris.should.include '/foo' + block_ran = true + } + } + sleep 1 + block_ran.should.be true + end + + specify "should provide a .run that maps a urlmap" do + block_ran = false + Thread.new { + map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}}) + Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server| + server.should.be.kind_of Mongrel::HttpServer + server.classifier.uris.size.should.be 2 + server.classifier.uris.should.not.include '/arf' + server.classifier.uris.should.include '/' + server.classifier.uris.should.include '/bar' + block_ran = true + } + } + sleep 1 + block_ran.should.be true + end + + specify "should provide a .run that maps a urlmap restricting by host" do + block_ran = false + Thread.new { + map = Rack::URLMap.new({ + '/' => lambda{}, + '/foo' => lambda{}, + '/bar' => lambda{}, + 'http://localhost/' => lambda{}, + 'http://localhost/bar' => lambda{}, + 'http://falsehost/arf' => lambda{}, + 'http://falsehost/qux' => lambda{} + }) + opt = {:map => true, :Port => 9241, :Host => 'localhost'} + Rack::Handler::Mongrel.run(map, opt) { |server| + server.should.be.kind_of Mongrel::HttpServer + server.classifier.uris.should.include '/' + server.classifier.handler_map['/'].size.should.be 2 + server.classifier.uris.should.include '/foo' + server.classifier.handler_map['/foo'].size.should.be 1 + server.classifier.uris.should.include '/bar' + server.classifier.handler_map['/bar'].size.should.be 2 + server.classifier.uris.should.not.include '/qux' + server.classifier.uris.should.not.include '/arf' + server.classifier.uris.size.should.be 3 + block_ran = true + } + } + sleep 1 + block_ran.should.be true + end + + specify "should stream #each part of the response" do + body = '' + begin + Timeout.timeout(1) do + Net::HTTP.start(@host, @port) do |http| + get = Net::HTTP::Get.new('/stream') + http.request(get) do |response| + response.read_body { |part| body << part } + end + end + end + rescue Timeout::Error + end + body.should.not.be.empty + end + + teardown do + @acc.raise Mongrel::StopServer + end +end + +rescue LoadError + $stderr.puts "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again." +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_nulllogger.rb b/vendor/gems/rack-1.1.0/test/spec_rack_nulllogger.rb new file mode 100644 index 0000000..b3c2bc9 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_nulllogger.rb @@ -0,0 +1,13 @@ +require 'rack/nulllogger' +require 'rack/lint' +require 'rack/mock' + +context "Rack::NullLogger" do + specify "acks as a nop logger" do + app = lambda { |env| + env['rack.logger'].warn "b00m" + [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] + } + Rack::NullLogger.new(app).call({}) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_recursive.rb b/vendor/gems/rack-1.1.0/test/spec_rack_recursive.rb new file mode 100644 index 0000000..afc1a0d --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_recursive.rb @@ -0,0 +1,77 @@ +require 'test/spec' + +require 'rack/recursive' +require 'rack/urlmap' +require 'rack/response' +require 'rack/mock' + +context "Rack::Recursive" do + setup do + + @app1 = lambda { |env| + res = Rack::Response.new + res["X-Path-Info"] = env["PATH_INFO"] + res["X-Query-String"] = env["QUERY_STRING"] + res.finish do |res| + res.write "App1" + end + } + + @app2 = lambda { |env| + Rack::Response.new.finish do |res| + res.write "App2" + _, _, body = env['rack.recursive.include'].call(env, "/app1") + body.each { |b| + res.write b + } + end + } + + @app3 = lambda { |env| + raise Rack::ForwardRequest.new("/app1") + } + + @app4 = lambda { |env| + raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh") + } + + end + + specify "should allow for subrequests" do + res = Rack::MockRequest.new(Rack::Recursive.new( + Rack::URLMap.new("/app1" => @app1, + "/app2" => @app2))). + get("/app2") + + res.should.be.ok + res.body.should.equal "App2App1" + end + + specify "should raise error on requests not below the app" do + app = Rack::URLMap.new("/app1" => @app1, + "/app" => Rack::Recursive.new( + Rack::URLMap.new("/1" => @app1, + "/2" => @app2))) + + lambda { + Rack::MockRequest.new(app).get("/app/2") + }.should.raise(ArgumentError). + message.should =~ /can only include below/ + end + + specify "should support forwarding" do + app = Rack::Recursive.new(Rack::URLMap.new("/app1" => @app1, + "/app3" => @app3, + "/app4" => @app4)) + + res = Rack::MockRequest.new(app).get("/app3") + res.should.be.ok + res.body.should.equal "App1" + + res = Rack::MockRequest.new(app).get("/app4") + res.should.be.ok + res.body.should.equal "App1" + res["X-Path-Info"].should.equal "/quux" + res["X-Query-String"].should.equal "meh" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_request.rb b/vendor/gems/rack-1.1.0/test/spec_rack_request.rb new file mode 100644 index 0000000..fcdeb48 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_request.rb @@ -0,0 +1,545 @@ +require 'test/spec' +require 'stringio' + +require 'rack/request' +require 'rack/mock' + +context "Rack::Request" do + specify "wraps the rack variables" do + req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/")) + + req.body.should.respond_to? :gets + req.scheme.should.equal "http" + req.request_method.should.equal "GET" + + req.should.be.get + req.should.not.be.post + req.should.not.be.put + req.should.not.be.delete + req.should.not.be.head + + req.script_name.should.equal "" + req.path_info.should.equal "/" + req.query_string.should.equal "" + + req.host.should.equal "example.com" + req.port.should.equal 8080 + + req.content_length.should.equal "0" + req.content_type.should.be.nil + end + + specify "can figure out the correct host" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") + req.host.should.equal "www2.example.org" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") + req.host.should.equal "example.org" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") + req.host.should.equal "example.org" + + env = Rack::MockRequest.env_for("/", "SERVER_ADDR" => "192.168.1.1", "SERVER_PORT" => "9292") + env.delete("SERVER_NAME") + req = Rack::Request.new(env) + req.host.should.equal "192.168.1.1" + + env = Rack::MockRequest.env_for("/") + env.delete("SERVER_NAME") + req = Rack::Request.new(env) + req.host.should.equal "" + end + + specify "can parse the query string" do + req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla")) + req.query_string.should.equal "foo=bar&quux=bla" + req.GET.should.equal "foo" => "bar", "quux" => "bla" + req.POST.should.be.empty + req.params.should.equal "foo" => "bar", "quux" => "bla" + end + + specify "raises if rack.input is missing" do + req = Rack::Request.new({}) + lambda { req.POST }.should.raise(RuntimeError) + end + + specify "can parse POST data when method is POST and no Content-Type given" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/?foo=quux", + "REQUEST_METHOD" => 'POST', + :input => "foo=bar&quux=bla") + req.content_type.should.be.nil + req.media_type.should.be.nil + req.query_string.should.equal "foo=quux" + req.GET.should.equal "foo" => "quux" + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.params.should.equal "foo" => "bar", "quux" => "bla" + end + + specify "can parse POST data with explicit content type regardless of method" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', + :input => "foo=bar&quux=bla") + req.content_type.should.equal 'application/x-www-form-urlencoded;foo=bar' + req.media_type.should.equal 'application/x-www-form-urlencoded' + req.media_type_params['foo'].should.equal 'bar' + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.params.should.equal "foo" => "bar", "quux" => "bla" + end + + specify "does not parse POST data when media type is not form-data" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/?foo=quux", + "REQUEST_METHOD" => 'POST', + "CONTENT_TYPE" => 'text/plain;charset=utf-8', + :input => "foo=bar&quux=bla") + req.content_type.should.equal 'text/plain;charset=utf-8' + req.media_type.should.equal 'text/plain' + req.media_type_params['charset'].should.equal 'utf-8' + req.POST.should.be.empty + req.params.should.equal "foo" => "quux" + req.body.read.should.equal "foo=bar&quux=bla" + end + + specify "can parse POST data on PUT when media type is form-data" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/?foo=quux", + "REQUEST_METHOD" => 'PUT', + "CONTENT_TYPE" => 'application/x-www-form-urlencoded', + :input => "foo=bar&quux=bla") + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.body.read.should.equal "foo=bar&quux=bla" + end + + specify "rewinds input after parsing POST data" do + input = StringIO.new("foo=bar&quux=bla") + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', + :input => input) + req.params.should.equal "foo" => "bar", "quux" => "bla" + input.read.should.equal "foo=bar&quux=bla" + end + + specify "cleans up Safari's ajax POST body" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", + 'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0") + req.POST.should.equal "foo" => "bar", "quux" => "bla" + end + + specify "can get value by key from params with #[]" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("?foo=quux") + req['foo'].should.equal 'quux' + req[:foo].should.equal 'quux' + end + + specify "can set value to key on params with #[]=" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("?foo=duh") + req['foo'].should.equal 'duh' + req[:foo].should.equal 'duh' + req.params.should.equal 'foo' => 'duh' + + req['foo'] = 'bar' + req.params.should.equal 'foo' => 'bar' + req['foo'].should.equal 'bar' + req[:foo].should.equal 'bar' + + req[:foo] = 'jaz' + req.params.should.equal 'foo' => 'jaz' + req['foo'].should.equal 'jaz' + req[:foo].should.equal 'jaz' + end + + specify "values_at answers values by keys in order given" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful") + req.values_at('foo').should.equal ['baz'] + req.values_at('foo', 'wun').should.equal ['baz', 'der'] + req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der'] + end + + specify "referrer should be extracted correct" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path") + req.referer.should.equal "/some/path" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/") + req.referer.should.equal "/" + end + + specify "user agent should be extracted correct" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)") + req.user_agent.should.equal "Mozilla/4.0 (compatible)" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/") + req.user_agent.should.equal nil + end + + specify "can cache, but invalidates the cache" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/?foo=quux", + "CONTENT_TYPE" => "application/x-www-form-urlencoded", + :input => "foo=bar&quux=bla") + req.GET.should.equal "foo" => "quux" + req.GET.should.equal "foo" => "quux" + req.env["QUERY_STRING"] = "bla=foo" + req.GET.should.equal "bla" => "foo" + req.GET.should.equal "bla" => "foo" + + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.env["rack.input"] = StringIO.new("foo=bla&quux=bar") + req.POST.should.equal "foo" => "bla", "quux" => "bar" + req.POST.should.equal "foo" => "bla", "quux" => "bar" + end + + specify "can figure out if called via XHR" do + req = Rack::Request.new(Rack::MockRequest.env_for("")) + req.should.not.be.xhr + + req = Rack::Request.new \ + Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest") + req.should.be.xhr + end + + specify "can parse cookies" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") + req.cookies.should.equal "foo" => "bar", "quux" => "h&m" + req.cookies.should.equal "foo" => "bar", "quux" => "h&m" + req.env.delete("HTTP_COOKIE") + req.cookies.should.equal({}) + end + + specify "parses cookies according to RFC 2109" do + req = Rack::Request.new \ + Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car') + req.cookies.should.equal 'foo' => 'bar' + end + + specify "provides setters" do + req = Rack::Request.new(e=Rack::MockRequest.env_for("")) + req.script_name.should.equal "" + req.script_name = "/foo" + req.script_name.should.equal "/foo" + e["SCRIPT_NAME"].should.equal "/foo" + + req.path_info.should.equal "/" + req.path_info = "/foo" + req.path_info.should.equal "/foo" + e["PATH_INFO"].should.equal "/foo" + end + + specify "provides the original env" do + req = Rack::Request.new(e=Rack::MockRequest.env_for("")) + req.env.should.be e + end + + specify "can restore the URL" do + Rack::Request.new(Rack::MockRequest.env_for("")).url. + should.equal "http://example.org/" + Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url. + should.equal "http://example.org/foo/" + Rack::Request.new(Rack::MockRequest.env_for("/foo")).url. + should.equal "http://example.org/foo" + Rack::Request.new(Rack::MockRequest.env_for("?foo")).url. + should.equal "http://example.org/?foo" + Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).url. + should.equal "http://example.org:8080/" + Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).url. + should.equal "https://example.org/" + + Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url. + should.equal "https://example.com:8080/foo?foo" + end + + specify "can restore the full path" do + Rack::Request.new(Rack::MockRequest.env_for("")).fullpath. + should.equal "/" + Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath. + should.equal "/foo/" + Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath. + should.equal "/foo" + Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath. + should.equal "/?foo" + Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath. + should.equal "/" + Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath. + should.equal "/" + + Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath. + should.equal "/foo?foo" + end + + specify "can handle multiple media type parameters" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam') + req.should.not.be.form_data + req.media_type_params.should.include 'foo' + req.media_type_params['foo'].should.equal 'BAR' + req.media_type_params.should.include 'baz' + req.media_type_params['baz'].should.equal 'bizzle dizzle' + req.media_type_params.should.not.include 'BLING' + req.media_type_params.should.include 'bling' + req.media_type_params['bling'].should.equal 'bam' + end + + specify "can parse multipart form data" do + # Adapted from RFC 1867. + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + req.POST.should.include "fileupload" + req.POST.should.include "reply" + + req.should.be.form_data + req.content_length.should.equal input.size + req.media_type.should.equal 'multipart/form-data' + req.media_type_params.should.include 'boundary' + req.media_type_params['boundary'].should.equal 'AaB03x' + + req.POST["reply"].should.equal "yes" + + f = req.POST["fileupload"] + f.should.be.kind_of Hash + f[:type].should.equal "image/jpeg" + f[:filename].should.equal "dj.jpg" + f.should.include :tempfile + f[:tempfile].size.should.equal 76 + end + + specify "can parse big multipart form data" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + req.POST["huge"][:tempfile].size.should.equal 32768 + req.POST["mean"][:tempfile].size.should.equal 10 + req.POST["mean"][:tempfile].read.should.equal "--AaB03xha" + end + + specify "can detect invalid multipart form data" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda { req.POST }.should.raise(EOFError) + + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda { req.POST }.should.raise(EOFError) + + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda { req.POST }.should.raise(EOFError) + end + + specify "shouldn't try to interpret binary as utf8" do + begin + original_kcode = $KCODE + $KCODE='UTF8' + + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda{req.POST}.should.not.raise(EOFError) + req.POST["fileupload"][:tempfile].size.should.equal 4 + ensure + $KCODE = original_kcode + end + end + + + specify "should work around buggy 1.8.* Tempfile equality" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => rack_input) + + lambda {req.POST}.should.not.raise + lambda {req.POST}.should.blaming("input re-processed!").not.raise + end + + specify "does conform to the Rack spec" do + app = lambda { |env| + content = Rack::Request.new(env).POST["file"].inspect + size = content.respond_to?(:bytesize) ? content.bytesize : content.size + [200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, [content]] + } + + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input) + + res.should.be.ok + end + + specify "should parse Accept-Encoding correctly" do + parser = lambda do |x| + Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding + end + + parser.call(nil).should.equal([]) + + parser.call("compress, gzip").should.equal([["compress", 1.0], ["gzip", 1.0]]) + parser.call("").should.equal([]) + parser.call("*").should.equal([["*", 1.0]]) + parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]]) + parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ]) + + lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError) + end + + specify 'should provide ip information' do + app = lambda { |env| + request = Rack::Request.new(env) + response = Rack::Response.new + response.write request.ip + response.finish + } + + mock = Rack::MockRequest.new(Rack::Lint.new(app)) + res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123' + res.body.should.equal '123.123.123.123' + + res = mock.get '/', + 'REMOTE_ADDR' => '123.123.123.123', + 'HTTP_X_FORWARDED_FOR' => '234.234.234.234' + + res.body.should.equal '234.234.234.234' + + res = mock.get '/', + 'REMOTE_ADDR' => '123.123.123.123', + 'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212' + + res.body.should.equal '212.212.212.212' + end + + class MyRequest < Rack::Request + def params + {:foo => "bar"} + end + end + + specify "should allow subclass request to be instantiated after parent request" do + env = Rack::MockRequest.env_for("/?foo=bar") + + req1 = Rack::Request.new(env) + req1.GET.should.equal "foo" => "bar" + req1.params.should.equal "foo" => "bar" + + req2 = MyRequest.new(env) + req2.GET.should.equal "foo" => "bar" + req2.params.should.equal :foo => "bar" + end + + specify "should allow parent request to be instantiated after subclass request" do + env = Rack::MockRequest.env_for("/?foo=bar") + + req1 = MyRequest.new(env) + req1.GET.should.equal "foo" => "bar" + req1.params.should.equal :foo => "bar" + + req2 = Rack::Request.new(env) + req2.GET.should.equal "foo" => "bar" + req2.params.should.equal "foo" => "bar" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_response.rb b/vendor/gems/rack-1.1.0/test/spec_rack_response.rb new file mode 100644 index 0000000..7989013 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_response.rb @@ -0,0 +1,221 @@ +require 'test/spec' +require 'set' + +require 'rack/response' + +context "Rack::Response" do + specify "has sensible default values" do + response = Rack::Response.new + status, header, body = response.finish + status.should.equal 200 + header.should.equal "Content-Type" => "text/html" + body.each { |part| + part.should.equal "" + } + + response = Rack::Response.new + status, header, body = *response + status.should.equal 200 + header.should.equal "Content-Type" => "text/html" + body.each { |part| + part.should.equal "" + } + end + + specify "can be written to" do + response = Rack::Response.new + + status, header, body = response.finish do + response.write "foo" + response.write "bar" + response.write "baz" + end + + parts = [] + body.each { |part| parts << part } + + parts.should.equal ["foo", "bar", "baz"] + end + + specify "can set and read headers" do + response = Rack::Response.new + response["Content-Type"].should.equal "text/html" + response["Content-Type"] = "text/plain" + response["Content-Type"].should.equal "text/plain" + end + + specify "can set cookies" do + response = Rack::Response.new + + response.set_cookie "foo", "bar" + response["Set-Cookie"].should.equal "foo=bar" + response.set_cookie "foo2", "bar2" + response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"] + response.set_cookie "foo3", "bar3" + response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"] + end + + specify "formats the Cookie expiration date accordingly to RFC 2109" do + response = Rack::Response.new + + response.set_cookie "foo", {:value => "bar", :expires => Time.now+10} + response["Set-Cookie"].should.match( + /expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../) + end + + specify "can set secure cookies" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :secure => true} + response["Set-Cookie"].should.equal "foo=bar; secure" + end + + specify "can set http only cookies" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :httponly => true} + response["Set-Cookie"].should.equal "foo=bar; HttpOnly" + end + + specify "can delete cookies" do + response = Rack::Response.new + response.set_cookie "foo", "bar" + response.set_cookie "foo2", "bar2" + response.delete_cookie "foo" + response["Set-Cookie"].should.equal ["foo2=bar2", + "foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"] + end + + specify "can do redirects" do + response = Rack::Response.new + response.redirect "/foo" + status, header, body = response.finish + + status.should.equal 302 + header["Location"].should.equal "/foo" + + response = Rack::Response.new + response.redirect "/foo", 307 + status, header, body = response.finish + + status.should.equal 307 + end + + specify "has a useful constructor" do + r = Rack::Response.new("foo") + status, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.equal "foo" + + r = Rack::Response.new(["foo", "bar"]) + status, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.equal "foobar" + + r = Rack::Response.new(["foo", "bar"].to_set) + r.write "foo" + status, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.equal "foobarfoo" + + r = Rack::Response.new([], 500) + r.status.should.equal 500 + + r = Rack::Response.new([], "200 OK") + r.status.should.equal 200 + end + + specify "has a constructor that can take a block" do + r = Rack::Response.new { |res| + res.status = 404 + res.write "foo" + } + status, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.equal "foo" + status.should.equal 404 + end + + specify "doesn't return invalid responses" do + r = Rack::Response.new(["foo", "bar"], 204) + status, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.be.empty + header["Content-Type"].should.equal nil + + lambda { + Rack::Response.new(Object.new) + }.should.raise(TypeError). + message.should =~ /stringable or iterable required/ + end + + specify "knows if it's empty" do + r = Rack::Response.new + r.should.be.empty + r.write "foo" + r.should.not.be.empty + + r = Rack::Response.new + r.should.be.empty + r.finish + r.should.be.empty + + r = Rack::Response.new + r.should.be.empty + r.finish { } + r.should.not.be.empty + end + + specify "should provide access to the HTTP status" do + res = Rack::Response.new + res.status = 200 + res.should.be.successful + res.should.be.ok + + res.status = 404 + res.should.not.be.successful + res.should.be.client_error + res.should.be.not_found + + res.status = 501 + res.should.not.be.successful + res.should.be.server_error + + res.status = 307 + res.should.be.redirect + end + + specify "should provide access to the HTTP headers" do + res = Rack::Response.new + res["Content-Type"] = "text/yaml" + + res.should.include "Content-Type" + res.headers["Content-Type"].should.equal "text/yaml" + res["Content-Type"].should.equal "text/yaml" + res.content_type.should.equal "text/yaml" + res.content_length.should.be.nil + res.location.should.be.nil + end + + specify "does not add or change Content-Length when #finish()ing" do + res = Rack::Response.new + res.status = 200 + res.finish + res.headers["Content-Length"].should.be.nil + + res = Rack::Response.new + res.status = 200 + res.headers["Content-Length"] = "10" + res.finish + res.headers["Content-Length"].should.equal "10" + end + + specify "updates Content-Length when body appended to using #write" do + res = Rack::Response.new + res.status = 200 + res.headers["Content-Length"].should.be.nil + res.write "Hi" + res.headers["Content-Length"].should.equal "2" + res.write " there" + res.headers["Content-Length"].should.equal "8" + end + +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_rewindable_input.rb b/vendor/gems/rack-1.1.0/test/spec_rack_rewindable_input.rb new file mode 100644 index 0000000..78bebfc --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_rewindable_input.rb @@ -0,0 +1,118 @@ +require 'test/spec' +require 'stringio' +require 'rack/rewindable_input' + +shared_context "a rewindable IO object" do + setup do + @rio = Rack::RewindableInput.new(@io) + end + + teardown do + @rio.close + end + + specify "should be able to handle to read()" do + @rio.read.should.equal "hello world" + end + + specify "should be able to handle to read(nil)" do + @rio.read(nil).should.equal "hello world" + end + + specify "should be able to handle to read(length)" do + @rio.read(1).should.equal "h" + end + + specify "should be able to handle to read(length, buffer)" do + buffer = "" + result = @rio.read(1, buffer) + result.should.equal "h" + result.object_id.should.equal buffer.object_id + end + + specify "should be able to handle to read(nil, buffer)" do + buffer = "" + result = @rio.read(nil, buffer) + result.should.equal "hello world" + result.object_id.should.equal buffer.object_id + end + + specify "should rewind to the beginning when #rewind is called" do + @rio.read(1) + @rio.rewind + @rio.read.should.equal "hello world" + end + + specify "should be able to handle gets" do + @rio.gets.should == "hello world" + end + + specify "should be able to handle each" do + array = [] + @rio.each do |data| + array << data + end + array.should.equal(["hello world"]) + end + + specify "should not buffer into a Tempfile if no data has been read yet" do + @rio.instance_variable_get(:@rewindable_io).should.be.nil + end + + specify "should buffer into a Tempfile when data has been consumed for the first time" do + @rio.read(1) + tempfile = @rio.instance_variable_get(:@rewindable_io) + tempfile.should.not.be.nil + @rio.read(1) + tempfile2 = @rio.instance_variable_get(:@rewindable_io) + tempfile2.should.equal tempfile + end + + specify "should close the underlying tempfile upon calling #close" do + @rio.read(1) + tempfile = @rio.instance_variable_get(:@rewindable_io) + @rio.close + tempfile.should.be.closed + end + + specify "should be possibel to call #close when no data has been buffered yet" do + @rio.close + end + + specify "should be possible to call #close multiple times" do + @rio.close + @rio.close + end +end + +context "Rack::RewindableInput" do + context "given an IO object that is already rewindable" do + setup do + @io = StringIO.new("hello world") + end + + it_should_behave_like "a rewindable IO object" + end + + context "given an IO object that is not rewindable" do + setup do + @io = StringIO.new("hello world") + @io.instance_eval do + undef :rewind + end + end + + it_should_behave_like "a rewindable IO object" + end + + context "given an IO object whose rewind method raises Errno::ESPIPE" do + setup do + @io = StringIO.new("hello world") + def @io.rewind + raise Errno::ESPIPE, "You can't rewind this!" + end + end + + it_should_behave_like "a rewindable IO object" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_runtime.rb b/vendor/gems/rack-1.1.0/test/spec_rack_runtime.rb new file mode 100644 index 0000000..62d8095 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_runtime.rb @@ -0,0 +1,35 @@ +require 'test/spec' +require 'rack/mock' +require 'rack/runtime' + +context "Rack::Runtime" do + specify "sets X-Runtime is none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + response = Rack::Runtime.new(app).call({}) + response[1]['X-Runtime'].should =~ /[\d\.]+/ + end + + specify "does not set the X-Runtime if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] } + response = Rack::Runtime.new(app).call({}) + response[1]['X-Runtime'].should == "foobar" + end + + specify "should allow a suffix to be set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + response = Rack::Runtime.new(app, "Test").call({}) + response[1]['X-Runtime-Test'].should =~ /[\d\.]+/ + end + + specify "should allow multiple timers to be set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + runtime1 = Rack::Runtime.new(app, "App") + runtime2 = Rack::Runtime.new(runtime1, "All") + response = runtime2.call({}) + + response[1]['X-Runtime-App'].should =~ /[\d\.]+/ + response[1]['X-Runtime-All'].should =~ /[\d\.]+/ + + Float(response[1]['X-Runtime-All']).should > Float(response[1]['X-Runtime-App']) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_sendfile.rb b/vendor/gems/rack-1.1.0/test/spec_rack_sendfile.rb new file mode 100644 index 0000000..8cfe201 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_sendfile.rb @@ -0,0 +1,86 @@ +require 'test/spec' +require 'rack/mock' +require 'rack/sendfile' + +context "Rack::File" do + specify "should respond to #to_path" do + Rack::File.new(Dir.pwd).should.respond_to :to_path + end +end + +context "Rack::Sendfile" do + def sendfile_body + res = ['Hello World'] + def res.to_path ; "/tmp/hello.txt" ; end + res + end + + def simple_app(body=sendfile_body) + lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] } + end + + def sendfile_app(body=sendfile_body) + Rack::Sendfile.new(simple_app(body)) + end + + setup do + @request = Rack::MockRequest.new(sendfile_app) + end + + def request(headers={}) + yield @request.get('/', headers) + end + + specify "does nothing when no X-Sendfile-Type header present" do + request do |response| + response.should.be.ok + response.body.should.equal 'Hello World' + response.headers.should.not.include 'X-Sendfile' + end + end + + specify "sets X-Sendfile response header and discards body" do + request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['X-Sendfile'].should.equal '/tmp/hello.txt' + end + end + + specify "sets X-Lighttpd-Send-File response header and discards body" do + request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['X-Lighttpd-Send-File'].should.equal '/tmp/hello.txt' + end + end + + specify "sets X-Accel-Redirect response header and discards body" do + headers = { + 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect', + 'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/' + } + request headers do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['X-Accel-Redirect'].should.equal '/foo/bar/hello.txt' + end + end + + specify 'writes to rack.error when no X-Accel-Mapping is specified' do + request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response| + response.should.be.ok + response.body.should.equal 'Hello World' + response.headers.should.not.include 'X-Accel-Redirect' + response.errors.should.include 'X-Accel-Mapping' + end + end + + specify 'does nothing when body does not respond to #to_path' do + @request = Rack::MockRequest.new(sendfile_app(['Not a file...'])) + request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response| + response.body.should.equal 'Not a file...' + response.headers.should.not.include 'X-Sendfile' + end + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_session_cookie.rb b/vendor/gems/rack-1.1.0/test/spec_rack_session_cookie.rb new file mode 100644 index 0000000..fba3f83 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_session_cookie.rb @@ -0,0 +1,73 @@ +require 'test/spec' + +require 'rack/session/cookie' +require 'rack/mock' +require 'rack/response' + +context "Rack::Session::Cookie" do + incrementor = lambda { |env| + env["rack.session"]["counter"] ||= 0 + env["rack.session"]["counter"] += 1 + Rack::Response.new(env["rack.session"].inspect).to_a + } + + specify "creates a new cookie" do + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/") + res["Set-Cookie"].should.match("rack.session=") + res.body.should.equal '{"counter"=>1}' + end + + specify "loads from a cookie" do + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/") + cookie = res["Set-Cookie"] + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)). + get("/", "HTTP_COOKIE" => cookie) + res.body.should.equal '{"counter"=>2}' + cookie = res["Set-Cookie"] + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)). + get("/", "HTTP_COOKIE" => cookie) + res.body.should.equal '{"counter"=>3}' + end + + specify "survives broken cookies" do + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)). + get("/", "HTTP_COOKIE" => "rack.session=blarghfasel") + res.body.should.equal '{"counter"=>1}' + end + + bigcookie = lambda { |env| + env["rack.session"]["cookie"] = "big" * 3000 + Rack::Response.new(env["rack.session"].inspect).to_a + } + + specify "barks on too big cookies" do + lambda { + Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)). + get("/", :fatal => true) + }.should.raise(Rack::MockRequest::FatalWarning) + end + + specify "loads from a cookie wih integrity hash" do + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/") + cookie = res["Set-Cookie"] + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')). + get("/", "HTTP_COOKIE" => cookie) + res.body.should.equal '{"counter"=>2}' + cookie = res["Set-Cookie"] + res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')). + get("/", "HTTP_COOKIE" => cookie) + res.body.should.equal '{"counter"=>3}' + end + + specify "ignores tampered with session cookies" do + app = Rack::Session::Cookie.new(incrementor, :secret => 'test') + response1 = Rack::MockRequest.new(app).get("/") + _, digest = response1["Set-Cookie"].split("--") + tampered_with_cookie = "hackerman-was-here" + "--" + digest + response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" => + tampered_with_cookie) + + # The tampered-with cookie is ignored, so we get back an identical Set-Cookie + response2["Set-Cookie"].should.equal(response1["Set-Cookie"]) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_session_memcache.rb b/vendor/gems/rack-1.1.0/test/spec_rack_session_memcache.rb new file mode 100644 index 0000000..faac796 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_session_memcache.rb @@ -0,0 +1,273 @@ +require 'test/spec' + +begin + require 'rack/session/memcache' + require 'rack/mock' + require 'rack/response' + require 'thread' + + context "Rack::Session::Memcache" do + session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key] + session_match = /#{session_key}=([0-9a-fA-F]+);/ + incrementor = lambda do |env| + env["rack.session"]["counter"] ||= 0 + env["rack.session"]["counter"] += 1 + Rack::Response.new(env["rack.session"].inspect).to_a + end + drop_session = proc do |env| + env['rack.session.options'][:drop] = true + incrementor.call(env) + end + renew_session = proc do |env| + env['rack.session.options'][:renew] = true + incrementor.call(env) + end + defer_session = proc do |env| + env['rack.session.options'][:defer] = true + incrementor.call(env) + end + + specify "faults on no connection" do + if RUBY_VERSION < "1.9" + lambda do + Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' + end.should.raise + else + lambda do + Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' + end.should.raise ArgumentError + end + end + + specify "connect to existing server" do + test_pool = MemCache.new incrementor, :namespace => 'test:rack:session' + end + + specify "creates a new cookie" do + pool = Rack::Session::Memcache.new(incrementor) + res = Rack::MockRequest.new(pool).get("/") + res["Set-Cookie"].should.match("#{session_key}=") + res.body.should.equal '{"counter"=>1}' + end + + specify "determines session from a cookie" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + res = req.get("/") + cookie = res["Set-Cookie"] + req.get("/", "HTTP_COOKIE" => cookie). + body.should.equal '{"counter"=>2}' + req.get("/", "HTTP_COOKIE" => cookie). + body.should.equal '{"counter"=>3}' + end + + specify "survives nonexistant cookies" do + bad_cookie = "rack.session=blarghfasel" + pool = Rack::Session::Memcache.new(incrementor) + res = Rack::MockRequest.new(pool). + get("/", "HTTP_COOKIE" => bad_cookie) + res.body.should.equal '{"counter"=>1}' + cookie = res["Set-Cookie"][session_match] + cookie.should.not.match(/#{bad_cookie}/) + end + + specify "maintains freshness" do + pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3) + res = Rack::MockRequest.new(pool).get('/') + res.body.should.include '"counter"=>1' + cookie = res["Set-Cookie"] + res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie) + res["Set-Cookie"].should.equal cookie + res.body.should.include '"counter"=>2' + puts 'Sleeping to expire session' if $DEBUG + sleep 4 + res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie) + res["Set-Cookie"].should.not.equal cookie + res.body.should.include '"counter"=>1' + end + + specify "deletes cookies with :drop option" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + drop = Rack::Utils::Context.new(pool, drop_session) + dreq = Rack::MockRequest.new(drop) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.equal nil + res2.body.should.equal '{"counter"=>3}' + + res3 = req.get("/", "HTTP_COOKIE" => cookie) + res3["Set-Cookie"][session_match].should.not.equal session + res3.body.should.equal '{"counter"=>1}' + end + + specify "provides new session id with :renew option" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + renew = Rack::Utils::Context.new(pool, renew_session) + rreq = Rack::MockRequest.new(renew) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + + res2 = rreq.get("/", "HTTP_COOKIE" => cookie) + new_cookie = res2["Set-Cookie"] + new_session = new_cookie[session_match] + new_session.should.not.equal session + res2.body.should.equal '{"counter"=>3}' + + res3 = req.get("/", "HTTP_COOKIE" => new_cookie) + res3["Set-Cookie"][session_match].should.equal new_session + res3.body.should.equal '{"counter"=>4}' + end + + specify "omits cookie with :defer option" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + defer = Rack::Utils::Context.new(pool, defer_session) + dreq = Rack::MockRequest.new(defer) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.equal nil + res2.body.should.equal '{"counter"=>3}' + + res3 = req.get("/", "HTTP_COOKIE" => cookie) + res3["Set-Cookie"][session_match].should.equal session + res3.body.should.equal '{"counter"=>4}' + end + + specify "deep hashes are correctly updated" do + store = nil + hash_check = proc do |env| + session = env['rack.session'] + unless session.include? 'test' + session.update :a => :b, :c => { :d => :e }, + :f => { :g => { :h => :i} }, 'test' => true + else + session[:f][:g][:h] = :j + end + [200, {}, session.inspect] + end + pool = Rack::Session::Memcache.new(hash_check) + req = Rack::MockRequest.new(pool) + + res0 = req.get("/") + session_id = (cookie = res0["Set-Cookie"])[session_match, 1] + ses0 = pool.pool.get(session_id, true) + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + ses1 = pool.pool.get(session_id, true) + + ses1.should.not.equal ses0 + end + + # anyone know how to do this better? + specify "multithread: should cleanly merge sessions" do + next unless $DEBUG + warn 'Running multithread test for Session::Memcache' + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + + res = req.get('/') + res.body.should.equal '{"counter"=>1}' + cookie = res["Set-Cookie"] + session_id = cookie[session_match, 1] + + delta_incrementor = lambda do |env| + # emulate disconjoinment of threading + env['rack.session'] = env['rack.session'].dup + Thread.stop + env['rack.session'][(Time.now.usec*rand).to_i] = true + incrementor.call(env) + end + tses = Rack::Utils::Context.new pool, delta_incrementor + treq = Rack::MockRequest.new(tses) + tnum = rand(7).to_i+5 + r = Array.new(tnum) do + Thread.new(treq) do |run| + run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) + end + end.reverse.map{|t| t.run.join.value } + r.each do |request| + request['Set-Cookie'].should.equal cookie + request.body.should.include '"counter"=>2' + end + + session = pool.pool.get(session_id) + session.size.should.be tnum+1 # counter + session['counter'].should.be 2 # meeeh + + tnum = rand(7).to_i+5 + r = Array.new(tnum) do |i| + delta_time = proc do |env| + env['rack.session'][i] = Time.now + Thread.stop + env['rack.session'] = env['rack.session'].dup + env['rack.session'][i] -= Time.now + incrementor.call(env) + end + app = Rack::Utils::Context.new pool, time_delta + req = Rack::MockRequest.new app + Thread.new(req) do |run| + run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) + end + end.reverse.map{|t| t.run.join.value } + r.each do |request| + request['Set-Cookie'].should.equal cookie + request.body.should.include '"counter"=>3' + end + + session = pool.pool.get(session_id) + session.size.should.be tnum+1 + session['counter'].should.be 3 + + drop_counter = proc do |env| + env['rack.session'].delete 'counter' + env['rack.session']['foo'] = 'bar' + [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect] + end + tses = Rack::Utils::Context.new pool, drop_counter + treq = Rack::MockRequest.new(tses) + tnum = rand(7).to_i+5 + r = Array.new(tnum) do + Thread.new(treq) do |run| + run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) + end + end.reverse.map{|t| t.run.join.value } + r.each do |request| + request['Set-Cookie'].should.equal cookie + request.body.should.include '"foo"=>"bar"' + end + + session = pool.pool.get(session_id) + session.size.should.be r.size+1 + session['counter'].should.be.nil? + session['foo'].should.equal 'bar' + end + end +rescue RuntimeError + $stderr.puts "Skipping Rack::Session::Memcache tests. Start memcached and try again." +rescue LoadError + $stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again." +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_session_pool.rb b/vendor/gems/rack-1.1.0/test/spec_rack_session_pool.rb new file mode 100644 index 0000000..6be382e --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_session_pool.rb @@ -0,0 +1,172 @@ +require 'test/spec' + +require 'rack/session/pool' +require 'rack/mock' +require 'rack/response' +require 'thread' + +context "Rack::Session::Pool" do + session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key] + session_match = /#{session_key}=[0-9a-fA-F]+;/ + incrementor = lambda do |env| + env["rack.session"]["counter"] ||= 0 + env["rack.session"]["counter"] += 1 + Rack::Response.new(env["rack.session"].inspect).to_a + end + drop_session = proc do |env| + env['rack.session.options'][:drop] = true + incrementor.call(env) + end + renew_session = proc do |env| + env['rack.session.options'][:renew] = true + incrementor.call(env) + end + defer_session = proc do |env| + env['rack.session.options'][:defer] = true + incrementor.call(env) + end + + specify "creates a new cookie" do + pool = Rack::Session::Pool.new(incrementor) + res = Rack::MockRequest.new(pool).get("/") + res["Set-Cookie"].should.match session_match + res.body.should.equal '{"counter"=>1}' + end + + specify "determines session from a cookie" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + cookie = req.get("/")["Set-Cookie"] + req.get("/", "HTTP_COOKIE" => cookie). + body.should.equal '{"counter"=>2}' + req.get("/", "HTTP_COOKIE" => cookie). + body.should.equal '{"counter"=>3}' + end + + specify "survives nonexistant cookies" do + pool = Rack::Session::Pool.new(incrementor) + res = Rack::MockRequest.new(pool). + get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel") + res.body.should.equal '{"counter"=>1}' + end + + specify "deletes cookies with :drop option" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + drop = Rack::Utils::Context.new(pool, drop_session) + dreq = Rack::MockRequest.new(drop) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + pool.pool.size.should.be 1 + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + pool.pool.size.should.be 1 + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.equal nil + res2.body.should.equal '{"counter"=>3}' + pool.pool.size.should.be 0 + + res3 = req.get("/", "HTTP_COOKIE" => cookie) + res3["Set-Cookie"][session_match].should.not.equal session + res3.body.should.equal '{"counter"=>1}' + pool.pool.size.should.be 1 + end + + specify "provides new session id with :renew option" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + renew = Rack::Utils::Context.new(pool, renew_session) + rreq = Rack::MockRequest.new(renew) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + pool.pool.size.should.be 1 + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + pool.pool.size.should.be 1 + + res2 = rreq.get("/", "HTTP_COOKIE" => cookie) + new_cookie = res2["Set-Cookie"] + new_session = new_cookie[session_match] + new_session.should.not.equal session + res2.body.should.equal '{"counter"=>3}' + pool.pool.size.should.be 1 + + res3 = req.get("/", "HTTP_COOKIE" => new_cookie) + res3["Set-Cookie"][session_match].should.equal new_session + res3.body.should.equal '{"counter"=>4}' + pool.pool.size.should.be 1 + end + + specify "omits cookie with :defer option" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + defer = Rack::Utils::Context.new(pool, defer_session) + dreq = Rack::MockRequest.new(defer) + + res0 = req.get("/") + session = (cookie = res0["Set-Cookie"])[session_match] + res0.body.should.equal '{"counter"=>1}' + pool.pool.size.should.be 1 + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"][session_match].should.equal session + res1.body.should.equal '{"counter"=>2}' + pool.pool.size.should.be 1 + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.equal nil + res2.body.should.equal '{"counter"=>3}' + pool.pool.size.should.be 1 + + res3 = req.get("/", "HTTP_COOKIE" => cookie) + res3["Set-Cookie"][session_match].should.equal session + res3.body.should.equal '{"counter"=>4}' + pool.pool.size.should.be 1 + end + + # anyone know how to do this better? + specify "multithread: should merge sessions" do + next unless $DEBUG + warn 'Running multithread tests for Session::Pool' + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + + res = req.get('/') + res.body.should.equal '{"counter"=>1}' + cookie = res["Set-Cookie"] + sess_id = cookie[/#{pool.key}=([^,;]+)/,1] + + delta_incrementor = lambda do |env| + # emulate disconjoinment of threading + env['rack.session'] = env['rack.session'].dup + Thread.stop + env['rack.session'][(Time.now.usec*rand).to_i] = true + incrementor.call(env) + end + tses = Rack::Utils::Context.new pool, delta_incrementor + treq = Rack::MockRequest.new(tses) + tnum = rand(7).to_i+5 + r = Array.new(tnum) do + Thread.new(treq) do |run| + run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) + end + end.reverse.map{|t| t.run.join.value } + r.each do |res| + res['Set-Cookie'].should.equal cookie + res.body.should.include '"counter"=>2' + end + + session = pool.pool[sess_id] + session.size.should.be tnum+1 # counter + session['counter'].should.be 2 # meeeh + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_showexceptions.rb b/vendor/gems/rack-1.1.0/test/spec_rack_showexceptions.rb new file mode 100644 index 0000000..bdbc120 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_showexceptions.rb @@ -0,0 +1,21 @@ +require 'test/spec' + +require 'rack/showexceptions' +require 'rack/mock' + +context "Rack::ShowExceptions" do + specify "catches exceptions" do + res = nil + req = Rack::MockRequest.new(Rack::ShowExceptions.new(lambda { |env| + raise RuntimeError + })) + lambda { + res = req.get("/") + }.should.not.raise + res.should.be.a.server_error + res.status.should.equal 500 + + res.should =~ /RuntimeError/ + res.should =~ /ShowExceptions/ + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_showstatus.rb b/vendor/gems/rack-1.1.0/test/spec_rack_showstatus.rb new file mode 100644 index 0000000..7870013 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_showstatus.rb @@ -0,0 +1,72 @@ +require 'test/spec' + +require 'rack/showstatus' +require 'rack/mock' + +context "Rack::ShowStatus" do + specify "should provide a default status message" do + req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| + [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] + })) + + res = req.get("/", :lint => true) + res.should.be.not_found + res.should.be.not.empty + + res["Content-Type"].should.equal("text/html") + res.should =~ /404/ + res.should =~ /Not Found/ + end + + specify "should let the app provide additional information" do + req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| + env["rack.showstatus.detail"] = "gone too meta." + [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] + })) + + res = req.get("/", :lint => true) + res.should.be.not_found + res.should.be.not.empty + + res["Content-Type"].should.equal("text/html") + res.should =~ /404/ + res.should =~ /Not Found/ + res.should =~ /too meta/ + end + + specify "should not replace existing messages" do + req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| + [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]] + })) + res = req.get("/", :lint => true) + res.should.be.not_found + + res.body.should == "foo!" + end + + specify "should pass on original headers" do + headers = {"WWW-Authenticate" => "Basic blah"} + + req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| [401, headers, []] })) + res = req.get("/", :lint => true) + + res["WWW-Authenticate"].should.equal("Basic blah") + end + + specify "should replace existing messages if there is detail" do + req = Rack::MockRequest.new(Rack::ShowStatus.new(lambda { |env| + env["rack.showstatus.detail"] = "gone too meta." + [404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]] + })) + + res = req.get("/", :lint => true) + res.should.be.not_found + res.should.be.not.empty + + res["Content-Type"].should.equal("text/html") + res["Content-Length"].should.not.equal("4") + res.should =~ /404/ + res.should =~ /too meta/ + res.body.should.not =~ /foo/ + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_static.rb b/vendor/gems/rack-1.1.0/test/spec_rack_static.rb new file mode 100644 index 0000000..19d2ecb --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_static.rb @@ -0,0 +1,37 @@ +require 'test/spec' + +require 'rack/static' +require 'rack/mock' + +class DummyApp + def call(env) + [200, {}, ["Hello World"]] + end +end + +context "Rack::Static" do + root = File.expand_path(File.dirname(__FILE__)) + OPTIONS = {:urls => ["/cgi"], :root => root} + + setup do + @request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS)) + end + + specify "serves files" do + res = @request.get("/cgi/test") + res.should.be.ok + res.body.should =~ /ruby/ + end + + specify "404s if url root is known but it can't find the file" do + res = @request.get("/cgi/foo") + res.should.be.not_found + end + + specify "calls down the chain if url root is not known" do + res = @request.get("/something/else") + res.should.be.ok + res.body.should == "Hello World" + end + +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_thin.rb b/vendor/gems/rack-1.1.0/test/spec_rack_thin.rb new file mode 100644 index 0000000..324f649 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_thin.rb @@ -0,0 +1,91 @@ +require 'test/spec' + +begin +require 'rack/handler/thin' +require 'testrequest' +require 'timeout' + +context "Rack::Handler::Thin" do + include TestRequest::Helpers + + setup do + @app = Rack::Lint.new(TestRequest.new) + @server = nil + Thin::Logging.silent = true + @thread = Thread.new do + Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server| + @server = server + end + end + Thread.pass until @server && @server.running? + end + + specify "should respond" do + lambda { + GET("/") + }.should.not.raise + end + + specify "should be a Thin" do + GET("/") + status.should.be 200 + response["SERVER_SOFTWARE"].should =~ /thin/ + response["HTTP_VERSION"].should.equal "HTTP/1.1" + response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" + response["SERVER_PORT"].should.equal "9204" + response["SERVER_NAME"].should.equal "0.0.0.0" + end + + specify "should have rack headers" do + GET("/") + response["rack.version"].should.equal [0,3] + response["rack.multithread"].should.be false + response["rack.multiprocess"].should.be false + response["rack.run_once"].should.be false + end + + specify "should have CGI headers on GET" do + GET("/") + response["REQUEST_METHOD"].should.equal "GET" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.be.equal "/" + response["QUERY_STRING"].should.equal "" + response["test.postdata"].should.equal "" + + GET("/test/foo?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["REQUEST_PATH"].should.equal "/test/foo" + response["PATH_INFO"].should.equal "/test/foo" + response["QUERY_STRING"].should.equal "quux=1" + end + + specify "should have CGI headers on POST" do + POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) + status.should.equal 200 + response["REQUEST_METHOD"].should.equal "POST" + response["REQUEST_PATH"].should.equal "/" + response["QUERY_STRING"].should.equal "" + response["HTTP_X_TEST_HEADER"].should.equal "42" + response["test.postdata"].should.equal "rack-form-data=23" + end + + specify "should support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + specify "should set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + teardown do + @server.stop! + @thread.kill + end +end + +rescue LoadError + $stderr.puts "Skipping Rack::Handler::Thin tests (Thin is required). `gem install thin` and try again." +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_urlmap.rb b/vendor/gems/rack-1.1.0/test/spec_rack_urlmap.rb new file mode 100644 index 0000000..3d8fe60 --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_urlmap.rb @@ -0,0 +1,215 @@ +require 'test/spec' + +require 'rack/urlmap' +require 'rack/mock' + +context "Rack::URLMap" do + specify "dispatches paths correctly" do + app = lambda { |env| + [200, { + 'X-ScriptName' => env['SCRIPT_NAME'], + 'X-PathInfo' => env['PATH_INFO'], + 'Content-Type' => 'text/plain' + }, [""]] + } + map = Rack::URLMap.new({ + 'http://foo.org/bar' => app, + '/foo' => app, + '/foo/bar' => app + }) + + res = Rack::MockRequest.new(map).get("/") + res.should.be.not_found + + res = Rack::MockRequest.new(map).get("/qux") + res.should.be.not_found + + res = Rack::MockRequest.new(map).get("/foo") + res.should.be.ok + res["X-ScriptName"].should.equal "/foo" + res["X-PathInfo"].should.equal "" + + res = Rack::MockRequest.new(map).get("/foo/") + res.should.be.ok + res["X-ScriptName"].should.equal "/foo" + res["X-PathInfo"].should.equal "/" + + res = Rack::MockRequest.new(map).get("/foo/bar") + res.should.be.ok + res["X-ScriptName"].should.equal "/foo/bar" + res["X-PathInfo"].should.equal "" + + res = Rack::MockRequest.new(map).get("/foo/bar/") + res.should.be.ok + res["X-ScriptName"].should.equal "/foo/bar" + res["X-PathInfo"].should.equal "/" + + res = Rack::MockRequest.new(map).get("/foo///bar//quux") + res.status.should.equal 200 + res.should.be.ok + res["X-ScriptName"].should.equal "/foo/bar" + res["X-PathInfo"].should.equal "//quux" + + res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh") + res.should.be.ok + res["X-ScriptName"].should.equal "/bleh/foo" + res["X-PathInfo"].should.equal "/quux" + + res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org') + res.should.be.ok + res["X-ScriptName"].should.equal "/bar" + res["X-PathInfo"].should.be.empty + + res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org') + res.should.be.ok + res["X-ScriptName"].should.equal "/bar" + res["X-PathInfo"].should.equal '/' + end + + + specify "dispatches hosts correctly" do + map = Rack::URLMap.new("http://foo.org/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "foo.org", + "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"], + }, [""]]}, + "http://subdomain.foo.org/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "subdomain.foo.org", + "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"], + }, [""]]}, + "http://bar.org/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "bar.org", + "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"], + }, [""]]}, + "/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "default.org", + "X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"], + }, [""]]} + ) + + res = Rack::MockRequest.new(map).get("/") + res.should.be.ok + res["X-Position"].should.equal "default.org" + + res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org") + res.should.be.ok + res["X-Position"].should.equal "bar.org" + + res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org") + res.should.be.ok + res["X-Position"].should.equal "foo.org" + + res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org") + res.should.be.ok + res["X-Position"].should.equal "subdomain.foo.org" + + res = Rack::MockRequest.new(map).get("http://foo.org/") + res.should.be.ok + res["X-Position"].should.equal "default.org" + + res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org") + res.should.be.ok + res["X-Position"].should.equal "default.org" + + res = Rack::MockRequest.new(map).get("/", + "HTTP_HOST" => "example.org:9292", + "SERVER_PORT" => "9292") + res.should.be.ok + res["X-Position"].should.equal "default.org" + end + + specify "should be nestable" do + map = Rack::URLMap.new("/foo" => + Rack::URLMap.new("/bar" => + Rack::URLMap.new("/quux" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "/foo/bar/quux", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"], + }, [""]]} + ))) + + res = Rack::MockRequest.new(map).get("/foo/bar") + res.should.be.not_found + + res = Rack::MockRequest.new(map).get("/foo/bar/quux") + res.should.be.ok + res["X-Position"].should.equal "/foo/bar/quux" + res["X-PathInfo"].should.equal "" + res["X-ScriptName"].should.equal "/foo/bar/quux" + end + + specify "should route root apps correctly" do + map = Rack::URLMap.new("/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "root", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"] + }, [""]]}, + "/foo" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "foo", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"] + }, [""]]} + ) + + res = Rack::MockRequest.new(map).get("/foo/bar") + res.should.be.ok + res["X-Position"].should.equal "foo" + res["X-PathInfo"].should.equal "/bar" + res["X-ScriptName"].should.equal "/foo" + + res = Rack::MockRequest.new(map).get("/foo") + res.should.be.ok + res["X-Position"].should.equal "foo" + res["X-PathInfo"].should.equal "" + res["X-ScriptName"].should.equal "/foo" + + res = Rack::MockRequest.new(map).get("/bar") + res.should.be.ok + res["X-Position"].should.equal "root" + res["X-PathInfo"].should.equal "/bar" + res["X-ScriptName"].should.equal "" + + res = Rack::MockRequest.new(map).get("") + res.should.be.ok + res["X-Position"].should.equal "root" + res["X-PathInfo"].should.equal "/" + res["X-ScriptName"].should.equal "" + end + + specify "should not squeeze slashes" do + map = Rack::URLMap.new("/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "root", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"] + }, [""]]}, + "/foo" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "foo", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"] + }, [""]]} + ) + + res = Rack::MockRequest.new(map).get("/http://example.org/bar") + res.should.be.ok + res["X-Position"].should.equal "root" + res["X-PathInfo"].should.equal "/http://example.org/bar" + res["X-ScriptName"].should.equal "" + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_utils.rb b/vendor/gems/rack-1.1.0/test/spec_rack_utils.rb new file mode 100644 index 0000000..269a52b --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_utils.rb @@ -0,0 +1,552 @@ +require 'test/spec' + +require 'rack/utils' +require 'rack/lint' +require 'rack/mock' + +context "Rack::Utils" do + specify "should escape correctly" do + Rack::Utils.escape("fobar").should.equal "fo%3Co%3Ebar" + Rack::Utils.escape("a space").should.equal "a+space" + Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\"). + should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C" + end + + specify "should escape correctly for multibyte characters" do + matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto + matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding + Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8' + matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto + matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding + Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8' + end + + specify "should unescape correctly" do + Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fobar" + Rack::Utils.unescape("a+space").should.equal "a space" + Rack::Utils.unescape("a%20space").should.equal "a space" + Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C"). + should.equal "q1!2\"'w$5&7/z8)?\\" + end + + specify "should parse query strings correctly" do + Rack::Utils.parse_query("foo=bar"). + should.equal "foo" => "bar" + Rack::Utils.parse_query("foo=\"bar\""). + should.equal "foo" => "bar" + Rack::Utils.parse_query("foo=bar&foo=quux"). + should.equal "foo" => ["bar", "quux"] + Rack::Utils.parse_query("foo=1&bar=2"). + should.equal "foo" => "1", "bar" => "2" + Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"). + should.equal "my weird field" => "q1!2\"'w$5&7/z8)?" + Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar" + end + + specify "should parse nested query strings correctly" do + Rack::Utils.parse_nested_query("foo"). + should.equal "foo" => nil + Rack::Utils.parse_nested_query("foo="). + should.equal "foo" => "" + Rack::Utils.parse_nested_query("foo=bar"). + should.equal "foo" => "bar" + Rack::Utils.parse_nested_query("foo=\"bar\""). + should.equal "foo" => "bar" + + Rack::Utils.parse_nested_query("foo=bar&foo=quux"). + should.equal "foo" => "quux" + Rack::Utils.parse_nested_query("foo&foo="). + should.equal "foo" => "" + Rack::Utils.parse_nested_query("foo=1&bar=2"). + should.equal "foo" => "1", "bar" => "2" + Rack::Utils.parse_nested_query("&foo=1&&bar=2"). + should.equal "foo" => "1", "bar" => "2" + Rack::Utils.parse_nested_query("foo&bar="). + should.equal "foo" => nil, "bar" => "" + Rack::Utils.parse_nested_query("foo=bar&baz="). + should.equal "foo" => "bar", "baz" => "" + Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"). + should.equal "my weird field" => "q1!2\"'w$5&7/z8)?" + + Rack::Utils.parse_nested_query("foo[]"). + should.equal "foo" => [nil] + Rack::Utils.parse_nested_query("foo[]="). + should.equal "foo" => [""] + Rack::Utils.parse_nested_query("foo[]=bar"). + should.equal "foo" => ["bar"] + + Rack::Utils.parse_nested_query("foo[]=1&foo[]=2"). + should.equal "foo" => ["1", "2"] + Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3"). + should.equal "foo" => "bar", "baz" => ["1", "2", "3"] + Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3"). + should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"] + + Rack::Utils.parse_nested_query("x[y][z]=1"). + should.equal "x" => {"y" => {"z" => "1"}} + Rack::Utils.parse_nested_query("x[y][z][]=1"). + should.equal "x" => {"y" => {"z" => ["1"]}} + Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2"). + should.equal "x" => {"y" => {"z" => "2"}} + Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2"). + should.equal "x" => {"y" => {"z" => ["1", "2"]}} + + Rack::Utils.parse_nested_query("x[y][][z]=1"). + should.equal "x" => {"y" => [{"z" => "1"}]} + Rack::Utils.parse_nested_query("x[y][][z][]=1"). + should.equal "x" => {"y" => [{"z" => ["1"]}]} + Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2"). + should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]} + + Rack::Utils.parse_nested_query("x[y][][v][w]=1"). + should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]} + Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2"). + should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]} + + Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2"). + should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]} + Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3"). + should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]} + + lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }. + should.raise(TypeError). + message.should.equal "expected Hash (got String) for param `y'" + + lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }. + should.raise(TypeError). + message.should.equal "expected Array (got Hash) for param `x'" + + lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }. + should.raise(TypeError). + message.should.equal "expected Array (got String) for param `y'" + end + + specify "should build query strings correctly" do + Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar" + Rack::Utils.build_query("foo" => ["bar", "quux"]). + should.equal "foo=bar&foo=quux" + Rack::Utils.build_query("foo" => "1", "bar" => "2"). + should.equal "foo=1&bar=2" + Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?"). + should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F" + end + + specify "should build nested query strings correctly" do + Rack::Utils.build_nested_query("foo" => nil).should.equal "foo" + Rack::Utils.build_nested_query("foo" => "").should.equal "foo=" + Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar" + + Rack::Utils.build_nested_query("foo" => "1", "bar" => "2"). + should.equal "foo=1&bar=2" + Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?"). + should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F" + + Rack::Utils.build_nested_query("foo" => [nil]). + should.equal "foo[]" + Rack::Utils.build_nested_query("foo" => [""]). + should.equal "foo[]=" + Rack::Utils.build_nested_query("foo" => ["bar"]). + should.equal "foo[]=bar" + + # The ordering of the output query string is unpredictable with 1.8's + # unordered hash. Test that build_nested_query performs the inverse + # function of parse_nested_query. + [{"foo" => nil, "bar" => ""}, + {"foo" => "bar", "baz" => ""}, + {"foo" => ["1", "2"]}, + {"foo" => "bar", "baz" => ["1", "2", "3"]}, + {"foo" => ["bar"], "baz" => ["1", "2", "3"]}, + {"foo" => ["1", "2"]}, + {"foo" => "bar", "baz" => ["1", "2", "3"]}, + {"x" => {"y" => {"z" => "1"}}}, + {"x" => {"y" => {"z" => ["1"]}}}, + {"x" => {"y" => {"z" => ["1", "2"]}}}, + {"x" => {"y" => [{"z" => "1"}]}}, + {"x" => {"y" => [{"z" => ["1"]}]}}, + {"x" => {"y" => [{"z" => "1", "w" => "2"}]}}, + {"x" => {"y" => [{"v" => {"w" => "1"}}]}}, + {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}}, + {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}}, + {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}} + ].each { |params| + qs = Rack::Utils.build_nested_query(params) + Rack::Utils.parse_nested_query(qs).should.equal params + } + + lambda { Rack::Utils.build_nested_query("foo=bar") }. + should.raise(ArgumentError). + message.should.equal "value must be a Hash" + end + + specify "should figure out which encodings are acceptable" do + helper = lambda do |a, b| + request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a)) + Rack::Utils.select_best_encoding(a, b) + end + + helper.call(%w(), [["x", 1]]).should.equal(nil) + helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil) + helper.call(%w(identity), [["*", 0.0]]).should.equal(nil) + + helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity") + + helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress") + helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip") + + helper.call(%w(foo bar identity), []).should.equal("identity") + helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo") + helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar") + + helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity") + helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity") + end + + specify "should return the bytesize of String" do + Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6 + end + + specify "should return status code for integer" do + Rack::Utils.status_code(200).should.equal 200 + end + + specify "should return status code for string" do + Rack::Utils.status_code("200").should.equal 200 + end + + specify "should return status code for symbol" do + Rack::Utils.status_code(:ok).should.equal 200 + end +end + +context "Rack::Utils::HeaderHash" do + specify "should retain header case" do + h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...") + h['ETag'] = 'Boo!' + h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!' + end + + specify "should check existence of keys case insensitively" do + h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...") + h.should.include 'content-md5' + h.should.not.include 'ETag' + end + + specify "should merge case-insensitively" do + h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123') + merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR') + merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR' + end + + specify "should overwrite case insensitively and assume the new key's case" do + h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz") + h["foo-bar"] = "bizzle" + h["FOO-BAR"].should.equal "bizzle" + h.length.should.equal 1 + h.to_hash.should.equal "foo-bar" => "bizzle" + end + + specify "should be converted to real Hash" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.to_hash.should.be.instance_of Hash + end + + specify "should convert Array values to Strings when converting to Hash" do + h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"]) + h.to_hash.should.equal({ "foo" => "bar\nbaz" }) + end + + specify "should replace hashes correctly" do + h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz") + j = {"foo" => "bar"} + h.replace(j) + h["foo"].should.equal "bar" + end + + specify "should be able to delete the given key case-sensitively" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.delete("foo") + h["foo"].should.be.nil + h["FOO"].should.be.nil + end + + specify "should be able to delete the given key case-insensitively" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.delete("FOO") + h["foo"].should.be.nil + h["FOO"].should.be.nil + end + + specify "should return the deleted value when #delete is called on an existing key" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.delete("Foo").should.equal("bar") + end + + specify "should return nil when #delete is called on a non-existant key" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.delete("Hello").should.be.nil + end + + specify "should avoid unnecessary object creation if possible" do + a = Rack::Utils::HeaderHash.new("foo" => "bar") + b = Rack::Utils::HeaderHash.new(a) + b.object_id.should.equal(a.object_id) + b.should.equal(a) + end + + specify "should convert Array values to Strings when responding to #each" do + h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"]) + h.each do |k,v| + k.should.equal("foo") + v.should.equal("bar\nbaz") + end + end + +end + +context "Rack::Utils::Context" do + class ContextTest + attr_reader :app + def initialize app; @app=app; end + def call env; context env; end + def context env, app=@app; app.call(env); end + end + test_target1 = proc{|e| e.to_s+' world' } + test_target2 = proc{|e| e.to_i+2 } + test_target3 = proc{|e| nil } + test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] } + test_app = ContextTest.new test_target4 + + specify "should set context correctly" do + test_app.app.should.equal test_target4 + c1 = Rack::Utils::Context.new(test_app, test_target1) + c1.for.should.equal test_app + c1.app.should.equal test_target1 + c2 = Rack::Utils::Context.new(test_app, test_target2) + c2.for.should.equal test_app + c2.app.should.equal test_target2 + end + + specify "should alter app on recontexting" do + c1 = Rack::Utils::Context.new(test_app, test_target1) + c2 = c1.recontext(test_target2) + c2.for.should.equal test_app + c2.app.should.equal test_target2 + c3 = c2.recontext(test_target3) + c3.for.should.equal test_app + c3.app.should.equal test_target3 + end + + specify "should run different apps" do + c1 = Rack::Utils::Context.new test_app, test_target1 + c2 = c1.recontext test_target2 + c3 = c2.recontext test_target3 + c4 = c3.recontext test_target4 + a4 = Rack::Lint.new c4 + a5 = Rack::Lint.new test_app + r1 = c1.call('hello') + r1.should.equal 'hello world' + r2 = c2.call(2) + r2.should.equal 4 + r3 = c3.call(:misc_symbol) + r3.should.be.nil + r4 = Rack::MockRequest.new(a4).get('/') + r4.status.should.be 200 + r5 = Rack::MockRequest.new(a5).get('/') + r5.status.should.be 200 + r4.body.should.equal r5.body + end +end + +context "Rack::Utils::Multipart" do + specify "should return nil if content type is not multipart" do + env = Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'application/x-www-form-urlencoded') + Rack::Utils::Multipart.parse_multipart(env).should.equal nil + end + + specify "should parse multipart upload with text file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:type].should.equal "text/plain" + params["files"][:filename].should.equal "file1.txt" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + specify "should parse multipart upload with nested parameters" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:nested)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["foo"]["submit-name"].should.equal "Larry" + params["foo"]["files"][:type].should.equal "text/plain" + params["foo"]["files"][:filename].should.equal "file1.txt" + params["foo"]["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"foo[files]\"; filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["foo"]["files"][:name].should.equal "foo[files]" + params["foo"]["files"][:tempfile].read.should.equal "contents" + end + + specify "should parse multipart upload with binary file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:binary)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:type].should.equal "image/png" + params["files"][:filename].should.equal "rack-logo.png" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; filename=\"rack-logo.png\"\r\n" + + "Content-Type: image/png\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.length.should.equal 26473 + end + + specify "should parse multipart upload with empty file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:type].should.equal "text/plain" + params["files"][:filename].should.equal "file1.txt" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "" + end + + specify "should parse multipart upload with filename with semicolons" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["files"][:type].should.equal "text/plain" + params["files"][:filename].should.equal "fi;le1.txt" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; filename=\"fi;le1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + specify "should not include file params if no file was selected" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"].should.equal nil + params.keys.should.not.include "files" + end + + specify "should parse IE multipart upload and clean up filename" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:ie)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["files"][:type].should.equal "text/plain" + params["files"][:filename].should.equal "file1.txt" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; " + + 'filename="C:\Documents and Settings\Administrator\Desktop\file1.txt"' + + "\r\nContent-Type: text/plain\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + specify "rewinds input after parsing upload" do + options = multipart_fixture(:text) + input = options[:input] + env = Rack::MockRequest.env_for("/", options) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:filename].should.equal "file1.txt" + input.read.length.should.equal 197 + end + + specify "builds multipart body" do + files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt")) + data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files) + + options = { + "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", + "CONTENT_LENGTH" => data.length.to_s, + :input => StringIO.new(data) + } + env = Rack::MockRequest.env_for("/", options) + params = Rack::Utils::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:filename].should.equal "file1.txt" + params["files"][:tempfile].read.should.equal "contents" + end + + specify "builds nested multipart body" do + files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt")) + data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}]) + + options = { + "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", + "CONTENT_LENGTH" => data.length.to_s, + :input => StringIO.new(data) + } + env = Rack::MockRequest.env_for("/", options) + params = Rack::Utils::Multipart.parse_multipart(env) + params["people"][0]["submit-name"].should.equal "Larry" + params["people"][0]["files"][:filename].should.equal "file1.txt" + params["people"][0]["files"][:tempfile].read.should.equal "contents" + end + + specify "can parse fields that end at the end of the buffer" do + input = File.read(multipart_file("bad_robots")) + + req = Rack::Request.new Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => "multipart/form-data, boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon", + "CONTENT_LENGTH" => input.size, + :input => input) + + req.POST['file.path'].should.equal "/var/tmp/uploads/4/0001728414" + req.POST['addresses'].should.not.equal nil + end + + specify "builds complete params with the chunk size of 16384 slicing exactly on boundary" do + data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n") + options = { + "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo", + "CONTENT_LENGTH" => data.length.to_s, + :input => StringIO.new(data) + } + env = Rack::MockRequest.env_for("/", options) + params = Rack::Utils::Multipart.parse_multipart(env) + + params.should.not.equal nil + params.keys.should.include "AAAAAAAAAAAAAAAAAAA" + params["AAAAAAAAAAAAAAAAAAA"].keys.should.include "PLAPLAPLA_MEMMEMMEMM_ATTRATTRER" + params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"].keys.should.include "new" + params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"].keys.should.include "-2" + params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"].keys.should.include "ba_unit_id" + params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017" + end + + specify "should return nil if no UploadedFiles were used" do + data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}]) + data.should.equal nil + end + + specify "should raise ArgumentError if params is not a Hash" do + lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }. + should.raise(ArgumentError). + message.should.equal "value must be a Hash" + end + + private + def multipart_fixture(name) + file = multipart_file(name) + data = File.open(file, 'rb') { |io| io.read } + + type = "multipart/form-data; boundary=AaB03x" + length = data.respond_to?(:bytesize) ? data.bytesize : data.size + + { "CONTENT_TYPE" => type, + "CONTENT_LENGTH" => length.to_s, + :input => StringIO.new(data) } + end + + def multipart_file(name) + File.join(File.dirname(__FILE__), "multipart", name.to_s) + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rack_webrick.rb b/vendor/gems/rack-1.1.0/test/spec_rack_webrick.rb new file mode 100644 index 0000000..599425c --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rack_webrick.rb @@ -0,0 +1,130 @@ +require 'test/spec' + +require 'rack/handler/webrick' +require 'rack/lint' +require 'rack/response' +require 'testrequest' + +Thread.abort_on_exception = true + +context "Rack::Handler::WEBrick" do + include TestRequest::Helpers + + setup do + @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0', + :Port => @port=9202, + :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN), + :AccessLog => []) + @server.mount "/test", Rack::Handler::WEBrick, + Rack::Lint.new(TestRequest.new) + Thread.new { @server.start } + trap(:INT) { @server.shutdown } + end + + specify "should respond" do + lambda { + GET("/test") + }.should.not.raise + end + + specify "should be a WEBrick" do + GET("/test") + status.should.be 200 + response["SERVER_SOFTWARE"].should =~ /WEBrick/ + response["HTTP_VERSION"].should.equal "HTTP/1.1" + response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" + response["SERVER_PORT"].should.equal "9202" + response["SERVER_NAME"].should.equal "0.0.0.0" + end + + specify "should have rack headers" do + GET("/test") + response["rack.version"].should.equal [1,1] + response["rack.multithread"].should.be true + response["rack.multiprocess"].should.be false + response["rack.run_once"].should.be false + end + + specify "should have CGI headers on GET" do + GET("/test") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.be.equal "" + response["QUERY_STRING"].should.equal "" + response["test.postdata"].should.equal "" + + GET("/test/foo?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "/foo" + response["QUERY_STRING"].should.equal "quux=1" + + GET("/test/foo%25encoding?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["PATH_INFO"].should.equal "/foo%25encoding" + response["QUERY_STRING"].should.equal "quux=1" + end + + specify "should have CGI headers on POST" do + POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) + status.should.equal 200 + response["REQUEST_METHOD"].should.equal "POST" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/" + response["QUERY_STRING"].should.equal "" + response["HTTP_X_TEST_HEADER"].should.equal "42" + response["test.postdata"].should.equal "rack-form-data=23" + end + + specify "should support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + specify "should set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + specify "should correctly set cookies" do + @server.mount "/cookie-test", Rack::Handler::WEBrick, + Rack::Lint.new(lambda { |req| + res = Rack::Response.new + res.set_cookie "one", "1" + res.set_cookie "two", "2" + res.finish + }) + + Net::HTTP.start(@host, @port) { |http| + res = http.get("/cookie-test") + res.code.to_i.should.equal 200 + res.get_fields("set-cookie").should.equal ["one=1", "two=2"] + } + end + + specify "should provide a .run" do + block_ran = false + catch(:done) { + Rack::Handler::WEBrick.run(lambda {}, + {:Port => 9210, + :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN), + :AccessLog => []}) { |server| + block_ran = true + server.should.be.kind_of WEBrick::HTTPServer + @s = server + throw :done + } + } + block_ran.should.be true + @s.shutdown + end + + teardown do + @server.shutdown + end +end diff --git a/vendor/gems/rack-1.1.0/test/spec_rackup.rb b/vendor/gems/rack-1.1.0/test/spec_rackup.rb new file mode 100644 index 0000000..d9926fd --- /dev/null +++ b/vendor/gems/rack-1.1.0/test/spec_rackup.rb @@ -0,0 +1,154 @@ +require 'test/spec' +require 'testrequest' +require 'rack/server' +require 'open3' + +begin +require "mongrel" + +context "rackup" do + include TestRequest::Helpers + + def run_rackup(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + flags = args.first + @host = options[:host] || "0.0.0.0" + @port = options[:port] || 9292 + + Dir.chdir("#{root}/test/rackup") do + @in, @rackup, @err = Open3.popen3("#{Gem.ruby} -S #{rackup} #{flags}") + end + + return if options[:port] == false + + # Wait until the server is available + begin + GET("/") + rescue + sleep 0.05 + retry + end + end + + def output + @rackup.read + end + + after do + # This doesn't actually return a response, so we rescue + GET "/die" rescue nil + + Dir["#{root}/**/*.pid"].each do |file| + File.delete(file) + end + + File.delete("#{root}/log_output") if File.exist?("#{root}/log_output") + end + + specify "rackup" do + run_rackup + response["PATH_INFO"].should.equal '/' + response["test.$DEBUG"].should.be false + response["test.$EVAL"].should.be nil + response["test.$VERBOSE"].should.be false + response["test.Ping"].should.be nil + response["SERVER_SOFTWARE"].should.not =~ /webrick/ + end + + specify "rackup --help" do + run_rackup "--help", :port => false + output.should.match /--port/ + end + + specify "rackup --port" do + run_rackup "--port 9000", :port => 9000 + response["SERVER_PORT"].should.equal "9000" + end + + specify "rackup --debug" do + run_rackup "--debug" + response["test.$DEBUG"].should.be true + end + + specify "rackup --eval" do + run_rackup %{--eval "BUKKIT = 'BUKKIT'"} + response["test.$EVAL"].should.equal "BUKKIT" + end + + specify "rackup --warn" do + run_rackup %{--warn} + response["test.$VERBOSE"].should.be true + end + + specify "rackup --include" do + run_rackup %{--include /foo/bar} + response["test.$LOAD_PATH"].should.include "/foo/bar" + end + + specify "rackup --require" do + run_rackup %{--require ping} + response["test.Ping"].should.equal "constant" + end + + specify "rackup --server" do + run_rackup %{--server webrick} + response["SERVER_SOFTWARE"].should =~ /webrick/i + end + + specify "rackup --host" do + run_rackup %{--host 127.0.0.1}, :host => "127.0.0.1" + response["REMOTE_ADDR"].should.equal "127.0.0.1" + end + + specify "rackup --daemonize --pid" do + run_rackup %{--daemonize --pid testing.pid} + status.should.be 200 + @rackup.should.be.eof? + Dir["#{root}/**/testing.pid"].should.not.be.empty? + end + + specify "rackup --pid" do + run_rackup %{--pid testing.pid} + status.should.be 200 + Dir["#{root}/**/testing.pid"].should.not.be.empty? + end + + specify "rackup --version" do + run_rackup %{--version}, :port => false + output.should =~ /1.0/ + end + + specify "rackup --env development includes lint" do + run_rackup + GET("/broken_lint") + status.should.be 500 + end + + specify "rackup --env deployment does not include lint" do + run_rackup %{--env deployment} + GET("/broken_lint") + status.should.be 200 + end + + specify "rackup --env none does not include lint" do + run_rackup %{--env none} + GET("/broken_lint") + status.should.be 200 + end + + specify "rackup --env deployment does log" do + run_rackup %{--env deployment} + log = File.read(response["test.stderr"]) + log.should.be.empty? + end + + specify "rackup --env none does not log" do + run_rackup %{--env none} + GET("/") + log = File.read(response["test.stderr"]) + log.should.be.empty? + end +end +rescue LoadError + $stderr.puts "Skipping rackup --server tests (mongrel is required). `gem install thin` and try again." +end \ No newline at end of file diff --git a/vendor/gems/sinatra-0.10.1/Rakefile b/vendor/gems/sinatra-0.10.1/Rakefile deleted file mode 100644 index 5bb0caf..0000000 --- a/vendor/gems/sinatra-0.10.1/Rakefile +++ /dev/null @@ -1,129 +0,0 @@ -require 'rake/clean' -require 'rake/testtask' -require 'fileutils' - -task :default => :test -task :spec => :test - -# SPECS =============================================================== - -Rake::TestTask.new(:test) do |t| - t.test_files = FileList['test/*_test.rb'] - t.ruby_opts = ['-rubygems'] if defined? Gem -end - -# PACKAGING ============================================================ - -# Load the gemspec using the same limitations as github -def spec - @spec ||= - begin - require 'rubygems/specification' - data = File.read('sinatra.gemspec') - spec = nil - Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join - spec - end -end - -def package(ext='') - "pkg/sinatra-#{spec.version}" + ext -end - -desc 'Build packages' -task :package => %w[.gem .tar.gz].map {|e| package(e)} - -desc 'Build and install as local gem' -task :install => package('.gem') do - sh "gem install #{package('.gem')}" -end - -directory 'pkg/' -CLOBBER.include('pkg') - -file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f| - sh "gem build sinatra.gemspec" - mv File.basename(f.name), f.name -end - -file package('.tar.gz') => %w[pkg/] + spec.files do |f| - sh <<-SH - git archive \ - --prefix=sinatra-#{source_version}/ \ - --format=tar \ - HEAD | gzip > #{f.name} - SH -end - -# Rubyforge Release / Publish Tasks ================================== - -desc 'Publish gem and tarball to rubyforge' -task 'release' => [package('.gem'), package('.tar.gz')] do |t| - sh <<-end - rubyforge add_release sinatra sinatra #{spec.version} #{package('.gem')} && - rubyforge add_file sinatra sinatra #{spec.version} #{package('.tar.gz')} - end -end - -# Website ============================================================ -# Building docs requires HAML and the hanna gem: -# gem install mislav-hanna --source=http://gems.github.com - -task 'doc' => ['doc:api'] - -desc 'Generate Hanna RDoc under doc/api' -task 'doc:api' => ['doc/api/index.html'] - -file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f| - rb_files = f.prerequisites - sh((<<-end).gsub(/\s+/, ' ')) - hanna --charset utf8 \ - --fmt html \ - --inline-source \ - --line-numbers \ - --main README.rdoc \ - --op doc/api \ - --title 'Sinatra API Documentation' \ - #{rb_files.join(' ')} - end -end -CLEAN.include 'doc/api' - -# Gemspec Helpers ==================================================== - -def source_version - line = File.read('lib/sinatra/base.rb')[/^\s*VERSION = .*/] - line.match(/.*VERSION = '(.*)'/)[1] -end - -task 'sinatra.gemspec' => FileList['{lib,test,compat}/**','Rakefile','CHANGES','*.rdoc'] do |f| - # read spec file and split out manifest section - spec = File.read(f.name) - head, manifest, tail = spec.split(" # = MANIFEST =\n") - # replace version and date - head.sub!(/\.version = '.*'/, ".version = '#{source_version}'") - head.sub!(/\.date = '.*'/, ".date = '#{Date.today.to_s}'") - # determine file list from git ls-files - files = `git ls-files`. - split("\n"). - sort. - reject{ |file| file =~ /^\./ }. - reject { |file| file =~ /^doc/ }. - map{ |file| " #{file}" }. - join("\n") - # piece file back together and write... - manifest = " s.files = %w[\n#{files}\n ]\n" - spec = [head,manifest,tail].join(" # = MANIFEST =\n") - File.open(f.name, 'w') { |io| io.write(spec) } - puts "updated #{f.name}" -end - -# Rcov ============================================================== -namespace :test do - desc 'Mesures test coverage' - task :coverage do - rm_f "coverage" - rcov = "rcov --text-summary --test-unit-only -Ilib" - system("#{rcov} --no-html --no-color test/*_test.rb") - end -end diff --git a/vendor/gems/sinatra-0.10.1/AUTHORS b/vendor/gems/sinatra-1.0.a/AUTHORS similarity index 100% rename from vendor/gems/sinatra-0.10.1/AUTHORS rename to vendor/gems/sinatra-1.0.a/AUTHORS diff --git a/vendor/gems/sinatra-0.10.1/CHANGES b/vendor/gems/sinatra-1.0.a/CHANGES similarity index 98% rename from vendor/gems/sinatra-0.10.1/CHANGES rename to vendor/gems/sinatra-1.0.a/CHANGES index ff999d0..1434844 100644 --- a/vendor/gems/sinatra-0.10.1/CHANGES +++ b/vendor/gems/sinatra-1.0.a/CHANGES @@ -9,21 +9,24 @@ template backtraces, and support for new template engines, like mustache and liquid. + * New boolean 'reload_templates' setting controls whether template files + are reread from disk and recompiled on each request. Template read/compile + is cached by default in all environments except development. + * New 'settings' method gives access to options in both class and request scopes. This replaces the 'options' method. - * New 'erubis' helper method for rendering Erubis templates. - * New 'expires' helper method is like cache_control but takes an - integer number of seconds or Time object: - expires 300, :public, :must_revalidate - * New 'cache_control' helper method provides a convenient way of setting the Cache-Control response header. Takes a variable number of boolean directives followed by a hash of value directives, like this: cache_control :public, :must_revalidate, :max_age => 60 + * New 'expires' helper method is like cache_control but takes an + integer number of seconds or Time object: + expires 300, :public, :must_revalidate + * Sinatra apps can now be run with a `-h ` argument to specify the address to bind to. diff --git a/vendor/gems/sinatra-0.10.1/LICENSE b/vendor/gems/sinatra-1.0.a/LICENSE similarity index 100% rename from vendor/gems/sinatra-0.10.1/LICENSE rename to vendor/gems/sinatra-1.0.a/LICENSE diff --git a/vendor/gems/sinatra-0.10.1/README.jp.rdoc b/vendor/gems/sinatra-1.0.a/README.jp.rdoc similarity index 100% rename from vendor/gems/sinatra-0.10.1/README.jp.rdoc rename to vendor/gems/sinatra-1.0.a/README.jp.rdoc diff --git a/vendor/gems/sinatra-0.10.1/README.rdoc b/vendor/gems/sinatra-1.0.a/README.rdoc similarity index 100% rename from vendor/gems/sinatra-0.10.1/README.rdoc rename to vendor/gems/sinatra-1.0.a/README.rdoc diff --git a/vendor/gems/sinatra-1.0.a/Rakefile b/vendor/gems/sinatra-1.0.a/Rakefile new file mode 100644 index 0000000..54875e6 --- /dev/null +++ b/vendor/gems/sinatra-1.0.a/Rakefile @@ -0,0 +1,113 @@ +require 'rake/clean' +require 'rake/testtask' +require 'fileutils' + +task :default => :test +task :spec => :test + +def source_version + line = File.read('lib/sinatra/base.rb')[/^\s*VERSION = .*/] + line.match(/.*VERSION = '(.*)'/)[1] +end + +# SPECS =============================================================== + +Rake::TestTask.new(:test) do |t| + t.test_files = FileList['test/*_test.rb'] + t.ruby_opts = ['-rubygems -I.'] if defined? Gem +end + +# Rcov ================================================================ +namespace :test do + desc 'Mesures test coverage' + task :coverage do + rm_f "coverage" + rcov = "rcov --text-summary --test-unit-only -Ilib" + system("#{rcov} --no-html --no-color test/*_test.rb") + end +end + +# Website ============================================================= +# Building docs requires HAML and the hanna gem: +# gem install mislav-hanna --source=http://gems.github.com + +desc 'Generate RDoc under doc/api' +task 'doc' => ['doc:api'] + +task 'doc:api' => ['doc/api/index.html'] + +file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f| + rb_files = f.prerequisites + sh((<<-end).gsub(/\s+/, ' ')) + hanna --charset utf8 \ + --fmt html \ + --inline-source \ + --line-numbers \ + --main README.rdoc \ + --op doc/api \ + --title 'Sinatra API Documentation' \ + #{rb_files.join(' ')} + end +end +CLEAN.include 'doc/api' + +# PACKAGING ============================================================ + +if defined?(Gem) + # Load the gemspec using the same limitations as github + def spec + require 'rubygems' unless defined? Gem::Specification + @spec ||= eval(File.read('sinatra.gemspec')) + end + + def package(ext='') + "pkg/sinatra-#{spec.version}" + ext + end + + desc 'Build packages' + task :package => %w[.gem .tar.gz].map {|e| package(e)} + + desc 'Build and install as local gem' + task :install => package('.gem') do + sh "gem install #{package('.gem')}" + end + + directory 'pkg/' + CLOBBER.include('pkg') + + file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f| + sh "gem build sinatra.gemspec" + mv File.basename(f.name), f.name + end + + file package('.tar.gz') => %w[pkg/] + spec.files do |f| + sh <<-SH + git archive \ + --prefix=sinatra-#{source_version}/ \ + --format=tar \ + HEAD | gzip > #{f.name} + SH + end + + task 'sinatra.gemspec' => FileList['{lib,test,compat}/**','Rakefile','CHANGES','*.rdoc'] do |f| + # read spec file and split out manifest section + spec = File.read(f.name) + head, manifest, tail = spec.split(" # = MANIFEST =\n") + # replace version and date + head.sub!(/\.version = '.*'/, ".version = '#{source_version}'") + head.sub!(/\.date = '.*'/, ".date = '#{Date.today.to_s}'") + # determine file list from git ls-files + files = `git ls-files`. + split("\n"). + sort. + reject{ |file| file =~ /^\./ }. + reject { |file| file =~ /^doc/ }. + map{ |file| " #{file}" }. + join("\n") + # piece file back together and write... + manifest = " s.files = %w[\n#{files}\n ]\n" + spec = [head,manifest,tail].join(" # = MANIFEST =\n") + File.open(f.name, 'w') { |io| io.write(spec) } + puts "updated #{f.name}" + end +end diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra.rb b/vendor/gems/sinatra-1.0.a/lib/sinatra.rb similarity index 100% rename from vendor/gems/sinatra-0.10.1/lib/sinatra.rb rename to vendor/gems/sinatra-1.0.a/lib/sinatra.rb diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra/base.rb b/vendor/gems/sinatra-1.0.a/lib/sinatra/base.rb similarity index 95% rename from vendor/gems/sinatra-0.10.1/lib/sinatra/base.rb rename to vendor/gems/sinatra-1.0.a/lib/sinatra/base.rb index 5766ca5..3970312 100644 --- a/vendor/gems/sinatra-0.10.1/lib/sinatra/base.rb +++ b/vendor/gems/sinatra-1.0.a/lib/sinatra/base.rb @@ -13,30 +13,34 @@ end module Sinatra - VERSION = '0.10.1' + VERSION = '1.0.a' # The request object. See Rack::Request for more info: # http://rack.rubyforge.org/doc/classes/Rack/Request.html class Request < Rack::Request - def user_agent - @env['HTTP_USER_AGENT'] - end - # Returns an array of acceptable media types for the response def accept @env['HTTP_ACCEPT'].to_s.split(',').map { |a| a.strip } end - # Override Rack 0.9.x's #params implementation (see #72 in lighthouse) - def params - self.GET.update(self.POST) - rescue EOFError, Errno::ESPIPE - self.GET - end - def secure? (@env['HTTP_X_FORWARDED_PROTO'] || @env['rack.url_scheme']) == 'https' end + + # Override Rack < 1.1's Request#params implementation (see lh #72 for + # more info) and add a Request#user_agent method. + # XXX remove when we require rack > 1.1 + if Rack.release < '1.1' + def params + self.GET.update(self.POST) + rescue EOFError, Errno::ESPIPE + self.GET + end + + def user_agent + @env['HTTP_USER_AGENT'] + end + end end # The response object. See Rack::Response and Rack::ResponseHelpers for @@ -383,6 +387,7 @@ def call!(env) @request = Request.new(env) @response = Response.new @params = indifferent_params(@request.params) + @template_cache.clear if settings.reload_templates invoke { dispatch! } invoke { error_block!(response.status) } @@ -520,6 +525,7 @@ def static! return if path[0, public_dir.length] != public_dir return unless File.file?(path) + env['sinatra.static_file'] = path send_file path, :disposition => nil end @@ -579,7 +585,7 @@ def dispatch! rescue ::Exception => boom handle_exception!(boom) ensure - after_filter! + after_filter! unless env['sinatra.static_file'] end def handle_not_found!(boom) @@ -838,9 +844,9 @@ def route(verb, path, options={}, &block) unbound_method = instance_method("#{verb} #{path}") block = if block.arity != 0 - lambda { unbound_method.bind(self).call(*@block_params) } + proc { unbound_method.bind(self).call(*@block_params) } else - lambda { unbound_method.bind(self).call } + proc { unbound_method.bind(self).call } end invoke_hook(:route_added, verb, path, block) @@ -1017,15 +1023,14 @@ def caller_locations reset! - set :raise_errors, true - set :dump_errors, false + set :environment, (ENV['RACK_ENV'] || :development).to_sym + set :raise_errors, Proc.new { !development? } + set :dump_errors, Proc.new { development? } + set :show_exceptions, Proc.new { development? } set :clean_trace, true - set :show_exceptions, false set :sessions, false set :logging, false set :methodoverride, false - set :static, false - set :environment, (ENV['RACK_ENV'] || :development).to_sym set :run, false # start server via at-exit hook? set :running, false # is the built-in server running now? @@ -1036,9 +1041,12 @@ def caller_locations set :app_file, nil set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } set :views, Proc.new { root && File.join(root, 'views') } - set :public, Proc.new { root && File.join(root, 'public') } + set :reload_templates, Proc.new { !development? } set :lock, false + set :public, Proc.new { root && File.join(root, 'public') } + set :static, Proc.new { self.public && File.exist?(self.public) } + error ::Exception do response.status = 500 content_type 'text/html' @@ -1079,16 +1087,21 @@ def caller_locations end end - # Base class for classic style (top-level) applications. - class Default < Base + # Execution context for classic style (top-level) applications. All + # DSL methods executed on main are delegated to this class. + # + # The Application class should not be subclassed, unless you want to + # inherit all settings, routes, handlers, and error pages from the + # top-level. Subclassing Sinatra::Base is heavily recommended for + # modular applications. + class Application < Base set :raise_errors, Proc.new { test? } - set :show_exceptions, Proc.new { development? } set :dump_errors, true set :sessions, false set :logging, Proc.new { ! test? } set :methodoverride, true - set :static, true set :run, Proc.new { ! test? } + set :static, true def self.register(*extensions, &block) #:nodoc: added_methods = extensions.map {|m| m.public_instance_methods }.flatten @@ -1097,11 +1110,6 @@ def self.register(*extensions, &block) #:nodoc: end end - # The top-level Application. All DSL methods executed on main are delegated - # to this class. - class Application < Default - end - # Sinatra delegation mixin. Mixing this module into an object causes all # methods to be delegated to the Sinatra::Application class. Used primarily # at the top-level. @@ -1119,8 +1127,8 @@ def #{method_name}(*args, &b) delegate :get, :put, :post, :delete, :head, :template, :layout, :before, :after, :error, :not_found, :configure, :set, :mime_type, - :enable, :disable, :use, :development?, :test?, - :production?, :use_in_file_templates!, :helpers + :enable, :disable, :use, :development?, :test?, :production?, + :helpers, :settings end # Create a new Sinatra application. The block is evaluated in the new app's @@ -1133,11 +1141,11 @@ def self.new(base=Base, options={}, &block) # Extend the top-level DSL with the modules provided. def self.register(*extensions, &block) - Default.register(*extensions, &block) + Application.register(*extensions, &block) end # Include the helper modules provided in Sinatra's request context. def self.helpers(*extensions, &block) - Default.helpers(*extensions, &block) + Application.helpers(*extensions, &block) end end diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra/images/404.png b/vendor/gems/sinatra-1.0.a/lib/sinatra/images/404.png similarity index 100% rename from vendor/gems/sinatra-0.10.1/lib/sinatra/images/404.png rename to vendor/gems/sinatra-1.0.a/lib/sinatra/images/404.png diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra/images/500.png b/vendor/gems/sinatra-1.0.a/lib/sinatra/images/500.png similarity index 100% rename from vendor/gems/sinatra-0.10.1/lib/sinatra/images/500.png rename to vendor/gems/sinatra-1.0.a/lib/sinatra/images/500.png diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra/main.rb b/vendor/gems/sinatra-1.0.a/lib/sinatra/main.rb similarity index 96% rename from vendor/gems/sinatra-0.10.1/lib/sinatra/main.rb rename to vendor/gems/sinatra-1.0.a/lib/sinatra/main.rb index f890c3d..d0afbfe 100644 --- a/vendor/gems/sinatra-0.10.1/lib/sinatra/main.rb +++ b/vendor/gems/sinatra-1.0.a/lib/sinatra/main.rb @@ -1,7 +1,7 @@ require 'sinatra/base' module Sinatra - class Default < Base + class Application < Base # we assume that the first file that requires 'sinatra' is the # app_file. all other path related options are calculated based diff --git a/vendor/gems/sinatra-0.10.1/lib/sinatra/showexceptions.rb b/vendor/gems/sinatra-1.0.a/lib/sinatra/showexceptions.rb similarity index 95% rename from vendor/gems/sinatra-0.10.1/lib/sinatra/showexceptions.rb rename to vendor/gems/sinatra-1.0.a/lib/sinatra/showexceptions.rb index 477f0c4..86738c9 100644 --- a/vendor/gems/sinatra-0.10.1/lib/sinatra/showexceptions.rb +++ b/vendor/gems/sinatra-1.0.a/lib/sinatra/showexceptions.rb @@ -73,14 +73,14 @@ def frame_class(frame) #explanation {font-size: 12px; color: #666666; margin: 20px 0 0 100px;} /* WRAP */ - #wrap {width: 860px; background: #FFFFFF; margin: 0 auto; + #wrap {width: 1000px; background: #FFFFFF; margin: 0 auto; padding: 30px 50px 20px 50px; border-left: 1px solid #DDDDDD; border-right: 1px solid #DDDDDD;} /* HEADER */ #header {margin: 0 auto 25px auto;} #header img {float: left;} - #header #summary {float: left; margin: 12px 0 0 20px; width:520px; + #header #summary {float: left; margin: 12px 0 0 20px; width:660px; font-family: 'Lucida Grande', 'Lucida Sans Unicode';} h1 {margin: 0; font-size: 36px; color: #981919;} h2 {margin: 0; font-size: 22px; color: #333333;} @@ -94,7 +94,7 @@ def frame_class(frame) #get, #post, #cookies, - #rack {width: 860px; margin: 0 auto 10px auto;} + #rack {width: 980px; margin: 0 auto 10px auto;} p#nav {float: right; font-size: 14px;} /* BACKTRACE */ a#expando {float: left; padding-left: 5px; color: #666666; @@ -107,7 +107,7 @@ def frame_class(frame) font-size: 12px; color: #333333;} #backtrace ul {list-style-position: outside; border: 1px solid #E9E9E9; border-bottom: 0;} - #backtrace ol {width: 808px; margin-left: 50px; + #backtrace ol {width: 920px; margin-left: 50px; font: 10px 'Lucida Console', monospace; color: #666666;} #backtrace ol li {border: 0; border-left: 1px solid #E9E9E9; padding: 2px 0;} @@ -119,10 +119,11 @@ def frame_class(frame) #backtrace.condensed .framework {display:none;} /* REQUEST DATA */ p.no-data {padding-top: 2px; font-size: 12px; color: #666666;} - table.req {width: 760px; text-align: left; font-size: 12px; + table.req {width: 980px; text-align: left; font-size: 12px; color: #666666; padding: 0; border-spacing: 0; border: 1px solid #EEEEEE; border-bottom: 0; - border-left: 0;} + border-left: 0; + clear:both} table.req tr th {padding: 2px 10px; font-weight: bold; background: #F7F7F7; border-bottom: 1px solid #EEEEEE; border-left: 1px solid #EEEEEE;} @@ -132,12 +133,15 @@ def frame_class(frame) /* HIDE PRE/POST CODE AT START */ .pre-context, .post-context {display: none;} + + table td.code {width:750px} + table td.code div {width:750px;overflow:hidden}