diff --git a/.gitignore b/.gitignore index 83078086d..dae590ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ iron.json dj.config.json dj.cluster.*.json - +script.tar.gz +router +app.zip diff --git a/README.md b/README.md index 2e5a6f087..e3bce801c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,18 @@ The idea here is that IronWorker backend can tell the router that it started a process and to start routing requests. +## Usage + +``` +iron worker upload --name hello-sinatra --host YOURHOST treeder/hello-sinatra +``` + +Then hit the url: + +``` +http://router.iron.io/?rhost=YOURHOST +``` + ## Todo This is just a simple prototype. To get to production would need: @@ -15,9 +27,18 @@ This is just a simple prototype. To get to production would need: ## Testing for reals on staging +### 1) Deploy router + Using DockerJockey: -`dj run -i --name mygoprog -v "$(pwd)":/app -w /app -p 8080:8080 treeder/golang-ubuntu:1.3.3on14.04 ./mygoprog` +``` +dj run --on aws -i --name router -v "$(pwd)":/app -w /app -p 80:8080 treeder/golang-ubuntu:1.3.3on14.04 ./router +``` + +### 2) Update DNS + +Update DNS entry `router.iron.io` to point to the newly launched server after dj deploy. + Or SimpleDeployer: diff --git a/config_development.json b/config_development.json index 1923be7fb..e0f74a85a 100644 --- a/config_development.json +++ b/config_development.json @@ -2,16 +2,14 @@ "cache": { "description": "using production cache even for dev/staging", "host": "cache-aws-us-east-1.iron.io", - "project_id": "5577b5403864900009000006", - "token": "15iDumDYhHt_3hrwaGhPHLVyfA4" + "project_id":"4e25e1d25c0dd27801000275", + "token":"PXNX3uPb4Wi6nk3hcK-X6agSD40" }, "iron": { - "project_id": "515b5da6c731ff1e690008aa", - "token": "TXT6wq-pfkHSTvWbaJ_wPUmqVnE", - "super_token": "lJLWJFR3J339cCQRzW8igVbwI8o", - "worker_host": "staging-worker.iron.io", - "auth_host": "https://auth-staging.iron.io" - }, + "project_id": "5577b5403864900009000006", + "token": "15iDumDYhHt_3hrwaGhPHLVyfA4", + "super_token": "nhsMxPhW7R8U6GTxKQVorKWJVzw" + }, "logging": { "to": "papertrail", "level": "debug", diff --git a/hello-sinatra/.bundle/config b/hello-sinatra/.bundle/config new file mode 100644 index 000000000..4d6dd2296 --- /dev/null +++ b/hello-sinatra/.bundle/config @@ -0,0 +1,4 @@ +--- +BUNDLE_PATH: bundle +BUNDLE_CLEAN: true +BUNDLE_DISABLE_SHARED_GEMS: '1' diff --git a/hello-sinatra/Dockerfile b/hello-sinatra/Dockerfile new file mode 100644 index 000000000..fa9d7724a --- /dev/null +++ b/hello-sinatra/Dockerfile @@ -0,0 +1,8 @@ +FROM treeder/ruby:2.2.2 + +WORKDIR /app + +ADD . /app + +ENTRYPOINT ["ruby", "app.rb"] +# CMD ["/bin/bash"] FROM ubuntu diff --git a/hello-sinatra/Gemfile b/hello-sinatra/Gemfile new file mode 100644 index 000000000..c10abd72f --- /dev/null +++ b/hello-sinatra/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'sinatra' diff --git a/hello-sinatra/Gemfile.lock b/hello-sinatra/Gemfile.lock new file mode 100644 index 000000000..4c0bb1653 --- /dev/null +++ b/hello-sinatra/Gemfile.lock @@ -0,0 +1,20 @@ +GEM + remote: https://rubygems.org/ + specs: + rack (1.6.4) + rack-protection (1.5.3) + rack + sinatra (1.4.6) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (>= 1.3, < 3) + tilt (2.0.1) + +PLATFORMS + ruby + +DEPENDENCIES + sinatra + +BUNDLED WITH + 1.10.5 diff --git a/hello-sinatra/README.md b/hello-sinatra/README.md new file mode 100644 index 000000000..d6d04068b --- /dev/null +++ b/hello-sinatra/README.md @@ -0,0 +1,19 @@ + + +``` +docker run --rm -p 8080:8080 treeder/hello-sinatra +``` + + + +## Building + +Update gems and vendor them +``` +dj run treeder/ruby:2.2.2 bundle update +dj run treeder/ruby:2.2.2 bundle install --standalone --clean +``` + +``` +docker build -t treeder/hello-sinatra:latest . +``` diff --git a/sinatra.rb b/hello-sinatra/app.rb similarity index 76% rename from sinatra.rb rename to hello-sinatra/app.rb index 8046e3c53..c0fbcf484 100644 --- a/sinatra.rb +++ b/hello-sinatra/app.rb @@ -1,10 +1,10 @@ -require 'rest' +require_relative 'bundle/bundler/setup' require 'sinatra' # Now we start the actual worker ##################################################################3 -port = ENV['PORT'].to_i +port = ENV['PORT'] || 8080 puts "STARTING SINATRA on port #{port}" my_app = Sinatra.new do set :port, port @@ -13,11 +13,9 @@ my_app = Sinatra.new do puts "in somepost" p params end - get('/pingsinatra') { "pong" } - get('/') { "hi" } + get('/ping') { "pong" } + get('/') { "hi!" } # get('/*') { "you passed in #{params[:splat].inspect}" } end my_app.run! - -# bump.... \ No newline at end of file diff --git a/hello-sinatra/bundle/bundler/setup.rb b/hello-sinatra/bundle/bundler/setup.rb new file mode 100644 index 000000000..5a6a8c722 --- /dev/null +++ b/hello-sinatra/bundle/bundler/setup.rb @@ -0,0 +1,9 @@ +require 'rbconfig' +# ruby 1.8.7 doesn't define RUBY_ENGINE +ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' +ruby_version = RbConfig::CONFIG["ruby_version"] +path = File.expand_path('..', __FILE__) +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rack-1.6.4/lib" +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/rack-protection-1.5.3/lib" +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tilt-2.0.1/lib" +$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/sinatra-1.4.6/lib" diff --git a/hello-sinatra/bundle/ruby/2.2.0/bin/rackup b/hello-sinatra/bundle/ruby/2.2.0/bin/rackup new file mode 100755 index 000000000..416d30abe --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/bin/rackup @@ -0,0 +1,23 @@ +#!/usr/bin/env 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 + str = ARGV.first + str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding + if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then + version = $1 + ARGV.shift + end +end + +gem 'rack', version +load Gem.bin_path('rack', 'rackup', version) diff --git a/hello-sinatra/bundle/ruby/2.2.0/bin/tilt b/hello-sinatra/bundle/ruby/2.2.0/bin/tilt new file mode 100755 index 000000000..cba5196b4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/bin/tilt @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +# +# This file was generated by RubyGems. +# +# The application 'tilt' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0" + +if ARGV.first + str = ARGV.first + str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding + if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then + version = $1 + ARGV.shift + end +end + +gem 'tilt', version +load Gem.bin_path('tilt', 'tilt', version) diff --git a/hello-sinatra/bundle/ruby/2.2.0/cache/rack-1.6.4.gem b/hello-sinatra/bundle/ruby/2.2.0/cache/rack-1.6.4.gem new file mode 100644 index 000000000..6d06fed8e Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/cache/rack-1.6.4.gem differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/cache/rack-protection-1.5.3.gem b/hello-sinatra/bundle/ruby/2.2.0/cache/rack-protection-1.5.3.gem new file mode 100644 index 000000000..5a00e8e9a Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/cache/rack-protection-1.5.3.gem differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/cache/sinatra-1.4.6.gem b/hello-sinatra/bundle/ruby/2.2.0/cache/sinatra-1.4.6.gem new file mode 100644 index 000000000..4c594c80d Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/cache/sinatra-1.4.6.gem differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/cache/tilt-2.0.1.gem b/hello-sinatra/bundle/ruby/2.2.0/cache/tilt-2.0.1.gem new file mode 100644 index 000000000..a9467e041 Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/cache/tilt-2.0.1.gem differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/COPYING b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/COPYING new file mode 100644 index 000000000..e10475696 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/COPYING @@ -0,0 +1,18 @@ +Copyright (c) 2007-2015 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. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/HISTORY.md b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/HISTORY.md new file mode 100644 index 000000000..2635c6052 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/HISTORY.md @@ -0,0 +1,355 @@ +Fri Jun 19 07:14:50 2015 Matthew Draper + + * Work around a Rails incompatibility in our private API + +Fri Jun 12 11:37:41 2015 Aaron Patterson + + * Prevent extremely deep parameters from being parsed. CVE-2015-3225 + +### December 18th, Thirty sixth public release 1.6.0 + +### February 7th, Thirty fifth public release 1.5.2 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + - Fix CVE-2013-0262, symlink path traversal in Rack::File + - Add various methods to Session for enhanced Rails compatibility + - Request#trusted_proxy? now only matches whole stirngs + - Add JSON cookie coder, to be default in Rack 1.6+ due to security concerns + - URLMap host matching in environments that don't set the Host header fixed + - Fix a race condition that could result in overwritten pidfiles + - Various documentation additions + +### February 7th, Thirty fifth public release 1.4.5 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + - Fix CVE-2013-0262, symlink path traversal in Rack::File + +### February 7th, Thirty fifth public release 1.1.6, 1.2.8, 1.3.10 + - Fix CVE-2013-0263, timing attack against Rack::Session::Cookie + +### January 28th, 2013: Thirty fourth public release 1.5.1 + - Rack::Lint check_hijack now conforms to other parts of SPEC + - Added hash-like methods to Abstract::ID::SessionHash for compatibility + - Various documentation corrections + +### January 21st, 2013: Thirty third public release 1.5.0 + - Introduced hijack SPEC, for before-response and after-response hijacking + - SessionHash is no longer a Hash subclass + - Rack::File cache_control parameter is removed, in place of headers options + - Rack::Auth::AbstractRequest#scheme now yields strings, not symbols + - Rack::Utils cookie functions now format expires in RFC 2822 format + - Rack::File now has a default mime type + - rackup -b 'run Rack::File.new(".")', option provides command line configs + - Rack::Deflater will no longer double encode bodies + - Rack::Mime#match? provides convenience for Accept header matching + - Rack::Utils#q_values provides splitting for Accept headers + - Rack::Utils#best_q_match provides a helper for Accept headers + - Rack::Handler.pick provides convenience for finding available servers + - Puma added to the list of default servers (preferred over Webrick) + - Various middleware now correctly close body when replacing it + - Rack::Request#params is no longer persistent with only GET params + - Rack::Request#update_param and #delete_param provide persistent operations + - Rack::Request#trusted_proxy? now returns true for local unix sockets + - Rack::Response no longer forces Content-Types + - Rack::Sendfile provides local mapping configuration options + - Rack::Utils#rfc2109 provides old netscape style time output + - Updated HTTP status codes + - Ruby 1.8.6 likely no longer passes tests, and is no longer fully supported + +### January 13th, 2013: Thirty second public release 1.4.4, 1.3.9, 1.2.7, 1.1.5 + - [SEC] Rack::Auth::AbstractRequest no longer symbolizes arbitrary strings + - Fixed erroneous test case in the 1.3.x series + +### January 7th, 2013: Thirty first public release 1.4.3 + - Security: Prevent unbounded reads in large multipart boundaries + +### January 7th, 2013: Thirtieth public release 1.3.8 + - Security: Prevent unbounded reads in large multipart boundaries + +### January 6th, 2013: Twenty ninth public release 1.4.2 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + - Updated URI backports + - Fix URI backport version matching, and silence constant warnings + - Correct parameter parsing with empty values + - Correct rackup '-I' flag, to allow multiple uses + - Correct rackup pidfile handling + - Report rackup line numbers correctly + - Fix request loops caused by non-stale nonces with time limits + - Fix reloader on Windows + - Prevent infinite recursions from Response#to_ary + - Various middleware better conforms to the body close specification + - Updated language for the body close specification + - Additional notes regarding ECMA escape compatibility issues + - Fix the parsing of multiple ranges in range headers + - Prevent errors from empty parameter keys + - Added PATCH verb to Rack::Request + - Various documentation updates + - Fix session merge semantics (fixes rack-test) + - Rack::Static :index can now handle multiple directories + - All tests now utilize Rack::Lint (special thanks to Lars Gierth) + - Rack::File cache_control parameter is now deprecated, and removed by 1.5 + - Correct Rack::Directory script name escaping + - Rack::Static supports header rules for sophisticated configurations + - Multipart parsing now works without a Content-Length header + - New logos courtesy of Zachary Scott! + - Rack::BodyProxy now explicitly defines #each, useful for C extensions + - Cookies that are not URI escaped no longer cause exceptions + +### January 6th, 2013: Twenty eighth public release 1.3.7 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + - Updated URI backports + - Fix URI backport version matching, and silence constant warnings + - Correct parameter parsing with empty values + - Correct rackup '-I' flag, to allow multiple uses + - Correct rackup pidfile handling + - Report rackup line numbers correctly + - Fix request loops caused by non-stale nonces with time limits + - Fix reloader on Windows + - Prevent infinite recursions from Response#to_ary + - Various middleware better conforms to the body close specification + - Updated language for the body close specification + - Additional notes regarding ECMA escape compatibility issues + - Fix the parsing of multiple ranges in range headers + +### January 6th, 2013: Twenty seventh public release 1.2.6 + - Add warnings when users do not provide a session secret + - Fix parsing performance for unquoted filenames + +### January 6th, 2013: Twenty sixth public release 1.1.4 + - Add warnings when users do not provide a session secret + +### January 22nd, 2012: Twenty fifth public release 1.4.1 + - Alter the keyspace limit calculations to reduce issues with nested params + - Add a workaround for multipart parsing where files contain unescaped "%" + - Added Rack::Response::Helpers#method_not_allowed? (code 405) + - Rack::File now returns 404 for illegal directory traversals + - Rack::File now returns 405 for illegal methods (non HEAD/GET) + - Rack::Cascade now catches 405 by default, as well as 404 + - Cookies missing '--' no longer cause an exception to be raised + - Various style changes and documentation spelling errors + - Rack::BodyProxy always ensures to execute its block + - Additional test coverage around cookies and secrets + - Rack::Session::Cookie can now be supplied either secret or old_secret + - Tests are no longer dependent on set order + - Rack::Static no longer defaults to serving index files + - Rack.release was fixed + +### December 28th, 2011: Twenty fourth public release 1.4.0 + - Ruby 1.8.6 support has officially been dropped. Not all tests pass. + - Raise sane error messages for broken config.ru + - Allow combining run and map in a config.ru + - Rack::ContentType will not set Content-Type for responses without a body + - Status code 205 does not send a response body + - Rack::Response::Helpers will not rely on instance variables + - Rack::Utils.build_query no longer outputs '=' for nil query values + - Various mime types added + - Rack::MockRequest now supports HEAD + - Rack::Directory now supports files that contain RFC3986 reserved chars + - Rack::File now only supports GET and HEAD requests + - Rack::Server#start now passes the block to Rack::Handler::#run + - Rack::Static now supports an index option + - Added the Teapot status code + - rackup now defaults to Thin instead of Mongrel (if installed) + - Support added for HTTP_X_FORWARDED_SCHEME + - Numerous bug fixes, including many fixes for new and alternate rubies + +### December 28th, 2011: Twenty first public release: 1.1.3. + - Security fix. http://www.ocert.org/advisories/ocert-2011-003.html + Further information here: http://jruby.org/2011/12/27/jruby-1-6-5-1 + +### October 17, 2011: Twentieth public release 1.3.5 + - Fix annoying warnings caused by the backport in 1.3.4 + +### October 1, 2011: Nineteenth public release 1.3.4 + - Backport security fix from 1.9.3, also fixes some roundtrip issues in URI + - Small documentation update + - Fix an issue where BodyProxy could cause an infinite recursion + - Add some supporting files for travis-ci + +### September 16, 2011: Eighteenth public release 1.2.4 + - Fix a bug with MRI regex engine to prevent XSS by malformed unicode + +### September 16, 2011: Seventeenth public release 1.3.3 + - Fix bug with broken query parameters in Rack::ShowExceptions + - Rack::Request#cookies no longer swallows exceptions on broken input + - Prevents XSS attacks enabled by bug in Ruby 1.8's regexp engine + - Rack::ConditionalGet handles broken If-Modified-Since helpers + +### July 16, 2011: Sixteenth public release 1.3.2 + - Fix for Rails and rack-test, Rack::Utils#escape calls to_s + +### July 13, 2011: Fifteenth public release 1.3.1 + - Fix 1.9.1 support + - Fix JRuby support + - Properly handle $KCODE in Rack::Utils.escape + - Make method_missing/respond_to behavior consistent for Rack::Lock, + Rack::Auth::Digest::Request and Rack::Multipart::UploadedFile + - Reenable passing rack.session to session middleware + - Rack::CommonLogger handles streaming responses correctly + - Rack::MockResponse calls close on the body object + - Fix a DOS vector from MRI stdlib backport + +### May 22nd, 2011: Fourteenth public release 1.2.3 + - Pulled in relevant bug fixes from 1.3 + - Fixed 1.8.6 support + +### May 22nd, 2011: Thirteenth public release 1.3.0 + - Various performance optimizations + - Various multipart fixes + - Various multipart refactors + - Infinite loop fix for multipart + - Test coverage for Rack::Server returns + - Allow files with '..', but not path components that are '..' + - rackup accepts handler-specific options on the command line + - Request#params no longer merges POST into GET (but returns the same) + - Use URI.encode_www_form_component instead. Use core methods for escaping. + - Allow multi-line comments in the config file + - Bug L#94 reported by Nikolai Lugovoi, query parameter unescaping. + - Rack::Response now deletes Content-Length when appropriate + - Rack::Deflater now supports streaming + - Improved Rack::Handler loading and searching + - Support for the PATCH verb + - env['rack.session.options'] now contains session options + - Cookies respect renew + - Session middleware uses SecureRandom.hex + +### March 13th, 2011: Twelfth public release 1.2.2/1.1.2. + - Security fix in Rack::Auth::Digest::MD5: when authenticator + returned nil, permission was granted on empty password. + +### June 15th, 2010: Eleventh public release 1.2.1. + - Make CGI handler rewindable + - Rename spec/ to test/ to not conflict with SPEC on lesser + operating systems + +### June 13th, 2010: Tenth public release 1.2.0. + - Removed Camping adapter: Camping 2.0 supports Rack as-is + - Removed parsing of quoted values + - Add Request.trace? and Request.options? + - Add mime-type for .webm and .htc + - Fix HTTP_X_FORWARDED_FOR + - Various multipart fixes + - Switch test suite to bacon + +### January 3rd, 2010: 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 + +### 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. + +### 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. + +### January 9th, 2009: Sixth public release 0.9.1. + - Fix directory traversal exploits in Rack::File and Rack::Directory. + +### 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. + +### 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. + +### 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. + +### 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. + +### March 3rd, 2007: First public release 0.1. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/KNOWN-ISSUES b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/KNOWN-ISSUES new file mode 100644 index 000000000..ceb2e61fe --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/KNOWN-ISSUES @@ -0,0 +1,44 @@ += Known issues with Rack and ECMA-262 + +* Many users expect the escape() function defined in ECMA-262 to be compatible + with URI. Confusion is especially strong because the documentation for the + escape function includes a reference to the URI specifications. ECMA-262 + escape is not however a URI escape function, it is a javascript escape + function, and is not fully compatible. Most notably, for characters outside of + the BMP. Users should use the more correct encodeURI functions. + += 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. + += Known conflicts regarding parameter parsing + + * Many users have differing opinions about parameter parsing. The current + parameter parsers in Rack are based on a combination of the HTTP and CGI + specs, and are intended to round-trip encoding and decoding. There are some + choices that may be viewed as deficiencies, specifically: + - Rack does not create implicit arrays for multiple instances of a parameter + - Rack returns nil when a value is not given + - Rack does not support multi-type keys in parameters + These issues or choices, will not be fixed before 2.0, if at all. They are + very major breaking changes. Users are free to write alternative parameter + parsers, and their own Request and Response wrappers. Moreover, users are + encouraged to do so. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/README.rdoc b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/README.rdoc new file mode 100644 index 000000000..6c4a0c39c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/README.rdoc @@ -0,0 +1,312 @@ += Rack, a modular Ruby webserver interface {Build Status}[http://travis-ci.org/rack/rack] {Dependency Status}[https://gemnasium.com/rack/rack] + +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. + +== 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) +* Puma +* Rainbows! +* Reel +* Unicorn +* unixrack +* uWSGI +* yahns +* Zbatery + +Any valid Rack app will run the same on all these handlers, without +changing anything. + +== Supported web frameworks + +These frameworks include Rack adapters in their distributions: +* Camping +* Coset +* Espresso +* Halcyon +* Mack +* Maveric +* Merb +* Racktools::SimpleApplication +* Ramaze +* Ruby on Rails +* Rum +* Sinatra +* Sin +* Vintage +* Waves +* Wee +* ... and many others. + +== 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. + +* https://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 rubygems.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 bacon testing framework: + + bundle install --without extra # to be able to run the fast tests + +Or: + + bundle install # this assumes that you have installed native extensions! + +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 bacon. + +To run the test suite completely, you need: + + * 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. + +== Configuration + +Several parameters can be modified on Rack::Utils to configure Rack behaviour. + +e.g: + + Rack::Utils.key_space_limit = 128 + +=== key_space_limit + +The default number of bytes to allow a single parameter key to take up. +This helps prevent a rogue client from flooding a Request. + +Default to 65536 characters (4 kiB in worst case). + +=== multipart_part_limit + +The maximum number of parts a request can contain. +Accepting too many part can lead to the server running out of file handles. + +The default is 128, which means that a single request can't upload more than 128 files at once. + +Set to 0 for no limit. + +Can also be set via the RACK_MULTIPART_PART_LIMIT environment variable. + +== History + +See . + +== Contact + +Please post bugs, suggestions and patches to +the bug tracker at . + +Please post security related bugs and suggestions to the core team at + or rack-core@googlegroups.com. This +list is not public. Due to wide usage of the library, it is strongly preferred +that we manage timing in order to provide viable patches at the time of +disclosure. Your assistance in this matter is greatly appreciated. + +Mailing list archives are available at +. + +Git repository (send Git patches to the mailing list): +* https://github.com/rack/rack +* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack-github.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) +* José Valim (josevalim) +* Michael Fellinger (manveru) +* Aaron Patterson (tenderlove) +* Santiago Pastorino (spastorino) +* Konstantin Haase (rkh) + +and the Rack Alumnis + +* 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. +* Alex Beregszaszi, Alexander Kahn, Anil Wadghule, 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, Kazuhiro + Nishiyama, Jon Bardin, Konstantin Haase, Larry Siden, Matias + Korhonen, Sam Ruby, Simon Chiang, Tim Connor, Timur Batyrshin, and + Zach Brock 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 contributors 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:: +Official Rack repositories:: +Rack Bug Tracking:: +rack-devel mailing list:: +Rack's Rubyforge project:: + +Christian Neukirchen:: + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/Rakefile b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/Rakefile new file mode 100644 index 000000000..a11c2f7e5 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/Rakefile @@ -0,0 +1,124 @@ +# Rakefile for Rack. -*-ruby-*- + +desc "Run all the tests" +task :default => [:test] + +desc "Install gem dependencies" +task :deps do + require 'rubygems' + spec = Gem::Specification.load('rack.gemspec') + spec.dependencies.each do |dep| + reqs = dep.requirements_list + reqs = (["-v"] * reqs.size).zip(reqs).flatten + # Use system over sh, because we want to ignore errors! + system Gem.ruby, "-S", "gem", "install", '--conservative', dep.name, *reqs + end +end + +desc "Make an archive as .tar.gz" +task :dist => %w[chmod ChangeLog SPEC rdoc] do + sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar" + sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC ChangeLog doc rack.gemspec" + sh "gzip -f -9 #{release}.tar" +end + +desc "Make an official release" +task :officialrelease do + puts "Official build for #{release}..." + sh "rm -rf stage" + sh "git clone --shared . stage" + sh "cd stage && rake officialrelease_really" + sh "mv stage/#{release}.tar.gz stage/#{release}.gem ." +end + +task :officialrelease_really => %w[SPEC dist gem] do + sh "shasum #{release}.tar.gz #{release}.gem" +end + +def release + "rack-#{File.read("rack.gemspec")[/s.version *= *"(.*?)"/, 1]}" +end + +desc "Make binaries executable" +task :chmod do + Dir["bin/*"].each { |binary| File.chmod(0755, binary) } + Dir["test/cgi/test*"].each { |binary| File.chmod(0755, binary) } +end + +desc "Generate a ChangeLog" +task :changelog => %w[ChangeLog] + +file '.git/index' +file "ChangeLog" => '.git/index' do + File.open("ChangeLog", "w") { |out| + log = `git log -z` + log.force_encoding(Encoding::BINARY) if log.respond_to?(:force_encoding) + log.split("\0").map { |chunk| + author = chunk[/Author: (.*)/, 1].strip + date = chunk[/Date: (.*)/, 1].strip + desc, detail = $'.strip.split("\n", 2) + detail ||= "" + detail = detail.gsub(/.*darcs-hash:.*/, '') + detail.rstrip! + out.puts "#{date} #{author}" + out.puts " * #{desc.strip}" + out.puts detail unless detail.empty? + out.puts + } + } +end + +file 'lib/rack/lint.rb' +desc "Generate Rack Specification" +file "SPEC" => 'lib/rack/lint.rb' do + File.open("SPEC", "wb") { |file| + IO.foreach("lib/rack/lint.rb") { |line| + if line =~ /## (.*)/ + file.puts $1 + end + } + } +end + +desc "Run all the fast + platform agnostic tests" +task :test => 'SPEC' do + opts = ENV['TEST'] || '-a' + specopts = ENV['TESTOPTS'] || + "-q -t '^(?!Rack::Adapter|Rack::Session::Memcache|Rack::Server|Rack::Handler)'" + + sh "bacon -w -I./lib:./test #{opts} #{specopts}" +end + +desc "Run all the tests we run on CI" +task :ci => :fulltest + +desc "Run all the tests" +task :fulltest => %w[SPEC chmod] do + opts = ENV['TEST'] || '-a' + specopts = ENV['TESTOPTS'] || '-q' + sh "bacon -r./test/gemloader -I./lib:./test -w #{opts} #{specopts}" +end + +task :gem => ["SPEC"] do + sh "gem build rack.gemspec" +end + +task :doc => :rdoc +desc "Generate RDoc documentation" +task :rdoc => %w[ChangeLog SPEC] do + sh(*%w{rdoc --line-numbers --main README.rdoc + --title 'Rack\ Documentation' --charset utf-8 -U -o doc} + + %w{README.rdoc KNOWN-ISSUES SPEC ChangeLog} + + `git ls-files lib/\*\*/\*.rb`.strip.split) + cp "contrib/rdoc.css", "doc/rdoc.css" +end + +task :pushdoc => %w[rdoc] do + sh "rsync -avz doc/ rack.rubyforge.org:/var/www/gforge-projects/rack/doc/" +end + +task :pushsite => %w[pushdoc] do + sh "cd site && git gc" + sh "rsync -avz site/ rack.rubyforge.org:/var/www/gforge-projects/rack/" + sh "cd site && git push" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/SPEC b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/SPEC new file mode 100644 index 000000000..6871b815a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/SPEC @@ -0,0 +1,264 @@ +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 a 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 instance of Hash 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. See + + RFC3875 section 4.1.18 for + specific behavior. +In addition to this, the Rack environment must include these +Rack-specific variables: +rack.version:: The Array representing this version of Rack + See Rack::VERSION, that corresponds to + the version of this SPEC. +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). +rack.hijack?:: present and true if the server supports + connection hijacking. See below, hijacking. +rack.hijack:: an object responding to #call that must be + called at least once before using + rack.hijack_io. + It is recommended #call return rack.hijack_io + as well as setting it in env if necessary. +rack.hijack_io:: if rack.hijack? is true, and rack.hijack + has received #call, this will contain + an object resembling an IO. See hijacking. +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) +rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. +rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. +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. +* There may be a valid hijack stream in rack.hijack_io +* 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 a 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. +=== Hijacking +==== Request (before status) +If rack.hijack? is true then rack.hijack must respond to #call. +rack.hijack must return the io that will also be assigned (or is +already present, in rack.hijack_io. +rack.hijack_io must respond to: +read, write, read_nonblock, write_nonblock, flush, close, +close_read, close_write, closed? +The semantics of these IO methods must be a best effort match to +those of a normal ruby IO or Socket object, using standard +arguments and raising standard exceptions. Servers are encouraged +to simply pass on real IO objects, although it is recognized that +this approach is not directly compatible with SPDY and HTTP 2.0. +IO provided in rack.hijack_io should preference the +IO::WaitReadable and IO::WaitWritable APIs wherever supported. +There is a deliberate lack of full specification around +rack.hijack_io, as semantics will change from server to server. +Users are encouraged to utilize this API with a knowledge of their +server choice, and servers may extend the functionality of +hijack_io to provide additional features to users. The purpose of +rack.hijack is for Rack to "get out of the way", as such, Rack only +provides the minimum of specification and support. +If rack.hijack? is false, then rack.hijack should not be set. +If rack.hijack? is false, then rack.hijack_io should not be set. +==== Response (after headers) +It is also possible to hijack a response after the status and headers +have been sent. +In order to do this, an application may set the special header +rack.hijack to an object that responds to call +accepting an argument that conforms to the rack.hijack_io +protocol. +After the headers have been sent, and this hijack callback has been +called, the application is now responsible for the remaining lifecycle +of the IO. The application is also responsible for maintaining HTTP +semantics. Of specific note, in almost all cases in the current SPEC, +applications will have wanted to specify the header Connection:close in +HTTP/1.1, and not Connection:keep-alive, as there is no protocol for +returning hijacked sockets to the web server. For that purpose, use the +body streaming API instead (progressively yielding strings via each). +Servers must ignore the body part of the response tuple when +the rack.hijack response API is in use. +The special response header rack.hijack must only be set +if the request env has rack.hijack? true. +==== Conventions +* Middleware should not use hijack unless it is handling the whole + response. +* Middleware may wrap the IO object for the response pattern. +* Middleware should not wrap the IO object for the request pattern. The + request pattern is intended to provide the hijacker with "raw tcp". +== 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. +Special headers starting "rack." are for communicating with the +server, and must not be sent back to the client. +The header keys must be Strings. +The header must not contain a +Status+ key. +The header must conform to RFC7230 token specification, i.e. cannot +contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". +The values of the header must be Strings, +consisting of lines (for multiple header values, e.g. multiple +Set-Cookie values) separated by "\\n". +The lines must not contain characters below 037. +=== The Content-Type +There must not be a Content-Type, when the +Status+ is 1xx, +204, 205 or 304. +=== The Content-Length +There must not be a Content-Length header when the ++Status+ is 1xx, 204, 205 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 is replaced by a middleware after action, the original body +must be closed first, if it responds to close. +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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/bin/rackup b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/bin/rackup new file mode 100755 index 000000000..ad94af4be --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/bin/rackup @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require "rack" +Rack::Server.start diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.png b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.png new file mode 100644 index 000000000..0920c4fb0 Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.png differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.svg b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.svg new file mode 100644 index 000000000..0d3f961cc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack_logo.svg b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack_logo.svg new file mode 100644 index 000000000..434175aac --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rack_logo.svg @@ -0,0 +1,111 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rdoc.css b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rdoc.css new file mode 100644 index 000000000..f1e342f43 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/contrib/rdoc.css @@ -0,0 +1,412 @@ +/* Forked from the Darkfish templates rdoc.css file, much hacked, probably + * imperfect */ + +html { max-width: 960px; margin: 0 auto; } +body { + font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; +} +body.file-popup { + font-size: 90%; + margin-left: 0; +} + +h1 { + color: #4183C4; +} + +:link, +:visited { + color: #4183C4; + text-decoration: none; +} +:link:hover, +:visited:hover { + border-bottom: 1px dotted #4183C4; +} + +pre, pre.description { + font: 12px Monaco,"Courier New","DejaVu Sans Mono","Bitstream Vera Sans Mono",monospace; + padding: 1em; + overflow: auto; + line-height: 1.4; +} + +/* @group Generic Classes */ + +.initially-hidden { + display: none; +} + +#search-field { + width: 98%; +} + +.missing-docs { + font-size: 120%; + background: white url(images/wrench_orange.png) no-repeat 4px center; + color: #ccc; + line-height: 2em; + border: 1px solid #d00; + opacity: 1; + text-indent: 24px; + letter-spacing: 3px; + font-weight: bold; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +.target-section { + border: 2px solid #dcce90; + border-left-width: 8px; + background: #fff3c2; +} + +/* @end */ + +/* @group Index Page, Standalone file pages */ +.indexpage ul { + line-height: 160%; + list-style: none; +} +.indexpage ul :link, +.indexpage ul :visited { + font-size: 16px; +} + +.indexpage li { + padding-left: 20px; +} + +.indexpage ul > li { + background: url(images/bullet_black.png) no-repeat left 4px; +} +.indexpage li.method { + background: url(images/plugin.png) no-repeat left 4px; +} +.indexpage li.module { + background: url(images/package.png) no-repeat left 4px; +} +.indexpage li.class { + background: url(images/ruby.png) no-repeat left 4px; +} +.indexpage li.file { + background: url(images/page_white_text.png) no-repeat left 4px; +} +.indexpage li li { + background: url(images/tag_blue.png) no-repeat left 4px; +} +.indexpage li .toc-toggle { + width: 16px; + height: 16px; + background: url(images/add.png) no-repeat; +} + +.indexpage li .toc-toggle.open { + background: url(images/delete.png) no-repeat; +} + +/* @end */ + +/* @group Top-Level Structure */ + +.project-section, #file-metadata, #class-metadata { + display: block; + background: #f5f5f5; + margin-bottom: 1em; + padding: 0.5em; +} +.project-section h3, #file-metadata h3, #class-metadata h3 { + margin: 0; +} + +#metadata { + float: left; + width: 280px; +} + +#documentation { + margin: 2em 1em 2em 300px; +} + +#validator-badges { + clear: both; + margin: 1em 1em 2em; + font-size: smaller; +} + +/* @end */ + +/* @group Metadata Section */ + +#metadata ul, +#metadata dl, +#metadata p { + padding: 0px; + list-style: none; +} + +dl.svninfo { + color: #666; + margin: 0; +} +dl.svninfo dt { + font-weight: bold; +} + +ul.link-list li { + white-space: nowrap; +} +ul.link-list .type { + font-size: 8px; + text-transform: uppercase; + color: white; + background: #969696; +} + +/* @end */ + +/* @group Documentation Section */ + +.note-list { + margin: 8px 0; +} + +.label-list { + margin: 8px 1.5em; + border: 1px solid #ccc; +} +.description .label-list { + font-size: 14px; +} + +.note-list dt { + font-weight: bold; +} +.note-list dd { +} + +.label-list dt { + font-weight: bold; + background: #ddd; +} +.label-list dd { +} +.label-list dd + dt, +.note-list dd + dt { + margin-top: 0.7em; +} + +#documentation .section { + font-size: 90%; +} + +#documentation h2.section-header { + color: #333; + font-size: 175%; +} + +.documentation-section-title { + position: relative; +} +.documentation-section-title .section-click-top { + position: absolute; + top: 6px; + right: 12px; + font-size: 10px; + visibility: hidden; +} + +.documentation-section-title:hover .section-click-top { + visibility: visible; +} + +#documentation h3.section-header { + color: #333; + font-size: 150%; +} + +#constants-list > dl, +#attributes-list > dl { + margin: 1em 0 2em; + border: 0; +} +#constants-list > dl dt, +#attributes-list > dl dt { + font-weight: bold; + font-family: Monaco, "Andale Mono"; + background: inherit; +} +#constants-list > dl dt a, +#attributes-list > dl dt a { + color: inherit; +} +#constants-list > dl dd, +#attributes-list > dl dd { + margin: 0 0 1em 0; + color: #666; +} + +.documentation-section h2 { + position: relative; +} + +.documentation-section h2 a { + position: absolute; + top: 8px; + right: 10px; + font-size: 12px; + color: #9b9877; + visibility: hidden; +} + +.documentation-section h2:hover a { + visibility: visible; +} + +/* @group Method Details */ + +#documentation .method-source-code { + display: none; +} + +#documentation .method-detail { + margin: 0.2em 0.2em; + padding: 0.5em; +} +#documentation .method-detail:hover { + background-color: #f5f5f5; +} +#documentation .method-heading { + cursor: pointer; + position: relative; + font-size: 125%; + line-height: 125%; + font-weight: bold; + color: #333; + background: url(images/brick.png) no-repeat left bottom; + padding-left: 20px; +} +#documentation .method-heading :link, +#documentation .method-heading :visited { + color: inherit; +} +#documentation .method-click-advice { + position: absolute; + right: 5px; + font-size: 10px; + color: #aaa; + visibility: hidden; + background: url(images/zoom.png) no-repeat right 5px; + padding-right: 20px; + overflow: show; +} +#documentation .method-heading:hover .method-click-advice { + visibility: visible; +} + +#documentation .method-alias .method-heading { + color: #666; + background: url(images/brick_link.png) no-repeat left bottom; +} + +#documentation .method-description, +#documentation .aliases { + margin: 0 20px; + color: #666; +} + +#documentation .method-description p, +#documentation .aliases p { + line-height: 1.2em; +} + +#documentation .aliases { + font-style: italic; + cursor: default; +} +#documentation .method-description p { + margin-bottom: 0.5em; +} +#documentation .method-description ul { + margin-left: 1.5em; +} + +#documentation .attribute-method-heading { + background: url(images/tag_green.png) no-repeat left bottom; +} +#documentation #attribute-method-details .method-detail:hover { + background-color: transparent; + cursor: default; +} +#documentation .attribute-access-type { + font-size: 60%; + text-transform: uppercase; + vertical-align: super; +} + +.method-section .method-source-code { + background: white; +} + +/* @group Source Code */ + +.ruby-constant .ruby-keyword .ruby-ivar .ruby-operator .ruby-identifier +.ruby-node .ruby-comment .ruby-regexp .ruby-value { + background: transparent; +} + +/* Thanks GitHub!!! */ +.ruby-constant { color: #458; font-weight: bold; } +.ruby-keyword { color: black; font-weight: bold; } +.ruby-ivar { color: teal; } +.ruby-operator { color: #000; } +.ruby-identifier { color: black; } +.ruby-node { color: red; } +.ruby-comment { color: #998; font-weight: bold; } +.ruby-regexp { color: #009926; } +.ruby-value { color: #099; } +.ruby-string { color: red; } + +/* @group search results */ + +#search-section .section-header { + margin: 0; + padding: 0; +} + +#search-results { + width: 100%; + list-style: none; + margin: 0; + padding: 0; +} + +#search-results h1 { + font-size: 1em; + font-weight: normal; + text-shadow: none; +} + +#search-results .current { + background: #eee; +} + +#search-results li { + list-style: none; + line-height: 1em; + padding: 0.5em; + border-bottom: 1px solid black; +} + +#search-results .search-namespace { + font-weight: bold; +} + +#search-results li em { + background: yellow; + font-style: normal; +} + +#search-results pre { + margin: 0.5em; +} diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/lobster.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/lobster.ru new file mode 100644 index 000000000..cc7ffcae8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/lobster.ru @@ -0,0 +1,4 @@ +require 'rack/lobster' + +use Rack::ShowExceptions +run Rack::Lobster.new diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/protectedlobster.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/protectedlobster.rb new file mode 100644 index 000000000..d904b4cec --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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::Server.start :app => pretty_protected_lobster, :Port => 9292 diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/protectedlobster.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/example/protectedlobster.ru new file mode 100644 index 000000000..b0da62f0c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack.rb new file mode 100644 index 000000000..740ec0d5c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack.rb @@ -0,0 +1,98 @@ +# 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,3] + + # 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.6.4" + end + PATH_INFO = 'PATH_INFO'.freeze + REQUEST_METHOD = 'REQUEST_METHOD'.freeze + SCRIPT_NAME = 'SCRIPT_NAME'.freeze + QUERY_STRING = 'QUERY_STRING'.freeze + CACHE_CONTROL = 'Cache-Control'.freeze + CONTENT_LENGTH = 'Content-Length'.freeze + CONTENT_TYPE = 'Content-Type'.freeze + + GET = 'GET'.freeze + HEAD = 'HEAD'.freeze + + autoload :Builder, "rack/builder" + autoload :BodyProxy, "rack/body_proxy" + 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 :TempfileReaper, "rack/tempfile_reaper" + autoload :URLMap, "rack/urlmap" + autoload :Utils, "rack/utils" + autoload :Multipart, "rack/multipart" + + 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 + + module Utils + autoload :OkJson, "rack/utils/okjson" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/abstract/handler.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/abstract/handler.rb new file mode 100644 index 000000000..c657691e1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/abstract/request.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/abstract/request.rb new file mode 100644 index 000000000..80d1c272c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/abstract/request.rb @@ -0,0 +1,43 @@ +require 'rack/request' + +module Rack + module Auth + class AbstractRequest + + def initialize(env) + @env = env + end + + def request + @request ||= Request.new(@env) + end + + def provided? + !authorization_key.nil? + end + + def parts + @parts ||= @env[authorization_key].split(' ', 2) + end + + def scheme + @scheme ||= parts.first && parts.first.downcase + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/basic.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/basic.rb new file mode 100644 index 000000000..9c5892141 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/md5.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/md5.rb new file mode 100644 index 000000000..ddee35def --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/md5.rb @@ -0,0 +1,129 @@ +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(app, realm=nil, opaque=nil, &authenticator) + @passwords_hashed = nil + if opaque.nil? and realm.respond_to? :values_at + realm, opaque, @passwords_hashed = realm.values_at :realm, :opaque, :passwords_hashed + end + super(app, realm, &authenticator) + @opaque = opaque + 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) + pw = @authenticator.call(auth.username) + pw && Rack::Utils.secure_compare(digest(auth, pw), 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/nonce.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/nonce.rb new file mode 100644 index 000000000..57089cb30 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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? && (Time.now.to_i - @timestamp) > self.class.time_limit + end + + def fresh? + !stale? + end + + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/params.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/params.rb new file mode 100644 index 000000000..f35a7bab8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/params.rb @@ -0,0 +1,53 @@ +module Rack + module Auth + module Digest + class Params < Hash + + def self.parse(str) + Params[*split_header_value(str).map do |param| + k, v = param.split('=', 2) + [k, dequote(v)] + end.flatten] + 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 = ['nc', 'stale'] + + def to_s + map do |k, v| + "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v)) + end.join(', ') + end + + def quote(str) # From WEBrick::HTTPUtils + '"' << str.gsub(/[\\\"]/o, "\\\1") << '"' + end + + end + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/request.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/request.rb new file mode 100644 index 000000000..019943641 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/auth/digest/request.rb @@ -0,0 +1,41 @@ +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? + request.fullpath == uri + end + + def nonce + @nonce ||= Nonce.parse(params['nonce']) + end + + def params + @params ||= Params.parse(parts.last) + end + + def respond_to?(sym, *) + super or params.has_key? sym.to_s + end + + def method_missing(sym, *args) + return super unless params.has_key?(key = sym.to_s) + return params[key] if args.size == 0 + raise ArgumentError, "wrong number of arguments (#{args.size} for 0)" + end + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_18.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_18.rb new file mode 100644 index 000000000..ca3a6360a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_18.rb @@ -0,0 +1,56 @@ +# :stopdoc: + +# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x +# +# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb +# +# + +module URI + TBLENCWWWCOMP_ = {} # :nodoc: + 256.times do |i| + TBLENCWWWCOMP_[i.chr] = '%%%02X' % i + end + TBLENCWWWCOMP_[' '] = '+' + TBLENCWWWCOMP_.freeze + TBLDECWWWCOMP_ = {} # :nodoc: + 256.times do |i| + h, l = i>>4, i&15 + TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr + end + TBLDECWWWCOMP_['+'] = ' ' + TBLDECWWWCOMP_.freeze + + # Encode given +s+ to URL-encoded form data. + # + # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP + # (ASCII space) to + and converts others to %XX. + # + # This is an implementation of + # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data + # + # See URI.decode_www_form_component, URI.encode_www_form + def self.encode_www_form_component(s) + str = s.to_s + if RUBY_VERSION < "1.9" && $KCODE =~ /u/i + str.gsub(/([^ a-zA-Z0-9_.-]+)/) do + '%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase + end.tr(' ', '+') + else + str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]} + end + end + + # Decode given +str+ of URL-encoded form data. + # + # This decodes + to SP. + # + # See URI.encode_www_form_component, URI.decode_www_form + def self.decode_www_form_component(str, enc=nil) + raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str + str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]} + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_192.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_192.rb new file mode 100644 index 000000000..1a0522bf2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_192.rb @@ -0,0 +1,52 @@ +# :stopdoc: + +# Stolen from ruby core's uri/common.rb @32618ba to fix DoS issues in 1.9.2 +# +# https://github.com/ruby/ruby/blob/32618ba7438a2247042bba9b5d85b5d49070f5e5/lib/uri/common.rb +# +# Issue: +# http://redmine.ruby-lang.org/issues/5149 +# +# Relevant Fixes: +# https://github.com/ruby/ruby/commit/b5f91deee04aa6ccbe07c23c8222b937c22a799b +# https://github.com/ruby/ruby/commit/93177c1e5c3906abf14472ae0b905d8b5c72ce1b +# +# This should probably be removed once there is a Ruby 1.9.2 patch level that +# includes this fix. + +require 'uri/common' + +module URI + TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_) #:nodoc: + if TBLDECWWWCOMP_.empty? + 256.times do |i| + h, l = i>>4, i&15 + TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr + end + TBLDECWWWCOMP_['+'] = ' ' + TBLDECWWWCOMP_.freeze + end + + def self.decode_www_form(str, enc=Encoding::UTF_8) + return [] if str.empty? + unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str + raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})" + end + ary = [] + $&.scan(/([^=;&]+)=([^;&]*)/) do + ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)] + end + ary + end + + def self.decode_www_form_component(str, enc=Encoding::UTF_8) + raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str + str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) + end + + remove_const :WFKV_ if const_defined?(:WFKV_) + WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc: +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_193.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_193.rb new file mode 100644 index 000000000..2e5820339 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/backports/uri/common_193.rb @@ -0,0 +1,29 @@ +# :stopdoc: + +require 'uri/common' + +# Issue: +# http://bugs.ruby-lang.org/issues/5925 +# +# Relevant commit: +# https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1 + +module URI + 256.times do |i| + TBLENCWWWCOMP_[i.chr] = '%%%02X' % i + end + TBLENCWWWCOMP_[' '] = '+' + TBLENCWWWCOMP_.freeze + + 256.times do |i| + h, l = i>>4, i&15 + TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr + TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr + end + TBLDECWWWCOMP_['+'] = ' ' + TBLDECWWWCOMP_.freeze +end + +# :startdoc: diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/body_proxy.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/body_proxy.rb new file mode 100644 index 000000000..95a74626d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/body_proxy.rb @@ -0,0 +1,39 @@ +module Rack + class BodyProxy + def initialize(body, &block) + @body, @block, @closed = body, block, false + end + + def respond_to?(*args) + return false if args.first.to_s =~ /^to_ary$/ + super or @body.respond_to?(*args) + end + + def close + return if @closed + @closed = true + begin + @body.close if @body.respond_to? :close + ensure + @block.call + end + end + + def closed? + @closed + end + + # N.B. This method is a special case to address the bug described by #434. + # We are applying this special case for #each only. Future bugs of this + # class will be handled by requesting users to patch their ruby + # implementation, to save adding too many methods in this class. + def each(*args, &block) + @body.each(*args, &block) + end + + def method_missing(*args, &block) + super if args.first.to_s =~ /^to_ary$/ + @body.__send__(*args, &block) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/builder.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/builder.rb new file mode 100644 index 000000000..bda3be27f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/builder.rb @@ -0,0 +1,164 @@ +module Rack + # Rack::Builder implements a small DSL to iteratively construct Rack + # applications. + # + # Example: + # + # require 'rack/lobster' + # app = Rack::Builder.new do + # use Rack::CommonLogger + # use Rack::ShowExceptions + # map "/lobster" do + # use Rack::Lint + # run Rack::Lobster.new + # end + # end + # + # run app + # + # Or + # + # app = Rack::Builder.app do + # use Rack::CommonLogger + # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } + # end + # + # run app + # + # +use+ adds 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.*\Z/m, '') + app = new_from_string cfgfile, config + else + require config + app = Object.const_get(::File.basename(config, '.rb').capitalize) + end + return app, options + end + + def self.new_from_string(builder_script, file="(rackup)") + eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app", + TOPLEVEL_BINDING, file, 0 + end + + def initialize(default_app = nil,&block) + @use, @map, @run, @warmup = [], nil, default_app, nil + instance_eval(&block) if block_given? + end + + def self.app(default_app = nil, &block) + self.new(default_app, &block).to_app + end + + # Specifies middleware to use in a stack. + # + # class Middleware + # def initialize(app) + # @app = app + # end + # + # def call(env) + # env["rack.some_header"] = "setting an example" + # @app.call(env) + # end + # end + # + # use Middleware + # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } + # + # All requests through to this application will first be processed by the middleware class. + # The +call+ method in this example sets an additional environment key which then can be + # referenced in the application if required. + def use(middleware, *args, &block) + if @map + mapping, @map = @map, nil + @use << proc { |app| generate_map app, mapping } + end + @use << proc { |app| middleware.new(app, *args, &block) } + end + + # Takes an argument that is an object that responds to #call and returns a Rack response. + # The simplest form of this is a lambda object: + # + # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] } + # + # However this could also be a class: + # + # class Heartbeat + # def self.call(env) + # [200, { "Content-Type" => "text/plain" }, ["OK"]] + # end + # end + # + # run Heartbeat + def run(app) + @run = app + end + + # Takes a lambda or block that is used to warm-up the application. + # + # warmup do |app| + # client = Rack::MockRequest.new(app) + # client.get('/') + # end + # + # use SomeMiddleware + # run MyApp + def warmup(prc=nil, &block) + @warmup = prc || block + end + + # Creates a route within the application. + # + # Rack::Builder.app do + # map '/' do + # run Heartbeat + # end + # end + # + # The +use+ method can also be used here to specify middleware to run under a specific path: + # + # Rack::Builder.app do + # map '/' do + # use Middleware + # run Heartbeat + # end + # end + # + # This example includes a piece of middleware which will run before requests hit +Heartbeat+. + # + def map(path, &block) + @map ||= {} + @map[path] = block + end + + def to_app + app = @map ? generate_map(@run, @map) : @run + fail "missing run or map statement" unless app + app = @use.reverse.inject(app) { |a,e| e[a] } + @warmup.call(app) if @warmup + app + end + + def call(env) + to_app.call(env) + end + + private + + def generate_map(default_app, mapping) + mapped = default_app ? {'/' => default_app} : {} + mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app } + URLMap.new(mapped) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/cascade.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/cascade.rb new file mode 100644 index 000000000..6b8f415ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/cascade.rb @@ -0,0 +1,52 @@ +module Rack + # Rack::Cascade tries a request on several apps, and returns the + # first response that is not 404 or 405 (or in a list of configurable + # status codes). + + class Cascade + NotFound = [404, {CONTENT_TYPE => "text/plain"}, []] + + attr_reader :apps + + def initialize(apps, catch=[404, 405]) + @apps = []; @has_app = {} + apps.each { |app| add app } + + @catch = {} + [*catch].each { |status| @catch[status] = true } + end + + def call(env) + result = NotFound + + last_body = nil + + @apps.each do |app| + # The SPEC says that the body must be closed after it has been iterated + # by the server, or if it is replaced by a middleware action. Cascade + # replaces the body each time a cascade happens. It is assumed that nil + # does not respond to close, otherwise the previous application body + # will be closed. The final application body will not be closed, as it + # will be passed to the server as a result. + last_body.close if last_body.respond_to? :close + + result = app.call(env) + last_body = result[2] + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/chunked.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/chunked.rb new file mode 100644 index 000000000..36c4959d6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/chunked.rb @@ -0,0 +1,69 @@ +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 + + # A body wrapper that emits chunked responses + class Body + TERM = "\r\n" + TAIL = "0#{TERM}#{TERM}" + + include Rack::Utils + + def initialize(body) + @body = body + end + + def each + term = TERM + @body.each do |chunk| + size = bytesize(chunk) + next if size == 0 + + chunk = chunk.dup.force_encoding(Encoding::BINARY) if chunk.respond_to?(:force_encoding) + yield [size.to_s(16), term, chunk, term].join + end + yield TAIL + end + + def close + @body.close if @body.respond_to?(:close) + end + end + + def initialize(app) + @app = app + end + + # pre-HTTP/1.0 (informally "HTTP/0.9") HTTP requests did not have + # a version (nor response headers) + def chunkable_version?(ver) + case ver + when "HTTP/1.0", nil, "HTTP/0.9" + false + else + true + end + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if ! chunkable_version?(env['HTTP_VERSION']) || + STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers[CONTENT_LENGTH] || + headers['Transfer-Encoding'] + [status, headers, body] + else + headers.delete(CONTENT_LENGTH) + headers['Transfer-Encoding'] = 'chunked' + [status, headers, Body.new(body)] + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/commonlogger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/commonlogger.rb new file mode 100644 index 000000000..d2d6dc342 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/commonlogger.rb @@ -0,0 +1,72 @@ +require 'rack/body_proxy' + +module Rack + # Rack::CommonLogger forwards every request to the given +app+, and + # logs a line in the + # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common] + # to the +logger+. + # + # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is + # an instance of Rack::NullLogger. + # + # +logger+ can be any class, including the standard library Logger, and is + # expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT. + # According to the SPEC, the error stream must also respond to +puts+ + # (which takes a single argument that responds to +to_s+), and +flush+ + # (which is called without arguments in order to make the error appear for + # sure) + class CommonLogger + # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common + # + # lilith.local - - [07/Aug/2006 23:58:02 -0400] "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) + body = BodyProxy.new(body) { 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) + + msg = FORMAT % [ + env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-", + env["REMOTE_USER"] || "-", + now.strftime("%d/%b/%Y:%H:%M:%S %z"), + 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 ] + + logger = @logger || env['rack.errors'] + # Standard library logger doesn't support write but it supports << which actually + # calls to write on the log device without formatting + if logger.respond_to?(:write) + logger.write(msg) + else + logger << msg + end + end + + def extract_content_length(headers) + value = headers[CONTENT_LENGTH] or return '-' + value.to_s == '0' ? '-' : value + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/conditionalget.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/conditionalget.rb new file mode 100644 index 000000000..441dd3823 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/conditionalget.rb @@ -0,0 +1,79 @@ +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: + # https://github.com/wycats/merb/blob/master/merb-core/lib/merb-core/rack/middleware/conditional_get.rb + class ConditionalGet + def initialize(app) + @app = app + end + + def call(env) + case env[REQUEST_METHOD] + when "GET", "HEAD" + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + if status == 200 && fresh?(env, headers) + status = 304 + headers.delete(CONTENT_TYPE) + headers.delete(CONTENT_LENGTH) + original_body = body + body = Rack::BodyProxy.new([]) do + original_body.close if original_body.respond_to?(:close) + end + end + [status, headers, body] + else + @app.call(env) + end + end + + private + + def fresh?(env, headers) + modified_since = env['HTTP_IF_MODIFIED_SINCE'] + none_match = env['HTTP_IF_NONE_MATCH'] + + return false unless modified_since || none_match + + success = true + success &&= modified_since?(to_rfc2822(modified_since), headers) if modified_since + success &&= etag_matches?(none_match, headers) if none_match + success + end + + def etag_matches?(none_match, headers) + etag = headers['ETag'] and etag == none_match + end + + def modified_since?(modified_since, headers) + last_modified = to_rfc2822(headers['Last-Modified']) and + modified_since and + modified_since >= last_modified + end + + def to_rfc2822(since) + # shortest possible valid date is the obsolete: 1 Nov 97 09:55 A + # anything shorter is invalid, this avoids exceptions for common cases + # most common being the empty string + if since && since.length >= 16 + # NOTE: there is no trivial way to write this in a non execption way + # _rfc2822 returns a hash but is not that usable + Time.rfc2822(since) rescue nil + else + nil + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/config.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/config.rb new file mode 100644 index 000000000..dc255d27e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/config.rb @@ -0,0 +1,20 @@ +module Rack + # Rack::Config modifies the environment using the block given during + # initialization. + # + # Example: + # use Rack::Config do |env| + # env['my-key'] = 'some-value' + # end + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_length.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_length.rb new file mode 100644 index 000000000..d0f491d1c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_length.rb @@ -0,0 +1,37 @@ +require 'rack/utils' +require 'rack/body_proxy' + +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.to_i) && + !headers[CONTENT_LENGTH] && + !headers['Transfer-Encoding'] && + body.respond_to?(:to_ary) + + obody = body + body, length = [], 0 + obody.each { |part| body << part; length += bytesize(part) } + + body = BodyProxy.new(body) do + obody.close if obody.respond_to?(:close) + end + + headers[CONTENT_LENGTH] = length.to_s + end + + [status, headers, body] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_type.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_type.rb new file mode 100644 index 000000000..78ba43b71 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/content_type.rb @@ -0,0 +1,29 @@ +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 + include Rack::Utils + + 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) + + unless STATUS_WITH_NO_ENTITY_BODY.include?(status) + headers[CONTENT_TYPE] ||= @content_type + end + + [status, headers, body] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/deflater.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/deflater.rb new file mode 100644 index 000000000..1bddedb1a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/deflater.rb @@ -0,0 +1,154 @@ +require "zlib" +require "time" # for Time.httpdate +require 'rack/utils' + +module Rack + # This middleware enables compression of http responses. + # + # Currently supported compression algorithms: + # + # * gzip + # * deflate + # * identity (no transformation) + # + # The middleware automatically detects when compression is supported + # and allowed. For example no transformation is made when a cache + # directive of 'no-transform' is present, or when the response status + # code is one that doesn't allow an entity body. + class Deflater + ## + # Creates Rack::Deflater middleware. + # + # [app] rack app instance + # [options] hash of deflater options, i.e. + # 'if' - a lambda enabling / disabling deflation based on returned boolean value + # e.g use Rack::Deflater, :if => lambda { |env, status, headers, body| body.length > 512 } + # 'include' - a list of content types that should be compressed + def initialize(app, options = {}) + @app = app + + @condition = options[:if] + @compressible_types = options[:include] + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + + unless should_deflate?(env, status, headers, body) + 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." + bp = Rack::BodyProxy.new([message]) { body.close if body.respond_to?(:close) } + [406, {CONTENT_TYPE => "text/plain", CONTENT_LENGTH => message.length.to_s}, bp] + end + end + + class GzipStream + def initialize(body, mtime) + @body = body + @mtime = mtime + @closed = false + end + + def each(&block) + @writer = block + gzip =::Zlib::GzipWriter.new(self) + gzip.mtime = @mtime + @body.each { |part| + gzip.write(part) + gzip.flush + } + ensure + gzip.close + @writer = nil + end + + def write(data) + @writer.call(data) + end + + def close + return if @closed + @closed = true + @body.close if @body.respond_to?(:close) + 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 + @closed = false + end + + def each + deflator = ::Zlib::Deflate.new(*DEFLATE_ARGS) + @body.each { |part| yield deflator.deflate(part, Zlib::SYNC_FLUSH) } + yield deflator.finish + nil + ensure + deflator.close + end + + def close + return if @closed + @closed = true + @body.close if @body.respond_to?(:close) + end + end + + private + + def should_deflate?(env, status, headers, body) + # 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/ || + (headers['Content-Encoding'] && headers['Content-Encoding'] !~ /\bidentity\b/) + return false + end + + # Skip if @compressible_types are given and does not include request's content type + return false if @compressible_types && !(headers.has_key?('Content-Type') && @compressible_types.include?(headers['Content-Type'][/[^;]*/])) + + # Skip if @condition lambda is given and evaluates to false + return false if @condition && !@condition.call(env, status, headers, body) + + true + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/directory.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/directory.rb new file mode 100644 index 000000000..98d66e02f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/directory.rb @@ -0,0 +1,167 @@ +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, '*') + + url_head = (@script_name.split('/') + @path_info.split('/')).map do |part| + Rack::Utils.escape part + end + + Dir[glob].sort.each do |node| + stat = stat(node) + next unless stat + basename = F.basename(node) + ext = F.extname(node) + + url = F.join(*url_head + [Rack::Utils.escape(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 = Rack::Utils.escape_html(@path.sub(/^#{@root}/,'')) + files = @files.map{|f| DIR_FILE % DIR_FILE_escape(*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 + + private + # Assumes url is already escaped. + def DIR_FILE_escape url, *html + [url, *html.map { |e| Utils.escape_html(e) }] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/etag.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/etag.rb new file mode 100644 index 000000000..88973131d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/etag.rb @@ -0,0 +1,73 @@ +require 'digest/md5' + +module Rack + # Automatically sets the ETag header on all String bodies. + # + # The ETag header is skipped if ETag or Last-Modified headers are sent or if + # a sendfile body (body.responds_to :to_path) is given (since such cases + # should be handled by apache/nginx). + # + # On initialization, you can pass two parameters: a Cache-Control directive + # used when Etag is absent and a directive when it is present. The first + # defaults to nil, while the second defaults to "max-age=0, private, must-revalidate" + class ETag + ETAG_STRING = 'ETag'.freeze + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze + + def initialize(app, no_cache_control = nil, cache_control = DEFAULT_CACHE_CONTROL) + @app = app + @cache_control = cache_control + @no_cache_control = no_cache_control + end + + def call(env) + status, headers, body = @app.call(env) + + if etag_status?(status) && etag_body?(body) && !skip_caching?(headers) + original_body = body + digest, new_body = digest_body(body) + body = Rack::BodyProxy.new(new_body) do + original_body.close if original_body.respond_to?(:close) + end + headers[ETAG_STRING] = %(W/"#{digest}") if digest + end + + unless headers[CACHE_CONTROL] + if digest + headers[CACHE_CONTROL] = @cache_control if @cache_control + else + headers[CACHE_CONTROL] = @no_cache_control if @no_cache_control + end + end + + [status, headers, body] + end + + private + + def etag_status?(status) + status == 200 || status == 201 + end + + def etag_body?(body) + !body.respond_to?(:to_path) + end + + def skip_caching?(headers) + (headers[CACHE_CONTROL] && headers[CACHE_CONTROL].include?('no-cache')) || + headers.key?(ETAG_STRING) || headers.key?('Last-Modified') + end + + def digest_body(body) + parts = [] + digest = nil + + body.each do |part| + parts << part + (digest ||= Digest::MD5.new) << part unless part.empty? + end + + [digest && digest.hexdigest, parts] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/file.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/file.rb new file mode 100644 index 000000000..d7b343ab8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/file.rb @@ -0,0 +1,152 @@ +require 'time' +require 'rack/utils' +require 'rack/mime' + +module Rack + # Rack::File serves files below the +root+ directory given, according to the + # path info of the Rack request. + # e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file + # as http://localhost:9292/passwd + # + # Handlers can detect if bodies are a Rack::File, and use mechanisms + # like sendfile on the +path+. + + class File + ALLOWED_VERBS = %w[GET HEAD OPTIONS] + ALLOW_HEADER = ALLOWED_VERBS.join(', ') + + attr_accessor :root + attr_accessor :path + attr_accessor :cache_control + + alias :to_path :path + + def initialize(root, headers={}, default_mime = 'text/plain') + @root = root + @headers = headers + @default_mime = default_mime + end + + def call(env) + dup._call(env) + end + + F = ::File + + def _call(env) + unless ALLOWED_VERBS.include? env[REQUEST_METHOD] + return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER}) + end + + path_info = Utils.unescape(env[PATH_INFO]) + clean_path_info = Utils.clean_path_info(path_info) + + @path = F.join(@root, clean_path_info) + + available = begin + F.file?(@path) && F.readable?(@path) + rescue SystemCallError + false + end + + if available + serving(env) + else + fail(404, "File not found: #{path_info}") + end + end + + def serving(env) + if env["REQUEST_METHOD"] == "OPTIONS" + return [200, {'Allow' => ALLOW_HEADER, CONTENT_LENGTH => '0'}, []] + end + last_modified = F.mtime(@path).httpdate + return [304, {}, []] if env['HTTP_IF_MODIFIED_SINCE'] == last_modified + + headers = { "Last-Modified" => last_modified } + headers[CONTENT_TYPE] = mime_type if mime_type + + # Set custom headers + @headers.each { |field, content| headers[field] = content } if @headers + + response = [ 200, headers, env[REQUEST_METHOD] == "HEAD" ? [] : self ] + + size = filesize + + ranges = Rack::Utils.byte_ranges(env, size) + if ranges.nil? || ranges.length > 1 + # No ranges, or multiple ranges (which we don't support): + # TODO: Support multiple byte-ranges + response[0] = 200 + @range = 0..size-1 + elsif ranges.empty? + # Unsatisfiable. Return error, and file size: + response = fail(416, "Byte range unsatisfiable") + response[1]["Content-Range"] = "bytes */#{size}" + return response + else + # Partial content: + @range = ranges[0] + response[0] = 206 + response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}" + size = @range.end - @range.begin + 1 + end + + response[2] = [response_body] unless response_body.nil? + + response[1][CONTENT_LENGTH] = size.to_s + response + end + + def each + F.open(@path, "rb") do |file| + file.seek(@range.begin) + remaining_len = @range.end-@range.begin+1 + while remaining_len > 0 + part = file.read([8192, remaining_len].min) + break unless part + remaining_len -= part.length + + yield part + end + end + end + + private + + def fail(status, body, headers = {}) + body += "\n" + [ + status, + { + CONTENT_TYPE => "text/plain", + CONTENT_LENGTH => body.size.to_s, + "X-Cascade" => "pass" + }.merge!(headers), + [body] + ] + end + + # The MIME type for the contents of the file located at @path + def mime_type + Mime.mime_type(F.extname(@path), @default_mime) + end + + def filesize + # If response_body is present, use its size. + return Rack::Utils.bytesize(response_body) if response_body + + # 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. + F.size?(@path) || Utils.bytesize(F.read(@path)) + end + + # By default, the response body for file requests is nil. + # In this case, the response body will be generated later + # from the file at @path + def response_body + nil + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler.rb new file mode 100644 index 000000000..8a5fadd54 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler.rb @@ -0,0 +1,109 @@ +module Rack + # *Handlers* connect web servers with Rack. + # + # Rack includes Handlers for Thin, 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 + + unless @handlers.include? server + load_error = try_require('rack/handler', server) + end + + if klass = @handlers[server] + klass.split("::").inject(Object) { |o, x| o.const_get(x) } + else + const_get(server, false) + end + + rescue NameError => name_error + raise load_error || name_error + end + + # Select first available Rack handler given an `Array` of server names. + # Raises `LoadError` if no handler was found. + # + # > pick ['thin', 'webrick'] + # => Rack::Handler::WEBrick + def self.pick(server_names) + server_names = Array(server_names) + server_names.each do |server_name| + begin + return get(server_name.to_s) + rescue LoadError, NameError + end + end + + raise LoadError, "Couldn't find handler for: #{server_names.join(', ')}." + 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 + elsif ENV.include?("RACK_HANDLER") + self.get(ENV["RACK_HANDLER"]) + else + pick ['thin', 'puma', 'webrick'] + 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)) + nil + rescue LoadError => error + error + end + + def self.register(server, klass) + @handlers ||= {} + @handlers[server.to_s] = klass.to_s + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/cgi.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/cgi.rb new file mode 100644 index 000000000..78e135e90 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/cgi.rb @@ -0,0 +1,61 @@ +require 'rack/content_length' +require 'rack/rewindable_input' + +module Rack + module Handler + class CGI + def self.run(app, options=nil) + $stdin.binmode + serve app + end + + def self.serve(app) + env = ENV.to_hash + env.delete "HTTP_CONTENT_LENGTH" + + env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" + + env.update({"rack.version" => Rack::VERSION, + "rack.input" => Rack::RewindableInput.new($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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/evented_mongrel.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/evented_mongrel.rb new file mode 100644 index 000000000..0f5cbf729 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/fastcgi.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/fastcgi.rb new file mode 100644 index 000000000..5137992b9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/fastcgi.rb @@ -0,0 +1,101 @@ +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={}) + if options[:File] + STDIN.reopen(UNIXServer.new(options[:File])) + elsif options[:Port] + STDIN.reopen(TCPServer.new(options[:Host], options[:Port])) + end + FCGI.each { |request| + serve request, app + } + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + "File=PATH" => "Creates a Domain socket at PATH instead of a TCP socket. Ignores Host and Port if set.", + } + end + + def self.serve(request, 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" => Rack::VERSION, + "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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/lsws.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/lsws.rb new file mode 100644 index 000000000..aec273229 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/lsws.rb @@ -0,0 +1,61 @@ +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) + 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" => Rack::VERSION, + "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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/mongrel.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/mongrel.rb new file mode 100644 index 000000000..ab9891b12 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/mongrel.rb @@ -0,0 +1,106 @@ +require 'mongrel' +require 'stringio' +require 'rack/content_length' +require 'rack/chunked' + +module Rack + module Handler + class Mongrel < ::Mongrel::HttpHandler + def self.run(app, options={}) + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + server = ::Mongrel::HttpServer.new( + options[:Host] || default_host, + 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 self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + "Processors=N" => "Number of concurrent processors to accept (default: 950)", + "Timeout=N" => "Time before a request is dropped for inactivity (default: 60)", + "Throttle=N" => "Throttle time between socket.accept calls in hundredths of a second (default: 0)", + } + end + + def initialize(app) + @app = app + end + + def process(request, response) + env = Hash[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" => Rack::VERSION, + "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[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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/scgi.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/scgi.rb new file mode 100644 index 000000000..9a465eae7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/scgi.rb @@ -0,0 +1,70 @@ +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) + options[:Socket] = UNIXServer.new(options[:File]) if options[:File] + new(options.merge(:app=>app, + :host=>options[:Host], + :port=>options[:Port], + :socket=>options[:Socket])).listen + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + + def initialize(settings = {}) + @app = settings[:app] + super(settings) + end + + def process_request(request, input_body, socket) + env = Hash[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" => Rack::VERSION, + "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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/swiftiplied_mongrel.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/swiftiplied_mongrel.rb new file mode 100644 index 000000000..4bafd0b95 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/thin.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/thin.rb new file mode 100644 index 000000000..704db06c7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/thin.rb @@ -0,0 +1,33 @@ +require "thin" +require "rack/content_length" +require "rack/chunked" + +module Rack + module Handler + class Thin + def self.run(app, options={}) + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + host = options.delete(:Host) || default_host + port = options.delete(:Port) || 8080 + args = [host, port, app, options] + # Thin versions below 0.8.0 do not support additional options + args.pop if ::Thin::VERSION::MAJOR < 1 && ::Thin::VERSION::MINOR < 8 + server = ::Thin::Server.new(*args) + yield server if block_given? + server.start + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/webrick.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/webrick.rb new file mode 100644 index 000000000..9594fa04a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/handler/webrick.rb @@ -0,0 +1,122 @@ +require 'webrick' +require 'stringio' +require 'rack/content_length' + +# This monkey patch allows for applications to perform their own chunking +# through WEBrick::HTTPResponse iff rack is set to true. +class WEBrick::HTTPResponse + attr_accessor :rack + + alias _rack_setup_header setup_header + def setup_header + app_chunking = rack && @header['transfer-encoding'] == 'chunked' + + @chunked = app_chunking if app_chunking + + _rack_setup_header + + @chunked = false if app_chunking + end +end + +module Rack + module Handler + class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet + def self.run(app, options={}) + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + options[:BindAddress] = options.delete(:Host) || default_host + options[:Port] ||= 8080 + @server = ::WEBrick::HTTPServer.new(options) + @server.mount "/", Rack::Handler::WEBrick, app + yield @server if block_given? + @server.start + end + + def self.valid_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + "Host=HOST" => "Hostname to listen on (default: #{default_host})", + "Port=PORT" => "Port to listen on (default: 8080)", + } + end + + def self.shutdown + @server.shutdown + @server = nil + end + + def initialize(server, app) + super server + @app = app + end + + def service(req, res) + res.rack = true + 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" => Rack::VERSION, + "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", + + "rack.hijack?" => true, + "rack.hijack" => lambda { raise NotImplementedError, "only partial hijack is supported."}, + "rack.hijack_io" => nil, + }) + + env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] + env[QUERY_STRING] ||= "" + unless env[PATH_INFO] == "" + path, n = req.request_uri.path, env["SCRIPT_NAME"].length + env[PATH_INFO] = path[n, path.length-n] + end + env["REQUEST_PATH"] ||= [env["SCRIPT_NAME"], env[PATH_INFO]].join + + status, headers, body = @app.call(env) + begin + res.status = status.to_i + headers.each { |k, vs| + next if k.downcase == "rack.hijack" + + if k.downcase == "set-cookie" + res.cookies.concat vs.split("\n") + else + # Since WEBrick won't accept repeated headers, + # merge the values per RFC 1945 section 4.2. + res[k] = vs.split("\n").join(", ") + end + } + + io_lambda = headers["rack.hijack"] + if io_lambda + rd, wr = IO.pipe + res.body = rd + res.chunked = true + io_lambda.call wr + elsif body.respond_to?(:to_path) + res.body = ::File.open(body.to_path, 'rb') + else + body.each { |part| + res.body << part + } + end + ensure + body.close if body.respond_to? :close + end + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/head.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/head.rb new file mode 100644 index 000000000..f487254ad --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/head.rb @@ -0,0 +1,27 @@ +require 'rack/body_proxy' + +module Rack + +class Head + # Rack::Head returns an empty body for all HEAD requests. It leaves + # all other requests unchanged. + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + + if env[REQUEST_METHOD] == HEAD + [ + status, headers, Rack::BodyProxy.new([]) do + body.close if body.respond_to? :close + end + ] + else + [status, headers, body] + end + end +end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lint.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lint.rb new file mode 100644 index 000000000..df4a5832c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lint.rb @@ -0,0 +1,760 @@ +require 'rack/utils' +require 'forwardable' + +module Rack + # Rack::Lint validates your application and the requests and + # responses according to the Rack spec. + + class Lint + def initialize(app) + @app = app + @content_length = nil + 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 a 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 + + check_hijack_response headers, env + + ## and the *body*. + check_content_type status, headers + check_content_length status, headers + @head_request = env[REQUEST_METHOD] == "HEAD" + [status, headers, self] + end + + ## == The Environment + def check_env(env) + ## The environment must be an instance of Hash 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. See + ## + ## RFC3875 section 4.1.18 for + ## specific behavior. + + ## In addition to this, the Rack environment must include these + ## Rack-specific variables: + + ## rack.version:: The Array representing this version of Rack + ## See Rack::VERSION, that corresponds to + ## the version of this SPEC. + + ## 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). + + ## rack.hijack?:: present and true if the server supports + ## connection hijacking. See below, hijacking. + + ## rack.hijack:: an object responding to #call that must be + ## called at least once before using + ## rack.hijack_io. + ## It is recommended #call return rack.hijack_io + ## as well as setting it in env if necessary. + + ## rack.hijack_io:: if rack.hijack? is true, and rack.hijack + ## has received #call, this will contain + ## an object resembling an IO. See hijacking. + + ## 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 + + ## rack.multipart.buffer_size:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes. + if bufsize = env['rack.multipart.buffer_size'] + assert("rack.multipart.buffer_size must be an Integer > 0 if specified") { + bufsize.is_a?(Integer) && bufsize > 0 + } + end + + ## rack.multipart.tempfile_factory:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile. + if tempfile_factory = env['rack.multipart.tempfile_factory'] + assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) } + env['rack.multipart.tempfile_factory'] = lambda do |filename, content_type| + io = tempfile_factory.call(filename, content_type) + assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) } + io + end + 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"] + ## * There may be a valid hijack stream in rack.hijack_io + check_hijack env + + ## * 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 + + ## * +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 a 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 + + class HijackWrapper + include Assertion + extend Forwardable + + REQUIRED_METHODS = [ + :read, :write, :read_nonblock, :write_nonblock, :flush, :close, + :close_read, :close_write, :closed? + ] + + def_delegators :@io, *REQUIRED_METHODS + + def initialize(io) + @io = io + REQUIRED_METHODS.each do |meth| + assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth } + end + end + end + + ## === Hijacking + # + # AUTHORS: n.b. The trailing whitespace between paragraphs is important and + # should not be removed. The whitespace creates paragraphs in the RDoc + # output. + # + ## ==== Request (before status) + def check_hijack(env) + if env['rack.hijack?'] + ## If rack.hijack? is true then rack.hijack must respond to #call. + original_hijack = env['rack.hijack'] + assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) } + env['rack.hijack'] = proc do + ## rack.hijack must return the io that will also be assigned (or is + ## already present, in rack.hijack_io. + io = original_hijack.call + HijackWrapper.new(io) + ## + ## rack.hijack_io must respond to: + ## read, write, read_nonblock, write_nonblock, flush, close, + ## close_read, close_write, closed? + ## + ## The semantics of these IO methods must be a best effort match to + ## those of a normal ruby IO or Socket object, using standard + ## arguments and raising standard exceptions. Servers are encouraged + ## to simply pass on real IO objects, although it is recognized that + ## this approach is not directly compatible with SPDY and HTTP 2.0. + ## + ## IO provided in rack.hijack_io should preference the + ## IO::WaitReadable and IO::WaitWritable APIs wherever supported. + ## + ## There is a deliberate lack of full specification around + ## rack.hijack_io, as semantics will change from server to server. + ## Users are encouraged to utilize this API with a knowledge of their + ## server choice, and servers may extend the functionality of + ## hijack_io to provide additional features to users. The purpose of + ## rack.hijack is for Rack to "get out of the way", as such, Rack only + ## provides the minimum of specification and support. + env['rack.hijack_io'] = HijackWrapper.new(env['rack.hijack_io']) + io + end + else + ## + ## If rack.hijack? is false, then rack.hijack should not be set. + assert("rack.hijack? is false, but rack.hijack is present") { env['rack.hijack'].nil? } + ## + ## If rack.hijack? is false, then rack.hijack_io should not be set. + assert("rack.hijack? is false, but rack.hijack_io is present") { env['rack.hijack_io'].nil? } + end + end + + ## ==== Response (after headers) + ## It is also possible to hijack a response after the status and headers + ## have been sent. + def check_hijack_response(headers, env) + + # this check uses headers like a hash, but the spec only requires + # headers respond to #each + headers = Rack::Utils::HeaderHash.new(headers) + + ## In order to do this, an application may set the special header + ## rack.hijack to an object that responds to call + ## accepting an argument that conforms to the rack.hijack_io + ## protocol. + ## + ## After the headers have been sent, and this hijack callback has been + ## called, the application is now responsible for the remaining lifecycle + ## of the IO. The application is also responsible for maintaining HTTP + ## semantics. Of specific note, in almost all cases in the current SPEC, + ## applications will have wanted to specify the header Connection:close in + ## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for + ## returning hijacked sockets to the web server. For that purpose, use the + ## body streaming API instead (progressively yielding strings via each). + ## + ## Servers must ignore the body part of the response tuple when + ## the rack.hijack response API is in use. + + if env['rack.hijack?'] && headers['rack.hijack'] + assert('rack.hijack header must respond to #call') { + headers['rack.hijack'].respond_to? :call + } + original_hijack = headers['rack.hijack'] + headers['rack.hijack'] = proc do |io| + original_hijack.call HijackWrapper.new(io) + end + else + ## + ## The special response header rack.hijack must only be set + ## if the request env has rack.hijack? true. + assert('rack.hijack header must not be present if server does not support hijacking') { + headers['rack.hijack'].nil? + } + end + end + ## ==== Conventions + ## * Middleware should not use hijack unless it is handling the whole + ## response. + ## * Middleware may wrap the IO object for the response pattern. + ## * Middleware should not wrap the IO object for the request pattern. The + ## request pattern is intended to provide the hijacker with "raw tcp". + + ## == 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| + ## Special headers starting "rack." are for communicating with the + ## server, and must not be sent back to the client. + next if key =~ /^rack\..+$/ + + ## 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" } + ## The header must conform to RFC7230 token specification, i.e. cannot + ## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}". + assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ } + + ## 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) separated 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 not be a Content-Type, when the +Status+ is 1xx, + ## 204, 205 or 304. + 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 + } + end + + ## === The Content-Length + def check_content_length(status, headers) + headers.each { |key, value| + if key.downcase == 'content-length' + ## There must not be a Content-Length header when the + ## +Status+ is 1xx, 204, 205 or 304. + assert("Content-Length header found in #{status} response, not allowed") { + not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i + } + @content_length = value + end + } + end + + def verify_content_length(bytes) + if @head_request + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 + } + elsif @content_length + assert("Content-Length header was #{@content_length}, but should be #{bytes}") { + @content_length == bytes.to_s + } + end + end + + ## === The Body + def each + @closed = false + bytes = 0 + + ## The Body must respond to +each+ + assert("Response body must respond to each") do + @body.respond_to?(:each) + end + + @body.each { |part| + ## and must only yield String values. + assert("Body yielded non-string value #{part.inspect}") { + part.kind_of? String + } + bytes += Rack::Utils.bytesize(part) + yield part + } + verify_content_length(bytes) + + ## + ## 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 is replaced by a middleware after action, the original body + ## must be closed first, if it responds to close. + # 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lobster.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lobster.rb new file mode 100644 index 000000000..b7d152786 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lobster.rb @@ -0,0 +1,70 @@ +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 do |line| + line.ljust(42).reverse. + gsub('\\', 'TEMP'). + gsub('/', '\\'). + gsub('TEMP', '/'). + gsub('{','}'). + gsub('(',')') + end.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::Server.start( + :app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)), :Port => 9292 + ) +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lock.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lock.rb new file mode 100644 index 000000000..b3139c037 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/lock.rb @@ -0,0 +1,26 @@ +require 'thread' +require 'rack/body_proxy' + +module Rack + # Rack::Lock locks every request inside a mutex, so that every request + # will effectively be executed synchronously. + class Lock + FLAG = 'rack.multithread'.freeze + + def initialize(app, mutex = Mutex.new) + @app, @mutex = app, mutex + end + + def call(env) + old, env[FLAG] = env[FLAG], false + @mutex.lock + response = @app.call(env) + body = BodyProxy.new(response[2]) { @mutex.unlock } + response[2] = body + response + ensure + @mutex.unlock unless body + env[FLAG] = old + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/logger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/logger.rb new file mode 100644 index 000000000..88f9837d0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/logger.rb @@ -0,0 +1,18 @@ +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) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/methodoverride.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/methodoverride.rb new file mode 100644 index 000000000..c0963c1a6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/methodoverride.rb @@ -0,0 +1,43 @@ +module Rack + class MethodOverride + HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK) + + METHOD_OVERRIDE_PARAM_KEY = "_method".freeze + HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze + ALLOWED_METHODS = ["POST"] + + def initialize(app) + @app = app + end + + def call(env) + if allowed_methods.include?(env[REQUEST_METHOD]) + method = method_override(env) + if HTTP_METHODS.include?(method) + env["rack.methodoverride.original_method"] = env[REQUEST_METHOD] + env[REQUEST_METHOD] = method + end + end + + @app.call(env) + end + + def method_override(env) + req = Request.new(env) + method = method_override_param(req) || + env[HTTP_METHOD_OVERRIDE_HEADER] + method.to_s.upcase + end + + private + + def allowed_methods + ALLOWED_METHODS + end + + def method_override_param(req) + req.POST[METHOD_OVERRIDE_PARAM_KEY] + rescue Utils::InvalidParameterError, Utils::ParameterTypeError + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mime.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mime.rb new file mode 100644 index 000000000..3208cc3f5 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mime.rb @@ -0,0 +1,664 @@ +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 + + # Returns true if the given value is a mime match for the given mime match + # specification, false otherwise. + # + # Rack::Mime.match?('text/html', 'text/*') => true + # Rack::Mime.match?('text/plain', '*') => true + # Rack::Mime.match?('text/html', 'application/json') => false + + def match?(value, matcher) + v1, v2 = value.split('/', 2) + m1, m2 = matcher.split('/', 2) + + (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2) + end + module_function :match? + + # 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) + # + # N.B. On Ubuntu the mime.types file does not include the leading period, so + # users may need to modify the data before merging into the hash. + # + # To add the list mongrel provides, use: + # + # require 'mongrel/handlers' + # Rack::Mime::MIME_TYPES.merge!(Mongrel::DirHandler::MIME_TYPES) + + MIME_TYPES = { + ".123" => "application/vnd.lotus-1-2-3", + ".3dml" => "text/vnd.in3d.3dml", + ".3g2" => "video/3gpp2", + ".3gp" => "video/3gpp", + ".a" => "application/octet-stream", + ".acc" => "application/vnd.americandynamics.acc", + ".ace" => "application/x-ace-compressed", + ".acu" => "application/vnd.acucobol", + ".aep" => "application/vnd.audiograph", + ".afp" => "application/vnd.ibm.modcap", + ".ai" => "application/postscript", + ".aif" => "audio/x-aiff", + ".aiff" => "audio/x-aiff", + ".ami" => "application/vnd.amiga.ami", + ".appcache" => "text/cache-manifest", + ".apr" => "application/vnd.lotus-approach", + ".asc" => "application/pgp-signature", + ".asf" => "video/x-ms-asf", + ".asm" => "text/x-asm", + ".aso" => "application/vnd.accpac.simply.aso", + ".asx" => "video/x-ms-asf", + ".atc" => "application/vnd.acucorp", + ".atom" => "application/atom+xml", + ".atomcat" => "application/atomcat+xml", + ".atomsvc" => "application/atomsvc+xml", + ".atx" => "application/vnd.antix.game-component", + ".au" => "audio/basic", + ".avi" => "video/x-msvideo", + ".bat" => "application/x-msdownload", + ".bcpio" => "application/x-bcpio", + ".bdm" => "application/vnd.syncml.dm+wbxml", + ".bh2" => "application/vnd.fujitsu.oasysprs", + ".bin" => "application/octet-stream", + ".bmi" => "application/vnd.bmi", + ".bmp" => "image/bmp", + ".box" => "application/vnd.previewsystems.box", + ".btif" => "image/prs.btif", + ".bz" => "application/x-bzip", + ".bz2" => "application/x-bzip2", + ".c" => "text/x-c", + ".c4g" => "application/vnd.clonk.c4group", + ".cab" => "application/vnd.ms-cab-compressed", + ".cc" => "text/x-c", + ".ccxml" => "application/ccxml+xml", + ".cdbcmsg" => "application/vnd.contact.cmsg", + ".cdkey" => "application/vnd.mediastation.cdkey", + ".cdx" => "chemical/x-cdx", + ".cdxml" => "application/vnd.chemdraw+xml", + ".cdy" => "application/vnd.cinderella", + ".cer" => "application/pkix-cert", + ".cgm" => "image/cgm", + ".chat" => "application/x-chat", + ".chm" => "application/vnd.ms-htmlhelp", + ".chrt" => "application/vnd.kde.kchart", + ".cif" => "chemical/x-cif", + ".cii" => "application/vnd.anser-web-certificate-issue-initiation", + ".cil" => "application/vnd.ms-artgalry", + ".cla" => "application/vnd.claymore", + ".class" => "application/octet-stream", + ".clkk" => "application/vnd.crick.clicker.keyboard", + ".clkp" => "application/vnd.crick.clicker.palette", + ".clkt" => "application/vnd.crick.clicker.template", + ".clkw" => "application/vnd.crick.clicker.wordbank", + ".clkx" => "application/vnd.crick.clicker", + ".clp" => "application/x-msclip", + ".cmc" => "application/vnd.cosmocaller", + ".cmdf" => "chemical/x-cmdf", + ".cml" => "chemical/x-cml", + ".cmp" => "application/vnd.yellowriver-custom-menu", + ".cmx" => "image/x-cmx", + ".com" => "application/x-msdownload", + ".conf" => "text/plain", + ".cpio" => "application/x-cpio", + ".cpp" => "text/x-c", + ".cpt" => "application/mac-compactpro", + ".crd" => "application/x-mscardfile", + ".crl" => "application/pkix-crl", + ".crt" => "application/x-x509-ca-cert", + ".csh" => "application/x-csh", + ".csml" => "chemical/x-csml", + ".csp" => "application/vnd.commonspace", + ".css" => "text/css", + ".csv" => "text/csv", + ".curl" => "application/vnd.curl", + ".cww" => "application/prs.cww", + ".cxx" => "text/x-c", + ".daf" => "application/vnd.mobius.daf", + ".davmount" => "application/davmount+xml", + ".dcr" => "application/x-director", + ".dd2" => "application/vnd.oma.dd2+xml", + ".ddd" => "application/vnd.fujixerox.ddd", + ".deb" => "application/x-debian-package", + ".der" => "application/x-x509-ca-cert", + ".dfac" => "application/vnd.dreamfactory", + ".diff" => "text/x-diff", + ".dis" => "application/vnd.mobius.dis", + ".djv" => "image/vnd.djvu", + ".djvu" => "image/vnd.djvu", + ".dll" => "application/x-msdownload", + ".dmg" => "application/octet-stream", + ".dna" => "application/vnd.dna", + ".doc" => "application/msword", + ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".dot" => "application/msword", + ".dp" => "application/vnd.osgi.dp", + ".dpg" => "application/vnd.dpgraph", + ".dsc" => "text/prs.lines.tag", + ".dtd" => "application/xml-dtd", + ".dts" => "audio/vnd.dts", + ".dtshd" => "audio/vnd.dts.hd", + ".dv" => "video/x-dv", + ".dvi" => "application/x-dvi", + ".dwf" => "model/vnd.dwf", + ".dwg" => "image/vnd.dwg", + ".dxf" => "image/vnd.dxf", + ".dxp" => "application/vnd.spotfire.dxp", + ".ear" => "application/java-archive", + ".ecelp4800" => "audio/vnd.nuera.ecelp4800", + ".ecelp7470" => "audio/vnd.nuera.ecelp7470", + ".ecelp9600" => "audio/vnd.nuera.ecelp9600", + ".ecma" => "application/ecmascript", + ".edm" => "application/vnd.novadigm.edm", + ".edx" => "application/vnd.novadigm.edx", + ".efif" => "application/vnd.picsel", + ".ei6" => "application/vnd.pg.osasli", + ".eml" => "message/rfc822", + ".eol" => "audio/vnd.digital-winds", + ".eot" => "application/vnd.ms-fontobject", + ".eps" => "application/postscript", + ".es3" => "application/vnd.eszigno3+xml", + ".esf" => "application/vnd.epson.esf", + ".etx" => "text/x-setext", + ".exe" => "application/x-msdownload", + ".ext" => "application/vnd.novadigm.ext", + ".ez" => "application/andrew-inset", + ".ez2" => "application/vnd.ezpix-album", + ".ez3" => "application/vnd.ezpix-package", + ".f" => "text/x-fortran", + ".f77" => "text/x-fortran", + ".f90" => "text/x-fortran", + ".fbs" => "image/vnd.fastbidsheet", + ".fdf" => "application/vnd.fdf", + ".fe_launch" => "application/vnd.denovo.fcselayout-link", + ".fg5" => "application/vnd.fujitsu.oasysgp", + ".fli" => "video/x-fli", + ".flo" => "application/vnd.micrografx.flo", + ".flv" => "video/x-flv", + ".flw" => "application/vnd.kde.kivio", + ".flx" => "text/vnd.fmi.flexstor", + ".fly" => "text/vnd.fly", + ".fm" => "application/vnd.framemaker", + ".fnc" => "application/vnd.frogans.fnc", + ".for" => "text/x-fortran", + ".fpx" => "image/vnd.fpx", + ".fsc" => "application/vnd.fsc.weblaunch", + ".fst" => "image/vnd.fst", + ".ftc" => "application/vnd.fluxtime.clip", + ".fti" => "application/vnd.anser-web-funds-transfer-initiation", + ".fvt" => "video/vnd.fvt", + ".fzs" => "application/vnd.fuzzysheet", + ".g3" => "image/g3fax", + ".gac" => "application/vnd.groove-account", + ".gdl" => "model/vnd.gdl", + ".gem" => "application/octet-stream", + ".gemspec" => "text/x-script.ruby", + ".ghf" => "application/vnd.groove-help", + ".gif" => "image/gif", + ".gim" => "application/vnd.groove-identity-message", + ".gmx" => "application/vnd.gmx", + ".gph" => "application/vnd.flographit", + ".gqf" => "application/vnd.grafeq", + ".gram" => "application/srgs", + ".grv" => "application/vnd.groove-injector", + ".grxml" => "application/srgs+xml", + ".gtar" => "application/x-gtar", + ".gtm" => "application/vnd.groove-tool-message", + ".gtw" => "model/vnd.gtw", + ".gv" => "text/vnd.graphviz", + ".gz" => "application/x-gzip", + ".h" => "text/x-c", + ".h261" => "video/h261", + ".h263" => "video/h263", + ".h264" => "video/h264", + ".hbci" => "application/vnd.hbci", + ".hdf" => "application/x-hdf", + ".hh" => "text/x-c", + ".hlp" => "application/winhlp", + ".hpgl" => "application/vnd.hp-hpgl", + ".hpid" => "application/vnd.hp-hpid", + ".hps" => "application/vnd.hp-hps", + ".hqx" => "application/mac-binhex40", + ".htc" => "text/x-component", + ".htke" => "application/vnd.kenameaapp", + ".htm" => "text/html", + ".html" => "text/html", + ".hvd" => "application/vnd.yamaha.hv-dic", + ".hvp" => "application/vnd.yamaha.hv-voice", + ".hvs" => "application/vnd.yamaha.hv-script", + ".icc" => "application/vnd.iccprofile", + ".ice" => "x-conference/x-cooltalk", + ".ico" => "image/vnd.microsoft.icon", + ".ics" => "text/calendar", + ".ief" => "image/ief", + ".ifb" => "text/calendar", + ".ifm" => "application/vnd.shana.informed.formdata", + ".igl" => "application/vnd.igloader", + ".igs" => "model/iges", + ".igx" => "application/vnd.micrografx.igx", + ".iif" => "application/vnd.shana.informed.interchange", + ".imp" => "application/vnd.accpac.simply.imp", + ".ims" => "application/vnd.ms-ims", + ".ipk" => "application/vnd.shana.informed.package", + ".irm" => "application/vnd.ibm.rights-management", + ".irp" => "application/vnd.irepository.package+xml", + ".iso" => "application/octet-stream", + ".itp" => "application/vnd.shana.informed.formtemplate", + ".ivp" => "application/vnd.immervision-ivp", + ".ivu" => "application/vnd.immervision-ivu", + ".jad" => "text/vnd.sun.j2me.app-descriptor", + ".jam" => "application/vnd.jam", + ".jar" => "application/java-archive", + ".java" => "text/x-java-source", + ".jisp" => "application/vnd.jisp", + ".jlt" => "application/vnd.hp-jlyt", + ".jnlp" => "application/x-java-jnlp-file", + ".joda" => "application/vnd.joost.joda-archive", + ".jp2" => "image/jp2", + ".jpeg" => "image/jpeg", + ".jpg" => "image/jpeg", + ".jpgv" => "video/jpeg", + ".jpm" => "video/jpm", + ".js" => "application/javascript", + ".json" => "application/json", + ".karbon" => "application/vnd.kde.karbon", + ".kfo" => "application/vnd.kde.kformula", + ".kia" => "application/vnd.kidspiration", + ".kml" => "application/vnd.google-earth.kml+xml", + ".kmz" => "application/vnd.google-earth.kmz", + ".kne" => "application/vnd.kinar", + ".kon" => "application/vnd.kde.kontour", + ".kpr" => "application/vnd.kde.kpresenter", + ".ksp" => "application/vnd.kde.kspread", + ".ktz" => "application/vnd.kahootz", + ".kwd" => "application/vnd.kde.kword", + ".latex" => "application/x-latex", + ".lbd" => "application/vnd.llamagraphics.life-balance.desktop", + ".lbe" => "application/vnd.llamagraphics.life-balance.exchange+xml", + ".les" => "application/vnd.hhe.lesson-player", + ".link66" => "application/vnd.route66.link66+xml", + ".log" => "text/plain", + ".lostxml" => "application/lost+xml", + ".lrm" => "application/vnd.ms-lrm", + ".ltf" => "application/vnd.frogans.ltf", + ".lvp" => "audio/vnd.lucent.voice", + ".lwp" => "application/vnd.lotus-wordpro", + ".m3u" => "audio/x-mpegurl", + ".m4a" => "audio/mp4a-latm", + ".m4v" => "video/mp4", + ".ma" => "application/mathematica", + ".mag" => "application/vnd.ecowin.chart", + ".man" => "text/troff", + ".manifest" => "text/cache-manifest", + ".mathml" => "application/mathml+xml", + ".mbk" => "application/vnd.mobius.mbk", + ".mbox" => "application/mbox", + ".mc1" => "application/vnd.medcalcdata", + ".mcd" => "application/vnd.mcd", + ".mdb" => "application/x-msaccess", + ".mdi" => "image/vnd.ms-modi", + ".mdoc" => "text/troff", + ".me" => "text/troff", + ".mfm" => "application/vnd.mfmp", + ".mgz" => "application/vnd.proteus.magazine", + ".mid" => "audio/midi", + ".midi" => "audio/midi", + ".mif" => "application/vnd.mif", + ".mime" => "message/rfc822", + ".mj2" => "video/mj2", + ".mlp" => "application/vnd.dolby.mlp", + ".mmd" => "application/vnd.chipnuts.karaoke-mmd", + ".mmf" => "application/vnd.smaf", + ".mml" => "application/mathml+xml", + ".mmr" => "image/vnd.fujixerox.edmics-mmr", + ".mng" => "video/x-mng", + ".mny" => "application/x-msmoney", + ".mov" => "video/quicktime", + ".movie" => "video/x-sgi-movie", + ".mp3" => "audio/mpeg", + ".mp4" => "video/mp4", + ".mp4a" => "audio/mp4", + ".mp4s" => "application/mp4", + ".mp4v" => "video/mp4", + ".mpc" => "application/vnd.mophun.certificate", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mpga" => "audio/mpeg", + ".mpkg" => "application/vnd.apple.installer+xml", + ".mpm" => "application/vnd.blueice.multipass", + ".mpn" => "application/vnd.mophun.application", + ".mpp" => "application/vnd.ms-project", + ".mpy" => "application/vnd.ibm.minipay", + ".mqy" => "application/vnd.mobius.mqy", + ".mrc" => "application/marc", + ".ms" => "text/troff", + ".mscml" => "application/mediaservercontrol+xml", + ".mseq" => "application/vnd.mseq", + ".msf" => "application/vnd.epson.msf", + ".msh" => "model/mesh", + ".msi" => "application/x-msdownload", + ".msl" => "application/vnd.mobius.msl", + ".msty" => "application/vnd.muvee.style", + ".mts" => "model/vnd.mts", + ".mus" => "application/vnd.musician", + ".mvb" => "application/x-msmediaview", + ".mwf" => "application/vnd.mfer", + ".mxf" => "application/mxf", + ".mxl" => "application/vnd.recordare.musicxml", + ".mxml" => "application/xv+xml", + ".mxs" => "application/vnd.triscape.mxs", + ".mxu" => "video/vnd.mpegurl", + ".n" => "application/vnd.nokia.n-gage.symbian.install", + ".nc" => "application/x-netcdf", + ".ngdat" => "application/vnd.nokia.n-gage.data", + ".nlu" => "application/vnd.neurolanguage.nlu", + ".nml" => "application/vnd.enliven", + ".nnd" => "application/vnd.noblenet-directory", + ".nns" => "application/vnd.noblenet-sealer", + ".nnw" => "application/vnd.noblenet-web", + ".npx" => "image/vnd.net-fpx", + ".nsf" => "application/vnd.lotus-notes", + ".oa2" => "application/vnd.fujitsu.oasys2", + ".oa3" => "application/vnd.fujitsu.oasys3", + ".oas" => "application/vnd.fujitsu.oasys", + ".obd" => "application/x-msbinder", + ".oda" => "application/oda", + ".odc" => "application/vnd.oasis.opendocument.chart", + ".odf" => "application/vnd.oasis.opendocument.formula", + ".odg" => "application/vnd.oasis.opendocument.graphics", + ".odi" => "application/vnd.oasis.opendocument.image", + ".odp" => "application/vnd.oasis.opendocument.presentation", + ".ods" => "application/vnd.oasis.opendocument.spreadsheet", + ".odt" => "application/vnd.oasis.opendocument.text", + ".oga" => "audio/ogg", + ".ogg" => "application/ogg", + ".ogv" => "video/ogg", + ".ogx" => "application/ogg", + ".org" => "application/vnd.lotus-organizer", + ".otc" => "application/vnd.oasis.opendocument.chart-template", + ".otf" => "application/vnd.oasis.opendocument.formula-template", + ".otg" => "application/vnd.oasis.opendocument.graphics-template", + ".oth" => "application/vnd.oasis.opendocument.text-web", + ".oti" => "application/vnd.oasis.opendocument.image-template", + ".otm" => "application/vnd.oasis.opendocument.text-master", + ".ots" => "application/vnd.oasis.opendocument.spreadsheet-template", + ".ott" => "application/vnd.oasis.opendocument.text-template", + ".oxt" => "application/vnd.openofficeorg.extension", + ".p" => "text/x-pascal", + ".p10" => "application/pkcs10", + ".p12" => "application/x-pkcs12", + ".p7b" => "application/x-pkcs7-certificates", + ".p7m" => "application/pkcs7-mime", + ".p7r" => "application/x-pkcs7-certreqresp", + ".p7s" => "application/pkcs7-signature", + ".pas" => "text/x-pascal", + ".pbd" => "application/vnd.powerbuilder6", + ".pbm" => "image/x-portable-bitmap", + ".pcl" => "application/vnd.hp-pcl", + ".pclxl" => "application/vnd.hp-pclxl", + ".pcx" => "image/x-pcx", + ".pdb" => "chemical/x-pdb", + ".pdf" => "application/pdf", + ".pem" => "application/x-x509-ca-cert", + ".pfr" => "application/font-tdpfr", + ".pgm" => "image/x-portable-graymap", + ".pgn" => "application/x-chess-pgn", + ".pgp" => "application/pgp-encrypted", + ".pic" => "image/x-pict", + ".pict" => "image/pict", + ".pkg" => "application/octet-stream", + ".pki" => "application/pkixcmp", + ".pkipath" => "application/pkix-pkipath", + ".pl" => "text/x-script.perl", + ".plb" => "application/vnd.3gpp.pic-bw-large", + ".plc" => "application/vnd.mobius.plc", + ".plf" => "application/vnd.pocketlearn", + ".pls" => "application/pls+xml", + ".pm" => "text/x-script.perl-module", + ".pml" => "application/vnd.ctc-posml", + ".png" => "image/png", + ".pnm" => "image/x-portable-anymap", + ".pntg" => "image/x-macpaint", + ".portpkg" => "application/vnd.macports.portpkg", + ".ppd" => "application/vnd.cups-ppd", + ".ppm" => "image/x-portable-pixmap", + ".pps" => "application/vnd.ms-powerpoint", + ".ppt" => "application/vnd.ms-powerpoint", + ".prc" => "application/vnd.palm", + ".pre" => "application/vnd.lotus-freelance", + ".prf" => "application/pics-rules", + ".ps" => "application/postscript", + ".psb" => "application/vnd.3gpp.pic-bw-small", + ".psd" => "image/vnd.adobe.photoshop", + ".ptid" => "application/vnd.pvi.ptid1", + ".pub" => "application/x-mspublisher", + ".pvb" => "application/vnd.3gpp.pic-bw-var", + ".pwn" => "application/vnd.3m.post-it-notes", + ".py" => "text/x-script.python", + ".pya" => "audio/vnd.ms-playready.media.pya", + ".pyv" => "video/vnd.ms-playready.media.pyv", + ".qam" => "application/vnd.epson.quickanime", + ".qbo" => "application/vnd.intu.qbo", + ".qfx" => "application/vnd.intu.qfx", + ".qps" => "application/vnd.publishare-delta-tree", + ".qt" => "video/quicktime", + ".qtif" => "image/x-quicktime", + ".qxd" => "application/vnd.quark.quarkxpress", + ".ra" => "audio/x-pn-realaudio", + ".rake" => "text/x-script.ruby", + ".ram" => "audio/x-pn-realaudio", + ".rar" => "application/x-rar-compressed", + ".ras" => "image/x-cmu-raster", + ".rb" => "text/x-script.ruby", + ".rcprofile" => "application/vnd.ipunplugged.rcprofile", + ".rdf" => "application/rdf+xml", + ".rdz" => "application/vnd.data-vision.rdz", + ".rep" => "application/vnd.businessobjects", + ".rgb" => "image/x-rgb", + ".rif" => "application/reginfo+xml", + ".rl" => "application/resource-lists+xml", + ".rlc" => "image/vnd.fujixerox.edmics-rlc", + ".rld" => "application/resource-lists-diff+xml", + ".rm" => "application/vnd.rn-realmedia", + ".rmp" => "audio/x-pn-realaudio-plugin", + ".rms" => "application/vnd.jcp.javame.midlet-rms", + ".rnc" => "application/relax-ng-compact-syntax", + ".roff" => "text/troff", + ".rpm" => "application/x-redhat-package-manager", + ".rpss" => "application/vnd.nokia.radio-presets", + ".rpst" => "application/vnd.nokia.radio-preset", + ".rq" => "application/sparql-query", + ".rs" => "application/rls-services+xml", + ".rsd" => "application/rsd+xml", + ".rss" => "application/rss+xml", + ".rtf" => "application/rtf", + ".rtx" => "text/richtext", + ".ru" => "text/x-script.ruby", + ".s" => "text/x-asm", + ".saf" => "application/vnd.yamaha.smaf-audio", + ".sbml" => "application/sbml+xml", + ".sc" => "application/vnd.ibm.secure-container", + ".scd" => "application/x-msschedule", + ".scm" => "application/vnd.lotus-screencam", + ".scq" => "application/scvp-cv-request", + ".scs" => "application/scvp-cv-response", + ".sdkm" => "application/vnd.solent.sdkm+xml", + ".sdp" => "application/sdp", + ".see" => "application/vnd.seemail", + ".sema" => "application/vnd.sema", + ".semd" => "application/vnd.semd", + ".semf" => "application/vnd.semf", + ".setpay" => "application/set-payment-initiation", + ".setreg" => "application/set-registration-initiation", + ".sfd" => "application/vnd.hydrostatix.sof-data", + ".sfs" => "application/vnd.spotfire.sfs", + ".sgm" => "text/sgml", + ".sgml" => "text/sgml", + ".sh" => "application/x-sh", + ".shar" => "application/x-shar", + ".shf" => "application/shf+xml", + ".sig" => "application/pgp-signature", + ".sit" => "application/x-stuffit", + ".sitx" => "application/x-stuffitx", + ".skp" => "application/vnd.koan", + ".slt" => "application/vnd.epson.salt", + ".smi" => "application/smil+xml", + ".snd" => "audio/basic", + ".so" => "application/octet-stream", + ".spf" => "application/vnd.yamaha.smaf-phrase", + ".spl" => "application/x-futuresplash", + ".spot" => "text/vnd.in3d.spot", + ".spp" => "application/scvp-vp-response", + ".spq" => "application/scvp-vp-request", + ".src" => "application/x-wais-source", + ".srx" => "application/sparql-results+xml", + ".sse" => "application/vnd.kodak-descriptor", + ".ssf" => "application/vnd.epson.ssf", + ".ssml" => "application/ssml+xml", + ".stf" => "application/vnd.wt.stf", + ".stk" => "application/hyperstudio", + ".str" => "application/vnd.pg.format", + ".sus" => "application/vnd.sus-calendar", + ".sv4cpio" => "application/x-sv4cpio", + ".sv4crc" => "application/x-sv4crc", + ".svd" => "application/vnd.svd", + ".svg" => "image/svg+xml", + ".svgz" => "image/svg+xml", + ".swf" => "application/x-shockwave-flash", + ".swi" => "application/vnd.arastra.swi", + ".t" => "text/troff", + ".tao" => "application/vnd.tao.intent-module-archive", + ".tar" => "application/x-tar", + ".tbz" => "application/x-bzip-compressed-tar", + ".tcap" => "application/vnd.3gpp2.tcap", + ".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", + ".tmo" => "application/vnd.tmobile-livetv", + ".torrent" => "application/x-bittorrent", + ".tpl" => "application/vnd.groove-tool-template", + ".tpt" => "application/vnd.trid.tpt", + ".tr" => "text/troff", + ".tra" => "application/vnd.trueapp", + ".trm" => "application/x-msterminal", + ".tsv" => "text/tab-separated-values", + ".ttf" => "application/octet-stream", + ".twd" => "application/vnd.simtech-mindmapper", + ".txd" => "application/vnd.genomatix.tuxedo", + ".txf" => "application/vnd.mobius.txf", + ".txt" => "text/plain", + ".ufd" => "application/vnd.ufdl", + ".umj" => "application/vnd.umajin", + ".unityweb" => "application/vnd.unity", + ".uoml" => "application/vnd.uoml+xml", + ".uri" => "text/uri-list", + ".ustar" => "application/x-ustar", + ".utz" => "application/vnd.uiq.theme", + ".uu" => "text/x-uuencode", + ".vcd" => "application/x-cdlink", + ".vcf" => "text/x-vcard", + ".vcg" => "application/vnd.groove-vcard", + ".vcs" => "text/x-vcalendar", + ".vcx" => "application/vnd.vcx", + ".vis" => "application/vnd.visionary", + ".viv" => "video/vnd.vivo", + ".vrml" => "model/vrml", + ".vsd" => "application/vnd.visio", + ".vsf" => "application/vnd.vsf", + ".vtu" => "model/vnd.vtu", + ".vxml" => "application/voicexml+xml", + ".war" => "application/java-archive", + ".wav" => "audio/x-wav", + ".wax" => "audio/x-ms-wax", + ".wbmp" => "image/vnd.wap.wbmp", + ".wbs" => "application/vnd.criticaltools.wbs+xml", + ".wbxml" => "application/vnd.wap.wbxml", + ".webm" => "video/webm", + ".wm" => "video/x-ms-wm", + ".wma" => "audio/x-ms-wma", + ".wmd" => "application/x-ms-wmd", + ".wmf" => "application/x-msmetafile", + ".wml" => "text/vnd.wap.wml", + ".wmlc" => "application/vnd.wap.wmlc", + ".wmls" => "text/vnd.wap.wmlscript", + ".wmlsc" => "application/vnd.wap.wmlscriptc", + ".wmv" => "video/x-ms-wmv", + ".wmx" => "video/x-ms-wmx", + ".wmz" => "application/x-ms-wmz", + ".woff" => "application/font-woff", + ".woff2" => "application/font-woff2", + ".wpd" => "application/vnd.wordperfect", + ".wpl" => "application/vnd.ms-wpl", + ".wps" => "application/vnd.ms-works", + ".wqd" => "application/vnd.wqd", + ".wri" => "application/x-mswrite", + ".wrl" => "model/vrml", + ".wsdl" => "application/wsdl+xml", + ".wspolicy" => "application/wspolicy+xml", + ".wtb" => "application/vnd.webturbo", + ".wvx" => "video/x-ms-wvx", + ".x3d" => "application/vnd.hzn-3d-crossword", + ".xar" => "application/vnd.xara", + ".xbd" => "application/vnd.fujixerox.docuworks.binder", + ".xbm" => "image/x-xbitmap", + ".xdm" => "application/vnd.syncml.dm+xml", + ".xdp" => "application/vnd.adobe.xdp+xml", + ".xdw" => "application/vnd.fujixerox.docuworks", + ".xenc" => "application/xenc+xml", + ".xer" => "application/patch-ops-error+xml", + ".xfdf" => "application/vnd.adobe.xfdf", + ".xfdl" => "application/vnd.xfdl", + ".xhtml" => "application/xhtml+xml", + ".xif" => "image/vnd.xiff", + ".xls" => "application/vnd.ms-excel", + ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".xml" => "application/xml", + ".xo" => "application/vnd.olpc-sugar", + ".xop" => "application/xop+xml", + ".xpm" => "image/x-xpixmap", + ".xpr" => "application/vnd.is-xpr", + ".xps" => "application/vnd.ms-xpsdocument", + ".xpw" => "application/vnd.intercon.formnet", + ".xsl" => "application/xml", + ".xslt" => "application/xslt+xml", + ".xsm" => "application/vnd.syncml+xml", + ".xspf" => "application/xspf+xml", + ".xul" => "application/vnd.mozilla.xul+xml", + ".xwd" => "image/x-xwindowdump", + ".xyz" => "chemical/x-xyz", + ".yaml" => "text/yaml", + ".yml" => "text/yaml", + ".zaz" => "application/vnd.zzazz.deck+xml", + ".zip" => "application/zip", + ".zmm" => "application/vnd.handheld-entertainment+xml", + } + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mock.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mock.rb new file mode 100644 index 000000000..217ae0f77 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/mock.rb @@ -0,0 +1,198 @@ +require 'uri' +require 'stringio' +require 'rack' +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/patch/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/patch/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" => Rack::VERSION, + "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 patch(uri, opts={}) request("PATCH", uri, opts) end + def delete(uri, opts={}) request("DELETE", uri, opts) end + def head(uri, opts={}) request("HEAD", uri, opts) end + def options(uri, opts={}) request("OPTIONS", 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"] + status, headers, body = app.call(env) + MockResponse.new(status, headers, body, errors) + ensure + body.close if body.respond_to?(:close) + end + + # For historical reasons, we're pinning to RFC 2396. It's easier for users + # and we get support from ruby 1.8 to 2.2 using this method. + def self.parse_uri_rfc2396(uri) + @parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI + @parser.parse(uri) + end + + # Return the Rack environment used for a request to +uri+. + def self.env_for(uri="", opts={}) + uri = parse_uri_rfc2396(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 < Rack::Response + # Headers + attr_reader :original_headers + + # Errors + attr_accessor :errors + + def initialize(status, headers, body, errors=StringIO.new("")) + @original_headers = headers + @errors = errors.string if errors.respond_to?(:string) + @body_string = nil + + super(body, status, headers) + end + + def =~(other) + body =~ other + end + + def match(other) + body.match other + end + + def body + # FIXME: apparently users of MockResponse expect the return value of + # MockResponse#body to be a string. However, the real response object + # returns the body as a list. + # + # See spec_showstatus.rb: + # + # should "not replace existing messages" do + # ... + # res.body.should == "foo!" + # end + super.join + end + + def empty? + [201, 204, 205, 304].include? status + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart.rb new file mode 100644 index 000000000..7a44c4d43 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart.rb @@ -0,0 +1,34 @@ +module Rack + # A multipart form data parser, adapted from IOWA. + # + # Usually, Rack::Request#POST takes care of calling this. + module Multipart + autoload :UploadedFile, 'rack/multipart/uploaded_file' + autoload :Parser, 'rack/multipart/parser' + autoload :Generator, 'rack/multipart/generator' + + EOL = "\r\n" + MULTIPART_BOUNDARY = "AaB03x" + MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni + TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/ + CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i + DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/ + RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i + BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i + BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i + MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni + MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni + MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni + + class << self + def parse_multipart(env) + Parser.create(env).parse + end + + def build_multipart(params, first = true) + Generator.new(params, first).dump + end + end + + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/generator.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/generator.rb new file mode 100644 index 000000000..1c586b751 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/generator.rb @@ -0,0 +1,93 @@ +module Rack + module Multipart + class Generator + def initialize(params, first = true) + @params, @first = params, first + + if @first && !@params.is_a?(Hash) + raise ArgumentError, "value must be a Hash" + end + end + + def dump + return nil if @first && !multipart? + return flattened_params if !@first + + flattened_params.map do |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) + content_for_tempfile(f, file, name) + end + else + content_for_other(file, name) + end + end.join + "--#{MULTIPART_BOUNDARY}--\r" + end + + private + def multipart? + multipart = false + + query = lambda { |value| + case value + when Array + value.each(&query) + when Hash + value.values.each(&query) + when Rack::Multipart::UploadedFile + multipart = true + end + } + @params.values.each(&query) + + multipart + end + + def flattened_params + @flattened_params ||= begin + h = Hash.new + @params.each do |key, value| + k = @first ? key.to_s : "[#{key}]" + + case value + when Array + value.map { |v| + Multipart.build_multipart(v, false).each { |subkey, subvalue| + h["#{k}[]#{subkey}"] = subvalue + } + } + when Hash + Multipart.build_multipart(value, false).each { |subkey, subvalue| + h[k + subkey] = subvalue + } + else + h[k] = value + end + end + h + end + end + + def content_for_tempfile(io, file, name) +<<-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 +#{io.read}\r +EOF + end + + def content_for_other(file, name) +<<-EOF +--#{MULTIPART_BOUNDARY}\r +Content-Disposition: form-data; name="#{name}"\r +\r +#{file}\r +EOF + end + end + end +end \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/parser.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/parser.rb new file mode 100644 index 000000000..22d38e748 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/parser.rb @@ -0,0 +1,254 @@ +require 'rack/utils' + +module Rack + module Multipart + class MultipartPartLimitError < Errno::EMFILE; end + + class Parser + BUFSIZE = 16384 + + DUMMY = Struct.new(:parse).new + + def self.create(env) + return DUMMY unless env['CONTENT_TYPE'] =~ MULTIPART + + io = env['rack.input'] + io.rewind + + content_length = env['CONTENT_LENGTH'] + content_length = content_length.to_i if content_length + + tempfile = env['rack.multipart.tempfile_factory'] || + lambda { |filename, content_type| Tempfile.new(["RackMultipart", ::File.extname(filename)]) } + bufsize = env['rack.multipart.buffer_size'] || BUFSIZE + + new($1, io, content_length, env, tempfile, bufsize) + end + + def initialize(boundary, io, content_length, env, tempfile, bufsize) + @buf = "" + + if @buf.respond_to? :force_encoding + @buf.force_encoding Encoding::ASCII_8BIT + end + + @params = Utils::KeySpaceConstrainedParams.new + @boundary = "--#{boundary}" + @io = io + @content_length = content_length + @boundary_size = Utils.bytesize(@boundary) + EOL.size + @env = env + @tempfile = tempfile + @bufsize = bufsize + + if @content_length + @content_length -= @boundary_size + end + + @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n + @full_boundary = @boundary + EOL + end + + def parse + fast_forward_to_first_boundary + + opened_files = 0 + loop do + + head, filename, content_type, name, body = + get_current_head_and_filename_and_content_type_and_name_and_body + + if Utils.multipart_part_limit > 0 + opened_files += 1 if filename + raise MultipartPartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit + 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 + + get_data(filename, body, content_type, name, head) do |data| + tag_multipart_encoding(filename, content_type, name, data) + + Utils.normalize_params(@params, name, data) + end + + # 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 + end + + @io.rewind + + @params.to_params_hash + end + + private + def full_boundary; @full_boundary; end + + def rx; @rx; end + + def fast_forward_to_first_boundary + loop do + content = @io.read(@bufsize) + raise EOFError, "bad content body" unless content + @buf << content + + while @buf.gsub!(/\A([^\n]*\n)/, '') + read_buffer = $1 + return if read_buffer == full_boundary + end + + raise EOFError, "bad content body" if Utils.bytesize(@buf) >= @bufsize + end + end + + def get_current_head_and_filename_and_content_type_and_name_and_body + head = nil + body = '' + + if body.respond_to? :force_encoding + body.force_encoding Encoding::ASCII_8BIT + end + + 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 + + content_type = head[MULTIPART_CONTENT_TYPE, 1] + name = head[MULTIPART_CONTENT_DISPOSITION, 1] || head[MULTIPART_CONTENT_ID, 1] + + filename = get_filename(head) + + if name.nil? || name.empty? && filename + name = filename + end + + if filename + (@env['rack.tempfiles'] ||= []) << body = @tempfile.call(filename, content_type) + 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 + + content = @io.read(@content_length && @bufsize >= @content_length ? @content_length : @bufsize) + raise EOFError, "bad content body" if content.nil? || content.empty? + + @buf << content + @content_length -= content.size if @content_length + end + + [head, filename, content_type, name, body] + end + + def get_filename(head) + filename = nil + case head + when RFC2183 + filename = Hash[head.scan(DISPPARM)]['filename'] + filename = $1 if filename and filename =~ /^"(.*)"$/ + when BROKEN_QUOTED, BROKEN_UNQUOTED + filename = $1 + end + + return unless filename + + if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ } + filename = Utils.unescape(filename) + end + + scrub_filename filename + + if filename !~ /\\[^\\"]/ + filename = filename.gsub(/\\(.)/, '\1') + end + filename + end + + if "<3".respond_to? :valid_encoding? + def scrub_filename(filename) + unless filename.valid_encoding? + # FIXME: this force_encoding is for Ruby 2.0 and 1.9 support. + # We can remove it after they are dropped + filename.force_encoding(Encoding::ASCII_8BIT) + filename.encode!(:invalid => :replace, :undef => :replace) + end + end + + CHARSET = "charset" + TEXT_PLAIN = "text/plain" + + def tag_multipart_encoding(filename, content_type, name, body) + name.force_encoding Encoding::UTF_8 + + return if filename + + encoding = Encoding::UTF_8 + + if content_type + list = content_type.split(';') + type_subtype = list.first + type_subtype.strip! + if TEXT_PLAIN == type_subtype + rest = list.drop 1 + rest.each do |param| + k,v = param.split('=', 2) + k.strip! + v.strip! + encoding = Encoding.find v if k == CHARSET + end + end + end + + name.force_encoding encoding + body.force_encoding encoding + end + else + def scrub_filename(filename) + end + def tag_multipart_encoding(filename, content_type, name, body) + end + end + + def get_data(filename, body, content_type, name, head) + data = body + if filename == "" + # filename is blank which means no file has been selected + return + elsif filename + body.rewind if body.respond_to?(: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 = filename.split(/[\/\\]/).last + + data = {:filename => filename, :type => content_type, + :name => name, :tempfile => body, :head => head} + elsif !filename && content_type && body.is_a?(IO) + body.rewind + + # Generic multipart cases, not coming from a form + data = {:type => content_type, + :name => name, :tempfile => body, :head => head} + end + + yield data + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/uploaded_file.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/uploaded_file.rb new file mode 100644 index 000000000..1b56ad75c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/multipart/uploaded_file.rb @@ -0,0 +1,34 @@ +module Rack + 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, ::File.extname(path)]) + @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 respond_to?(*args) + super or @tempfile.respond_to?(*args) + end + + def method_missing(method_name, *args, &block) #:nodoc: + @tempfile.__send__(method_name, *args, &block) + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/nulllogger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/nulllogger.rb new file mode 100644 index 000000000..2d5a2c976 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/nulllogger.rb @@ -0,0 +1,37 @@ +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 + def unknown(progname = nil, &block); end + def info? ; end + def debug? ; end + def warn? ; end + def error? ; end + def fatal? ; end + def level ; end + def progname ; end + def datetime_format ; end + def formatter ; end + def sev_threshold ; end + def level=(level); end + def progname=(progname); end + def datetime_format=(datetime_format); end + def formatter=(formatter); end + def sev_threshold=(sev_threshold); end + def close ; end + def add(severity, message = nil, progname = nil, &block); end + def <<(msg); end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/recursive.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/recursive.rb new file mode 100644 index 000000000..17d17dfdb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/recursive.rb @@ -0,0 +1,62 @@ +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) + dup._call(env) + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/reloader.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/reloader.rb new file mode 100644 index 000000000..5f643592e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/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, Errno::ESRCH + @cache.delete(file) and false + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/request.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/request.rb new file mode 100644 index 000000000..ac95b1ca6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/request.rb @@ -0,0 +1,398 @@ +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"] + + class Request + # The environment of the request. + attr_reader :env + + def initialize(env) + @env = env + end + + def body; @env["rack.input"] end + def script_name; @env[SCRIPT_NAME].to_s end + def path_info; @env[PATH_INFO].to_s 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 + content_type = @env['CONTENT_TYPE'] + content_type.nil? || content_type.empty? ? nil : 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? + Hash[*content_type.split(/\s*[;,]\s*/)[1..-1]. + collect { |s| s.split('=', 2) }. + map { |k,v| [k.downcase, strip_doublequotes(v)] }.flatten] + 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 scheme + if @env['HTTPS'] == 'on' + 'https' + elsif @env['HTTP_X_FORWARDED_SSL'] == 'on' + 'https' + elsif @env['HTTP_X_FORWARDED_SCHEME'] + @env['HTTP_X_FORWARDED_SCHEME'] + elsif @env['HTTP_X_FORWARDED_PROTO'] + @env['HTTP_X_FORWARDED_PROTO'].split(',')[0] + else + @env["rack.url_scheme"] + end + end + + def ssl? + scheme == 'https' + 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 port + if port = host_with_port.split(/:/)[1] + port.to_i + elsif port = @env['HTTP_X_FORWARDED_PORT'] + port.to_i + elsif @env.has_key?("HTTP_X_FORWARDED_HOST") + DEFAULT_PORTS[scheme] + elsif @env.has_key?("HTTP_X_FORWARDED_PROTO") + DEFAULT_PORTS[@env['HTTP_X_FORWARDED_PROTO'].split(',')[0]] + else + @env["SERVER_PORT"].to_i + end + end + + def host + # Remove port number. + host_with_port.to_s.sub(/:\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 + + + # Checks the HTTP request method (or verb) to see if it was of type DELETE + def delete?; request_method == "DELETE" end + + # Checks the HTTP request method (or verb) to see if it was of type GET + def get?; request_method == GET end + + # Checks the HTTP request method (or verb) to see if it was of type HEAD + def head?; request_method == HEAD end + + # Checks the HTTP request method (or verb) to see if it was of type OPTIONS + def options?; request_method == "OPTIONS" end + + # Checks the HTTP request method (or verb) to see if it was of type LINK + def link?; request_method == "LINK" end + + # Checks the HTTP request method (or verb) to see if it was of type PATCH + def patch?; request_method == "PATCH" end + + # Checks the HTTP request method (or verb) to see if it was of type POST + def post?; request_method == "POST" end + + # Checks the HTTP request method (or verb) to see if it was of type PUT + def put?; request_method == "PUT" end + + # Checks the HTTP request method (or verb) to see if it was of type TRACE + def trace?; request_method == "TRACE" end + + # Checks the HTTP request method (or verb) to see if it was of type UNLINK + def unlink?; request_method == "UNLINK" 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' + ] + + # Default ports depending on scheme. Used to decide whether or not + # to include the port in a generated URI. + DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } + + # 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 received in the query string. + def GET + if @env["rack.request.query_string"] == query_string + @env["rack.request.query_hash"] + else + p = parse_query({ :query => query_string, :separator => '&;' }) + @env["rack.request.query_string"] = query_string + @env["rack.request.query_hash"] = p + end + end + + # Returns the data received 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"].equal? @env["rack.input"] + @env["rack.request.form_hash"] + elsif form_data? || parseable_data? + 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/, '') # performance replacement: + form_vars.slice!(-1) if form_vars[-1] == ?\0 + + @env["rack.request.form_vars"] = form_vars + @env["rack.request.form_hash"] = parse_query({ :query => form_vars, :separator => '&' }) + + @env["rack.input"].rewind + end + @env["rack.request.form_input"] = @env["rack.input"] + @env["rack.request.form_hash"] + else + {} + end + end + + # The union of GET and POST data. + # + # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. + def params + @params ||= self.GET.merge(self.POST) + rescue EOFError + self.GET.dup + end + + # Destructively update a parameter, whether it's in GET and/or POST. Returns nil. + # + # The parameter is updated wherever it was previous defined, so GET, POST, or both. If it wasn't previously defined, it's inserted into GET. + # + # env['rack.input'] is not touched. + def update_param(k, v) + found = false + if self.GET.has_key?(k) + found = true + self.GET[k] = v + end + if self.POST.has_key?(k) + found = true + self.POST[k] = v + end + unless found + self.GET[k] = v + end + @params = nil + nil + end + + # Destructively delete a parameter, whether it's in GET or POST. Returns the value of the deleted parameter. + # + # If the parameter is in both GET and POST, the POST value takes precedence since that's how #params works. + # + # env['rack.input'] is not touched. + def delete_param(k) + v = [ self.POST.delete(k), self.GET.delete(k) ].compact.first + @params = nil + v + end + + # shortcut for request.params[key] + def [](key) + params[key.to_s] + end + + # shortcut for request.params[key] = value + # + # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params. + 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 + def referer + @env['HTTP_REFERER'] + end + alias referrer referer + + def user_agent + @env['HTTP_USER_AGENT'] + end + + def cookies + hash = @env["rack.request.cookie_hash"] ||= {} + string = @env["HTTP_COOKIE"] + + return hash if string == @env["rack.request.cookie_string"] + hash.clear + + # 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. + cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s } + cookies.each { |k,v| hash[k] = Array === v ? v.first : v } + @env["rack.request.cookie_string"] = string + hash + end + + def xhr? + @env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" + end + + def base_url + url = "#{scheme}://#{host}" + url << ":#{port}" if port != DEFAULT_PORTS[scheme] + url + end + + # Tries to return a remake of the original request URL as a string. + def url + base_url + fullpath + end + + def path + script_name + path_info + end + + def fullpath + query_string.empty? ? path : "#{path}?#{query_string}" + end + + def accept_encoding + parse_http_accept_header(@env["HTTP_ACCEPT_ENCODING"]) + end + + def accept_language + parse_http_accept_header(@env["HTTP_ACCEPT_LANGUAGE"]) + end + + def trusted_proxy?(ip) + ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i + end + + def ip + remote_addrs = split_ip_addresses(@env['REMOTE_ADDR']) + remote_addrs = reject_trusted_ip_addresses(remote_addrs) + + return remote_addrs.first if remote_addrs.any? + + forwarded_ips = split_ip_addresses(@env['HTTP_X_FORWARDED_FOR']) + + return reject_trusted_ip_addresses(forwarded_ips).last || @env["REMOTE_ADDR"] + end + + protected + def split_ip_addresses(ip_addresses) + ip_addresses ? ip_addresses.strip.split(/[,\s]+/) : [] + end + + def reject_trusted_ip_addresses(ip_addresses) + ip_addresses.reject { |ip| trusted_proxy?(ip) } + end + + def parse_query(qs) + d = '&' + qs, d = qs[:query], qs[:separator] if Hash === qs + Utils.parse_nested_query(qs, d) + end + + def parse_multipart(env) + Rack::Multipart.parse_multipart(env) + end + + def parse_http_accept_header(header) + header.to_s.split(/\s*,\s*/).map do |part| + attribute, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if parameters and /\Aq=([\d.]+)/ =~ parameters + quality = $1.to_f + end + [attribute, quality] + end + end + + private + def strip_doublequotes(s) + if s[0] == ?" && s[-1] == ?" + s[1..-2] + else + s + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/response.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/response.rb new file mode 100644 index 000000000..7f0c6b71b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/response.rb @@ -0,0 +1,162 @@ +require 'rack/request' +require 'rack/utils' +require 'rack/body_proxy' +require 'time' + +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 synchronous with the Rack response. + # + # Your application's +call+ should end returning Response#finish. + + class Response + attr_accessor :length + + CHUNKED = 'chunked'.freeze + TRANSFER_ENCODING = 'Transfer-Encoding'.freeze + def initialize(body=[], status=200, header={}) + @status = status.to_i + @header = Utils::HeaderHash.new.merge(header) + + @chunked = CHUNKED == @header[TRANSFER_ENCODING] + @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, 205, 304].include?(status.to_i) + header.delete CONTENT_TYPE + header.delete CONTENT_LENGTH + close + [status.to_i, header, []] + else + [status.to_i, header, BodyProxy.new(self){}] + end + end + alias to_a finish # For *response + alias to_ary finish # For implicit-splat on Ruby 1.9.2 + + 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) unless @chunked + @writer.call s + + header[CONTENT_LENGTH] = @length.to_s unless @chunked + 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 created?; status == 201; end + def accepted?; status == 202; end + def bad_request?; status == 400; end + def unauthorized?; status == 401; end + def forbidden?; status == 403; end + def not_found?; status == 404; end + def method_not_allowed?; status == 405; end + def i_m_a_teapot?; status == 418; end + def unprocessable?; status == 422; end + + def redirect?; [301, 302, 303, 307].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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/rewindable_input.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/rewindable_input.rb new file mode 100644 index 000000000..15ecc5869 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/rewindable_input.rb @@ -0,0 +1,104 @@ +# -*- encoding: binary -*- +require 'tempfile' +require 'rack/utils' + +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? + # Use ::File.unlink as 1.9.1 Tempfile has a bug where unlink closes the file! + ::File.unlink @rewindable_io.path + raise 'Unlink failed. IO closed.' if @rewindable_io.closed? + @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 == Rack::Utils.bytesize(buffer) + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/runtime.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/runtime.rb new file mode 100644 index 000000000..e86add31e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/runtime.rb @@ -0,0 +1,40 @@ +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 + + FORMAT_STRING = "%0.6f" + def call(env) + start_time = clock_time + status, headers, body = @app.call(env) + request_time = clock_time - start_time + + if !headers.has_key?(@header_name) + headers[@header_name] = FORMAT_STRING % request_time + end + + [status, headers, body] + end + + private + + if defined?(Process::CLOCK_MONOTONIC) + def clock_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + else + def clock_time + Time.now.to_f + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/sendfile.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/sendfile.rb new file mode 100644 index 000000000..4a9b428b8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/sendfile.rb @@ -0,0 +1,160 @@ +require 'rack/file' +require 'rack/body_proxy' + +module Rack + + # = 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 server's 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 + # hierarchy. + # + # 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/$1; + # } + # + # location / { + # proxy_redirect off; + # + # 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 /var/www/=/files/; + # + # 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 location on the file system, + # followed by an equals sign (=), followed name of the private URL pattern + # 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: + # + # https://tn123.org/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 + # + # === Mapping parameter + # + # The third parameter allows for an overriding extension of the + # X-Accel-Mapping header. Mappings should be provided in tuples of internal to + # external. The internal values may contain regular expression syntax, they + # will be matched with case indifference. + + class Sendfile + F = ::File + + def initialize(app, variation=nil, mappings=[]) + @app = app + @variation = variation + @mappings = mappings.map do |internal, external| + [/^#{internal}/i, external] + end + 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[CONTENT_LENGTH] = '0' + headers[type] = url + obody = body + body = Rack::BodyProxy.new([]) do + obody.close if obody.respond_to?(:close) + end + else + env['rack.errors'].puts "X-Accel-Mapping header missing" + end + when 'X-Sendfile', 'X-Lighttpd-Send-File' + path = F.expand_path(body.to_path) + headers[CONTENT_LENGTH] = '0' + headers[type] = path + obody = body + body = Rack::BodyProxy.new([]) do + obody.close if obody.respond_to?(:close) + end + when '', nil + else + env['rack.errors'].puts "Unknown x-sendfile variation: '#{type}'.\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, path) + if mapping = @mappings.find { |internal,_| internal =~ path } + path.sub(*mapping) + elsif mapping = env['HTTP_X_ACCEL_MAPPING'] + internal, external = mapping.split('=', 2).map{ |p| p.strip } + path.sub(/^#{internal}/i, external) + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/server.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/server.rb new file mode 100644 index 000000000..2253b314a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/server.rb @@ -0,0 +1,387 @@ +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("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line| + options[:builder] = line + } + + 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("-q", "--quiet", "turn off logging") { + options[:quiet] = true + } + + opts.on("-I", "--include PATH", + "specify $LOAD_PATH (may be used more than once)") { |path| + (options[:include] ||= []).concat(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 (thin/puma/webrick/mongrel)") { |s| + options[:server] = s + } + + opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host| + options[:Host] = host + } + + opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port| + options[:Port] = port + } + + opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name| + name, value = name.split('=', 2) + value = true if value.nil? + options[name.to_sym] = value + } + + 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") { |f| + options[:pid] = ::File.expand_path(f) + } + + opts.separator "" + opts.separator "Common options:" + + opts.on_tail("-h", "-?", "--help", "Show this message") do + puts opts + puts handler_opts(options) + + exit + end + + opts.on_tail("--version", "Show version") do + puts "Rack #{Rack.version} (Release: #{Rack.release})" + exit + end + end + + begin + opt_parser.parse! args + rescue OptionParser::InvalidOption => e + warn e.message + abort opt_parser.to_s + end + + options[:config] = args.last if args.last + options + end + + def handler_opts(options) + begin + info = [] + server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options) + if server && server.respond_to?(:valid_options) + info << "" + info << "Server-specific options for #{server.name}:" + + has_options = false + server.valid_options.each do |name, description| + next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own. + info << " -O %-21s %s" % [name, description] + has_options = true + end + return "" if !has_options + end + info.join("\n") + rescue NameError + return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options" + end + end + end + + # Start a new rack server (like running rackup). This will parse ARGV and + # provide standard ARGV rackup options, defaulting to load 'config.ru'. + # + # Providing an options hash will prevent ARGV parsing and will not include + # any default options. + # + # This method can be used to very easily launch a CGI application, for + # example: + # + # Rack::Server.start( + # :app => lambda do |e| + # [200, {'Content-Type' => 'text/html'}, ['hello world']] + # end, + # :server => 'cgi' + # ) + # + # Further options available here are documented on Rack::Server#initialize + def self.start(options = nil) + new(options).start + end + + attr_writer :options + + # Options may include: + # * :app + # a rack application to run (overrides :config) + # * :config + # a rackup configuration file path to load (.ru) + # * :environment + # this selects the middleware that will be wrapped around + # your application. Default options available are: + # - development: CommonLogger, ShowExceptions, and Lint + # - deployment: CommonLogger + # - none: no extra middleware + # note: when the server is a cgi server, CommonLogger is not included. + # * :server + # choose a specific Rack::Handler, e.g. cgi, fcgi, webrick + # * :daemonize + # if true, the server will daemonize itself (fork, detach, etc) + # * :pid + # path to write a pid file after daemonize + # * :Host + # the host address to bind to (used by supporting Rack::Handler) + # * :Port + # the port to bind to (used by supporting Rack::Handler) + # * :AccessLog + # webrick access log options (or supporting Rack::Handler) + # * :debug + # turn on debug output ($DEBUG = true) + # * :warn + # turn on warnings ($-w = true) + # * :include + # add given paths to $LOAD_PATH + # * :require + # require the given libraries + def initialize(options = nil) + @options = options + @app = options[:app] if options && options[:app] + end + + def options + @options ||= parse_options(ARGV) + end + + def default_options + environment = ENV['RACK_ENV'] || 'development' + default_host = environment == 'development' ? 'localhost' : '0.0.0.0' + + { + :environment => environment, + :pid => nil, + :Port => 9292, + :Host => default_host, + :AccessLog => [], + :config => "config.ru" + } + end + + def app + @app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config + end + + class << self + def logging_middleware + lambda { |server| + server.server.name =~ /CGI/ || server.options[:quiet] ? nil : [Rack::CommonLogger, $stderr] + } + end + + def default_middleware_by_environment + m = Hash.new {|h,k| h[k] = []} + m["deployment"] = [ + [Rack::ContentLength], + [Rack::Chunked], + logging_middleware, + [Rack::TempfileReaper] + ] + m["development"] = [ + [Rack::ContentLength], + [Rack::Chunked], + logging_middleware, + [Rack::ShowExceptions], + [Rack::Lint], + [Rack::TempfileReaper] + ] + + m + end + + def middleware + default_middleware_by_environment + end + end + + def middleware + self.class.middleware + end + + def start &blk + if options[:warn] + $-w = true + end + + if includes = options[:include] + $LOAD_PATH.unshift(*includes) + end + + if library = options[:require] + require library + end + + if options[:debug] + $DEBUG = true + require 'pp' + p options[:server] + pp wrapped_app + pp app + end + + check_pid! if options[:pid] + + # Touch the wrapped app, so that the config.ru is loaded before + # daemonization (i.e. before chdir, etc). + wrapped_app + + daemonize_app if options[:daemonize] + + write_pid if options[:pid] + + trap(:INT) do + if server.respond_to?(:shutdown) + server.shutdown + else + exit + end + end + + server.run wrapped_app, options, &blk + end + + def server + @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options) + end + + private + def build_app_and_options_from_config + 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 + + def build_app_from_string + Rack::Builder.new_from_string(self.options[:builder]) + end + + def parse_options(args) + options = default_options + + # Don't evaluate CGI ISINDEX parameters. + # http://www.meb.uni-bonn.de/docs/cgi/cl.html + args.clear if ENV.include?(REQUEST_METHOD) + + options.merge! opt_parser.parse!(args) + options[:config] = ::File.expand_path(options[:config]) + ENV["RACK_ENV"] = options[:environment] + 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, *args = middleware + app = klass.new(app, *args) + 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 "/" + 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], ::File::CREAT | ::File::EXCL | ::File::WRONLY ){ |f| f.write("#{Process.pid}") } + at_exit { ::File.delete(options[:pid]) if ::File.exist?(options[:pid]) } + rescue Errno::EEXIST + check_pid! + retry + end + + def check_pid! + case pidfile_process_status + when :running, :not_owned + $stderr.puts "A server is already running. Check #{options[:pid]}." + exit(1) + when :dead + ::File.delete(options[:pid]) + end + end + + def pidfile_process_status + return :exited unless ::File.exist?(options[:pid]) + + pid = ::File.read(options[:pid]).to_i + return :dead if pid == 0 + + Process.kill(0, pid) + :running + rescue Errno::ESRCH + :dead + rescue Errno::EPERM + :not_owned + end + + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/abstract/id.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/abstract/id.rb new file mode 100644 index 000000000..62bdb04b2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/abstract/id.rb @@ -0,0 +1,399 @@ +# AUTHOR: blink ; blink#ruby-lang@irc.freenode.net +# bugrep: Andreas Zehnder + +require 'time' +require 'rack/request' +require 'rack/response' +begin + require 'securerandom' +rescue LoadError + # We just won't get securerandom +end + +module Rack + + module Session + + module Abstract + ENV_SESSION_KEY = 'rack.session'.freeze + ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze + + # SessionHash is responsible to lazily load the session from store. + + class SessionHash + include Enumerable + attr_writer :id + + def self.find(env) + env[ENV_SESSION_KEY] + end + + def self.set(env, session) + env[ENV_SESSION_KEY] = session + end + + def self.set_options(env, options) + env[ENV_SESSION_OPTIONS_KEY] = options.dup + end + + def initialize(store, env) + @store = store + @env = env + @loaded = false + end + + def id + return @id if @loaded or instance_variable_defined?(:@id) + @id = @store.send(:extract_session_id, @env) + end + + def options + @env[ENV_SESSION_OPTIONS_KEY] + end + + def each(&block) + load_for_read! + @data.each(&block) + end + + def [](key) + load_for_read! + @data[key.to_s] + end + alias :fetch :[] + + def has_key?(key) + load_for_read! + @data.has_key?(key.to_s) + end + alias :key? :has_key? + alias :include? :has_key? + + def []=(key, value) + load_for_write! + @data[key.to_s] = value + end + alias :store :[]= + + def clear + load_for_write! + @data.clear + end + + def destroy + clear + @id = @store.send(:destroy_session, @env, id, options) + end + + def to_hash + load_for_read! + @data.dup + end + + def update(hash) + load_for_write! + @data.update(stringify_keys(hash)) + end + alias :merge! :update + + def replace(hash) + load_for_write! + @data.replace(stringify_keys(hash)) + end + + def delete(key) + load_for_write! + @data.delete(key.to_s) + end + + def inspect + if loaded? + @data.inspect + else + "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>" + end + end + + def exists? + return @exists if instance_variable_defined?(:@exists) + @data = {} + @exists = @store.send(:session_exists?, @env) + end + + def loaded? + @loaded + end + + def empty? + load_for_read! + @data.empty? + end + + def keys + @data.keys + end + + def values + @data.values + end + + private + + def load_for_read! + load! if !loaded? && exists? + end + + def load_for_write! + load! unless loaded? + end + + def load! + @id, session = @store.send(:load_session, @env) + @data = stringify_keys(session) + @loaded = true + end + + def stringify_keys(other) + hash = {} + other.each do |key, value| + hash[key.to_s] = value + end + hash + end + end + + # 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 + # * :skip will not a set a cookie in the response nor update the session state + # * :defer will not set a cookie in the response but still update the session + # state if it is used with a backend + # * :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. + # + # Not included by default; you must require 'rack/session/abstract/id' + # to use. + + class ID + DEFAULT_OPTIONS = { + :key => 'rack.session', + :path => '/', + :domain => nil, + :expire_after => nil, + :secure => false, + :httponly => true, + :defer => false, + :renew => false, + :sidbits => 128, + :cookie_only => true, + :secure_random => (::SecureRandom rescue false) + } + + attr_reader :key, :default_options + + def initialize(app, options={}) + @app = app + @default_options = self.class::DEFAULT_OPTIONS.merge(options) + @key = @default_options.delete(:key) + @cookie_only = @default_options.delete(:cookie_only) + initialize_sid + end + + def call(env) + context(env) + end + + def context(env, app=@app) + prepare_session(env) + status, headers, body = app.call(env) + commit_session(env, status, headers, body) + end + + private + + def initialize_sid + @sidbits = @default_options[:sidbits] + @sid_secure = @default_options[:secure_random] + @sid_length = @sidbits / 4 + end + + # 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(secure = @sid_secure) + if secure + secure.hex(@sid_length) + else + "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1) + end + rescue NotImplementedError + generate_sid(false) + end + + # Sets the lazy session at 'rack.session' and places options and session + # metadata into 'rack.session.options'. + + def prepare_session(env) + session_was = env[ENV_SESSION_KEY] + env[ENV_SESSION_KEY] = session_class.new(self, env) + env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup + env[ENV_SESSION_KEY].merge! session_was if session_was + end + + # Extracts the session id from provided cookies and passes it and the + # environment to #get_session. + + def load_session(env) + sid = current_session_id(env) + sid, session = get_session(env, sid) + [sid, session || {}] + end + + # Extract session id from request object. + + def extract_session_id(env) + request = Rack::Request.new(env) + sid = request.cookies[@key] + sid ||= request.params[@key] unless @cookie_only + sid + end + + # Returns the current session id from the SessionHash. + + def current_session_id(env) + env[ENV_SESSION_KEY].id + end + + # Check if the session exists or not. + + def session_exists?(env) + value = current_session_id(env) + value && !value.empty? + end + + # Session should be committed if it was loaded, any of specific options like :renew, :drop + # or :expire_after was given and the security permissions match. Skips if skip is given. + + def commit_session?(env, session, options) + if options[:skip] + false + else + has_session = loaded_session?(session) || forced_session_update?(session, options) + has_session && security_matches?(env, options) + end + end + + def loaded_session?(session) + !session.is_a?(session_class) || session.loaded? + end + + def forced_session_update?(session, options) + force_options?(options) && session && !session.empty? + end + + def force_options?(options) + options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any? + end + + def security_matches?(env, options) + return true unless options[:secure] + request = Rack::Request.new(env) + request.ssl? + 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[ENV_SESSION_KEY] + options = session.options + + if options[:drop] || options[:renew] + session_id = destroy_session(env, session.id || generate_sid, options) + return [status, headers, body] unless session_id + end + + return [status, headers, body] unless commit_session?(env, session, options) + + session.send(:load!) unless loaded_session?(session) + session_id ||= session.id + session_data = session.to_hash.delete_if { |k,v| v.nil? } + + if not data = set_session(env, session_id, session_data, 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("Deferring cookie for #{session_id}") if $VERBOSE + else + cookie = Hash.new + cookie[:value] = data + cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after] + cookie[:expires] = Time.now + options[:max_age] if options[:max_age] + set_cookie(env, headers, cookie.merge!(options)) + end + + [status, headers, body] + end + + # Sets the cookie back to the client with session id. We skip the cookie + # setting if the value didn't change (sid is the same) or expires was given. + + def set_cookie(env, headers, cookie) + request = Rack::Request.new(env) + if request.cookies[@key] != cookie[:value] || cookie[:expires] + Utils.set_cookie_header!(headers, @key, cookie) + end + end + + # Allow subclasses to prepare_session for different Session classes + + def session_class + SessionHash + end + + # All thread safety and session retrieval procedures 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 procedures should occur here. + # Must return the session id if the session was saved successfully, or + # false if the session could not be saved. + + def set_session(env, sid, session, options) + raise '#set_session not implemented.' + end + + # All thread safety and session destroy procedures should occur here. + # Should return a new session id or nil if options[:drop] + + def destroy_session(env, sid, options) + raise '#destroy_session not implemented' + end + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/cookie.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/cookie.rb new file mode 100644 index 000000000..9bea586cd --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/cookie.rb @@ -0,0 +1,188 @@ +require 'openssl' +require 'zlib' +require 'rack/request' +require 'rack/response' +require 'rack/session/abstract/id' + +module Rack + + module Session + + # Rack::Session::Cookie provides simple cookie based session management. + # By default, the session is a Ruby Hash stored as base64 encoded marshalled + # data set to :key (default: rack.session). The object that encodes the + # session data is configurable and must respond to +encode+ and +decode+. + # Both methods must take a string and return a string. + # + # When the secret key is set, cookie data is checked for data integrity. + # The old secret key is also accepted and allows graceful secret rotation. + # + # Example: + # + # use Rack::Session::Cookie, :key => 'rack.session', + # :domain => 'foo.com', + # :path => '/', + # :expire_after => 2592000, + # :secret => 'change_me', + # :old_secret => 'also_change_me' + # + # All parameters are optional. + # + # Example of a cookie with no encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Rack::Session::Cookie::Identity.new + # }) + # + # Example of a cookie with custom encoding: + # + # Rack::Session::Cookie.new(application, { + # :coder => Class.new { + # def encode(str); str.reverse; end + # def decode(str); str.reverse; end + # }.new + # }) + # + + class Cookie < Abstract::ID + # Encode session cookies as Base64 + class Base64 + def encode(str) + [str].pack('m') + end + + def decode(str) + str.unpack('m').first + end + + # Encode session cookies as Marshaled Base64 data + class Marshal < Base64 + def encode(str) + super(::Marshal.dump(str)) + end + + def decode(str) + return unless str + ::Marshal.load(super(str)) rescue nil + end + end + + # N.B. Unlike other encoding methods, the contained objects must be a + # valid JSON composite type, either a Hash or an Array. + class JSON < Base64 + def encode(obj) + super(::Rack::Utils::OkJson.encode(obj)) + end + + def decode(str) + return unless str + ::Rack::Utils::OkJson.decode(super(str)) rescue nil + end + end + + class ZipJSON < Base64 + def encode(obj) + super(Zlib::Deflate.deflate(::Rack::Utils::OkJson.encode(obj))) + end + + def decode(str) + return unless str + ::Rack::Utils::OkJson.decode(Zlib::Inflate.inflate(super(str))) + rescue + nil + end + end + end + + # Use no encoding for session cookies + class Identity + def encode(str); str; end + def decode(str); str; end + end + + attr_reader :coder + + def initialize(app, options={}) + @secrets = options.values_at(:secret, :old_secret).compact + warn <<-MSG unless @secrets.size >= 1 + SECURITY WARNING: No secret option provided to Rack::Session::Cookie. + This poses a security threat. It is strongly recommended that you + provide a secret to prevent exploits that may be possible from crafted + cookies. This will not be supported in future versions of Rack, and + future versions will even invalidate your existing user cookies. + + Called from: #{caller[0]}. + MSG + @coder = options[:coder] ||= Base64::Marshal.new + super(app, options.merge!(:cookie_only => true)) + end + + private + + def get_session(env, sid) + data = unpacked_cookie_data(env) + data = persistent_session_id!(data) + [data["session_id"], data] + end + + def extract_session_id(env) + unpacked_cookie_data(env)["session_id"] + end + + def unpacked_cookie_data(env) + env["rack.session.unpacked_cookie_data"] ||= begin + request = Rack::Request.new(env) + session_data = request.cookies[@key] + + if @secrets.size > 0 && session_data + digest, session_data = session_data.reverse.split("--", 2) + digest.reverse! if digest + session_data.reverse! if session_data + session_data = nil unless digest_match?(session_data, digest) + end + + coder.decode(session_data) || {} + end + end + + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + + def set_session(env, session_id, session, options) + session = session.merge("session_id" => session_id) + session_data = coder.encode(session) + + if @secrets.first + session_data << "--#{generate_hmac(session_data, @secrets.first)}" + end + + if session_data.size > (4096 - @key.size) + env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.") + nil + else + session_data + end + end + + def destroy_session(env, session_id, options) + # Nothing to do here, data is in the client + generate_sid unless options[:drop] + end + + def digest_match?(data, digest) + return unless data && digest + @secrets.any? do |secret| + Rack::Utils.secure_compare(digest, generate_hmac(data, secret)) + end + end + + def generate_hmac(data, secret) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data) + end + + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/memcache.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/memcache.rb new file mode 100644 index 000000000..c0e1f3ec9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/memcache.rb @@ -0,0 +1,93 @@ +# 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 = options[:cache] || 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, sid) + with_lock(env) do + unless sid and session = @pool.get(sid) + sid, session = generate_sid, {} + unless /^STORED/ =~ @pool.add(sid, session) + raise "Session collision on '#{sid.inspect}'" + end + end + [sid, session] + end + end + + def set_session(env, session_id, new_session, options) + expiry = options[:expire_after] + expiry = expiry.nil? ? 0 : expiry + 1 + + with_lock(env) do + @pool.set session_id, new_session, expiry + session_id + end + end + + def destroy_session(env, session_id, options) + with_lock(env) do + @pool.delete(session_id) + generate_sid unless options[:drop] + end + end + + def with_lock(env) + @mutex.lock if env['rack.multithread'] + yield + rescue MemCache::MemCacheError, Errno::ECONNREFUSED + if $VERBOSE + warn "#{self} is unable to find memcached server." + warn $!.inspect + end + raise + ensure + @mutex.unlock if @mutex.locked? + end + + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/pool.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/pool.rb new file mode 100644 index 000000000..fcb34ec45 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/session/pool.rb @@ -0,0 +1,76 @@ +# 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) + with_lock(env) do + unless sid and session = @pool[sid] + sid, session = generate_sid, {} + @pool.store sid, session + end + [sid, session] + end + end + + def set_session(env, session_id, new_session, options) + with_lock(env) do + @pool.store session_id, new_session + session_id + end + end + + def destroy_session(env, session_id, options) + with_lock(env) do + @pool.delete(session_id) + generate_sid unless options[:drop] + end + end + + def with_lock(env) + @mutex.lock if env['rack.multithread'] + yield + ensure + @mutex.unlock if @mutex.locked? + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showexceptions.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showexceptions.rb new file mode 100644 index 000000000..60999e642 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showexceptions.rb @@ -0,0 +1,387 @@ +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 + exception_string = dump_exception(e) + + env["rack.errors"].puts(exception_string) + env["rack.errors"].flush + + if accepts_html?(env) + content_type = "text/html" + body = pretty(env, e) + else + content_type = "text/plain" + body = exception_string + end + + [ + 500, + { + CONTENT_TYPE => content_type, + CONTENT_LENGTH => Rack::Utils.bytesize(body).to_s, + }, + [body], + ] + end + + def prefers_plaintext?(env) + !accepts_html(env) + end + + def accepts_html?(env) + Rack::Utils.best_q_match(env["HTTP_ACCEPT"], %w[text/html]) + end + private :accepts_html? + + def dump_exception(exception) + string = "#{exception.class}: #{exception.message}\n" + string << exception.backtrace.map { |l| "\t#{l}" }.join("\n") + string + end + + def pretty(env, exception) + req = Rack::Request.new(env) + + # This double assignment is to prevent an "unused variable" warning on + # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. + path = path = (req.script_name + req.path_info).squeeze("/") + + # This double assignment is to prevent an "unused variable" warning on + # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. + frames = 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 + + @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 +<% if first = frames.first %> + <%=h first.filename %>: in <%=h first.function %>, line <%=h frames.first.lineno %> +<% else %> + unknown location +<% end %> +
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

+ <% if req.GET and not 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

+ <% if req.POST and not 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showstatus.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showstatus.rb new file mode 100644 index 000000000..4426310a2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/showstatus.rb @@ -0,0 +1,113 @@ +require 'erb' +require 'rack/request' +require 'rack/utils' + +module Rack + # Rack::ShowStatus catches all empty responses 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"] + # This double assignment is to prevent an "unused variable" warning on + # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. + req = req = Rack::Request.new(env) + + message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s + + # This double assignment is to prevent an "unused variable" warning on + # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. + detail = 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 %>
+
+
+

<%=h detail %>

+
+ +
+

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

+
+ + +HTML + + # :startdoc: + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/static.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/static.rb new file mode 100644 index 000000000..074133d5f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/static.rb @@ -0,0 +1,150 @@ +module Rack + + # The Rack::Static middleware intercepts requests for static files + # (javascript files, images, stylesheets, etc) based on the url prefixes or + # route mappings 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: + # + # Serve all requests beginning with /media from the "media" folder located + # in the current directory (ie media/*): + # + # use Rack::Static, :urls => ["/media"] + # + # Serve all requests beginning with /css or /images from the folder "public" + # in the current directory (ie public/css/* and public/images/*): + # + # use Rack::Static, :urls => ["/css", "/images"], :root => "public" + # + # Serve all requests to / with "index.html" from the folder "public" in the + # current directory (ie public/index.html): + # + # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public' + # + # Serve all requests normally from the folder "public" in the current + # directory but uses index.html as default route for "/" + # + # use Rack::Static, :urls => [""], :root => 'public', :index => + # 'index.html' + # + # Set custom HTTP Headers for based on rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # [rule, {header_field => content, header_field => content}], + # [rule, {header_field => content}] + # ] + # + # Rules for selecting files: + # + # 1) All files + # Provide the :all symbol + # :all => Matches every file + # + # 2) Folders + # Provide the folder path as a string + # '/folder' or '/folder/subfolder' => Matches files in a certain folder + # + # 3) File Extensions + # Provide the file extensions as an array + # ['css', 'js'] or %w(css js) => Matches files ending in .css or .js + # + # 4) Regular Expressions / Regexp + # Provide a regular expression + # %r{\.(?:css|js)\z} => Matches files ending in .css or .js + # /\.(?:eot|ttf|otf|woff2|woff|svg)\z/ => Matches files ending in + # the most common web font formats (.eot, .ttf, .otf, .woff2, .woff, .svg) + # Note: This Regexp is available as a shortcut, using the :fonts rule + # + # 5) Font Shortcut + # Provide the :fonts symbol + # :fonts => Uses the Regexp rule stated right above to match all common web font endings + # + # Rule Ordering: + # Rules are applied in the order that they are provided. + # List rather general rules above special ones. + # + # Complete example use case including HTTP header rules: + # + # use Rack::Static, :root => 'public', + # :header_rules => [ + # # Cache all static files in public caches (e.g. Rack::Cache) + # # as well as in the browser + # [:all, {'Cache-Control' => 'public, max-age=31536000'}], + # + # # Provide web fonts with cross-origin access-control-headers + # # Firefox requires this when serving assets using a Content Delivery Network + # [:fonts, {'Access-Control-Allow-Origin' => '*'}] + # ] + # + class Static + + def initialize(app, options={}) + @app = app + @urls = options[:urls] || ["/favicon.ico"] + @index = options[:index] + root = options[:root] || Dir.pwd + + # HTTP Headers + @header_rules = options[:header_rules] || [] + # Allow for legacy :cache_control option while prioritizing global header_rules setting + @header_rules.insert(0, [:all, {'Cache-Control' => options[:cache_control]}]) if options[:cache_control] + + @file_server = Rack::File.new(root) + end + + def overwrite_file_path(path) + @urls.kind_of?(Hash) && @urls.key?(path) || @index && path =~ /\/$/ + end + + def route_file(path) + @urls.kind_of?(Array) && @urls.any? { |url| path.index(url) == 0 } + end + + def can_serve(path) + route_file(path) || overwrite_file_path(path) + end + + def call(env) + path = env[PATH_INFO] + + if can_serve(path) + env["PATH_INFO"] = (path =~ /\/$/ ? path + @index : @urls[path]) if overwrite_file_path(path) + path = env["PATH_INFO"] + response = @file_server.call(env) + + headers = response[1] + applicable_rules(path).each do |rule, new_headers| + new_headers.each { |field, content| headers[field] = content } + end + + response + else + @app.call(env) + end + end + + # Convert HTTP header rules to HTTP headers + def applicable_rules(path) + @header_rules.find_all do |rule, new_headers| + case rule + when :all + true + when :fonts + path =~ /\.(?:ttf|otf|eot|woff2|woff|svg)\z/ + when String + path = ::Rack::Utils.unescape(path) + path.start_with?(rule) || path.start_with?('/' + rule) + when Array + path =~ /\.(#{rule.join('|')})\z/ + when Regexp + path =~ rule + else + false + end + end + end + + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/tempfile_reaper.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/tempfile_reaper.rb new file mode 100644 index 000000000..1500b06ab --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/tempfile_reaper.rb @@ -0,0 +1,22 @@ +require 'rack/body_proxy' + +module Rack + + # Middleware tracks and cleans Tempfiles created throughout a request (i.e. Rack::Multipart) + # Ideas/strategy based on posts by Eric Wong and Charles Oliver Nutter + # https://groups.google.com/forum/#!searchin/rack-devel/temp/rack-devel/brK8eh-MByw/sw61oJJCGRMJ + class TempfileReaper + def initialize(app) + @app = app + end + + def call(env) + env['rack.tempfiles'] ||= [] + status, headers, body = @app.call(env) + body_proxy = BodyProxy.new(body) do + env['rack.tempfiles'].each { |f| f.close! } unless env['rack.tempfiles'].nil? + end + [status, headers, body_proxy] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/urlmap.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/urlmap.rb new file mode 100644 index 000000000..c62baeccc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/urlmap.rb @@ -0,0 +1,90 @@ +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 + NEGATIVE_INFINITY = -1.0 / 0.0 + INFINITY = 1.0 / 0.0 + + 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 do |(host, location, _, _)| + [host ? -host.size : INFINITY, -location.size] + end + end + + def call(env) + path = env[PATH_INFO] + script_name = env['SCRIPT_NAME'] + hHost = env['HTTP_HOST'] + sName = env['SERVER_NAME'] + sPort = env['SERVER_PORT'] + + @mapping.each do |host, location, match, app| + unless casecmp?(hHost, host) \ + || casecmp?(sName, host) \ + || (!host && (casecmp?(hHost, sName) || + casecmp?(hHost, sName+':'+sPort))) + next + end + + next unless m = match.match(path.to_s) + + rest = m[1] + next unless !rest || rest.empty? || rest[0] == ?/ + + env['SCRIPT_NAME'] = (script_name + location) + env['PATH_INFO'] = rest + + return app.call(env) + end + + [404, {CONTENT_TYPE => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] + + ensure + env['PATH_INFO'] = path + env['SCRIPT_NAME'] = script_name + end + + private + def casecmp?(v1, v2) + # if both nil, or they're the same string + return true if v1 == v2 + + # if either are nil... (but they're not the same) + return false if v1.nil? + return false if v2.nil? + + # otherwise check they're not case-insensitive the same + v1.casecmp(v2).zero? + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils.rb new file mode 100644 index 000000000..3b6f69f33 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils.rb @@ -0,0 +1,684 @@ +# -*- encoding: binary -*- +require 'fileutils' +require 'set' +require 'tempfile' +require 'rack/multipart' +require 'time' + +major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i } + +if major == 1 && minor < 9 + require 'rack/backports/uri/common_18' +elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby' + require 'rack/backports/uri/common_192' +elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125 + require 'rack/backports/uri/common_193' +else + require 'uri/common' +end + +module Rack + # Rack::Utils contains a grab-bag of useful methods for writing web + # applications adopted from all kinds of Ruby libraries. + + module Utils + # ParameterTypeError is the error that is raised when incoming structural + # parameters (parsed by parse_nested_query) contain conflicting types. + class ParameterTypeError < TypeError; end + + # InvalidParameterError is the error that is raised when incoming structural + # parameters (parsed by parse_nested_query) contain invalid format or byte + # sequence. + class InvalidParameterError < ArgumentError; end + + # URI escapes. (CGI style space to +) + def escape(s) + URI.encode_www_form_component(s) + end + module_function :escape + + # Like URI escaping, but with %20 instead of +. Strictly speaking this is + # true URI escaping. + def escape_path(s) + escape(s).gsub('+', '%20') + end + module_function :escape_path + + # Unescapes a URI escaped string with +encoding+. +encoding+ will be the + # target encoding of the string returned, and it defaults to UTF-8 + if defined?(::Encoding) + def unescape(s, encoding = Encoding::UTF_8) + URI.decode_www_form_component(s, encoding) + end + else + def unescape(s, encoding = nil) + URI.decode_www_form_component(s, encoding) + end + end + module_function :unescape + + DEFAULT_SEP = /[&;] */n + + class << self + attr_accessor :key_space_limit + attr_accessor :param_depth_limit + attr_accessor :multipart_part_limit + end + + # The default number of bytes to allow parameter keys to take up. + # This helps prevent a rogue client from flooding a Request. + self.key_space_limit = 65536 + + # Default depth at which the parameter parser will raise an exception for + # being too deep. This helps prevent SystemStackErrors + self.param_depth_limit = 100 + + # The maximum number of parts a request can contain. Accepting too many part + # can lead to the server running out of file handles. + # Set to `0` for no limit. + # FIXME: RACK_MULTIPART_LIMIT was introduced by mistake and it will be removed in 1.7.0 + self.multipart_part_limit = (ENV['RACK_MULTIPART_PART_LIMIT'] || ENV['RACK_MULTIPART_LIMIT'] || 128).to_i + + # 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, &unescaper) + unescaper ||= method(:unescape) + + params = KeySpaceConstrainedParams.new + + (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| + next if p.empty? + k, v = p.split('=', 2).map(&unescaper) + + 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.to_params_hash + end + module_function :parse_query + + # parse_nested_query expands a query string into structural types. Supported + # types are Arrays, Hashes and basic value types. It is possible to supply + # query strings with parameters of conflicting types, in this case a + # ParameterTypeError is raised. Users are encouraged to return a 400 in this + # case. + def parse_nested_query(qs, d = nil) + params = KeySpaceConstrainedParams.new + + (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| + k, v = p.split('=', 2).map { |s| unescape(s) } + + normalize_params(params, k, v) + end + + return params.to_params_hash + rescue ArgumentError => e + raise InvalidParameterError, e.message + end + module_function :parse_nested_query + + # normalize_params recursively expands parameters into structural types. If + # the structural types represented by two different parameter names are in + # conflict, a ParameterTypeError is raised. + def normalize_params(params, name, v = nil, depth = Utils.param_depth_limit) + raise RangeError if depth <= 0 + + name =~ %r(\A[\[\]]*([^\[\]]+)\]*) + k = $1 || '' + after = $' || '' + + return if k.empty? + + if after == "" + params[k] = v + elsif after == "[" + params[name] = v + elsif after == "[]" + params[k] ||= [] + raise ParameterTypeError, "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 ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array) + if params_hash_type?(params[k].last) && !params[k].last.key?(child_key) + normalize_params(params[k].last, child_key, v, depth - 1) + else + params[k] << normalize_params(params.class.new, child_key, v, depth - 1) + end + else + params[k] ||= params.class.new + raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k]) + params[k] = normalize_params(params[k], after, v, depth - 1) + end + + return params + end + module_function :normalize_params + + def params_hash_type?(obj) + obj.kind_of?(KeySpaceConstrainedParams) || obj.kind_of?(Hash) + end + module_function :params_hash_type? + + def build_query(params) + params.map { |k, v| + if v.class == Array + build_query(v.map { |x| [k, x] }) + else + v.nil? ? escape(k) : "#{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)) + }.reject(&:empty?).join('&') + when nil + prefix + else + raise ArgumentError, "value must be a Hash" if prefix.nil? + "#{prefix}=#{escape(value)}" + end + end + module_function :build_nested_query + + def q_values(q_value_header) + q_value_header.to_s.split(/\s*,\s*/).map do |part| + value, parameters = part.split(/\s*;\s*/, 2) + quality = 1.0 + if md = /\Aq=([\d.]+)/.match(parameters) + quality = md[1].to_f + end + [value, quality] + end + end + module_function :q_values + + def best_q_match(q_value_header, available_mimes) + values = q_values(q_value_header) + + matches = values.map do |req_mime, quality| + match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) } + next unless match + [match, quality] + end.compact.sort_by do |match, quality| + (match.split('/', 2).count('*') * -10) + quality + end.last + matches && matches.first + end + module_function :best_q_match + + ESCAPE_HTML = { + "&" => "&", + "<" => "<", + ">" => ">", + "'" => "'", + '"' => """, + "/" => "/" + } + if //.respond_to?(:encoding) + ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys) + else + # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwise + # TODO doesn't apply to jruby, so a better condition above might be preferable? + ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n + end + + # Escape ampersands, brackets and quotes to their HTML/XML entities. + def escape_html(string) + string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] } + 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.each { |m, q| + encoding_candidates.delete(m) if q == 0.0 + } + + 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] + max_age = "; max-age=" + value[:max_age].to_s if value[:max_age] + # There is an RFC mess in the area of date formatting for Cookies. Not + # only are there contradicting RFCs and examples within RFC text, but + # there are also numerous conflicting names of fields and partially + # cross-applicable specifications. + # + # These are best described in RFC 2616 3.3.1. This RFC text also + # specifies that RFC 822 as updated by RFC 1123 is preferred. That is a + # fixed length format with space-date delimeted fields. + # + # See also RFC 1123 section 5.2.14. + # + # RFC 6265 also specifies "sane-cookie-date" as RFC 1123 date, defined + # in RFC 2616 3.3.1. RFC 6265 also gives examples that clearly denote + # the space delimited format. These formats are compliant with RFC 2822. + # + # For reference, all involved RFCs are: + # RFC 822 + # RFC 1123 + # RFC 2109 + # RFC 2616 + # RFC 2822 + # RFC 2965 + # RFC 6265 + expires = "; expires=" + + rfc2822(value[:expires].clone.gmtime) if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only]) + value = value[:value] + end + value = [value] unless Array === value + cookie = escape(key) + "=" + + value.map { |v| escape v }.join("&") + + "#{domain}#{path}#{max_age}#{expires}#{secure}#{httponly}" + + case header["Set-Cookie"] + when nil, '' + header["Set-Cookie"] = cookie + when String + header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n") + when Array + header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n") + end + + nil + end + module_function :set_cookie_header! + + def delete_cookie_header!(header, key, value = {}) + case header["Set-Cookie"] + when nil, '' + cookies = [] + when String + cookies = header["Set-Cookie"].split("\n") + when Array + cookies = header["Set-Cookie"] + end + + cookies.reject! { |cookie| + if value[:domain] + cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/ + elsif value[:path] + cookie =~ /\A#{escape(key)}=.*path=#{value[:path]}/ + else + cookie =~ /\A#{escape(key)}=/ + end + } + + header["Set-Cookie"] = cookies.join("\n") + + set_cookie_header!(header, key, + {:value => '', :path => nil, :domain => nil, + :max_age => '0', + :expires => Time.at(0) }.merge(value)) + + nil + end + module_function :delete_cookie_header! + + # Return the bytesize of String; uses String#size 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 + + def rfc2822(time) + time.rfc2822 + end + module_function :rfc2822 + + # Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead + # of '% %b %Y'. + # It assumes that the time is in GMT to comply to the RFC 2109. + # + # NOTE: I'm not sure the RFC says it requires GMT, but is ambiguous enough + # that I'm certain someone implemented only that option. + # Do not use %a and %b from Time.strptime, it would use localized names for + # weekday and month. + # + def rfc2109(time) + wday = Time::RFC2822_DAY_NAME[time.wday] + mon = Time::RFC2822_MONTH_NAME[time.mon - 1] + time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT") + end + module_function :rfc2109 + + # Parses the "Range:" header, if present, into an array of Range objects. + # Returns nil if the header is missing or syntactically invalid. + # Returns an empty array if none of the ranges are satisfiable. + def byte_ranges(env, size) + # See + http_range = env['HTTP_RANGE'] + return nil unless http_range && http_range =~ /bytes=([^;]+)/ + ranges = [] + $1.split(/,\s*/).each do |range_spec| + return nil unless range_spec =~ /(\d*)-(\d*)/ + r0,r1 = $1, $2 + if r0.empty? + return nil if r1.empty? + # suffix-byte-range-spec, represents trailing suffix of file + r0 = size - r1.to_i + r0 = 0 if r0 < 0 + r1 = size - 1 + else + r0 = r0.to_i + if r1.empty? + r1 = size - 1 + else + r1 = r1.to_i + return nil if r1 < r0 # backwards range is syntactically invalid + r1 = size-1 if r1 >= size + end + end + ranges << (r0..r1) if r0 <= r1 + end + ranges + end + module_function :byte_ranges + + # Constant time string comparison. + # + # NOTE: the values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless bytesize(a) == bytesize(b) + + l = a.unpack("C*") + + r, i = 0, -1 + b.each_byte { |v| r |= v ^ l[i+=1] } + r == 0 + end + module_function :secure_compare + + # 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 + hash = {} + each { |k,v| hash[k] = v } + hash + end + + def [](k) + super(k) || super(@names[k.downcase]) + end + + def []=(k, v) + canonical = k.downcase + delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary + @names[k] = @names[canonical] = 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 + + class KeySpaceConstrainedParams + def initialize(limit = Utils.key_space_limit) + @limit = limit + @size = 0 + @params = {} + end + + def [](key) + @params[key] + end + + def []=(key, value) + @size += key.size if key && !@params.key?(key) + raise RangeError, 'exceeded available parameter key space' if @size > @limit + @params[key] = value + end + + def key?(key) + @params.key?(key) + end + + def to_params_hash + hash = @params + hash.keys.each do |key| + value = hash[key] + if value.kind_of?(self.class) + if value.object_id == self.object_id + hash[key] = hash + else + hash[key] = value.to_params_hash + end + elsif value.kind_of?(Array) + value.map! {|x| x.kind_of?(self.class) ? x.to_params_hash : x} + end + end + hash + end + end + + # Every standard HTTP code mapped to the appropriate message. + # Generated with: + # curl -s https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv | \ + # ruby -ne 'm = /^(\d{3}),(?!Unassigned|\(Unused\))([^,]+)/.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', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent 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 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 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', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required' + } + + # Responses with HTTP status codes that should not have an entity body + STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 205 << 304) + + SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message| + [message.downcase.gsub(/\s|-|'/, '_').to_sym, code] + }.flatten] + + 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 + + Multipart = Rack::Multipart + + PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact) + + def clean_path_info(path_info) + parts = path_info.split PATH_SEPS + + clean = [] + + parts.each do |part| + next if part.empty? || part == '.' + part == '..' ? clean.pop : clean << part + end + + clean.unshift '/' if parts.empty? || parts.first.empty? + + ::File.join(*clean) + end + module_function :clean_path_info + + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils/okjson.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils/okjson.rb new file mode 100644 index 000000000..0d1049e13 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/lib/rack/utils/okjson.rb @@ -0,0 +1,600 @@ +# encoding: UTF-8 +# +# Copyright 2011, 2012 Keith Rarick +# +# 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 OR COPYRIGHT HOLDERS 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. + +# See https://github.com/kr/okjson for updates. + +require 'stringio' + +# Some parts adapted from +# https://golang.org/src/encoding/json/decode.go and +# https://golang.org/src/unicode/utf8/utf8.go +module Rack::Utils::OkJson + Upstream = '43' + extend self + + + # Decodes a json document in string s and + # returns the corresponding ruby value. + # String s must be valid UTF-8. If you have + # a string in some other encoding, convert + # it first. + # + # String values in the resulting structure + # will be UTF-8. + def decode(s) + ts = lex(s) + v, ts = textparse(ts) + if ts.length > 0 + raise Error, 'trailing garbage' + end + v + end + + + # Encodes x into a json text. It may contain only + # Array, Hash, String, Numeric, true, false, nil. + # (Note, this list excludes Symbol.) + # X itself must be an Array or a Hash. + # No other value can be encoded, and an error will + # be raised if x contains any other value, such as + # Nan, Infinity, Symbol, and Proc, or if a Hash key + # is not a String. + # Strings contained in x must be valid UTF-8. + def encode(x) + case x + when Hash then objenc(x) + when Array then arrenc(x) + else + raise Error, 'root value must be an Array or a Hash' + end + end + + + def valenc(x) + case x + when Hash then objenc(x) + when Array then arrenc(x) + when String then strenc(x) + when Numeric then numenc(x) + when true then "true" + when false then "false" + when nil then "null" + else + raise Error, "cannot encode #{x.class}: #{x.inspect}" + end + end + + +private + + + # Parses a "json text" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + # Note: this is almost the same as valparse, + # except that it does not accept atomic values. + def textparse(ts) + if ts.length <= 0 + raise Error, 'empty' + end + + typ, _, val = ts[0] + case typ + when '{' then objparse(ts) + when '[' then arrparse(ts) + else + raise Error, "unexpected #{val.inspect}" + end + end + + + # Parses a "value" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def valparse(ts) + if ts.length <= 0 + raise Error, 'empty' + end + + typ, _, val = ts[0] + case typ + when '{' then objparse(ts) + when '[' then arrparse(ts) + when :val,:str then [val, ts[1..-1]] + else + raise Error, "unexpected #{val.inspect}" + end + end + + + # Parses an "object" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def objparse(ts) + ts = eat('{', ts) + obj = {} + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + + k, v, ts = pairparse(ts) + obj[k] = v + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + + loop do + ts = eat(',', ts) + + k, v, ts = pairparse(ts) + obj[k] = v + + if ts[0][0] == '}' + return obj, ts[1..-1] + end + end + end + + + # Parses a "member" in the sense of RFC 4627. + # Returns the parsed values and any trailing tokens. + def pairparse(ts) + (typ, _, k), ts = ts[0], ts[1..-1] + if typ != :str + raise Error, "unexpected #{k.inspect}" + end + ts = eat(':', ts) + v, ts = valparse(ts) + [k, v, ts] + end + + + # Parses an "array" in the sense of RFC 4627. + # Returns the parsed value and any trailing tokens. + def arrparse(ts) + ts = eat('[', ts) + arr = [] + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + + v, ts = valparse(ts) + arr << v + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + + loop do + ts = eat(',', ts) + + v, ts = valparse(ts) + arr << v + + if ts[0][0] == ']' + return arr, ts[1..-1] + end + end + end + + + def eat(typ, ts) + if ts[0][0] != typ + raise Error, "expected #{typ} (got #{ts[0].inspect})" + end + ts[1..-1] + end + + + # Scans s and returns a list of json tokens, + # excluding white space (as defined in RFC 4627). + def lex(s) + ts = [] + while s.length > 0 + typ, lexeme, val = tok(s) + if typ == nil + raise Error, "invalid character at #{s[0,10].inspect}" + end + if typ != :space + ts << [typ, lexeme, val] + end + s = s[lexeme.length..-1] + end + ts + end + + + # Scans the first token in s and + # returns a 3-element list, or nil + # if s does not begin with a valid token. + # + # The first list element is one of + # '{', '}', ':', ',', '[', ']', + # :val, :str, and :space. + # + # The second element is the lexeme. + # + # The third element is the value of the + # token for :val and :str, otherwise + # it is the lexeme. + def tok(s) + case s[0] + when ?{ then ['{', s[0,1], s[0,1]] + when ?} then ['}', s[0,1], s[0,1]] + when ?: then [':', s[0,1], s[0,1]] + when ?, then [',', s[0,1], s[0,1]] + when ?[ then ['[', s[0,1], s[0,1]] + when ?] then [']', s[0,1], s[0,1]] + when ?n then nulltok(s) + when ?t then truetok(s) + when ?f then falsetok(s) + when ?" then strtok(s) + when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]] + else + numtok(s) + end + end + + + def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end + def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end + def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end + + + def numtok(s) + m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s) + if m && m.begin(0) == 0 + if !m[2] && !m[3] + [:val, m[0], Integer(m[0])] + elsif m[2] + [:val, m[0], Float(m[0])] + else + [:val, m[0], Integer(m[1])*(10**m[3][1..-1].to_i(10))] + end + else + [] + end + end + + + def strtok(s) + m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s) + if ! m + raise Error, "invalid string literal at #{abbrev(s)}" + end + [:str, m[0], unquote(m[0])] + end + + + def abbrev(s) + t = s[0,10] + p = t['`'] + t = t[0,p] if p + t = t + '...' if t.length < s.length + '`' + t + '`' + end + + + # Converts a quoted json string literal q into a UTF-8-encoded string. + # The rules are different than for Ruby, so we cannot use eval. + # Unquote will raise an error if q contains control characters. + def unquote(q) + q = q[1...-1] + a = q.dup # allocate a big enough string + # In ruby >= 1.9, a[w] is a codepoint, not a byte. + if rubydoesenc? + a.force_encoding('UTF-8') + end + r, w = 0, 0 + while r < q.length + c = q[r] + if c == ?\\ + r += 1 + if r >= q.length + raise Error, "string literal ends with a \"\\\": \"#{q}\"" + end + + case q[r] + when ?",?\\,?/,?' + a[w] = q[r] + r += 1 + w += 1 + when ?b,?f,?n,?r,?t + a[w] = Unesc[q[r]] + r += 1 + w += 1 + when ?u + r += 1 + uchar = begin + hexdec4(q[r,4]) + rescue RuntimeError => e + raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}" + end + r += 4 + if surrogate? uchar + if q.length >= r+6 + uchar1 = hexdec4(q[r+2,4]) + uchar = subst(uchar, uchar1) + if uchar != Ucharerr + # A valid pair; consume. + r += 6 + end + end + end + if rubydoesenc? + a[w] = '' << uchar + w += 1 + else + w += ucharenc(a, w, uchar) + end + else + raise Error, "invalid escape char #{q[r]} in \"#{q}\"" + end + elsif c == ?" || c < Spc + raise Error, "invalid character in string literal \"#{q}\"" + else + # Copy anything else byte-for-byte. + # Valid UTF-8 will remain valid UTF-8. + # Invalid UTF-8 will remain invalid UTF-8. + # In ruby >= 1.9, c is a codepoint, not a byte, + # in which case this is still what we want. + a[w] = c + r += 1 + w += 1 + end + end + a[0,w] + end + + + # Encodes unicode character u as UTF-8 + # bytes in string a at position i. + # Returns the number of bytes written. + def ucharenc(a, i, u) + if u <= Uchar1max + a[i] = (u & 0xff).chr + 1 + elsif u <= Uchar2max + a[i+0] = (Utag2 | ((u>>6)&0xff)).chr + a[i+1] = (Utagx | (u&Umaskx)).chr + 2 + elsif u <= Uchar3max + a[i+0] = (Utag3 | ((u>>12)&0xff)).chr + a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr + a[i+2] = (Utagx | (u&Umaskx)).chr + 3 + else + a[i+0] = (Utag4 | ((u>>18)&0xff)).chr + a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr + a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr + a[i+3] = (Utagx | (u&Umaskx)).chr + 4 + end + end + + + def hexdec4(s) + if s.length != 4 + raise Error, 'short' + end + (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3]) + end + + + def subst(u1, u2) + if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3 + return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself + end + return Ucharerr + end + + + def surrogate?(u) + Usurr1 <= u && u < Usurr3 + end + + + def nibble(c) + if ?0 <= c && c <= ?9 then c.ord - ?0.ord + elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10 + elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10 + else + raise Error, "invalid hex code #{c}" + end + end + + + def objenc(x) + '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}' + end + + + def arrenc(a) + '[' + a.map{|x| valenc(x)}.join(',') + ']' + end + + + def keyenc(k) + case k + when String then strenc(k) + else + raise Error, "Hash key is not a string: #{k.inspect}" + end + end + + + def strenc(s) + t = StringIO.new + t.putc(?") + r = 0 + + while r < s.length + case s[r] + when ?" then t.print('\\"') + when ?\\ then t.print('\\\\') + when ?\b then t.print('\\b') + when ?\f then t.print('\\f') + when ?\n then t.print('\\n') + when ?\r then t.print('\\r') + when ?\t then t.print('\\t') + else + c = s[r] + # In ruby >= 1.9, s[r] is a codepoint, not a byte. + if rubydoesenc? + begin + # c.ord will raise an error if c is invalid UTF-8 + if c.ord < Spc.ord + c = "\\u%04x" % [c.ord] + end + t.write(c) + rescue + t.write(Ustrerr) + end + elsif c < Spc + t.write("\\u%04x" % c) + elsif Spc <= c && c <= ?~ + t.putc(c) + else + n = ucharcopy(t, s, r) # ensure valid UTF-8 output + r += n - 1 # r is incremented below + end + end + r += 1 + end + t.putc(?") + t.string + end + + + def numenc(x) + if ((x.nan? || x.infinite?) rescue false) + raise Error, "Numeric cannot be represented: #{x}" + end + "#{x}" + end + + + # Copies the valid UTF-8 bytes of a single character + # from string s at position i to I/O object t, and + # returns the number of bytes copied. + # If no valid UTF-8 char exists at position i, + # ucharcopy writes Ustrerr and returns 1. + def ucharcopy(t, s, i) + n = s.length - i + raise Utf8Error if n < 1 + + c0 = s[i].ord + + # 1-byte, 7-bit sequence? + if c0 < Utagx + t.putc(c0) + return 1 + end + + raise Utf8Error if c0 < Utag2 # unexpected continuation byte? + + raise Utf8Error if n < 2 # need continuation byte + c1 = s[i+1].ord + raise Utf8Error if c1 < Utagx || Utag2 <= c1 + + # 2-byte, 11-bit sequence? + if c0 < Utag3 + raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max + t.putc(c0) + t.putc(c1) + return 2 + end + + # need second continuation byte + raise Utf8Error if n < 3 + + c2 = s[i+2].ord + raise Utf8Error if c2 < Utagx || Utag2 <= c2 + + # 3-byte, 16-bit sequence? + if c0 < Utag4 + u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx) + raise Utf8Error if u <= Uchar2max + t.putc(c0) + t.putc(c1) + t.putc(c2) + return 3 + end + + # need third continuation byte + raise Utf8Error if n < 4 + c3 = s[i+3].ord + raise Utf8Error if c3 < Utagx || Utag2 <= c3 + + # 4-byte, 21-bit sequence? + if c0 < Utag5 + u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx) + raise Utf8Error if u <= Uchar3max + t.putc(c0) + t.putc(c1) + t.putc(c2) + t.putc(c3) + return 4 + end + + raise Utf8Error + rescue Utf8Error + t.write(Ustrerr) + return 1 + end + + + def rubydoesenc? + ::String.method_defined?(:force_encoding) + end + + + class Utf8Error < ::StandardError + end + + + class Error < ::StandardError + end + + + Utagx = 0b1000_0000 + Utag2 = 0b1100_0000 + Utag3 = 0b1110_0000 + Utag4 = 0b1111_0000 + Utag5 = 0b1111_1000 + Umaskx = 0b0011_1111 + Umask2 = 0b0001_1111 + Umask3 = 0b0000_1111 + Umask4 = 0b0000_0111 + Uchar1max = (1<<7) - 1 + Uchar2max = (1<<11) - 1 + Uchar3max = (1<<16) - 1 + Ucharerr = 0xFFFD # unicode "replacement char" + Ustrerr = "\xef\xbf\xbd" # unicode "replacement char" + Usurrself = 0x10000 + Usurr1 = 0xd800 + Usurr2 = 0xdc00 + Usurr3 = 0xe000 + + Spc = ' '[0] + Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/rack.gemspec b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/rack.gemspec new file mode 100644 index 000000000..82ce1979e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/rack.gemspec @@ -0,0 +1,33 @@ +Gem::Specification.new do |s| + s.name = "rack" + s.version = "1.6.4" + s.platform = Gem::Platform::RUBY + s.summary = "a modular Ruby webserver interface" + s.license = "MIT" + + s.description = <<-EOF +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. + +Also see http://rack.github.io/. +EOF + + s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,test/**/*}'] + + %w(COPYING KNOWN-ISSUES rack.gemspec Rakefile README.rdoc SPEC) + s.bindir = 'bin' + s.executables << 'rackup' + s.require_path = 'lib' + s.extra_rdoc_files = ['README.rdoc', 'KNOWN-ISSUES', 'HISTORY.md'] + s.test_files = Dir['test/spec_*.rb'] + + s.author = 'Christian Neukirchen' + s.email = 'chneukirchen@gmail.com' + s.homepage = 'http://rack.github.io/' + s.rubyforge_project = 'rack' + + s.add_development_dependency 'bacon' + s.add_development_dependency 'rake' +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/anything.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/anything.rb new file mode 100644 index 000000000..c07f82cda --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/anything.rb @@ -0,0 +1,5 @@ +class Anything + def self.call(env) + [200, {'Content-Type' => 'text/plain'}, ['OK']] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/comment.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/comment.ru new file mode 100644 index 000000000..0722f0a0e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/comment.ru @@ -0,0 +1,4 @@ +=begin + +=end +run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/end.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/end.ru new file mode 100644 index 000000000..7f36d8cbb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/end.ru @@ -0,0 +1,5 @@ +run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } +__END__ +Should not be evaluated +Neither should +This diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/line.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/line.ru new file mode 100644 index 000000000..f4c84aded --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/line.ru @@ -0,0 +1 @@ +run lambda{ |env| [200, {'Content-Type' => 'text/plain'}, [__LINE__.to_s]] } diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/options.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/options.ru new file mode 100644 index 000000000..8562da65d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/builder/options.ru @@ -0,0 +1,2 @@ +#\ -d +run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] } diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/folder/test.js b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/folder/test.js new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/folder/test.js @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/fonts/font.eot b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/fonts/font.eot new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/fonts/font.eot @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/images/image.png b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/images/image.png new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/images/image.png @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/index.html b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/index.html new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/index.html @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/javascripts/app.js b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/javascripts/app.js new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/javascripts/app.js @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/stylesheets/app.css b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/stylesheets/app.css new file mode 100644 index 000000000..6874e45ae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/assets/stylesheets/app.css @@ -0,0 +1 @@ +### TestFile ### diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.conf b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.conf new file mode 100755 index 000000000..c195f78cd --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.conf @@ -0,0 +1,26 @@ +server.modules = ("mod_fastcgi", "mod_cgi") +server.document-root = "." +server.errorlog = var.CWD + "/lighttpd.errors" +server.port = 9203 +server.bind = "127.0.0.1" + +server.event-handler = "select" + +cgi.assign = ("/test" => "", +# ".ru" => "" + ) + +fastcgi.server = ( + "test.fcgi" => ("localhost" => + ("min-procs" => 1, + "socket" => "/tmp/rack-test-fcgi", + "bin-path" => "test.fcgi")), + "test.ru" => ("localhost" => + ("min-procs" => 1, + "socket" => "/tmp/rack-test-ru-fcgi", + "bin-path" => CWD + "/rackup_stub.rb test.ru")), + "sample_rackup.ru" => ("localhost" => + ("min-procs" => 1, + "socket" => "/tmp/rack-test-rackup-fcgi", + "bin-path" => CWD + "/rackup_stub.rb sample_rackup.ru")), + ) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.errors b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.errors new file mode 100644 index 000000000..1a74b4e83 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/lighttpd.errors @@ -0,0 +1 @@ +2015-06-16 14:11:43: (log.c.164) server started diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/rackup_stub.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/rackup_stub.rb new file mode 100755 index 000000000..a216cdc39 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/rackup_stub.rb @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +$:.unshift '../../lib' +require 'rack' +Rack::Server.start diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/sample_rackup.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/sample_rackup.ru new file mode 100755 index 000000000..a73df81c1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/sample_rackup.ru @@ -0,0 +1,5 @@ +# -*- ruby -*- + +require '../testrequest' + +run Rack::Lint.new(TestRequest.new) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test new file mode 100755 index 000000000..e4837a4eb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +$: << File.join(File.dirname(__FILE__), "..", "..", "lib") + +require 'rack' +require '../testrequest' + +Rack::Handler::CGI.run(Rack::Lint.new(TestRequest.new)) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test+directory/test+file b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test+directory/test+file new file mode 100644 index 000000000..f4273fb83 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test+directory/test+file @@ -0,0 +1 @@ +this file has plusses! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.fcgi b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.fcgi new file mode 100755 index 000000000..5e104fc9a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.fcgi @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# -*- ruby -*- + +$:.unshift '../../lib' +require 'rack' +require '../testrequest' + +Rack::Handler::FastCGI.run(Rack::Lint.new(TestRequest.new)) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.ru new file mode 100755 index 000000000..7913ef781 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/cgi/test.ru @@ -0,0 +1,5 @@ +#!../../bin/rackup +# -*- ruby -*- + +require '../testrequest' +run Rack::Lint.new(TestRequest.new) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/gemloader.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/gemloader.rb new file mode 100644 index 000000000..22be69758 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/gemloader.rb @@ -0,0 +1,10 @@ +require 'rubygems' +project = 'rack' +gemspec = File.expand_path("#{project}.gemspec", Dir.pwd) +Gem::Specification.load(gemspec).dependencies.each do |dep| + begin + gem dep.name, *dep.requirement.as_list + rescue Gem::LoadError + warn "Cannot load #{dep.name} #{dep.requirement.to_s}" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/bad_robots b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/bad_robots new file mode 100644 index 000000000..1fd53c7ea --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/bad_robots @@ -0,0 +1,259 @@ +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="bbbbbbbbbbbbbbb" + +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaa + +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="ccccccc" + +ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="file.name" + +INPUTMSG.gz +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="file.content_type" + +application/octet-stream +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="file.path" + +/var/tmp/uploads/4/0001728414 +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="file.md5" + +aa73198feb4b4c1c3186f5e7466cbbcc +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="file.size" + +13212 +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="size" + +80892 +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="mail_server_id" + +<1111111111.22222222.3333333333333.JavaMail.app@ffff-aaaa.dddd> + +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="addresses" + +{"campsy_programmer@pinkedum.com":{"domain":"pinkedum.com","name":"Campsy Programmer","type":["env_sender"],"mailbox":"campsy_programmer"},"tex@rapidcity.com":{"domain":"rapidcity.com","name":"Big Tex","type":["env_recipients","to"],"mailbox":"tex"},"group-digests@linkedin.com":{"domain":"linkedin.com","name":"Group Members","type":["from"],"mailbox":"group-digests"}} +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="received_on" + +2009-11-15T14:21:11Z +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="id" + +dbfd9804d26d11deab24e3037639bf77 +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon +Content-Disposition: form-data; name="ip_address" + +127.0.0.1 +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/binary b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/binary new file mode 100644 index 000000000..a3bd67c49 Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/binary differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/content_type_and_no_filename b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/content_type_and_no_filename new file mode 100644 index 000000000..2a323a0de --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/content_type_and_no_filename @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="text" +Content-Type: text/plain; charset=US-ASCII + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/empty b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/empty new file mode 100644 index 000000000..7ba009902 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/empty @@ -0,0 +1,10 @@ +--AaB03x +Content-Disposition: form-data; name="submit-name" + +Larry +--AaB03x +Content-Disposition: form-data; name="files"; filename="file1.txt" +Content-Type: text/plain + + +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/fail_16384_nofile b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/fail_16384_nofile new file mode 100644 index 000000000..bdcd3320f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/fail_16384_nofile @@ -0,0 +1,814 @@ +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="_method" + +put +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="authenticity_token" + +XCUgSyYsZ+iHQunq/yCSKFzjeVmsXV/WcphHQ0J+05I= +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[SESE]" + +BooBar +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[BBBBBBBBB]" + +18 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[CCCCCCCCCCCCCCCCCCC]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[STARTFOO]" + +2009-11-04 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ENDFOO]" + +2009-12-01 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[DDDDDDDD]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[EEEEEEEEEE]" + +10000 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[FFFFFFFFF]" + +boskoizcool +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[GGGGGGGGGGG]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[YYYYYYYYYYYYYYY]" + +5.00 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[ZZZZZZZZZZZZZ]" + +mille +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[XXXXXXXXXXXXXXXXXXXXX]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][1][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][2][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][3][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][4][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][5][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][6][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][9]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][10]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][11]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][12]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][13]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][14]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][15]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][16]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][17]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][18]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][19]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][20]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][21]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][22]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][23]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][0]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][1]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][2]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][3]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][4]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][5]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][6]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][7]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[VVVVVVVVVVVVVVVVVVVVVVV][0][8]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][ZEZE]" + +PLAPLAPLAINCINCINC +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][123412341234e]" + +SITE +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[WWWWWWWWWWWWWWWWWWWWWWWWW][678][12345678901]" + +56 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_type]" + +none +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][has_hashashas_has]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_freee]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][frefrefre_fre_frefre]" + +forever +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][self_block]" + +0 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][COUCOUN]" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][REGREG]" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_wizard][GGG_RULES][][c1c1]" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA_TARTARTAR_wizard_rule" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[TARTARTAR_rule]" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[selection_selection]" + +R +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][selection_selection]" + +1 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-1][ba_unit_id]" + +1015 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][selection_selection]" + +2 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[PLAPLAPLA_MEMMEMMEMM_ATTRATTRER][new][-2][ba_unit_id]" + +1017 +------WebKitFormBoundaryWsY0GnpbI5U7ztzo +Content-Disposition: form-data; name="AAAAAAAAAAAAAAAAAAA[tile_name]" + + +------WebKitFormBoundaryWsY0GnpbI5U7ztzo-- + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/file1.txt b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/file1.txt new file mode 100644 index 000000000..0839b2e94 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/file1.txt @@ -0,0 +1 @@ +contents \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_modification_param b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_modification_param new file mode 100644 index 000000000..9cefe5f3d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_modification_param @@ -0,0 +1,7 @@ +--AaB03x +Content-Type: image/jpeg +Content-Disposition: attachment; name="files"; filename=genome.jpeg; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; +Content-Description: a complete map of the human genome + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_no_name b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_no_name new file mode 100644 index 000000000..08c0f763f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_and_no_name @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; filename="file1.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes new file mode 100644 index 000000000..ef554aaca --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="escape \"quotes" +Content-Type: application/octet-stream + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes_and_modification_param b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes_and_modification_param new file mode 100644 index 000000000..0d2d3b63e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_escaped_quotes_and_modification_param @@ -0,0 +1,7 @@ +--AaB03x +Content-Type: image/jpeg +Content-Disposition: attachment; name="files"; filename=""human" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; +Content-Description: a complete map of the human genome + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_percent_escaped_quotes b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_percent_escaped_quotes new file mode 100644 index 000000000..08d1cba54 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_percent_escaped_quotes @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="escape %22quotes" +Content-Type: application/octet-stream + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages new file mode 100644 index 000000000..f20d9db26 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages @@ -0,0 +1,6 @@ +------WebKitFormBoundary2NHc7OhsgU68l3Al +Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg" +Content-Type: image/jpeg + +contents +------WebKitFormBoundary2NHc7OhsgU68l3Al-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages2 b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages2 new file mode 100644 index 000000000..d0d46a41f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages2 @@ -0,0 +1,6 @@ +------WebKitFormBoundary2NHc7OhsgU68l3Al +Content-Disposition: form-data; name="document[attachment]"; filename="100%a" +Content-Type: image/jpeg + +contents +------WebKitFormBoundary2NHc7OhsgU68l3Al-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages3 b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages3 new file mode 100644 index 000000000..eec1d739e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_percentages3 @@ -0,0 +1,6 @@ +------WebKitFormBoundary2NHc7OhsgU68l3Al +Content-Disposition: form-data; name="document[attachment]"; filename="100%" +Content-Type: image/jpeg + +contents +------WebKitFormBoundary2NHc7OhsgU68l3Al-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_quotes b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_quotes new file mode 100644 index 000000000..1c5f93d42 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/filename_with_unescaped_quotes @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="escape "quotes" +Content-Type: application/octet-stream + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/ie b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/ie new file mode 100644 index 000000000..271740837 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/ie @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="C:\Documents and Settings\Administrator\Desktop\file1.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/invalid_character b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/invalid_character new file mode 100644 index 000000000..8c6ae75f2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/invalid_character @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="invalid.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/mixed_files b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/mixed_files new file mode 100644 index 000000000..d2c2792e8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/mixed_files @@ -0,0 +1,21 @@ +--AaB03x +Content-Disposition: form-data; name="foo" + +bar +--AaB03x +Content-Disposition: form-data; name="files" +Content-Type: multipart/mixed, boundary=BbC04y + +--BbC04y +Content-Disposition: attachment; filename="file.txt" +Content-Type: text/plain + +contents +--BbC04y +Content-Disposition: attachment; filename="flowers.jpg" +Content-Type: image/jpeg +Content-Transfer-Encoding: binary + +contents +--BbC04y-- +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/nested b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/nested new file mode 100644 index 000000000..d2471a986 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/nested @@ -0,0 +1,10 @@ +--AaB03x +Content-Disposition: form-data; name="foo[submit-name]" + +Larry +--AaB03x +Content-Disposition: form-data; name="foo[files]"; filename="file1.txt" +Content-Type: text/plain + +contents +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/none b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/none new file mode 100644 index 000000000..47138799b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/none @@ -0,0 +1,9 @@ +--AaB03x +Content-Disposition: form-data; name="submit-name" + +Larry +--AaB03x +Content-Disposition: form-data; name="files"; filename="" + + +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/semicolon b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/semicolon new file mode 100644 index 000000000..849873e39 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/semicolon @@ -0,0 +1,6 @@ +--AaB03x +Content-Disposition: form-data; name="files"; filename="fi;le1.txt" +Content-Type: text/plain + +contents +--AaB03x-- \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/text b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/text new file mode 100644 index 000000000..67bb5b9c2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/text @@ -0,0 +1,15 @@ +--AaB03x +Content-Disposition: form-data; name="submit-name" + +Larry +--AaB03x +Content-Disposition: form-data; name="submit-name-with-content" +Content-Type: text/plain + +Berry +--AaB03x +Content-Disposition: form-data; name="files"; filename="file1.txt" +Content-Type: text/plain + +contents +--AaB03x-- \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/three_files_three_fields b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/three_files_three_fields new file mode 100644 index 000000000..fa577fd31 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/three_files_three_fields @@ -0,0 +1,31 @@ +--AaB03x +content-disposition: form-data; name="reply" + +yes +--AaB03x +content-disposition: form-data; name="to" + +people +--AaB03x +content-disposition: form-data; name="from" + +others +--AaB03x +content-disposition: form-data; name="fileupload1"; filename="file1.jpg" +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 + +/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg +--AaB03x +content-disposition: form-data; name="fileupload2"; filename="file2.jpg" +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 + +/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg +--AaB03x +content-disposition: form-data; name="fileupload3"; filename="file3.jpg" +Content-Type: image/jpeg +Content-Transfer-Encoding: base64 + +/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg +--AaB03x-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/webkit b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/webkit new file mode 100644 index 000000000..d8025372b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/multipart/webkit @@ -0,0 +1,32 @@ +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="_method" + +put +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="profile[blog]" + + +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="profile[public_email]" + + +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="profile[interests]" + + +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="profile[bio]" + +hello + +"quote" +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="media"; filename="" +Content-Type: application/octet-stream + + +------WebKitFormBoundaryWLHCs9qmcJJoyjKR +Content-Disposition: form-data; name="commit" + +Save +------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/rackup/config.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/rackup/config.ru new file mode 100644 index 000000000..f1e2e1f30 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/rackup/config.ru @@ -0,0 +1,31 @@ +require "#{File.dirname(__FILE__)}/../testrequest" + +$stderr = File.open("#{File.dirname(__FILE__)}/log_output", "w") + +class EnvMiddleware + def initialize(app) + @app = app + end + + def call(env) + # provides a way to test that lint is present + if env["PATH_INFO"] == "/broken_lint" + return [200, {}, ["Broken Lint"]] + # provides a way to kill the process without knowing the pid + elsif env["PATH_INFO"] == "/die" + exit! + end + + env["test.$DEBUG"] = $DEBUG + env["test.$EVAL"] = BUKKIT if defined?(BUKKIT) + env["test.$VERBOSE"] = $VERBOSE + env["test.$LOAD_PATH"] = $LOAD_PATH + env["test.stderr"] = File.expand_path($stderr.path) + env["test.Ping"] = defined?(Ping) + env["test.pid"] = Process.pid + @app.call(env) + end +end + +use EnvMiddleware +run TestRequest.new diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/registering_handler/rack/handler/registering_myself.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/registering_handler/rack/handler/registering_myself.rb new file mode 100644 index 000000000..1635efa4b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/registering_handler/rack/handler/registering_myself.rb @@ -0,0 +1,8 @@ +module Rack + module Handler + class RegisteringMyself + end + + register :registering_myself, RegisteringMyself + end +end \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_basic.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_basic.rb new file mode 100644 index 000000000..af8e779b4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_basic.rb @@ -0,0 +1,81 @@ +require 'rack/auth/basic' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Auth::Basic do + def realm + 'WallysWorld' + end + + def unprotected_app + Rack::Lint.new 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 + + before 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 + + should 'challenge correctly when no credentials are specified' do + request do |response| + assert_basic_auth_challenge response + end + end + + should 'rechallenge if incorrect credentials are specified' do + request_with_basic_auth 'joe', 'password' do |response| + assert_basic_auth_challenge response + end + end + + 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 + + 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 + + should 'return 400 Bad Request for a malformed authorization header' do + request 'HTTP_AUTHORIZATION' => '' do |response| + response.should.be.a.client_error + response.status.should.equal 400 + response.should.not.include 'WWW-Authenticate' + end + end + + it 'takes realm as optional constructor arg' do + app = Rack::Auth::Basic.new(unprotected_app, realm) { true } + realm.should == app.realm + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_digest.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_digest.rb new file mode 100644 index 000000000..040be2eff --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_auth_digest.rb @@ -0,0 +1,259 @@ +require 'rack/auth/digest/md5' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Auth::Digest::MD5 do + def realm + 'WallysWorld' + end + + def unprotected_app + Rack::Lint.new lambda { |env| + friend = Rack::Utils.parse_query(env["QUERY_STRING"])["friend"] + [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}#{friend ? " and #{friend}" : ''}"] ] + } + end + + def protected_app + Rack::Auth::Digest::MD5.new(unprotected_app, :realm => realm, :opaque => 'this-should-be-secret') do |username| + { 'Alice' => 'correct-password' }[username] + end + 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 + + before 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 + + should 'challenge when no credentials are specified' do + request 'GET', '/' do |response| + assert_digest_auth_challenge response + end + end + + 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 + + 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 + + should 'rechallenge if incorrect username given' do + request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response| + assert_digest_auth_challenge response + end + end + + should 'rechallenge if incorrect password given' do + request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response| + assert_digest_auth_challenge response + end + end + + should 'rechallenge if incorrect user and blank password given' do + request_with_digest_auth 'GET', '/', 'Bob', '' do |response| + assert_digest_auth_challenge response + end + end + + should 'not rechallenge if nonce is not stale' do + begin + Rack::Auth::Digest::Nonce.time_limit = 10 + + request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', :wait => 1 do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice' + response.headers['WWW-Authenticate'].should.not =~ /\bstale=true\b/ + end + ensure + Rack::Auth::Digest::Nonce.time_limit = nil + end + end + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + should 'return application output when used with a query string and path as uri' do + @request = Rack::MockRequest.new(partially_protected_app) + request_with_digest_auth 'GET', '/protected?friend=Mike', 'Alice', 'correct-password' do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice and Mike' + end + end + + should 'return application output when used with a query string and fullpath as uri' do + @request = Rack::MockRequest.new(partially_protected_app) + qs_uri = '/protected?friend=Mike' + request_with_digest_auth 'GET', qs_uri, 'Alice', 'correct-password', 'uri' => qs_uri do |response| + response.status.should.equal 200 + response.body.to_s.should.equal 'Hi Alice and Mike' + end + end + + 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 + + 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 + + it 'takes realm as optional constructor arg' do + app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true } + realm.should == app.realm + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_body_proxy.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_body_proxy.rb new file mode 100644 index 000000000..8b6e6a5e4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_body_proxy.rb @@ -0,0 +1,85 @@ +require 'rack/body_proxy' +require 'stringio' +require 'ostruct' + +describe Rack::BodyProxy do + should 'call each on the wrapped body' do + called = false + proxy = Rack::BodyProxy.new(['foo']) { } + proxy.each do |str| + called = true + str.should.equal 'foo' + end + called.should.equal true + end + + should 'call close on the wrapped body' do + body = StringIO.new + proxy = Rack::BodyProxy.new(body) { } + proxy.close + body.should.be.closed + end + + should 'only call close on the wrapped body if it responds to close' do + body = [] + proxy = Rack::BodyProxy.new(body) { } + proc { proxy.close }.should.not.raise + end + + should 'call the passed block on close' do + called = false + proxy = Rack::BodyProxy.new([]) { called = true } + called.should.equal false + proxy.close + called.should.equal true + end + + should 'call the passed block on close even if there is an exception' do + object = Object.new + def object.close() raise "No!" end + called = false + + begin + proxy = Rack::BodyProxy.new(object) { called = true } + called.should.equal false + proxy.close + rescue RuntimeError => e + end + + raise "Expected exception to have been raised" unless e + called.should.equal true + end + + should 'allow multiple arguments in respond_to?' do + body = [] + proxy = Rack::BodyProxy.new(body) { } + proc { proxy.respond_to?(:foo, false) }.should.not.raise + end + + should 'not respond to :to_ary' do + body = OpenStruct.new(:to_ary => true) + body.respond_to?(:to_ary).should.equal true + + proxy = Rack::BodyProxy.new(body) { } + proxy.respond_to?(:to_ary).should.equal false + proxy.respond_to?("to_ary").should.equal false + end + + should 'not close more than one time' do + count = 0 + proxy = Rack::BodyProxy.new([]) { count += 1; raise "Block invoked more than 1 time!" if count > 1 } + 2.times { proxy.close } + count.should.equal 1 + end + + should 'be closed when the callback is triggered' do + closed = false + proxy = Rack::BodyProxy.new([]) { closed = proxy.closed? } + proxy.close + closed.should.equal true + end + + should 'provide an #each method' do + Rack::BodyProxy.method_defined?(:each).should.equal true + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_builder.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_builder.rb new file mode 100644 index 000000000..20ea66812 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_builder.rb @@ -0,0 +1,223 @@ +require 'rack/builder' +require 'rack/lint' +require 'rack/mock' +require 'rack/showexceptions' +require 'rack/urlmap' + +class NothingMiddleware + def initialize(app) + @app = app + end + def call(env) + @@env = env + response = @app.call(env) + response + end + def self.env + @@env + end +end + +describe Rack::Builder do + def builder(&block) + Rack::Lint.new Rack::Builder.new(&block) + end + + def builder_to_app(&block) + Rack::Lint.new Rack::Builder.new(&block).to_app + end + + it "supports mapping" do + app = builder_to_app do + map '/' do |outer_env| + run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] } + end + map '/sub' do + run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] } + end + end + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root' + Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub' + end + + it "doesn't dupe env even when mapping" do + app = builder_to_app do + use NothingMiddleware + map '/' do |outer_env| + run lambda { |inner_env| + inner_env['new_key'] = 'new_value' + [200, {"Content-Type" => "text/plain"}, ['root']] + } + end + end + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root' + NothingMiddleware.env['new_key'].should.equal 'new_value' + end + + it "chains apps by default" do + app = builder_to_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 + + it "has implicit #to_app" do + app = builder 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 + + it "supports blocks on use" do + app = builder do + use Rack::ShowExceptions + use Rack::Auth::Basic do |username, password| + 'secret' == password + end + + run lambda { |env| [200, {"Content-Type" => "text/plain"}, ['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 + + it "has explicit #to_app" do + app = builder 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 + + it "can mix map and run for endpoints" do + app = builder do + map '/sub' do + run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['sub']] } + end + run lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, ['root']] } + end + + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root' + Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub' + end + + it "accepts middleware-only map blocks" do + app = builder do + map('/foo') { use Rack::ShowExceptions } + run lambda { |env| raise "bzzzt" } + end + + proc { Rack::MockRequest.new(app).get("/") }.should.raise(RuntimeError) + Rack::MockRequest.new(app).get("/foo").should.be.server_error + end + + it "yields the generated app to a block for warmup" do + warmed_up_app = nil + + app = Rack::Builder.new do + warmup { |a| warmed_up_app = a } + run lambda { |env| [200, {}, []] } + end.to_app + + warmed_up_app.should.equal app + end + + should "initialize apps once" do + app = builder 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 + + it "allows use after run" do + app = builder do + run lambda { |env| raise "bzzzt" } + use Rack::ShowExceptions + 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 + + it 'complains about a missing run' do + proc do + Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions } + end.should.raise(RuntimeError) + end + + describe "parse_file" do + def config_file(name) + File.join(File.dirname(__FILE__), 'builder', name) + end + + it "parses commented options" do + app, options = Rack::Builder.parse_file config_file('options.ru') + options[:debug].should.be.true + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK' + end + + it "removes __END__ before evaluating app" do + app, _ = Rack::Builder.parse_file config_file('end.ru') + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK' + end + + it "supports multi-line comments" do + lambda { + Rack::Builder.parse_file config_file('comment.ru') + }.should.not.raise(SyntaxError) + end + + it "requires anything not ending in .ru" do + $: << File.dirname(__FILE__) + app, * = Rack::Builder.parse_file 'builder/anything' + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK' + $:.pop + end + + it "sets __LINE__ correctly" do + app, _ = Rack::Builder.parse_file config_file('line.ru') + Rack::MockRequest.new(app).get("/").body.to_s.should.equal '1' + end + end + + describe 'new_from_string' do + it "builds a rack app from string" do + app, = Rack::Builder.new_from_string "run lambda{|env| [200, {'Content-Type' => 'text/plane'}, ['OK']] }" + Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'OK' + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cascade.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cascade.rb new file mode 100644 index 000000000..38a18daa2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cascade.rb @@ -0,0 +1,61 @@ +require 'rack/cascade' +require 'rack/file' +require 'rack/lint' +require 'rack/urlmap' +require 'rack/mock' + +describe Rack::Cascade do + def cascade(*args) + Rack::Lint.new Rack::Cascade.new(*args) + end + + 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"}, [""]]}) + + should "dispatch onward on 404 and 405 by default" do + cascade = cascade([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/../..").should.be.client_error + + # Put is not allowed by Rack::File so it'll 405. + Rack::MockRequest.new(cascade).put("/foo").should.be.ok + end + + should "dispatch onward on whatever is passed" do + cascade = cascade([app1, app2, app3], [404, 403]) + Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found + end + + should "return 404 if empty" do + Rack::MockRequest.new(cascade([])).get('/').should.be.not_found + end + + 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/../..').should.be.client_error + Rack::MockRequest.new(cascade).get('/foo').should.be.not_found + cascade << app3 + Rack::MockRequest.new(cascade).get('/foo').should.be.ok + end + + should "close the body on cascade" do + body = StringIO.new + closer = lambda { |env| [404, {}, body] } + cascade = Rack::Cascade.new([closer, app3], [404]) + Rack::MockRequest.new(cascade).get("/foo").should.be.ok + body.should.be.closed + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cgi.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cgi.rb new file mode 100644 index 000000000..2e6e79a7b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_cgi.rb @@ -0,0 +1,102 @@ +begin +require File.expand_path('../testrequest', __FILE__) +require 'rack/handler/cgi' + +describe Rack::Handler::CGI do + extend TestRequest::Helpers + + @host = '127.0.0.1' + @port = 9203 + + if `which lighttpd` && !$?.success? + raise "lighttpd not found" + end + + # Keep this first. + $pid = fork { + ENV['RACK_ENV'] = 'deployment' + ENV['RUBYLIB'] = [ + File.expand_path('../../lib', __FILE__), + ENV['RUBYLIB'], + ].compact.join(':') + + Dir.chdir(File.expand_path("../cgi", __FILE__)) do + exec "lighttpd -D -f lighttpd.conf" + end + } + + should "respond" do + sleep 1 + GET("/test") + response.should.not.be.nil + end + + should "be a lighttpd" do + GET("/test") + status.should.equal 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.equal @host + end + + should "have rack headers" do + GET("/test") + response["rack.version"].should.equal([1,3]) + response["rack.multithread"].should.be.false + response["rack.multiprocess"].should.be.true + response["rack.run_once"].should.be.true + end + + 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.nil + 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 + + 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 + + should "support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + should "set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + # Keep this last. + should "shutdown" do + Process.kill 15, $pid + Process.wait($pid).should == $pid + end +end + +rescue RuntimeError + $stderr.puts "Skipping Rack::Handler::CGI tests (lighttpd is required). Install lighttpd and try again." +rescue NotImplementedError + $stderr.puts "Your Ruby implemenation or platform does not support fork. Skipping Rack::Handler::CGI tests." +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_chunked.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_chunked.rb new file mode 100644 index 000000000..0a6d9ff11 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_chunked.rb @@ -0,0 +1,101 @@ +require 'rack/chunked' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Chunked do + def chunked(app) + proc do |env| + app = Rack::Chunked.new(app) + response = Rack::Lint.new(app).call(env) + # we want to use body like an array, but it only has #each + response[2] = response[2].to_enum.to_a + response + end + end + + before do + @env = Rack::MockRequest. + env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET') + end + + should 'chunk responses with no Content-Length' do + app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] } + response = Rack::MockResponse.new(*chunked(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 + + should 'chunks empty bodies properly' do + app = lambda { |env| [200, {"Content-Type" => "text/plain"}, []] } + response = Rack::MockResponse.new(*chunked(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 + + should 'chunks encoded bodies properly' do + body = ["\uFFFEHello", " ", "World"].map {|t| t.encode("UTF-16LE") } + app = lambda { |env| [200, {"Content-Type" => "text/plain"}, body] } + response = Rack::MockResponse.new(*chunked(app).call(@env)) + response.headers.should.not.include 'Content-Length' + response.headers['Transfer-Encoding'].should.equal 'chunked' + response.body.encoding.to_s.should.equal "ASCII-8BIT" + response.body.should.equal "c\r\n\xFE\xFFH\x00e\x00l\x00l\x00o\x00\r\n2\r\n \x00\r\na\r\nW\x00o\x00r\x00l\x00d\x00\r\n0\r\n\r\n".force_encoding("BINARY") + end if RUBY_VERSION >= "1.9" + + should 'not modify response when Content-Length header present' do + app = lambda { |env| + [200, {"Content-Type" => "text/plain", 'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] + } + status, headers, body = chunked(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 + + should 'not modify response when client is HTTP/1.0' do + app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] } + @env['HTTP_VERSION'] = 'HTTP/1.0' + status, headers, body = chunked(app).call(@env) + status.should.equal 200 + headers.should.not.include 'Transfer-Encoding' + body.join.should.equal 'Hello World!' + end + + should 'not modify response when client is ancient, pre-HTTP/1.0' do + app = lambda { |env| [200, {"Content-Type" => "text/plain"}, ['Hello', ' ', 'World!']] } + check = lambda do + status, headers, body = chunked(app).call(@env.dup) + status.should.equal 200 + headers.should.not.include 'Transfer-Encoding' + body.join.should.equal 'Hello World!' + end + + @env.delete('HTTP_VERSION') # unicorn will do this on pre-HTTP/1.0 requests + check.call + + @env['HTTP_VERSION'] = 'HTTP/0.9' # not sure if this happens in practice + check.call + end + + should 'not modify response when Transfer-Encoding header already present' do + app = lambda { |env| + [200, {"Content-Type" => "text/plain", 'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] + } + status, headers, body = chunked(app).call(@env) + status.should.equal 200 + headers['Transfer-Encoding'].should.equal 'identity' + body.join.should.equal 'Hello World!' + end + + [100, 204, 205, 304].each do |status_code| + should "not modify response when status code is #{status_code}" do + app = lambda { |env| [status_code, {}, []] } + status, headers, _ = chunked(app).call(@env) + status.should.equal status_code + headers.should.not.include 'Transfer-Encoding' + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_commonlogger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_commonlogger.rb new file mode 100644 index 000000000..fd1f2521a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_commonlogger.rb @@ -0,0 +1,93 @@ +require 'rack/commonlogger' +require 'rack/lint' +require 'rack/mock' + +require 'logger' + +describe Rack::CommonLogger do + obj = 'foobar' + length = obj.size + + app = Rack::Lint.new lambda { |env| + [200, + {"Content-Type" => "text/html", "Content-Length" => length.to_s}, + [obj]]} + app_without_length = Rack::Lint.new lambda { |env| + [200, + {"Content-Type" => "text/html"}, + []]} + app_with_zero_length = Rack::Lint.new lambda { |env| + [200, + {"Content-Type" => "text/html", "Content-Length" => "0"}, + []]} + + 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 + + should "log to anything with +write+" do + log = StringIO.new + Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") + + log.string.should =~ /"GET \/ " 200 #{length} / + end + + should "work with standartd library logger" do + logdev = StringIO.new + log = Logger.new(logdev) + Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") + + logdev.string.should =~ /"GET \/ " 200 #{length} / + end + + 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 + + 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 with_mock_time(t = 0) + mc = class <timestamp}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp) + + response.status.should.equal 304 + response.body.should.be.empty + end + + should "set a 304 status and truncate body when If-Modified-Since hits and is higher than current time" do + app = conditional_get(lambda { |env| + [200, {'Last-Modified'=>(Time.now - 3600).httpdate}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => Time.now.httpdate) + + response.status.should.equal 304 + response.body.should.be.empty + end + + should "set a 304 status and truncate body when If-None-Match hits" do + app = conditional_get(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 + + should "not set a 304 status if If-Modified-Since hits but Etag does not" do + timestamp = Time.now.httpdate + app = conditional_get(lambda { |env| + [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '4321') + + response.status.should.equal 200 + response.body.should.equal 'TEST' + end + + should "set a 304 status and truncate body when both If-None-Match and If-Modified-Since hits" do + timestamp = Time.now.httpdate + app = conditional_get(lambda { |env| + [200, {'Last-Modified'=>timestamp, 'Etag'=>'1234'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => timestamp, 'HTTP_IF_NONE_MATCH' => '1234') + + response.status.should.equal 304 + response.body.should.be.empty + end + + should "not affect non-GET/HEAD requests" do + app = conditional_get(lambda { |env| + [200, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + post("/", 'HTTP_IF_NONE_MATCH' => '1234') + + response.status.should.equal 200 + response.body.should.equal 'TEST' + end + + should "not affect non-200 requests" do + app = conditional_get(lambda { |env| + [302, {'Etag'=>'1234', 'Content-Type' => 'text/plain'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_NONE_MATCH' => '1234') + + response.status.should.equal 302 + response.body.should.equal 'TEST' + end + + should "not affect requests with malformed HTTP_IF_NONE_MATCH" do + bad_timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S %z') + app = conditional_get(lambda { |env| + [200,{'Last-Modified'=>(Time.now - 3600).httpdate, 'Content-Type' => 'text/plain'}, ['TEST']] }) + + response = Rack::MockRequest.new(app). + get("/", 'HTTP_IF_MODIFIED_SINCE' => bad_timestamp) + + response.status.should.equal 200 + response.body.should.equal 'TEST' + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_config.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_config.rb new file mode 100644 index 000000000..29bca0a3f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_config.rb @@ -0,0 +1,22 @@ +require 'rack/builder' +require 'rack/config' +require 'rack/content_length' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Config do + should "accept a block that modifies the environment" do + app = Rack::Builder.new do + use Rack::Lint + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_length.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_length.rb new file mode 100644 index 000000000..12c047fbe --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_length.rb @@ -0,0 +1,85 @@ +require 'rack/content_length' +require 'rack/lint' +require 'rack/mock' + +describe Rack::ContentLength do + def content_length(app) + Rack::Lint.new Rack::ContentLength.new(app) + end + + def request + Rack::MockRequest.env_for + end + + should "set Content-Length on Array bodies if none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = content_length(app).call(request) + response[1]['Content-Length'].should.equal '13' + end + + should "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 = content_length(app).call(request) + response[1]['Content-Length'].should.be.nil + end + + should "not change Content-Length if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] } + response = content_length(app).call(request) + response[1]['Content-Length'].should.equal '1' + end + + should "not set Content-Length on 304 responses" do + app = lambda { |env| [304, {}, []] } + response = content_length(app).call(request) + response[1]['Content-Length'].should.equal nil + end + + should "not set Content-Length when Transfer-Encoding is chunked" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Transfer-Encoding' => 'chunked'}, []] } + response = content_length(app).call(request) + response[1]['Content-Length'].should.equal nil + end + + # Using "Connection: close" for this is fairly contended. It might be useful + # to have some other way to signal this. + # + # should "not force a Content-Length when Connection:close" do + # app = lambda { |env| [200, {'Connection' => 'close'}, []] } + # response = content_length(app).call({}) + # response[1]['Content-Length'].should.equal nil + # end + + should "close bodies that need to be closed" do + body = Struct.new(:body) do + attr_reader :closed + def each; body.join; end + def close; @closed = true; end + def to_ary; end + end.new(%w[one two three]) + + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] } + response = content_length(app).call(request) + body.closed.should.equal nil + response[2].close + body.closed.should.equal true + end + + should "support single-execute bodies" do + body = Struct.new(:body) do + def each + yield body.shift until body.empty? + end + def to_ary; end + end.new(%w[one two three]) + + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] } + response = content_length(app).call(request) + expected = %w[one two three] + response[1]['Content-Length'].should.equal expected.join.size.to_s + response[2].to_enum.to_a.should.equal expected + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_type.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_type.rb new file mode 100644 index 000000000..308611b3d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_content_type.rb @@ -0,0 +1,45 @@ +require 'rack/content_type' +require 'rack/lint' +require 'rack/mock' + +describe Rack::ContentType do + def content_type(app, *args) + Rack::Lint.new Rack::ContentType.new(app, *args) + end + + def request + Rack::MockRequest.env_for + end + + should "set Content-Type to default text/html if none is set" do + app = lambda { |env| [200, {}, "Hello, World!"] } + headers = content_type(app).call(request)[1] + headers['Content-Type'].should.equal 'text/html' + end + + should "set Content-Type to chosen default if none is set" do + app = lambda { |env| [200, {}, "Hello, World!"] } + headers = + content_type(app, 'application/octet-stream').call(request)[1] + headers['Content-Type'].should.equal 'application/octet-stream' + end + + should "not change Content-Type if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] } + headers = content_type(app).call(request)[1] + headers['Content-Type'].should.equal 'foo/bar' + end + + should "detect Content-Type case insensitive" do + app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] } + headers = content_type(app).call(request)[1] + headers.to_a.select { |k,v| k.downcase == "content-type" }. + should.equal [["CONTENT-Type","foo/bar"]] + end + + should "not set Content-Type on 304 responses" do + app = lambda { |env| [304, {}, []] } + response = content_type(app, "text/html").call(request) + response[1]['Content-Type'].should.equal nil + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_deflater.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_deflater.rb new file mode 100644 index 000000000..1e921eff5 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_deflater.rb @@ -0,0 +1,339 @@ +require 'stringio' +require 'time' # for Time#httpdate +require 'rack/deflater' +require 'rack/lint' +require 'rack/mock' +require 'zlib' + +describe Rack::Deflater do + + def build_response(status, body, accept_encoding, options = {}) + body = [body] if body.respond_to? :to_str + app = lambda do |env| + res = [status, options['response_headers'] || {}, body] + res[1]['Content-Type'] = 'text/plain' unless res[0] == 304 + res + end + + request = Rack::MockRequest.env_for('', (options['request_headers'] || {}).merge('HTTP_ACCEPT_ENCODING' => accept_encoding)) + deflater = Rack::Lint.new Rack::Deflater.new(app, options['deflater_options'] || {}) + + deflater.call(request) + end + + ## + # Constructs response object and verifies if it yields right results + # + # [expected_status] expected response status, e.g. 200, 304 + # [expected_body] expected response body + # [accept_encoing] what Accept-Encoding header to send and expect, e.g. + # 'deflate' - accepts and expects deflate encoding in response + # { 'gzip' => nil } - accepts gzip but expects no encoding in response + # [options] hash of request options, i.e. + # 'app_status' - what status dummy app should return (may be changed by deflater at some point) + # 'app_body' - what body dummy app should return (may be changed by deflater at some point) + # 'request_headers' - extra reqest headers to be sent + # 'response_headers' - extra response headers to be returned + # 'deflater_options' - options passed to deflater middleware + # [block] useful for doing some extra verification + def verify(expected_status, expected_body, accept_encoding, options = {}, &block) + accept_encoding, expected_encoding = if accept_encoding.kind_of?(Hash) + [accept_encoding.keys.first, accept_encoding.values.first] + else + [accept_encoding, accept_encoding.dup] + end + + # build response + status, headers, body = build_response( + options['app_status'] || expected_status, + options['app_body'] || expected_body, + accept_encoding, + options + ) + + # verify status + status.should.equal(expected_status) + + # verify body + unless options['skip_body_verify'] + body_text = '' + body.each { |part| body_text << part } + + deflated_body = case expected_encoding + when 'deflate' + inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + inflater.inflate(body_text) << inflater.finish + when 'gzip' + io = StringIO.new(body_text) + gz = Zlib::GzipReader.new(io) + tmp = gz.read + gz.close + tmp + else + body_text + end + + deflated_body.should.equal(expected_body) + end + + # yield full response verification + yield(status, headers, body) if block_given? + end + + should 'be able to deflate bodies that respond to each' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end + + verify(200, 'foobar', 'deflate', { 'app_body' => app_body }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end + + should 'flush deflated chunks to the client as they become ready' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end + + verify(200, app_body, 'deflate', { 'skip_body_verify' => true }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + + buf = [] + inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) + body.each { |part| buf << inflater.inflate(part) } + buf << inflater.finish + + buf.delete_if { |part| part.empty? }.join.should.equal('foobar') + end + end + + # TODO: This is really just a special case of the above... + should 'be able to deflate String bodies' do + verify(200, 'Hello world!', 'deflate') do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'deflate', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end + + should 'be able to gzip bodies that respond to each' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end + + verify(200, 'foobar', 'gzip', { 'app_body' => app_body }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end + + should 'flush gzipped chunks to the client as they become ready' do + app_body = Object.new + class << app_body; def each; yield('foo'); yield('bar'); end; end + + verify(200, app_body, 'gzip', { 'skip_body_verify' => true }) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + + buf = [] + inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32) + body.each { |part| buf << inflater.inflate(part) } + buf << inflater.finish + + buf.delete_if { |part| part.empty? }.join.should.equal('foobar') + end + end + + should 'be able to fallback to no deflation' do + verify(200, 'Hello world!', 'superzip') do |status, headers, body| + headers.should.equal({ + 'Vary' => 'Accept-Encoding', + 'Content-Type' => 'text/plain' + }) + end + end + + should 'be able to skip when there is no response entity body' do + verify(304, '', { 'gzip' => nil }, { 'app_body' => [] }) do |status, headers, body| + headers.should.equal({}) + end + end + + should 'handle the lack of an acceptable encoding' do + app_body = 'Hello world!' + not_found_body1 = 'An acceptable encoding for the requested resource / could not be found.' + not_found_body2 = 'An acceptable encoding for the requested resource /foo/bar could not be found.' + options1 = { + 'app_status' => 200, + 'app_body' => app_body, + 'request_headers' => { + 'PATH_INFO' => '/' + } + } + options2 = { + 'app_status' => 200, + 'app_body' => app_body, + 'request_headers' => { + 'PATH_INFO' => '/foo/bar' + } + } + + verify(406, not_found_body1, 'identity;q=0', options1) do |status, headers, body| + headers.should.equal({ + 'Content-Type' => 'text/plain', + 'Content-Length' => not_found_body1.length.to_s + }) + end + + verify(406, not_found_body2, 'identity;q=0', options2) do |status, headers, body| + headers.should.equal({ + 'Content-Type' => 'text/plain', + 'Content-Length' => not_found_body2.length.to_s + }) + end + end + + should 'handle gzip response with Last-Modified header' do + last_modified = Time.now.httpdate + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Last-Modified' => last_modified + } + } + + verify(200, 'Hello World!', 'gzip', options) do |status, headers, body| + headers.should.equal({ + 'Content-Encoding' => 'gzip', + 'Vary' => 'Accept-Encoding', + 'Last-Modified' => last_modified, + 'Content-Type' => 'text/plain' + }) + end + end + + should 'do nothing when no-transform Cache-Control directive present' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Cache-Control' => 'no-transform' + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) do |status, headers, body| + headers.should.not.include 'Content-Encoding' + end + end + + should 'do nothing when Content-Encoding already present' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Content-Encoding' => 'gzip' + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end + + should 'deflate when Content-Encoding is identity' do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain', + 'Content-Encoding' => 'identity' + } + } + verify(200, 'Hello World!', 'deflate', options) + end + + should "deflate if content-type matches :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) + end + + should "deflate if content-type is included it :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain; charset=us-ascii' + }, + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(200, 'Hello World!', 'gzip', options) + end + + should "not deflate if content-type is not set but given in :include" do + options = { + 'deflater_options' => { + :include => %w(text/plain) + } + } + verify(304, 'Hello World!', { 'gzip' => nil }, options) + end + + should "not deflate if content-type do not match :include" do + options = { + 'response_headers' => { + 'Content-Type' => 'text/plain' + }, + 'deflater_options' => { + :include => %w(text/json) + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end + + should "deflate response if :if lambda evaluates to true" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| true } + } + } + verify(200, 'Hello World!', 'deflate', options) + end + + should "not deflate if :if lambda evaluates to false" do + options = { + 'deflater_options' => { + :if => lambda { |env, status, headers, body| false } + } + } + verify(200, 'Hello World!', { 'gzip' => nil }, options) + end + + should "check for Content-Length via :if" do + body = 'Hello World!' + body_len = body.length + options = { + 'response_headers' => { + 'Content-Length' => body_len.to_s + }, + 'deflater_options' => { + :if => lambda { |env, status, headers, body| + headers['Content-Length'].to_i >= body_len + } + } + } + + verify(200, body, 'gzip', options) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_directory.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_directory.rb new file mode 100644 index 000000000..d41ef67ca --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_directory.rb @@ -0,0 +1,88 @@ +require 'rack/directory' +require 'rack/lint' +require 'rack/mock' + +describe 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::Lint.new(Rack::Directory.new(DOCROOT, FILE_CATCH)) + + should "serve directory indices" do + res = Rack::MockRequest.new(Rack::Lint.new(app)). + get("/cgi/") + + res.should.be.ok + res.should =~ // + end + + should "pass 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 + + should "serve 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 + + should "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 + + should "404 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 + + should "uri escape path parts" do # #265, properly escape file names + mr = Rack::MockRequest.new(Rack::Lint.new(app)) + + res = mr.get("/cgi/test%2bdirectory") + + res.should.be.ok + res.body.should =~ %r[/cgi/test%2Bdirectory/test%2Bfile] + + res = mr.get("/cgi/test%2bdirectory/test%2bfile") + res.should.be.ok + end + + should "correctly escape script name" do + app2 = Rack::Builder.new do + map '/script-path' do + run app + end + end + + mr = Rack::MockRequest.new(Rack::Lint.new(app2)) + + res = mr.get("/script-path/cgi/test%2bdirectory") + + res.should.be.ok + res.body.should =~ %r[/script-path/cgi/test%2Bdirectory/test%2Bfile] + + res = mr.get("/script-path/cgi/test%2bdirectory/test%2bfile") + res.should.be.ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_etag.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_etag.rb new file mode 100644 index 000000000..c075d9d09 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_etag.rb @@ -0,0 +1,107 @@ +require 'rack/etag' +require 'rack/lint' +require 'rack/mock' +require 'time' + +describe Rack::ETag do + def etag(app, *args) + Rack::Lint.new Rack::ETag.new(app, *args) + end + + def request + Rack::MockRequest.env_for + end + + def sendfile_body + res = ['Hello World'] + def res.to_path ; "/tmp/hello.txt" ; end + res + end + + should "set ETag if none is set if status is 200" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\"" + end + + should "set ETag if none is set if status is 201" do + app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['ETag'].should.equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\"" + end + + should "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do + app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['Cache-Control'].should.equal 'max-age=0, private, must-revalidate' + end + + should "set Cache-Control to chosen one if none is set" do + app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = etag(app, nil, 'public').call(request) + response[1]['Cache-Control'].should.equal 'public' + end + + should "set a given Cache-Control even if digest could not be calculated" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, []] } + response = etag(app, 'no-cache').call(request) + response[1]['Cache-Control'].should.equal 'no-cache' + end + + should "not set Cache-Control if it is already set" do + app = lambda { |env| [201, {'Content-Type' => 'text/plain', 'Cache-Control' => 'public'}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['Cache-Control'].should.equal 'public' + end + + should "not set Cache-Control if directive isn't present" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } + response = etag(app, nil, nil).call(request) + response[1]['Cache-Control'].should.equal nil + end + + should "not change ETag if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['ETag'].should.equal "\"abc\"" + end + + should "not set ETag if body is empty" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, []] } + response = etag(app).call(request) + response[1]['ETag'].should.be.nil + end + + should "not set ETag if Last-Modified is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Last-Modified' => Time.now.httpdate}, ["Hello, World!"]] } + response = etag(app).call(request) + response[1]['ETag'].should.be.nil + end + + should "not set ETag if a sendfile_body is given" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, sendfile_body] } + response = etag(app).call(request) + response[1]['ETag'].should.be.nil + end + + should "not set ETag if a status is not 200 or 201" do + app = lambda { |env| [401, {'Content-Type' => 'text/plain'}, ['Access denied.']] } + response = etag(app).call(request) + response[1]['ETag'].should.be.nil + end + + should "not set ETag if no-cache is given" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Cache-Control' => 'no-cache, must-revalidate'}, ['Hello, World!']] } + response = etag(app).call(request) + response[1]['ETag'].should.be.nil + end + + should "close the original body" do + body = StringIO.new + app = lambda { |env| [200, {}, body] } + response = etag(app).call(request) + body.should.not.be.closed + response[2].close + body.should.be.closed + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_fastcgi.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_fastcgi.rb new file mode 100644 index 000000000..91ab29615 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_fastcgi.rb @@ -0,0 +1,107 @@ +begin +require File.expand_path('../testrequest', __FILE__) +require 'rack/handler/fastcgi' + +describe Rack::Handler::FastCGI do + extend TestRequest::Helpers + + @host = '127.0.0.1' + @port = 9203 + + if `which lighttpd` && !$?.success? + raise "lighttpd not found" + end + + # Keep this first. + $pid = fork { + ENV['RACK_ENV'] = 'deployment' + ENV['RUBYLIB'] = [ + File.expand_path('../../lib', __FILE__), + ENV['RUBYLIB'], + ].compact.join(':') + + Dir.chdir(File.expand_path("../cgi", __FILE__)) do + exec "lighttpd -D -f lighttpd.conf" + end + } + + should "respond" do + sleep 1 + GET("/test") + response.should.not.be.nil + end + + should "respond via rackup server" do + GET("/sample_rackup.ru") + status.should.equal 200 + end + + should "be a lighttpd" do + GET("/test.fcgi") + status.should.equal 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.equal @host + end + + should "have rack headers" do + GET("/test.fcgi") + response["rack.version"].should.equal [1,3] + response["rack.multithread"].should.be.false + response["rack.multiprocess"].should.be.true + response["rack.run_once"].should.be.false + end + + 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 + + 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 + + should "support HTTP auth" do + GET("/test.fcgi", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + should "set status" do + GET("/test.fcgi?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + # Keep this last. + should "shutdown" do + Process.kill 15, $pid + Process.wait($pid).should.equal $pid + end +end + +rescue RuntimeError + $stderr.puts "Skipping Rack::Handler::FastCGI tests (lighttpd is required). Install lighttpd and try again." +rescue LoadError + $stderr.puts "Skipping Rack::Handler::FastCGI tests (FCGI is required). `gem install fcgi` and try again." +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_file.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_file.rb new file mode 100644 index 000000000..25c31ef82 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_file.rb @@ -0,0 +1,221 @@ +require 'rack/file' +require 'rack/lint' +require 'rack/mock' + +describe Rack::File do + DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT + + def file(*args) + Rack::Lint.new Rack::File.new(*args) + end + + should "serve files" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test") + + res.should.be.ok + res.should =~ /ruby/ + end + + should "set Last-Modified header" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test") + + path = File.join(DOCROOT, "/cgi/test") + + res.should.be.ok + res["Last-Modified"].should.equal File.mtime(path).httpdate + end + + should "return 304 if file isn't modified since last serve" do + path = File.join(DOCROOT, "/cgi/test") + res = Rack::MockRequest.new(file(DOCROOT)). + get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => File.mtime(path).httpdate) + + res.status.should.equal 304 + res.body.should.be.empty + end + + should "return the file if it's modified since last serve" do + path = File.join(DOCROOT, "/cgi/test") + res = Rack::MockRequest.new(file(DOCROOT)). + get("/cgi/test", 'HTTP_IF_MODIFIED_SINCE' => (File.mtime(path) - 100).httpdate) + + res.should.be.ok + end + + should "serve files with URL encoded filenames" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%74%65%73%74") # "/cgi/test" + + res.should.be.ok + res.should =~ /ruby/ + end + + should "allow safe directory traversal" do + req = Rack::MockRequest.new(file(DOCROOT)) + + res = req.get('/cgi/../cgi/test') + res.should.be.successful + + res = req.get('.') + res.should.be.not_found + + res = req.get("test/..") + res.should.be.not_found + end + + should "not allow unsafe directory traversal" do + req = Rack::MockRequest.new(file(DOCROOT)) + + res = req.get("/../README.rdoc") + res.should.be.client_error + + res = req.get("../test/spec_file.rb") + res.should.be.client_error + + res = req.get("../README.rdoc") + res.should.be.client_error + + res.should.be.not_found + end + + should "allow files with .. in their name" do + req = Rack::MockRequest.new(file(DOCROOT)) + res = req.get("/cgi/..test") + res.should.be.not_found + + res = req.get("/cgi/test..") + res.should.be.not_found + + res = req.get("/cgi../test..") + res.should.be.not_found + end + + should "not allow unsafe directory traversal with encoded periods" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/%2E%2E/README") + + res.should.be.client_error? + res.should.be.not_found + end + + should "allow safe directory traversal with encoded periods" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/%2E%2E/cgi/test") + + res.should.be.successful + end + + should "404 if it can't find the file" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/blubb") + + res.should.be.not_found + end + + should "detect SystemCallErrors" do + res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi") + + res.should.be.not_found + end + + should "return bodies that respond to #to_path" do + env = Rack::MockRequest.env_for("/cgi/test") + status, _, 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 + + should "return correct byte range in body" do + env = Rack::MockRequest.env_for("/cgi/test") + env["HTTP_RANGE"] = "bytes=22-33" + res = Rack::MockResponse.new(*file(DOCROOT).call(env)) + + res.status.should.equal 206 + res["Content-Length"].should.equal "12" + res["Content-Range"].should.equal "bytes 22-33/193" + res.body.should.equal "-*- ruby -*-" + end + + should "return error for unsatisfiable byte range" do + env = Rack::MockRequest.env_for("/cgi/test") + env["HTTP_RANGE"] = "bytes=1234-5678" + res = Rack::MockResponse.new(*file(DOCROOT).call(env)) + + res.status.should.equal 416 + res["Content-Range"].should.equal "bytes */193" + end + + should "support custom http headers" do + env = Rack::MockRequest.env_for("/cgi/test") + status, heads, _ = file(DOCROOT, 'Cache-Control' => 'public, max-age=38', + 'Access-Control-Allow-Origin' => '*').call(env) + + status.should.equal 200 + heads['Cache-Control'].should.equal 'public, max-age=38' + heads['Access-Control-Allow-Origin'].should.equal '*' + end + + should "support not add custom http headers if none are supplied" do + env = Rack::MockRequest.env_for("/cgi/test") + status, heads, _ = file(DOCROOT).call(env) + + status.should.equal 200 + heads['Cache-Control'].should.equal nil + heads['Access-Control-Allow-Origin'].should.equal nil + end + + should "only support GET, HEAD, and OPTIONS requests" do + req = Rack::MockRequest.new(file(DOCROOT)) + + forbidden = %w[post put patch delete] + forbidden.each do |method| + res = req.send(method, "/cgi/test") + res.should.be.client_error + res.should.be.method_not_allowed + res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS) + end + + allowed = %w[get head options] + allowed.each do |method| + res = req.send(method, "/cgi/test") + res.should.be.successful + end + end + + should "set Allow correctly for OPTIONS requests" do + req = Rack::MockRequest.new(file(DOCROOT)) + res = req.options('/cgi/test') + res.should.be.successful + res.headers['Allow'].should.not.equal nil + res.headers['Allow'].split(/, */).sort.should == %w(GET HEAD OPTIONS) + end + + should "set Content-Length correctly for HEAD requests" do + req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))) + res = req.head "/cgi/test" + res.should.be.successful + res['Content-Length'].should.equal "193" + end + + should "default to a mime type of text/plain" do + req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))) + res = req.get "/cgi/test" + res.should.be.successful + res['Content-Type'].should.equal "text/plain" + end + + should "allow the default mime type to be set" do + req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, 'application/octet-stream'))) + res = req.get "/cgi/test" + res.should.be.successful + res['Content-Type'].should.equal "application/octet-stream" + end + + should "not set Content-Type if the mime type is not set" do + req = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT, nil, nil))) + res = req.get "/cgi/test" + res.should.be.successful + res['Content-Type'].should.equal nil + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_handler.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_handler.rb new file mode 100644 index 000000000..c252c1767 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_handler.rb @@ -0,0 +1,63 @@ +require 'rack/handler' + +class Rack::Handler::Lobster; end +class RockLobster; end + +describe Rack::Handler do + it "has registered default handlers" do + Rack::Handler.get('cgi').should.equal Rack::Handler::CGI + Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick + + begin + Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI + rescue LoadError + end + + begin + Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel + rescue LoadError + end + end + + should "raise LoadError if handler doesn't exist" do + lambda { + Rack::Handler.get('boom') + }.should.raise(LoadError) + + lambda { + Rack::Handler.get('Object') + }.should.raise(LoadError) + end + + should "get unregistered, but already required, handler by name" do + Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster + end + + should "register custom handler" do + Rack::Handler.register('rock_lobster', 'RockLobster') + Rack::Handler.get('rock_lobster').should.equal RockLobster + end + + should "not need registration for properly coded handlers even if not already required" do + begin + $LOAD_PATH.push File.expand_path('../unregistered_handler', __FILE__) + Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered + lambda { + Rack::Handler.get('UnRegistered') + }.should.raise LoadError + Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne + ensure + $LOAD_PATH.delete File.expand_path('../unregistered_handler', __FILE__) + end + end + + should "allow autoloaded handlers to be registered properly while being loaded" do + path = File.expand_path('../registering_handler', __FILE__) + begin + $LOAD_PATH.push path + Rack::Handler.get('registering_myself').should.equal Rack::Handler::RegisteringMyself + ensure + $LOAD_PATH.delete path + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_head.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_head.rb new file mode 100644 index 000000000..78bc6ad73 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_head.rb @@ -0,0 +1,45 @@ +require 'rack/head' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Head do + + def test_response(headers = {}) + body = StringIO.new "foo" + app = lambda do |env| + [200, {"Content-type" => "test/plain", "Content-length" => "3"}, body] + end + request = Rack::MockRequest.env_for("/", headers) + response = Rack::Lint.new(Rack::Head.new(app)).call(request) + + return response, body + end + + should "pass 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].to_enum.to_a.should.equal(["foo"]) + end + end + + should "remove 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].to_enum.to_a.should.equal([]) + end + + should "close the body when it is removed" do + resp, body = test_response("REQUEST_METHOD" => "HEAD") + resp[0].should.equal(200) + resp[1].should.equal({"Content-type" => "test/plain", "Content-length" => "3"}) + resp[2].to_enum.to_a.should.equal([]) + body.should.not.be.closed + resp[2].close + body.should.be.closed + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lint.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lint.rb new file mode 100644 index 000000000..64278c71e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lint.rb @@ -0,0 +1,550 @@ +require 'stringio' +require 'tempfile' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Lint do + def env(*args) + Rack::MockRequest.env_for("/", *args) + end + + should "pass valid request" do + lambda { + Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] + }).call(env({})) + }.should.not.raise + end + + should "notice fatal errors" do + lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError). + message.should.match(/No env given/) + end + + should "notice 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("rack.multipart.buffer_size" => 0)) + }.should.raise(Rack::Lint::LintError). + message.should.equal("rack.multipart.buffer_size must be an Integer > 0 if specified") + + lambda { + Rack::Lint.new(nil).call(env("rack.multipart.tempfile_factory" => Tempfile)) + }.should.raise(Rack::Lint::LintError). + message.should.equal("rack.multipart.tempfile_factory must respond to #call") + + lambda { + Rack::Lint.new(lambda { |env| + env['rack.multipart.tempfile_factory'].call("testfile", "text/plain") + }).call(env("rack.multipart.tempfile_factory" => lambda { |filename, content_type| Object.new })) + }.should.raise(Rack::Lint::LintError). + message.should.equal("rack.multipart.tempfile_factory return value must respond to #<<") + + 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 + + should "notice 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 + + should "notice 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 + + should "notice 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 + + should "notice 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/) + + # From RFC 7230: + # Most HTTP header field values are defined using common syntax + # components (token, quoted-string, and comment) separated by + # whitespace or specific delimiting characters. Delimiters are chosen + # from the set of US-ASCII visual characters not allowed in a token + # (DQUOTE and "(),/:;<=>?@[\]{}"). + # + # token = 1*tchar + # + # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + # / DIGIT / ALPHA + # ; any VCHAR, except delimiters + invalid_headers = 0.upto(31).map(&:chr) + %W<( ) , / : ; < = > ? @ [ \\ ] { } \x7F> + invalid_headers.each do |invalid_header| + lambda { + Rack::Lint.new(lambda { |env| + [200, {invalid_header => "text/plain"}, []] + }).call(env({})) + }.should.raise(Rack::Lint::LintError, "on invalid header: #{invalid_header}"). + message.should.equal("invalid header name: #{invalid_header}") + end + valid_headers = 0.upto(127).map(&:chr) - invalid_headers + valid_headers.each do |valid_header| + lambda { + Rack::Lint.new(lambda { |env| + [200, {valid_header => "text/plain"}, []] + }).call(env({})) + }.should.not.raise(Rack::Lint::LintError, "on valid header: #{valid_header}") + end + + 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) + + # non-Hash header responses should be allowed + lambda { + Rack::Lint.new(lambda { |env| + [200, [%w(Content-Type text/plain), %w(Content-Length 0)], []] + }).call(env({})) + }.should.not.raise(TypeError) + end + + should "notice 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, 205, 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 + + should "notice content-length errors" do + [100, 101, 204, 205, 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({}))[2].each { } + }.should.raise(Rack::Lint::LintError). + message.should.match(/Content-Length header was 1, but should be 0/) + end + + should "notice body errors" do + lambda { + body = Rack::Lint.new(lambda { |env| + [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]] + }).call(env({}))[2] + body.each { |part| } + }.should.raise(Rack::Lint::LintError). + message.should.match(/yielded non-string/) + end + + should "notice 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 + + should "notice 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 + + should "notice 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"}))[2].each { } + }.should.raise(Rack::Lint::LintError). + message.should.match(/body was given for HEAD/) + end + + should "pass 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 + +describe "Rack::Lint::InputWrapper" do + should "delegate :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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lobster.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lobster.rb new file mode 100644 index 000000000..c6ec2b062 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lobster.rb @@ -0,0 +1,58 @@ +require 'rack/lobster' +require 'rack/lint' +require 'rack/mock' + +module LobsterHelpers + def lobster + Rack::MockRequest.new Rack::Lint.new(Rack::Lobster.new) + end + + def lambda_lobster + Rack::MockRequest.new Rack::Lint.new(Rack::Lobster::LambdaLobster) + end +end + +describe Rack::Lobster::LambdaLobster do + extend LobsterHelpers + + should "be a single lambda" do + Rack::Lobster::LambdaLobster.should.be.kind_of Proc + end + + should "look like a lobster" do + res = lambda_lobster.get("/") + res.should.be.ok + res.body.should.include "(,(,,(,,,(" + res.body.should.include "?flip" + end + + should "be flippable" do + res = lambda_lobster.get("/?flip") + res.should.be.ok + res.body.should.include "(,,,(,,(,(" + end +end + +describe Rack::Lobster do + extend LobsterHelpers + + should "look like a lobster" do + res = lobster.get("/") + res.should.be.ok + res.body.should.include "(,(,,(,,,(" + res.body.should.include "?flip" + res.body.should.include "crash" + end + + should "be flippable" do + res = lobster.get("/?flip=left") + res.should.be.ok + res.body.should.include "),,,),,),)" + end + + should "provide crashing for testing purposes" do + lambda { + lobster.get("/?flip=crash") + }.should.raise + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lock.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lock.rb new file mode 100644 index 000000000..0cbb54478 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_lock.rb @@ -0,0 +1,164 @@ +require 'rack/lint' +require 'rack/lock' +require 'rack/mock' + +class Lock + attr_reader :synchronized + + def initialize + @synchronized = false + end + + def synchronize + @synchronized = true + yield + end + + def lock + @synchronized = true + end + + def unlock + @synchronized = false + end +end + +module LockHelpers + def lock_app(app, lock = Lock.new) + app = if lock + Rack::Lock.new app, lock + else + Rack::Lock.new app + end + Rack::Lint.new app + end +end + +describe Rack::Lock do + extend LockHelpers + + describe 'Proxy' do + extend LockHelpers + + should 'delegate each' do + env = Rack::MockRequest.env_for("/") + response = Class.new { + attr_accessor :close_called + def initialize; @close_called = false; end + def each; %w{ hi mom }.each { |x| yield x }; end + }.new + + app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] }) + response = app.call(env)[2] + list = [] + response.each { |x| list << x } + list.should.equal %w{ hi mom } + end + + should 'delegate to_path' do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + + res = ['Hello World'] + def res.to_path ; "/tmp/hello.txt" ; end + + app = Rack::Lock.new(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] }, lock) + body = app.call(env)[2] + + body.should.respond_to :to_path + body.to_path.should.equal "/tmp/hello.txt" + end + + should 'not delegate to_path if body does not implement it' do + env = Rack::MockRequest.env_for("/") + + res = ['Hello World'] + + app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] }) + body = app.call(env)[2] + + body.should.not.respond_to :to_path + end + end + + should 'call super on close' do + env = Rack::MockRequest.env_for("/") + response = Class.new { + attr_accessor :close_called + def initialize; @close_called = false; end + def close; @close_called = true; end + }.new + + app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] }) + app.call(env) + response.close_called.should.equal false + response.close + response.close_called.should.equal true + end + + should "not unlock until body is closed" do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + response = Object.new + app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] }, lock) + lock.synchronized.should.equal false + response = app.call(env)[2] + lock.synchronized.should.equal true + response.close + lock.synchronized.should.equal false + end + + should "return value from app" do + env = Rack::MockRequest.env_for("/") + body = [200, {"Content-Type" => "text/plain"}, %w{ hi mom }] + app = lock_app(lambda { |inner_env| body }) + + res = app.call(env) + res[0].should.equal body[0] + res[1].should.equal body[1] + res[2].to_enum.to_a.should.equal ["hi", "mom"] + end + + should "call synchronize on lock" do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, %w{ a b c }] }, lock) + lock.synchronized.should.equal false + app.call(env) + lock.synchronized.should.equal true + end + + should "unlock if the app raises" do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + app = lock_app(lambda { raise Exception }, lock) + lambda { app.call(env) }.should.raise(Exception) + lock.synchronized.should.equal false + end + + should "unlock if the app throws" do + lock = Lock.new + env = Rack::MockRequest.env_for("/") + app = lock_app(lambda {|_| throw :bacon }, lock) + lambda { app.call(env) }.should.throw(:bacon) + lock.synchronized.should.equal false + end + + should "set multithread flag to false" do + app = lock_app(lambda { |env| + env['rack.multithread'].should.equal false + [200, {"Content-Type" => "text/plain"}, %w{ a b c }] + }, false) + app.call(Rack::MockRequest.env_for("/")) + end + + should "reset original multithread flag when exiting lock" do + app = Class.new(Rack::Lock) { + def call(env) + env['rack.multithread'].should.equal true + super + end + }.new(lambda { |env| [200, {"Content-Type" => "text/plain"}, %w{ a b c }] }) + Rack::Lint.new(app).call(Rack::MockRequest.env_for("/")) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_logger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_logger.rb new file mode 100644 index 000000000..2ff44080a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_logger.rb @@ -0,0 +1,23 @@ +require 'stringio' +require 'rack/lint' +require 'rack/logger' +require 'rack/mock' + +describe Rack::Logger 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!"]] + } + + should "conform to Rack::Lint" do + errors = StringIO.new + a = Rack::Lint.new(Rack::Logger.new(app)) + Rack::MockRequest.new(a).get('/', 'rack.errors' => errors) + errors.string.should.match(/INFO -- : Program started/) + errors.string.should.match(/WARN -- : Nothing to do/) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_methodoverride.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_methodoverride.rb new file mode 100644 index 000000000..c62fcdc5a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_methodoverride.rb @@ -0,0 +1,82 @@ +require 'stringio' +require 'rack/methodoverride' +require 'rack/mock' + +describe Rack::MethodOverride do + def app + Rack::Lint.new(Rack::MethodOverride.new(lambda {|e| + [200, {"Content-Type" => "text/plain"}, []] + })) + end + + should "not affect GET requests" do + env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET") + app.call env + + env["REQUEST_METHOD"].should.equal "GET" + end + + should "modify REQUEST_METHOD for POST requests when _method parameter is set" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put") + app.call env + + env["REQUEST_METHOD"].should.equal "PUT" + end + + should "modify REQUEST_METHOD for POST requests when X-HTTP-Method-Override is set" do + env = Rack::MockRequest.env_for("/", + :method => "POST", + "HTTP_X_HTTP_METHOD_OVERRIDE" => "PATCH" + ) + app.call env + + env["REQUEST_METHOD"].should.equal "PATCH" + end + + should "not modify REQUEST_METHOD if the method is unknown" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo") + app.call env + + env["REQUEST_METHOD"].should.equal "POST" + end + + should "not modify REQUEST_METHOD when _method is nil" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar") + app.call env + + env["REQUEST_METHOD"].should.equal "POST" + end + + should "store the original REQUEST_METHOD prior to overriding" do + env = Rack::MockRequest.env_for("/", + :method => "POST", + :input => "_method=options") + app.call env + + env["rack.methodoverride.original_method"].should.equal "POST" + end + + should "not modify REQUEST_METHOD when given invalid multipart form data" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size.to_s, + :method => "POST", :input => input) + begin + app.call env + rescue EOFError + end + + env["REQUEST_METHOD"].should.equal "POST" + end + + should "not modify REQUEST_METHOD for POST requests when the params are unparseable" do + env = Rack::MockRequest.env_for("/", :method => "POST", :input => "(%bad-params%)") + app.call env + + env["REQUEST_METHOD"].should.equal "POST" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mime.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mime.rb new file mode 100644 index 000000000..231bf35b7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mime.rb @@ -0,0 +1,51 @@ +require 'rack/mime' + +describe Rack::Mime do + + it "should return the fallback mime-type for files with no extension" do + fallback = 'image/jpg' + Rack::Mime.mime_type(File.extname('no_ext'), fallback).should.equal fallback + end + + it "should always return 'application/octet-stream' for unknown file extensions" do + unknown_ext = File.extname('unknown_ext.abcdefg') + Rack::Mime.mime_type(unknown_ext).should.equal 'application/octet-stream' + end + + it "should return the mime-type for a given extension" do + # sanity check. it would be infeasible test every single mime-type. + Rack::Mime.mime_type(File.extname('image.jpg')).should.equal 'image/jpeg' + end + + it "should support null fallbacks" do + Rack::Mime.mime_type('.nothing', nil).should.equal nil + end + + it "should match exact mimes" do + Rack::Mime.match?('text/html', 'text/html').should.equal true + Rack::Mime.match?('text/html', 'text/meme').should.equal false + Rack::Mime.match?('text', 'text').should.equal true + Rack::Mime.match?('text', 'binary').should.equal false + end + + it "should match class wildcard mimes" do + Rack::Mime.match?('text/html', 'text/*').should.equal true + Rack::Mime.match?('text/plain', 'text/*').should.equal true + Rack::Mime.match?('application/json', 'text/*').should.equal false + Rack::Mime.match?('text/html', 'text').should.equal true + end + + it "should match full wildcards" do + Rack::Mime.match?('text/html', '*').should.equal true + Rack::Mime.match?('text/plain', '*').should.equal true + Rack::Mime.match?('text/html', '*/*').should.equal true + Rack::Mime.match?('text/plain', '*/*').should.equal true + end + + it "should match type wildcard mimes" do + Rack::Mime.match?('text/html', '*/html').should.equal true + Rack::Mime.match?('text/plain', '*/plain').should.equal true + end + +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mock.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mock.rb new file mode 100644 index 000000000..3ebd7776a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mock.rb @@ -0,0 +1,277 @@ +require 'yaml' +require 'rack/lint' +require 'rack/mock' +require 'stringio' + +app = Rack::Lint.new(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 + + body = req.head? ? "" : env.to_yaml + Rack::Response.new(body, + req.GET["status"] || 200, + "Content-Type" => "text/yaml").finish +}) + +describe Rack::MockRequest do + should "return a MockResponse" do + res = Rack::MockRequest.new(app).get("") + res.should.be.kind_of Rack::MockResponse + end + + 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 + + should "return an environment with a path" do + env = Rack::MockRequest.env_for("http://www.example.com/parse?location[]=1&location[]=2&age_group[]=2") + env["QUERY_STRING"].should.equal "location[]=1&location[]=2&age_group[]=2" + env["PATH_INFO"].should.equal "/parse" + env.should.be.kind_of Hash + env.should.include "rack.version" + end + + 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 + + should "allow GET/POST/PUT/DELETE/HEAD" 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).patch("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "PATCH" + + res = Rack::MockRequest.new(app).delete("", :input => "foo") + env = YAML.load(res.body) + env["REQUEST_METHOD"].should.equal "DELETE" + + Rack::MockRequest.env_for("/", :method => "HEAD")["REQUEST_METHOD"]. + should.equal "HEAD" + + Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"]. + should.equal "OPTIONS" + end + + should "set content length" do + env = Rack::MockRequest.env_for("/", :input => "foo") + env["CONTENT_LENGTH"].should.equal "3" + end + + 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 + + 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 + + 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 + + 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 + + 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 + + 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.include "baz=2" + env["QUERY_STRING"].should.include "foo[bar]=1" + env["PATH_INFO"].should.equal "/foo" + env["mock.postdata"].should.equal "" + end + + 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.include "baz=2" + env["QUERY_STRING"].should.include "foo[bar]=1" + env["PATH_INFO"].should.equal "/foo" + env["mock.postdata"].should.equal "" + end + + 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 + + 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 + + should "accept params and build multipart encoded params for POST requests" do + files = Rack::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" + # The gsub accounts for differences in YAMLs affect on the data. + env["mock.postdata"].gsub("\r", "").length.should.equal 206 + end + + should "behave valid according to the Rack spec" do + lambda { + Rack::MockRequest.new(app). + get("https://bla.example.org:9292/meh/foo?bar", :lint => true) + }.should.not.raise(Rack::Lint::LintError) + end + + should "call close on the original body object" do + called = false + body = Rack::BodyProxy.new(['hi']) { called = true } + capp = proc { |e| [200, {'Content-Type' => 'text/plain'}, body] } + called.should.equal false + Rack::MockRequest.new(capp).get('/', :lint => true) + called.should.equal true + end +end + +describe Rack::MockResponse do + 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 + + 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.not.equal 0 + res.location.should.be.nil + end + + 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 + + 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 + + should "allow calling body.close afterwards" do + # this is exactly what rack-test does + body = StringIO.new("hi") + res = Rack::MockResponse.new(200, {}, body) + body.close if body.respond_to?(:close) + res.body.should == 'hi' + end + + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mongrel.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mongrel.rb new file mode 100644 index 000000000..6399ad0b1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_mongrel.rb @@ -0,0 +1,182 @@ +begin +require 'rack' +require 'rack/handler/mongrel' +require File.expand_path('../testrequest', __FILE__) +require 'timeout' + +Thread.abort_on_exception = true +$tcp_defer_accept_opts = nil +$tcp_cork_opts = nil + +describe Rack::Handler::Mongrel do + extend TestRequest::Helpers + + @server = Mongrel::HttpServer.new(@host='127.0.0.1', @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 + + should "respond" do + lambda { + GET("/test") + }.should.not.raise + end + + should "be a Mongrel" do + GET("/test") + status.should.equal 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 "127.0.0.1" + end + + should "have rack headers" do + GET("/test") + response["rack.version"].should.equal [1,3] + response["rack.multithread"].should.be.true + response["rack.multiprocess"].should.be.false + response["rack.run_once"].should.be.false + end + + 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 + + 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 + + should "support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + should "set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + should "provide a .run" do + block_ran = false + Thread.new { + Rack::Handler::Mongrel.run(lambda {}, {:Host => '127.0.0.1', :Port => 9211}) { |server| + server.should.be.kind_of Mongrel::HttpServer + block_ran = true + } + } + sleep 1 + block_ran.should.be.true + end + + 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, :Host => '127.0.0.1', :Port => 9221) { |server| + server.should.be.kind_of Mongrel::HttpServer + server.classifier.uris.size.should.equal 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 + + 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, :Host => '127.0.0.1', :Port => 9231}) { |server| + server.should.be.kind_of Mongrel::HttpServer + server.classifier.uris.size.should.equal 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 + + 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://127.0.0.1/' => lambda{}, + 'http://127.0.0.1/bar' => lambda{}, + 'http://falsehost/arf' => lambda{}, + 'http://falsehost/qux' => lambda{} + }) + opt = {:map => true, :Port => 9241, :Host => '127.0.0.1'} + 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.equal 2 + server.classifier.uris.should.include '/foo' + server.classifier.handler_map['/foo'].size.should.equal 1 + server.classifier.uris.should.include '/bar' + server.classifier.handler_map['/bar'].size.should.equal 2 + server.classifier.uris.should.not.include '/qux' + server.classifier.uris.should.not.include '/arf' + server.classifier.uris.size.should.equal 3 + block_ran = true + } + } + sleep 1 + block_ran.should.be.true + end + + 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 + + @acc.raise Mongrel::StopServer +end + +rescue LoadError + warn "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again." +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_multipart.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_multipart.rb new file mode 100644 index 000000000..74578d7b5 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_multipart.rb @@ -0,0 +1,590 @@ +require 'rack/utils' +require 'rack/mock' + +describe Rack::Multipart do + def multipart_fixture(name, boundary = "AaB03x") + file = multipart_file(name) + data = File.open(file, 'rb') { |io| io.read } + + type = "multipart/form-data; boundary=#{boundary}" + 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 + + should "return nil if content type is not multipart" do + env = Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => 'application/x-www-form-urlencoded') + Rack::Multipart.parse_multipart(env).should.equal nil + end + + should "parse multipart content when content type present but filename is not" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename)) + params = Rack::Multipart.parse_multipart(env) + params["text"].should.equal "contents" + end + + if "<3".respond_to?(:force_encoding) + should "set US_ASCII encoding based on charset" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename)) + params = Rack::Multipart.parse_multipart(env) + params["text"].encoding.should.equal Encoding::US_ASCII + + # I'm not 100% sure if making the param name encoding match the + # Content-Type charset is the right thing to do. We should revisit this. + params.keys.each do |key| + key.encoding.should.equal Encoding::US_ASCII + end + end + + should "set BINARY encoding on things without content type" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) + params = Rack::Multipart.parse_multipart(env) + params["submit-name"].encoding.should.equal Encoding::UTF_8 + end + + should "set UTF8 encoding on names of things without content type" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) + params = Rack::Multipart.parse_multipart(env) + params.keys.each do |key| + key.encoding.should.equal Encoding::UTF_8 + end + end + + should "default text to UTF8" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) + params = Rack::Multipart.parse_multipart(env) + params['submit-name'].encoding.should.equal Encoding::UTF_8 + params['submit-name-with-content'].encoding.should.equal Encoding::UTF_8 + params.keys.each do |key| + key.encoding.should.equal Encoding::UTF_8 + end + end + end + + should "raise RangeError if the key space is exhausted" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename)) + + old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1 + begin + lambda { Rack::Multipart.parse_multipart(env) }.should.raise(RangeError) + ensure + Rack::Utils.key_space_limit = old + end + end + + should "parse multipart form webkit style" do + env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit) + env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR" + params = Rack::Multipart.parse_multipart(env) + params['profile']['bio'].should.include 'hello' + end + + should "reject insanely long boundaries" do + # using a pipe since a tempfile can use up too much space + rd, wr = IO.pipe + + # we only call rewind once at start, so make sure it succeeds + # and doesn't hit ESPIPE + def rd.rewind; end + wr.sync = true + + # mock out length to make this pipe look like a Tempfile + def rd.length + 1024 * 1024 * 8 + end + + # write to a pipe in a background thread, this will write a lot + # unless Rack (properly) shuts down the read end + thr = Thread.new do + begin + wr.write("--AaB03x") + + # make the initial boundary a few gigs long + longer = "0123456789" * 1024 * 1024 + (1024 * 1024).times { wr.write(longer) } + + wr.write("\r\n") + wr.write('Content-Disposition: form-data; name="a"; filename="a.txt"') + wr.write("\r\n") + wr.write("Content-Type: text/plain\r\n") + wr.write("\r\na") + wr.write("--AaB03x--\r\n") + wr.close + rescue => err # this is EPIPE if Rack shuts us down + err + end + end + + fixture = { + "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", + "CONTENT_LENGTH" => rd.length.to_s, + :input => rd, + } + + env = Rack::MockRequest.env_for '/', fixture + lambda { + Rack::Multipart.parse_multipart(env) + }.should.raise(EOFError) + rd.close + + err = thr.value + err.should.be.instance_of Errno::EPIPE + wr.close + end + + should "parse multipart upload with text file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) + params = Rack::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["submit-name-with-content"].should.equal "Berry" + 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 + + should "preserve extension in the created tempfile" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) + params = Rack::Multipart.parse_multipart(env) + File.extname(params["files"][:tempfile].path).should.equal ".txt" + end + + should "parse multipart upload with text file with no name field" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_no_name)) + params = Rack::Multipart.parse_multipart(env) + params["file1.txt"][:type].should.equal "text/plain" + params["file1.txt"][:filename].should.equal "file1.txt" + params["file1.txt"][:head].should.equal "Content-Disposition: form-data; " + + "filename=\"file1.txt\"\r\n" + + "Content-Type: text/plain\r\n" + params["file1.txt"][:name].should.equal "file1.txt" + params["file1.txt"][:tempfile].read.should.equal "contents" + end + + should "parse multipart upload file using custom tempfile class" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) + my_tempfile = "" + env['rack.multipart.tempfile_factory'] = lambda { |filename, content_type| my_tempfile } + params = Rack::Multipart.parse_multipart(env) + params["files"][:tempfile].object_id.should.equal my_tempfile.object_id + my_tempfile.should.equal "contents" + end + + should "parse multipart upload with nested parameters" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:nested)) + params = Rack::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 + + should "parse multipart upload with binary file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:binary)) + params = Rack::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 + + should "parse multipart upload with empty file" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) + params = Rack::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 + + should "parse multipart upload with filename with semicolons" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon)) + params = Rack::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 + + should "parse multipart upload with filename with invalid characters" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:invalid_character)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "text/plain" + params["files"][:filename].should.match(/invalid/) + head = "Content-Disposition: form-data; " + + "name=\"files\"; filename=\"invalid\xC3.txt\"\r\n" + + "Content-Type: text/plain\r\n" + head = head.force_encoding("ASCII-8BIT") if head.respond_to?(:force_encoding) + params["files"][:head].should.equal head + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "not include file params if no file was selected" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) + params = Rack::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"].should.equal nil + params.keys.should.not.include "files" + end + + should "parse multipart/mixed" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["foo"].should.equal "bar" + params["files"].should.be.instance_of String + params["files"].size.should.equal 252 + end + + should "parse multipart/mixed" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:mixed_files)) + params = Rack::Utils::Multipart.parse_multipart(env) + params["foo"].should.equal "bar" + params["files"].should.be.instance_of String + params["files"].size.should.equal 252 + end + + should "parse IE multipart upload and clean up filename" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:ie)) + params = Rack::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 + + should "parse filename and modification param" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "image/jpeg" + params["files"][:filename].should.equal "genome.jpeg" + params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" + + "Content-Disposition: attachment; " + + "name=\"files\"; " + + "filename=genome.jpeg; " + + "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" + + "Content-Description: a complete map of the human genome\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "parse filename with escaped quotes" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "application/octet-stream" + params["files"][:filename].should.equal "escape \"quotes" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; " + + "filename=\"escape \\\"quotes\"\r\n" + + "Content-Type: application/octet-stream\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "parse filename with percent escaped quotes" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "application/octet-stream" + params["files"][:filename].should.equal "escape \"quotes" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; " + + "filename=\"escape %22quotes\"\r\n" + + "Content-Type: application/octet-stream\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "parse filename with unescaped quotes" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "application/octet-stream" + params["files"][:filename].should.equal "escape \"quotes" + params["files"][:head].should.equal "Content-Disposition: form-data; " + + "name=\"files\"; " + + "filename=\"escape \"quotes\"\r\n" + + "Content-Type: application/octet-stream\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "parse filename with escaped quotes and modification param" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param)) + params = Rack::Multipart.parse_multipart(env) + params["files"][:type].should.equal "image/jpeg" + params["files"][:filename].should.equal "\"human\" genome.jpeg" + params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" + + "Content-Disposition: attachment; " + + "name=\"files\"; " + + "filename=\"\"human\" genome.jpeg\"; " + + "modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" + + "Content-Description: a complete map of the human genome\r\n" + params["files"][:name].should.equal "files" + params["files"][:tempfile].read.should.equal "contents" + end + + should "parse filename with unescaped percentage characters" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) + params = Rack::Multipart.parse_multipart(env) + files = params["document"]["attachment"] + files[:type].should.equal "image/jpeg" + files[:filename].should.equal "100% of a photo.jpeg" + files[:head].should.equal <<-MULTIPART +Content-Disposition: form-data; name="document[attachment]"; filename="100% of a photo.jpeg"\r +Content-Type: image/jpeg\r + MULTIPART + + files[:name].should.equal "document[attachment]" + files[:tempfile].read.should.equal "contents" + end + + should "parse filename with unescaped percentage characters that look like partial hex escapes" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages2, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) + params = Rack::Multipart.parse_multipart(env) + files = params["document"]["attachment"] + files[:type].should.equal "image/jpeg" + files[:filename].should.equal "100%a" + files[:head].should.equal <<-MULTIPART +Content-Disposition: form-data; name="document[attachment]"; filename="100%a"\r +Content-Type: image/jpeg\r + MULTIPART + + files[:name].should.equal "document[attachment]" + files[:tempfile].read.should.equal "contents" + end + + should "parse filename with unescaped percentage characters that look like partial hex escapes" do + env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_percentages3, "----WebKitFormBoundary2NHc7OhsgU68l3Al")) + params = Rack::Multipart.parse_multipart(env) + files = params["document"]["attachment"] + files[:type].should.equal "image/jpeg" + files[:filename].should.equal "100%" + files[:head].should.equal <<-MULTIPART +Content-Disposition: form-data; name="document[attachment]"; filename="100%"\r +Content-Type: image/jpeg\r + MULTIPART + + files[:name].should.equal "document[attachment]" + files[:tempfile].read.should.equal "contents" + end + + it "rewinds input after parsing upload" do + options = multipart_fixture(:text) + input = options[:input] + env = Rack::MockRequest.env_for("/", options) + params = Rack::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["files"][:filename].should.equal "file1.txt" + input.read.length.should.equal 307 + end + + it "builds multipart body" do + files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")) + data = Rack::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::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 + + it "builds nested multipart body" do + files = Rack::Multipart::UploadedFile.new(multipart_file("file1.txt")) + data = Rack::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::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 + + it "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 + + it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do + begin + previous_limit = Rack::Utils.multipart_part_limit + Rack::Utils.multipart_part_limit = 256 + + data = File.open(multipart_file("fail_16384_nofile"), 'rb') { |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::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" + ensure + Rack::Utils.multipart_part_limit = previous_limit + end + end + + should "not reach a multi-part limit" do + begin + previous_limit = Rack::Utils.multipart_part_limit + Rack::Utils.multipart_part_limit = 4 + + env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) + params = Rack::Multipart.parse_multipart(env) + params['reply'].should.equal 'yes' + params['to'].should.equal 'people' + params['from'].should.equal 'others' + ensure + Rack::Utils.multipart_part_limit = previous_limit + end + end + + should "reach a multipart limit" do + begin + previous_limit = Rack::Utils.multipart_part_limit + Rack::Utils.multipart_part_limit = 3 + + env = Rack::MockRequest.env_for '/', multipart_fixture(:three_files_three_fields) + lambda { Rack::Multipart.parse_multipart(env) }.should.raise(Rack::Multipart::MultipartPartLimitError) + ensure + Rack::Utils.multipart_part_limit = previous_limit + end + end + + should "return nil if no UploadedFiles were used" do + data = Rack::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}]) + data.should.equal nil + end + + should "raise ArgumentError if params is not a Hash" do + lambda { Rack::Multipart.build_multipart("foo=bar") }. + should.raise(ArgumentError). + message.should.equal "value must be a Hash" + end + + it "can parse fields with a content type" do + data = <<-EOF +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon\r +Content-Disposition: form-data; name="description"\r +Content-Type: text/plain"\r +\r +Very very blue\r +--1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon--\r +EOF + options = { + "CONTENT_TYPE" => "multipart/form-data; boundary=1yy3laWhgX31qpiHinh67wJXqKalukEUTvqTzmon", + "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.equal({"description"=>"Very very blue"}) + end + + should "parse multipart upload with no content-length header" do + env = Rack::MockRequest.env_for '/', multipart_fixture(:webkit) + env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR" + env.delete 'CONTENT_LENGTH' + params = Rack::Multipart.parse_multipart(env) + params['profile']['bio'].should.include 'hello' + end + + should "parse very long unquoted multipart file names" do + data = <<-EOF +--AaB03x\r +Content-Type: text/plain\r +Content-Disposition: attachment; name=file; filename=#{'long' * 100}\r +\r +contents\r +--AaB03x--\r + EOF + + 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["file"][:filename].should.equal('long' * 100) + end + + should "support mixed case metadata" do + file = multipart_file(:text) + data = File.open(file, 'rb') { |io| io.read } + + type = "Multipart/Form-Data; Boundary=AaB03x" + length = data.respond_to?(:bytesize) ? data.bytesize : data.size + + e = { "CONTENT_TYPE" => type, + "CONTENT_LENGTH" => length.to_s, + :input => StringIO.new(data) } + + env = Rack::MockRequest.env_for("/", e) + params = Rack::Multipart.parse_multipart(env) + params["submit-name"].should.equal "Larry" + params["submit-name-with-content"].should.equal "Berry" + 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 + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_nulllogger.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_nulllogger.rb new file mode 100644 index 000000000..88ba52168 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_nulllogger.rb @@ -0,0 +1,20 @@ +require 'rack/lint' +require 'rack/mock' +require 'rack/nulllogger' + +describe Rack::NullLogger do + should "act as a noop logger" do + app = lambda { |env| + env['rack.logger'].warn "b00m" + [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] + } + + logger = Rack::Lint.new(Rack::NullLogger.new(app)) + + res = logger.call(Rack::MockRequest.env_for) + res[0..1].should.equal [ + 200, {'Content-Type' => 'text/plain'} + ] + res[2].to_enum.to_a.should.equal ["Hello, World!"] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_recursive.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_recursive.rb new file mode 100644 index 000000000..764c44cea --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_recursive.rb @@ -0,0 +1,72 @@ +require 'rack/lint' +require 'rack/recursive' +require 'rack/mock' + +describe Rack::Recursive 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 |inner_res| + inner_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") + } + + def recursive(map) + Rack::Lint.new Rack::Recursive.new(Rack::URLMap.new(map)) + end + + should "allow for subrequests" do + res = Rack::MockRequest.new(recursive("/app1" => @app1, + "/app2" => @app2)). + get("/app2") + + res.should.be.ok + res.body.should.equal "App2App1" + end + + should "raise error on requests not below the app" do + app = Rack::URLMap.new("/app1" => @app1, + "/app" => recursive("/1" => @app1, + "/2" => @app2)) + + lambda { + Rack::MockRequest.new(app).get("/app/2") + }.should.raise(ArgumentError). + message.should =~ /can only include below/ + end + + should "support forwarding" do + app = recursive("/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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_request.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_request.rb new file mode 100644 index 000000000..a44e0a71a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_request.rb @@ -0,0 +1,1227 @@ +require 'stringio' +require 'cgi' +require 'rack/request' +require 'rack/mock' +require 'securerandom' + +describe Rack::Request do + should "wrap 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.should.not.be.patch + + 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 + + should "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 + + should "figure out the correct port" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") + req.port.should.equal 80 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org:81") + req.port.should.equal 81 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") + req.port.should.equal 9292 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") + req.port.should.equal 9292 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org") + req.port.should.equal 80 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_SSL" => "on") + req.port.should.equal 443 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PROTO" => "https") + req.port.should.equal 443 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "HTTP_X_FORWARDED_PORT" => "9393") + req.port.should.equal 9393 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9393", "SERVER_PORT" => "80") + req.port.should.equal 9393 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") + req.port.should.equal 80 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https", "SERVER_PORT" => "80") + req.port.should.equal 443 + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost", "HTTP_X_FORWARDED_PROTO" => "https,https", "SERVER_PORT" => "80") + req.port.should.equal 443 + end + + should "figure out the correct host with port" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") + req.host_with_port.should.equal "www2.example.org" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81") + req.host_with_port.should.equal "localhost:81" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org", "SERVER_PORT" => "9292") + req.host_with_port.should.equal "example.org:9292" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org:9292") + req.host_with_port.should.equal "example.org:9292" + + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "HTTP_HOST" => "localhost:81", "HTTP_X_FORWARDED_HOST" => "example.org", "SERVER_PORT" => "9393") + req.host_with_port.should.equal "example.org" + end + + should "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 + + should "not truncate query strings containing semi-colons #543 only in POST" do + mr = Rack::MockRequest.env_for("/", + "REQUEST_METHOD" => 'POST', + :input => "foo=bar&quux=b;la") + req = Rack::Request.new mr + req.query_string.should.equal "" + req.GET.should.be.empty + req.POST.should.equal "foo" => "bar", "quux" => "b;la" + req.params.should.equal req.GET.merge(req.POST) + end + + should "use semi-colons as separators for query strings in GET" do + req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=b;la;wun=duh")) + req.query_string.should.equal "foo=bar&quux=b;la;wun=duh" + req.GET.should.equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh" + req.POST.should.be.empty + req.params.should.equal "foo" => "bar", "quux" => "b", "la" => nil, "wun" => "duh" + end + + should "limit the keys from the GET query string" do + env = Rack::MockRequest.env_for("/?foo=bar") + + old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1 + begin + req = Rack::Request.new(env) + lambda { req.GET }.should.raise(RangeError) + ensure + Rack::Utils.key_space_limit = old + end + end + + should "limit the key size per nested params hash" do + nested_query = Rack::MockRequest.env_for("/?foo%5Bbar%5D%5Bbaz%5D%5Bqux%5D=1") + plain_query = Rack::MockRequest.env_for("/?foo_bar__baz__qux_=1") + + old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 3 + begin + lambda { Rack::Request.new(nested_query).GET }.should.not.raise(RangeError) + lambda { Rack::Request.new(plain_query).GET }.should.raise(RangeError) + ensure + Rack::Utils.key_space_limit = old + end + end + + should "not unify GET and POST when calling params" do + mr = Rack::MockRequest.env_for("/?foo=quux", + "REQUEST_METHOD" => 'POST', + :input => "foo=bar&quux=bla" + ) + req = Rack::Request.new mr + + req.params + + req.GET.should.equal "foo" => "quux" + req.POST.should.equal "foo" => "bar", "quux" => "bla" + req.params.should.equal req.GET.merge(req.POST) + end + + should "raise if input params has invalid %-encoding" do + mr = Rack::MockRequest.env_for("/?foo=quux", + "REQUEST_METHOD" => 'POST', + :input => "a%=1" + ) + req = Rack::Request.new mr + + lambda { req.POST }. + should.raise(Rack::Utils::InvalidParameterError). + message.should.equal "invalid %-encoding (a%)" + end + + should "raise if rack.input is missing" do + req = Rack::Request.new({}) + lambda { req.POST }.should.raise(RuntimeError) + end + + should "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 + + should "limit the keys from the POST form data" do + env = Rack::MockRequest.env_for("", + "REQUEST_METHOD" => 'POST', + :input => "foo=bar&quux=bla") + + old, Rack::Utils.key_space_limit = Rack::Utils.key_space_limit, 1 + begin + req = Rack::Request.new(env) + lambda { req.POST }.should.raise(RangeError) + ensure + Rack::Utils.key_space_limit = old + end + end + + should "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 + + should "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 + + should "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 + + should "rewind 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 + + should "clean 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 + + should "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 + + should "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 + + should "return values for the keys in the order given from values_at" 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 + + should "extract referrer correctly" 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 nil + end + + should "extract user agent correctly" 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 + + should "treat missing content type as nil" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/") + req.content_type.should.equal nil + end + + should "treat empty content type as nil" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") + req.content_type.should.equal nil + end + + should "return nil media type for empty content type" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("/", "CONTENT_TYPE" => "") + req.media_type.should.equal nil + end + + should "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 + + should "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 + + should "ssl detection" do + request = Rack::Request.new(Rack::MockRequest.env_for("/")) + request.scheme.should.equal "http" + request.should.not.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTPS' => 'on')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'rack.url_scheme' => 'https')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8080')) + request.scheme.should.equal "http" + request.should.not.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_HOST' => 'www.example.org:8443', 'HTTP_X_FORWARDED_SSL' => 'on')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_SCHEME' => 'https')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https')) + request.scheme.should.equal "https" + request.should.be.ssl? + + request = Rack::Request.new(Rack::MockRequest.env_for("/", 'HTTP_X_FORWARDED_PROTO' => 'https, http, http')) + request.scheme.should.equal "https" + request.should.be.ssl? + end + + should "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 + + should "always return the same hash object" do + req = Rack::Request.new \ + Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") + hash = req.cookies + req.env.delete("HTTP_COOKIE") + req.cookies.should.equal(hash) + req.env["HTTP_COOKIE"] = "zoo=m" + req.cookies.should.equal(hash) + end + + should "modify the cookies hash in place" do + req = Rack::Request.new(Rack::MockRequest.env_for("")) + req.cookies.should.equal({}) + req.cookies['foo'] = 'bar' + req.cookies.should.equal 'foo' => 'bar' + end + + should "not modify the params hash in place" do + e = Rack::MockRequest.env_for("") + req1 = Rack::Request.new(e) + req1.params.should.equal({}) + req1.params['foo'] = 'bar' + req1.params.should.equal 'foo' => 'bar' + req2 = Rack::Request.new(e) + req2.params.should.equal({}) + end + + should "modify params hash if param is in GET" do + e = Rack::MockRequest.env_for("?foo=duh") + req1 = Rack::Request.new(e) + req1.params.should.equal 'foo' => 'duh' + req1.update_param 'foo', 'bar' + req1.params.should.equal 'foo' => 'bar' + req2 = Rack::Request.new(e) + req2.params.should.equal 'foo' => 'bar' + end + + should "modify params hash if param is in POST" do + e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=duh') + req1 = Rack::Request.new(e) + req1.params.should.equal 'foo' => 'duh' + req1.update_param 'foo', 'bar' + req1.params.should.equal 'foo' => 'bar' + req2 = Rack::Request.new(e) + req2.params.should.equal 'foo' => 'bar' + end + + should "modify params hash, even if param didn't exist before" do + e = Rack::MockRequest.env_for("") + req1 = Rack::Request.new(e) + req1.params.should.equal({}) + req1.update_param 'foo', 'bar' + req1.params.should.equal 'foo' => 'bar' + req2 = Rack::Request.new(e) + req2.params.should.equal 'foo' => 'bar' + end + + should "modify params hash by changing only GET" do + e = Rack::MockRequest.env_for("?foo=duhget") + req = Rack::Request.new(e) + req.GET.should.equal 'foo' => 'duhget' + req.POST.should.equal({}) + req.update_param 'foo', 'bar' + req.GET.should.equal 'foo' => 'bar' + req.POST.should.equal({}) + end + + should "modify params hash by changing only POST" do + e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") + req = Rack::Request.new(e) + req.GET.should.equal({}) + req.POST.should.equal 'foo' => 'duhpost' + req.update_param 'foo', 'bar' + req.GET.should.equal({}) + req.POST.should.equal 'foo' => 'bar' + end + + should "modify params hash, even if param is defined in both POST and GET" do + e = Rack::MockRequest.env_for("?foo=duhget", "REQUEST_METHOD" => 'POST', :input => "foo=duhpost") + req1 = Rack::Request.new(e) + req1.GET.should.equal 'foo' => 'duhget' + req1.POST.should.equal 'foo' => 'duhpost' + req1.params.should.equal 'foo' => 'duhpost' + req1.update_param 'foo', 'bar' + req1.GET.should.equal 'foo' => 'bar' + req1.POST.should.equal 'foo' => 'bar' + req1.params.should.equal 'foo' => 'bar' + req2 = Rack::Request.new(e) + req2.GET.should.equal 'foo' => 'bar' + req2.POST.should.equal 'foo' => 'bar' + req2.params.should.equal 'foo' => 'bar' + req2.params.should.equal 'foo' => 'bar' + end + + should "allow deleting from params hash if param is in GET" do + e = Rack::MockRequest.env_for("?foo=bar") + req1 = Rack::Request.new(e) + req1.params.should.equal 'foo' => 'bar' + req1.delete_param('foo').should.equal 'bar' + req1.params.should.equal({}) + req2 = Rack::Request.new(e) + req2.params.should.equal({}) + end + + should "allow deleting from params hash if param is in POST" do + e = Rack::MockRequest.env_for("", "REQUEST_METHOD" => 'POST', :input => 'foo=bar') + req1 = Rack::Request.new(e) + req1.params.should.equal 'foo' => 'bar' + req1.delete_param('foo').should.equal 'bar' + req1.params.should.equal({}) + req2 = Rack::Request.new(e) + req2.params.should.equal({}) + end + + should "pass through non-uri escaped cookies as-is" do + req = Rack::Request.new Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=%") + req.cookies["foo"].should == "%" + end + + should "parse 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 + + should 'parse cookies with quotes' do + req = Rack::Request.new Rack::MockRequest.env_for('', { + 'HTTP_COOKIE' => '$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme"' + }) + req.cookies.should.equal({ + '$Version' => '"1"', + 'Customer' => '"WILE_E_COYOTE"', + '$Path' => '"/acme"', + 'Part_Number' => '"Rocket_Launcher_0001"', + }) + end + + should "provide 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 + + should "provide the original env" do + req = Rack::Request.new(e = Rack::MockRequest.env_for("")) + req.env.should == e + end + + should "restore the base URL" do + Rack::Request.new(Rack::MockRequest.env_for("")).base_url. + should.equal "http://example.org" + Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).base_url. + should.equal "http://example.org" + end + + should "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("coffee://example.org/")).url. + should.equal "coffee://example.org/" + Rack::Request.new(Rack::MockRequest.env_for("coffee://example.org:443/")).url. + should.equal "coffee://example.org:443/" + Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url. + should.equal "https://example.com:8080/foo?foo" + end + + should "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 + + should "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;blong="boo";zump="zoo\"o";weird=lol"') + 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' + req.media_type_params['blong'].should.equal 'boo' + req.media_type_params['zump'].should.equal 'zoo\"o' + req.media_type_params['weird'].should.equal 'lol"' + end + + should "parse with junk before boundry" 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 + + should "not infinite loop with a malformed HTTP request" do + # Adapted from RFC 1867. + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda{req.POST}.should.raise(EOFError) + end + + + should "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 + + should "MultipartPartLimitError when request has too many multipart parts if limit set" do + begin + data = 10000.times.map { "--AaB03x\r\nContent-Type: text/plain\r\nContent-Disposition: attachment; name=#{SecureRandom.hex(10)}; filename=#{SecureRandom.hex(10)}\r\n\r\ncontents\r\n" }.join("\r\n") + data += "--AaB03x--\r" + + options = { + "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x", + "CONTENT_LENGTH" => data.length.to_s, + :input => StringIO.new(data) + } + + request = Rack::Request.new Rack::MockRequest.env_for("/", options) + lambda { request.POST }.should.raise(Rack::Multipart::MultipartPartLimitError) + end + end + + should "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 + + should "record tempfiles from multipart form data in env[rack.tempfiles]" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + req = Rack::Request.new(env) + req.params + env['rack.tempfiles'].size.should.equal(2) + end + + should "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 + + should "consistently raise EOFError on bad multipart form data" do + input = < "multipart/form-data, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + lambda { req.POST }.should.raise(EOFError) + lambda { req.POST }.should.raise(EOFError) + end + + should "correctly parse the part name from Content-Id header" do + input = <\r +Content-Transfer-Encoding: 7bit\r +\r +foo\r +--AaB03x--\r +EOF + req = Rack::Request.new Rack::MockRequest.env_for("/", + "CONTENT_TYPE" => "multipart/related, boundary=AaB03x", + "CONTENT_LENGTH" => input.size, + :input => input) + + req.params.keys.should.equal [""] + end + + should "not try to interpret binary as utf8" do + if /regexp/.respond_to?(:kcode) # < 1.9 + 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 + else # >= 1.9 + 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 + end + end + + 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.not.raise("input re-processed!") + end + + should "use form_hash when form_input is a Tempfile" do + input = "{foo: 'bar'}" + + rack_input = Tempfile.new("rackspec") + rack_input.write(input) + rack_input.rewind + + req = Rack::Request.new Rack::MockRequest.env_for("/", + "rack.request.form_hash" => {'foo' => 'bar'}, + "rack.request.form_input" => rack_input, + :input => rack_input) + + req.POST.should.equal(req.env['rack.request.form_hash']) + end + + should "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 + + 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] ]) + + parser.call("gzip ; q=0.9").should.equal([["gzip", 0.9]]) + parser.call("gzip ; deflate").should.equal([["gzip", 1.0]]) + end + + should "parse Accept-Language correctly" do + parser = lambda do |x| + Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_LANGUAGE" => x)).accept_language + end + + parser.call(nil).should.equal([]) + + parser.call("fr, en").should.equal([["fr", 1.0], ["en", 1.0]]) + parser.call("").should.equal([]) + parser.call("*").should.equal([["*", 1.0]]) + parser.call("fr;q=0.5, en;q=1.0").should.equal([["fr", 0.5], ["en", 1.0]]) + parser.call("fr;q=1.0, en; q=0.5, *;q=0").should.equal([["fr", 1.0], ["en", 0.5], ["*", 0] ]) + + parser.call("fr ; q=0.9").should.equal([["fr", 0.9]]) + parser.call("fr").should.equal([["fr", 1.0]]) + end + + ip_app = lambda { |env| + request = Rack::Request.new(env) + response = Rack::Response.new + response.write request.ip + response.finish + } + + should 'provide ip information' do + mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) + + res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4' + res.body.should.equal '1.2.3.4' + + res = mock.get '/', 'REMOTE_ADDR' => 'fe80::202:b3ff:fe1e:8329' + res.body.should.equal 'fe80::202:b3ff:fe1e:8329' + + res = mock.get '/', 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' + res.body.should.equal '1.2.3.4' + end + + should 'deals with proxies' do + mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) + + res = mock.get '/', + 'REMOTE_ADDR' => '1.2.3.4', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + res.body.should.equal '1.2.3.4' + + res = mock.get '/', + 'REMOTE_ADDR' => '1.2.3.4', + 'HTTP_X_FORWARDED_FOR' => 'unknown' + res.body.should.equal '1.2.3.4' + + res = mock.get '/', + 'REMOTE_ADDR' => '127.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '127.0.0.1, 3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' + res.body.should.equal 'unknown' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'other,unknown,192.168.0.1' + res.body.should.equal 'unknown' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'unknown,localhost,192.168.0.1' + res.body.should.equal 'unknown' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '::1,2620:0:1c00:0:812c:9583:754b:ca11' + res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,::1' + res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => 'fd5b:982e:9130:247f:0000:0000:0000:0000,2620:0:1c00:0:812c:9583:754b:ca11' + res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '2620:0:1c00:0:812c:9583:754b:ca11,fd5b:982e:9130:247f:0000:0000:0000:0000' + res.body.should.equal '2620:0:1c00:0:812c:9583:754b:ca11' + + res = mock.get '/', + 'HTTP_X_FORWARDED_FOR' => '1.1.1.1, 127.0.0.1', + 'HTTP_CLIENT_IP' => '1.1.1.1' + res.body.should.equal '1.1.1.1' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' + res.body.should.equal '9.9.9.9' + + res = mock.get '/', 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, fe80::202:b3ff:fe1e:8329' + res.body.should.equal 'fe80::202:b3ff:fe1e:8329' + + # Unix Sockets + res = mock.get '/', + 'REMOTE_ADDR' => 'unix', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + res.body.should.equal '3.4.5.6' + + res = mock.get '/', + 'REMOTE_ADDR' => 'unix:/tmp/foo', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + res.body.should.equal '3.4.5.6' + end + + should "not allow IP spoofing via Client-IP and X-Forwarded-For headers" do + mock = Rack::MockRequest.new(Rack::Lint.new(ip_app)) + + # IP Spoofing attempt: + # Client sends X-Forwarded-For: 6.6.6.6 + # Client-IP: 6.6.6.6 + # Load balancer adds X-Forwarded-For: 2.2.2.3, 192.168.0.7 + # App receives: X-Forwarded-For: 6.6.6.6 + # X-Forwarded-For: 2.2.2.3, 192.168.0.7 + # Client-IP: 6.6.6.6 + # Rack env: HTTP_X_FORWARDED_FOR: '6.6.6.6, 2.2.2.3, 192.168.0.7' + # HTTP_CLIENT_IP: '6.6.6.6' + res = mock.get '/', + 'HTTP_X_FORWARDED_FOR' => '6.6.6.6, 2.2.2.3, 192.168.0.7', + 'HTTP_CLIENT_IP' => '6.6.6.6' + res.body.should.equal '2.2.2.3' + end + + should "regard local addresses as proxies" do + req = Rack::Request.new(Rack::MockRequest.env_for("/")) + req.trusted_proxy?('127.0.0.1').should.equal 0 + req.trusted_proxy?('10.0.0.1').should.equal 0 + req.trusted_proxy?('172.16.0.1').should.equal 0 + req.trusted_proxy?('172.20.0.1').should.equal 0 + req.trusted_proxy?('172.30.0.1').should.equal 0 + req.trusted_proxy?('172.31.0.1').should.equal 0 + req.trusted_proxy?('192.168.0.1').should.equal 0 + req.trusted_proxy?('::1').should.equal 0 + req.trusted_proxy?('fd00::').should.equal 0 + req.trusted_proxy?('localhost').should.equal 0 + req.trusted_proxy?('unix').should.equal 0 + req.trusted_proxy?('unix:/tmp/sock').should.equal 0 + + req.trusted_proxy?("unix.example.org").should.equal nil + req.trusted_proxy?("example.org\n127.0.0.1").should.equal nil + req.trusted_proxy?("127.0.0.1\nexample.org").should.equal nil + req.trusted_proxy?("11.0.0.1").should.equal nil + req.trusted_proxy?("172.15.0.1").should.equal nil + req.trusted_proxy?("172.32.0.1").should.equal nil + req.trusted_proxy?("2001:470:1f0b:18f8::1").should.equal nil + end + + class MyRequest < Rack::Request + def params + {:foo => "bar"} + end + end + + 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 + + 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 + + should "raise TypeError every time if request parameters are broken" do + broken_query = Rack::MockRequest.env_for("/?foo%5B%5D=0&foo%5Bbar%5D=1") + req = Rack::Request.new(broken_query) + lambda{req.GET}.should.raise(TypeError) + lambda{req.params}.should.raise(TypeError) + end + + (0x20...0x7E).collect { |a| + b = a.chr + c = CGI.escape(b) + should "not strip '#{a}' => '#{c}' => '#{b}' escaped character from parameters when accessed as string" do + url = "/?foo=#{c}bar#{c}" + env = Rack::MockRequest.env_for(url) + req2 = Rack::Request.new(env) + req2.GET.should.equal "foo" => "#{b}bar#{b}" + req2.params.should.equal "foo" => "#{b}bar#{b}" + end + } +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_response.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_response.rb new file mode 100644 index 000000000..6b13c0c92 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_response.rb @@ -0,0 +1,343 @@ +require 'rack/response' +require 'stringio' + +describe Rack::Response do + should "have sensible default values" do + response = Rack::Response.new + status, header, body = response.finish + status.should.equal 200 + header.should.equal({}) + body.each { |part| + part.should.equal "" + } + + response = Rack::Response.new + status, header, body = *response + status.should.equal 200 + header.should.equal({}) + body.each { |part| + part.should.equal "" + } + end + + it "can be written to" do + response = Rack::Response.new + + _, _, 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 + + it "can set and read headers" do + response = Rack::Response.new + response["Content-Type"].should.equal nil + response["Content-Type"] = "text/plain" + response["Content-Type"].should.equal "text/plain" + end + + it "can override the initial Content-Type with a different case" do + response = Rack::Response.new("", 200, "content-type" => "text/plain") + response["Content-Type"].should.equal "text/plain" + end + + it "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"].join("\n") + response.set_cookie "foo3", "bar3" + response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"].join("\n") + end + + it "can set cookies with the same name for multiple domains" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"} + response.set_cookie "foo", {:value => "bar", :domain => ".example.com"} + response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n") + end + + it "formats the Cookie expiration date accordingly to RFC 6265" 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 + + it "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 + + it "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 + + it "can set http only cookies with :http_only" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :http_only => true} + response["Set-Cookie"].should.equal "foo=bar; HttpOnly" + end + + it "can set prefers :httponly for http only cookie setting when :httponly and :http_only provided" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :httponly => false, :http_only => true} + response["Set-Cookie"].should.equal "foo=bar" + end + + it "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=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000" + ].join("\n") + end + + it "can delete cookies with the same name from multiple domains" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"} + response.set_cookie "foo", {:value => "bar", :domain => ".example.com"} + response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n") + response.delete_cookie "foo", :domain => ".example.com" + response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n") + response.delete_cookie "foo", :domain => "sample.example.com" + response["Set-Cookie"].should.equal ["foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000", + "foo=; domain=sample.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n") + end + + it "can delete cookies with the same name with different paths" do + response = Rack::Response.new + response.set_cookie "foo", {:value => "bar", :path => "/"} + response.set_cookie "foo", {:value => "bar", :path => "/path"} + response["Set-Cookie"].should.equal ["foo=bar; path=/", + "foo=bar; path=/path"].join("\n") + + response.delete_cookie "foo", :path => "/path" + response["Set-Cookie"].should.equal ["foo=bar; path=/", + "foo=; path=/path; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"].join("\n") + end + + it "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 + + it "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" + + object_with_each = Object.new + def object_with_each.each + yield "foo" + yield "bar" + end + r = Rack::Response.new(object_with_each) + 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 + + it "has a constructor that can take a block" do + r = Rack::Response.new { |res| + res.status = 404 + res.write "foo" + } + status, _, body = r.finish + str = ""; body.each { |part| str << part } + str.should.equal "foo" + status.should.equal 404 + end + + it "doesn't return invalid responses" do + r = Rack::Response.new(["foo", "bar"], 204) + _, header, body = r.finish + str = ""; body.each { |part| str << part } + str.should.be.empty + header["Content-Type"].should.equal nil + header['Content-Length'].should.equal nil + + lambda { + Rack::Response.new(Object.new) + }.should.raise(TypeError). + message.should =~ /stringable or iterable required/ + end + + it "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 + + 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 = 201 + res.should.be.successful + res.should.be.created + + res.status = 202 + res.should.be.successful + res.should.be.accepted + + res.status = 400 + res.should.not.be.successful + res.should.be.client_error + res.should.be.bad_request + + res.status = 401 + res.should.not.be.successful + res.should.be.client_error + res.should.be.unauthorized + + res.status = 404 + res.should.not.be.successful + res.should.be.client_error + res.should.be.not_found + + res.status = 405 + res.should.not.be.successful + res.should.be.client_error + res.should.be.method_not_allowed + + res.status = 418 + res.should.not.be.successful + res.should.be.client_error + res.should.be.i_m_a_teapot + + res.status = 422 + res.should.not.be.successful + res.should.be.client_error + res.should.be.unprocessable + + res.status = 501 + res.should.not.be.successful + res.should.be.server_error + + res.status = 307 + res.should.be.redirect + end + + 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 + + it "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 + + it "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 + + it "calls close on #body" do + res = Rack::Response.new + res.body = StringIO.new + res.close + res.body.should.be.closed + end + + it "calls close on #body when 204, 205, or 304" do + res = Rack::Response.new + res.body = StringIO.new + res.finish + res.body.should.not.be.closed + + res.status = 204 + _, _, b = res.finish + res.body.should.be.closed + b.should.not.equal res.body + + res.body = StringIO.new + res.status = 205 + _, _, b = res.finish + res.body.should.be.closed + b.should.not.equal res.body + + res.body = StringIO.new + res.status = 304 + _, _, b = res.finish + res.body.should.be.closed + b.should.not.equal res.body + end + + it "wraps the body from #to_ary to prevent infinite loops" do + res = Rack::Response.new + res.finish.last.should.not.respond_to?(:to_ary) + lambda { res.finish.last.to_ary }.should.raise(NoMethodError) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_rewindable_input.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_rewindable_input.rb new file mode 100644 index 000000000..025d830d9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_rewindable_input.rb @@ -0,0 +1,118 @@ +require 'stringio' +require 'rack/rewindable_input' + +shared "a rewindable IO object" do + before do + @rio = Rack::RewindableInput.new(@io) + end + + should "be able to handle to read()" do + @rio.read.should.equal "hello world" + end + + should "be able to handle to read(nil)" do + @rio.read(nil).should.equal "hello world" + end + + should "be able to handle to read(length)" do + @rio.read(1).should.equal "h" + end + + 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 + + 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 + + should "rewind to the beginning when #rewind is called" do + @rio.read(1) + @rio.rewind + @rio.read.should.equal "hello world" + end + + should "be able to handle gets" do + @rio.gets.should == "hello world" + end + + should "be able to handle each" do + array = [] + @rio.each do |data| + array << data + end + array.should.equal(["hello world"]) + end + + should "not buffer into a Tempfile if no data has been read yet" do + @rio.instance_variable_get(:@rewindable_io).should.be.nil + end + + 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.path.should == tempfile.path + end + + 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 + + should "be possible to call #close when no data has been buffered yet" do + lambda{ @rio.close }.should.not.raise + end + + should "be possible to call #close multiple times" do + lambda{ + @rio.close + @rio.close + }.should.not.raise + end + + @rio.close + @rio = nil +end + +describe Rack::RewindableInput do + describe "given an IO object that is already rewindable" do + before do + @io = StringIO.new("hello world") + end + + behaves_like "a rewindable IO object" + end + + describe "given an IO object that is not rewindable" do + before do + @io = StringIO.new("hello world") + @io.instance_eval do + undef :rewind + end + end + + behaves_like "a rewindable IO object" + end + + describe "given an IO object whose rewind method raises Errno::ESPIPE" do + before do + @io = StringIO.new("hello world") + def @io.rewind + raise Errno::ESPIPE, "You can't rewind this!" + end + end + + behaves_like "a rewindable IO object" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_runtime.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_runtime.rb new file mode 100644 index 000000000..f9897a133 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_runtime.rb @@ -0,0 +1,49 @@ +require 'rack/lint' +require 'rack/mock' +require 'rack/runtime' + +describe Rack::Runtime do + def runtime_app(app, *args) + Rack::Lint.new Rack::Runtime.new(app, *args) + end + + def request + Rack::MockRequest.env_for + end + + it "sets X-Runtime is none is set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + response = runtime_app(app).call(request) + response[1]['X-Runtime'].should =~ /[\d\.]+/ + end + + it "doesn't set the X-Runtime if it is already set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] } + response = runtime_app(app).call(request) + response[1]['X-Runtime'].should == "foobar" + end + + should "allow a suffix to be set" do + app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + response = runtime_app(app, "Test").call(request) + response[1]['X-Runtime-Test'].should =~ /[\d\.]+/ + end + + should "allow multiple timers to be set" do + app = lambda { |env| sleep 0.1; [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } + runtime = runtime_app(app, "App") + + # wrap many times to guarantee a measurable difference + 100.times do |i| + runtime = Rack::Runtime.new(runtime, i.to_s) + end + runtime = Rack::Runtime.new(runtime, "All") + + response = runtime.call(request) + + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_sendfile.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_sendfile.rb new file mode 100644 index 000000000..7c9acd6fb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_sendfile.rb @@ -0,0 +1,130 @@ +require 'fileutils' +require 'rack/lint' +require 'rack/sendfile' +require 'rack/mock' +require 'tmpdir' + +describe Rack::File do + should "respond to #to_path" do + Rack::File.new(Dir.pwd).should.respond_to :to_path + end +end + +describe Rack::Sendfile do + def sendfile_body + FileUtils.touch File.join(Dir.tmpdir, "rack_sendfile") + res = ['Hello World'] + def res.to_path ; File.join(Dir.tmpdir, "rack_sendfile") ; end + res + end + + def simple_app(body=sendfile_body) + lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] } + end + + def sendfile_app(body, mappings = []) + Rack::Lint.new Rack::Sendfile.new(simple_app(body), nil, mappings) + end + + def request(headers={}, body=sendfile_body, mappings=[]) + yield Rack::MockRequest.new(sendfile_app(body, mappings)).get('/', headers) + end + + def open_file(path) + Class.new(File) do + unless method_defined?(:to_path) + alias :to_path :path + end + end.open(path, 'wb+') + end + + it "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 + + it "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['Content-Length'].should.equal '0' + response.headers['X-Sendfile'].should.equal File.join(Dir.tmpdir, "rack_sendfile") + end + end + + it "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['Content-Length'].should.equal '0' + response.headers['X-Lighttpd-Send-File'].should.equal File.join(Dir.tmpdir, "rack_sendfile") + end + end + + it "sets X-Accel-Redirect response header and discards body" do + headers = { + 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect', + 'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar/" + } + request headers do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['Content-Length'].should.equal '0' + response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile' + end + end + + it '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 + + it 'does nothing when body does not respond to #to_path' do + request({'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile'}, ['Not a file...']) do |response| + response.body.should.equal 'Not a file...' + response.headers.should.not.include 'X-Sendfile' + end + end + + it "sets X-Accel-Redirect response header and discards body when initialized with multiple mappings" do + begin + dir1 = Dir.mktmpdir + dir2 = Dir.mktmpdir + + first_body = open_file(File.join(dir1, 'rack_sendfile')) + first_body.puts 'hello world' + + second_body = open_file(File.join(dir2, 'rack_sendfile')) + second_body.puts 'goodbye world' + + mappings = [ + ["#{dir1}/", '/foo/bar/'], + ["#{dir2}/", '/wibble/'] + ] + + request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, first_body, mappings) do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['Content-Length'].should.equal '0' + response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile' + end + + request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, second_body, mappings) do |response| + response.should.be.ok + response.body.should.be.empty + response.headers['Content-Length'].should.equal '0' + response.headers['X-Accel-Redirect'].should.equal '/wibble/rack_sendfile' + end + ensure + FileUtils.remove_entry_secure dir1 + FileUtils.remove_entry_secure dir2 + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_server.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_server.rb new file mode 100644 index 000000000..b27285057 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_server.rb @@ -0,0 +1,167 @@ +require 'rack' +require 'rack/server' +require 'tempfile' +require 'socket' +require 'open-uri' + +describe Rack::Server do + + def app + lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] } + end + + def with_stderr + old, $stderr = $stderr, StringIO.new + yield $stderr + ensure + $stderr = old + end + + it "overrides :config if :app is passed in" do + server = Rack::Server.new(:app => "FOO") + server.app.should.equal "FOO" + end + + should "prefer to use :builder when it is passed in" do + server = Rack::Server.new(:builder => "run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }") + server.app.class.should.equal Proc + Rack::MockRequest.new(server.app).get("/").body.to_s.should.equal 'success' + end + + should "allow subclasses to override middleware" do + server = Class.new(Rack::Server).class_eval { def middleware; Hash.new [] end; self } + server.middleware['deployment'].should.not.equal [] + server.new(:app => 'foo').middleware['deployment'].should.equal [] + end + + should "allow subclasses to override default middleware" do + server = Class.new(Rack::Server).instance_eval { def default_middleware_by_environment; Hash.new [] end; self } + server.middleware['deployment'].should.equal [] + server.new(:app => 'foo').middleware['deployment'].should.equal [] + end + + should "only provide default middleware for development and deployment environments" do + Rack::Server.default_middleware_by_environment.keys.sort.should.equal %w(deployment development) + end + + should "always return an empty array for unknown environments" do + server = Rack::Server.new(:app => 'foo') + server.middleware['production'].should.equal [] + end + + should "not include Rack::Lint in deployment environment" do + server = Rack::Server.new(:app => 'foo') + server.middleware['deployment'].flatten.should.not.include(Rack::Lint) + end + + should "not include Rack::ShowExceptions in deployment environment" do + server = Rack::Server.new(:app => 'foo') + server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions) + end + + should "include Rack::TempfileReaper in deployment environment" do + server = Rack::Server.new(:app => 'foo') + server.middleware['deployment'].flatten.should.include(Rack::TempfileReaper) + end + + should "support CGI" do + begin + o, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], 'foo' + server = Rack::Server.new(:app => 'foo') + server.server.name =~ /CGI/ + Rack::Server.logging_middleware.call(server).should.eql(nil) + ensure + ENV['REQUEST_METHOD'] = o + end + end + + should "be quiet if said so" do + server = Rack::Server.new(:app => "FOO", :quiet => true) + Rack::Server.logging_middleware.call(server).should.eql(nil) + end + + should "use a full path to the pidfile" do + # avoids issues with daemonize chdir + opts = Rack::Server.new.send(:parse_options, %w[--pid testing.pid]) + opts[:pid].should.eql(::File.expand_path('testing.pid')) + end + + should "run a server" do + pidfile = Tempfile.open('pidfile') { |f| break f }.path + FileUtils.rm pidfile + server = Rack::Server.new( + :app => app, + :environment => 'none', + :pid => pidfile, + :Port => TCPServer.open('127.0.0.1', 0){|s| s.addr[1] }, + :Host => '127.0.0.1', + :daemonize => false, + :server => 'webrick' + ) + t = Thread.new { server.start { |s| Thread.current[:server] = s } } + t.join(0.01) until t[:server] && t[:server].status != :Stop + body = open("http://127.0.0.1:#{server.options[:Port]}/") { |f| f.read } + body.should.eql('success') + + Process.kill(:INT, $$) + t.join + open(pidfile) { |f| f.read.should.eql $$.to_s } + end + + should "check pid file presence and running process" do + pidfile = Tempfile.open('pidfile') { |f| f.write($$); break f }.path + server = Rack::Server.new(:pid => pidfile) + server.send(:pidfile_process_status).should.eql :running + end + + should "check pid file presence and dead process" do + dead_pid = `echo $$`.to_i + pidfile = Tempfile.open('pidfile') { |f| f.write(dead_pid); break f }.path + server = Rack::Server.new(:pid => pidfile) + server.send(:pidfile_process_status).should.eql :dead + end + + should "check pid file presence and exited process" do + pidfile = Tempfile.open('pidfile') { |f| break f }.path + ::File.delete(pidfile) + server = Rack::Server.new(:pid => pidfile) + server.send(:pidfile_process_status).should.eql :exited + end + + should "check pid file presence and not owned process" do + pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path + server = Rack::Server.new(:pid => pidfile) + server.send(:pidfile_process_status).should.eql :not_owned + end + + should "not write pid file when it is created after check" do + pidfile = Tempfile.open('pidfile') { |f| break f }.path + ::File.delete(pidfile) + server = Rack::Server.new(:pid => pidfile) + ::File.open(pidfile, 'w') { |f| f.write(1) } + with_stderr do |err| + should.raise(SystemExit) do + server.send(:write_pid) + end + err.rewind + output = err.read + output.should.match(/already running/) + output.should.include? pidfile + end + end + + should "inform the user about existing pidfiles with running processes" do + pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path + server = Rack::Server.new(:pid => pidfile) + with_stderr do |err| + should.raise(SystemExit) do + server.start + end + err.rewind + output = err.read + output.should.match(/already running/) + output.should.include? pidfile + end + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_abstract_id.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_abstract_id.rb new file mode 100644 index 000000000..911f43b17 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_abstract_id.rb @@ -0,0 +1,53 @@ +### WARNING: there be hax in this file. + +require 'rack/session/abstract/id' + +describe Rack::Session::Abstract::ID do + id = Rack::Session::Abstract::ID + + def silence_warning + o, $VERBOSE = $VERBOSE, nil + yield + ensure + $VERBOSE = o + end + + def reload_id + $".delete $".find { |part| part =~ %r{session/abstract/id.rb} } + silence_warning { require 'rack/session/abstract/id' } + end + + should "use securerandom when available" do + begin + fake = false + silence_warning do + ::SecureRandom = fake = true unless defined?(SecureRandom) + end + reload_id + id::DEFAULT_OPTIONS[:secure_random].should.eql(fake || SecureRandom) + ensure + Object.send(:remove_const, :SecureRandom) if fake + end + end + + should "not use securerandom when unavailable" do + begin + sr = Object.send(:remove_const, :SecureRandom) if defined?(SecureRandom) + reload_id + id::DEFAULT_OPTIONS[:secure_random].should.eql false + ensure + ::SecureRandom = sr if defined?(sr) + end + end + + should "allow to use another securerandom provider" do + secure_random = Class.new do + def hex(*args) + 'fake_hex' + end + end + id = Rack::Session::Abstract::ID.new nil, :secure_random => secure_random.new + id.send(:generate_sid).should.eql 'fake_hex' + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_cookie.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_cookie.rb new file mode 100644 index 000000000..944fde020 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_cookie.rb @@ -0,0 +1,410 @@ +require 'rack/session/cookie' +require 'rack/lint' +require 'rack/mock' + +describe Rack::Session::Cookie do + incrementor = lambda do |env| + env["rack.session"]["counter"] ||= 0 + env["rack.session"]["counter"] += 1 + hash = env["rack.session"].dup + hash.delete("session_id") + Rack::Response.new(hash.inspect).to_a + end + + session_id = lambda do |env| + Rack::Response.new(env["rack.session"].to_hash.inspect).to_a + end + + session_option = lambda do |opt| + lambda do |env| + Rack::Response.new(env["rack.session.options"][opt].inspect).to_a + end + end + + nothing = lambda do |env| + Rack::Response.new("Nothing").to_a + end + + renewer = lambda do |env| + env["rack.session.options"][:renew] = true + Rack::Response.new("Nothing").to_a + end + + only_session_id = lambda do |env| + Rack::Response.new(env["rack.session"]["session_id"].to_s).to_a + end + + bigcookie = lambda do |env| + env["rack.session"]["cookie"] = "big" * 3000 + Rack::Response.new(env["rack.session"].inspect).to_a + end + + destroy_session = lambda do |env| + env["rack.session"].destroy + Rack::Response.new("Nothing").to_a + end + + def response_for(options={}) + request_options = options.fetch(:request, {}) + cookie = if options[:cookie].is_a?(Rack::Response) + options[:cookie]["Set-Cookie"] + else + options[:cookie] + end + request_options["HTTP_COOKIE"] = cookie || "" + + app_with_cookie = Rack::Session::Cookie.new(*options[:app]) + app_with_cookie = Rack::Lint.new(app_with_cookie) + Rack::MockRequest.new(app_with_cookie).get("/", request_options) + end + + before do + @warnings = warnings = [] + Rack::Session::Cookie.class_eval do + define_method(:warn) { |m| warnings << m } + end + end + + after do + Rack::Session::Cookie.class_eval { remove_method :warn } + end + + describe 'Base64' do + it 'uses base64 to encode' do + coder = Rack::Session::Cookie::Base64.new + str = 'fuuuuu' + coder.encode(str).should.equal [str].pack('m') + end + + it 'uses base64 to decode' do + coder = Rack::Session::Cookie::Base64.new + str = ['fuuuuu'].pack('m') + coder.decode(str).should.equal str.unpack('m').first + end + + describe 'Marshal' do + it 'marshals and base64 encodes' do + coder = Rack::Session::Cookie::Base64::Marshal.new + str = 'fuuuuu' + coder.encode(str).should.equal [::Marshal.dump(str)].pack('m') + end + + it 'marshals and base64 decodes' do + coder = Rack::Session::Cookie::Base64::Marshal.new + str = [::Marshal.dump('fuuuuu')].pack('m') + coder.decode(str).should.equal ::Marshal.load(str.unpack('m').first) + end + + it 'rescues failures on decode' do + coder = Rack::Session::Cookie::Base64::Marshal.new + coder.decode('lulz').should.equal nil + end + end + + describe 'JSON' do + it 'marshals and base64 encodes' do + coder = Rack::Session::Cookie::Base64::JSON.new + obj = %w[fuuuuu] + coder.encode(obj).should.equal [::Rack::Utils::OkJson.encode(obj)].pack('m') + end + + it 'marshals and base64 decodes' do + coder = Rack::Session::Cookie::Base64::JSON.new + str = [::Rack::Utils::OkJson.encode(%w[fuuuuu])].pack('m') + coder.decode(str).should.equal ::Rack::Utils::OkJson.decode(str.unpack('m').first) + end + + it 'rescues failures on decode' do + coder = Rack::Session::Cookie::Base64::JSON.new + coder.decode('lulz').should.equal nil + end + end + + describe 'ZipJSON' do + it 'jsons, deflates, and base64 encodes' do + coder = Rack::Session::Cookie::Base64::ZipJSON.new + obj = %w[fuuuuu] + json = Rack::Utils::OkJson.encode(obj) + coder.encode(obj).should.equal [Zlib::Deflate.deflate(json)].pack('m') + end + + it 'base64 decodes, inflates, and decodes json' do + coder = Rack::Session::Cookie::Base64::ZipJSON.new + obj = %w[fuuuuu] + json = Rack::Utils::OkJson.encode(obj) + b64 = [Zlib::Deflate.deflate(json)].pack('m') + coder.decode(b64).should.equal obj + end + + it 'rescues failures on decode' do + coder = Rack::Session::Cookie::Base64::ZipJSON.new + coder.decode('lulz').should.equal nil + end + end + end + + it "warns if no secret is given" do + Rack::Session::Cookie.new(incrementor) + @warnings.first.should =~ /no secret/i + @warnings.clear + Rack::Session::Cookie.new(incrementor, :secret => 'abc') + @warnings.should.be.empty? + end + + it 'uses a coder' do + identity = Class.new { + attr_reader :calls + + def initialize + @calls = [] + end + + def encode(str); @calls << :encode; str; end + def decode(str); @calls << :decode; str; end + }.new + response = response_for(:app => [incrementor, { :coder => identity }]) + + response["Set-Cookie"].should.include("rack.session=") + response.body.should.equal '{"counter"=>1}' + identity.calls.should.equal [:decode, :encode] + end + + it "creates a new cookie" do + response = response_for(:app => incrementor) + response["Set-Cookie"].should.include("rack.session=") + response.body.should.equal '{"counter"=>1}' + end + + it "loads from a cookie" do + response = response_for(:app => incrementor) + + response = response_for(:app => incrementor, :cookie => response) + response.body.should.equal '{"counter"=>2}' + + response = response_for(:app => incrementor, :cookie => response) + response.body.should.equal '{"counter"=>3}' + end + + it "renew session id" do + response = response_for(:app => incrementor) + cookie = response['Set-Cookie'] + response = response_for(:app => only_session_id, :cookie => cookie) + cookie = response['Set-Cookie'] if response['Set-Cookie'] + + response.body.should.not.equal "" + old_session_id = response.body + + response = response_for(:app => renewer, :cookie => cookie) + cookie = response['Set-Cookie'] if response['Set-Cookie'] + response = response_for(:app => only_session_id, :cookie => cookie) + + response.body.should.not.equal "" + response.body.should.not.equal old_session_id + end + + it "destroys session" do + response = response_for(:app => incrementor) + response = response_for(:app => only_session_id, :cookie => response) + + response.body.should.not.equal "" + old_session_id = response.body + + response = response_for(:app => destroy_session, :cookie => response) + response = response_for(:app => only_session_id, :cookie => response) + + response.body.should.not.equal "" + response.body.should.not.equal old_session_id + end + + it "survives broken cookies" do + response = response_for( + :app => incrementor, + :cookie => "rack.session=blarghfasel" + ) + response.body.should.equal '{"counter"=>1}' + + response = response_for( + :app => [incrementor, { :secret => "test" }], + :cookie => "rack.session=" + ) + response.body.should.equal '{"counter"=>1}' + end + + it "barks on too big cookies" do + lambda{ + response_for(:app => bigcookie, :request => { :fatal => true }) + }.should.raise(Rack::MockRequest::FatalWarning) + end + + it "loads from a cookie with integrity hash" do + app = [incrementor, { :secret => "test" }] + + response = response_for(:app => app) + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>2}' + + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>3}' + + app = [incrementor, { :secret => "other" }] + + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>1}' + end + + it "loads from a cookie wih accept-only integrity hash for graceful key rotation" do + response = response_for(:app => [incrementor, { :secret => "test" }]) + + app = [incrementor, { :secret => "test2", :old_secret => "test" }] + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>2}' + + app = [incrementor, { :secret => "test3", :old_secret => "test2" }] + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>3}' + end + + it "ignores tampered with session cookies" do + app = [incrementor, { :secret => "test" }] + response = response_for(:app => app) + response.body.should.equal '{"counter"=>1}' + + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>2}' + + _, digest = response["Set-Cookie"].split("--") + tampered_with_cookie = "hackerman-was-here" + "--" + digest + + response = response_for(:app => app, :cookie => tampered_with_cookie) + response.body.should.equal '{"counter"=>1}' + end + + it "supports either of secret or old_secret" do + app = [incrementor, { :secret => "test" }] + response = response_for(:app => app) + response.body.should.equal '{"counter"=>1}' + + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>2}' + + app = [incrementor, { :old_secret => "test" }] + response = response_for(:app => app) + response.body.should.equal '{"counter"=>1}' + + response = response_for(:app => app, :cookie => response) + response.body.should.equal '{"counter"=>2}' + end + + it "can handle Rack::Lint middleware" do + response = response_for(:app => incrementor) + + lint = Rack::Lint.new(session_id) + response = response_for(:app => lint, :cookie => response) + response.body.should.not.be.nil + end + + it "can handle middleware that inspects the env" do + class TestEnvInspector + def initialize(app) + @app = app + end + def call(env) + env.inspect + @app.call(env) + end + end + + response = response_for(:app => incrementor) + + inspector = TestEnvInspector.new(session_id) + response = response_for(:app => inspector, :cookie => response) + response.body.should.not.be.nil + end + + it "returns the session id in the session hash" do + response = response_for(:app => incrementor) + response.body.should.equal '{"counter"=>1}' + + response = response_for(:app => session_id, :cookie => response) + response.body.should.match(/"session_id"=>/) + response.body.should.match(/"counter"=>1/) + end + + it "does not return a cookie if set to secure but not using ssl" do + app = [incrementor, { :secure => true }] + + response = response_for(:app => app) + response["Set-Cookie"].should.be.nil + + response = response_for(:app => app, :request => { "HTTPS" => "on" }) + response["Set-Cookie"].should.not.be.nil + response["Set-Cookie"].should.match(/secure/) + end + + it "does not return a cookie if cookie was not read/written" do + response = response_for(:app => nothing) + response["Set-Cookie"].should.be.nil + end + + it "does not return a cookie if cookie was not written (only read)" do + response = response_for(:app => session_id) + response["Set-Cookie"].should.be.nil + end + + it "returns even if not read/written if :expire_after is set" do + app = [nothing, { :expire_after => 3600 }] + request = { "rack.session" => { "not" => "empty" }} + response = response_for(:app => app, :request => request) + response["Set-Cookie"].should.not.be.nil + end + + it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do + app = [nothing, { :expire_after => 3600 }] + response = response_for(:app => app) + response["Set-Cookie"].should.be.nil + end + + it "exposes :secret in env['rack.session.option']" do + response = response_for(:app => [session_option[:secret], { :secret => "foo" }]) + response.body.should == '"foo"' + end + + it "exposes :coder in env['rack.session.option']" do + response = response_for(:app => session_option[:coder]) + response.body.should.match(/Base64::Marshal/) + end + + it "allows passing in a hash with session data from middleware in front" do + request = { 'rack.session' => { :foo => 'bar' }} + response = response_for(:app => session_id, :request => request) + response.body.should.match(/foo/) + end + + it "allows modifying session data with session data from middleware in front" do + request = { 'rack.session' => { :foo => 'bar' }} + response = response_for(:app => incrementor, :request => request) + response.body.should.match(/counter/) + response.body.should.match(/foo/) + end + + it "allows more than one '--' in the cookie when calculating digests" do + @counter = 0 + app = lambda do |env| + env["rack.session"]["message"] ||= "" + env["rack.session"]["message"] << "#{(@counter += 1).to_s}--" + hash = env["rack.session"].dup + hash.delete("session_id") + Rack::Response.new(hash["message"]).to_a + end + # another example of an unsafe coder is Base64.urlsafe_encode64 + unsafe_coder = Class.new { + def encode(hash); hash.inspect end + def decode(str); eval(str) if str; end + }.new + _app = [ app, { :secret => "test", :coder => unsafe_coder } ] + response = response_for(:app => _app) + response.body.should.equal "1--" + response = response_for(:app => _app, :cookie => response) + response.body.should.equal "1--2--" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_memcache.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_memcache.rb new file mode 100644 index 000000000..2b759806d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_memcache.rb @@ -0,0 +1,321 @@ +begin + require 'rack/session/memcache' + require 'rack/lint' + require 'rack/mock' + require 'thread' + + describe 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 = Rack::Lint.new(proc do |env| + env['rack.session.options'][:drop] = true + incrementor.call(env) + end) + renew_session = Rack::Lint.new(proc do |env| + env['rack.session.options'][:renew] = true + incrementor.call(env) + end) + defer_session = Rack::Lint.new(proc do |env| + env['rack.session.options'][:defer] = true + incrementor.call(env) + end) + skip_session = Rack::Lint.new(proc do |env| + env['rack.session.options'][:skip] = true + incrementor.call(env) + end) + incrementor = Rack::Lint.new(incrementor) + + # test memcache connection + Rack::Session::Memcache.new(incrementor) + + it "faults on no connection" do + lambda{ + Rack::Session::Memcache.new(incrementor, :memcache_server => 'nosuchserver') + }.should.raise + end + + it "connects to existing server" do + test_pool = MemCache.new(incrementor, :namespace => 'test:rack:session') + test_pool.namespace.should.equal 'test:rack:session' + end + + it "passes options to MemCache" do + pool = Rack::Session::Memcache.new(incrementor, :namespace => 'test:rack:session') + pool.pool.namespace.should.equal 'test:rack:session' + end + + it "creates a new cookie" do + pool = Rack::Session::Memcache.new(incrementor) + res = Rack::MockRequest.new(pool).get("/") + res["Set-Cookie"].should.include("#{session_key}=") + res.body.should.equal '{"counter"=>1}' + end + + it "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 + + it "determines session only from a cookie by default" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + res = req.get("/") + sid = res["Set-Cookie"][session_match, 1] + req.get("/?rack.session=#{sid}"). + body.should.equal '{"counter"=>1}' + req.get("/?rack.session=#{sid}"). + body.should.equal '{"counter"=>1}' + end + + it "determines session from params" do + pool = Rack::Session::Memcache.new(incrementor, :cookie_only => false) + req = Rack::MockRequest.new(pool) + res = req.get("/") + sid = res["Set-Cookie"][session_match, 1] + req.get("/?rack.session=#{sid}"). + body.should.equal '{"counter"=>2}' + req.get("/?rack.session=#{sid}"). + body.should.equal '{"counter"=>3}' + end + + it "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 + + it "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 + + it "does not send the same session id if it did not change" do + pool = Rack::Session::Memcache.new(incrementor) + req = Rack::MockRequest.new(pool) + + res0 = req.get("/") + cookie = res0["Set-Cookie"][session_match] + res0.body.should.equal '{"counter"=>1}' + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"].should.be.nil + res1.body.should.equal '{"counter"=>2}' + + res2 = req.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.be.nil + res2.body.should.equal '{"counter"=>3}' + end + + it "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) + + res1 = req.get("/") + session = (cookie = res1["Set-Cookie"])[session_match] + res1.body.should.equal '{"counter"=>1}' + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.equal nil + res2.body.should.equal '{"counter"=>2}' + + res3 = req.get("/", "HTTP_COOKIE" => cookie) + res3["Set-Cookie"][session_match].should.not.equal session + res3.body.should.equal '{"counter"=>1}' + end + + it "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) + + res1 = req.get("/") + session = (cookie = res1["Set-Cookie"])[session_match] + res1.body.should.equal '{"counter"=>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"=>2}' + + res3 = req.get("/", "HTTP_COOKIE" => new_cookie) + res3.body.should.equal '{"counter"=>3}' + + # Old cookie was deleted + res4 = req.get("/", "HTTP_COOKIE" => cookie) + res4.body.should.equal '{"counter"=>1}' + end + + it "omits cookie with :defer option but still updates the state" do + pool = Rack::Session::Memcache.new(incrementor) + count = Rack::Utils::Context.new(pool, incrementor) + defer = Rack::Utils::Context.new(pool, defer_session) + dreq = Rack::MockRequest.new(defer) + creq = Rack::MockRequest.new(count) + + res0 = dreq.get("/") + res0["Set-Cookie"].should.equal nil + res0.body.should.equal '{"counter"=>1}' + + res0 = creq.get("/") + res1 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"]) + res1.body.should.equal '{"counter"=>2}' + res2 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"]) + res2.body.should.equal '{"counter"=>3}' + end + + it "omits cookie and state update with :skip option" do + pool = Rack::Session::Memcache.new(incrementor) + count = Rack::Utils::Context.new(pool, incrementor) + skip = Rack::Utils::Context.new(pool, skip_session) + sreq = Rack::MockRequest.new(skip) + creq = Rack::MockRequest.new(count) + + res0 = sreq.get("/") + res0["Set-Cookie"].should.equal nil + res0.body.should.equal '{"counter"=>1}' + + res0 = creq.get("/") + res1 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"]) + res1.body.should.equal '{"counter"=>2}' + res2 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"]) + res2.body.should.equal '{"counter"=>2}' + end + + it "updates deep hashes correctly" do + 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) + + 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? + it "cleanly merges sessions when multithreaded" do + unless $DEBUG + 1.should.equal 1 # fake assertion to appease the mighty bacon + next + end + 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.equal tnum+1 # counter + session['counter'].should.equal 2 # meeeh + + tnum = rand(7).to_i+5 + r = Array.new(tnum) do + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_pool.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_pool.rb new file mode 100644 index 000000000..984f55a38 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_session_pool.rb @@ -0,0 +1,209 @@ +require 'thread' +require 'rack/lint' +require 'rack/mock' +require 'rack/session/pool' + +describe 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 + + session_id = Rack::Lint.new(lambda do |env| + Rack::Response.new(env["rack.session"].inspect).to_a + end) + + nothing = Rack::Lint.new(lambda do |env| + Rack::Response.new("Nothing").to_a + end) + + drop_session = Rack::Lint.new(lambda do |env| + env['rack.session.options'][:drop] = true + incrementor.call(env) + end) + + renew_session = Rack::Lint.new(lambda do |env| + env['rack.session.options'][:renew] = true + incrementor.call(env) + end) + + defer_session = Rack::Lint.new(lambda do |env| + env['rack.session.options'][:defer] = true + incrementor.call(env) + end) + + incrementor = Rack::Lint.new(incrementor) + + it "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 + + it "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 + + it "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 + + it "does not send the same session id if it did not change" do + pool = Rack::Session::Pool.new(incrementor) + req = Rack::MockRequest.new(pool) + + res0 = req.get("/") + cookie = res0["Set-Cookie"][session_match] + res0.body.should.equal '{"counter"=>1}' + pool.pool.size.should.equal 1 + + res1 = req.get("/", "HTTP_COOKIE" => cookie) + res1["Set-Cookie"].should.be.nil + res1.body.should.equal '{"counter"=>2}' + pool.pool.size.should.equal 1 + + res2 = req.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.be.nil + res2.body.should.equal '{"counter"=>3}' + pool.pool.size.should.equal 1 + end + + it "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) + + res1 = req.get("/") + session = (cookie = res1["Set-Cookie"])[session_match] + res1.body.should.equal '{"counter"=>1}' + pool.pool.size.should.equal 1 + + res2 = dreq.get("/", "HTTP_COOKIE" => cookie) + res2["Set-Cookie"].should.be.nil + res2.body.should.equal '{"counter"=>2}' + pool.pool.size.should.equal 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.equal 1 + end + + it "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) + + res1 = req.get("/") + session = (cookie = res1["Set-Cookie"])[session_match] + res1.body.should.equal '{"counter"=>1}' + pool.pool.size.should.equal 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"=>2}' + pool.pool.size.should.equal 1 + + res3 = req.get("/", "HTTP_COOKIE" => new_cookie) + res3.body.should.equal '{"counter"=>3}' + pool.pool.size.should.equal 1 + + res4 = req.get("/", "HTTP_COOKIE" => cookie) + res4.body.should.equal '{"counter"=>1}' + pool.pool.size.should.equal 2 + end + + it "omits cookie with :defer option" do + pool = Rack::Session::Pool.new(incrementor) + defer = Rack::Utils::Context.new(pool, defer_session) + dreq = Rack::MockRequest.new(defer) + + res1 = dreq.get("/") + res1["Set-Cookie"].should.equal nil + res1.body.should.equal '{"counter"=>1}' + pool.pool.size.should.equal 1 + end + + # anyone know how to do this better? + it "should merge sessions when multithreaded" do + unless $DEBUG + 1.should.equal 1 + next + end + + 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 |resp| + resp['Set-Cookie'].should.equal cookie + resp.body.should.include '"counter"=>2' + end + + session = pool.pool[sess_id] + session.size.should.equal tnum+1 # counter + session['counter'].should.equal 2 # meeeh + end + + it "does not return a cookie if cookie was not read/written" do + app = Rack::Session::Pool.new(nothing) + res = Rack::MockRequest.new(app).get("/") + res["Set-Cookie"].should.be.nil + end + + it "does not return a cookie if cookie was not written (only read)" do + app = Rack::Session::Pool.new(session_id) + res = Rack::MockRequest.new(app).get("/") + res["Set-Cookie"].should.be.nil + end + + it "returns even if not read/written if :expire_after is set" do + app = Rack::Session::Pool.new(nothing, :expire_after => 3600) + res = Rack::MockRequest.new(app).get("/", 'rack.session' => {'not' => 'empty'}) + res["Set-Cookie"].should.not.be.nil + end + + it "returns no cookie if no data was written and no session was created previously, even if :expire_after is set" do + app = Rack::Session::Pool.new(nothing, :expire_after => 3600) + res = Rack::MockRequest.new(app).get("/") + res["Set-Cookie"].should.be.nil + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showexceptions.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showexceptions.rb new file mode 100644 index 000000000..7d50c59f7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showexceptions.rb @@ -0,0 +1,85 @@ +require 'rack/showexceptions' +require 'rack/lint' +require 'rack/mock' + +describe Rack::ShowExceptions do + def show_exceptions(app) + Rack::Lint.new Rack::ShowExceptions.new(app) + end + + it "catches exceptions" do + res = nil + + req = Rack::MockRequest.new( + show_exceptions( + lambda{|env| raise RuntimeError } + )) + + lambda{ + res = req.get("/", "HTTP_ACCEPT" => "text/html") + }.should.not.raise + + res.should.be.a.server_error + res.status.should.equal 500 + + res.should =~ /RuntimeError/ + res.should =~ /ShowExceptions/ + end + + it "responds with HTML only to requests accepting HTML" do + res = nil + + req = Rack::MockRequest.new( + show_exceptions( + lambda{|env| raise RuntimeError, "It was never supposed to work" } + )) + + [ + # Serve text/html when the client accepts text/html + ["text/html", ["/", {"HTTP_ACCEPT" => "text/html"}]], + ["text/html", ["/", {"HTTP_ACCEPT" => "*/*"}]], + # Serve text/plain when the client does not accept text/html + ["text/plain", ["/"]], + ["text/plain", ["/", {"HTTP_ACCEPT" => "application/json"}]] + ].each do |exmime, rargs| + lambda{ + res = req.get(*rargs) + }.should.not.raise + + res.should.be.a.server_error + res.status.should.equal 500 + + res.content_type.should.equal exmime + + res.body.should.include "RuntimeError" + res.body.should.include "It was never supposed to work" + + if exmime == "text/html" + res.body.should.include '' + else + res.body.should.not.include '' + end + end + end + + it "handles exceptions without a backtrace" do + res = nil + + req = Rack::MockRequest.new( + show_exceptions( + lambda{|env| raise RuntimeError, "", [] } + ) + ) + + lambda{ + res = req.get("/", "HTTP_ACCEPT" => "text/html") + }.should.not.raise + + res.should.be.a.server_error + res.status.should.equal 500 + + res.should =~ /RuntimeError/ + res.should =~ /ShowExceptions/ + res.should =~ /unknown location/ + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showstatus.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showstatus.rb new file mode 100644 index 000000000..5d97e8e59 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_showstatus.rb @@ -0,0 +1,103 @@ +require 'rack/showstatus' +require 'rack/lint' +require 'rack/mock' +require 'rack/utils' + +describe Rack::ShowStatus do + def show_status(app) + Rack::Lint.new Rack::ShowStatus.new(app) + end + + should "provide a default status message" do + req = Rack::MockRequest.new( + show_status(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 + + should "let the app provide additional information" do + req = Rack::MockRequest.new( + show_status( + 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 + + should "escape error" do + detail = "" + req = Rack::MockRequest.new( + show_status( + lambda{|env| + env["rack.showstatus.detail"] = detail + [500, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] + })) + + res = req.get("/", :lint => true) + res.should.be.not.empty + + res["Content-Type"].should.equal("text/html") + res.should =~ /500/ + res.should.not.include detail + res.body.should.include Rack::Utils.escape_html(detail) + end + + should "not replace existing messages" do + req = Rack::MockRequest.new( + show_status( + 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 + + should "pass on original headers" do + headers = {"WWW-Authenticate" => "Basic blah"} + + req = Rack::MockRequest.new( + show_status(lambda{|env| [401, headers, []] })) + res = req.get("/", :lint => true) + + res["WWW-Authenticate"].should.equal("Basic blah") + end + + should "replace existing messages if there is detail" do + req = Rack::MockRequest.new( + show_status( + 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_static.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_static.rb new file mode 100644 index 000000000..fed1df252 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_static.rb @@ -0,0 +1,145 @@ +require 'rack/static' +require 'rack/lint' +require 'rack/mock' + +class DummyApp + def call(env) + [200, {"Content-Type" => "text/plain"}, ["Hello World"]] + end +end + +describe Rack::Static do + def static(app, *args) + Rack::Lint.new Rack::Static.new(app, *args) + end + + root = File.expand_path(File.dirname(__FILE__)) + + OPTIONS = {:urls => ["/cgi"], :root => root} + STATIC_OPTIONS = {:urls => [""], :root => "#{root}/static", :index => 'index.html'} + HASH_OPTIONS = {:urls => {"/cgi/sekret" => 'cgi/test'}, :root => root} + + @request = Rack::MockRequest.new(static(DummyApp.new, OPTIONS)) + @static_request = Rack::MockRequest.new(static(DummyApp.new, STATIC_OPTIONS)) + @hash_request = Rack::MockRequest.new(static(DummyApp.new, HASH_OPTIONS)) + + it "serves files" do + res = @request.get("/cgi/test") + res.should.be.ok + res.body.should =~ /ruby/ + end + + it "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 + + it "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 + + it "calls index file when requesting root in the given folder" do + res = @static_request.get("/") + res.should.be.ok + res.body.should =~ /index!/ + + res = @static_request.get("/other/") + res.should.be.not_found + + res = @static_request.get("/another/") + res.should.be.ok + res.body.should =~ /another index!/ + end + + it "doesn't call index file if :index option was omitted" do + res = @request.get("/") + res.body.should == "Hello World" + end + + it "serves hidden files" do + res = @hash_request.get("/cgi/sekret") + res.should.be.ok + res.body.should =~ /ruby/ + end + + it "calls down the chain if the URI is not specified" do + res = @hash_request.get("/something/else") + res.should.be.ok + res.body.should == "Hello World" + end + + it "supports serving fixed cache-control (legacy option)" do + opts = OPTIONS.merge(:cache_control => 'public') + request = Rack::MockRequest.new(static(DummyApp.new, opts)) + res = request.get("/cgi/test") + res.should.be.ok + res.headers['Cache-Control'].should == 'public' + end + + HEADER_OPTIONS = {:urls => ["/cgi"], :root => root, :header_rules => [ + [:all, {'Cache-Control' => 'public, max-age=100'}], + [:fonts, {'Cache-Control' => 'public, max-age=200'}], + [%w(png jpg), {'Cache-Control' => 'public, max-age=300'}], + ['/cgi/assets/folder/', {'Cache-Control' => 'public, max-age=400'}], + ['cgi/assets/javascripts', {'Cache-Control' => 'public, max-age=500'}], + [/\.(css|erb)\z/, {'Cache-Control' => 'public, max-age=600'}] + ]} + @header_request = Rack::MockRequest.new(static(DummyApp.new, HEADER_OPTIONS)) + + it "supports header rule :all" do + # Headers for all files via :all shortcut + res = @header_request.get('/cgi/assets/index.html') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=100' + end + + it "supports header rule :fonts" do + # Headers for web fonts via :fonts shortcut + res = @header_request.get('/cgi/assets/fonts/font.eot') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=200' + end + + it "supports file extension header rules provided as an Array" do + # Headers for file extensions via array + res = @header_request.get('/cgi/assets/images/image.png') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=300' + end + + it "supports folder rules provided as a String" do + # Headers for files in folder via string + res = @header_request.get('/cgi/assets/folder/test.js') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=400' + end + + it "supports folder header rules provided as a String not starting with a slash" do + res = @header_request.get('/cgi/assets/javascripts/app.js') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=500' + end + + it "supports flexible header rules provided as Regexp" do + # Flexible Headers via Regexp + res = @header_request.get('/cgi/assets/stylesheets/app.css') + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=600' + end + + it "prioritizes header rules over fixed cache-control setting (legacy option)" do + opts = OPTIONS.merge( + :cache_control => 'public, max-age=24', + :header_rules => [ + [:all, {'Cache-Control' => 'public, max-age=42'}] + ]) + + request = Rack::MockRequest.new(static(DummyApp.new, opts)) + res = request.get("/cgi/test") + res.should.be.ok + res.headers['Cache-Control'].should == 'public, max-age=42' + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_tempfile_reaper.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_tempfile_reaper.rb new file mode 100644 index 000000000..ac39d8789 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_tempfile_reaper.rb @@ -0,0 +1,63 @@ +require 'rack/tempfile_reaper' +require 'rack/lint' +require 'rack/mock' + +describe Rack::TempfileReaper do + class MockTempfile + attr_reader :closed + + def initialize + @closed = false + end + + def close! + @closed = true + end + end + + before do + @env = Rack::MockRequest.env_for + end + + def call(app) + Rack::Lint.new(Rack::TempfileReaper.new(app)).call(@env) + end + + should 'do nothing (i.e. not bomb out) without env[rack.tempfiles]' do + app = lambda { |_| [200, {}, ['Hello, World!']] } + response = call(app) + response[2].close + response[0].should.equal(200) + end + + should 'close env[rack.tempfiles] when body is closed' do + tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new + @env['rack.tempfiles'] = [ tempfile1, tempfile2 ] + app = lambda { |_| [200, {}, ['Hello, World!']] } + call(app)[2].close + tempfile1.closed.should.equal true + tempfile2.closed.should.equal true + end + + should 'initialize env[rack.tempfiles] when not already present' do + tempfile = MockTempfile.new + app = lambda do |env| + env['rack.tempfiles'] << tempfile + [200, {}, ['Hello, World!']] + end + call(app)[2].close + tempfile.closed.should.equal true + end + + should 'append env[rack.tempfiles] when already present' do + tempfile1, tempfile2 = MockTempfile.new, MockTempfile.new + @env['rack.tempfiles'] = [ tempfile1 ] + app = lambda do |env| + env['rack.tempfiles'] << tempfile2 + [200, {}, ['Hello, World!']] + end + call(app)[2].close + tempfile1.closed.should.equal true + tempfile2.closed.should.equal true + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_thin.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_thin.rb new file mode 100644 index 000000000..15a1ab543 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_thin.rb @@ -0,0 +1,91 @@ +begin +require 'rack/handler/thin' +require File.expand_path('../testrequest', __FILE__) +require 'timeout' + +describe Rack::Handler::Thin do + extend TestRequest::Helpers + + @app = Rack::Lint.new(TestRequest.new) + @server = nil + Thin::Logging.silent = true + + @thread = Thread.new do + Rack::Handler::Thin.run(@app, :Host => @host='127.0.0.1', :Port => @port=9204, :tag => "tag") do |server| + @server = server + end + end + + Thread.pass until @server && @server.running? + + should "respond" do + GET("/") + response.should.not.be.nil + end + + should "be a Thin" do + GET("/") + status.should.equal 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 "127.0.0.1" + end + + should "have rack headers" do + GET("/") + response["rack.version"].should.equal [1,0] + response["rack.multithread"].should.equal false + response["rack.multiprocess"].should.equal false + response["rack.run_once"].should.equal false + end + + 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 + + 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 + + should "support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + should "set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + should "set tag for server" do + @server.tag.should.equal 'tag' + end + + @server.stop! + @thread.kill + +end + +rescue LoadError + $stderr.puts "Skipping Rack::Handler::Thin tests (Thin is required). `gem install thin` and try again." +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_urlmap.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_urlmap.rb new file mode 100644 index 000000000..2ef41cdc7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_urlmap.rb @@ -0,0 +1,236 @@ +require 'rack/urlmap' +require 'rack/mock' + +describe Rack::URLMap do + it "dispatches paths correctly" do + app = lambda { |env| + [200, { + 'X-ScriptName' => env['SCRIPT_NAME'], + 'X-PathInfo' => env['PATH_INFO'], + 'Content-Type' => 'text/plain' + }, [""]] + } + map = Rack::Lint.new(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 + + + it "dispatches hosts correctly" do + map = Rack::Lint.new(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 "foo.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 + + should "be nestable" do + map = Rack::Lint.new(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 + + should "route root apps correctly" do + map = Rack::Lint.new(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 + + should "not squeeze slashes" do + map = Rack::Lint.new(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 + + should "not be case sensitive with hosts" do + map = Rack::Lint.new(Rack::URLMap.new("http://example.org/" => lambda { |env| + [200, + { "Content-Type" => "text/plain", + "X-Position" => "root", + "X-PathInfo" => env["PATH_INFO"], + "X-ScriptName" => env["SCRIPT_NAME"] + }, [""]]} + )) + + res = Rack::MockRequest.new(map).get("http://example.org/") + res.should.be.ok + res["X-Position"].should.equal "root" + res["X-PathInfo"].should.equal "/" + res["X-ScriptName"].should.equal "" + + res = Rack::MockRequest.new(map).get("http://EXAMPLE.ORG/") + res.should.be.ok + res["X-Position"].should.equal "root" + res["X-PathInfo"].should.equal "/" + res["X-ScriptName"].should.equal "" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_utils.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_utils.rb new file mode 100644 index 000000000..c2d479f97 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_utils.rb @@ -0,0 +1,647 @@ +# -*- encoding: utf-8 -*- +require 'rack/utils' +require 'rack/mock' +require 'timeout' + +describe Rack::Utils do + + # A helper method which checks + # if certain query parameters + # are equal. + def equal_query_to(query) + parts = query.split('&') + lambda{|other| (parts & other.split('&')) == parts } + end + + def kcodeu + one8 = RUBY_VERSION.to_f < 1.9 + default_kcode, $KCODE = $KCODE, 'U' if one8 + yield + ensure + $KCODE = default_kcode if one8 + end + + should "round trip binary data" do + r = [218, 0].pack 'CC' + if defined?(::Encoding) + z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY) + else + z = Rack::Utils.unescape(Rack::Utils.escape(r)) + end + r.should.equal z + end + + 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 + + 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 + + if RUBY_VERSION[/^\d+\.\d+/] == '1.8' + should "escape correctly for multibyte characters if $KCODE is set to 'U'" do + kcodeu 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 + end + + should "unescape multibyte characters correctly if $KCODE is set to 'U'" do + kcodeu do + Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal( + "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0]) + end + end + end + + should "escape objects that responds to to_s" do + kcodeu do + Rack::Utils.escape(:id).should.equal "id" + end + end + + if "".respond_to?(:encode) + should "escape non-UTF8 strings" do + Rack::Utils.escape("ø".encode("ISO-8859-1")).should.equal "%F8" + end + end + + should "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do + lambda { + timeout(1) do + lambda { + URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %" + }.should.raise(ArgumentError) + end + }.should.not.raise(Timeout::Error) + end + + should "escape path spaces with %20" do + Rack::Utils.escape_path("foo bar").should.equal "foo%20bar" + end + + 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 + + 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" + Rack::Utils.parse_query("=").should.equal "" => "" + Rack::Utils.parse_query("=value").should.equal "" => "value" + Rack::Utils.parse_query("key=").should.equal "key" => "" + Rack::Utils.parse_query("&key&").should.equal "key" => nil + Rack::Utils.parse_query(";key;", ";,").should.equal "key" => nil + Rack::Utils.parse_query(",key,", ";,").should.equal "key" => nil + Rack::Utils.parse_query(";foo=bar,;", ";,").should.equal "foo" => "bar" + Rack::Utils.parse_query(",foo=bar;,", ";,").should.equal "foo" => "bar" + end + + should "not create infinite loops with cycle structures" do + ex = { "foo" => nil } + ex["foo"] = ex + + params = Rack::Utils::KeySpaceConstrainedParams.new + params['foo'] = params + lambda { + params.to_params_hash.to_s.should.equal ex.to_s + }.should.not.raise + end + + should "raise an exception if the params are too deep" do + len = Rack::Utils.param_depth_limit + + lambda { + Rack::Utils.parse_nested_query("foo#{"[a]" * len}=bar") + }.should.raise(RangeError) + + lambda { + Rack::Utils.parse_nested_query("foo#{"[a]" * (len - 1)}=bar") + }.should.not.raise + end + + 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("a=b&pid%3D1234=1023"). + should.equal "pid=1234" => "1023", "a" => "b" + + 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&foo"). + should.equal "foo" => nil + Rack::Utils.parse_nested_query("foo[]=bar&foo["). + should.equal "foo" => ["bar"], "foo[" => nil + Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz"). + should.equal "foo" => ["bar"], "foo[" => "baz" + Rack::Utils.parse_nested_query("foo[]=bar&foo[]"). + should.equal "foo" => ["bar", nil] + Rack::Utils.parse_nested_query("foo[]=bar&foo[]="). + 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(Rack::Utils::ParameterTypeError). + message.should.equal "expected Hash (got String) for param `y'" + + lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }. + should.raise(Rack::Utils::ParameterTypeError). + message.should.match(/expected Array \(got [^)]*\) for param `x'/) + + lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }. + should.raise(Rack::Utils::ParameterTypeError). + message.should.equal "expected Array (got String) for param `y'" + + if RUBY_VERSION.to_f > 1.9 + lambda { Rack::Utils.parse_nested_query("foo%81E=1") }. + should.raise(Rack::Utils::InvalidParameterError). + message.should.equal "invalid byte sequence in UTF-8" + end + end + + should "build query strings correctly" do + Rack::Utils.build_query("foo" => "bar").should.be equal_query_to("foo=bar") + Rack::Utils.build_query("foo" => ["bar", "quux"]). + should.be equal_query_to("foo=bar&foo=quux") + Rack::Utils.build_query("foo" => "1", "bar" => "2"). + should.be equal_query_to("foo=1&bar=2") + Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?"). + should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F") + end + + 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.be equal_query_to("foo=1&bar=2") + Rack::Utils.build_nested_query("foo" => 1, "bar" => 2). + should.be equal_query_to("foo=1&bar=2") + Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?"). + should.be equal_query_to("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" + Rack::Utils.build_nested_query('foo' => []). + should.equal '' + Rack::Utils.build_nested_query('foo' => {}). + should.equal '' + Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []). + should.equal 'foo=bar' + Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}). + 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 + + should "parse query strings that have a non-existent value" do + key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit" + Rack::Utils.parse_query(key).should.equal Rack::Utils.unescape(key) => nil + end + + should "build query strings without = with non-existent values" do + key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit" + key = Rack::Utils.unescape(key) + Rack::Utils.build_query(key => nil).should.equal Rack::Utils.escape(key) + end + + should "parse q-values" do + # XXX handle accept-extension + Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").should.equal [ + [ 'foo', 0.5 ], + [ 'bar', 1.0 ], + [ 'baz', 0.9 ] + ] + end + + should "select best quality match" do + Rack::Utils.best_q_match("text/html", %w[text/html]).should.equal "text/html" + + # More specific matches are preferred + Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).should.equal "text/html" + + # Higher quality matches are preferred + Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).should.equal "text/plain" + + # Respect requested content type + Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).should.equal "application/json" + + # All else equal, the available mimes are preferred in order + Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).should.equal "text/html" + Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).should.equal "text/html" + + # When there are no matches, return nil: + Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).should.equal nil + end + + should "escape html entities [&><'\"/]" do + Rack::Utils.escape_html("foo").should.equal "foo" + Rack::Utils.escape_html("f&o").should.equal "f&o" + Rack::Utils.escape_html("fo").should.equal "f>o" + Rack::Utils.escape_html("f'o").should.equal "f'o" + Rack::Utils.escape_html('f"o').should.equal "f"o" + Rack::Utils.escape_html("f/o").should.equal "f/o" + Rack::Utils.escape_html("").should.equal "<foo></foo>" + end + + should "escape html entities even on MRI when it's bugged" do + test_escape = lambda do + kcodeu do + Rack::Utils.escape_html("\300<").should.equal "\300<" + end + end + + if RUBY_VERSION.to_f < 1.9 + test_escape.call + else + test_escape.should.raise(ArgumentError) + end + end + + if "".respond_to?(:encode) + should "escape html entities in unicode strings" do + # the following will cause warnings if the regex is poorly encoded: + Rack::Utils.escape_html("☃").should.equal "☃" + end + end + + should "figure out which encodings are acceptable" do + helper = lambda do |a, b| + 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 + + should "return the bytesize of String" do + Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6 + end + + should "should perform constant time string comparison" do + Rack::Utils.secure_compare('a', 'a').should.equal true + Rack::Utils.secure_compare('a', 'b').should.equal false + end + + should "return status code for integer" do + Rack::Utils.status_code(200).should.equal 200 + end + + should "return status code for string" do + Rack::Utils.status_code("200").should.equal 200 + end + + should "return status code for symbol" do + Rack::Utils.status_code(:ok).should.equal 200 + end + + should "return rfc2822 format from rfc2822 helper" do + Rack::Utils.rfc2822(Time.at(0).gmtime).should == "Thu, 01 Jan 1970 00:00:00 -0000" + end + + should "return rfc2109 format from rfc2109 helper" do + Rack::Utils.rfc2109(Time.at(0).gmtime).should == "Thu, 01-Jan-1970 00:00:00 GMT" + end + + should "clean directory traversal" do + Rack::Utils.clean_path_info("/cgi/../cgi/test").should.equal "/cgi/test" + Rack::Utils.clean_path_info(".").should.empty + Rack::Utils.clean_path_info("test/..").should.empty + end + + should "clean unsafe directory traversal to safe path" do + Rack::Utils.clean_path_info("/../README.rdoc").should.equal "/README.rdoc" + Rack::Utils.clean_path_info("../test/spec_utils.rb").should.equal "test/spec_utils.rb" + end + + should "not clean directory traversal with encoded periods" do + Rack::Utils.clean_path_info("/%2E%2E/README").should.equal "/%2E%2E/README" + end + + should "clean slash only paths" do + Rack::Utils.clean_path_info("/").should.equal "/" + end +end + +describe Rack::Utils, "byte_range" do + should "ignore missing or syntactically invalid byte ranges" do + Rack::Utils.byte_ranges({},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).should.equal nil + # A range of non-positive length is syntactically invalid and ignored: + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).should.equal nil + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).should.equal nil + end + + should "parse simple byte ranges" do + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).should.equal [(123..456)] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).should.equal [(123..499)] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).should.equal [(400..499)] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).should.equal [(0..0)] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).should.equal [(499..499)] + end + + should "parse several byte ranges" do + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).should.equal [(500..600),(601..999)] + end + + should "truncate byte ranges" do + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).should.equal [(123..499)] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).should.equal [(0..499)] + end + + should "ignore unsatisfiable byte ranges" do + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).should.equal [] + end + + should "handle byte ranges of empty files" do + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).should.equal [] + Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).should.equal [] + end +end + +describe Rack::Utils::HeaderHash do + 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 + + 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 + + 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 + + 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 + + should "be converted to real Hash" do + h = Rack::Utils::HeaderHash.new("foo" => "bar") + h.to_hash.should.be.instance_of Hash + end + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + should "not create headers out of thin air" do + h = Rack::Utils::HeaderHash.new + h['foo'] + h['foo'].should.be.nil + h.should.not.include 'foo' + end +end + +describe 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 + + 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 + + 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 + + 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.equal 200 + r5 = Rack::MockRequest.new(a5).get('/') + r5.status.should.equal 200 + r4.body.should.equal r5.body + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_version.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_version.rb new file mode 100644 index 000000000..9ba165246 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_version.rb @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +require 'rack' + +describe Rack do + describe 'version' do + it 'defaults to a hard-coded api version' do + Rack.version.should.equal("1.3") + end + end + describe 'release' do + it 'matches version in .gemspec' do + gemspec_path = File.join(File.dirname(File.expand_path(__FILE__)), '../rack.gemspec') + gemspec = Gem::Specification.load(gemspec_path) + Rack.release.split('.').take(2).should.equal gemspec.version.to_s.split('.').take(2) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_webrick.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_webrick.rb new file mode 100644 index 000000000..663388312 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/spec_webrick.rb @@ -0,0 +1,184 @@ +require 'rack/mock' +require File.expand_path('../testrequest', __FILE__) + +Thread.abort_on_exception = true + +describe Rack::Handler::WEBrick do + extend TestRequest::Helpers + + @server = WEBrick::HTTPServer.new(:Host => @host='127.0.0.1', + :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 } + + should "respond" do + lambda { + GET("/test") + }.should.not.raise + end + + should "be a WEBrick" do + GET("/test") + status.should.equal 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 "127.0.0.1" + end + + should "have rack headers" do + GET("/test") + response["rack.version"].should.equal [1,3] + response["rack.multithread"].should.be.true + response["rack.multiprocess"].should.be.false + response["rack.run_once"].should.be.false + end + + 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" + + GET("/test/foo%25encoding?quux=1") + response["REQUEST_METHOD"].should.equal "GET" + response["SCRIPT_NAME"].should.equal "/test" + response["REQUEST_PATH"].should.equal "/test/foo%25encoding" + response["PATH_INFO"].should.equal "/foo%25encoding" + response["QUERY_STRING"].should.equal "quux=1" + end + + 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["PATH_INFO"].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 + + should "support HTTP auth" do + GET("/test", {:user => "ruth", :passwd => "secret"}) + response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" + end + + should "set status" do + GET("/test?secret") + status.should.equal 403 + response["rack.url_scheme"].should.equal "http" + end + + 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 + + should "provide a .run" do + block_ran = false + catch(:done) { + Rack::Handler::WEBrick.run(lambda {}, + { + :Host => '127.0.0.1', + :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 + + should "return repeated headers" do + @server.mount "/headers", Rack::Handler::WEBrick, + Rack::Lint.new(lambda { |req| + [ + 401, + { "Content-Type" => "text/plain", + "WWW-Authenticate" => "Bar realm=X\nBaz realm=Y" }, + [""] + ] + }) + + Net::HTTP.start(@host, @port) { |http| + res = http.get("/headers") + res.code.to_i.should.equal 401 + res["www-authenticate"].should.equal "Bar realm=X, Baz realm=Y" + } + end + + should "support Rack partial hijack" do + io_lambda = lambda{ |io| + 5.times do + io.write "David\r\n" + end + io.close + } + + @server.mount "/partial", Rack::Handler::WEBrick, + Rack::Lint.new(lambda{ |req| + [ + 200, + {"rack.hijack" => io_lambda}, + [""] + ] + }) + + Net::HTTP.start(@host, @port){ |http| + res = http.get("/partial") + res.body.should.equal "David\r\nDavid\r\nDavid\r\nDavid\r\nDavid\r\n" + } + end + + should "produce correct HTTP semantics with and without app chunking" do + @server.mount "/chunked", Rack::Handler::WEBrick, + Rack::Lint.new(lambda{ |req| + [ + 200, + {"Transfer-Encoding" => "chunked"}, + ["7\r\nchunked\r\n0\r\n\r\n"] + ] + }) + + Net::HTTP.start(@host, @port){ |http| + res = http.get("/chunked") + res["Transfer-Encoding"].should.equal "chunked" + res["Content-Length"].should.equal nil + res.body.should.equal "chunked" + } + end + + @server.shutdown +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/another/index.html b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/another/index.html new file mode 100644 index 000000000..d93e6fc34 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/another/index.html @@ -0,0 +1 @@ +another index! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/index.html b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/index.html new file mode 100644 index 000000000..fc2d9ad1e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/static/index.html @@ -0,0 +1 @@ +index! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/testrequest.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/testrequest.rb new file mode 100644 index 000000000..c5e339aec --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/testrequest.rb @@ -0,0 +1,78 @@ +require 'yaml' +require 'net/http' +require 'rack/lint' + +class TestRequest + NOSERIALIZE = [Method, Proc, Rack::Lint::InputWrapper] + + def call(env) + status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200 + env["test.postdata"] = env["rack.input"].read + minienv = env.dup + # This may in the future want to replace with a dummy value instead. + minienv.delete_if { |k,v| NOSERIALIZE.any? { |c| v.kind_of?(c) } } + body = minienv.to_yaml + size = body.respond_to?(:bytesize) ? body.bytesize : body.size + [status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]] + end + + module Helpers + attr_reader :status, :response + + ROOT = File.expand_path(File.dirname(__FILE__) + "/..") + ENV["RUBYOPT"] = "-I#{ROOT}/lib -rubygems" + + def root + ROOT + end + + def rackup + "#{ROOT}/bin/rackup" + end + + def GET(path, header={}) + Net::HTTP.start(@host, @port) { |http| + user = header.delete(:user) + passwd = header.delete(:passwd) + + get = Net::HTTP::Get.new(path, header) + get.basic_auth user, passwd if user && passwd + http.request(get) { |response| + @status = response.code.to_i + begin + @response = YAML.load(response.body) + rescue TypeError, ArgumentError + @response = nil + end + } + } + end + + def POST(path, formdata={}, header={}) + Net::HTTP.start(@host, @port) { |http| + user = header.delete(:user) + passwd = header.delete(:passwd) + + post = Net::HTTP::Post.new(path, header) + post.form_data = formdata + post.basic_auth user, passwd if user && passwd + http.request(post) { |response| + @status = response.code.to_i + @response = YAML.load(response.body) + } + } + end + end +end + +class StreamingRequest + def self.call(env) + [200, {"Content-Type" => "text/plain"}, new] + end + + def each + yield "hello there!\n" + sleep 5 + yield "that is all.\n" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered.rb new file mode 100644 index 000000000..6dd9436d9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered.rb @@ -0,0 +1,7 @@ +module Rack + module Handler + # this class doesn't do anything, we're just seeing if we get it. + class Unregistered + end + end +end \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered_long_one.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered_long_one.rb new file mode 100644 index 000000000..1920685fc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-1.6.4/test/unregistered_handler/rack/handler/unregistered_long_one.rb @@ -0,0 +1,7 @@ +module Rack + module Handler + # this class doesn't do anything, we're just seeing if we get it. + class UnregisteredLongOne + end + end +end \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/License b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/License new file mode 100644 index 000000000..858737cd7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/License @@ -0,0 +1,20 @@ +Copyright (c) 2011 Konstantin Haase + +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 OR COPYRIGHT HOLDERS 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/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/README.md b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/README.md new file mode 100644 index 000000000..725572cc2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/README.md @@ -0,0 +1,90 @@ +You should use protection! + +This gem protects against typical web attacks. +Should work for all Rack apps, including Rails. + +# Usage + +Use all protections you probably want to use: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection +run MyApp +``` + +Skip a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection, :except => :path_traversal +run MyApp +``` + +Use a single protection middleware: + +``` ruby +# config.ru +require 'rack/protection' +use Rack::Protection::AuthenticityToken +run MyApp +``` + +# Prevented Attacks + +## Cross Site Request Forgery + +Prevented by: + +* `Rack::Protection::AuthenticityToken` (not included by `use Rack::Protection`) +* `Rack::Protection::FormToken` (not included by `use Rack::Protection`) +* `Rack::Protection::JsonCsrf` +* `Rack::Protection::RemoteReferrer` (not included by `use Rack::Protection`) +* `Rack::Protection::RemoteToken` +* `Rack::Protection::HttpOrigin` + +## Cross Site Scripting + +Prevented by: + +* `Rack::Protection::EscapedParams` (not included by `use Rack::Protection`) +* `Rack::Protection::XSSHeader` (Internet Explorer only) + +## Clickjacking + +Prevented by: + +* `Rack::Protection::FrameOptions` + +## Directory Traversal + +Prevented by: + +* `Rack::Protection::PathTraversal` + +## Session Hijacking + +Prevented by: + +* `Rack::Protection::SessionHijacking` + +## IP Spoofing + +Prevented by: + +* `Rack::Protection::IPSpoofing` + +# Installation + + gem install rack-protection + +# Instrumentation + +Instrumentation is enabled by passing in an instrumenter as an option. +``` +use Rack::Protection, instrumenter: ActiveSupport::Notifications +``` + +The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/Rakefile b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/Rakefile new file mode 100644 index 000000000..b642a1a3e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/Rakefile @@ -0,0 +1,48 @@ +# encoding: utf-8 +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) + +begin + require 'bundler' + Bundler::GemHelper.install_tasks +rescue LoadError => e + $stderr.puts e +end + +desc "run specs" +task(:spec) { ruby '-S rspec spec' } + +desc "generate gemspec" +task 'rack-protection.gemspec' do + require 'rack/protection/version' + content = File.binread 'rack-protection.gemspec' + + # fetch data + fields = { + :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), + :email => `git shortlog -sne`.force_encoding('utf-8').scan(/[^<]+@[^>]+/), + :files => `git ls-files`.force_encoding('utf-8').split("\n").reject { |f| f =~ /^(\.|Gemfile)/ } + } + + # double email :( + fields[:email].delete("konstantin.haase@gmail.com") + + # insert data + fields.each do |field, values| + updated = " s.#{field} = [" + updated << values.map { |v| "\n %p" % v }.join(',') + updated << "\n ]" + content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) + end + + # set version + content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\"" + + # escape unicode + content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c } + + File.open('rack-protection.gemspec', 'w') { |f| f << content } +end + +task :gemspec => 'rack-protection.gemspec' +task :default => :spec +task :test => :spec diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack-protection.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack-protection.rb new file mode 100644 index 000000000..1633086e2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack-protection.rb @@ -0,0 +1 @@ +require "rack/protection" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection.rb new file mode 100644 index 000000000..604e749c4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection.rb @@ -0,0 +1,40 @@ +require 'rack/protection/version' +require 'rack' + +module Rack + module Protection + autoload :AuthenticityToken, 'rack/protection/authenticity_token' + autoload :Base, 'rack/protection/base' + autoload :EscapedParams, 'rack/protection/escaped_params' + autoload :FormToken, 'rack/protection/form_token' + autoload :FrameOptions, 'rack/protection/frame_options' + autoload :HttpOrigin, 'rack/protection/http_origin' + autoload :IPSpoofing, 'rack/protection/ip_spoofing' + autoload :JsonCsrf, 'rack/protection/json_csrf' + autoload :PathTraversal, 'rack/protection/path_traversal' + autoload :RemoteReferrer, 'rack/protection/remote_referrer' + autoload :RemoteToken, 'rack/protection/remote_token' + autoload :SessionHijacking, 'rack/protection/session_hijacking' + autoload :XSSHeader, 'rack/protection/xss_header' + + def self.new(app, options = {}) + # does not include: RemoteReferrer, AuthenticityToken and FormToken + except = Array options[:except] + use_these = Array options[:use] + Rack::Builder.new do + use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer + use ::Rack::Protection::AuthenticityToken,options if use_these.include? :authenticity_token + use ::Rack::Protection::FormToken, options if use_these.include? :form_token + use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options + use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin + use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing + use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf + use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal + use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token + use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking + use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header + run app + end.to_app + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/authenticity_token.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/authenticity_token.rb new file mode 100644 index 000000000..e4524e4a6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/authenticity_token.rb @@ -0,0 +1,31 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts unsafe HTTP requests if a given access token matches the token + # included in the session. + # + # Compatible with Rails and rack-csrf. + # + # Options: + # + # authenticity_param: Defines the param's name that should contain the token on a request. + # + class AuthenticityToken < Base + default_options :authenticity_param => 'authenticity_token' + + def accepts?(env) + session = session env + token = session[:csrf] ||= session['_csrf_token'] || random_string + safe?(env) || + env['HTTP_X_CSRF_TOKEN'] == token || + Request.new(env).params[options[:authenticity_param]] == token + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/base.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/base.rb new file mode 100755 index 000000000..fe6ab37d2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/base.rb @@ -0,0 +1,121 @@ +require 'rack/protection' +require 'digest' +require 'logger' +require 'uri' + +module Rack + module Protection + class Base + DEFAULT_OPTIONS = { + :reaction => :default_reaction, :logging => true, + :message => 'Forbidden', :encryptor => Digest::SHA1, + :session_key => 'rack.session', :status => 403, + :allow_empty_referrer => true, + :report_key => "protection.failed", + :html_types => %w[text/html application/xhtml] + } + + attr_reader :app, :options + + def self.default_options(options) + define_method(:default_options) { super().merge(options) } + end + + def self.default_reaction(reaction) + alias_method(:default_reaction, reaction) + end + + def default_options + DEFAULT_OPTIONS + end + + def initialize(app, options = {}) + @app, @options = app, default_options.merge(options) + end + + def safe?(env) + %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD'] + end + + def accepts?(env) + raise NotImplementedError, "#{self.class} implementation pending" + end + + def call(env) + unless accepts? env + instrument env + result = react env + end + result or app.call(env) + end + + def react(env) + result = send(options[:reaction], env) + result if Array === result and result.size == 3 + end + + def warn(env, message) + return unless options[:logging] + l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) + l.warn(message) + end + + def instrument(env) + return unless i = options[:instrumenter] + env['rack.protection.attack'] = self.class.name.split('::').last.downcase + i.instrument('rack.protection', env) + end + + def deny(env) + warn env, "attack prevented by #{self.class}" + [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] + end + + def report(env) + warn env, "attack reported by #{self.class}" + env[options[:report_key]] = true + end + + def session?(env) + env.include? options[:session_key] + end + + def session(env) + return env[options[:session_key]] if session? env + fail "you need to set up a session middleware *before* #{self.class}" + end + + def drop_session(env) + session(env).clear if session? env + end + + def referrer(env) + ref = env['HTTP_REFERER'].to_s + return if !options[:allow_empty_referrer] and ref.empty? + URI.parse(ref).host || Request.new(env).host + rescue URI::InvalidURIError + end + + def origin(env) + env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN'] + end + + def random_string(secure = defined? SecureRandom) + secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) + rescue NotImplementedError + random_string false + end + + def encrypt(value) + options[:encryptor].hexdigest value.to_s + end + + alias default_reaction deny + + def html?(headers) + return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } + options[:html_types].include? header.last[/^\w+\/\w+/] + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/escaped_params.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/escaped_params.rb new file mode 100644 index 000000000..3283deca2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/escaped_params.rb @@ -0,0 +1,87 @@ +require 'rack/protection' +require 'rack/utils' + +begin + require 'escape_utils' +rescue LoadError +end + +module Rack + module Protection + ## + # Prevented attack:: XSS + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting + # + # Automatically escapes Rack::Request#params so they can be embedded in HTML + # or JavaScript without any further issues. Calls +html_safe+ on the escaped + # strings if defined, to avoid double-escaping in Rails. + # + # Options: + # escape:: What escaping modes to use, should be Symbol or Array of Symbols. + # Available: :html (default), :javascript, :url + class EscapedParams < Base + extend Rack::Utils + + class << self + alias escape_url escape + public :escape_html + end + + default_options :escape => :html, + :escaper => defined?(EscapeUtils) ? EscapeUtils : self + + def initialize(*) + super + + modes = Array options[:escape] + @escaper = options[:escaper] + @html = modes.include? :html + @javascript = modes.include? :javascript + @url = modes.include? :url + + if @javascript and not @escaper.respond_to? :escape_javascript + fail("Use EscapeUtils for JavaScript escaping.") + end + end + + def call(env) + request = Request.new(env) + get_was = handle(request.GET) + post_was = handle(request.POST) rescue nil + app.call env + ensure + request.GET.replace get_was if get_was + request.POST.replace post_was if post_was + end + + def handle(hash) + was = hash.dup + hash.replace escape(hash) + was + end + + def escape(object) + case object + when Hash then escape_hash(object) + when Array then object.map { |o| escape(o) } + when String then escape_string(object) + else nil + end + end + + def escape_hash(hash) + hash = hash.dup + hash.each { |k,v| hash[k] = escape(v) } + hash + end + + def escape_string(str) + str = @escaper.escape_url(str) if @url + str = @escaper.escape_html(str) if @html + str = @escaper.escape_javascript(str) if @javascript + str + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/form_token.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/form_token.rb new file mode 100644 index 000000000..eb12af759 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/form_token.rb @@ -0,0 +1,23 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts submitted forms if a given access token matches the token + # included in the session. Does not expect such a token from Ajax request. + # + # This middleware is not used when using the Rack::Protection collection, + # since it might be a security issue, depending on your application + # + # Compatible with Rails and rack-csrf. + class FormToken < AuthenticityToken + def accepts?(env) + env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb new file mode 100644 index 000000000..bce75c47e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/frame_options.rb @@ -0,0 +1,37 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Clickjacking + # Supported browsers:: Internet Explorer 8, Firefox 3.6.9, Opera 10.50, + # Safari 4.0, Chrome 4.1.249.1042 and later + # More infos:: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header + # + # Sets X-Frame-Options header to tell the browser avoid embedding the page + # in a frame. + # + # Options: + # + # frame_options:: Defines who should be allowed to embed the page in a + # frame. Use :deny to forbid any embedding, :sameorigin + # to allow embedding from the same origin (default). + class FrameOptions < Base + default_options :frame_options => :sameorigin + + def frame_options + @frame_options ||= begin + frame_options = options[:frame_options] + frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str + frame_options.to_str + end + end + + def call(env) + status, headers, body = @app.call(env) + headers['X-Frame-Options'] ||= frame_options if html? headers + [status, headers, body] + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/http_origin.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/http_origin.rb new file mode 100644 index 000000000..4eb9e4df1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/http_origin.rb @@ -0,0 +1,32 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: Google Chrome 2, Safari 4 and later + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # http://tools.ietf.org/html/draft-abarth-origin + # + # Does not accept unsafe HTTP requests when value of Origin HTTP request header + # does not match default or whitelisted URIs. + class HttpOrigin < Base + DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } + default_reaction :deny + + def base_url(env) + request = Rack::Request.new(env) + port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme] + "#{request.scheme}://#{request.host}#{port}" + end + + def accepts?(env) + return true if safe? env + return true unless origin = env['HTTP_ORIGIN'] + return true if base_url(env) == origin + Array(options[:origin_whitelist]).include? origin + end + + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/ip_spoofing.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/ip_spoofing.rb new file mode 100644 index 000000000..3e404ad7c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/ip_spoofing.rb @@ -0,0 +1,23 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: IP spoofing + # Supported browsers:: all + # More infos:: http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ + # + # Detect (some) IP spoofing attacks. + class IPSpoofing < Base + default_reaction :deny + + def accepts?(env) + return true unless env.include? 'HTTP_X_FORWARDED_FOR' + ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) + return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] + return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] + true + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb new file mode 100644 index 000000000..f163f6869 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/json_csrf.rb @@ -0,0 +1,35 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://flask.pocoo.org/docs/security/#json-security + # + # JSON GET APIs are vulnerable to being embedded as JavaScript while the + # Array prototype has been patched to track data. Checks the referrer + # even on GET requests if the content type is JSON. + class JsonCsrf < Base + alias react deny + + def call(env) + request = Request.new(env) + status, headers, body = app.call(env) + + if has_vector? request, headers + warn env, "attack prevented by #{self.class}" + react(env) or [status, headers, body] + else + [status, headers, body] + end + end + + def has_vector?(request, headers) + return false if request.xhr? + return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ + origin(request.env).nil? and referrer(request.env) != request.host + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb new file mode 100644 index 000000000..0f58a6430 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/path_traversal.rb @@ -0,0 +1,47 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Directory traversal + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Directory_traversal + # + # Unescapes '/' and '.', expands +path_info+. + # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. + class PathTraversal < Base + def call(env) + path_was = env["PATH_INFO"] + env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? + app.call env + ensure + env["PATH_INFO"] = path_was + end + + def cleanup(path) + if path.respond_to?(:encoding) + # Ruby 1.9+ M17N + encoding = path.encoding + dot = '.'.encode(encoding) + slash = '/'.encode(encoding) + else + # Ruby 1.8 + dot = '.' + slash = '/' + end + + parts = [] + unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash) + + unescaped.split(slash).each do |part| + next if part.empty? or part == dot + part == '..' ? parts.pop : parts << part + end + + cleaned = slash + parts.join(slash) + cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} + cleaned + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_referrer.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_referrer.rb new file mode 100644 index 000000000..5375ebc3d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_referrer.rb @@ -0,0 +1,20 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Does not accept unsafe HTTP requests if the Referer [sic] header is set to + # a different host. + class RemoteReferrer < Base + default_reaction :deny + + def accepts?(env) + safe?(env) or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_token.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_token.rb new file mode 100644 index 000000000..ed25e5935 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/remote_token.rb @@ -0,0 +1,22 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: CSRF + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery + # + # Only accepts unsafe HTTP requests if a given access token matches the token + # included in the session *or* the request comes from the same origin. + # + # Compatible with Rails and rack-csrf. + class RemoteToken < AuthenticityToken + default_reaction :deny + + def accepts?(env) + super or referrer(env) == Request.new(env).host + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/session_hijacking.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/session_hijacking.rb new file mode 100644 index 000000000..4ab047a12 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/session_hijacking.rb @@ -0,0 +1,36 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Session Hijacking + # Supported browsers:: all + # More infos:: http://en.wikipedia.org/wiki/Session_hijacking + # + # Tracks request properties like the user agent in the session and empties + # the session if those properties change. This essentially prevents attacks + # from Firesheep. Since all headers taken into consideration can be + # spoofed, too, this will not prevent determined hijacking attempts. + class SessionHijacking < Base + default_reaction :drop_session + default_options :tracking_key => :tracking, :encrypt_tracking => true, + :track => %w[HTTP_USER_AGENT HTTP_ACCEPT_LANGUAGE] + + def accepts?(env) + session = session env + key = options[:tracking_key] + if session.include? key + session[key].all? { |k,v| v == encrypt(env[k]) } + else + session[key] = {} + options[:track].each { |k| session[key][k] = encrypt(env[k]) } + end + end + + def encrypt(value) + value = value.to_s.downcase + options[:encrypt_tracking] ? super(value) : value + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/version.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/version.rb new file mode 100644 index 000000000..dba6c9b97 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/version.rb @@ -0,0 +1,16 @@ +module Rack + module Protection + def self.version + VERSION + end + + SIGNATURE = [1, 5, 3] + VERSION = SIGNATURE.join('.') + + VERSION.extend Comparable + def VERSION.<=>(other) + other = other.split('.').map { |i| i.to_i } if other.respond_to? :split + SIGNATURE <=> Array(other) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb new file mode 100644 index 000000000..6bb148616 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/lib/rack/protection/xss_header.rb @@ -0,0 +1,25 @@ +require 'rack/protection' + +module Rack + module Protection + ## + # Prevented attack:: Non-permanent XSS + # Supported browsers:: Internet Explorer 8 and later + # More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx + # + # Sets X-XSS-Protection header to tell the browser to block attacks. + # + # Options: + # xss_mode:: How the browser should prevent the attack (default: :block) + class XSSHeader < Base + default_options :xss_mode => :block, :nosniff => true + + def call(env) + status, headers, body = @app.call(env) + headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers + headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff] + [status, headers, body] + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/rack-protection.gemspec b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/rack-protection.gemspec new file mode 100644 index 000000000..0a86646e9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/rack-protection.gemspec @@ -0,0 +1,118 @@ +# Run `rake rack-protection.gemspec` to update the gemspec. +Gem::Specification.new do |s| + # general infos + s.name = "rack-protection" + s.version = "1.5.3" + s.description = "You should use protection!" + s.homepage = "http://github.com/rkh/rack-protection" + s.summary = s.description + s.license = 'MIT' + + # generated from git shortlog -sn + s.authors = [ + "Konstantin Haase", + "Alex Rodionov", + "Patrick Ellis", + "Jason Staten", + "ITO Nobuaki", + "Jeff Welling", + "Matteo Centenaro", + "Egor Homakov", + "Florian Gilcher", + "Fojas", + "Igor Bochkariov", + "Mael Clerambault", + "Martin Mauch", + "Renne Nissinen", + "SAKAI, Kazuaki", + "Stanislav Savulchik", + "Steve Agalloco", + "TOBY", + "Thais Camilo and Konstantin Haase", + "Vipul A M", + "Akzhan Abdulin", + "brookemckim", + "Bj\u{f8}rge N\u{e6}ss", + "Chris Heald", + "Chris Mytton", + "Corey Ward", + "Dario Cravero", + "David Kellum" + ] + + # generated from git shortlog -sne + s.email = [ + "konstantin.mailinglists@googlemail.com", + "p0deje@gmail.com", + "jstaten07@gmail.com", + "patrick@soundcloud.com", + "jeff.welling@gmail.com", + "bugant@gmail.com", + "daydream.trippers@gmail.com", + "florian.gilcher@asquera.de", + "developer@fojasaur.us", + "ujifgc@gmail.com", + "mael@clerambault.fr", + "martin.mauch@gmail.com", + "rennex@iki.fi", + "kaz.july.7@gmail.com", + "s.savulchik@gmail.com", + "steve.agalloco@gmail.com", + "toby.net.info.mail+git@gmail.com", + "dev+narwen+rkh@rkh.im", + "vipulnsward@gmail.com", + "akzhan.abdulin@gmail.com", + "brooke@digitalocean.com", + "bjoerge@bengler.no", + "cheald@gmail.com", + "self@hecticjeff.net", + "coreyward@me.com", + "dario@uxtemple.com", + "dek-oss@gravitext.com", + "homakov@gmail.com" + ] + + # generated from git ls-files + s.files = [ + "License", + "README.md", + "Rakefile", + "lib/rack-protection.rb", + "lib/rack/protection.rb", + "lib/rack/protection/authenticity_token.rb", + "lib/rack/protection/base.rb", + "lib/rack/protection/escaped_params.rb", + "lib/rack/protection/form_token.rb", + "lib/rack/protection/frame_options.rb", + "lib/rack/protection/http_origin.rb", + "lib/rack/protection/ip_spoofing.rb", + "lib/rack/protection/json_csrf.rb", + "lib/rack/protection/path_traversal.rb", + "lib/rack/protection/remote_referrer.rb", + "lib/rack/protection/remote_token.rb", + "lib/rack/protection/session_hijacking.rb", + "lib/rack/protection/version.rb", + "lib/rack/protection/xss_header.rb", + "rack-protection.gemspec", + "spec/authenticity_token_spec.rb", + "spec/base_spec.rb", + "spec/escaped_params_spec.rb", + "spec/form_token_spec.rb", + "spec/frame_options_spec.rb", + "spec/http_origin_spec.rb", + "spec/ip_spoofing_spec.rb", + "spec/json_csrf_spec.rb", + "spec/path_traversal_spec.rb", + "spec/protection_spec.rb", + "spec/remote_referrer_spec.rb", + "spec/remote_token_spec.rb", + "spec/session_hijacking_spec.rb", + "spec/spec_helper.rb", + "spec/xss_header_spec.rb" + ] + + # dependencies + s.add_dependency "rack" + s.add_development_dependency "rack-test" + s.add_development_dependency "rspec", "~> 2.0" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/authenticity_token_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/authenticity_token_spec.rb new file mode 100644 index 000000000..ec8e34bd4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/authenticity_token_spec.rb @@ -0,0 +1,48 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::AuthenticityToken do + it_behaves_like "any rack application" + + it "denies post requests without any token" do + post('/').should_not be_ok + end + + it "accepts post requests with correct X-CSRF-Token header" do + post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") + last_response.should be_ok + end + + it "denies post requests with wrong X-CSRF-Token header" do + post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") + last_response.should_not be_ok + end + + it "accepts post form requests with correct authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}) + last_response.should be_ok + end + + it "denies post form requests with wrong authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}) + last_response.should_not be_ok + end + + it "prevents ajax requests without a valid token" do + post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should_not be_ok + end + + it "allows for a custom authenticity token param" do + mock_app do + use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param' + run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } + end + + post('/', {"csrf_param" => "a"}, 'rack.session' => {:csrf => "a"}) + last_response.should be_ok + end + + it "sets a new csrf token for the session in env, even after a 'safe' request" do + get('/', {}, {}) + env['rack.session'][:csrf].should_not be_nil + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/base_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/base_spec.rb new file mode 100644 index 000000000..415634aca --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/base_spec.rb @@ -0,0 +1,40 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::Base do + + subject { described_class.new(lambda {}) } + + describe "#random_string" do + it "outputs a string of 32 characters" do + subject.random_string.length.should == 32 + end + end + + describe "#referrer" do + it "Reads referrer from Referer header" do + env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"} + subject.referrer(env).should == "bar.com" + end + + it "Reads referrer from Host header when Referer header is relative" do + env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"} + subject.referrer(env).should == "foo.com" + end + + it "Reads referrer from Host header when Referer header is missing" do + env = {"HTTP_HOST" => "foo.com"} + subject.referrer(env).should == "foo.com" + end + + it "Returns nil when Referer header is missing and allow_empty_referrer is false" do + env = {"HTTP_HOST" => "foo.com"} + subject.options[:allow_empty_referrer] = false + subject.referrer(env).should be_nil + end + + it "Returns nil when Referer header is invalid" do + env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"} + subject.referrer(env).should be_nil + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/escaped_params_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/escaped_params_spec.rb new file mode 100644 index 000000000..17afa808f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/escaped_params_spec.rb @@ -0,0 +1,43 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::EscapedParams do + it_behaves_like "any rack application" + + context 'escaping' do + it 'escapes html entities' do + mock_app do |env| + request = Rack::Request.new(env) + [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] + end + get '/', :foo => "" + body.should == '<bar>' + end + + it 'leaves normal params untouched' do + mock_app do |env| + request = Rack::Request.new(env) + [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] + end + get '/', :foo => "bar" + body.should == 'bar' + end + + it 'copes with nested arrays' do + mock_app do |env| + request = Rack::Request.new(env) + [200, {'Content-Type' => 'text/plain'}, [request.params['foo']['bar']]] + end + get '/', :foo => {:bar => ""} + body.should == '<bar>' + end + + it 'leaves cache-breaker params untouched' do + mock_app do |env| + [200, {'Content-Type' => 'text/plain'}, ['hi']] + end + + get '/?95df8d9bf5237ad08df3115ee74dcb10' + body.should == 'hi' + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/form_token_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/form_token_spec.rb new file mode 100644 index 000000000..0c4aa3712 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/form_token_spec.rb @@ -0,0 +1,33 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::FormToken do + it_behaves_like "any rack application" + + it "denies post requests without any token" do + post('/').should_not be_ok + end + + it "accepts post requests with correct X-CSRF-Token header" do + post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") + last_response.should be_ok + end + + it "denies post requests with wrong X-CSRF-Token header" do + post('/', {}, 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") + last_response.should_not be_ok + end + + it "accepts post form requests with correct authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}) + last_response.should be_ok + end + + it "denies post form requests with wrong authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}) + last_response.should_not be_ok + end + + it "accepts ajax requests without a valid token" do + post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest").should be_ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/frame_options_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/frame_options_spec.rb new file mode 100644 index 000000000..a34ab0ad2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/frame_options_spec.rb @@ -0,0 +1,39 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::FrameOptions do + it_behaves_like "any rack application" + + it 'should set the X-Frame-Options' do + get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "SAMEORIGIN" + end + + it 'should not set the X-Frame-Options for other content types' do + get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"].should be_nil + end + + it 'should allow changing the protection mode' do + # I have no clue what other modes are available + mock_app do + use Rack::Protection::FrameOptions, :frame_options => :deny + run DummyApp + end + + get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "DENY" + end + + + it 'should allow changing the protection mode to a string' do + # I have no clue what other modes are available + mock_app do + use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo" + run DummyApp + end + + get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "ALLOW-FROM foo" + end + + it 'should not override the header if already set' do + mock_app with_headers("X-Frame-Options" => "allow") + get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"].should == "allow" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/http_origin_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/http_origin_spec.rb new file mode 100644 index 000000000..7fff61f71 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/http_origin_spec.rb @@ -0,0 +1,38 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::HttpOrigin do + it_behaves_like "any rack application" + + before(:each) do + mock_app do + use Rack::Protection::HttpOrigin + run DummyApp + end + end + + %w(GET HEAD POST PUT DELETE).each do |method| + it "accepts #{method} requests with no Origin" do + send(method.downcase, '/').should be_ok + end + end + + %w(GET HEAD).each do |method| + it "accepts #{method} requests with non-whitelisted Origin" do + send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should be_ok + end + end + + %w(POST PUT DELETE).each do |method| + it "denies #{method} requests with non-whitelisted Origin" do + send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com').should_not be_ok + end + + it "accepts #{method} requests with whitelisted Origin" do + mock_app do + use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com'] + run DummyApp + end + send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com').should be_ok + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/ip_spoofing_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/ip_spoofing_spec.rb new file mode 100644 index 000000000..729083732 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/ip_spoofing_spec.rb @@ -0,0 +1,35 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::IPSpoofing do + it_behaves_like "any rack application" + + it 'accepts requests without X-Forward-For header' do + get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_REAL_IP' => '4.3.2.1') + last_response.should be_ok + end + + it 'accepts requests with proper X-Forward-For header' do + get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') + last_response.should be_ok + end + + it 'denies requests where the client spoofs X-Forward-For but not the IP' do + get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5') + last_response.should_not be_ok + end + + it 'denies requests where the client spoofs the IP but not X-Forward-For' do + get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', + 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') + last_response.should_not be_ok + end + + it 'denies requests where IP and X-Forward-For are spoofed but not X-Real-IP' do + get('/', {}, + 'HTTP_CLIENT_IP' => '1.2.3.5', + 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', + 'HTTP_X_REAL_IP' => '1.2.3.4') + last_response.should_not be_ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/json_csrf_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/json_csrf_spec.rb new file mode 100644 index 000000000..264f94832 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/json_csrf_spec.rb @@ -0,0 +1,58 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::JsonCsrf do + it_behaves_like "any rack application" + + describe 'json response' do + before do + mock_app { |e| [200, {'Content-Type' => 'application/json'}, []]} + end + + it "denies get requests with json responses with a remote referrer" do + get('/', {}, 'HTTP_REFERER' => 'http://evil.com').should_not be_ok + end + + it "accepts requests with json responses with a remote referrer when there's an origin header set" do + get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com').should be_ok + end + + it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do + get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com').should be_ok + end + + it "accepts get requests with json responses with a local referrer" do + get('/', {}, 'HTTP_REFERER' => '/').should be_ok + end + + it "accepts get requests with json responses with no referrer" do + get('/', {}).should be_ok + end + + it "accepts XHR requests" do + get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest').should be_ok + end + + end + + describe 'not json response' do + + it "accepts get requests with 304 headers" do + mock_app { |e| [304, {}, []]} + get('/', {}).status.should == 304 + end + + end + + describe 'with drop_session as default reaction' do + it 'still denies' do + mock_app do + use Rack::Protection, :reaction => :drop_session + run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} + end + + session = {:foo => :bar} + get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session) + last_response.should_not be_ok + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/path_traversal_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/path_traversal_spec.rb new file mode 100644 index 000000000..9ebd7f045 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/path_traversal_spec.rb @@ -0,0 +1,41 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::PathTraversal do + it_behaves_like "any rack application" + + context 'escaping' do + before do + mock_app { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO']]] } + end + + %w[/foo/bar /foo/bar/ / /.f /a.x].each do |path| + it("does not touch #{path.inspect}") { get(path).body.should == path } + end + + { # yes, this is ugly, feel free to change that + '/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/', + '/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/', + '//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd' + }.each do |a, b| + it("replaces #{a.inspect} with #{b.inspect}") { get(a).body.should == b } + end + + it 'should be able to deal with PATH_INFO = nil (fcgi?)' do + app = Rack::Protection::PathTraversal.new(proc { 42 }) + app.call({}).should be == 42 + end + end + + if "".respond_to?(:encoding) # Ruby 1.9+ M17N + context "PATH_INFO's encoding" do + before do + @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] }) + end + + it 'should remain unchanged as ASCII-8BIT' do + body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0] + body.should == 'ASCII-8BIT' + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/protection_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/protection_spec.rb new file mode 100755 index 000000000..083670c90 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/protection_spec.rb @@ -0,0 +1,105 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection do + it_behaves_like "any rack application" + + it 'passes on options' do + mock_app do + use Rack::Protection, :track => ['HTTP_FOO'] + run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } + end + + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' + session[:foo].should be == :bar + + get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR' + session.should be_empty + end + + it 'passes errors through if :reaction => :report is used' do + mock_app do + use Rack::Protection, :reaction => :report + run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] } + end + + session = {:foo => :bar} + post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com') + last_response.should be_ok + body.should == "true" + end + + describe "#react" do + it 'prevents attacks and warns about it' do + io = StringIO.new + mock_app do + use Rack::Protection, :logger => Logger.new(io) + run DummyApp + end + post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') + io.string.should match /prevented.*Origin/ + end + + it 'reports attacks if reaction is to report' do + io = StringIO.new + mock_app do + use Rack::Protection, :reaction => :report, :logger => Logger.new(io) + run DummyApp + end + post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') + io.string.should match /reported.*Origin/ + io.string.should_not match /prevented.*Origin/ + end + + it 'passes errors to reaction method if specified' do + io = StringIO.new + Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect } + mock_app do + use Rack::Protection, :reaction => :special, :logger => Logger.new(io) + run DummyApp + end + post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') + io.string.should match /HTTP_ORIGIN.*malicious.com/ + io.string.should_not match /reported|prevented/ + end + end + + describe "#html?" do + context "given an appropriate content-type header" do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" } + it { should be_true } + end + + context "given an inappropriate content-type header" do + subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" } + it { should be_false } + end + + context "given no content-type header" do + subject { Rack::Protection::Base.new(nil).html?({}) } + it { should be_false } + end + end + + describe "#instrument" do + let(:env) { { 'rack.protection.attack' => 'base' } } + let(:instrumenter) { double('Instrumenter') } + + after do + app.instrument(env) + end + + context 'with an instrumenter specified' do + let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) } + + it { instrumenter.should_receive(:instrument).with('rack.protection', env) } + end + + context 'with no instrumenter specified' do + let(:app) { Rack::Protection::Base.new(nil) } + + it { instrumenter.should_not_receive(:instrument) } + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_referrer_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_referrer_spec.rb new file mode 100644 index 000000000..657cd4065 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_referrer_spec.rb @@ -0,0 +1,31 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::RemoteReferrer do + it_behaves_like "any rack application" + + it "accepts post requests with no referrer" do + post('/').should be_ok + end + + it "does not accept post requests with no referrer if allow_empty_referrer is false" do + mock_app do + use Rack::Protection::RemoteReferrer, :allow_empty_referrer => false + run DummyApp + end + post('/').should_not be_ok + end + + it "should allow post request with a relative referrer" do + post('/', {}, 'HTTP_REFERER' => '/').should be_ok + end + + it "accepts post requests with the same host in the referrer" do + post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.com') + last_response.should be_ok + end + + it "denies post requests with a remote referrer" do + post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') + last_response.should_not be_ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_token_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_token_spec.rb new file mode 100644 index 000000000..e1abf8473 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/remote_token_spec.rb @@ -0,0 +1,42 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::RemoteToken do + it_behaves_like "any rack application" + + it "accepts post requests with no referrer" do + post('/').should be_ok + end + + it "accepts post requests with a local referrer" do + post('/', {}, 'HTTP_REFERER' => '/').should be_ok + end + + it "denies post requests with a remote referrer and no token" do + post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') + last_response.should_not be_ok + end + + it "accepts post requests with a remote referrer and correct X-CSRF-Token header" do + post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', + 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "a") + last_response.should be_ok + end + + it "denies post requests with a remote referrer and wrong X-CSRF-Token header" do + post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', + 'rack.session' => {:csrf => "a"}, 'HTTP_X_CSRF_TOKEN' => "b") + last_response.should_not be_ok + end + + it "accepts post form requests with a remote referrer and correct authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'HTTP_REFERER' => 'http://example.com/foo', + 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "a"}) + last_response.should be_ok + end + + it "denies post form requests with a remote referrer and wrong authenticity_token field" do + post('/', {"authenticity_token" => "a"}, 'HTTP_REFERER' => 'http://example.com/foo', + 'HTTP_HOST' => 'example.org', 'rack.session' => {:csrf => "b"}) + last_response.should_not be_ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/session_hijacking_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/session_hijacking_spec.rb new file mode 100644 index 000000000..21cd13fdc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/session_hijacking_spec.rb @@ -0,0 +1,55 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::SessionHijacking do + it_behaves_like "any rack application" + + it "accepts a session without changes to tracked parameters" do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session + get '/', {}, 'rack.session' => session + session[:foo].should == :bar + end + + it "denies requests with a changing User-Agent header" do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' + session.should be_empty + end + + it "accepts requests with a changing Accept-Encoding header" do + # this is tested because previously it led to clearing the session + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' + session.should_not be_empty + end + + it "denies requests with a changing Accept-Language header" do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'b' + session.should be_empty + end + + it "accepts requests with the same Accept-Language header" do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' + session.should_not be_empty + end + + it "comparison of Accept-Language header is not case sensitive" do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'a' + get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_LANGUAGE' => 'A' + session.should_not be_empty + end + + it "accepts requests with a changing Version header"do + session = {:foo => :bar} + get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0' + get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1' + session[:foo].should == :bar + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/spec_helper.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/spec_helper.rb new file mode 100644 index 000000000..6a3a8f828 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/spec_helper.rb @@ -0,0 +1,163 @@ +require 'rack/protection' +require 'rack/test' +require 'rack' +require 'forwardable' +require 'stringio' + +if defined? Gem.loaded_specs and Gem.loaded_specs.include? 'rack' + version = Gem.loaded_specs['rack'].version.to_s +else + version = Rack.release + '.0' +end + +if version == "1.3" + Rack::Session::Abstract::ID.class_eval do + private + def prepare_session(env) + session_was = env[ENV_SESSION_KEY] + env[ENV_SESSION_KEY] = SessionHash.new(self, env) + env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) + env[ENV_SESSION_KEY].merge! session_was if session_was + end + end +end + +unless Rack::MockResponse.method_defined? :header + Rack::MockResponse.send(:alias_method, :header, :headers) +end + +module DummyApp + def self.call(env) + Thread.current[:last_env] = env + body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok') + [200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]] + end +end + +module TestHelpers + extend Forwardable + def_delegators :last_response, :body, :headers, :status, :errors + def_delegators :current_session, :env_for + attr_writer :app + + def app + @app || mock_app(DummyApp) + end + + def mock_app(app = nil, &block) + app = block if app.nil? and block.arity == 1 + if app + klass = described_class + mock_app do + use Rack::Head + use(Rack::Config) { |e| e['rack.session'] ||= {}} + use klass + run app + end + else + @app = Rack::Lint.new Rack::Builder.new(&block).to_app + end + end + + def with_headers(headers) + proc { [200, {'Content-Type' => 'text/plain'}.merge(headers), ['ok']] } + end + + def env + Thread.current[:last_env] + end +end + +# see http://blog.101ideas.cz/posts/pending-examples-via-not-implemented-error-in-rspec.html +module NotImplementedAsPending + def self.included(base) + base.class_eval do + alias_method :__finish__, :finish + remove_method :finish + end + end + + def finish(reporter) + if @exception.is_a?(NotImplementedError) + from = @exception.backtrace[0] + message = "#{@exception.message} (from #{from})" + @pending_declared_in_example = message + metadata[:pending] = true + @exception = nil + end + + __finish__(reporter) + end + + RSpec::Core::Example.send :include, self +end + +RSpec.configure do |config| + config.expect_with :rspec, :stdlib + config.include Rack::Test::Methods + config.include TestHelpers +end + +shared_examples_for 'any rack application' do + it "should not interfere with normal get requests" do + get('/').should be_ok + body.should == 'ok' + end + + it "should not interfere with normal head requests" do + head('/').should be_ok + end + + it 'should not leak changes to env' do + klass = described_class + detector = Struct.new(:app) + + detector.send(:define_method, :call) do |env| + was = env.dup + res = app.call(env) + was.each do |k,v| + next if env[k] == v + fail "env[#{k.inspect}] changed from #{v.inspect} to #{env[k].inspect}" + end + res + end + + mock_app do + use Rack::Head + use(Rack::Config) { |e| e['rack.session'] ||= {}} + use detector + use klass + run DummyApp + end + + get('/..', :foo => '').should be_ok + end + + it 'allows passing on values in env' do + klass = described_class + detector = Struct.new(:app) + changer = Struct.new(:app) + + detector.send(:define_method, :call) do |env| + res = app.call(env) + env['foo.bar'].should == 42 + res + end + + changer.send(:define_method, :call) do |env| + env['foo.bar'] = 42 + app.call(env) + end + + mock_app do + use Rack::Head + use(Rack::Config) { |e| e['rack.session'] ||= {}} + use detector + use klass + use changer + run DummyApp + end + + get('/').should be_ok + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/xss_header_spec.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/xss_header_spec.rb new file mode 100644 index 000000000..05c94697b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/rack-protection-1.5.3/spec/xss_header_spec.rb @@ -0,0 +1,56 @@ +require File.expand_path('../spec_helper.rb', __FILE__) + +describe Rack::Protection::XSSHeader do + it_behaves_like "any rack application" + + it 'should set the X-XSS-Protection' do + get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"].should == "1; mode=block" + end + + it 'should set the X-XSS-Protection for XHTML' do + get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"].should == "1; mode=block" + end + + it 'should not set the X-XSS-Protection for other content types' do + get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"].should be_nil + end + + it 'should allow changing the protection mode' do + # I have no clue what other modes are available + mock_app do + use Rack::Protection::XSSHeader, :xss_mode => :foo + run DummyApp + end + + get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"].should == "1; mode=foo" + end + + it 'should not override the header if already set' do + mock_app with_headers("X-XSS-Protection" => "0") + get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"].should == "0" + end + + it 'should set the X-Content-Type-Options' do + get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"].should == "nosniff" + end + + + it 'should set the X-Content-Type-Options for other content types' do + get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"].should == "nosniff" + end + + + it 'should allow changing the nosniff-mode off' do + mock_app do + use Rack::Protection::XSSHeader, :nosniff => false + run DummyApp + end + + get('/').headers["X-Content-Type-Options"].should be_nil + end + + it 'should not override the header if already set X-Content-Type-Options' do + mock_app with_headers("X-Content-Type-Options" => "sniff") + get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"].should == "sniff" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/.yardopts b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/.yardopts new file mode 100644 index 000000000..60e00e9a3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/.yardopts @@ -0,0 +1,5 @@ +--readme README.md +--title 'Sinatra API Documentation' +--charset utf-8 +--markup markdown +'lib/**/*.rb' - '*.md' diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/AUTHORS.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/AUTHORS.md new file mode 100644 index 000000000..c9c9c9f5c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/AUTHORS.md @@ -0,0 +1,77 @@ +Sinatra was designed and developed by Blake Mizerany in California. + +### Current Team + +* **Konstantin Haase** (maintainer) +* **Zachary Scott** +* **Katrina Owen** + +### Alumni + +* **Blake Mizerany** (creator) +* **Ryan Tomayko** +* **Simon Rozet** + +### Thanks + +Sinatra would not have been possible without strong company backing. +In the past, financial and emotional support have been provided mainly by +[Heroku](http://heroku.com), [GitHub](https://github.com) and +[Engine Yard](http://www.engineyard.com/), and is now taken care of by +[Travis CI](http://travis-ci.com/). + +Special thanks to the following extraordinary individuals, without whom +Sinatra would not be possible: + +* [Ryan Tomayko](http://tomayko.com/) (rtomayko) for constantly fixing + whitespace errors __60d5006__ +* [Ezra Zygmuntowicz](http://brainspl.at/) (ezmobius) for initial help and + letting Blake steal some of merbs internal code. +* [Chris Schneider](http://gittr.com) (cschneid) for The Book, the blog, + [irclogger.com](http://irclogger.com/sinatra/), and a bunch of useful + patches. +* [Markus Prinz](http://nuclearsquid.com/) (cypher) for patches over the + years, caring about the README, and hanging in there when times were rough. +* [Erik Kastner](http://metaatem.net/) (kastner) for fixing `MIME_TYPES` under + Rack 0.5. +* [Ben Bleything](http://blog.bleything.net/) (bleything) for caring about HTTP + status codes and doc fixes. +* [Igal Koshevoy](http://twitter.com/igalko) (igal) for root path detection under + Thin/Passenger. +* [Jon Crosby](http://joncrosby.me/) (jcrosby) for coffee breaks, doc fixes, and + just because, man. +* [Karel Minarik](https://github.com/karmi) (karmi) for screaming until the + website came back up. +* [Jeremy Evans](http://code.jeremyevans.net/) (jeremyevans) for unbreaking + optional path params (twice!) +* [The GitHub guys](https://github.com/) for stealing Blake's table. +* [Nickolas Means](http://nmeans.org/) (nmeans) for Sass template support. +* [Victor Hugo Borja](https://github.com/vic) (vic) for splat'n routes specs and + doco. +* [Avdi Grimm](http://avdi.org/) (avdi) for basic RSpec support. +* [Jack Danger Canty](http://jåck.com/) for a more accurate root directory + and for making me watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just + now. +* Mathew Walker for making escaped paths work with static files. +* Millions of Us for having the problem that led to Sinatra's conception. +* [Songbird](http://getsongbird.com/) for the problems that helped Sinatra's + future become realized. +* [Rick Olson](http://techno-weenie.net/) (technoweenie) for the killer plug + at RailsConf '08. +* Steven Garcia for the amazing custom artwork you see on 404's and 500's +* [Pat Nakajima](http://patnakajima.com/) (nakajima) for fixing non-nested + params in nested params Hash's. +* Gabriel Andretta for having people wonder whether our documentation is + actually in English or in Spanish. +* Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang, + Mickael Riga, Bernhard Essl, Janos Hardi, Kouhei Yanagita and + "burningTyger" for willingly translating whatever ends up in the README. +* [Wordy](https://wordy.com/) for proofreading our README. **73e137d** +* cactus for digging through code and specs, multiple times. +* Nicolás Sanguinetti (foca) for strong demand of karma and shaping + helpers/register. + +And last but not least: + +* [Frank Sinatra](http://www.sinatra.com/) (chairman of the board) for having so much class he + deserves a web-framework named after him. \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/CHANGES b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/CHANGES new file mode 100644 index 000000000..c0a64ed5d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/CHANGES @@ -0,0 +1,1323 @@ += 1.4.6 / 2015-03-2x + + * Improve tests and documentation. (Darío Hereñú, Seiichi Yonezawa, kyoendo, + John Voloski, Ferenc-, Renaud Martinet, Christian Haase, marocchino, + huoxito, Damir Svrtan, Amaury Medeiros, Jeremy Evans, Kashyap, shenqihui, + Ausmarton Fernandes, kami, Vipul A M, Lei Wu, 7stud, Taylor Shuler, + namusyaka, burningTyger, Cornelius Bock, detomastah, hakeda, John Hope, + Ruben Gonzalez, Andrey Deryabin, attilaolah, Anton Davydov, Nikita Penzin, + Dyego Costa) + + * Remove duplicate require of sinatra/base. (Alexey Muranov) + + * Escape HTML in 404 error page. (Andy Brody) + + * Refactor to method call in `Stream#close` and `#callback`. (Damir Svrtan) + + * Depend on latest version of Slim. (Damir Svrtan) + + * Fix compatibility with Tilt version 2. (Yegor Timoschenko) + + * Fix compatibility issue with Rack `pretty` method from ShowExceptions. + (Kashyap) + + * Show date in local time in exception messages. (tayler1) + + * Fix logo on error pages when using Ruby 1.8. (Jeremy Evans) + + * Upgrade test suite to Minitest version 5 and fix Ruby 2.2 compatibility. + (Vipul A M) + += 1.4.5 / 2014-04-08 + + * Improve tests and documentation. (Seiichi Yonezawa, Mike Gehard, Andrew + Deitrick, Matthew Nicholas Bradley, GoGo tanaka, Carlos Lazo, Shim Tw, + kyoendo, Roman Kuznietsov, Stanislav Chistenko, Ryunosuke SATO, Ben Lewis, + wuleicanada, Patricio Mac Adden, Thais Camilo) + + * Fix Ruby warnings. (Vipul A M, Piotr Szotkowski) + + * Fix template cache memory leak. (Scott Holden) + + * Work around UTF-8 bug in JRuby. (namusyaka) + + * Don't set charset for JSON mime-type (Sebastian Borrazas) + + * Fix bug in request.accept? that might trigger a NoMethodError. (sbonami) + += 1.4.4 / 2013-10-21 + + * Allow setting layout to false specifically for a single rendering engine. + (Matt Wildig) + + * Allow using wildcard in argument passed to `request.accept?`. (wilkie) + + * Treat missing Accept header like wild card. (Patricio Mac Adden) + + * Improve tests and documentation. (Darío Javier Cravero, Armen P., michelc, + Patricio Mac Adden, Matt Wildig, Vipul A M, utenmiki, George Timoschenko, + Diogo Scudelletti) + + * Fix Ruby warnings. (Vipul A M, Patricio Mac Adden) + + * Improve self-hosted server started by `run!` method or in classic mode. + (Tobias Bühlmann) + + * Reduce objects allocated per request. (Vipul A M) + + * Drop unused, undocumented options hash from Sinatra.new. (George Timoschenko) + + * Keep Content-Length header when response is a `Rack::File` or when streaming. + (Patricio Mac Adden, George Timoschenko) + + * Use reel if it's the only server available besides webrick. (Tobias Bühlmann) + + * Add `disable :traps` so setting up signal traps for self hosted server can be + skipped. (George Timoschenko) + + * The `status` option passed to `send_file` may now be a string. (George + Timoschenko) + + * Reduce file size of dev mode images for 404 and 500 pages. (Francis Go) + += 1.4.3 / 2013-06-07 + + * Running a Sinatra file directly or via `run!` it will now ignore an + empty $PORT env variable. (noxqsgit) + + * Improve documentation. (burningTyger, Patricio Mac Adden, + Konstantin Haase, Diogo Scudelletti, Dominic Imhof) + + * Expose matched pattern as env["sinatra.route"]. (Aman Gupta) + + * Fix warning on Ruby 2.0. (Craig Little) + + * Improve running subset of tests in isolation. (Viliam Pucik) + + * Reorder private/public methods. (Patricio Mac Adden) + + * Loosen version dependency for rack, so it runs with Rails 3.2. + (Konstantin Haase) + + * Request#accept? now returns true instead of a truthy value. (Alan Harris) + += 1.4.2 / 2013-03-21 + + * Fix parsing error for case where both the pattern and the captured part + contain a dot. (Florian Hanke, Konstantin Haase) + + * Missing Accept header is treated like */*. (Greg Denton) + + * Improve documentation. (Patricio Mac Adden, Joe Bottigliero) + += 1.4.1 / 2013-03-15 + + * Make delegated methods available in config.ru (Konstantin Haase) + += 1.4.0 / 2013-03-15 + + * Add support for LINK and UNLINK requests. (Konstantin Haase) + + * Add support for Yajl templates. (Jamie Hodge) + + * Add support for Rabl templates. (Jesse Cooke) + + * Add support for Wlang templates. (Bernard Lambeau) + + * Add support for Stylus templates. (Juan David Pastas, Konstantin Haase) + + * You can now pass a block to ERb, Haml, Slim, Liquid and Wlang templates, + which will be used when calling `yield` in the template. (Alexey Muranov) + + * When running in classic mode, no longer include Sinatra::Delegator in Object, + instead extend the main object only. (Konstantin Haase) + + * Improved route parsing: "/:name.?:format?" with "/foo.png" now matches to + {name: "foo", format: "png"} instead of {name: "foo.png"}. (Florian Hanke) + + * Add :status option support to send_file. (Konstantin Haase) + + * The `provides` condition now respects an earlier set content type. + (Konstantin Haase) + + * Exception#code is only used when :use_code is enabled. Moreover, it will + be ignored if the value is not between 400 and 599. You should use + Exception#http_status instead. (Konstantin Haase) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + + * Sinatra::Base.new now returns a Sinatra::Wrapper instance, exposing + #settings and #helpers, yet going through the middleware stack on #call. + It also implements a nice #inspect, so it plays nice with Rails' `rake + routes`. (Konstantin Haase) + + * In addition to WebRick, Thin and Mongrel, Sinatra will now automatically pick + up Puma, Trinidad, ControlTower or Net::HTTP::Server when installed. The + logic for picking the server has been improved and now depends on the Ruby + implementation used. (Mark Rada, Konstantin Haase, Patricio Mac Adden) + + * "Sinatra doesn't know this ditty" pages now show the app class when running + a modular application. This helps detecting where the response came from when + combining multiple modular apps. (Konstantin Haase) + + * When port is not set explicitly, use $PORT env variable if set and only + default to 4567 if not. Plays nice with foreman. (Konstantin Haase) + + * Allow setting layout on a per engine basis. (Zachary Scott, Konstantin Haase) + + * You can now use `register` directly in a classic app. (Konstantin Haase) + + * `redirect` now accepts URI or Addressable::URI instances. (Nicolas + Sanguinetti) + + * Have Content-Disposition header also include file name for `inline`, not + just for `attachment`. (Konstantin Haase) + + * Better compatibility to Rack 1.5. (James Tucker, Konstantin Haase) + + * Make route parsing regex more robust. (Zoltan Dezso, Konstantin Haase) + + * Improve Accept header parsing, expose parameters. (Pieter van de Bruggen, + Konstantin Haase) + + * Add `layout_options` render option. Allows you, amongst other things, to + render a layout from a different folder. (Konstantin Haase) + + * Explicitly setting `layout` to `nil` is treated like setting it to `false`. + (richo) + + * Properly escape attributes in Content-Type header. (Pieter van de Bruggen) + + * Default to only serving localhost in development mode. (Postmodern) + + * Setting status code to 404 in error handler no longer triggers not_found + handler. (Konstantin Haase) + + * The `protection` option now takes a `session` key for force + disabling/enabling session based protections. (Konstantin Haase) + + * Add `x_cascade` option to disable `X-Cascade` header on missing route. + (Konstantin Haase) + + * Improve documentation. (Kashyap, Stanislav Chistenko, Zachary Scott, + Anthony Accomazzo, Peter Suschlik, Rachel Mehl, ymmtmsys, Anurag Priyam, + burningTyger, Tony Miller, akicho8, Vasily Polovnyov, Markus Prinz, + Alexey Muranov, Erik Johnson, Vipul A M, Konstantin Haase) + + * Convert documentation to Markdown. (Kashyap, Robin Dupret, burningTyger, + Vasily Polovnyov, Iain Barnett, Giuseppe Capizzi, Neil West) + + * Don't set not_found content type to HTML in development mode with custom + not_found handler. (Konstantin Haase) + + * Fix mixed indentation for private methods. (Robin Dupret) + + * Recalculate Content-Length even if hard coded if body is reset. Relevant + mostly for error handlers. (Nathan Esquenazi, Konstantin Haase) + + * Plus sign is once again kept as such when used for URL matches. (Konstantin + Haase) + + * Take views option into account for template caching. (Konstantin Haase) + + * Consistent use of `headers` instead of `header` internally. (Patricio Mac Adden) + + * Fix compatibility to RDoc 4. (Bohuslav Kabrda) + + * Make chat example work with latest jQuery. (loveky, Tony Miller) + + * Make tests run without warnings. (Patricio Mac Adden) + + * Make sure value returned by `mime_type` is a String or nil, even when a + different object is passed in, like an AcceptEntry. (Konstantin Haase) + + * Exceptions in `after` filter are now handled like any other exception. + (Nathan Esquenazi) + += 1.3.6 (backport release) / 2013-03-15 + +Backported from 1.4.0: + + * Take views option into account for template caching. (Konstantin Haase) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + += 1.3.5 / 2013-02-25 + + * Fix for RubyGems 2.0 (Uchio KONDO) + + * Improve documentation (Konstantin Haase) + + * No longer override `define_singleton_method`. (Konstantin Haase) + += 1.3.4 / 2013-01-26 + + * Improve documentation. (Kashyap, Stanislav Chistenko, Konstantin Haase, + ymmtmsys, Anurag Priyam) + + * Adjustments to template system to work with Tilt edge. (Konstantin Haase) + + * Fix streaming with latest Rack release. (Konstantin Haase) + + * Fix default content type for Sinatra::Response with latest Rack release. + (Konstantin Haase) + + * Fix regression where + was no longer treated like space. (Ross Boucher) + + * Status, headers and body will be set correctly in an after filter when using + halt in a before filter or route. (Konstantin Haase) + += 1.3.3 / 2012-08-19 + + * Improved documentation. (burningTyger, Konstantin Haase, Gabriel Andretta, + Anurag Priyam, michelc) + + * No longer modify the load path. (Konstantin Haase) + + * When keeping a stream open, set up callback/errback correctly to deal with + clients closing the connection. (Konstantin Haase) + + * Fix bug where having a query param and a URL param by the same name would + concatenate the two values. (Konstantin Haase) + + * Prevent duplicated log output when application is already wrapped in a + `Rack::CommonLogger`. (Konstantin Haase) + + * Fix issue where `Rack::Link` and Rails were preventing indefinite streaming. + (Konstantin Haase) + + * No longer cause warnings when running Ruby with `-w`. (Konstantin Haase) + + * HEAD requests on static files no longer report a Content-Length of 0, but + instead the proper length. (Konstantin Haase) + + * When protecting against CSRF attacks, drop the session instead of refusing + the request. (Konstantin Haase) + += 1.3.2 / 2011-12-30 + + * Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it, + too. (Konstantin Haase) + + * Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`. + (Konstantin Haase) + + * Route specific params are now available in the block passed to #stream. + (Konstantin Haase) + + * Fix bug where rendering a second template in the same request, after the + first one raised an exception, skipped the default layout. (Nathan Baum) + + * Fix bug where parameter escaping got enabled when disabling a different + protection. (Konstantin Haase) + + * Fix regression: Filters without a pattern may now again manipulate the params + hash. (Konstantin Haase) + + * Added examples directory. (Konstantin Haase) + + * Improved documentation. (Gabriel Andretta, Markus Prinz, Erick Zetta, Just + Lest, Adam Vaughan, Aleksander Dąbrowski) + + * Improved MagLev support. (Tim Felgentreff) + += 1.3.1 / 2011-10-05 + + * Support adding more than one callback to the stream object. (Konstantin + Haase) + + * Fix for infinite loop when streaming on 1.9.2 with Thin from a modular + application (Konstantin Haase) + += 1.3.0 / 2011-09-30 + + * Added `stream` helper method for easily creating streaming APIs, Server + Sent Events or even WebSockets. See README for more on that topic. + (Konstantin Haase) + + * If a HTTP 1.1 client is redirected from a different verb than GET, use 303 + instead of 302 by default. You may still pass 302 explicitly. Fixes AJAX + redirects in Internet Explorer 9 (to be fair, everyone else is doing it + wrong and IE is behaving correct). (Konstantin Haase) + + * Added support for HTTP PATCH requests. (Konstantin Haase) + + * Use rack-protection to defend against common opportunistic attacks. + (Josh Lane, Jacob Burkhart, Konstantin Haase) + + * Support for Creole templates, Creole is a standardized wiki markup, + supported by many wiki implementations. (Konstanin Haase) + + * The `erubis` method has been deprecated. If Erubis is available, Sinatra + will automatically use it for rendering ERB templates. `require 'erb'` + explicitly to prevent that behavior. (Magnus Holm, Ryan Tomayko, Konstantin + Haase) + + * Patterns now match against the escaped URLs rather than the unescaped + version. This makes Sinatra confirm with RFC 2396 section 2.2 and RFC 2616 + section 3.2.3 (escaped reserved characters should not be treated like the + unescaped version), meaning that "/:name" will also match `/foo%2Fbar`, but + not `/foo/bar`. To avoid incompatibility, pattern matching has been + adjusted. Moreover, since we do no longer need to keep an unescaped version + of path_info around, we handle all changes to `env['PATH_INFO']` correctly. + (Konstantin Haase) + + * `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in + modular applications. (Konstantin Haase) + + * Set up `Rack::Logger` or `Rack::NullLogger` depending on whether logging + was enabled or not. Also, expose that logger with the `logger` helper + method. (Konstantin Haase) + + * The sessions setting may be an options hash now. (Konstantin Haase) + + * Important: Ruby 1.8.6 support has been dropped. This version also depends + on at least Rack 1.3.0. This means that it is incompatible with Rails prior + to 3.1.0. Please use 1.2.x if you require an earlier version of Ruby or + Rack, which we will continue to supply with bug fixes. (Konstantin Haase) + + * Renamed `:public` to `:public_folder` to avoid overriding Ruby's built-in + `public` method/keyword. `set(:public, ...)` is still possible but shows a + warning. (Konstantin Haase) + + * It is now possible to use a different target class for the top level DSL + (aka classic style) than `Sinatra::Application` by setting + `Delegator.target`. This was mainly introduced to ease testing. (Konstantin + Haase) + + * Error handlers defined for an error class will now also handle subclasses + of that class, unless more specific error handlers exist. (Konstantin + Haase) + + * Error handling respects Exception#code, again. (Konstantin Haase) + + * Changing a setting will merge hashes: `set(:x, :a => 1); set(:x :b => 2)` + will result in `{:a => 1, :b => 2}`. Use `set(:x, {:a => 1}, true)` to + avoid this behavior. (Konstantin Haase) + + * Added `request.accept?` and `request.preferred_type` to ease dealing with + `Accept` headers. (Konstantin Haase) + + * Added `:static_cache_control` setting to automatically set cache control + headers to static files. (Kenichi Nakamura) + + * Added `informal?`, `success?`, `redirect?`, `client_error?`, + `server_error?` and `not_found?` helper methods to ease dealing with status + codes. (Konstantin Haase) + + * Uses SecureRandom to generate default session secret. (Konstantin Haase) + + * The `attachment` helper will set Content-Type (if it hasn't been set yet) + depending on the supplied file name. (Vasiliy Ermolovich) + + * Conditional requests on `etag` helper now work properly for unsafe HTTP + methods. (Matthew Schinckel, Konstantin Haase) + + * The `last_modified` helper does not stop execution and change the status code + if the status code is something different than 200. (Konstantin Haase) + + * Added support for If-Unmodified-Since header. (Konstantin Haase) + + * `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew + Armenia) + + * `Sinatra::Base.run!` takes a block allowing access to the Rack handler. + (David Waite) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Exception objects are now passed to error handlers. (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * Middleware setup is now distributed across multiple methods, allowing + Sinatra extensions to easily hook into the setup process. (Konstantin + Haase) + + * Internal refactoring and minor performance improvements. (Konstantin Haase) + + * Move Sinatra::VERSION to separate file, so it can be checked without + loading Sinatra. (Konstantin Haase) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + += 1.2.9 (backports release) / 2013-03-15 + +IMPORTANT: THIS IS THE LAST 1.2.x RELEASE, PLEASE UPGRADE. + + * Display EOL warning when loading Sinatra. (Konstantin Haase) + + * Improve documentation. (Anurag Priyam, Konstantin Haase) + + * Do not modify the load path. (Konstantin Haase) + + * Display deprecation warning if RUBY_IGNORE_CALLERS is used. (Konstantin Haase) + + * Add backports library so we can still run on Ruby 1.8.6. (Konstantin Haase) + += 1.2.8 (backports release) / 2011-12-30 + +Backported from 1.3.2: + +* Fix bug where rendering a second template in the same request after the + first one raised an exception skipped the default layout (Nathan Baum) + += 1.2.7 (backports release) / 2011-09-30 + +Custom changes: + + * Fix Ruby 1.8.6 issue with Accept header parsing. (Konstantin Haase) + +Backported from 1.3.0: + + * Ignore `to_ary` on response bodies. Fixes compatibility to Rails 3.1. + (Konstantin Haase) + + * `Sinatra.run!` now prints to stderr rather than stdout. (Andrew Armenia) + + * Automatic `app_file` detection now works in directories containing brackets + (Konstantin Haase) + + * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori + Ishikawa, Konstantin Haase) + + * Also specify charset in Content-Type header for JSON. (Konstantin Haase) + + * Rack handler names will not be converted to lower case internally, this + allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. + Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) + + * Fix uninitialized instance variable warning. (David Kellum) + + * Command line options now complain if value passed to `-p` is not a valid + integer. (Konstantin Haase) + + * Fix handling of broken query params when displaying exceptions. (Luke + Jahnke) + += 1.2.6 / 2011-05-01 + + * Fix broken delegation, backport delegation tests from Sinatra 1.3. + (Konstantin Haase) + += 1.2.5 / 2011-04-30 + + * Restore compatibility with Ruby 1.8.6. (Konstantin Haase) + += 1.2.4 / 2011-04-30 + + * Sinatra::Application (classic style) does not use a session secret in + development mode, so sessions are not invalidated after every request when + using Shotgun. (Konstantin Haase) + + * The request object was shared between multiple Sinatra instances in the + same middleware chain. This caused issues if any non-sinatra routing + happend in-between two of those instances, or running a request twice + against an application (described in the README). The caching was reverted. + See GH#239 and GH#256 for more infos. (Konstantin Haase) + + * Fixes issues where the top level DSL was interfering with method_missing + proxies. This issue surfaced when Rails 3 was used with older Sass versions + and Sinatra >= 1.2.0. (Konstantin Haase) + + * Sinatra::Delegator.delegate is now able to delegate any method names, even + those containing special characters. This allows better integration into + other programming languages on Rubinius (probably on the JVM, too), like + Fancy. (Konstantin Haase) + + * Remove HEAD request logic and let Rack::Head handle it instead. (Paolo + "Nusco" Perrotta) + += 1.2.3 / 2011-04-13 + + * This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2, + however, if you want to use a newer Tilt version, you have to upgrade to at + least this version of Sinatra. (Konstantin Haase) + + * Helpers dealing with time, like `expires`, handle objects that pretend to be + numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase) + += 1.2.2 / 2011-04-08 + + * The `:provides => :js` condition now matches both `application/javascript` + and `text/javascript`. The `:provides => :xml` condition now matches both + `application/xml` and `text/xml`. The `Content-Type` header is set + accordingly. If the client accepts both, the `application/*` version is + preferred, since the `text/*` versions are deprecated. (Konstantin Haase) + + * The `provides` condition now handles wildcards in `Accept` headers correctly. + Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`. + (Konstantin Haase) + + * When parsing `Accept` headers, `Content-Type` preferences are honored + according to RFC 2616 section 14.1. (Konstantin Haase) + + * URIs passed to the `url` helper or `redirect` may now use any schema to be + identified as absolute URIs, not only `http` or `https`. (Konstantin Haase) + + * Handles `Content-Type` strings that already contain parameters correctly in + `content_type` (example: `content_type "text/plain; charset=utf-16"`). + (Konstantin Haase) + + * If a route with an empty pattern is defined (`get("") { ... }`) requests with + an empty path info match this route instead of "/". (Konstantin Haase) + + * In development environment, when running under a nested path, the image URIs + on the error pages are set properly. (Konstantin Haase) + += 1.2.1 / 2011-03-17 + + * Use a generated session secret when using `enable :sessions`. (Konstantin + Haase) + + * Fixed a bug where the wrong content type was used if no content type was set + and a template engine was used with a different engine for the layout with + different default content types, say Less embedded in Slim. (Konstantin + Haase) + + * README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé, + Gregor Schmidt) + += 1.2.0 / 2011-03-03 + + * Added `slim` rendering method for rendering Slim templates. (Steve + Hodgkiss) + + * The `markaby` rendering method now allows passing a block, making inline + usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase) + + * All render methods now take a `:layout_engine` option, allowing to use a + layout in a different template language. Even more useful than using this + directly (`erb :index, :layout_engine => :haml`) is setting this globally for + a template engine that otherwise does not support layouts, like Markdown or + Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase) + + * Before and after filters now support conditions, both with and without + patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase) + + * Added a `url` helper method which constructs absolute URLs. Copes with + reverse proxies and Rack handlers correctly. Aliased to `to`, so you can + write `redirect to('/foo')`. (Konstantin Haase) + + * If running on 1.9, patterns for routes and filters now support named + captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`. + (Steve Price) + + * All rendering methods now take a `:scope` option, which renders them in + another context. Note that helpers and instance variables will be + unavailable if you use this feature. (Paul Walker) + + * The behavior of `redirect` can now be configured with `absolute_redirects` + and `prefixed_redirects`. (Konstantin Haase) + + * `send_file` now allows overriding the Last-Modified header, which defaults + to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase) + + * You can use your own template lookup method by defining `find_template`. + This allows, among other things, using more than one views folder. + (Konstantin Haase) + + * Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel + Andretta, Konstantin Haase) + + * Improved error handling. (cactus, Konstantin Haase) + + * Skip missing template engines in tests correctly. (cactus) + + * Sinatra now ships with a Gemfile for development dependencies, since it eases + supporting different platforms, like JRuby. (Konstantin Haase) + += 1.1.4 (backports release) / 2011-04-13 + + * Compatible with Tilt 1.3. (Konstantin Haase) + += 1.1.3 / 2011-02-20 + + * Fixed issues with `user_agent` condition if the user agent header is missing. + (Konstantin Haase) + + * Fix some routing tests that have been skipped by accident (Ross A. Baker) + + * Fix rendering issues with Builder and Nokogiri (Konstantin Haase) + + * Replace last_modified helper with better implementation. (cactus, + Konstantin Haase) + + * Fix issue with charset not being set when using `provides` condition. + (Konstantin Haase) + + * Fix issue with `render` not picking up all alternative file extensions for + a rendering engine - it was not possible to register ".html.erb" without + tricks. (Konstantin Haase) + += 1.1.2 / 2010-10-25 + +Like 1.1.1, but with proper CHANGES file. + += 1.1.1 / 2010-10-25 + + * README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov) + and Portuguese (Luciano Sousa). + + * Nested templates without a `:layout` option can now be used from the layout + template without causing an infinite loop. (Konstantin Haase) + + * Inline templates are now encoding aware and can therefore be used with + unicode characters on Ruby 1.9. Magic comments at the beginning of the file + will be honored. (Konstantin Haase) + + * Default `app_file` is set correctly when running with bundler. Using + bundler caused Sinatra not to find the `app_file` and therefore not to find + the `views` folder on it's own. (Konstantin Haase) + + * Better handling of Content-Type when using `send_file`: If file extension + is unknown, fall back to `application/octet-stream` and do not override + content type if it has already been set, except if `:type` is passed + explicitly (Konstantin Haase) + + * Path is no longer cached if changed between handlers that do pattern + matching. This means you can change `request.path_info` in a pattern + matching before filter. (Konstantin Haase) + + * Headers set by cache_control now always set max_age as an Integer, making + sure it is compatible with RFC2616. (Konstantin Haase) + + * Further improved handling of string encodings on Ruby 1.9, templates now + honor default_encoding and URLs support unicode characters. (Konstantin + Haase) + += 1.1.0 / 2010-10-24 + + * Before and after filters now support pattern matching, including the + ability to use captures: "before('/user/:name') { |name| ... }". This + avoids manual path checking. No performance loss if patterns are avoided. + (Konstantin Haase) + + * It is now possible to render SCSS files with the `scss` method, which + behaves exactly like `sass` except for the different file extension and + assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase) + + * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`, + `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown, + Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates. + (Konstantin Haase) + + * Now supports byte-range requests (the HTTP_RANGE header) for static files. + Multi-range requests are not supported, however. (Jens Alfke) + + * You can now use #settings method from class and top level for convenience. + (Konstantin Haase) + + * Setting multiple values now no longer relies on #to_hash and therefore + accepts any Enumerable as parameter. (Simon Rozet) + + * Nested templates default the `layout` option to `false` rather than `true`. + This eases the use of partials. If you wanted to render one haml template + embedded in another, you had to call `haml :partial, {}, :layout => false`. + As you almost never want the partial to be wrapped in the standard layout + in this situation, you now only have to call `haml :partial`. Passing in + `layout` explicitly is still possible. (Konstantin Haase) + + * If a the return value of one of the render functions is used as a response + body and the content type has not been set explicitly, Sinatra chooses a + content type corresponding to the rendering engine rather than just using + "text/html". (Konstantin Haase) + + * README is now available in Chinese (Wu Jiang), French (Mickael Riga), + German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos + Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese + README has been updated (Kouhei Yanagita). + + * It is now possible to access Sinatra's template_cache from the outside. + (Nick Sutterer) + + * The `last_modified` method now also accepts DateTime instances and makes + sure the header will always be set to a string. (Konstantin Haase) + + * 599 now is a legal status code. (Steve Shreeve) + + * This release is compatible with Ruby 1.9.2. Sinatra was trying to read + non existent files Ruby added to the call stack. (Shota Fukumori, + Konstantin Haase) + + * Prevents a memory leak on 1.8.6 in production mode. Note, however, that + this is due to a bug in 1.8.6 and request will have the additional overhead + of parsing templates again on that version. It is recommended to use at + least Ruby 1.8.7. (Konstantin Haase) + + * Compares last modified date correctly. `last_modified` was halting only + when the 'If-Modified-Since' header date was equal to the time specified. + Now, it halts when is equal or later than the time specified (Gabriel + Andretta). + + * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra + application under a subpath in Rails 3, the PATH_INFO is not prefixed with + a slash and no routes did match. (José Valim) + + * Better handling of encodings in 1.9, defaults params encoding to UTF-8. + (Konstantin Haase) + + * `show_exceptions` handling is now triggered after custom error handlers, if + it is set to `:after_handlers`, thus not disabling those handler in + development mode. (pangel, Konstantin Haase) + + * Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino) + + * `send_file` now always respects the `:type` option if set. Previously it + was discarded if no matching mime type was found, which made it impossible + to directly pass a mime type. (Konstantin Haase) + + * `redirect` always redirects to an absolute URI, even if a relative URI was + passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe + Garcia Ballester, Anthony Williams) + + * Broken examples for using Erubis, Haml and Test::Unit in README have been + fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden) + + * Sinatra now handles SIGTERM correctly. (Patrick Collison) + + * Fixes an issue with inline templates in modular applications that manually + call `run!`. (Konstantin Haase) + + * Spaces after inline template names are now ignored (Konstantin Haase) + + * It's now possible to use Sinatra with different package management + systems defining a custom require. (Konstantin Haase) + + * Lighthouse has been dropped in favor of GitHub issues. + + * Tilt is now a dependency and therefore no longer ships bundled with + Sinatra. (Ryan Tomayko, Konstantin Haase) + + * Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported. + (Konstantin Haase) + += 1.0 / 2010-03-23 + + * It's now possible to register blocks to run after each request using + after filters. After filters run at the end of each request, after + routes and error handlers. (Jimmy Schementi) + + * Sinatra now uses Tilt for rendering + templates. This adds support for template caching, consistent + template backtraces, and support for new template engines, like + mustache and liquid. (Ryan Tomayko) + + * ERB, Erubis, and Haml templates are now compiled the first time + they're rendered instead of being string eval'd on each invocation. + Benchmarks show a 5x-10x improvement in render time. This also + reduces the number of objects created, decreasing pressure on Ruby's + GC. (Ryan Tomayko) + + * New 'settings' method gives access to options in both class and request + scopes. This replaces the 'options' method. (Chris Wanstrath) + + * 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. (Ryan Tomayko) + + * New 'erubis' helper method for rendering ERB template with Erubis. The + erubis gem is required. (Dylan Egan) + + * 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 + (Ryan Tomayko) + + * New 'expires' helper method is like cache_control but takes an + integer number of seconds or Time object: + expires 300, :public, :must_revalidate + (Ryan Tomayko) + + * New request.secure? method for checking for an SSL connection. + (Adam Wiggins) + + * Sinatra apps can now be run with a `-o ` argument to specify + the address to bind to. (Ryan Tomayko) + + * Rack::Session::Cookie is now added to the middleware pipeline when + running in test environments if the :sessions option is set. + (Simon Rozet) + + * Route handlers, before filters, templates, error mappings, and + middleware are now resolved dynamically up the inheritance hierarchy + when needed instead of duplicating the superclass's version when + a new Sinatra::Base subclass is created. This should fix a variety + of issues with extensions that need to add any of these things + to the base class. (Ryan Tomayko) + + * Exception error handlers always override the raise_errors option now. + Previously, all exceptions would be raised outside of the application + when the raise_errors option was enabled, even if an error handler was + defined for that exception. The raise_errors option now controls + whether unhandled exceptions are raised (enabled) or if a generic 500 + error is returned (disabled). (Ryan Tomayko) + + * The X-Cascade response header is set to 'pass' when no matching route + is found or all routes pass. (Josh Peek) + + * Filters do not run when serving static files anymore. (Ryan Tomayko) + + * pass takes an optional block to be used as the route handler if no + subsequent route matches the request. (Blake Mizerany) + +The following Sinatra features have been obsoleted (removed entirely) in +the 1.0 release: + + * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test` + module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`, + `put_it`, `delete_it`, and `head_it` helper methods. The + [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should + be used instead. + + * Test framework specific libraries (`sinatra/test/spec`, + `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See + http://www.sinatrarb.com/testing.html for instructions on setting up a + testing environment under each of these frameworks. + + * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead. + `Sinatra::Base` acts more like `Sinatra::Default` in development mode. + For example, static file serving and sexy development error pages are + enabled by default. + + * Auto-requiring template libraries in the `erb`, `builder`, `haml`, + and `sass` methods is obsolete due to thread-safety issues. You must + require the template libraries explicitly in your app. + + * The `:views_directory` option to rendering methods is obsolete; use + `:views` instead. + + * The `:haml` and `:sass` options to rendering methods are obsolete. + Template engine options should be passed in the second Hash argument + instead. + + * The `use_in_file_templates` method is obsolete. Use + `enable :inline_templates` or `set :inline_templates, 'path/to/file'` + + * The 'media_type' helper method is obsolete. Use 'mime_type' instead. + + * The 'mime' main and class method is obsolete. Use 'mime_type' instead. + + * The request-level `send_data` method is no longer supported. + + * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer + supported. This may effect extensions written for versions prior to 0.9.2. + See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html) + for the officially supported extensions API. + + * The `set_option` and `set_options` methods are obsolete; use `set` + instead. + + * The `:env` setting (`settings.env`) is obsolete; use `:environment` + instead. + + * The request level `stop` method is obsolete; use `halt` instead. + + * The request level `entity_tag` method is obsolete; use `etag` + instead. + + * The request level `headers` method (HTTP response headers) is obsolete; + use `response['Header-Name']` instead. + + * `Sinatra.application` is obsolete; use `Sinatra::Application` instead. + + * Using `Sinatra.application = nil` to reset an application is obsolete. + This should no longer be necessary. + + * Using `Sinatra.default_options` to set base configuration items is + obsolete; use `Sinatra::Base.set(key, value)` instead. + + * The `Sinatra::ServerError` exception is obsolete. All exceptions raised + within a request are now treated as internal server errors and result in + a 500 response status. + + * The `:methodoverride' option to enable/disable the POST _method hack is + obsolete; use `:method_override` instead. + += 0.9.2 / 2009-05-18 + + * This version is compatible with Rack 1.0. [Rein Henrichs] + + * The development-mode unhandled exception / error page has been + greatly enhanced, functionally and aesthetically. The error + page is used when the :show_exceptions option is enabled and an + exception propagates outside of a route handler or before filter. + [Simon Rozet / Matte Noble / Ryan Tomayko] + + * Backtraces that move through templates now include filenames and + line numbers where possible. [#51 / S. Brent Faulkner] + + * All templates now have an app-level option for setting default + template options (:haml, :sass, :erb, :builder). The app-level + option value must be a Hash if set and is merged with the + template options specified to the render method (Base#haml, + Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko] + + * The method signature for all template rendering methods has + been unified: "def engine(template, options={}, locals={})". + The options Hash now takes the generic :views, :layout, and + :locals options but also any template-specific options. The + generic options are removed before calling the template specific + render method. Locals may be specified using either the + :locals key in the options hash or a second Hash option to the + rendering method. [#191 / Ryan Tomayko] + + * The receiver is now passed to "configure" blocks. This + allows for the following idiom in top-level apps: + configure { |app| set :foo, app.root + '/foo' } + [TJ Holowaychuck / Ryan Tomayko] + + * The "sinatra/test" lib is deprecated and will be removed in + Sinatra 1.0. This includes the Sinatra::Test module and + Sinatra::TestHarness class in addition to all the framework + test helpers that were deprecated in 0.9.1. The Rack::Test + lib should be used instead: http://gitrdoc.com/brynary/rack-test + [#176 / Simon Rozet] + + * Development mode source file reloading has been removed. The + "shotgun" (http://rtomayko.github.com/shotgun/) program can be + used to achieve the same basic functionality in most situations. + Passenger users should use the "tmp/always_restart.txt" + file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko] + + * Auto-requiring template libs in the erb, builder, haml, and + sass methods is deprecated due to thread-safety issues. You must + require the template libs explicitly in your app file. [Simon Rozet] + + * A new Sinatra::Base#route_missing method was added. route_missing + is sent when no route matches the request or all route handlers + pass. The default implementation forwards the request to the + downstream app when running as middleware (i.e., "@app" is + non-nil), or raises a NotFound exception when no downstream app + is defined. Subclasses can override this method to perform custom + route miss logic. [Jon Crosby] + + * A new Sinatra::Base#route_eval method was added. The method + yields to the block and throws :halt with the result. Subclasses + can override this method to tap into the route execution logic. + [TJ Holowaychuck] + + * Fix the "-x" (enable request mutex / locking) command line + argument. Passing -x now properly sets the :lock option. + [S. Brent Faulkner, Ryan Tomayko] + + * Fix writer ("foo=") and predicate ("foo?") methods in extension + modules not being added to the registering class. + [#172 / Pat Nakajima] + + * Fix in-file templates when running alongside activesupport and + fatal errors when requiring activesupport before sinatra + [#178 / Brian Candler] + + * Fix various issues running on Google AppEngine. + [Samuel Goebert, Simon Rozet] + + * Fix in-file templates __END__ detection when __END__ exists with + other stuff on a line [Yoji Shidara] + += 0.9.1.1 / 2009-03-09 + + * Fix directory traversal vulnerability in default static files + route. See [#177] for more info. + += 0.9.1 / 2009-03-01 + + * Sinatra now runs under Ruby 1.9.1 [#61] + + * Route patterns (splats, :named, or Regexp captures) are now + passed as arguments to the block. [#140] + + * The "helpers" method now takes a variable number of modules + along with the normal block syntax. [#133] + + * New request-level #forward method for middleware components: passes + the env to the downstream app and merges the response status, headers, + and body into the current context. [#126] + + * Requests are now automatically forwarded to the downstream app when + running as middleware and no matching route is found or all routes + pass. + + * New simple API for extensions/plugins to add DSL-level and + request-level methods. Use Sinatra.register(mixin) to extend + the DSL with all public methods defined in the mixin module; + use Sinatra.helpers(mixin) to make all public methods defined + in the mixin module available at the request level. [#138] + See http://www.sinatrarb.com/extensions.html for details. + + * Named parameters in routes now capture the "." character. This makes + routes like "/:path/:filename" match against requests like + "/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt". + Previously, the route would not match at all. + + * Added request-level "redirect back" to redirect to the referring + URL. + + * Added a new "clean_trace" option that causes backtraces dumped + to rack.errors and displayed on the development error page to + omit framework and core library backtrace lines. The option is + enabled by default. [#77] + + * The ERB output buffer is now available to helpers via the @_out_buf + instance variable. + + * It's now much easier to test sessions in unit tests by passing a + ":session" option to any of the mock request methods. e.g., + get '/', {}, :session => { 'foo' => 'bar' } + + * The testing framework specific files ('sinatra/test/spec', + 'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated. + See http://sinatrarb.com/testing.html for instructions on setting up + a testing environment with these frameworks. + + * The request-level #send_data method from Sinatra 0.3.3 has been added + for compatibility but is deprecated. + + * Fix :provides causing crash on any request when request has no + Accept header [#139] + + * Fix that ERB templates were evaluated twice per "erb" call. + + * Fix app-level middleware not being run when the Sinatra application is + run as middleware. + + * Fixed some issues with running under Rack's CGI handler caused by + writing informational stuff to stdout. + + * Fixed that reloading was sometimes enabled when starting from a + rackup file [#110] + + * Fixed that "." in route patterns erroneously matched any character + instead of a literal ".". [#124] + += 0.9.0.4 / 2009-01-25 + + * Using halt with more than 1 args causes ArgumentError [#131] + * using halt in a before filter doesn't modify response [#127] + * Add deprecated Sinatra::EventContext to unbreak plugins [#130] + * Give access to GET/POST params in filters [#129] + * Preserve non-nested params in nested params hash [#117] + * Fix backtrace dump with Rack::Lint [#116] + += 0.9.0.3 / 2009-01-21 + + * Fall back on mongrel then webrick when thin not found. [#75] + * Use :environment instead of :env in test helpers to + fix deprecation warnings coming from framework. + * Make sinatra/test/rspec work again [#113] + * Fix app_file detection on windows [#118] + * Fix static files with Rack::Lint in pipeline [#121] + += 0.9.0.2 / 2009-01-18 + + * Halting a before block should stop processing of routes [#85] + * Fix redirect/halt in before filters [#85] + += 0.9.0 / 2009-01-18 + + * Works with and requires Rack >= 0.9.1 + + * Multiple Sinatra applications can now co-exist peacefully within a + single process. The new "Sinatra::Base" class can be subclassed to + establish a blank-slate Rack application or middleware component. + Documentation on using these features is forth-coming; the following + provides the basic gist: http://gist.github.com/38605 + + * Parameters with subscripts are now parsed into a nested/recursive + Hash structure. e.g., "post[title]=Hello&post[body]=World" yields + params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. + + * Regular expressions may now be used in route pattens; captures are + available at "params[:captures]". + + * New ":provides" route condition takes an array of mime types and + matches only when an Accept request header is present with a + corresponding type. [cypher] + + * New request-level "pass" method; immediately exits the current block + and passes control to the next matching route. + + * The request-level "body" method now takes a block; evaluation is + deferred until an attempt is made to read the body. The block must + return a String or Array. + + * New "route conditions" system for attaching rules for when a route + matches. The :agent and :host route options now use this system. + + * New "dump_errors" option controls whether the backtrace is dumped to + rack.errors when an exception is raised from a route. The option is + enabled by default for top-level apps. + + * Better default "app_file", "root", "public", and "views" location + detection; changes to "root" and "app_file" automatically cascade to + other options that depend on them. + + * Error mappings are now split into two distinct layers: exception + mappings and custom error pages. Exception mappings are registered + with "error(Exception)" and are run only when the app raises an + exception. Custom error pages are registered with "error(status_code)", + where "status_code" is an integer, and are run any time the response + has the status code specified. It's also possible to register an error + page for a range of status codes: "error(500..599)". + + * In-file templates are now automatically imported from the file that + requires 'sinatra'. The use_in_file_templates! method is still available + for loading templates from other files. + + * Sinatra's testing support is no longer dependent on Test::Unit. Requiring + 'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness + class, which can be used with any test framework. The 'sinatra/test/unit', + 'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files + can be required to setup a framework-specific testing environment. See the + README for more information. + + * Added support for Bacon (test framework). The 'sinatra/test/bacon' file + can be required to setup Sinatra test helpers on Bacon::Context. + + * Deprecated "set_option" and "set_options"; use "set" instead. + + * Deprecated the "env" option ("options.env"); use "environment" instead. + + * Deprecated the request level "stop" method; use "halt" instead. + + * Deprecated the request level "entity_tag" method; use "etag" instead. + Both "entity_tag" and "etag" were previously supported. + + * Deprecated the request level "headers" method (HTTP response headers); + use "response['Header-Name']" instead. + + * Deprecated "Sinatra.application"; use "Sinatra::Application" instead. + + * Deprecated setting Sinatra.application = nil to reset an application. + This should no longer be necessary. + + * Deprecated "Sinatra.default_options"; use + "Sinatra::Default.set(key, value)" instead. + + * Deprecated the "ServerError" exception. All Exceptions are now + treated as internal server errors and result in a 500 response + status. + + * Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it" + test helper methods. Use "get", "post", "put", "delete", and "head", + respectively, instead. + + * Removed Event and EventContext classes. Applications are defined in a + subclass of Sinatra::Base; each request is processed within an + instance. + += 0.3.3 / 2009-01-06 + + * Pin to Rack 0.4.0 (this is the last release on Rack 0.4) + + * Log unhandled exception backtraces to rack.errors. + + * Use RACK_ENV environment variable to establish Sinatra + environment when given. Thin sets this when started with + the -e argument. + + * BUG: raising Sinatra::NotFound resulted in a 500 response + code instead of 404. + + * BUG: use_in_file_templates! fails with CR/LF (#45) + + * BUG: Sinatra detects the app file and root path when run under + thin/passenger. + += 0.3.2 + + * BUG: Static and send_file read entire file into String before + sending. Updated to stream with 8K chunks instead. + + * Rake tasks and assets for building basic documentation website. + See http://sinatra.rubyforge.org + + * Various minor doc fixes. + += 0.3.1 + + * Unbreak optional path parameters [jeremyevans] + += 0.3.0 + + * Add sinatra.gemspec w/ support for github gem builds. Forks can now + enable the build gem option in github to get free username-sinatra.gem + builds: gem install username-sinatra.gem --source=http://gems.github.com/ + + * Require rack-0.4 gem; removes frozen rack dir. + + * Basic RSpec support; require 'sinatra/test/rspec' instead of + 'sinatra/test/spec' to use. [avdi] + + * before filters can modify request environment vars used for + routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting + type functionality. + + * In-file templates now uses @@ instead of ## as template separator. + + * Top-level environment test predicates: development?, test?, production? + + * Top-level "set", "enable", and "disable" methods for tweaking + app options. [rtomayko] + + * Top-level "use" method for building Rack middleware pipelines + leading to app. See README for usage. [rtomayko] + + * New "reload" option - set false to disable reloading in development. + + * New "host" option - host/ip to bind to [cschneid] + + * New "app_file" option - override the file to reload in development + mode [cschneid] + + * Development error/not_found page cleanup [sr, adamwiggins] + + * Remove a bunch of core extensions (String#to_param, String#from_param, + Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass) + + * Various grammar and formatting fixes to README; additions on + community and contributing [cypher] + + * Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api + + * Specs, documentation and fixes for splat'n routes [vic] + + * Fix whitespace errors across all source files. [rtomayko] + + * Fix streaming issues with Mongrel (body not closed). [bmizerany] + + * Fix various issues with environment not being set properly (configure + blocks not running, error pages not registering, etc.) [cypher] + + * Fix to allow locals to be passed to ERB templates [cschneid] + + * Fix locking issues causing random errors during reload in development. + + * Fix for escaped paths not resolving static files [Matthew Walker] + += 0.2.1 + + * File upload fix and minor tweaks. + += 0.2.0 + + * Initial gem release of 0.2 codebase. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Gemfile b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Gemfile new file mode 100644 index 000000000..4df6d3c5d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Gemfile @@ -0,0 +1,76 @@ +# Why use bundler? +# Well, not all development dependencies install on all rubies. Moreover, `gem +# install sinatra --development` doesn't work, as it will also try to install +# development dependencies of our dependencies, and those are not conflict free. +# So, here we are, `bundle install`. +# +# If you have issues with a gem: `bundle install --without-coffee-script`. + +RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE +source 'https://rubygems.org' unless ENV['QUICK'] +gemspec + +gem 'rake' +gem 'rack-test', '>= 0.6.2' +gem "minitest", "~> 5.0" + +# Allows stuff like `tilt=1.2.2 bundle install` or `tilt=master ...`. +# Used by the CI. +repos = {'tilt' => "rtomayko/tilt", 'rack' => "rack/rack"} + +%w[tilt rack].each do |lib| + dep = case ENV[lib] + when 'stable', nil then nil + when /(\d+\.)+\d+/ then "~> " + ENV[lib].sub("#{lib}-", '') + else {:github => repos[lib], :branch => dep} + end + gem lib, dep +end + +if RUBY_ENGINE == 'jruby' + gem 'nokogiri', '!= 1.5.0' + gem 'jruby-openssl' + gem 'trinidad' +end + +if RUBY_ENGINE == "ruby" and RUBY_VERSION > '1.9.2' + gem 'less', '~> 2.0' + gem 'therubyracer' + gem 'redcarpet' + gem 'wlang', '>= 2.0.1' + gem 'bluecloth' + gem 'rdiscount' + gem 'RedCloth' + gem 'puma' + gem 'net-http-server' + gem 'yajl-ruby' + gem 'nokogiri' + gem 'thin' + gem 'slim', '~> 2.0' + gem 'coffee-script', '>= 2.0' + gem 'rdoc' + gem 'kramdown' + gem 'maruku' + gem 'creole' + gem 'wikicloth' + gem 'markaby' + gem 'radius' + gem 'asciidoctor' + gem 'liquid' + gem 'stylus' + gem 'rabl' + gem 'builder' + gem 'erubis' + gem 'haml', '>= 3.0' + gem 'sass' +end + +if RUBY_ENGINE == "rbx" + gem 'json' + gem 'rubysl' + gem 'rubysl-test-unit' +end + +platforms :ruby_18, :jruby do + gem 'json' unless RUBY_VERSION > '1.9' # is there a jruby but 1.8 only selector? +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/LICENSE b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/LICENSE new file mode 100644 index 000000000..9cd6fa5b9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2007, 2008, 2009 Blake Mizerany +Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase + +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 OR COPYRIGHT +HOLDERS 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. \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.de.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.de.md new file mode 100644 index 000000000..923a62a62 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.de.md @@ -0,0 +1,2994 @@ +# Sinatra + +*Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter +Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 1.4.5).* + +Sinatra ist eine +[DSL](http://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das +schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand +ermöglicht: + +Sinatra via `rubygems` installieren: + +```shell +gem install sinatra +``` + +Eine Datei mit dem Namen `myapp.rb` erstellen: + +```ruby +require 'sinatra' +get '/' do + 'Hallo Welt!' +end +``` + +und im gleichen Verzeichnis ausführen: + +```shell +ruby myapp.rb +``` + +Die Seite kann nun unter [http://localhost:4567](http://localhost:4567) +aufgerufen werden. + +## Inhalt + +* [Sinatra](#sinatra) + * [Routen](#routen) + * [Bedingungen](#bedingungen) + * [Rückgabewerte](#rckgabewerte) + * [Eigene Routen-Muster](#eigene-routen-muster) + * [Statische Dateien](#statische-dateien) + * [Views/Templates](#viewstemplates) + * [Direkte Templates](#direkte-templates) + * [Verfügbare Templatesprachen](#verfgbare-templatesprachen) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Auf Variablen in Templates zugreifen](#auf-variablen-in-templates-zugreifen) + * [Templates mit `yield` und verschachtelte Layouts](#templates-mit-yield-und-verschachtelte-layouts) + * [Inline-Templates](#inline-templates) + * [Benannte Templates](#benannte-templates) + * [Dateiendungen zuordnen](#dateiendungen-zuordnen) + * [Eine eigene Template-Engine hinzufügen](#eine-eigene-template-engine-hinzufgen) + * [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden) + * [Filter](#filter) + * [Helfer](#helfer) + * [Sessions verwenden](#sessions-verwenden) + * [Anhalten](#anhalten) + * [Weiterspringen](#weiterspringen) + * [Eine andere Route ansteuern](#eine-andere-route-ansteuern) + * [Body, Status-Code und Header setzen](#body-status-code-und-header-setzen) + * [Response-Streams](#response-streams) + * [Logger](#logger) + * [Mime-Types](#mime-types) + * [URLs generieren](#urls-generieren) + * [Browser-Umleitung](#browser-umleitung) + * [Cache einsetzen](#cache-einsetzen) + * [Dateien versenden](#dateien-versenden) + * [Das Request-Objekt](#das-request-objekt) + * [Anhänge](#anhnge) + * [Umgang mit Datum und Zeit](#umgang-mit-datum-und-zeit) + * [Nachschlagen von Template-Dateien](#nachschlagen-von-template-dateien) + * [Konfiguration](#konfiguration) + * [Einstellung des Angriffsschutzes](#einstellung-des-angriffsschutzes) + * [Mögliche Einstellungen](#mgliche-einstellungen) + * [Umgebungen](#umgebungen) + * [Fehlerbehandlung](#fehlerbehandlung) + * [Nicht gefunden](#nicht-gefunden) + * [Fehler](#fehler) + * [Rack-Middleware](#rack-middleware) + * [Testen](#testen) + * [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatrabase---middleware-bibliotheken-und-modulare-anwendungen) + * [Modularer vs. klassischer Stil](#modularer-vs-klassischer-stil) + * [Eine modulare Applikation bereitstellen](#eine-modulare-applikation-bereitstellen) + * [Eine klassische Anwendung mit einer config.ru verwenden](#eine-klassische-anwendung-mit-einer-configru-verwenden) + * [Wann sollte eine config.ru-Datei verwendet werden?](#wann-sollte-eine-configru-datei-verwendet-werden) + * [Sinatra als Middleware nutzen](#sinatra-als-middleware-nutzen) + * [Dynamische Applikationserstellung](#dynamische-applikationserstellung) + * [Geltungsbereich und Bindung](#geltungsbereich-und-bindung) + * [Anwendungs- oder Klassen-Scope](#anwendungs--oder-klassen-scope) + * [Anfrage- oder Instanz-Scope](#anfrage--oder-instanz-scope) + * [Delegation-Scope](#delegation-scope) + * [Kommandozeile](#kommandozeile) + * [Systemanforderungen](#systemanforderungen) + * [Der neuste Stand (The Bleeding Edge)](#der-neuste-stand-the-bleeding-edge) + * [Mit Bundler](#mit-bundler) + * [Eigenes Repository](#eigenes-repository) + * [Gem erstellen](#gem-erstellen) + * [Versions-Verfahren](#versions-verfahren) + * [Mehr](#mehr) + +## Routen + +In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster definiert. +Jeder dieser Routen wird ein Ruby-Block zugeordnet: + +```ruby +get '/' do + .. zeige etwas .. +end + +post '/' do + .. erstelle etwas .. +end + +put '/' do + .. update etwas .. +end + +delete '/' do + .. entferne etwas .. +end + +options '/' do + .. zeige, was wir können .. +end + +link '/' do + .. verbinde etwas .. +end + +unlink '/' do + .. trenne etwas .. +end +``` + +Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. +Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt. + +Die Muster der Routen können benannte Parameter beinhalten, die über den +`params`-Hash zugänglich gemacht werden: + +```ruby +get '/hallo/:name' do + # passt auf "GET /hallo/foo" und "GET /hallo/bar" + # params['name'] ist dann 'foo' oder 'bar' + "Hallo #{params['name']}!" +end +``` + +Man kann auf diese auch mit Block-Parametern zugreifen: + +```ruby +get '/hallo/:name' do |n| + # n entspricht hier params['name'] + "Hallo #{n}!" +end +``` + +Routen-Muster können auch mit sog. Splat- oder Wildcard-Parametern über das +`params['splat']`-Array angesprochen werden: + +```ruby +get '/sag/*/zu/*' do + # passt z.B. auf /sag/hallo/zu/welt + params['splat'] # => ["hallo", "welt"] +end + +get '/download/*.*' do + # passt auf /download/pfad/zu/datei.xml + params['splat'] # => ["pfad/zu/datei", "xml"] +end +``` + +Oder mit Block-Parametern: + +```ruby +get '/download/*.*' do |pfad, endung| + [pfad, endung] # => ["Pfad/zu/Datei", "xml"] +end +``` + +Routen mit regulären Ausdrücken sind auch möglich: + +```ruby +get /\A\/hallo\/([\w]+)\z/ do + "Hallo, #{params['captures'].first}!" +end +``` + +Und auch hier können Block-Parameter genutzt werden: + +```ruby +get %r{/hallo/([\w]+)} do |c| + "Hallo, #{c}!" +end +``` + +Routen-Muster können auch mit optionalen Parametern ausgestattet werden: + +```ruby +get '/posts.?:format?' do + # passt auf "GET /posts" sowie jegliche Erweiterung + # wie "GET /posts.json", "GET /posts.xml" etc. +end +``` + +Routen können auch den query-Parameter verwenden: + +``` ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert +(siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem +Abgleich mit den Routen modifiziert wird. + +### Bedingungen + +An Routen können eine Vielzahl von Bedingungen geknüpft werden, die erfüllt +sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine +Einschränkung des User-Agents über die interne Bedingung `:agent`: + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Du verwendest Songbird Version #{params['agent'][0]}" +end +``` + +Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: + +```ruby +get '/foo' do + # passt auf andere Browser +end +``` + +Andere mitgelieferte Bedingungen sind `:host_name` und `:provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Adminbereich, Zugriff verweigert!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` durchsucht den Accept-Header der Anfrage + +Eigene Bedingungen können relativ einfach hinzugefügt werden: + +```ruby +set(:wahrscheinlichkeit) { |value| condition { rand <= value } } + +get '/auto_gewinnen', :wahrscheinlichkeit => 0.1 do + "Du hast gewonnen!" +end + +get '/auto_gewinnen' do + "Tut mir leid, verloren." +end +``` + +Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet +werden: + +```ruby +set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mein/account/", :auth => [:user, :admin] do + "Mein Account" +end + +get "/nur/admin/", :auth => :admin do + "Nur Admins dürfen hier rein!" +end +``` + +### Rückgabewerte + +Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body +festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware, +weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den +vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings +auch andere Werte akzeptiert. + +Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um +einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt: + +* Ein Array mit drei Elementen: `[Status (Fixnum), Headers (Hash), + Response-Body (antwortet auf #each)]`. +* Ein Array mit zwei Elementen: `[Status (Fixnum), Response-Body (antwortet + auf #each)]`. +* Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen + Block nur mit Strings als Übergabewerte aufruft. +* Ein Fixnum, das den Status-Code festlegt. + +Damit lässt sich relativ einfach Streaming implementieren: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming +direkt in die Route integriert. + +### Eigene Routen-Muster + +Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für +String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. +Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene +Routen-Muster erstellt werden: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch einfacher: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Oder unter Verwendung eines negativen look ahead: + +```ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## Statische Dateien + +Statische Dateien werden im `./public`-Ordner erwartet. Es ist möglich, +einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: + +```ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +Zu beachten ist, dass der Ordnername `public` nicht Teil der URL ist. Die Datei +`./public/css/style.css` ist unter `http://example.com/css/style.css` zu finden. + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man +die `:static_cache_control`-Einstellung (s.u.). + +## Views/Templates + +Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils +einen String zurückgibt: + +```ruby +get '/' do + erb :index +end +``` + +Dieses Beispiel rendert `views/index.erb`. + +Anstelle eines Templatenamens kann man auch direkt die Templatesprache verwenden: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates nehmen ein zweite Argument an, den Options-Hash: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` +(Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) + +Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Für alle Templates können auch Einstellungen, die für alle Routen gelten, +festgelegt werden: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Optionen, die an die Rendermethode weitergegeben werden, überschreiben die +Einstellungen, die mit `set` festgelegt wurden. + +Einstellungen: + +
+
locals
+
Liste von lokalen Variablen, die an das Dokument weitergegeben werden. + Praktisch für Partials: + + erb "<%= foo %>", :locals => {:foo => "bar"}
+ +
default_encoding
+
Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt + auf settings.default_encoding.
+ +
views
+
Ordner, aus dem die Templates geladen werden. Voreingestellt auf + settings.views.
+ +
layout
+
Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht + (true oderfalse). Ist es ein Symbol, dann legt es fest, + welches Template als Layout verwendet wird: + + erb :index, :layout => !request.xhr?
+ +
content_type
+
Content-Typ den das Template ausgibt. Voreinstellung hängt von der + Templatesprache ab.
+ +
scope
+
Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb + der App-Instanz. Wird Scope geändert, sind Instanzvariablen und + Helfermethoden nicht verfügbar.
+ +
layout_engine
+
Legt fest, welcher Renderer für das Layout verantwortlich ist. Hilfreich + für Sprachen, die sonst keine Templates unterstützen. Voreingestellt auf + den Renderer, der für das Template verwendet wird: + + set :rdoc, :layout_engine => :erb
+ +
layout_options
+
Besondere Einstellungen, die nur für das Rendering verwendet werden: + + set :rdoc, :layout_options => { :views => 'views/layouts' }
+
+ +Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis +befinden. Es kann jedoch ein anderer Ordner festgelegt werden: + +```ruby +set :views, settings.root + '/templates' +``` + +Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden muss, +auch dann, wenn sie sich in einem Unterordner befinden: + +```ruby +haml :'unterverzeichnis/template' +``` + +Rendering-Methoden rendern jeden String direkt. + +### Direkte Templates + +``` ruby +get '/' do + haml '%div.title Hallo Welt' +end +``` + +Hier wird der String direkt gerendert. + +### Verfügbare Templatesprachen + +Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche +verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu +Beginn ein `'require'`: + +```ruby +require 'rdiscount' # oder require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
Abhängigkeithaml
Dateierweiterung.haml
Beispielhaml :index, :format => :html5
+ + +#### Erb Templates + + + + + + + + + + + + + + +
Abhängigkeiterubis oder erb + (Standardbibliothek von Ruby)
Dateierweiterungen.erb, .rhtml oder .erubis (nur Erubis)
Beispielerb :index
+ + +#### Builder Templates + + + + + + + + + + + + + + +
Abhängigkeitbuilder
Dateierweiterung.builder
Beispielbuilder { |xml| xml.em "Hallo" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
Abhängigkeitnokogiri
Dateierweiterung.nokogiri
Beispielnokogiri { |xml| xml.em "Hallo" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### Sass Templates + + + + + + + + + + + + + + +
Abhängigkeitsass
Dateierweiterung.sass
Beispielsass :stylesheet, :style => :expanded
+ + +#### SCSS Templates + + + + + + + + + + + + + + +
Abhängigkeitsass
Dateierweiterung.scss
Beispielscss :stylesheet, :style => :expanded
+ + +#### Less Templates + + + + + + + + + + + + + + +
Abhängigkeitless
Dateierweiterung.less
Beispielless :stylesheet
+ + +#### Liquid Templates + + + + + + + + + + + + + + +
Abhängigkeitliquid
Dateierweiterung.liquid
Beispielliquid :index, :locals => { :key => 'Wert' }
+ +Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Markdown Templates + + + + + + + + + + + + + + +
AbhängigkeitEine der folgenden Bibliotheken: + RDiscount, + RedCarpet, + BlueCloth, + kramdown oder + maruku +
Dateierweiterungen.markdown, .mkd und .md
Beispielmarkdown :index, :layout_engine => :erb
+ +Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Markdown üblicherweise in Kombination +mit anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => markdown(:einfuehrung) } +``` + +Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= markdown(:Grüße) +``` + +Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht +in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### Textile Templates + + + + + + + + + + + + + + +
AbhängigkeitRedCloth
Dateierweiterung.textile
Beispieltextile :index, :layout_engine => :erb
+ +Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => textile(:einfuehrung) } +``` + +Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= textile(:Grüße) +``` + +Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht +in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### RDoc Templates + + + + + + + + + + + + + + +
Abhängigkeitrdoc
Dateierweiterung.rdoc
Beispieltextile :README, :layout_engine => :erb
+ +Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => rdoc(:einfuehrung) } +``` + +Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= rdoc(:Grüße) +``` + +Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in +RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
AbhängigkeitAsciidoctor
Dateierweiterungen.asciidoc, .adoc und .ad
Beispielasciidoc :README, :layout_engine => :erb
+ +Da man aus dem AsciiDoc-Template heraus keine Ruby-Methoden aufrufen kann +(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit +denen man Variablen weitergibt. + +#### Radius Templates + + + + + + + + + + + + + + +
Abhängigkeitradius
Dateierweiterung.radius
Beispielradius :index, :locals => { :key => 'Wert' }
+ +Da man aus dem Radius-Template heraus keine Ruby-Methoden aufrufen kann, wird +man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt. + +#### Markaby Templates + + + + + + + + + + + + + + +
Abhängigkeitmarkaby
Dateierweiterung.mab
Beispielmarkaby { h1 "Willkommen!" }
+ +Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). + +#### RABL Templates + + + + + + + + + + + + + + +
Abhängigkeitrabl
Dateierweiterung.rabl
Beispielrabl :index
+ +#### Slim Templates + + + + + + + + + + + + + + +
Abhängigkeitslim
Dateierweiterung.slim
Beispielslim :index
+ +#### Creole Templates + + + + + + + + + + + + + + +
Abhängigkeitcreole
Dateierweiterung.creole
Beispielcreole :wiki, :layout_engine => :erb
+ +Da man aus dem Creole-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Creole üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +```ruby +erb :overview, :locals => { :text => creole(:einfuehrung) } +``` + +Beachte, dass man die `creole`-Methode auch aus anderen Templates heraus +aufrufen kann: + +```ruby +%h1 Gruß von Haml! +%p= creole(:Grüße) +``` + +Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts nicht in +Creole geschrieben werden. Es ist aber möglich, einen Renderer für die Templates +zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option +verwendet wird. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
AbhängigkeitWikiCloth
Dateierweiterungen.mediawiki und .mw
Beispielmediawiki :wiki, :layout_engine => :erb
+ +Da man aus dem Mediawiki-Template heraus keine Ruby-Methoden aufrufen und auch +keine locals verwenden kann, wird man Mediawiki üblicherweise in Kombination mit +anderen Renderern verwenden wollen: + +``` ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Beachte: Man kann die `mediawiki`-Methode auch aus anderen Templates +heraus aufrufen: + +``` ruby +%h1 Grüße von Haml! +%p= mediawiki(:greetings) +``` + +Da man Ruby nicht von MediaWiki heraus aufrufen kann, können auch Layouts nicht +in MediaWiki geschrieben werden. Es ist aber möglich, einen Renderer für die +Templates zu verwenden und einen anderen für das Layout, indem die +`:layout_engine`-Option verwendet wird. + +#### CoffeeScript Templates + + + + + + + + + + + + + +
Abhängigkeitcoffee-script + und eine Möglichkeit JavaScript auszuführen. +
Dateierweiterung.coffee
Beispielcoffee :index
+ +#### Stylus Templates + + + + + + + + + + + + + + +
Abhängigkeit + + Stylus + und eine Möglichkeit + + JavaScript auszuführen + . +
Dateierweiterung.styl
Beispielstylus :index
+ +Um Stylus-Templates ausführen zu können, müssen `stylus` und `stylus/tilt` +zuerst geladen werden: + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
Abhängigkeityajl-ruby
Dateierweiterung.yajl
Beispiel + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ +Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende +json Variable wird mit Hilfe von `#to_json` umgewandelt: + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt +verwendet werden: + +``` javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
Abhängigkeitwlang
Dateierweiterung.wlang
Beispielwlang :index, :locals => { :key => 'value' }
+ +Ruby-Methoden in Wlang aufzurufen entspricht nicht den idiomatischen Vorgaben +von Wlang, es bietet sich deshalb an, `:locals` zu verwenden. Layouts, die +Wlang und `yield` verwenden, werden aber trotzdem unterstützt. + +Rendert den eingebetteten Template-String. + +### Auf Variablen in Templates zugreifen + +Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen +in Routen sind auch direkt im Template verfügbar: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Oder durch einen expliziten Hash von lokalen Variablen: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen +Templates eingesetzt. + +### Templates mit `yield` und verschachtelte Layouts + +Ein Layout ist üblicherweise ein Template, das ein `yield` aufruft. Ein solches +Template kann entweder wie oben beschrieben über die `:template` Option +verwendet werden oder mit einem Block gerendert werden: + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +Dieser Code entspricht weitestgehend `erb :index, :layout => :post`. + +Blöcke an Render-Methoden weiterzugeben ist besonders bei verschachtelten +Layouts hilfreich: + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Der gleiche Effekt kann auch mit weniger Code erreicht werden: + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Zur Zeit nehmen folgende Renderer Blöcke an: `erb`, `haml`, `liquid`, `slim ` +und `wlang`. + +Das gleich gilt auch für die allgemeine `render` Methode. + +### Inline-Templates + +Templates können auch am Ende der Datei definiert werden: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hallo Welt!!!!! +``` + +Anmerkung: Inline-Templates, die in der Datei definiert sind, die `require +'sinatra'` aufruft, werden automatisch geladen. Um andere Inline-Templates in +anderen Dateien aufzurufen, muss explizit `enable :inline_templates` verwendet +werden. + +### Benannte Templates + +Templates können auch mit der Top-Level `template`-Methode definiert werden: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hallo Welt!' +end + +get '/' do + haml :index +end +``` + +Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf +verwendet. Durch `:layout => false` kann das Ausführen verhindert werden: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? + # !request.xhr? prüft, ob es sich um einen asynchronen Request handelt. + # wenn nicht, dann verwende ein Layout (negiert durch !) +end +``` + +### Dateiendungen zuordnen + +Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` +genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt +werden soll, lässt sich dies wie folgt bewerkstelligen: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Eine eigene Template-Engine hinzufügen + +Zu allererst muss die Engine bei Tilt registriert und danach eine +Rendering-Methode erstellt werden: + +```ruby +Tilt.register :mtt, MeineTolleTemplateEngine + +helpers do + def mtt(*args) render(:mtt, *args) end +end + +get '/' do + mtt :index +end +``` + +Dieser Code rendert `./views/application.mtt`. Siehe +[github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über +Tilt zu erfahren. + +### Eigene Methoden zum Aufsuchen von Templates verwenden + +Um einen eigenen Mechanismus zum Aufsuchen von Templates zu +implementieren, muss `#find_template` definiert werden: + +``` ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filter + +Before-Filter werden vor jedem Request in demselben Kontext, wie danach die +Routen, ausgeführt. So können etwa Request und Antwort geändert werden. +Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet +werden: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After-Filter werden nach jedem Request in demselben Kontext ausgeführt und +können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte +Instanzvariablen können in After-Filtern verwendet werden: + +```ruby +after do + puts response.status +end +``` + +Filter können optional auch mit einem Muster ausgestattet werden, das auf den +Request-Pfad passen muss, damit der Filter ausgeführt wird: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt +werden: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helfer + +Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden +definiert, die in Routen und Templates verwendet werden können: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +### Sessions verwenden +Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind +sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden: + +```ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter +Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten +höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack- +Session-Middleware verwendet werden. Dabei wird auf `enable :sessions` +verzichtet und die Middleware wie üblich im Programm eingebunden: + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit +einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem +Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu +wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret +teilen: + +```ruby +set :session_secret, 'super_geheimes_Gegeimnis' +``` + +Zur weiteren Konfiguration kann man einen Hash mit Optionen in den `sessions` +Einstellungen ablegen. + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Um eine Session mit anderen Apps und zwischen verschiedenen Subdomains +von foo.com zu teilen, wird ein *.* der Domain vorangestellt: + +``` ruby +set :sessions, :domain => '.foo,com' +``` + +### Anhalten + +Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: + +```ruby +halt +``` + +Der Status kann beim Stoppen mit angegeben werden: + +```ruby +halt 410 +``` + +Oder auch den Response-Body: + +```ruby +halt 'Hier steht der Body' +``` + +Oder beides: + +```ruby +halt 401, 'verschwinde!' +``` + +Sogar mit Headern: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'Rache' +``` + +Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: + +```ruby +halt erb(:error) +``` + +### Weiterspringen + +Eine Route kann mittels `pass` zu der nächsten passenden Route springen: + +```ruby +get '/raten/:wer' do + pass unless params['wer'] == 'Frank' + 'Du hast mich!' +end + +get '/raten/*' do + 'Du hast mich nicht!' +end +``` + +Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route +gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster +gefunden wird. + +### Eine andere Route ansteuern + +Wenn nicht zu einer anderen Route gesprungen werden soll, sondern nur das +Ergebnis einer anderen Route gefordert wird, kann `call` für einen internen +Request verwendet werden: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht +werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die +`/foo` und `/bar` zugreifen können. + +Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine +Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` +verwendet werden. + +### Body, Status-Code und Header setzen + +Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit einem +Returnwert in der Route zu setzen. In manchen Situationen kann es jedoch sein, +dass der Body an anderer Stelle während der Ausführung gesetzt werden soll. +Dafür kann man die Helfer-Methode `body` einsetzen. Ist sie gesetzt, kann sie zu +einem späteren Zeitpunkt aufgerufen werden: + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom +Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming +einsetzen, siehe auch "Rückgabewerte"). + +Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + halt "Ich bin ein Teekesselchen" +end +``` + +Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne +Argumente den aktuellen Wert aus. + +### Response-Streams + +In manchen Situationen sollen Daten bereits an den Client zurückgeschickt +werden, bevor ein vollständiger Response bereit steht. Manchmal will man die +Verbindung auch erst dann beenden und Daten so lange an den Client +zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die +`stream`-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben: + +```ruby +get '/' do + stream do |out| + out << "Das ist ja mal wieder fanta -\n" + sleep 0.5 + out << " (bitte warten…) \n" + sleep 1 + out << "- stisch!\n" + end +end +``` + +Damit lassen sich Streaming-APIs realisieren, sog. +[Server Sent Events](http://dev.w3.org/html5/eventsource/), die als Basis für +[WebSockets](http://en.wikipedia.org/wiki/WebSocket) dienen. Ebenso können sie +verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von +langsamen Ressourcen abhängig ist. + +Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl +nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die +Applikation verwendet wird. Einige Server, z.B. WEBRick, unterstützen +Streaming nicht oder nur teilweise. Sollte der Server Streaming nicht +unterstützen, wird ein vollständiger Response-Body zurückgeschickt, sobald der +an `stream` weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert +Streaming z.B. überhaupt nicht. + +Ist der optionale Parameter `keep_open` aktiviert, wird beim gestreamten Objekt +`close` nicht aufgerufen und es ist einem überlassen dies an einem beliebigen +späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei Event-gesteuerten +Serven wie Thin oder Rainbows möglich, andere Server werden trotzdem den Stream +beenden: + +```ruby +# Durchgehende Anfrage (long polling) + +set :server, :thin +connections = [] + +get '/subscribe' do + # Client-Registrierung beim Server, damit Events mitgeteilt werden können + stream(:keep_open) do |out| + connections << out + # tote Verbindungen entfernen + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # Den Client über eine neue Nachricht in Kenntnis setzen + # notify client that a new message has arrived + out << params['message'] << "\n" + + # Den Client zur erneuten Verbindung auffordern + out.close + end + + # Rückmeldung + "Mitteiling erhalten" +end +``` + +### Logger + +Im Geltungsbereich eines Request stellt die `logger` Helfer-Methode eine `Logger` +Instanz zur Verfügung: + +```ruby +get '/' do + logger.info "es passiert gerade etwas" + # ... +end +``` + +Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten +Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt zurück. +In den Routen und Filtern muss man sich also nicht weiter darum kümmern. + +Beachte, dass das Loggen standardmäßig nur für `Sinatra::Application` +voreingestellt ist. Wird über `Sinatra::Base` vererbt, muss es erst aktiviert +werden: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Damit auch keine Middleware das Logging aktivieren kann, muss die `logging` +Einstellung auf `nil` gesetzt werden. Das heißt aber auch, dass `logger` in +diesem Fall `nil` zurückgeben wird. Üblicherweise wird das eingesetzt, wenn ein +eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was in +`env['rack.logger']` eingetragen ist. + +### Mime-Types + +Wenn `send_file` oder statische Dateien verwendet werden, kann es vorkommen, +dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit `mime_type` +per Dateiendung: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Es kann aber auch der `content_type`-Helfer verwendet werden: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLs generieren + +Zum Generieren von URLs sollte die `url`-Helfer-Methode genutzen werden, so z.B. +beim Einsatz von Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen. + +Diese Methode ist ebenso über das Alias `to` zu erreichen (siehe Beispiel unten). + +### Browser-Umleitung + +Eine Browser-Umleitung kann mithilfe der `redirect`-Helfer-Methode erreicht +werden: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Weitere Parameter werden wie Argumente der `halt`-Methode behandelt: + +```ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'Hier bist du falsch' +``` + +Ebenso leicht lässt sich ein Schritt zurück mit dem Alias `redirect back` +erreichen: + +```ruby +get '/foo' do + "mach was" +end + +get '/bar' do + mach_was + redirect back +end +``` + +Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query +übergeben: + +```ruby +redirect to('/bar?summe=42') +``` + +oder eine Session verwendet werden: + +```ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache einsetzen + +Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein +ordentliches HTTP-Caching. + +Der Cache-Control-Header lässt sich ganz einfach einstellen: + +```ruby +get '/' do + cache_control :public + "schon gecached!" +end +``` + +Profitipp: Caching im before-Filter aktivieren + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Bei Verwendung der `expires`-Helfermethode zum Setzen des gleichnamigen Headers, +wird `Cache-Control` automatisch eigestellt: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Um alles richtig zu machen, sollten auch `etag` oder `last_modified` verwendet +werden. Es wird empfohlen, dass diese Helfer aufgerufen werden **bevor** die +eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client +eine aktuelle Version im Cache vorhält: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +ebenso ist es möglich einen +[schwachen ETag](http://de.wikipedia.org/wiki/HTTP_ETag) zu verwenden: + +```ruby +etag @article.sha1, :weak +``` + +Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür +notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy +Cache-Lösungen bietet sich z.B. +[rack-cache](https://github.com/rtomayko/rack-cache) an: + +```ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man die +`:static_cache_control`-Einstellung (s.u.). + +Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein If-Match oder +ein If-None_match Header auf `*` gesetzt wird in Abhängigkeit davon, ob die +Resource bereits existiert. Sinatra geht davon aus, dass Ressourcen bei sicheren +Anfragen (z.B. bei get oder Idempotenten Anfragen wie put) bereits existieren, +wobei anderen Ressourcen (besipielsweise bei post), als neue Ressourcen +behandelt werden. Dieses Verhalten lässt sich mit der `:new_resource` Option +ändern: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Soll das schwache ETag trotzdem verwendet werden, verwendet man die `:kind` +Option: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Dateien versenden + +Um den Inhalt einer Datei als Response zurückzugeben, kann die +`send_file`-Helfer-Methode verwendet werden: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Für `send_file` stehen einige Hash-Optionen zur Verfügung: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +
+
filename
+
Dateiname als Response. Standardwert ist der eigentliche Dateiname.
+ +
last_modified
+
Wert für den Last-Modified-Header, Standardwert ist mtime der + Datei.
+ +
type
+
Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, von + der Dateiendung abgeleitet.
+ +
disposition
+
Verwendet für Content-Disposition. Mögliche Werte sind: nil + (Standard), :attachment und :inline.
+ +
length
+
Content-Length-Header. Standardwert ist die Dateigröße.
+
+ +Soweit vom Rack-Handler unterstützt, werden neben der Übertragung über den +Ruby-Prozess auch andere Möglichkeiten genutzt. Bei Verwendung der +`send_file`-Helfer-Methode kümmert sich Sinatra selbstständig um die +Range-Requests. + +### Das Request-Objekt + +Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus +zugegriffen werden: + +```ruby +# App läuft unter http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # Request-Body des Client (siehe unten) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # Länge des request.body + request.media_type # Medientypus von request.body + request.host # "example.com" + request.get? # true (ähnliche Methoden für andere Verben) + request.form_data? # false + request["irgendein_param"] # Wert von einem Parameter; [] ist die Kurzform für den params Hash + request.referrer # Der Referrer des Clients oder '/' + request.user_agent # User-Agent (verwendet in der :agent Bedingung) + request.cookies # Hash des Browser-Cookies + request.xhr? # Ist das hier ein Ajax-Request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-Adresse des Clients + request.secure? # false (true wenn SSL) + request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) + request.env # vollständiger env-Hash von Rack übergeben +end +``` + +Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch +schreibbar: + +```ruby +before { request.path_info = "/" } + +get "/" do + "Alle Anfragen kommen hier an!" +end +``` + +Der `request.body` ist ein IO- oder StringIO-Objekt: + +```ruby +post "/api" do + request.body.rewind # falls schon jemand davon gelesen hat + daten = JSON.parse request.body.read + "Hallo #{daten['name']}!" +end +``` + +### Anhänge + +Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser +angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: + +```ruby +get '/' do + attachment + "Speichern!" +end +``` + +Ebenso kann eine Dateiname als Parameter hinzugefügt werden: + +```ruby +get '/' do + attachment "info.txt" + "Speichern!" +end +``` + +### Umgang mit Datum und Zeit + +Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert ein +Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und ähnliche +Klassen konvertieren: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "noch Zeit" +end +``` + +Diese Methode wird intern für +expires, `last_modiefied` und ihresgleichen +verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem +Verhalten erweitern, indem man `time_for` in der eigenen Applikation +überschreibt: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "Hallo" +end +``` + +### Nachschlagen von Template-Dateien + +Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum Rendern +aufzufinden: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "könnte diese hier sein: #{file}" +end +``` + +Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie +nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel +dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines +zu verwenden: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt +werden! + +Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich existiert. +Es wird lediglich der angegebene Block aufgerufen und nach allen möglichen +Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` `block` +verwendet, sobald eine Datei gefunden wurde. Ebenso werden Template-Pfade samt +Inhalt gecached, solange nicht im Entwicklungsmodus gearbeitet wird. Das sollte +im Hinterkopf behalten werden, wenn irgendwelche verrückten Methoden +zusammenbastelt werden. + +### Konfiguration + +Wird einmal beim Starten in jedweder Umgebung ausgeführt: + +```ruby +configure do + # setze eine Option + set :option, 'wert' + + # setze mehrere Optionen + set :a => 1, :b => 2 + + # das gleiche wie `set :option, true` + enable :option + + # das gleiche wie `set :option, false` + disable :option + + # dynamische Einstellungen mit Blöcken + set(:css_dir) { File.join(views, 'css') } +end +``` + +Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf `:production` +gesetzt ist: + +```ruby +configure :production do + ... +end +``` + +Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: + +```ruby +configure :production, :test do + ... +end +``` + +Diese Einstellungen sind über `settings` erreichbar: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +#### Einstellung des Angriffsschutzes + +Sinatra verwendet +[Rack::Protection](https://github.com/rkh/rack-protection#readme), um die +Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung +lässt sich selbstverständlich deaktivieren, der damit verbundene +Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen +Risiken. + +```ruby +disable :protection +``` + +Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` +einen Hash mit Optionen hinzu: + +```ruby +set :protection, :except => :path_traversal +``` + +Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere +Schutzmechanismen zu deaktivieren: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +#### Mögliche Einstellungen + +
+
absolute_redirects
+
Wenn ausgeschaltet, wird Sinatra relative Redirects zulassen. Jedoch ist + Sinatra dann nicht mehr mit RFC 2616 (HTTP 1.1) konform, das nur absolute + Redirects zulässt. Sollte eingeschaltet werden, wenn die Applikation hinter + einem Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. Beachte, + dass die url-Helfer-Methode nach wie vor absolute URLs erstellen + wird, es sei denn, es wird als zweiter Parameter false angegeben. + Standardmäßig nicht aktiviert.
+ +
add_charset
+
Mime-Types werden hier automatisch der Helfer-Methode + content_type zugeordnet. Es empfielt sich, Werte hinzuzufügen statt + sie zu überschreiben: settings.add_charset << "application/foobar" +
+ +
app_file
+
Pfad zur Hauptdatei der Applikation. Wird verwendet, um das Wurzel-, + Inline-, View- und öffentliche Verzeichnis des Projekts festzustellen.
+ +
bind
+
IP-Address, an die gebunden wird (Standardwert: 0.0.0.0 + oder localhost). Wird nur für den eingebauten Server + verwendet.
+ +
default_encoding
+
Das Encoding, falls keines angegeben wurde. Standardwert ist + "utf-8".
+ +
dump_errors
+
Fehler im Log anzeigen.
+ +
environment
+
Momentane Umgebung. Standardmäßig auf content_type oder + "development" eingestellt, soweit ersteres nicht vorhanden.
+ +
logging
+
Den Logger verwenden.
+ +
lock
+
Jeder Request wird gelocked. Es kann nur ein Request pro Ruby-Prozess + gleichzeitig verarbeitet werden. Eingeschaltet, wenn die Applikation + threadsicher ist. Standardmäßig nicht aktiviert.
+ +
method_override
+
Verwende _method, um put/delete-Formulardaten in Browsern zu + verwenden, die dies normalerweise nicht unterstützen.
+ +
port
+
Port für die Applikation. Wird nur im internen Server verwendet.
+ +
prefixed_redirects
+
Entscheidet, ob request.script_name in Redirects eingefügt wird + oder nicht, wenn kein absoluter Pfad angegeben ist. Auf diese Weise verhält + sich redirect '/foo' so, als wäre es ein redirect + to('/foo'). Standardmäßig nicht aktiviert.
+ +
protection
+
Legt fest, ob der Schutzmechanismus für häufig Vorkommende Webangriffe + auf Webapplikationen aktiviert wird oder nicht. Weitere Informationen im + vorhergehenden Abschnitt.
+ +
public_folder
+
Das öffentliche Verzeichnis, aus dem Daten zur Verfügung gestellt werden + können. Wird nur dann verwendet, wenn statische Daten zur Verfügung gestellt + werden können (s.u. static Option). Leitet sich von der + app_file Einstellung ab, wenn nicht gesetzt.
+ +
public_dir
+
Alias für public_folder, s.o.
+ +
reload_templates
+
Im development-Modus aktiviert.
+ +
root
+
Wurzelverzeichnis des Projekts. Leitet sich von der app_file + Einstellung ab, wenn nicht gesetzt.
+ +
raise_errors
+
Einen Ausnahmezustand aufrufen. Beendet die Applikation. Ist automatisch + aktiviert, wenn die Umgebung auf "test" eingestellt ist. Ansonsten + ist diese Option deaktiviert.
+ +
run
+
Wenn aktiviert, wird Sinatra versuchen, den Webserver zu starten. Nicht + verwenden, wenn Rackup oder anderes verwendet werden soll.
+ +
running
+
Läuft der eingebaute Server? Diese Einstellung nicht ändern!
+ +
server
+
Server oder Liste von Servern, die als eingebaute Server zur Verfügung + stehen. Die Reihenfolge gibt die Priorität vor, die Voreinstellung hängt von + der verwendenten Ruby Implementierung ab.
+ +
sessions
+
Sessions auf Cookiebasis mittels + Rack::Session::Cookieaktivieren. Für weitere Infos bitte in der + Sektion ‘Sessions verwenden’ nachschauen.
+ +
show_exceptions
+
Bei Fehlern einen Stacktrace im Browseranzeigen. Ist automatisch + aktiviert, wenn die Umgebung auf "development" eingestellt ist. + Ansonsten ist diese Option deaktiviert. Kann auch auf :after_handler + gestellt werden, um eine anwendungsspezifische Fehlerbehandlung auszulösen, + bevor der Fehlerverlauf im Browser angezeigt wird.
+ +
static
+
Entscheidet, ob Sinatra statische Dateien zur Verfügung stellen soll oder + nicht. Sollte nicht aktiviert werden, wenn ein Server verwendet wird, der + dies auch selbstständig erledigen kann. Deaktivieren wird die Performance + erhöhen. Standardmäßig aktiviert.
+ +
static_cache_control
+
Wenn Sinatra statische Daten zur Verfügung stellt, können mit dieser + Einstellung die Cache-Control Header zu den Responses hinzugefügt + werden. Die Einstellung verwendet dazu die cache_control + Helfer-Methode. Standardmäßig deaktiviert. Ein Array wird verwendet, um + mehrere Werte gleichzeitig zu übergeben: set :static_cache_control, + [:public, :max_age => 300]
+ +
threaded
+
Wird es auf true gesetzt, wird Thin aufgefordert + EventMachine.defer zur Verarbeitung des Requests einzusetzen.
+ +
traps
+
Einstellung, Sinatra System signalen umgehen soll.
+ +
views
+
Verzeichnis der Views. Leitet sich von der app_file Einstellung + ab, wenn nicht gesetzt.
+ +
x_cascade
+
Einstellung, ob der X-Cascade Header bei fehlender Route gesetzt wird oder + nicht. Standardeinstellung ist true.
+
+ +## Umgebungen + +Es gibt drei voreingestellte Umgebungen in Sinatra: `"development"`, +`"production"` und `"test"`. Umgebungen können über die `RACK_ENV` +Umgebungsvariable gesetzt werden. Die Standardeinstellung ist `"development"`. +In diesem Modus werden alle Templates zwischen Requests neu geladen. Dazu gibt +es besondere Fehlerseiten für 404 Stati und Fehlermeldungen. In `"production"` +und `"test"` werden Templates automatisch gecached. + +Um die Anwendung in einer anderen Umgebung auszuführen kann man die `-e` +Option verwenden: + +```shell +ruby my_app.rb -e [ENVIRONMENT] +``` + +In der Anwendung kann man die die Methoden `development?`, `test?` und +`production?` verwenden, um die aktuelle Umgebung zu erfahren. + +## Fehlerbehandlung + +Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, +dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. + +### Nicht gefunden + +Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 +ist, wird der `not_found`-Handler ausgeführt: + +```ruby +not_found do + 'Seite kann nirgendwo gefunden werden.' +end +``` + +### Fehler + +Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem +Routen-Block oder in einem Filter geworfen wurde. In der +`development`-Umgebung wird es nur dann funktionieren, wenn die +`:show_exceptions`-Option auf `:after_handler` eingestellt wurde: + +```ruby +set :show_exceptions, :after_handler +``` + +Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden: + +```ruby +error do + 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].message +end +``` + +Benutzerdefinierte Fehler: + +```ruby +error MeinFehler do + 'Au weia, ' + env['sinatra.error'].message +end +``` + +Dann, wenn das passiert: + +```ruby +get '/' do + raise MeinFehler, 'etwas Schlimmes ist passiert' +end +``` + +bekommt man dieses: + +```shell +Au weia, etwas Schlimmes ist passiert +``` + +Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden: + +```ruby +error 403 do + 'Zugriff verboten' +end + +get '/geheim' do + 403 +end +``` + +Oder ein Status-Code-Bereich: + +```ruby +error 400..510 do + 'Hallo?' +end +``` + +Sinatra setzt verschiedene `not_found`- und `error`-Handler in der +Development-Umgebung ein, um hilfreiche Debugging Informationen und Stack Traces +anzuzeigen. + +## Rack-Middleware + +Sinatra baut auf [Rack](http://rack.github.io/), einem minimalistischen +Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features +für Entwickler ist der Support von Middlewares, die zwischen den Server und +die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort +überwachen und/oder manipulieren können. + +Sinatra macht das Erstellen von Middleware-Verkettungen mit der +Top-Level-Methode `use` zu einem Kinderspiel: + +```ruby +require 'sinatra' +require 'meine_middleware' + +use Rack::Lint +use MeineMiddleware + +get '/hallo' do + 'Hallo Welt' +end +``` + +Die Semantik von `use` entspricht der gleichnamigen Methode der +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder)-DSL +(meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die +`use`-Methode mehrere/verschiedene Argumente und auch Blöcke entgegennimmt: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'geheim' +end +``` + +Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, +URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet +viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So +muss `use` häufig nicht explizit verwendet werden. + +Hilfreiche Middleware gibt es z.B. hier: +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +oder im [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testen + +Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben +werden. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) +wird empfohlen: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hallo Welt!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hallo Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Du verwendest Songbird!", last_response.body + end +end +``` + +Hinweis: Wird Sinatra modular verwendet, muss Sinatra::Application mit +dem Namen der Applikations-Klasse ersetzt werden. + +## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen + +Das Definieren einer Top-Level-Anwendung funktioniert gut für +Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie +Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder +auch Sinatra-Erweiterungen geschrieben werden sollen. + +Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie +sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, +Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins +Spiel: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hallo Welt!' + end +end +``` + +Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware, +Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch +`use` oder `run` von einer Rackup-`config.ru`-Datei oder als Server-Komponente +einer Bibliothek: + +```ruby +MyApp.run! :host => 'localhost', :port => 9090 +``` + +Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der +Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei +Veränderungen zu `Sinatra::Base` konvertiert werden: + +* Die Datei sollte `require 'sinatra/base'` anstelle von `require + 'sinatra/base'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden + in den Top-Level-Namespace importiert. +* Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in + einer Subklasse von `Sinatra::Base` definiert werden. + +`Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per +Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe +[Optionen und Konfiguration](http://sinatra.github.com/configuration.html) für +Details über mögliche Optionen. + +Damit eine App sich ähnlich wie eine klassische App verhält, kann man +auch eine Subclass von `Sinatra::Application` erstellen: + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modularer vs. klassischer Stil + +Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil +einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein +Grund, eine modulare Applikation zu erstellen. + +Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer +modularen ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. +Sollen mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen +werden. Dabei ist es kein Problem klassische und modulare Anwendungen +miteinander zu vermischen. + +Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet +werden: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SzenarioClassicModularModular
app_fileSinatra ladende DateiSinatra::Base subklassierende DateiSinatra::Application subklassierende Datei
run$0 == app_filefalsefalse
loggingtruefalsetrue
method_overridetruefalsetrue
inline_templatestruefalsetrue
statictruefalsetrue
+ +### Eine modulare Applikation bereitstellen + +Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über +`run!`: + +```ruby +# mein_app.rb +require 'sinatra/base' + +class MeinApp < Sinatra::Base + # ... Anwendungscode hierhin ... + + # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird + run! if app_file == $0 +end +``` + +Starte mit: + +```shell +ruby mein_app.rb +``` + +Oder über eine `config.ru`-Datei, die es erlaubt, einen beliebigen +Rack-Handler zu verwenden: + +```ruby +# config.ru (mit rackup starten) +require './mein_app' +run MeineApp +``` + +Starte: + +```shell +rackup -p 4567 +``` + +### Eine klassische Anwendung mit einer config.ru verwenden + +Schreibe eine Anwendungsdatei: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hallo Welt!' +end +``` + +sowie eine dazugehörige `config.ru`-Datei: + +```ruby +require './app' +run Sinatra::Application +``` + +### Wann sollte eine config.ru-Datei verwendet werden? + +Anzeichen dafür, dass eine `config.ru`-Datei gebraucht wird: + +* Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, + Heroku, ...). +* Es gibt mehr als nur eine Subklasse von `Sinatra::Base`. +* Sinatra soll als Middleware verwendet werden, nicht als Endpunkt. + + +**Es gibt keinen Grund, eine `config.ru`-Datei zu verwenden, nur weil eine +Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung +mit modularem Stil benötigt, um eine `config.ru`-Datei zu verwenden.** + +### Sinatra als Middleware nutzen + +Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es +kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden +beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich +nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere +Rack-Anwendung sein (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # Middleware wird vor Filtern ausgeführt + use LoginScreen + + before do + unless session['user_name'] + halt "Zugriff verweigert, bitte einloggen." + end + end + + get('/') { "Hallo #{session['user_name']}." } +end +``` + +### Dynamische Applikationserstellung + +Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, +ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit +`Sinatra.new` erreichen: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hallo" } } +my_app.run! +``` + +Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden +oder Sinatra in einer Bibliothek Verwendung findet. + +Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Geltungsbereich und Bindung + +Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur +Verfügung stehen. + +### Anwendungs- oder Klassen-Scope + +Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die +Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um +`Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit +angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur +Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die +`session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt +wird. + +Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungsscope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ich bin nicht mehr im Anwendungs-Scope! + end +end +``` + +Im Anwendungs-Scope befindet man sich: + +* In der Anwendungs-Klasse. +* In Methoden, die von Erweiterungen definiert werden. +* Im Block, der an `helpers` übergeben wird. +* In Procs und Blöcken, die an `set` übergeben werden. +* Der an `Sinatra.new` übergebene Block + + +Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: + +* Über das Objekt, das an den `configure`-Block übergeben wird (`configure { + |c| ... }`). +* `settings` aus den anderen Scopes heraus. + + +### Anfrage- oder Instanz-Scope + +Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse +erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus +kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder +`haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den +Anwendungs-Scope zugegriffen werden: + +```ruby +class MyApp < Sinatra::Base + # Hey, ich bin im Anwendungs-Scope! + get '/neue_route/:name' do + # Anfrage-Scope für '/neue_route/:name' + @value = 42 + + settings.get "/#{params['name']}" do + # Anfrage-Scope für "/#{params['name']}" + @value # => nil (nicht dieselbe Anfrage) + end + + "Route definiert!" + end +end +``` + +Im Anfrage-Scope befindet man sich: + +* In get, head, post, put, delete, options, patch, link und unlink Blöcken +* In before und after Filtern +* In Helfer-Methoden +* In Templates + + +### Delegation-Scope + +Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope +weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, +da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als +delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf +die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es +gibt ein anderes `self`). Weitere Delegationen können mit +`Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. + +Im Delegation-Scop befindet man sich: + +* Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. +* In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. + + +Schau am besten im Code nach: Hier ist [Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 +) definiert und wird in den [globalen Namespace +eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb + +## Kommandozeile + +Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: + +```shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER] +``` + +Die Optionen sind: + +``` +-h # Hilfe +-p # Port setzen (Standard ist 4567) +-h # Host setzen (Standard ist 0.0.0.0) +-e # Umgebung setzen (Standard ist development) +-s # Rack-Server/Handler setzen (Standard ist thin) +-x # Mutex-Lock einschalten (Standard ist off) +``` + +## Systemanforderungen + +Die folgenden Versionen werden offiziell unterstützt: + +
+
Ruby 1.8.7
+
1.8.7 wird vollständig unterstützt, ein Wechsel zu JRuby oder Rubinius wird +aber empfohlen. Ruby 1.8.7 wird noch bis Sinatra 2.0 unterstützt werden. Frühere +Versionen von Ruby sind nicht kompatibel mit Sinatra.
+ +
Ruby 1.9.2
+
1.9.2 wird mindestens bis Sinatra 1.5 voll unterstützt. Version 1.9.2p0 +sollte nicht verwendet werden, da unter Sinatra immer wieder Segfaults +auftreten.
+ +
Ruby 1.9.3
+
1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem +Upgrade von einer früheren Version von Ruby zu Ruby 1.9.3 werden alle Sessions +ungültig. Ruby 1.9.3 wird bis Sinatra 2.0 unterstützt werden.
+ +
Ruby 2.x
+
2.x wird vollständig unterstützt.
+ +
Rubinius
+
Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird empfohlen, den +Puma Server zu installieren (gem install puma +)
+ +
JRuby
+
Aktuelle JRuby Versionen werden offiziell unterstützt. Es wird empfohlen, +keine C-Erweiterungen zu verwenden und als Server Trinidad zu verwenden +(gem install trinidad).
+
+ +Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von +Sinatra unterstützt, funktionieren aber normalerweise: + +* Ruby Enterprise Edition +* Ältere Versionen von JRuby und Rubinius +* MacRuby (gem install control_tower wird empfohlen), Maglev, IronRuby +* Ruby 1.9.0 und 1.9.1 + +Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, +wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen +Implementierung liegt. + +Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head +(zukünftige Versionen von MRI) mit eingebunden. Es kann davon ausgegangen +werden, dass Sinatra MRI auch weiterhin vollständig unterstützen wird. + +Sinatra sollte auf jedem Betriebssystem laufen, dass einen funktionierenden +Ruby-Interpreter aufweist. + +Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= 1.8.7. + +## Der neuste Stand (The Bleeding Edge) + +Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. +Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, +die so installiert werden: + +```shell +gem install sinatra --pre +``` + +### Mit Bundler + +Wenn die Applikation mit der neuesten Version von Sinatra und +[Bundler](http://gembundler.com/) genutzt werden soll, empfehlen wir den +nachfolgenden Weg. + +Soweit Bundler noch nicht installiert ist: + +```shell +gem install bundler +``` + +Anschließend wird eine `Gemfile`-Datei im Projektverzeichnis mit folgendem +Inhalt erstellt: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# evtl. andere Abhängigkeiten +gem 'haml' # z.B. wenn du Haml verwendest... +gem 'activerecord', '~> 3.0' # ...oder ActiveRecord 3.x +``` + +Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, +direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem +Gemfile von Sinatra hinzugefügt. + +Jetzt kannst du deine Applikation starten: + +```shell +bundle exec ruby myapp.rb +``` + +### Eigenes Repository +Um auf dem neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie +angelegt werden. Gestartet wird in der Anwendung mit dem `sinatra/lib`-Ordner +im `LOAD_PATH`: + +```shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -Isinatra/lib myapp.rb +``` + +Alternativ kann der `sinatra/lib`-Ordner zum `LOAD_PATH` in der Anwendung +hinzugefügt werden: + +```ruby +$LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' +require 'rubygems' +require 'sinatra' + +get '/ueber' do + "Ich laufe auf Version " + Sinatra::VERSION +end +``` + +Um Sinatra-Code von Zeit zu Zeit zu aktualisieren: + +```shell +cd myproject/sinatra +git pull +``` + +### Gem erstellen + +Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden: + +```shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +Falls Gems als Root installiert werden sollen, sollte die letzte Zeile +folgendermaßen lauten: + +```shell +sudo rake install +``` + +## Versions-Verfahren + +Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. +SemVer und SemVerTag. + +## Mehr + +* [Projekt-Website](http://sinatra.github.com/) - Ergänzende Dokumentation, + News und Links zu anderen Ressourcen. +* [Mitmachen](http://sinatra.github.com/contributing.html) - Einen Fehler + gefunden? Brauchst du Hilfe? Hast du einen Patch? +* [Issue-Tracker](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Mailing-Liste](http://groups.google.com/group/sinatrarb) +* [#sinatra](irc://chat.freenode.net/#sinatra) auf http://freenode.net Es + gibt dort auch immer wieder deutschsprachige Entwickler, die gerne weiterhelfen. +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Kochbuch Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der + Community +* API Dokumentation für die [aktuelle + Version](http://rubydoc.info/gems/sinatra) oder für + [HEAD](http://rubydoc.info/github/sinatra/sinatra) auf http://rubydoc.info +* [CI Server](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.es.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.es.md new file mode 100644 index 000000000..99fbd9874 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.es.md @@ -0,0 +1,2786 @@ +# Sinatra + +*Atención: Este documento es una traducción de la versión en inglés y puede estar desactualizado.* + +Sinatra es un +[DSL](http://es.wikipedia.org/wiki/Lenguaje_específico_del_dominio) para +crear aplicaciones web rápidamente en Ruby con un mínimo esfuerzo: + +``` ruby +# miapp.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Instalar la gema y correr la aplicación con: + +``` shell +gem install sinatra +ruby miapp.rb +``` + +Ver en . + +Se recomienda ejecutar `gem install thin`, porque Sinatra lo utilizará si está disponible. + +## Rutas + +En Sinatra, una ruta es un método HTTP junto a un patrón de un URL. +Cada ruta está asociada a un bloque: + +``` ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. crear algo .. +end + +put '/' do + .. reemplazar algo .. +end + +patch '/' do + .. modificar algo .. +end + +delete '/' do + .. aniquilar algo .. +end + +options '/' do + .. informar algo .. +end + +link '/' do + .. afiliar a algo .. +end + +unlink '/' do + .. separar algo .. +end + +``` + +Las rutas son comparadas en el orden en el que son definidas. La primera ruta +que coincide con la petición es escogida. + +Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a +través del hash `params`: + +``` ruby +get '/hola/:nombre' do + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + "Hola #{params['nombre']}!" +end +``` + +También puede acceder a los parámetros nombrados usando parámetros de bloque: + +``` ruby +get '/hola/:nombre' do |n| + # coincide con "GET /hola/foo" y "GET /hola/bar" + # params['nombre'] es 'foo' o 'bar' + # n almacena params['nombre'] + "Hola #{n}!" +end +``` + +Los patrones de ruta también pueden incluir parámetros splat (o wildcard), +accesibles a través del arreglo `params['splat']`: + +``` ruby +get '/decir/*/al/*' do + # coincide con /decir/hola/al/mundo + params['splat'] # => ["hola", "mundo"] +end + +get '/descargar/*.*' do + # coincide con /descargar/path/al/archivo.xml + params['splat'] # => ["path/al/archivo", "xml"] +end +``` + +O, con parámetros de bloque: + +``` ruby +get '/descargar/*.*' do |path, ext| + [path, ext] # => ["path/al/archivo", "xml"] +end +``` + +Rutas con Expresiones Regulares: + +``` ruby +get /\A\/hola\/([\w]+)\z/ do + "Hola, #{params['captures'].first}!" +end +``` + +O con un parámetro de bloque: + +``` ruby +get %r{/hola/([\w]+)} do |c| + "Hola, #{c}!" +end +``` + +Los patrones de ruta pueden contener parámetros opcionales: + +``` ruby +get '/posts.?:formato?' do + # coincide con "GET /posts" y además admite cualquier extensión, por + # ejemplo, "GET /posts.json", "GET /posts.xml", etc. +end +``` + +A propósito, a menos que desactives la protección para el ataque *path +traversal* (ver más abajo), el path de la petición puede ser modificado +antes de que se compare con los de tus rutas. + +## Condiciones + +Las rutas pueden incluir una variedad de condiciones de selección, como por +ejemplo el user agent: + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Estás usando la versión de Songbird #{params['agent'][0]}" +end + +get '/foo' do + # Coincide con navegadores que no sean songbird +end +``` + +Otras condiciones disponibles son `host_name` y `provides`: + +``` ruby +get '/', :host_name => /^admin\./ do + "Área de Administración, Acceso denegado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +Puede definir sus propias condiciones fácilmente: + +``` ruby +set(:probabilidad) { |valor| condition { rand <= valor } } + +get '/gana_un_auto', :probabilidad => 0.1 do + "Ganaste!" +end + +get '/gana_un_auto' do + "Lo siento, perdiste." +end +``` + +Si su condición acepta más de un argumento, puede pasarle un arreglo. Al +definir la condición, se puede utilizar el operador splat en +la lista de parámetros: + +``` ruby +set(:autorizar) do |*roles| # <- mirá el splat + condition do + unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol } + redirect "/iniciar_sesion/", 303 + end + end +end + +get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do + "Detalles de mi cuenta" +end + +get "/solo/administradores/", :autorizar => :administrador do + "Únicamente para administradores!" +end +``` + +### Valores de Retorno + +El valor de retorno de un bloque de ruta que determina al menos el cuerpo de la +respuesta que se le pasa al cliente HTTP o al siguiente middleware en la pila +de Rack. Lo más común es que sea un string, como en los ejemplos anteriores. +Sin embargo, otros valores también son aceptados. + +Puede devolver cualquier objeto que sea una respuesta Rack válida, un objeto +que represente el cuerpo de una respuesta Rack o un código de estado HTTP: + +* Un arreglo con tres elementos: `[estado (Fixnum), cabeceras (Hash), cuerpo de + la respuesta (responde a #each)]` +* Un arreglo con dos elementos: `[estado (Fixnum), cuerpo de la respuesta + (responde a #each)]` +* Un objeto que responde a `#each` y que le pasa únicamente strings al bloque + dado +* Un Fixnum representando el código de estado + +De esa manera, podemos fácilmente implementar un ejemplo de streaming: + +``` ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +### Comparadores de Rutas Personalizados + +Como se mostró anteriormente, Sinatra permite utilizar strings y expresiones +regulares para definir las rutas. Sin embargo, la cosa no termina ahí. Podés +definir tus propios comparadores muy fácilmente: + +``` ruby +class PatronCualquieraMenos + Match = Struct.new(:captures) + + def initialize(excepto) + @excepto = excepto + @capturas = Match.new([]) + end + + def match(str) + @capturas unless @excepto === str + end +end + +def cualquiera_menos(patron) + PatronCualquieraMenos.new(patron) +end + +get cualquiera_menos("/index") do + # ... +end +``` + +Tenga en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado +similar puede conseguirse más sencillamente: + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +O, usando un lookahead negativo: + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + +### Archivos Estáticos + +Los archivos estáticos son servidos desde el directorio público +`./public`. Puede especificar una ubicación diferente ajustando la +opción `:public_folder`: + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/estaticos' +``` + +Note que el nombre del directorio público no está incluido en la URL. Por +ejemplo, el archivo `./public/css/style.css` se accede a través de +`http://ejemplo.com/css/style.css`. + +Use la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` (ver la sección de configuración para más detalles). + +### Vistas / Plantillas + +Cada lenguaje de plantilla se expone a través de un método de renderizado que +lleva su nombre. Estos métodos simplemente devuelven un string: + +``` ruby +get '/' do + erb :index +end +``` + +Renderiza `views/index.erb`. + +En lugar del nombre de la plantilla podés proporcionar directamente el +contenido de la misma: + +``` ruby +get '/' do + codigo = "<%= Time.now %>" + erb codigo +end +``` + +Los métodos de renderizado, aceptan además un segundo argumento, el hash de +opciones: + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +Renderiza `views/index.erb` incrustado en `views/post.erb` (por +defecto, la plantilla `:index` es incrustada en `views/layout.erb` siempre y +cuando este último archivo exista). + +Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado +de la plantilla: + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Además, puede definir las opciones para un lenguaje de plantillas de forma +general: + +``` ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Las opciones pasadas al método de renderizado tienen precedencia sobre las +definidas mediante `set`. + +Opciones disponibles: + +
+ +
locals
+
+ Lista de variables locales pasadas al documento. Resultan muy útiles cuando + se combinan con parciales. + Ejemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ Encoding utilizado cuando el de un string es dudoso. Por defecto toma el + valor de settings.default_encoding. +
+ +
views
+
+ Directorio desde donde se cargan las vistas. Por defecto toma el valor de + settings.views. +
+ +
layout
+
+ Si es true o false indica que se debe usar, o no, un layout, + respectivamente. También puede ser un símbolo que especifique qué plantilla + usar. Ejemplo: erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ Content-Type que produce la plantilla. El valor por defecto depende de cada + lenguaje de plantillas. +
+ +
scope
+
+ Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia + de la aplicación. Tené en cuenta que si cambiás esta opción las variables de + instancia y los helpers van a dejar de estar disponibles. +
+ +
layout_engine
+
+ Motor de renderizado de plantillas que usa para el layout. Resulta + conveniente para lenguajes que no soportan layouts. Por defecto toma el valor + del motor usado para renderizar la plantilla. + Ejemplo: set :rdoc, :layout_engine => :erb +
+ +
+ Se asume que las plantillas están ubicadas directamente bajo el directorio + ./views. Para usar un directorio de vistas diferente: + set :views, settings.root + '/plantillas' +
+ +
+ Es importante acordarse que siempre tenés que referenciar a las plantillas con + símbolos, incluso cuando se encuentran en un subdirectorio (en este caso + tenés que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Tenés que + usar un símbolo porque los métodos de renderización van a renderizar + directamente cualquier string que se les pase como argumento. +
+
+ +### Lenguajes de Plantillas Disponibles + +Algunos lenguajes tienen varias implementaciones. Para especificar que +implementación usar (y para ser thread-safe), deberías requerirla antes de +usarla: + +``` ruby +require 'rdiscount' # o require 'bluecloth' +get('/') { markdown :index } +``` + +### Plantillas Haml + + + + + + + + + + + + + + +
Dependenciashaml
Expresiones de Archivo.haml
Ejemplohaml :index, :format => :html5
+ +### Plantillas Erb + + + + + + + + + + + + + + +
Dependencias + erubis + o erb (incluida en Ruby) +
Extensiones de Archivo.erb, .rhtml o .erubis (solamente con Erubis)
Ejemploerb :index
+ +### Plantillas Builder + + + + + + + + + + + + + + +
Dependencias + builder +
Extensiones de Archivo.builder
Ejemplobuilder { |xml| xml.em "hola" }
+ +Además, acepta un bloque con la definición de la plantilla (ver ejemplo). + +### Plantillas Nokogiri + + + + + + + + + + + + + + +
Dependenciasnokogiri
Extensiones de Archivo.nokogiri
Ejemplonokogiri { |xml| xml.em "hola" }
+ +Además, acepta un bloque con la definición de la plantilla (ver ejemplo). + +### Plantillas Sass + + + + + + + + + + + + + + +
Dependenciassass
Extensiones de Archivo.sass
Ejemplosass :stylesheet, :style => :expanded
+ +### Plantillas SCSS + + + + + + + + + + + + + + +
Dependenciassass
Extensiones de Archivo.scss
Ejemploscss :stylesheet, :style => :expanded
+ +### Plantillas Less + + + + + + + + + + + + + + +
Dependenciasless
Extensiones de Archivo.less
Ejemploless :stylesheet
+ +### Plantillas Liquid + + + + + + + + + + + + + + +
Dependenciasliquid
Extensiones de Archivo.liquid
Ejemploliquid :index, :locals => { :clave => 'valor' }
+ +Como no va a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla Liquid, casi siempre va a querer pasarle locales. + +### Plantillas Markdown + + + + + + + + + + + + + + +
Dependencias + RDiscount, + RedCarpet, + BlueCloth, + kramdown o + maruku +
Extensiones de Archivo.markdown, .mkd y .md
Ejemplomarkdown :index, :layout_engine => :erb
+ +No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +``` ruby +erb :resumen, :locals => { :texto => markdown(:introduccion) } +``` + +Tenga en cuenta que también podés llamar al método `markdown` desde otras +plantillas: + +``` ruby +%h1 Hola Desde Haml! +%p= markdown(:saludos) +``` + +Como no puede utilizar Ruby desde Markdown, no puede usar layouts escritos en +Markdown. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +### Plantillas Textile + + + + + + + + + + + + + + +
DependenciasRedCloth
Extensiones de Archivo.textile
Ejemplotextile :index, :layout_engine => :erb
+ +No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +``` ruby +erb :resumen, :locals => { :texto => textile(:introduccion) } +``` + +Tené en cuenta que también podés llamar al método `textile` desde otras +plantillas: + +``` ruby +%h1 Hola Desde Haml! +%p= textile(:saludos) +``` + +Como no podés utilizar Ruby desde Textile, no podés usar layouts escritos en +Textile. De todos modos, es posible usar un motor de renderizado para el +layout distinto al de la plantilla pasando la opción `:layout_engine`. + +### Plantillas RDoc + + + + + + + + + + + + + + +
DependenciasRDoc
Extensiones de Archivo.rdoc
Ejemplordoc :README, :layout_engine => :erb
+ +No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto, +generalmente vas a usarlo en combinación con otro motor de renderizado: + +``` ruby +erb :resumen, :locals => { :texto => rdoc(:introduccion) } +``` + +Tené en cuenta que también podés llamar al método `rdoc` desde otras +plantillas: + +``` ruby +%h1 Hola Desde Haml! +%p= rdoc(:saludos) +``` + +Como no podés utilizar Ruby desde RDoc, no podés usar layouts escritos en RDoc. +De todos modos, es posible usar un motor de renderizado para el layout distinto +al de la plantilla pasando la opción `:layout_engine`. + +### Plantillas Radius + + + + + + + + + + + + + + +
DependenciasRadius
Extensiones de Archivo.radius
Ejemploradius :index, :locals => { :clave => 'valor' }
+ +Desde que no se puede utilizar métodos de Ruby (excepto por `yield`) de una +plantilla Radius, casi siempre se necesita pasar locales. + +### Plantillas Markaby + + + + + + + + + + + + + + +
DependenciasMarkaby
Extensiones de Archivo.mab
Ejemplomarkaby { h1 "Bienvenido!" }
+ +Además, acepta un bloque con la definición de la plantilla (ver ejemplo). + +### Plantillas RABL + + + + + + + + + + + + + + +
DependenciasRabl
Extensiones de Archivo.rabl
Ejemplorabl :index
+ +### Plantillas Slim + + + + + + + + + + + + + + +
DependenciasSlim Lang
Extensiones de Archivo.slim
Ejemploslim :index
+ +### Plantillas Creole + + + + + + + + + + + + + + +
DependenciasCreole
Extensiones de Archivo.creole
Ejemplocreole :wiki, :layout_engine => :erb
+ +No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, +generalmente va a usarlo en combinación con otro motor de renderizado: + +``` ruby +erb :resumen, :locals => { :texto => cerole(:introduccion) } +``` + +Debe tomar en cuenta que también puede llamar al método `creole` desde otras +plantillas: + +``` ruby +%h1 Hola Desde Haml! +%p= creole(:saludos) +``` + +Como no podés utilizar Ruby desde Creole, no podés usar layouts escritos en +Creole. De todos modos, es posible usar un motor de renderizado para el layout +distinto al de la plantilla pasando la opción `:layout_engine`. + +### Plantillas CoffeeScript + + + + + + + + + + + + + + +
Dependencias + + CoffeeScript + y un + + mecanismo para ejecutar javascript + +
Extensiones de Archivo.coffee
Ejemplocoffee :index
+ +### Plantillas Stylus + + + + + + + + + + + + + + +
Dependencias + + Stylus + y un + + mecanismo para ejecutar javascript + +
Extensiones de Archivo.styl
Ejemplostylus :index
+ +### Plantillas Yajl + + + + + + + + + + + + + + +
Dependenciasyajl-ruby
Extensiones de Archivo.yajl
Ejemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ +El contenido de la plantilla se evalúa como código Ruby, y la variable `json` es convertida a JSON mediante `#to_json`. + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Las opciones `:callback` y `:variable` se pueden utilizar para decorar el objeto renderizado: + +``` ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +### Plantillas WLang + + + + + + + + + + + + + + +
Dependenciaswlang
Extensiones de Archivo.wlang
Ejemplowlang :index, :locals => { :clave => 'valor' }
+ +Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una +plantilla WLang, casi siempre vas a querer pasarle locales. + +### Plantillas Embebidas + +``` ruby +get '/' do + haml '%div.titulo Hola Mundo' +end +``` + +Renderiza el template embebido en el string. + +### Accediendo a Variables en Plantillas + +Las plantillas son evaluadas dentro del mismo contexto que los manejadores de +ruta. Las variables de instancia asignadas en los manejadores de ruta son +accesibles directamente por las plantillas: + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nombre' +end +``` + +O es posible especificar un Hash de variables locales explícitamente: + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.nombre', :locals => { :bar => foo } +end +``` + +Esto es usado típicamente cuando se renderizan plantillas como parciales desde +adentro de otras plantillas. + +### Plantillas Inline + +Las plantillas pueden ser definidas al final del archivo fuente: + +``` ruby +require 'rubygems' +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.titulo Hola mundo!!!!! +``` + +NOTA: únicamente las plantillas inline definidas en el archivo fuente que +requiere Sinatra son cargadas automáticamente. Llamá `enable +:inline_templates` explícitamente si tenés plantillas inline en otros +archivos fuente. + +### Plantillas Nombradas + +Las plantillas también pueden ser definidas usando el método top-level +`template`: + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.titulo Hola Mundo!' +end + +get '/' do + haml :index +end +``` + +Si existe una plantilla con el nombre "layout", va a ser usada cada vez que +una plantilla es renderizada. Podés desactivar los layouts individualmente +pasando `:layout => false` o globalmente con +`set :haml, :layout => false`: + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Asociando Extensiones de Archivo + +Para asociar una extensión de archivo con un motor de renderizado, usá +`Tilt.register`. Por ejemplo, si querés usar la extensión `tt` para +las plantillas Textile, podés hacer lo siguiente: + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Agregando Tu Propio Motor de Renderizado + +Primero, registrá tu motor con Tilt, y después, creá tu método de renderizado: + +``` ruby +Tilt.register :mipg, MiMotorParaPlantillaGenial + +helpers do + def mypg(*args) render(:mypg, *args) end +end + +get '/' do + mypg :index +end +``` + +Renderiza `./views/index.mypg`. Mirá https://github.com/rtomayko/tilt +para aprender más de Tilt. + +## Filtros + +Los filtros `before` son evaluados antes de cada petición dentro del mismo +contexto que las rutas. Pueden modificar la petición y la respuesta. Las +variables de instancia asignadas en los filtros son accesibles por las rutas y +las plantillas: + +``` ruby +before do + @nota = 'Hey!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Hey!' + params['splat'] #=> 'bar/baz' +end +``` + +Los filtros `after` son evaluados después de cada petición dentro del mismo +contexto y también pueden modificar la petición y la respuesta. Las variables +de instancia asignadas en los filtros `before` y en las rutas son accesibles por +los filtros `after`: + +``` ruby +after do + puts response.status +end +``` + +Nota: A menos que uses el método `body` en lugar de simplemente devolver un +string desde una ruta, el cuerpo de la respuesta no va a estar disponible en +un filtro after, debido a que todavía no se ha generado. + +Los filtros aceptan un patrón opcional, que cuando está presente causa que los +mismos sean evaluados únicamente si el path de la petición coincide con ese +patrón: + +``` ruby +before '/protegido/*' do + autenticar! +end + +after '/crear/:slug' do |slug| + session[:ultimo_slug] = slug +end +``` + +Al igual que las rutas, los filtros también pueden aceptar condiciones: + +``` ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'ejemplo.com' do + # ... +end +``` + +## Ayudantes + +Usá el método top-level *helpers* para definir métodos ayudantes que +pueden ser utilizados dentro de los manejadores de rutas y las plantillas: + +``` ruby +helpers do + def bar(nombre) + "#{nombre}bar" + end +end + +get '/:nombre' do + bar(params['nombre']) +end +``` + +Por cuestiones organizativas, puede resultar conveniente organizar los métodos +ayudantes en distintos módulos: + +``` ruby +module FooUtils + def foo(nombre) "#{nombre}foo" end +end + +module BarUtils + def bar(nombre) "#{nombre}bar" end +end + +helpers FooUtils, BarUtils +``` + +El efecto de utilizar *helpers* de esta manera es el mismo que resulta de +incluir los módulos en la clase de la aplicación. + +### Usando Sesiones + +Una sesión es usada para mantener el estado a través de distintas peticiones. +Cuando están activadas, proporciona un hash de sesión para cada sesión de usuario: + +``` ruby +enable :sessions + +get '/' do + "valor = " << session[:valor].inspect +end + +get '/:valor' do + session[:valor] = params['valor'] +end +``` + +Tené en cuenta que `enable :sessions` guarda todos los datos en una +cookie, lo cual no es siempre deseable (guardar muchos datos va a incrementar +el tráfico, por citar un ejemplo). Podés usar cualquier middleware Rack para +manejar sesiones, de la misma manera que usarías cualquier otro middleware, +pero con la salvedad de que *no* tenés que llamar a `enable :sessions`: + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "valor = " << session[:valor].inspect +end + +get '/:valor' do + session[:valor] = params['valor'] +end +``` + +Para incrementar la seguridad, los datos de la sesión almacenados en +la cookie son firmados con un secreto de sesión. Este secreto, es +generado aleatoriamente por Sinatra. De cualquier manera, hay que +tener en cuenta que cada vez que inicies la aplicación se va a generar +uno nuevo. Así, si querés que todas las instancias de tu aplicación +compartan un único secreto, tenés que definirlo vos: + +``` ruby +set :session_secret, 'super secreto' +``` + +Si necesitás una configuración más específica, `sessions` acepta un +Hash con opciones: + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +### Interrupción + +Para detener inmediatamente una petición dentro de un filtro o una ruta usá: + +``` ruby +halt +``` + +También podés especificar el estado: + +``` ruby +halt 410 +``` + +O el cuerpo: + +``` ruby +halt 'esto va a ser el cuerpo' +``` + +O los dos: + +``` ruby +halt 401, 'salí de acá!' +``` + +Con cabeceras: + +``` ruby +halt 402, { 'Content-Type' => 'text/plain' }, 'venganza' +``` + +Obviamente, es posible utilizar `halt` con una plantilla: + +``` ruby +halt erb(:error) +``` + +### Paso + +Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con +la petición usando `pass`: + +``` ruby +get '/adivina/:quien' do + pass unless params['quien'] == 'Franco' + 'Adivinaste!' +end + +get '/adivina/*' do + 'Erraste!' +end +``` + +Se sale inmediatamente del bloque de la ruta y se le pasa el control a la +siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve 404. + +### Ejecutando Otra Ruta + +Cuando querés obtener el resultado de la llamada a una ruta, `pass` no te va a +servir. Para lograr esto, podés usar `call`: + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Notá que en el ejemplo anterior, es conveniente mover `"bar"` a un +helper, y llamarlo desde `/foo` y `/bar`. Así, vas a simplificar +las pruebas y a mejorar el rendimiento. + +Si querés que la petición se envíe a la misma instancia de la aplicación en +lugar de otra, usá `call!` en lugar de `call`. + +En la especificación de Rack podés encontrar más información sobre +`call`. + +### Asignando el Código de Estado, los Encabezados y el Cuerpo de una Respuesta + +Es posible, y se recomienda, asignar el código de estado y el cuerpo de una +respuesta con el valor de retorno de una ruta. De cualquier manera, en varios +escenarios, puede que sea conveniente asignar el cuerpo en un punto arbitrario +del flujo de ejecución con el método `body`. A partir de ahí, podés usar ese +mismo método para acceder al cuerpo de la respuesta: + +``` ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +También es posible pasarle un bloque a `body`, que será ejecutado por el Rack +handler (podés usar esto para implementar streaming, mirá "Valores de retorno"). + +De manera similar, también podés asignar el código de estado y encabezados: + +``` ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +También, al igual que `body`, tanto `status` como `headers` pueden utilizarse +para obtener sus valores cuando no se les pasa argumentos. + +### Streaming De Respuestas + +A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no +terminaste de generar su cuerpo. También es posible que, en algunos casos, +quieras seguir enviando información hasta que el cliente cierre la conexión. +Cuando esto ocurra, el helper `stream` te va a ser de gran ayuda: + +``` ruby +get '/' do + stream do |out| + out << "Esto va a ser legen -\n" + sleep 0.5 + out << " (esperalo) \n" + sleep 1 + out << "- dario!\n" + end +end +``` + +Podés implementar APIs de streaming, +[Server-Sent Events](http://dev.w3.org/html5/eventsource/) y puede ser usado +como base para [WebSockets](http://es.wikipedia.org/wiki/WebSockets). También +puede ser usado para incrementar el throughput si solo una parte del contenido +depende de un recurso lento. + +Hay que tener en cuenta que el comportamiento del streaming, especialmente el +número de peticiones concurrentes, depende del servidor web utilizado para +alojar la aplicación. Puede que algunos servidores, como es el caso de +WEBRick, no soporten streaming directamente, así el cuerpo de la respuesta será +enviado completamente de una vez cuando el bloque pasado a `stream` finalice su +ejecución. Si estás usando Shotgun, el streaming no va a funcionar. + +Cuando se pasa `keep_open` como parámetro, no se va a enviar el mensaje +`close` al objeto de stream. Queda en vos cerrarlo en el punto de ejecución +que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es +posible solo en servidores que soporten eventos, como Thin o Rainbows. El +resto de los servidores van a cerrar el stream de todos modos: + +``` ruby +set :server, :thin +conexiones = [] + +get '/' do + # mantenemos abierto el stream + stream(:keep_open) { |salida| conexiones << salida } +end + +post '/' do + # escribimos a todos los streams abiertos + conexiones.each { |salida| salida << params['mensaje'] << "\n" } + "mensaje enviado" +end +``` + +### Log (Registro) + +En el ámbito de la petición, el helper `logger` (registrador) expone +una instancia de `Logger`: + +``` ruby +get '/' do + logger.info "cargando datos" + # ... +end +``` + +Este logger tiene en cuenta la configuración de logueo de tu Rack +handler. Si el logueo está desactivado, este método va a devolver un +objeto que se comporta como un logger pero que en realidad no hace +nada. Así, no vas a tener que preocuparte por esta situación. + +Tené en cuenta que el logueo está habilitado por defecto únicamente +para `Sinatra::Application`. Si heredaste de +`Sinatra::Base`, probablemente quieras habilitarlo manualmente: + +``` ruby +class MiApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Para evitar que se inicialice cualquier middleware de logging, configurá +`logging` a `nil`. Tené en cuenta que, cuando hagas esto, `logger` va a +devolver `nil`. Un caso común es cuando querés usar tu propio logger. Sinatra +va a usar lo que encuentre en `env['rack.logger']`. + +### Tipos Mime + +Cuando usás `send_file` o archivos estáticos tal vez tengas tipos mime +que Sinatra no entiende. Usá `mime_type` para registrarlos a través de la +extensión de archivo: + +``` ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +También lo podés usar con el ayudante `content_type`: + +``` ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generando URLs + +Para generar URLs deberías usar el método `url`. Por ejemplo, en Haml: + +``` ruby +%a{:href => url('/foo')} foo +``` + +Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes. + +Este método también puede invocarse mediante su alias `to` (mirá un ejemplo +a continuación). + +### Redirección del Navegador + +Podés redireccionar al navegador con el método `redirect`: + +``` ruby +get '/foo' do + redirect to('/bar') +end +``` + +Cualquier parámetro adicional se utiliza de la misma manera que los argumentos +pasados a `halt`: + +``` ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'te confundiste de lugar, compañero' +``` + +También podés redireccionar fácilmente de vuelta hacia la página desde donde +vino el usuario con `redirect back`: + +``` ruby +get '/foo' do + "hacer algo" +end + +get '/bar' do + hacer_algo + redirect back +end +``` + +Para pasar argumentos con una redirección, podés agregarlos a la cadena de +búsqueda: + +``` ruby +redirect to('/bar?suma=42') +``` + +O usar una sesión: + +``` ruby +enable :sessions + +get '/foo' do + session[:secreto] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secreto] +end +``` + +### Cache Control + +Asignar tus encabezados correctamente es el cimiento para realizar un cacheo +HTTP correcto. + +Podés asignar el encabezado Cache-Control fácilmente: + +``` ruby +get '/' do + cache_control :public + "cachealo!" +end +``` + +Pro tip: configurar el cacheo en un filtro `before`: + +``` ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si estás usando el helper `expires` para definir el encabezado correspondiente, +`Cache-Control` se va a definir automáticamente: + +``` ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Para usar cachés adecuadamente, deberías considerar usar `etag` o +`last_modified`. Es recomendable que llames a estos asistentes *antes* de hacer +cualquier trabajo pesado, ya que van a enviar la respuesta inmediatamente si +el cliente ya tiene la versión actual en su caché: + +``` ruby +get '/articulo/:id' do + @articulo = Articulo.find params['id'] + last_modified @articulo.updated_at + etag @articulo.sha1 + erb :articulo +end +``` + +También es posible usar una +[weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +``` ruby +etag @articulo.sha1, :weak +``` + +Estos helpers no van a cachear nada por vos, sino que van a facilitar la +información necesaria para poder hacerlo. Si estás buscando soluciones rápidas +de cacheo con proxys reversos, mirá +[rack-cache](https://github.com/rtomayko/rack-cache): + +``` ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hola" +end +``` + +Usá la configuración `:static_cache_control` para agregar el encabezado +`Cache-Control` a archivos estáticos (ver la sección de configuración +para más detalles). + +De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las +cabeceras If-Match o If-None-Match se le asigna el valor `*` cuando el +recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get) +y potentes (como put) que el recurso existe, mientras que para el resto +(como post) asume que no. Podés cambiar este comportamiento con la opción +`:new_resource`: + +``` ruby +get '/crear' do + etag '', :new_resource => true + Articulo.create + erb :nuevo_articulo +end +``` + +Si querés seguir usando una weak ETag, indicalo con la opción `:kind`: + +``` ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Enviando Archivos + +Para enviar archivos, podés usar el método `send_file`: + +``` ruby +get '/' do + send_file 'foo.png' +end +``` + +Además acepta un par de opciones: + +``` ruby +send_file 'foo.png', :type => :jpg +``` + +Estas opciones son: + +[filename] + nombre del archivo devuelto, por defecto es el nombre real del archivo. + +[last_modified] + valor para el encabezado Last-Modified, por defecto toma el mtime del archivo. + +[type] + el content type que se va a utilizar, si no está presente se intenta adivinar + a partir de la extensión del archivo. + +[disposition] + se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los + siguientes valores: `nil` (por defecto), `:attachment` e + `:inline` + +[length] + encabezado Content-Length, por defecto toma el tamaño del archivo. + +[status] + código de estado devuelto. Resulta útil al enviar un archivo estático como una + página de error. + +Si el Rack handler lo soporta, se intentará no transmitir directamente desde el +proceso de Ruby. Si usás este método, Sinatra se va a encargar automáticamente de las +peticiones de rango. + +### Accediendo al objeto de la petición + +El objeto de la petición entrante puede ser accedido desde el nivel de la +petición (filtros, rutas y manejadores de errores) a través del método +`request`: + +``` ruby +# app corriendo en http://ejemplo.com/ejemplo +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # cuerpo de la petición enviado por el cliente (ver más abajo) + request.scheme # "http" + request.script_name # "/ejemplo" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # longitud de request.body + request.media_type # tipo de medio de request.body + request.host # "ejemplo.com" + request.get? # true (hay métodos análogos para los otros verbos) + request.form_data? # false + request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA + request.referrer # la referencia del cliente o '/' + request.user_agent # user agent (usado por la condición :agent) + request.cookies # hash de las cookies del navegador + request.xhr? # es una petición ajax? + request.url # "http://ejemplo.com/ejemplo/foo" + request.path # "/ejemplo/foo" + request.ip # dirección IP del cliente + request.secure? # false (sería true sobre ssl) + request.forwarded? # true (si se está corriendo atrás de un proxy reverso) + requuest.env # hash de entorno directamente entregado por Rack +end +``` + +Algunas opciones, como `script_name` o `path_info` pueden +también ser escritas: + +``` ruby +before { request.path_info = "/" } + +get "/" do + "todas las peticiones llegan acá" +end +``` + +El objeto `request.body` es una instancia de IO o StringIO: + +``` ruby +post "/api" do + request.body.rewind # en caso de que alguien ya lo haya leído + datos = JSON.parse request.body.read + "Hola #{datos['nombre']}!" +end +``` + +### Archivos Adjuntos + +Podés usar el helper `attachment` para indicarle al navegador que +almacene la respuesta en el disco en lugar de mostrarla en pantalla: + +``` ruby +get '/' do + attachment + "guardalo!" +end +``` + +También podés pasarle un nombre de archivo: + +``` ruby +get '/' do + attachment "info.txt" + "guardalo!" +end +``` + +### Fecha y Hora + +Sinatra pone a tu disposición el helper `time_for`, que genera un objeto `Time` +a partir del valor que recibe como argumento. Este valor puede ser un +`String`, pero también es capaz de convertir objetos `DateTime`, `Date` y de +otras clases similares: + +``` ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "todavía hay tiempo" +end +``` + +Este método es usado internamente por métodos como `expires` y `last_modified`, +entre otros. Por lo tanto, es posible extender el comportamiento de estos +métodos sobreescribiendo `time_for` en tu aplicación: + +``` ruby +helpers do + def time_for(value) + case value + when :ayer then Time.now - 24*60*60 + when :mañana then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :ayer + expires :mañana + "hola" +end +``` + +### Buscando los Archivos de las Plantillas + +El helper `find_template` se utiliza para encontrar los archivos de las +plantillas que se van a renderizar: + +``` ruby +find_template settings.views, 'foo', Tilt[:haml] do |archivo| + puts "podría ser #{archivo}" +end +``` + +Si bien esto no es muy útil, lo interesante es que podés sobreescribir este +método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para +poder utilizar más de un directorio de vistas: + +``` ruby +set :views, ['vistas', 'plantillas'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Otro ejemplo consiste en usar directorios diferentes para los distintos motores +de renderizado: + +``` ruby +set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:defecto] + super(folder, name, engine, &block) + end +end +``` + +¡Es muy fácil convertir estos ejemplos en una extensión y compartirla! + +Notá que `find_template` no verifica si un archivo existe realmente, sino +que llama al bloque que recibe para cada path posible. Esto no representa un +problema de rendimiento debido a que `render` va a usar `break` ni bien +encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y +su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno +tener en cuenta lo anterior si escribís un método extraño. + +## Configuración + +Ejecutar una vez, en el inicio, en cualquier entorno: + +``` ruby +configure do + # asignando una opción + set :opcion, 'valor' + + # asignando varias opciones + set :a => 1, :b => 2 + + # atajo para `set :opcion, true` + enable :opcion + + # atajo para `set :opcion, false` + disable :opcion + + # también podés tener configuraciones dinámicas usando bloques + set(:css_dir) { File.join(views, 'css') } +end +``` + +Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es +`:production`: + +``` ruby +configure :production do + ... +end +``` + +Ejecutar cuando el entorno es `:production` o `:test`: + +``` ruby +configure :production, :test do + ... +end +``` + +Podés acceder a estas opciones utilizando el método `settings`: + +``` ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configurando la Protección de Ataques + +Sinatra usa [Rack::Protection](https://github.com/rkh/rack-protection#readme) +para defender a tu aplicación de los ataques más comunes. Si por algún motivo, +querés desactivar esta funcionalidad, podés hacerlo como se indica a +continuación (ten en cuenta que tu aplicación va a quedar expuesta a un +montón de vulnerabilidades bien conocidas): + +``` ruby +disable :protection +``` + +También es posible desactivar una única capa de defensa: + +``` ruby +set :protection, :except => :path_traversal +``` + +O varias: + +``` ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +### Configuraciones Disponibles + +
+
absolute_redirects
+
+ Si está deshabilitada, Sinatra va a permitir + redirecciones relativas, sin embargo, como consecuencia + de esto, va a dejar de cumplir con el RFC 2616 (HTTP + 1.1), que solamente permite redirecciones absolutas. + + Activalo si tu apliación está corriendo atrás de un proxy + reverso que no se ha configurado adecuadamente. Notá que + el helper url va a seguir produciendo URLs absolutas, a + menos que le pasés false como segundo parámetro. + + Deshabilitada por defecto. +
+ +
add_charset
+
+ Tipos mime a los que el helper content_type les + añade automáticamente el charset. + + En general, no deberías asignar directamente esta opción, + sino añadirle los charsets que quieras: + settings.add_charset << "application/foobar" +
+ +
app_file
+
+ Path del archivo principal de la aplicación, se utiliza + para detectar la raíz del proyecto, el directorio de las + vistas y el público, así como las plantillas inline. +
+ +
bind
+
+ Dirección IP que utilizará el servidor integrado (por + defecto: 0.0.0.0). +
+ +
default_encoding
+
+ Encoding utilizado cuando el mismo se desconoce (por + defecto "utf-8"). +
+ +
dump_errors
+
+ Mostrar errores en el log. +
+ +
environment
+
+ Entorno actual, por defecto toma el valor de + ENV['RACK_ENV'], o "development" si no + está disponible. +
+ +
logging
+
+ Define si se utiliza el logger. +
+ +
lock
+
+ Coloca un lock alrededor de cada petición, procesando + solamente una por proceso. + + Habilitá esta opción si tu aplicación no es thread-safe. + Se encuentra deshabilitada por defecto. +
+ +
method_override
+
+ Utiliza el parámetro _method para permtir + formularios put/delete en navegadores que no los + soportan. +
+ +
port
+
+ Puerto en el que escuchará el servidor integrado. +
+ +
prefixed_redirects
+
+ Define si inserta request.script_name en las + redirecciones cuando no se proporciona un path absoluto. + De esta manera, cuando está habilitada, + redirect '/foo' se comporta de la misma manera + que redirect to('/foo'). Se encuentra + deshabilitada por defecto. +
+ +
protection
+
+ Define si deben activarse las protecciones para los + ataques web más comunes. Para más detalles mirá la + sección sobre la configuración de protección de ataques + más arriba. +
+ +
public_dir
+
+ Alias para public_folder, que se encuentra a + continuación. +
+ +
public_folder
+
+ Lugar del directorio desde donde se sirven los archivos + públicos. Solo se utiliza cuando se sirven archivos + estáticos (ver la opción static). Si no + está presente, se infiere del valor de la opción + app_file. +
+ +
reload_templates
+
+ Define si se recargan las plantillas entre peticiones. + + Se encuentra activado en el entorno de desarrollo. +
+ +
root
+
+ Lugar del directorio raíz del proyecto. Si no está + presente, se infiere del valor de la opción + app_file. +
+ +
raise_errors
+
+ Elevar excepciones (detiene la aplicación). Se + encuentra activada por defecto cuando el valor de + environment es "test". En caso + contrario estará desactivada. +
+ +
run
+
+ Cuando está habilitada, Sinatra se va a encargar de + iniciar el servidor web, no la habilites cuando estés + usando rackup o algún otro medio. +
+ +
running
+
+ Indica si el servidor integrado está ejecutándose, ¡no + cambiés esta configuración!. +
+ +
server
+
+ Servidor, o lista de servidores, para usar como servidor + integrado. Por defecto: ['thin', 'mongrel', 'webrick'], + el orden establece la prioridad. +
+ +
sessions
+
+ Habilita el soporte de sesiones basadas en cookies a + través de Rack::Session::Cookie. Ver la + sección 'Usando Sesiones' para más información. +
+ +
show_exceptions
+
+ Muestra un stack trace en el navegador cuando ocurre una + excepción. Se encuentra activada por defecto cuando el + valor de environment es "development". + En caso contrario estará desactivada. +
+ +
static
+
+ Define si Sinatra debe encargarse de servir archivos + estáticos. + + Deshabilitala cuando uses un servidor capaz de + hacerlo por sí solo, porque mejorará el + rendimiento. Se encuentra habilitada por + defecto en el estilo clásico y desactivado en el + el modular. +
+ +
static_cache_control
+
+ Cuando Sinatra está sirviendo archivos estáticos, y + esta opción está habilitada, les va a agregar encabezados + Cache-Control a las respuestas. Para esto + utiliza el helper cache_control. Se encuentra + deshabilitada por defecto. Notar que es necesario + utilizar un array cuando se asignan múltiples valores: + set :static_cache_control, [:public, :max_age => 300]. +
+ +
views
+
+ Path del directorio de las vistas. Si no está presente, + se infiere del valor de la opción app_file. +
+
+ +## Entornos + +Existen tres entornos (`environments`) predefinidos: `development`, +`production` y `test`. El entorno por defecto es +`development` y tiene algunas particularidades: + +* Se recargan las plantillas entre una petición y la siguiente, a diferencia +de `production` y `test`, donde se cachean. +* Se instalan manejadores de errores `not_found` y `error` +especiales que muestran un stack trace en el navegador cuando son disparados. + +Para utilizar alguno de los otros entornos puede asignarse el valor +correspondiente a la variable de entorno `RACK_ENV`, o bien utilizar la opción +`-e` al ejecutar la aplicación: + +``` shell +ruby mi_app.rb -e +``` + +Los métodos `development?`, `test?` y `production?` te permiten conocer el +entorno actual. + +## Manejo de Errores + +Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas +y los filtros `before`, lo que significa que podés usar, por ejemplo, +`haml`, `erb`, `halt`, etc. + +### No encontrado (Not Found) + +Cuando se eleva una excepción `Sinatra::NotFound`, o el código de +estado de la respuesta es 404, el manejador `not_found` es invocado: + +``` ruby +not_found do + 'No existo' +end +``` + +### Error + +El manejador `error` es invocado cada vez que una excepción es elevada +desde un bloque de ruta o un filtro. El objeto de la excepción se puede +obtener de la variable Rack `sinatra.error`: + +``` ruby +error do + 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].message +end +``` + +Errores personalizados: + +``` ruby +error MiErrorPersonalizado do + 'Lo que pasó fue...' + env['sinatra.error'].message +end +``` + +Entonces, si pasa esto: + +``` ruby +get '/' do + raise MiErrorPersonalizado, 'algo malo' +end +``` + +Obtenés esto: + + Lo que pasó fue... algo malo + +También, podés instalar un manejador de errores para un código de estado: + +``` ruby +error 403 do + 'Acceso prohibido' +end + +get '/secreto' do + 403 +end +``` + +O un rango: + +``` ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra instala manejadores `not_found` y `error` especiales +cuando se ejecuta dentro del entorno de desarrollo "development". + +## Rack Middleware + +Sinatra corre sobre [Rack](http://rack.github.io/), una interfaz minimalista +que es un estándar para frameworks webs escritos en Ruby. Una de las +características más interesantes de Rack para los desarrolladores de aplicaciones +es el soporte de "middleware" -- componentes que se ubican entre el servidor y +tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para +proporcionar varios tipos de funcionalidades comunes. + +Sinatra hace muy sencillo construir tuberías de Rack middleware a través del +método top-level `use`: + +``` ruby +require 'sinatra' +require 'mi_middleware_personalizado' + +use Rack::Lint +use MiMiddlewarePersonalizado + +get '/hola' do + 'Hola Mundo' +end +``` + +La semántica de `use` es idéntica a la definida para el DSL +Rack::Builder[http://rubydoc.info/github/rack/rack/master/Rack/Builder] (más +frecuentemente usado en archivos rackup). Por ejemplo, el método `use` +acepta argumentos múltiples/variables así como bloques: + +``` ruby +use Rack::Auth::Basic do |nombre_de_usuario, password| + nombre_de_usuario == 'admin' && password == 'secreto' +end +``` + +Rack es distribuido con una variedad de middleware estándar para logging, +debugging, enrutamiento URL, autenticación y manejo de sesiones. Sinatra +usa muchos de estos componentes automáticamente de acuerdo a su configuración +para que usualmente no tengas que usarlas (con `use`) explícitamente. + +Podés encontrar middleware útil en +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +o en la [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Pruebas + +Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando +cualquier framework o librería de pruebas basada en Rack. Se recomienda usar +[Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames): + +``` ruby +require 'mi_app_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MiAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_mi_defecto + get '/' + assert_equal 'Hola Mundo!', last_response.body + end + + def test_con_parametros + get '/saludar', :name => 'Franco' + assert_equal 'Hola Frank!', last_response.body + end + + def test_con_entorno_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Estás usando Songbird!", last_response.body + end +end +``` + +## Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares + +Definir tu aplicación en el nivel superior funciona bien para micro-aplicaciones +pero trae inconvenientes considerables a la hora de construir componentes +reutilizables como Rack middleware, Rails metal, librerías simples con un +componente de servidor o incluso extensiones de Sinatra. El DSL de alto nivel +asume una configuración apropiada para micro-aplicaciones (por ejemplo, un +único archivo de aplicación, los directorios `./public` y +`./views`, logging, página con detalles de excepción, etc.). Ahí es +donde `Sinatra::Base` entra en el juego: + +``` ruby +require 'sinatra/base' + +class MiApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hola Mundo!' + end +end +``` + +Las subclases de `Sinatra::Base` tienen disponibles exactamente los +mismos métodos que los provistos por el DSL de top-level. La mayoría de las +aplicaciones top-level se pueden convertir en componentes +`Sinatra::Base` con dos modificaciones: + +* Tu archivo debe requerir `sinatra/base` en lugar de `sinatra`; de otra + manera, todos los métodos del DSL de sinatra son importados dentro del + espacio de nombres principal. +* Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación + en una subclase de `Sinatra::Base`. + +`Sinatra::Base` es una pizarra en blanco. La mayoría de las opciones están +desactivadas por defecto, incluyendo el servidor incorporado. Mirá +[Opciones y Configuraciones](http://sinatra.github.com/configuration.html) +para detalles sobre las opciones disponibles y su comportamiento. + +### Estilo Modular vs. Clásico + +Contrariamente a la creencia popular, no hay nada de malo con el estilo clásico. +Si se ajusta a tu aplicación, no es necesario que la cambies a una modular. + +La desventaja de usar el estilo clásico en lugar del modular consiste en que +solamente podés tener una aplicación Sinatra por proceso Ruby. Si tenés +planificado usar más, cambiá al estilo modular. Al mismo tiempo, ten en +cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos +clásico y modular. + +A continuación se detallan las diferencias (sútiles) entre las configuraciones +de ambos estilos: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ConfiguraciónClásicaModular
app_filearchivo que carga sinatraarchivo con la subclase de Sinatra::Base
run$0 == app_filefalse
loggingtruefalse
method_overridetruefalse
inline_templatestruefalse
statictruefalse
+ +### Sirviendo una Aplicación Modular + +Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla +activamente con `run!`: + +``` ruby +# mi_app.rb +require 'sinatra/base' + +class MiApp < Sinatra::Base + # ... código de la app ... + + # iniciar el servidor si el archivo fue ejecutado directamente + run! if app_file == $0 +end +``` + +Iniciar con: + +``` shell +ruby mi_app.rb +``` + +O, con un archivo `config.ru`, que permite usar cualquier handler Rack: + +``` ruby +# config.ru +require './mi_app' +run MiApp +``` + +Después ejecutar: + +``` shell +rackup -p 4567 +``` + +### Usando una Aplicación Clásica con un Archivo config.ru + +Escribí el archivo de tu aplicación: + +``` ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hola mundo!' +end +``` + +Y el `config.ru` correspondiente: + +``` ruby +require './app' +run Sinatra::Application +``` + +### ¿Cuándo usar config.ru? + +Indicadores de que probablemente querés usar `config.ru`: + +* Querés realizar el deploy con un handler Rack distinto (Passenger, Unicorn, + Heroku, ...). +* Querés usar más de una subclase de `Sinatra::Base`. +* Querés usar Sinatra únicamente para middleware, pero no como un endpoint. + +No hay necesidad de utilizar un archivo `config.ru` exclusivamente +porque tenés una aplicación modular, y no necesitás una aplicación modular para +iniciarla con `config.ru`. + +### Utilizando Sinatra como Middleware + +Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, +cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack +como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier +aplicación basada en Rack (Rails/Ramaze/Camping/...): + +``` ruby +require 'sinatra/base' + +class PantallaDeLogin < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['nombre'] == 'admin' && params['password'] == 'admin' + session['nombre_de_usuario'] = params['nombre'] + else + redirect '/login' + end + end +end + +class MiApp < Sinatra::Base + # el middleware se ejecutará antes que los filtros + use PantallaDeLogin + + before do + unless session['nombre_de_usuario'] + halt "Acceso denegado, por favor iniciá sesión." + end + end + + get('/') { "Hola #{session['nombre_de_usuario']}." } +end +``` + +### Creación Dinámica de Aplicaciones + +Puede que en algunas ocasiones quieras crear nuevas aplicaciones en +tiempo de ejecución sin tener que asignarlas a una constante. Para +esto tenés `Sinatra.new`: + +``` ruby +require 'sinatra/base' +mi_app = Sinatra.new { get('/') { "hola" } } +mi_app.run! +``` + +Acepta como argumento opcional una aplicación desde la que se +heredará: + +``` ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MisHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Construir aplicaciones de esta forma resulta especialmente útil para +testear extensiones Sinatra o para usar Sinatra en tus librerías. + +Por otro lado, hace extremadamente sencillo usar Sinatra como +middleware: + +``` ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run ProyectoRails::Application +``` + +## Ámbitos y Ligaduras + +El ámbito en el que te encontrás determina que métodos y variables están +disponibles. + +### Ámbito de Aplicación/Clase + +Cada aplicación Sinatra es una subclase de `Sinatra::Base`. Si estás +usando el DSL de top-level (`require 'sinatra'`), entonces esta clase es +`Sinatra::Application`, de otra manera es la subclase que creaste +explícitamente. Al nivel de la clase tenés métodos como `get` o `before`, pero +no podés acceder a los objetos `request` o `session`, ya que hay una única +clase de la aplicación para todas las peticiones. + +Las opciones creadas utilizando `set` son métodos al nivel de la clase: + +``` ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, ya no estoy en el ámbito de la aplicación! + end +end +``` + +Tenés la ligadura al ámbito de la aplicación dentro de: + +* El cuerpo de la clase de tu aplicación +* Métodos definidos por extensiones +* El bloque pasado a `helpers` +* Procs/bloques usados como el valor para `set` + +Este ámbito puede alcanzarse de las siguientes maneras: + +* A través del objeto pasado a los bloques de configuración (`configure { |c| ...}`) +* Llamando a `settings` desde dentro del ámbito de la petición + +### Ámbito de Petición/Instancia + +Para cada petición entrante, una nueva instancia de la clase de tu aplicación +es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este +ámbito podés acceder a los objetos `request` y `session` o llamar a los métodos +de renderización como `erb` o `haml`. Podés acceder al ámbito de la aplicación +desde el ámbito de la petición utilizando `settings`: + +``` ruby +class MiApp < Sinatra::Base + # Ey, estoy en el ámbito de la aplicación! + get '/definir_ruta/:nombre' do + # Ámbito de petición para '/definir_ruta/:nombre' + @valor = 42 + + settings.get("/#{params['nombre']}") do + # Ámbito de petición para "/#{params['nombre']}" + @valor # => nil (no es la misma petición) + end + + "Ruta definida!" + end +end +``` + +Tenés la ligadura al ámbito de la petición dentro de: + +* bloques pasados a get/head/post/put/delete/options +* filtros before/after +* métodos ayudantes +* plantillas/vistas + +### Ámbito de Delegación + +El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier +manera, no se comporta 100% como el ámbito de clase porque no tenés la ligadura +de la clase: únicamente métodos marcados explícitamente para delegación están +disponibles y no compartís variables/estado con el ámbito de clase (léase: +tenés un `self` diferente). Podés agregar delegaciones de método llamando a +`Sinatra::Delegator.delegate :nombre_del_metodo`. + +Tenés la ligadura al ámbito de delegación dentro de: + +* La ligadura del top-level, si hiciste `require "sinatra"` +* Un objeto extendido con el mixin `Sinatra::Delegator` + +Hechale un vistazo al código: acá está el +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +que [extiende el objeto main](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Línea de Comandos + +Las aplicaciones Sinatra pueden ser ejecutadas directamente: + +``` shell +ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR] +``` + +Las opciones son: + +``` +-h # ayuda +-p # asigna el puerto (4567 es usado por defecto) +-o # asigna el host (0.0.0.0 es usado por defecto) +-e # asigna el entorno (development es usado por defecto) +-s # especifica el servidor/manejador rack (thin es usado por defecto) +-x # activa el mutex lock (está desactivado por defecto) +``` + +## Versiones de Ruby Soportadas + +Las siguientes versiones de Ruby son soportadas oficialmente: + +
+
Ruby 1.8.7
+
+ 1.8.7 es soportado completamente. Sin embargo, si no hay nada que te lo + prohiba, te recomendamos que uses 1.9.2 o cambies a JRuby o Rubinius. No se + dejará de dar soporte a 1.8.7 hasta Sinatra 2.0 y Ruby 2.0, aunque si se + libera la versión 1.8.8 de Ruby las cosas podrían llegar a cambiar. Sin + embargo, que eso ocurra es muy poco probable, e incluso el caso de que lo + haga, puede que se siga dando soporte a 1.8.7. Hemos dejado de soportar + Ruby 1.8.6. Si querés ejecutar Sinatra sobre 1.8.6, podés utilizar la + versión 1.2, pero ten en cuenta que una vez que Sinatra 1.4.0 sea liberado, + ya no se corregirán errores por más que se reciban reportes de los mismos. +
+ +
Ruby 1.9.2
+
+ 1.9.2 es soportado y recomendado. No uses 1.9.2p0, porque se producen fallos + de segmentación cuando se ejecuta Sinatra. El soporte se mantendrá al menos + hasta que se libere la versión 1.9.4/2.0 de Ruby. El soporte para la última + versión de la serie 1.9 se mantendrá mientras lo haga el equipo principal de Ruby. +
+ +
Ruby 1.9.3
+
+ 1.9.3 es soportado y recomendado. Ten en cuenta que el cambio a 1.9.3 desde + una versión anterior va a invalidar todas las sesiones. +
+ +
Rubinius
+
+ Rubinius es soportado oficialmente (Rubinius >= 1.2.4). Todo funciona + correctamente, incluyendo los lenguajes de plantillas. La próxima versión, + 2.0, también es soportada, incluyendo el modo 1.9. +
+ +
JRuby
+
+ JRuby es soportado oficialmente (JRuby >= 1.6.7). No se conocen problemas + con librerías de plantillas de terceras partes. Sin embargo, si elegís usar + JRuby, deberías examinar sus Rack handlers porque el servidor web Thin no es + soportado completamente. El soporte de JRuby para extensiones C se encuentra + en una etapa experimental, sin embargo, de momento, solamente RDiscount, + Redcarpet, RedCloth y Yajl, así como Thin y Mongrel se ven afectadas. +
+
+ +Siempre le prestamos atención a las nuevas versiones de Ruby. + +Las siguientes implementaciones de Ruby no se encuentran soportadas +oficialmente. De cualquier manera, pueden ejecutar Sinatra: + +* Versiones anteriores de JRuby y Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev e IronRuby +* Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los uses) + +No ser soportada oficialmente, significa que si las cosas se rompen +ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino +el suyo. + +Nuestro servidor CI también se ejecuta sobre ruby-head (que será la próxima +versión 2.1.0) y la rama 1.9.4. Como están en movimiento constante, no podemos +garantizar nada. De todas formas, podés contar con que tanto 1.9.4-p0 como +2.1.0-p0 sea soportadas. + +Sinatra debería funcionar en cualquier sistema operativo soportado por la +implementación de Ruby elegida. + +En este momento, no vas a poder ejecutar Sinatra en Cardinal, SmallRuby, +BlueRuby o cualquier versión de Ruby anterior a 1.8.7. + +## A la Vanguardia + +Si querés usar el código de Sinatra más reciente, sentite libre de ejecutar +tu aplicación sobre la rama master, en general es bastante estable. + +También liberamos prereleases de vez en cuando, así, podés hacer: + +``` shell +gem install sinatra --pre +``` + +Para obtener algunas de las últimas características. + +### Con Bundler + +Esta es la manera recomendada para ejecutar tu aplicación sobre la última +versión de Sinatra usando [Bundler](http://gembundler.com/). + +Primero, instalá Bundler si no lo hiciste todavía: + +``` shell +gem install bundler +``` + +Después, en el directorio de tu proyecto, creá un archivo `Gemfile`: + +``` ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# otras dependencias +gem 'haml' # por ejemplo, si usás haml +gem 'activerecord', '~> 3.0' # quizás también necesités ActiveRecord 3.x +``` + +Tené en cuenta que tenés que listar todas las dependencias directas de tu +aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) +porque Bundler las agrega directamente. + +Ahora podés arrancar tu aplicación así: + +``` shell +bundle exec ruby miapp.rb +``` + +### Con Git + +Cloná el repositorio localmente y ejecutá tu aplicación, asegurándote que el +directorio `sinatra/lib` esté en el `$LOAD_PATH`: + +``` shell +cd miapp +git clone git://github.com/sinatra/sinatra.git +ruby -Isinatra/lib miapp.rb +``` + +Para actualizar el código fuente de Sinatra en el futuro: + +``` shell +cd miapp/sinatra +git pull +``` + +### Instalación Global + +Podés construir la gem vos mismo: + +``` shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +Si instalás tus gems como root, el último paso debería ser + +``` shell +sudo rake install +``` + +## Versionado + +Sinatra utiliza el [Versionado Semántico](http://semver.org/), +siguiendo las especificaciones SemVer y SemVerTag. + +## Lecturas Recomendadas + +* [Sito web del proyecto](http://www.sinatrarb.com/) - Documentación + adicional, noticias, y enlaces a otros recursos. +* [Contribuyendo](http://www.sinatrarb.com/contributing) - ¿Encontraste un + error?. ¿Necesitás ayuda?. ¿Tenés un parche?. +* [Seguimiento de problemas](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Lista de Correo](http://groups.google.com/group/sinatrarb/topics) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) en http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutorial (en inglés). +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Recetas contribuidas + por la comunidad (en inglés). +* Documentación de la API para la + [última versión liberada](http://rubydoc.info/gems/sinatra) o para la + [rama de desarrollo actual](http://rubydoc.info/github/sinatra/sinatra) + en http://rubydoc.info/ +* [Servidor de CI](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.fr.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.fr.md new file mode 100644 index 000000000..8035175e4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.fr.md @@ -0,0 +1,2988 @@ +# Sinatra +*Attention : Ce document correspond à la traduction de la version anglaise et +il n'est peut être plus à jour.* + +Sinatra est un [DSL](http://fr.wikipedia.org/wiki/Langage_dédié) pour +créer rapidement et facilement des applications web en Ruby : + +``` ruby +# mon_application.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Installez la gem Sinatra : + +``` shell +gem install sinatra +``` + +Puis lancez votre programme : + +``` shell +ruby mon_application.rb +``` + +Le résultat est visible sur : http://localhost:4567 + +Il est recommandé d'exécuter également `gem install thin`, pour que +Sinatra utilise le server Thin quand il est disponible. + +## Table des matières + +* [Sinatra](#sinatra) + * [Table des matières](#table-des-matières) + * [Routes](#routes) + * [Conditions](#conditions) + * [Valeurs de retour](#valeurs-de-retour) + * [Masques de route spécifiques](#masques-de-route-spécifiques) + * [Fichiers statiques](#fichiers-statiques) + * [Vues / Templates](#vues--templates) + * [Templates littéraux](#templates-littéraux) + * [Langages de template disponibles](#langages-de-template-disponibles) + * [Templates Haml](#templates-haml) + * [Templates Erb](#templates-erb) + * [Templates Builder](#templates-builder) + * [Templates Nokogiri](#templates-nokogiri) + * [Templates Sass](#templates-sass) + * [Templates SCSS](#templates-scss) + * [Templates Less](#templates-less) + * [Templates Liquid](#templates-liquid) + * [Templates Markdown](#templates-markdown) + * [Templates Textile](#templates-textile) + * [Templates RDoc](#templates-rdoc) + * [Templates Radius](#templates-radius) + * [Templates Markaby](#templates-markaby) + * [Templates RABL](#templates-rabl) + * [Templates Slim](#templates-slim) + * [Templates Creole](#templates-creole) + * [Templates CoffeeScript](#templates-coffeescript) + * [Templates Stylus](#templates-stylus) + * [Templates Yajl](#templates-yajl) + * [Templates WLang](#templates-wlang) + * [Accéder aux variables dans un Template](#accéder-aux-variables-dans-un-template) + * [Templates avec `yield` et layouts imbriqués](#templates-avec-yield-et-layouts-imbriqués) + * [Templates dans le fichier source](#templates-dans-le-fichier-source) + * [Templates nommés](#templates-nommés) + * [Associer des extensions de fichier](#associer-des-extensions-de-fichier) + * [Ajouter son propre moteur de rendu](#ajouter-son-propre-moteur-de-rendu) + * [Filtres](#filtres) + * [Helpers](#helpers) + * [Utiliser les sessions](#utiliser-les-sessions) + * [Halt](#halt) + * [Passer](#passer) + * [Déclencher une autre route](#déclencher-une-autre-route) + * [Définir le corps, le code retour et les entêtes](#définir-le-corps-le-code-retour-et-les-entêtes) + * [Faire du streaming](#faire-du-streaming) + * [Journalisation (Logging)](#journalisation-logging) + * [Types Mime](#types-mime) + * [Former des URLs](#former-des-urls) + * [Redirection du navigateur](#redirection-du-navigateur) + * [Contrôle du cache](#contrôle-du-cache) + * [Envoyer des fichiers](#envoyer-des-fichiers) + * [Accéder à l'objet requête](#accéder-à-lobjet-requête) + * [Fichiers joints](#fichiers-joints) + * [Gérer Date et Time](#gérer-date-et-time) + * [Chercher les fichiers de templates](#chercher-les-fichiers-de-templates) + * [Configuration](#configuration) + * [Se protéger des attaques](#se-protéger-des-attaques) + * [Paramètres disponibles](#paramètres-disponibles) + * [Environements](#environements) + * [Gérer les erreurs](#gérer-les-erreurs) + * [NotFound](#notfound) + * [Error](#error) + * [Les Middlewares Rack](#les-middlewares-rack) + * [Tester](#tester) + * [Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires](#sinatrabase---les-middlewares-bibliothèques-et-applications-modulaires) + * [Style modulaire vs. style classique](#style-modulaire-vs-style-classique) + * [Servir une application modulaire](#servir-une-application-modulaire) + * [Utiliser une application de style classique avec un fichier config.ru](#utiliser-une-application-de-style-classique-avec-un-fichier-configru) + * [Quand utiliser un fichier config.ru ?](#quand-utiliser-un-fichier-configru-) + * [Utiliser Sinatra comme Middleware](#utiliser-sinatra-comme-middleware) + * [Création dynamique d'applications](#création-dynamique-dapplications) + * [Contextes et Binding](#contextes-et-binding) + * [Contexte de l'application/classe](#contexte-de-lapplicationclasse) + * [Contexte de la requête/instance](#contexte-de-la-requêteinstance) + * [Le contexte de délégation](#le-contexte-de-délégation) + * [Ligne de commande](#ligne-de-commande) + * [Configuration nécessaire](#configuration-nécessaire) + * [Essuyer les plâtres](#essuyer-les-plâtres) + * [Installer avec Bundler](#installer-avec-bundler) + * [Faire un clone local](#faire-un-clone-local) + * [Installer globalement](#installer-globalement) + * [Versions](#versions) + * [Mais encore](#mais-encore) + +## Routes + +Dans Sinatra, une route est une méthode HTTP couplée à un masque (pattern) +URL. Chaque route est associée à un bloc : + +``` ruby +get '/' do + .. montrer quelque chose .. +end + +post '/' do + .. créer quelque chose .. +end + +put '/' do + .. remplacer quelque chose .. +end + +patch '/' do + .. changer quelque chose .. +end + +delete '/' do + .. effacer quelque chose .. +end + +options '/' do + .. paramétrer quelque chose .. +end + +link '/' do + .. relier quelque chose .. +end + +unlink '/' do + .. séparer quelque chose .. +end +``` + +Les routes sont évaluées dans l'ordre où elles ont été définies. La première +route qui correspond à la requête est appelée. + +Les masques peuvent inclure des paramètres nommés, accessibles par +l'intermédiaire du hash `params` : + +``` ruby +get '/bonjour/:nom' do + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + "Bonjour #{params['nom']} !" +end +``` + +Vous pouvez aussi accéder aux paramètres nommés directement grâce aux +paramètres du bloc comme ceci : + +``` ruby +get '/bonjour/:nom' do |n| + # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" + # params['nom'] est 'foo' ou 'bar' + # n contient params['nom'] + "Bonjour #{n} !" +end +``` + +Une route peut contenir un splat (caractère joker), accessible par +l'intermédiaire du tableau `params['splat']` : + +``` ruby +get '/dire/*/a/*' do + # répond à /dire/bonjour/a/monde + params['splat'] # => ["bonjour", "monde"] +end + +get '/telecharger/*.*' do + # répond à /telecharger/chemin/vers/fichier.xml + params['splat'] # => ["chemin/vers/fichier", "xml"] +end +``` + +Ou par l'intermédiaire des paramètres du bloc : + +``` ruby +get '/telecharger/*.*' do |chemin, ext| + [chemin, ext] # => ["path/to/file", "xml"] +end +``` + +Une route peut aussi être définie par une expression régulière : + +``` ruby +get /\A\/bonjour\/([\w]+)\z/ do + "Bonjour, #{params['captures'].first} !" +end +``` + +Là encore on peut utiliser les paramètres de bloc : + +``` ruby +get %r{/bonjour/([\w]+)} do |c| + "Bonjour, #{c} !" +end +``` + +Les routes peuvent aussi comporter des paramètres optionnels : + +``` ruby +get '/posts.?:format?' do + # répond à "GET /posts" et aussi à "GET /posts.json", "GET /posts.xml" etc... +end +``` + +Ainsi que des paramètres d'URL : + +``` ruby +get '/posts' do + # répond à "GET /posts?titre=foo&auteur=bar" + titre = params['titre'] + auteur = params['auteur'] + # utilise les variables titre et auteur, ces paramètres d'URL sont optionnels pour la route /posts +end +``` + +A ce propos, à moins d'avoir désactivé la protection contre les attaques par +"path transversal" (voir plus loin), l'URL demandée peut avoir été modifiée +avant d'être comparée à vos routes. + +## Conditions + +Les routes peuvent définir toutes sortes de conditions, comme par exemple le +"user agent" : + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Vous utilisez Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Correspond à tous les autres navigateurs +end +``` + +Les autres conditions disponibles sont `host_name` et `provides` : + +``` ruby +get '/', :host_name => /^admin\./ do + "Zone Administrateur, Accès refusé !" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +Vous pouvez facilement définir vos propres conditions : + +``` ruby +set(:probability) { |value| condition { rand <= value } } + +get '/gagner_une_voiture', :probability => 0.1 do + "Vous avez gagné !" +end + +get '/gagner_une_voiture' do + "Désolé, vous avez perdu." +end +``` + +Utilisez un splat (caractère joker) dans le cas d'une condition qui prend +plusieurs valeurs : + +``` ruby +set(:auth) do |*roles| # <- ici on utilise un splat + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/mon/compte/", :auth => [:user, :admin] do + "Informations sur votre compte" +end + +get "/reserve/aux/admins/", :auth => :admin do + "Seuls les administrateurs sont acceptés ici !" +end +``` + +## Valeurs de retour + +La valeur renvoyée par le bloc correspondant à une route constitue le corps de +la réponse qui sera transmise au client HTTP ou du moins au prochain middleware +dans la pile Rack. Le plus souvent, il s'agit d'une chaîne de caractères, +comme dans les exemples précédents. Cependant, d'autres valeurs sont +acceptées. + +Vous pouvez renvoyer n'importe quel objet qu'il s'agisse d'une réponse Rack +valide, d'un corps de réponse Rack ou d'un code statut HTTP : + +* Un tableau de 3 éléments : `[code statut (Fixnum), entêtes (Hash), corps + de la réponse (répondant à #each)]` +* Un tableau de 2 élements : `[code statut (Fixnum), corps de la réponse + (répondant à #each)]` +* Un objet qui répond à `#each` et qui ne transmet que des chaînes de + caractères au bloc fourni +* Un Fixnum représentant le code statut + +Avec cela, on peut facilement implémenter un streaming par exemple : + +``` ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Vous pouvez aussi utiliser le helper `stream` (présenté un peu plus loin) pour +éviter la surcharge et intégrer le traitement relatif au streaming dans le bloc +de code de la route. + +## Masques de route spécifiques + +Comme cela a été vu auparavant, Sinatra offre la possibilité d'utiliser des +masques sous forme de chaines de caractères ou des expressions régulières +pour définir les routes. Mais il est possible de faire bien plus. Vous pouvez +facilement définir vos propres masques : + +``` ruby +class MasqueToutSauf + Masque = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Masque.new([]) + end + + def match(str) + @caputres unless @except === str + end +end + +def tout_sauf(masque) + MasqueToutSauf.new(masque) +end + +get tout_sauf("/index") do + # ... +end +``` + +Notez que l'exemple ci-dessus est bien trop compliqué et que le même résultat +peut être obtenu avec : + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou bien en utilisant la forme négative : + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## Fichiers statiques + +Les fichiers du dossier `./public` sont servis de façon statique. Vous +avez la possibilité d'utiliser un autre répertoire en définissant le paramètre +`:public_folder` : + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/statique' +``` + +Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier +`./public/css/style.css` sera appelé via l'URL : +`http://exemple.com/css/style.css`. + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête Cache-Control (voir plus loin). + +## Vues / Templates + +Chaqie langage de template est disponible via sa propre méthode de rendu, +lesquelles renvoient tout simplement une chaîne de caractères. + +``` ruby +get '/' do + erb :index +end +``` + +Ceci effectue le rendu de la vue `views/index.erb`. + +Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer +le contenu du template : + +``` ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Les méthodes de templates acceptent un second paramètre, un hash d'options : + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +Ceci effectuera le rendu de la vue `views/index.erb` en l'intégrant +au *layout* `views/post.erb` (les vues Erb sont intégrées par défaut +au *layout* `views/layout.erb` quand ce fichier existe). + +Toute option que Sinatra ne comprend pas sera passée au moteur de rendu : + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Vous pouvez également définir des options par langage de template de façon +générale : + +``` ruby +set :haml, :format => html5 + +get '/' do + haml :index +end +``` + +Les options passées à la méthode de rendu prennent le pas sur les options +définies au moyen de `set`. + +Options disponibles : + +
+
locals
+
+ Liste de variables locales passées au document. Pratique pour les vues + partielles. + Exemple : erb "<%= foo %>", :locals => {:foo => "bar"}. +
+ +
default_encoding
+
+ Encodage de caractères à utiliser en cas d'incertitude. Par défaut, c'est + settings.default_encoding. +
+ +
views
+
+ Dossier de vues dans lequel chercher les templates. Par défaut + settings.views. +
+ +
layout
+
+ S'il faut ou non utiliser un layout (true ou false). + Indique le template à utiliser lorsque c'est un symbole. Exemple : + erb :index, :layout => !request.xhr?. +
+ +
content_type
+
+ Content-Type que le template produit, dépend par défaut du langage de + template. +
+ +
scope
+
+ Contexte sous lequel effectuer le rendu du template. Par défaut il s'agit + de l'instance de l'application. Si vous changez cela, les variables + d'instance et les méthodes utilitaires ne seront pas disponibles. +
+ +
layout_engine
+
+ Moteur de rendu à utiliser pour le layout. Utile pour les langages ne + supportant pas les layouts. Il s'agit par défaut du moteur utilisé pour + le rendu du template. Exemple : set :rdoc, :layout_engine => :erb +
+ +
layout_options
+
+ Options spécifiques destinées au moteur de rendu. Exemple : set :rdoc, + :layout_options => { :views => 'views/layouts' } +
+
+ +Les templates sont supposés se trouver directement dans le dossier +`./views`. Pour utiliser un dossier de vues différent : + +``` ruby +set :views, settings.root + '/templates' +``` + +Il est important de se souvenir que les templates sont toujours référencés +sous forme de symboles, même lorsqu'ils sont dans un sous-répertoire (dans +ce cas, utilisez `:'sous_repertoire/template'`). Il faut utiliser +un symbole car les méthodes de rendu évaluent le contenu des chaînes de +caractères au lieu de les considérer comme un chemin vers un fichier. + +### Templates littéraux + +``` ruby +get '/' do + haml '%div.title Bonjour le monde' +end +``` + +Générera le code du template spécifié dans la chaîne de caractères. + +### Langages de template disponibles + +Certains langages ont plusieurs implémentations. Pour préciser l'implémentation +à utiliser (et garantir l'aspect thread-safe), vous devez simplement l'avoir +chargée au préalable : + +``` ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Templates Haml + + + + + + + + + + + + + + +
Dépendanceshaml
Extensions de fichier.haml
Exemplehaml :index, :format => :html5
+ +#### Templates Erb + + + + + + + + + + + + + + +
Dépendances + erubis + ou erb (inclus avec Ruby) +
Extensions de fichier.erb, .rhtml ou .erubis (Erubis seulement)
Exempleerb :index
+ +#### Templates Builder + + + + + + + + + + + + + + +
Dépendances + builder +
Extensions de fichier.builder
Exemplebuilder { |xml| xml.em "salut" }
+ +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Nokogiri + + + + + + + + + + + + + + +
Dépendancesnokogiri
Extensions de fichier.nokogiri
Exemplenokogiri { |xml| xml.em "salut" } +
+ +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates Sass + + + + + + + + + + + + + + +
Dépendancessass
Extensions de fichier.sass
Exemplesass :stylesheet, :style => :expanded
+ +#### Templates SCSS + + + + + + + + + + + + + + +
Dépendancessass
Extensions de fichier.scss
Exemplescss :stylesheet, :style => :expanded

+
+ +#### Templates Less + + + + + + + + + + + + + + +
Dépendancesless
Extensions de fichier.less
Exempleless :stylesheet +
+ +#### Templates Liquid + + + + + + + + + + + + + + +
Dépendancesliquid
Extensions de fichier.liquid
Exempleliquid :index, :locals => { :key => 'value' }
+ +Comme vous ne pouvez appeler de méthodes Ruby (autres que `yield`) +dans un template Liquid, vous aurez sûrement à lui passer des variables +locales. + +#### Templates Markdown + + + + + + + + + + + + + + + +

Dépendances

+ Au choix : + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
Extensions de fichier.markdown, .mkd et .md
Exemplemarkdown :index, :layout_engine => :erb
+ +Il n’est pas possible d’appeler des méthodes depuis markdown, ni de +lui passer des variables locales. Par conséquent, il sera souvent utilisé +en combinaison avec un autre moteur de rendu : + +``` ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `markdown` au +sein d’autres templates : + +``` ruby +%h1 Hello From Haml ! +%p= markdown(:greetings) +``` + +Comme vous ne pouvez pas appeler de Ruby au sein de Markdown, vous ne +pouvez pas utiliser de layouts écrits en Markdown. Toutefois, il +est possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Textile + + + + + + + + + + + + + + +
DépendancesRedCloth
Extensions de fichier.textile
Exempletextile :index, :layout_engine => :erb
+ +Il n’est pas possible d’appeler des méthodes depuis textile, ni de lui +passer des variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +``` ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `textile` au +sein d’autres templates : + +``` ruby +%h1 Hello From Haml ! +%p= textile(:greetings) +``` + +Comme vous ne pouvez pas appeler de Ruby au sein de Textile, vous ne pouvez +pas utiliser de layouts écrits en Textile. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates RDoc + + + + + + + + + + + + + + +
DépendancesRDoc
Extensions de fichier.rdoc
Exemplerdoc :README, :layout_engine => :erb
+ +Il n’est pas possible d’appeler des méthodes depuis rdoc, ni de lui +passer des variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +``` ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `rdoc` au sein +d’autres templates : + +``` ruby +%h1 Hello From Haml ! +%p= rdoc(:greetings) +``` + +Comme vous ne pouvez pas appeler de Ruby au sein de RDoc, vous ne pouvez +pas utiliser de layouts écrits en RDoc. Toutefois, il est +possible d’utiliser un moteur de rendu différent pour le template et +pour le layout en utilisant l’option `:layout_engine`. + +#### Templates Radius + + + + + + + + + + + + + +
DépendancesRadius
Extensions de fichier.radius
Exempleradius :index, :locals => { :key => 'value' }
+ +Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template +Radius, vous aurez sûrement à lui passer des variables locales. + +#### Templates Markaby + + + + + + + + + + + + + + +
DépendancesMarkaby
Extensions de fichier.mab
Exemplemarkaby { h1 "Bienvenue !" }
+ +Ce moteur accepte également un bloc pour des templates en ligne (voir +exemple). + +#### Templates RABL + + + + + + + + + + + + + + +
DépendancesRabl
Extensions de fichier.rabl
Exemplerabl :index
+ +#### Templates Slim + + + + + + + + + + + + + + +
DépendancesSlim Lang
Extensions de fichier.slim
Exempleslim :index
+ +#### Templates Creole + + + + + + + + + + + + + + +
DépendancesCreole
Extensions de fichier.creole
Exemplecreole :wiki, :layout_engine => :erb
+ +Il n'est pas possible d'appeler des méthodes depuis creole, ni de lui +passer des variables locales. Par conséquent, il sera souvent utilisé en +combinaison avec un autre moteur de rendu : + +``` ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Notez que vous pouvez également appeler la méthode `creole` au sein d'autres +templates : + +``` ruby +%h1 Hello From Haml ! +%p= creole(:greetings) +``` + +Comme vous ne pouvez pas appeler de Ruby au sein de Creole, vous ne pouvez +pas utiliser de layouts écrits en Creole. Toutefois, il est possible +d'utiliser un moteur de rendu différent pour le template et pour le layout +en utilisant l'option `:layout_engine`. + +#### Templates CoffeeScript + + + + + + + + + + + + + + +
Dépendances + + CoffeeScript + + et un + + moyen d'exécuter javascript + +
Extensions de fichier.coffee
Exemplecoffee :index
+ +#### Templates Stylus + + + + + + + + + + + + + + +
Dépendances + + Stylus + + et un + + moyen d'exécuter javascript + +
Extensions de fichier.styl
Exemplestylus :index
+ +Avant de pouvoir utiliser des templates Stylus, vous devez auparavant charger +`stylus` et `stylus/tilt` : + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemple +end +``` + +#### Templates Yajl + + + + + + + + + + + + + + +
Dépendances + yajl-ruby +
Extensions de fichier.yajl
Exempleyajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'

+
+ +Le source du template est évalué en tant que chaine Ruby, puis la +variable json obtenue est convertie avec #to_json. + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Les options `:callback` et `:variable` peuvent être utilisées pour décorer +l’objet retourné. + +``` ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +#### Templates WLang + + + + + + + + + + + + + + +
Dépendanceswlang
Extensions de fichier.wlang
Exemplewlang :index, :locals => { :key => 'value' }
+ +L’appel de code ruby au sein des templates n’est pas idiomatique en wlang. +L’écriture de templates sans logique est encouragé, via le passage de variables +locales. Il est néanmoins possible d’écrire un layout en wlang et d’y utiliser +`yield`. + +### Accéder aux variables dans un Template + +Un template est évalué dans le même contexte que l'endroit d'où il a été +appelé (gestionnaire de route). Les variables d'instance déclarées dans le +gestionnaire de route sont directement accessibles dans le template : + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nom' +end +``` + +Alternativement, on peut passer un hash contenant des variables locales : + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nom', :locals => { :foo => foo } +end +``` + +Ceci est généralement utilisé lorsque l'on veut utiliser un template comme +partiel (depuis un autre template) et qu'il est donc nécessaire d'adapter les +noms de variables. + + +### Templates avec `yield` et layouts imbriqués + +En général, un layout est un simple template qui appelle `yield`. Ce genre de +template peut s'utiliser via l'option `:template` comme décrit précédemment ou +peut être rendu depuis un bloc : + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +Ce code est à globalement équivalent à `erb :index, :layout => :post`. + +Le fait de passer des blocs aux méthodes de rendu est particulièrement utile +pour gérer des templates imbriqués : + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Ce qui peut aussi être fait avec un peu moins de code : + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Actuellement, les méthodes de rendu qui acceptent un bloc sont : `erb`, `haml`, +`liquid`, `slim ` et `wlang`. La méthode générale `render` accepte elle aussi +un bloc. + + +### Templates dans le fichier source + +Des templates peuvent être définis dans le fichier source comme ceci : + +``` ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Bonjour le monde ! +``` + +NOTE : Les templates du fichier source qui contient `require 'sinatra'` +sont automatiquement chargés. Si vous avez des templates dans d'autres +fichiers source, il faut explicitement les déclarer avec +`enable :inline_templates`. + + +### Templates nommés + +Les templates peuvent aussi être définis grâce à la méthode de haut niveau `template` : + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Bonjour le monde !' +end + +get '/' do + haml :index +end +``` + +Si un template nommé "layout" existe, il sera utilisé à chaque fois qu'un +template sera affiché. Vous pouvez désactivez les layouts au cas par cas en +passant `:layout => false` ou bien les désactiver par défaut au moyen +de `set :haml, :layout => false` : + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associer des extensions de fichier + +Pour associer une extension de fichier avec un moteur de rendu, utilisez +`Tilt.register`. Par exemple, si vous désirez utiliser l'extension +de fichier `tt` pour les templates Textile, vous pouvez faire comme suit : + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Ajouter son propre moteur de rendu + +En premier lieu, déclarez votre moteur de rendu avec Tilt, ensuite créez +votre méthode de rendu : + +``` ruby +Tilt.register :monmoteur, MonMerveilleurMoteurDeRendu + +helpers do + def monmoteur(*args) render(:monmoteur, *args) end +end + +get '/' do + monmoteur :index +end +``` + +Utilisera `./views/index.monmoteur`. Voir [le dépôt Github](https://github.com/rtomayko/tilt) pour en savoir plus sur Tilt. + +## Filtres + +Les filtres before sont exécutés avant chaque requête, dans le même contexte +que les routes, et permettent de modifier la requête et sa réponse. Les +variables d'instance déclarées dans les filtres sont accessibles au niveau +des routes et des templates : + +``` ruby +before do + @note = 'Coucou !' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Coucou !' + params['splat'] #=> 'bar/baz' +end +``` + +Les filtres after sont exécutés après chaque requête à l'intérieur du même +contexte et permettent de modifier la requête et sa réponse. Les variables +d'instance déclarées dans les filtres before ou les routes sont accessibles +au niveau des filtres after : + +``` ruby +after do + puts response.status +end +``` + +Note : Le corps de la réponse n'est pas disponible au niveau du filtre after +car il ne sera généré que plus tard (sauf dans le cas où vous utilisez la +méthode `body` au lieu de simplement renvoyer une chaine depuis vos routes). + +Les filtres peuvent être associés à un masque, ce qui permet de limiter leur +exécution aux cas où la requête correspond à ce masque : + +``` ruby +before '/secret/*' do + authentification! +end + +after '/faire/:travail' do |travail| + session['dernier_travail'] = travail +end +``` + +Tout comme les routes, les filtres acceptent également des conditions : + +``` ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Utilisez la méthode de haut niveau `helpers` pour définir des routines +qui seront accessibles dans vos gestionnaires de route et dans vos templates : + +``` ruby +helpers do + def bar(nom) + "#{nom}bar" + end +end + +get '/:nom' do + bar(params['nom']) +end +``` + +Vous pouvez aussi définir les méthodes helper dans un module séparé : + +``` ruby +module FooUtils + def foo(nom) "#{nom}foo" end +end + +module BarUtils + def bar(nom) "#{nom}bar" end +end + +helpers FooUtils, BarUtils +``` + +Cela a le même résultat que d'inclure les modules dans la classe de +l'application. + +### Utiliser les sessions + +Une session est utilisée pour conserver un état entre les requêtes. Une fois +activées, vous avez un hash de session par session utilisateur : + +``` ruby +enable :sessions + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:value' do + session['valeur'] = params['valeur'] +end +``` + +Notez que `enable :sessions` enregistre en fait toutes les données dans +un cookie. Ce n'est pas toujours ce que vous voulez (enregistrer beaucoup de +données va augmenter le traffic par exemple). Vous pouvez utiliser n'importe +quel middleware Rack de session afin d'éviter cela. N'utilisez **pas** +`enable :sessions` dans ce cas mais chargez le middleware de votre +choix comme vous le feriez pour n'importe quel autre middleware : + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "valeur = " << session['valeur'].inspect +end + +get '/:value' do + session['valeur'] = params['valeur'] +end +``` + +Pour renforcer la sécurité, les données de session dans le cookie sont signées +avec une clé secrète de session. Une clé secrète est générée pour vous au +hasard par Sinatra. Toutefois, comme cette clé change à chaque démarrage de +votre application, vous pouvez définir cette clé vous-même afin que toutes +les instances de votre application la partage : + +``` ruby +set :session_secret, 'super secret' +``` + +Si vous souhaitez avoir plus de contrôle, vous pouvez également enregistrer un +hash avec des options lors de la configuration de `sessions` : + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +Pour que les différents sous-domaines de foo.com puisse partager une session, +vous devez préfixer le domaine par *.* : + +``` ruby +set :sessions, :domain => '.foo.com' +``` + + +### Halt + +Pour arrêter immédiatement la requête dans un filtre ou un gestionnaire de +route : + +``` ruby +halt +``` + +Vous pouvez aussi passer le code retour ... + +``` ruby +halt 410 +``` + +Ou le texte ... + +``` ruby +halt 'Ceci est le texte' +``` + +Ou les deux ... + +``` ruby +halt 401, 'Partez !' +``` + +Ainsi que les entêtes ... + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +Bien sûr il est possible de combiner un template avec `halt` : + +``` ruby +halt erb(:erreur) +``` + +### Passer + +Une route peut passer le relais aux autres routes qui correspondent également +avec `pass` : + +``` ruby +get '/devine/:qui' do + pass unless params['qui'] == 'Frank' + "Tu m'as eu !" +end + +get '/devine/*' do + 'Manqué !' +end +``` + +On sort donc immédiatement de ce gestionnaire et on continue à chercher, +dans les masques suivants, le prochain qui correspond à la requête. +Si aucun des masques suivants ne correspond, un code 404 est retourné. + +### Déclencher une autre route + +Parfois, `pass` n'est pas ce que vous recherchez, au lieu de cela vous +souhaitez obtenir le résultat d'une autre route. Pour cela, utilisez +simplement `call` : + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Notez que dans l'exemple ci-dessus, vous faciliterez les tests et améliorerez +la performance en déplaçant simplement `"bar"` dans un helper +utilisé à la fois par `/foo` et `/bar`. + +Si vous souhiatez que la requête soit envoyée à la même instance de +l'application plutôt qu'à une copie, utilisez `call!` au lieu de +`call`. + +Lisez la spécification Rack si vous souhaitez en savoir plus sur +`call`. + +### Définir le corps, le code retour et les entêtes + +Il est possible et recommandé de définir le code retour et le corps de la +réponse au moyen de la valeur de retour d'un bloc définissant une route. +Quoiqu'il en soit, dans certains cas vous pourriez avoir besoin de définir +le coprs de la réponse à un moment arbitraire de l'exécution. Vous pouvez le +faire au moyen de la méthode `body`. Si vous faites ainsi, vous pouvez alors +utiliser cette même méthode pour accéder au corps de la réponse : + +``` ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Il est également possible de passer un bloc à `body`, qui sera exécuté par le +gestionnaire Rack (ceci peut être utilisé pour implémenter un streaming, +voir "Valeurs de retour"). + +Pareillement au corps de la réponse, vous pouvez également définir le code +retour et les entêtes : + +``` ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "Je suis une théière !" +end +``` + +Comme `body` `headers` et `status` peuvent être utilisés sans arguments +pour accéder à leurs valeurs. + +### Faire du streaming + +Il y a des cas où vous voulez commencer à renvoyer des données pendant que +vous êtes en train de générer le reste de la réponse. Dans les cas les plus +extrèmes, vous souhaitez continuer à envoyer des données tant que le client +n'abandonne pas la connection. Vous pouvez alors utiliser le helper `stream` +pour éviter de créer votre propre système : + +``` ruby +get '/' do + stream do |out| + out << "Ca va être hallu -\n" + sleep 0.5 + out << " (attends la suite) \n" + sleep 1 + out << "- cinant !\n" + end +end +``` + +Cela permet d'implémenter des API de streaming ou de +[Server Sent Events](http://dev.w3.org/html5/eventsource/) et peut servir de +base pour des [WebSockets](http://en.wikipedia.org/wiki/WebSocket). Vous +pouvez aussi l'employer pour augmenter le débit quand une partie du contenu +provient d'une resource lente. + +Le fonctionnement du streaming, notamment le nombre de requêtes simultanées, +dépend énormément du serveur web utilisé. Certains ne prennent pas du tout en +charge le streaming (WEBRick par exemple). Lorsque le serveur ne gère pas le +streaming, la partie body de la réponse sera envoyée au client en une seule +fois, après que l'exécution du bloc passé au helper `stream` sera terminée. Le +streaming ne fonctionne pas du tout avec Shotgun. + +En utilisant le helper stream avec le paramètre `keep_open`, il n'appelera +pas la méthode `close` du flux, vous laissant la possibilité de le fermer à +tout moment au cours de l'exécution. Ceci ne fonctionne qu'avec les serveurs +evented (ie non threadés) tels que Thin et Rainbows. Les autres serveurs +fermeront malgré tout le flux : + +``` ruby +# interrogation prolongée + +set :server, :thin +connexions = [] + +get '/souscrire' do + # abonne un client aux évènements du serveur + stream(:keep_open) do |out| + connexions << out + # purge les connexions abandonnées + connexions.reject!(&:closed?) + end +end + +post '/message' do + connexions.each do |out| + # prévient le client qu'un nouveau message est arrivé + out << params['message'] << "\n" + + # indique au client de se connecter à nouveau + out.close + end + + # compte-rendu + "message reçu" +end +``` + +### Journalisation (Logging) + +Dans le contexte de la requête, la méthode utilitaire `logger` expose une +instance de `Logger` : + +``` ruby +get '/' do + logger.info "chargement des données" + # ... +end +``` + +Ce logger va automatiquement prendre en compte les paramètres de +configuration pour la journalisation de votre gestionnaire Rack. Si la +journalisation est désactivée, cette méthode renverra un objet factice et +vous n'avez pas à vous en inquiéter dans vos routes en le filtrant. + +Notez que la journalisation est seulement activée par défaut pour +`Sinatra::Application`, donc si vous héritez de `>Sinatra::Base`, +vous aurez à l'activer vous-même : + +``` ruby +class MonApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Si vous souhaitez utiliser votre propre logger, vous devez définir le paramètre +`logging` à `nil` pour être certain qu'aucun middleware de logging ne sera +installé (notez toutefois que `logger` renverra alors `nil`). Dans ce cas, +Sinatra utilisera ce qui sera présent dans `env['rack.logger']`. + +### Types Mime + +Quand vous utilisez `send_file` ou des fichiers statiques, vous +pouvez rencontrer des types mime que Sinatra ne connaît pas. Utilisez +`mime_type` pour les déclarer par extension de fichier : + +``` ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Vous pouvez également les utiliser avec la méthode `content_type` : + +``` ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Former des URLs + +Pour former des URLs, vous devriez utiliser la méthode `url`, par exemple en +Haml : + +``` ruby +%a{:href => url('/foo')} foo +``` + +Cela prend en compte les proxy inverse et les routeurs Rack, s'ils existent. + +Cette méthode est également disponible sous l'alias `to` (voir ci-dessous +pour un exemple). + +### Redirection du navigateur + +Vous pouvez déclencher une redirection du navigateur avec la méthode +`redirect` : + +``` ruby +get '/foo' do + redirect to('/bar') +end +``` + +Tout paramètre additionnel est géré comme des arguments pour la méthode +`halt` : + +``` ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'mauvais endroit mon pote' +``` + +Vous pouvez aussi rediriger vers la page dont l'utilisateur venait au moyen de +`redirect back` : + +``` ruby +get '/foo' do + "faire quelque chose" +end + +get '/bar' do + faire_quelque_chose + redirect back +end +``` + +Pour passer des arguments à une redirection, ajoutez-les soit à la requête : + +``` ruby +redirect to('/bar?sum=42') +``` + +Ou bien utilisez une session : + +``` ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### Contrôle du cache + +Définir correctement vos entêtes à la base pour un bon cache HTTP. + +Vous pouvez facilement définir l'entête Cache-Control de la manière suivante : + +``` ruby +get '/' do + cache_control :public + "met le en cache !" +end +``` + +Conseil de pro : définir le cache dans un filtre before : + +``` ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Si vous utilisez la méthode `expires` pour définir l'entête correspondant, +`Cache-Control` sera alors défini automatiquement : + +``` ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Pour utiliser correctement les caches, vous devriez utiliser `etag` ou +`last_modified`. Il est recommandé d'utiliser ces méthodes *avant* de faire +d'importantes modifications, car elles vont immédiatement déclencher la réponse +si le client a déjà la version courante dans son cache : + +``` ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Il est également possible d'utiliser un +[weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) : + +``` ruby +etag @article.sha1, :weak +``` + +Ces méthodes ne sont pas chargées de mettre des données en cache, mais elles +fournissent les informations nécessaires pour votre cache. Si vous êtes à la +recherche de solutions rapides pour un reverse-proxy de cache, essayez +[rack-cache](https://github.com/rtomayko/rack-cache) : + +``` ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Utilisez le paramètre `:static_cache_control` pour ajouter l'information +d'en-tête `Cache-Control` (voir plus loin). + +D'après la RFC 2616, votre application devrait se comporter différement lorsque +l'en-tête If-Match ou If-None-Match est défini à `*` en tenant compte du +fait que la resource demandée existe déjà ou pas. Sinatra considère que les +requêtes portant sur des resources sûres (tel que get) ou idempotentes (tel que +put) existent déjà et pour les autres resources (par exemple dans le cas +de requêtes post) qu'il s'agit de nouvelles resources. Vous pouvez modifier ce +comportement en passant une option `:new_resource` : + +``` ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Si vous souhaitez utilisez un ETag faible, utilisez l'option :kind : + +``` ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Envoyer des fichiers + +Pour envoyer des fichiers, vous pouvez utiliser la méthode `send_file` : + +``` ruby +get '/' do + send_file 'foo.png' +end +``` + +Quelques options sont également acceptées : + +``` ruby +send_file 'foo.png', :type => :jpg +``` + +Les options sont : + +
+
filename
+
+ le nom du fichier dans la réponse, par défaut le nom du fichier envoyé. +
+ +
last_modified
+
+ valeur pour l’entête Last-Modified, par défaut la date de modification du + fichier +
+ +
type
+
+ type de contenu à utiliser, deviné à partir de l’extension de fichier si + absent +
+ +
disposition
+
+ utilisé pour Content-Disposition, les valuers possibles étant : nil + (par défaut), :attachment et :inline +
+ +
length
+
entête Content-Length, par défaut la taille du fichier
+ +
status
+
+ code état à renvoyer. Utile quand un fichier statique sert de page d’erreur. +
+
+ +Si le gestionnaire Rack le supporte, d'autres moyens que le streaming via le +processus Ruby seront utilisés. Si vous utilisez cette méthode, Sinatra gérera +automatiquement les requêtes de type range. + +### Accéder à l'objet requête + +L'objet correspondant à la requête envoyée peut être récupéré dans le contexte +de la requête (filtres, routes, gestionnaires d'erreur) au moyen de la méthode +`request` : + +``` ruby +# application tournant à l'adresse http://exemple.com/exemple +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # corps de la requête envoyée par le client + # (voir ci-dessous) + request.scheme # "http" + request.script_name # "/exemple" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # taille de request.body + request.media_type # type de média pour request.body + request.host # "exemple.com" + request.get? # true (méthodes similaires pour les autres + # verbes HTTP) + request.form_data? # false + request["UN_ENTETE"] # valeur de l'entête UN_ENTETE + request.referrer # référant du client ou '/' + request.user_agent # user agent (utilisé par la condition :agent) + request.cookies # tableau contenant les cookies du navigateur + request.xhr? # requête AJAX ? + request.url # "http://exemple.com/exemple/foo" + request.path # "/exemple/foo" + request.ip # adresse IP du client + request.secure? # false + request.forwarded? # vrai (si on est derrière un proxy inverse) + request.env # tableau brut de l'environnement fourni par Rack +end +``` + +Certaines options, telles que `script_name` ou `path_info` +peuvent également être modifiées : + +``` ruby +before { request.path_info = "/" } + +get "/" do + "toutes les requêtes arrivent ici" +end +``` + +`request.body` est un objet IO ou StringIO : + +``` ruby +post "/api" do + request.body.rewind # au cas où il a déjà été lu + donnees = JSON.parse request.body.read + "Bonjour #{donnees['nom']} !" +end +``` + +### Fichiers joints + +Vous pouvez utiliser la méthode `attachment` pour indiquer au navigateur que +la réponse devrait être stockée sur le disque plutôt qu'affichée : + + +``` ruby +get '/' do + attachment + "enregistre-le !" +end +``` + +Vous pouvez également lui passer un nom de fichier : + +``` ruby +get '/' do + attachment "info.txt" + "enregistre-le !" +end +``` + +### Gérer Date et Time + +Sinatra fourni un helper `time_for` pour convertir une valeur donnée en +objet `Time`. Il peut aussi faire la conversion à partir d'objets `DateTime`, +`Date` ou de classes similaires : + +``` ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "encore temps" +end +``` + +Cette méthode est utilisée en interne par `expires`, `last_modified` et +consorts. Par conséquent, vous pouvez très facilement étendre le +fonctionnement de ces méthodes en surchargeant le helper `time_for` dans +votre application : + +``` ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "salut" +end +``` + +### Chercher les fichiers de templates + +La méthode `find_template` est utilisée pour trouver les fichiers de +templates à générer : + +``` ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "pourrait être #{file}" +end +``` + +Ce n'est pas très utilise. En revanche, il est utile de pouvoir surcharger +cette méthode afin de définir son propre mécanisme de recherche. Par exemple, +vous pouvez utiliser plus d'un répertoire de vues : + +``` ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Un autre exemple est d'utiliser des répertoires différents pour des moteurs +de rendu différents : + +``` ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Vous pouvez également écrire cela dans une extension et la partager avec +d'autres ! + +Notez que `find_template` ne vérifie pas que le fichier existe mais +va plutôt exécuter le bloc pour tous les chemins possibles. Cela n'induit pas +un problème de performance dans le sens où `render` va utiliser `break` dès +qu'un fichier est trouvé. De plus, l'emplacement des templates (et leur +contenu) est mis en cache si vous n'êtes pas en mode développement. Vous +devriez garder cela en tête si vous écrivez une méthode vraiment dingue. + +## Configuration + +Lancé une seule fois au démarrage de tous les environnements : + +``` ruby +configure do + # définir un paramètre + set :option, 'value' + + # définir plusieurs paramètre + set :a => 1, :b => 2 + + # identique à "set :option, true" + enable :option + + # identique à "set :option, false"" + disable :option + + # vous pouvez également avoir des paramètres dynamiques avec des blocs + set(:css_dir) { File.join(views, 'css') } +end +``` + +Lancé si l'environnement (variable d'environnement RACK_ENV) est défini comme +`:production` : + +``` ruby + configure :production do + ... + end +``` + +Lancé si l'environnement est `:production` ou `:test` : + +``` ruby + configure :production, :test do + ... + end +``` + +Vous pouvez accéder à ces paramètres via `settings` : + +``` ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Se protéger des attaques + +Sinatra utilise [Rack::Protection](https://github.com/rkh/rack-protection#readme) +pour protéger votre application contre les principales attaques opportunistes. +Vous pouvez très simplement désactiver cette fonctionnalité (ce qui exposera +votre application à beaucoup de vulnerabilités courantes) : + +``` ruby +disable :protection +``` + +Pour désactiver seulement un type de protection, vous pouvez définir `protection` +avec un hash d'options : + +``` ruby +set :protection, :except => :path_traversal +``` + +Vous pouvez également lui passer un tableau pour désactiver plusieurs types de +protection : + +``` ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +Par défaut, il faut que `:sessions` soit activé pour que Sinatra mette en place +un système de protection au niveau de la session. Dans le cas où vous gérez +vous même les sessions, vous devez utiliser l'option `:session` pour que cela +soit le cas : + +``` ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### Paramètres disponibles + +
+
absolute_redirects
+
Si désactivé, Sinatra permettra les redirections relatives. Toutefois, + Sinatra ne sera plus conforme à la RFC 2616 (HTTP 1.1), qui n’autorise + que les redirections absolues.

+ + Activez si votre application tourne derrière un proxy inverse qui n’a + pas été correctement configuré. Notez que la méthode url + continuera de produire des URLs absolues, sauf si vous lui passez + false comme second argument.

+ +

Désactivé par défaut.

+ +
add_charset
+

types mime pour lesquels la méthode content_type va + automatiquement ajouter l’information du charset.

+ +

Vous devriez lui ajouter des valeurs plutôt que de l’écraser :

+ +
settings.add_charset >> "application/foobar"
+ +
app_file
+

chemin pour le fichier de l’application principale, utilisé pour + détecter la racine du projet, les dossiers public et vues, et les + templates en ligne.

+ +
bind
+
adresse IP sur laquelle se brancher (par défaut : 0.0.0.0). Utiliser + seulement pour le serveur intégré.
+ +
default_encoding
+
encodage à utiliser si inconnu (par défaut "utf-8")
+ +
dump_errors
+
afficher les erreurs dans le log. +
+ +
environment
+
environnement courant, par défaut ENV['RACK_ENV'], ou + "development" si absent.
+ +
logging
+
utiliser le logger.
+ +
lock
+

Place un lock autour de chaque requête, n’exécutant donc + qu’une seule requête par processus Ruby.

+ +

Activé si votre application n’est pas thread-safe. Désactivé + par défaut.

+ +
method_override
+
utilise la magie de _method afin de permettre des formulaires + put/delete dans des navigateurs qui ne le permettent pas. + +
+
port
+
port à écouter. Utiliser seulement pour le serveur intégré.
+ +
prefixed_redirects
+
si oui ou non request.script_name doit être inséré dans les + redirections si un chemin non absolu est utilisé. Ainsi, redirect + '/foo' se comportera comme redirect to('/foo'). Désactivé + par défaut.
+ +
protection
+
défini s’il faut activer ou non la protection contre les attaques web. + Voir la section protection précédente.
+ +
public_dir
+
alias pour public_folder. Voir ci-dessous.
+ +
public_folder
+
chemin pour le dossier à partir duquel les fichiers publics sont servis. + Utilisé seulement si les fichiers statiques doivent être servis (voir le + paramètre static). Si non défini, il découle du paramètre + app_file.
+ +
reload_templates
+
si oui ou non les templates doivent être rechargés entre les requêtes. + Activé en mode développement.
+ +
root
+
chemin pour le dossier racine du projet. Si non défini, il découle du + paramètre app_file.
+ +
raise_errors
+
soulever les erreurs (ce qui arrêtera l’application). Désactivé par + défaut sauf lorsque environment est défini à + "test".
+ +
run
+
si activé, Sinatra s’occupera de démarrer le serveur, ne pas activer si + vous utiliser rackup ou autres.
+ +
running
+
est-ce que le serveur intégré est en marche ? ne changez pas ce + paramètre !
+ +
server
+
serveur ou liste de serveurs à utiliser pour le serveur intégré. Par + défaut [‘thin’, ‘mongrel’, ‘webrick’], l’ordre indiquant la + priorité.
+ +
sessions
+
active le support des sessions basées sur les cookies, en utilisant + Rack::Session::Cookie. Reportez-vous à la section ‘Utiliser les + sessions’ pour plus d’informations.
+ +
show_exceptions
+
affiche la trace de l’erreur dans le navigateur lorsqu’une exception se + produit. Désactivé par défaut sauf lorsque environment est + défini à "development".
+ +
static
+
Si oui ou non Sinatra doit s’occuper de servir les fichiers statiques. + Désactivez si vous utilisez un serveur capable de le gérer lui même. Le + désactiver augmentera la performance. Activé par défaut pour le style + classique, désactivé pour le style modulaire.
+ +
static_cache_control
+
A définir quand Sinatra rend des fichiers statiques pour ajouter les + en-têtes Cache-Control. Utilise le helper cache_control. + Désactivé par défaut. Utiliser un array explicite pour définir des + plusieurs valeurs : set :static_cache_control, [:public, :max_age => + 300]
+ +
threaded
+
à définir à true pour indiquer à Thin d’utiliser + EventMachine.defer pour traiter la requête.
+ +
views
+
chemin pour le dossier des vues. Si non défini, il découle du paramètre + app_file.
+ +
x_cascade
+
+ Indique s'il faut ou non définir le header X-Cascade lorsqu'aucune route + ne correspond. Défini à true par défaut. +
+
+ +## Environements + +Il existe trois environnements prédéfinis : `"development"`, +`"production"` et `"test"`. Les environements peuvent être +sélectionné via la variable d'environnement `RACK_ENV`. Sa valeur par défaut +est `"development"`. Dans ce mode, tous les templates sont rechargés à +chaque requête. Des handlers spécifiques pour `not_found` et +`error` sont installés pour vous permettre d'avoir une pile de trace +dans votre navigateur. En mode `"production"` et `"test"` les +templates sont mis en cache par défaut. + +Pour exécuter votre application dans un environnement différent, définissez la +variable d'environnement `RACK_ENV` : + +``` shell +RACK_ENV=production ruby my_app.rb +``` + +Vous pouvez utiliser une des méthodes `development?`, `test?` et `production?` +pour déterminer quel est l'environnement en cours : + +``` ruby +get '/' do + if settings.development? + "développement !" + else + "pas en développement !" + end +end +``` + +## Gérer les erreurs + +Les gestionnaires d'erreur s'exécutent dans le même contexte que les routes ou +les filtres, ce qui veut dire que vous avez accès (entre autres) aux bons +vieux `haml`, `erb`, `halt`, etc. + +### NotFound + +Quand une exception Sinatra::NotFound est soulevée, ou que le code +retour est 404, le gestionnaire not_found est invoqué : + +``` ruby +not_found do + 'Pas moyen de trouver ce que vous cherchez' +end +``` + +### Error + +Le gestionnaire `error` est invoqué à chaque fois qu'une exception est +soulevée dans une route ou un filtre. L'objet exception est accessible via la +variable Rack `sinatra.error` : + +``` ruby +error do + 'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].message +end +``` + +Erreur sur mesure : + +``` ruby +error MonErreurSurMesure do + 'Donc il est arrivé ceci...' + env['sinatra.error'].message +end +``` + +Donc si ceci arrive : + +``` ruby +get '/' do + raise MonErreurSurMesure, 'quelque chose de mal' +end +``` + +Vous obtenez ça : + + Donc il est arrivé ceci... quelque chose de mal + +Alternativement, vous pouvez avoir un gestionnaire d'erreur associé à un code +particulier : + +``` ruby +error 403 do + 'Accès interdit' +end + +get '/secret' do + 403 +end +``` + +Ou un intervalle : + +``` ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installe pour vous quelques gestionnaires `not_found` et +`error` génériques lorsque vous êtes en environnement +`development`. + +## Les Middlewares Rack + +Sinatra tourne avec [Rack](http://rack.github.io/), une interface standard +et minimale pour les web frameworks Ruby. Un des points forts de Rack est le +support de ce que l'on appelle des "middlewares" -- composant qui vient se +situer entre le serveur et votre application, et dont le but est de +visualiser/manipuler la requête/réponse HTTP, et d'offrir diverses +fonctionnalités classiques. + +Sinatra permet de construire facilement des middlewares Rack via la méthode de +haut niveau `use` : + +``` ruby +require 'sinatra' +require 'mon_middleware_perso' + +use Rack::Lint +use MonMiddlewarePerso + +get '/bonjour' do + 'Bonjour le monde' +end +``` + +La sémantique de `use` est identique à celle définie dans le DSL de +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) +(le plus souvent utilisé dans un fichier rackup). Par exemple, la méthode +`use` accepte divers arguments ainsi que des blocs : + +``` ruby +use Rack::Auth::Basic do |login, password| + login == 'admin' && password == 'secret' +end +``` + +Rack est distribué avec de nombreux middlewares standards pour loguer, débuguer, +faire du routage URL, de l'authentification ou gérer des sessions. Sinatra gère +plusieurs de ces composants automatiquement via son système de configuration, ce +qui vous dispense de faire un `use` en ce qui les concerne. + +Vous trouverez d'autres middlewares intéressants sur +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readm), +ou en consultant le [wiki de Rack](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Tester + +Les tests pour Sinatra peuvent être écrit avec n'importe quelle bibliothèque +basée sur Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) est +recommandé : + +``` ruby +require 'mon_application_sinatra' +require 'minitest/autorun' +require 'rack/test' + +class MonTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_ma_racine + get '/' + assert_equal 'Bonjour le monde !', last_response.body + end + + def test_avec_des_parametres + get '/rencontrer', :name => 'Frank' + assert_equal 'Salut Frank !', last_response.body + end + + def test_avec_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Vous utilisez Songbird !", last_response.body + end +end +``` + +## Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires + +Définir votre application au niveau supérieur fonctionne bien dans le cas des +micro-applications mais présente pas mal d'inconvénients pour créer des +composants réutilisables sous forme de middlewares Rack, de Rails metal, de +simples librairies avec un composant serveur ou même d'extensions Sinatra. Le +niveau supérieur suppose une configuration dans le style des micro-applications +(une application d'un seul fichier, des répertoires `./public` et +`./views`, des logs, une page d'erreur, etc...). C'est là que +`Sinatra::Base` prend tout son intérêt : + +``` ruby +require 'sinatra/base' + +class MonApplication < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Bonjour le monde !' + end +end +``` + +Les méthodes de la classe `Sinatra::Base` sont parfaitement identiques à +celles disponibles via le DSL de haut niveau. Il suffit de deux modifications +pour transformer la plupart des applications de haut niveau en un composant +`Sinatra::Base` : + +* Votre fichier doit charger `sinatra/base` au lieu de `sinatra`, sinon toutes + les méthodes du DSL Sinatra seront importées dans l'espace de nom principal. +* Les gestionnaires de routes, la gestion d'erreur, les filtres et les options + doivent être placés dans une classe héritant de `Sinatra::Base`. + +`Sinatra::Base` est une page blanche. La plupart des options sont +désactivées par défaut, y compris le serveur intégré. Reportez-vous à +[Options et Configuration](http://sinatra.github.com/configuration.html) +pour plus d'informations sur les options et leur fonctionnement. Si vous +souhaitez un comportement plus proche de celui obtenu lorsque vous définissez +votre application au niveau supérieur (aussi connu sous le nom de style +Classique), vous pouvez créer une classe héritant de `Sinatra::Application`. + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Bonjour le monde !' + end +end +``` + +### Style modulaire vs. style classique + +Contrairement aux idées reçues, il n'y a rien de mal à utiliser le style +classique. Si c'est ce qui convient pour votre application, vous n'avez pas +aucune raison de passer à une application modulaire. + +Le principal inconvénient du style classique sur le style modulaire est que vous +ne pouvez avoir qu'une application Ruby par processus Ruby. Si vous pensez en +utiliser plus, passez au style modulaire. Et rien ne vous empêche de mixer style +classique et style modulaire. + +Si vous passez d'un style à l'autre, souvenez-vous des quelques différences +mineures en ce qui concerne les paramètres par défaut : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParamètreClassiqueModulaireModulaire
app_filefichier chargeant sinatrafichier héritant de Sinatra::Basefichier héritant de Sinatra::Application
run$0 == app_filefalsefalse
loggingtruefalsetrue
method_overridetruefalsetrue
inline_templatestruefalsetrue
statictruefalsetrue
+ +### Servir une application modulaire + +Il y a deux façons de faire pour démarrer une application modulaire, démarrez +avec `run!` : + +``` ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... code de l'application ici ... + + # démarre le serveur si ce fichier est directement exécuté + run! if app_file == $0 +end +``` + +Démarrez ensuite avec : + +``` shell +ruby my_app.rb +``` + +Ou alors avec un fichier `config.ru`, qui permet d'utiliser n'importe +quel gestionnaire Rack : + +``` ruby +# config.ru +require './my_app' +run MyApp +``` + +Exécutez : + +``` shell +rackup -p 4567 +``` + +### Utiliser une application de style classique avec un fichier config.ru + +Ecrivez votre application : + +``` ruby +# app.rb +require 'sinatra' + +get '/' do + 'Bonjour le monde !' +end +``` + +Et un fichier `config.ru` correspondant : + +``` ruby +require './app' +run Sinatra::Application +``` + +### Quand utiliser un fichier config.ru ? + +Quelques cas où vous devriez utiliser un fichier `config.ru` : + +* Vous souhaitez déployer avec un autre gestionnaire Rack (Passenger, Unicorn, + Heroku, ...). +* Vous souhaitez utiliser plus d'une sous-classe de `Sinatra::Base`. +* Vous voulez utiliser Sinatra comme un middleware, non en tant que + endpoint. + +**Il n'est pas nécessaire de passer par un fichier `config.ru` pour la +seule raison que vous êtes passé au style modulaire, et vous n'avez pas besoin +de passer au style modulaire pour utiliser un fichier `config.ru`.** + +### Utiliser Sinatra comme Middleware + +Non seulement Sinatra peut utiliser d'autres middlewares Rack, il peut +également être à son tour utilisé au-dessus de n'importe quel endpoint Rack +en tant que middleware. Ce endpoint peut très bien être une autre +application Sinatra, ou n'importe quelle application basée sur Rack +(Rails/Ramaze/Camping/...) : + +``` ruby +require 'sinatra/base' + +class EcranDeConnexion < Sinatra::Base + enable :sessions + + get('/connexion') { haml :connexion } + + post('/connexion') do + if params['nom'] = 'admin' && params['motdepasse'] = 'admin' + session['nom_utilisateur'] = params['nom'] + else + redirect '/connexion' + end + end +end + +class MonApp < Sinatra::Base + # le middleware sera appelé avant les filtres + use EcranDeConnexion + + before do + unless session['nom_utilisateur'] + halt "Accès refusé, merci de vous connecter." + end + end + + get('/') { "Bonjour #{session['nom_utilisateur']}." } +end +``` + +### Création dynamique d'applications + +Il se peut que vous ayez besoin de créer une nouvelle application à l'exécution +sans avoir à les assigner à une constante, vous pouvez le faire grâce à +`Sinatra.new` : + +``` ruby +require 'sinatra/base' +mon_app = Sinatra.new { get('/') { "salut" } } +mon_app.run! +``` + +L'application dont elle hérite peut être passé en argument optionnel : + +``` ruby +# config.ru +require 'sinatra/base' + +controleur = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controleur) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controleur) { get('/') { 'b' } } +end +``` + +C'est notamment utile pour tester des extensions à Sinatra ou bien pour +utiliser Sinatra dans votre propre bibliothèque. + +Cela permet également d'utiliser très facilement Sinatra comme middleware : + +``` ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Contextes et Binding + +Le contexte dans lequel vous êtes détermine les méthodes et variables +disponibles. + +### Contexte de l'application/classe + +Une application Sinatra correspond à une sous-classe de `Sinatra::Base`. Il +s'agit de `Sinatra::Application` si vous utilisez le DSL de haut niveau +(`require 'sinatra'`). Sinon c'est la sous-classe que vous avez définie. Dans +le contexte de cette classe, vous avez accès aux méthodes telles que `get` ou +`before`, mais pas aux objets `request` ou `session` étant donné que toutes +les requêtes sont traitées par une seule classe d'application. + +Les options définies au moyen de `set` deviennent des méthodes de classe : + +``` ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Eh, je ne suis plus dans le contexte de l'application ! + end +end +``` + +Vous avez le binding du contexte de l'application dans : + +* Le corps de la classe d'application +* Les méthodes définies par les extensions +* Le bloc passé à `helpers` +* Les procs/blocs utilisés comme argument pour `set` +* Le bloc passé à `Sinatra.new` + +Vous pouvez atteindre ce contexte (donc la classe) de la façon suivante : + +* Via l'objet passé dans les blocs `configure` (`configure { |c| ... }`) +* En utilisant `settings` dans le contexte de la requête + +### Contexte de la requête/instance + +Pour chaque requête traitée, une nouvelle instance de votre classe +d'application est créée et tous vos gestionnaires sont exécutés dans ce +contexte. Depuis celui-ci, vous pouvez accéder aux objets `request` et +`session` ou faire appel aux fonctions de rendu telles que `erb` ou `haml`. +Vous pouvez accéder au contexte de l'application depuis le contexte de la +requête au moyen de `settings` : + +``` ruby +class MonApp < Sinatra::Base + # Eh, je suis dans le contexte de l'application ! + get '/ajouter_route/:nom' do + # Contexte de la requête pour '/ajouter_route/:nom' + @value = 42 + + settings.get("/#{params['nom']}") do + # Contexte de la requête pour "/#{params['nom']}" + @value # => nil (on est pas au sein de la même requête) + end + + "Route ajoutée !" + end +end +``` + +Vous avez le binding du contexte de la requête dans : + +* les blocs get, head, post, put, delete, options, patch, link et unlink +* les filtres before et after +* les méthodes utilitaires (définies au moyen de `helpers`) +* les vues et templates + +### Le contexte de délégation + +Le contexte de délégation se contente de transmettre les appels de méthodes au +contexte de classe. Toutefois, il ne se comporte pas à 100% comme le contexte +de classe car vous n'avez pas le binding de la classe : seules les méthodes +spécifiquement déclarées pour délégation sont disponibles et il n'est pas +possible de partager des variables/états avec le contexte de classe +(comprenez : `self` n'est pas le même). Vous pouvez ajouter des délégation de +méthodes en appelant `Sinatra::Delegator.delegate :method_name`. + +Vous avez le binding du contexte de délégation dans : + +* Le binding de haut niveau, si vous avez utilisé `require "sinatra"` +* Un objet qui inclut le module `Sinatra::Delegator` + +Pour vous faire une idée, vous pouvez jeter un coup d'oeil au +[mixin Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +qui [étend l'objet principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Ligne de commande + +Les applications Sinatra peuvent être lancées directement : + +``` shell +ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR] +``` + +Avec les options : + +``` +-h # aide +-p # déclare le port (4567 par défaut) +-o # déclare l'hôte (0.0.0.0 par défaut) +-e # déclare l'environnement (development par défaut) +-s # déclare le serveur/gestionnaire à utiliser (thin par défaut) +-x # active le mutex lock (off par défaut) +``` + +## Configuration nécessaire + +Les versions suivantes de Ruby sont officiellement supportées : + +
+
Ruby 1.8.7
+
+ 1.8.7 est complètement supporté, toutefois si rien ne vous en empêche, + nous vous recommandons de faire une mise à jour ou bien de passer à JRuby + ou Rubinius. Le support de Ruby 1.8.7 ne sera pas supprimé avant la sortie + de Sinatra 2.0. Ruby 1.8.6 n’est plus supporté. +
+ +
Ruby 1.9.2
+
+ 1.9.2 est totalement supporté. N’utilisez pas 1.9.2p0 car il provoque des + erreurs de segmentation à l’exécution de Sinatra. Son support continuera + au minimum jusqu’à la sortie de Sinatra 1.5. +
+ +
Ruby 1.9.3
+
+ 1.9.3 est totalement supporté et recommandé. Nous vous rappelons que passer + à 1.9.3 depuis une version précédente annulera toutes les sessions. 1.9.3 + sera supporté jusqu'à la sortie de Sinatra 2.0. +
+ +
Ruby 2.0.0
+
+ 2.0.0 est totalement supporté et recommandé. L'abandon de son support + officiel n'est pas à l'ordre du jour. +
+ +
Rubinius
+
+ Rubinius est officiellement supporté (Rubinius >= 2.x). Un gem install + puma est recommandé. +
+ +
JRuby
+
+ La dernière version stable de JRuby est officiellement supportée. Il est + déconseillé d'utiliser des extensions C avec JRuby. Un gem install + trinidad est recommandé. +
+
+ +Nous gardons également un oeil sur les versions Ruby à venir. + +Les implémentations Ruby suivantes ne sont pas officiellement supportées mais +sont malgré tout connues pour permettre de faire fonctionner Sinatra : + +* Versions plus anciennes de JRuby et Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation) + +Le fait de ne pas être officiellement supporté signifie que si quelque chose +ne fonctionne pas sur cette plateforme uniquement alors c'est un problème de la +plateforme et pas un bug de Sinatra. + +Nous lançons également notre intégration continue (CI) avec ruby-head (la +future 2.1.0), mais nous ne pouvont rien garantir étant donné les évolutions +continuelles. La version 2.1.0 devrait être totalement supportée. + +Sinatra devrait fonctionner sur n'importe quel système d'exploitation +supporté par l'implémentation Ruby choisie. + +Si vous utilisez MacRuby, vous devriez `gem install control_tower`. + +Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, BlueRuby ou +toute version de Ruby antérieure à 1.8.7 à l'heure actuelle. + +## Essuyer les plâtres + +Si vous souhaitez tester la toute dernière version de Sinatra, n'hésitez pas +à faire tourner votre application sur la branche master, celle-ci devrait être +stable. + +Pour cela, la méthode la plus simple est d'installer une gem de prerelease que +nous publions de temps en temps : + +``` shell +gem install sinatra --pre +``` +Ce qui permet de bénéficier des toutes dernières fonctionnalités. + +### Installer avec Bundler + +Il est cependant conseillé de passer par [Bundler](http://gembundler.com/) pour +faire tourner votre application avec la dernière version de Sinatra. + +Pour commencer, installez bundler si nécessaire : + +``` shell +gem install bundler +``` + +Ensuite, créez un fichier `Gemfile` dans le dossier de votre projet : + +``` ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# autres dépendances +gem 'haml' # si par exemple vous utilisez haml +gem 'activerecord', '~> 3.0' # au cas où vous auriez besoin de ActiveRecord 3.x +``` + +Notez que vous devez lister toutes les dépendances de votre application dans +ce fichier `Gemfile`. Les dépendances directes de Sinatra (Rack et Tilt) seront +automatiquement téléchargées et ajoutées par Bundler. + +Vous pouvez alors lancer votre application de la façon suivante : + +``` shell +bundle exec ruby myapp.rb +``` + +### Faire un clone local + +Si vous ne souhaitez pas employer Bundler, vous pouvez cloner Sinatra en local +dans votre projet et démarrez votre application avec le dossier `sinatra/lib` +dans le `$LOAD_PATH` : + +``` shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +Et de temps en temps, vous devrez récupérer la dernière version du code source +de Sinatra : + +``` shell +cd myapp/sinatra +git pull +``` + +### Installer globalement + +Une dernière méthode consiste à construire la gem vous-même : + +``` shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +Si vous installez les gems en tant que root, vous devez encore faire un : + +``` shell +sudo rake install +``` + +## Versions + +Sinatra se conforme aux [versions sémantiques](http://semver.org/), aussi bien +SemVer que SemVerTag. + +## Mais encore + +* [Site internet](http://www.sinatrarb.com/) - Plus de documentation, + de news, et des liens vers d'autres ressources. +* [Contribuer](http://www.sinatrarb.com/contributing) - Vous avez trouvé un + bug ? Besoin d'aide ? Vous avez un patch ? +* [Suivi des problèmes](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Mailing List](http://groups.google.com/group/sinatrarb/topics) +* IRC : [#sinatra](irc://chat.freenode.net/#sinatra) sur http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Tutoriels et recettes +* [Sinatra Recipes](http://recipes.sinatrarb.com/) trucs et astuces rédigés par + la communauté +* Documentation API de la [dernière version](http://rubydoc.info/gems/sinatra) + ou du [HEAD courant](http://rubydoc.info/github/sinatra/sinatra) sur + http://rubydoc.info +* [CI server](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.hu.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.hu.md new file mode 100644 index 000000000..ff67ac2de --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.hu.md @@ -0,0 +1,694 @@ +# Sinatra +*Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű +változatnak, és lehet, hogy nem naprakész.* + +A Sinatra egy [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) +webalkalmazások Ruby nyelven történő fejlesztéséhez, minimális +energiabefektetéssel: + +```ruby + # myapp.rb + require 'sinatra' + get '/' do + 'Helló Világ!' + end +``` + +Telepítsd a gem-et és indítsd el az alkalmazást a következőképpen: + +```ruby + sudo gem install sinatra + ruby myapp.rb +``` + +Az alkalmazás elérhető lesz itt: `http://localhost:4567` + +## Útvonalak (routes) + +A Sinatrában az útvonalat egy HTTP metódus és egy URL-re illeszkedő minta +párosa alkotja. Minden egyes útvonalhoz tartozik egy blokk: + +```ruby + get '/' do + .. megjelenítünk valamit .. + end + + post '/' do + .. létrehozunk valamit .. + end + + put '/' do + .. frissítünk valamit .. + end + + delete '/' do + .. törlünk valamit .. + end +``` + +Az útvonalak illeszkedését a rendszer a definiálásuk sorrendjében +ellenőrzi. Sorrendben mindig az első illeszkedő útvonalhoz tartozó metódus kerül +meghívásra. + +Az útvonalminták tartalmazhatnak paramétereket is, melyeket a `params` +hash-ből érhetünk el: + +```ruby + get '/hello/:name' do + # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra + # ekkor params['name'] értéke 'foo' vagy 'bar' lesz + "Helló #{params['name']}!" + end +``` + +A kulcsszavas argumentumokat (named parameters) blokk paraméterek útján +is el tudod érni: + +```ruby + get '/hello/:name' do |n| + "Helló #{n}!" + end +``` + +Az útvonalmintákban szerepelhetnek joker paraméterek is, melyeket a +`params['splat']` tömbön keresztül tudunk elérni. + +```ruby + get '/say/*/to/*' do + # illeszkedik a /say/hello/to/world mintára + params['splat'] # => ["hello", "world"] + end + + get '/download/*.*' do + # illeszkedik a /download/path/to/file.xml mintára + params['splat'] # => ["path/to/file", "xml"] + end +``` + +Reguláris kifejezéseket is felvehetünk az útvonalba: + +```ruby + get /\A\/hello\/([\w]+)\z/ do + "Helló, #{params['captures'].first}!" + end +``` + +Vagy blokk paramétereket: + +```ruby + get %r{/hello/([\w]+)} do |c| + "Helló, #{c}!" + end +``` + +Az útvonalak azonban számos egyéb illeszkedési feltétel szerint is +tervezhetők, így például az user agent karakterláncot alapul véve: + +```ruby + get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "A Songbird #{params['agent'][0]} verzióját használod" + end + + get '/foo' do + # illeszkedik az egyéb user agentekre + end +``` + +## Statikus állományok + +A statikus fájlok kiszolgálása a `./public` könyvtárból +történik, de természetesen más könyvtárat is megadhatsz erre a célra, +mégpedig a :public_folder kapcsoló beállításával: + + set :public_folder, File.dirname(__FILE__) + '/static' + +Fontos megjegyezni, hogy a nyilvános könyvtár neve nem szerepel az URL-ben. +A ./public/css/style.css fájl az +`http://example.com/css/style.css` URL-en lesz elérhető. + +## Nézetek és Sablonok + +A sablonfájlokat rendszerint a `./views` könyvtárba helyezzük, de +itt is lehetőség nyílik egyéb könyvtár használatára: + + set :views, File.dirname(__FILE__) + '/templates' + +Nagyon fontos észben tartani, hogy a sablononkra mindig szimbólumokkal +hivatkozunk, még akkor is, ha egyéb (ebben az esetben a +:'subdir/template') könyvtárban tároljuk őket. A renderelő +metódusok minden, nekik közvetlenül átadott karakterláncot megjelenítenek. + +### Haml sablonok + +HAML sablonok rendereléséhez szükségünk lesz a haml gem-re vagy könyvtárra: + +```ruby + # Importáljuk be a haml-t az alkalmazásba + require 'haml' + + get '/' do + haml :index + end +``` + +Ez szépen lerendereli a `./views/index.haml` sablont. + +A [Haml kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml + + get '/' do + haml :index, :haml_options => {:format => :html4 } # immár felülírva + end +``` + +### Erb sablonok + + # Importáljuk be az erb-t az alkalmazásba + +```ruby + require 'erb' + + get '/' do + erb :index + end +``` + +Ez a `./views/index.erb` sablont fogja lerenderelni. + +### Builder sablonok + +Szükségünk lesz a builder gem-re vagy könyvtárra a builder sablonok +rendereléséhez: + + # Importáljuk be a builder-t az alkalmazásba + +```ruby + require 'builder' + + get '/' do + builder :index + end +``` + +Ez pedig a `./views/index.builder` állományt fogja renderelni. + +### Sass sablonok + +Sass sablonok használatához szükség lesz a haml gem-re vagy könyvtárra: + + # Be kell importálni a haml, vagy a sass könyvtárat + +```ruby + require 'sass' + + get '/stylesheet.css' do + sass :stylesheet + end +``` + +Így a `./views/stylesheet.sass` fájl máris renderelhető. + +A [Sass kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html) +globálisan is beállíthatók a Sinatra konfigurációi között, lásd az +[Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. +A globális beállításokat lehetőségünk van felülírni metódus szinten is. + +```ruby + set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested + + get '/stylesheet.css' do + sass :stylesheet, :sass_options => {:style => :expanded } # felülírva + end +``` + +### Beágyazott sablonok + +```ruby + get '/' do + haml '%div.title Helló Világ' + end +``` + +Lerendereli a beágyazott sablon karakerláncát. + +### Változók elérése a sablonokban + +A sablonok ugyanabban a kontextusban kerülnek kiértékelésre, mint az +útvonal metódusok (route handlers). Az útvonal metódusokban megadott +változók közvetlenül elérhetőek lesznek a sablonokban: + +```ruby + get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' + end +``` + +De megadhatod egy lokális változókat tartalmazó explicit hash-ben is: + +```ruby + get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } + end +``` + +Ezt leginkább akkor érdemes megtenni, ha partial-eket akarunk renderelni +valamely más sablonból. + +### Fájlon belüli sablonok + +Sablonokat úgy is megadhatunk, hogy egyszerűen az alkalmazás fájl +végére begépeljük őket: + +```ruby + require 'rubygems' + require 'sinatra' + + get '/' do + haml :index + end + + __END__ + + @@ layout + %html + = yield + + @@ index + %div.title Helló Világ!!!!! +``` + +Megjegyzés: azok a fájlon belüli sablonok, amelyek az alkalmazás fájl végére +kerülnek és függnek a sinatra könyvtártól, automatikusan betöltődnek. +Ha ugyanezt más alkalmazásfájlban is szeretnéd megtenni, hívd meg +a use_in_file_templates! metódust az adott fájlban. + +### Kulcsszavas sablonok + +Sablonokat végül a felsőszintű template metódussal is +definiálhatunk: + +```ruby + template :layout do + "%html\n =yield\n" + end + + template :index do + '%div.title Helló Világ!' + end + + get '/' do + haml :index + end +``` + +Ha létezik "layout" nevű sablon, akkor az minden esetben meghívódik, amikor +csak egy sablon renderelésre kerül. A layoutokat ki lehet kapcsolni a +`:layout => false` meghívásával. + +```ruby + get '/' do + haml :index, :layout => !request.xhr? + end +``` + +## Helperek + +Használd a felső szintű helpers metódust azokhoz a helper +függvényekhez, amiket az útvonal metódusokban és a sablonokban akarsz +használni: + +```ruby + helpers do + def bar(name) + "#{name}bar" + end + end + + get '/:name' do + bar(params['name']) + end +``` + +## Szűrők (filters) + +Az előszűrők (before filter) az adott hívás kontextusában minden egyes +kérés alkalmával kiértékelődnek, így módosíthatják a kérést és a +választ egyaránt. A szűrőkbe felvett példányváltozók elérhetőek lesznek +az útvonalakban és a sablonokban is: + +```ruby + before do + @note = 'Csá!' + request.path_info = '/foo/bar/baz' + end + + get '/foo/*' do + @note #=> 'Szeva!' + params['splat'] #=> 'bar/baz' + end +``` + +Az utószűrők az egyes kérések után, az adott kérés kontextusában kerülnek +kiértékelésre, így ezek is képesek módosítani a kérést és a választ egyaránt. +Az előszűrőkben és úvonalakban létrehozott példányváltozók elérhetőek lesznek +az utószűrők számára: + +```ruby + after do + puts response.status + end +``` + +## Megállítás + +Egy kérés szűrőben vagy útvonalban történő azonnal blokkolásához +használd a következő parancsot: + + halt + +A megállításkor egy blokktörzset is megadhatsz ... + + halt 'ez fog megjelenni a törzsben' + +Vagy állítsd be a HTTP státuszt és a törzset is egyszerre ... + + halt 401, 'menj innen!' + +## Passzolás + +Az útvonalak továbbadhatják a végrehajtást egy másik útvonalnak +a `pass` függvényhívással: + +```ruby + get '/guess/:who' do + pass unless params['who'] == 'Frici' + "Elkaptál!" + end + + get '/guess/*' do + "Elhibáztál!" + end +``` + +Az útvonal blokkja azonnal kilép és átadja a vezérlést a következő +illeszkedő útvonalnak. Ha nem talál megfelelő útvonalat, a Sinatra +egy 404-es hibával tér vissza. + +## Beállítások + +Csak indításkor, de minden környezetre érvényesen fusson le: + +```ruby + configure do + ... + end +``` + +Csak akkor fusson le, ha a környezet (a RACK_ENV környezeti változóban) +`:production`-ra van állítva: + +```ruby + configure :production do + ... + end +``` + +Csak akkor fusson le, ha a környezet :production vagy :test: + +```ruby + configure :production, :test do + ... + end +``` + +## Hibakezelés + +A hibakezelők ugyanabban a kontextusban futnak le, mint az útvonalak és +előszűrők, ezért számukra is elérhetőek mindazok a könyvtárak, amelyek +az utóbbiak rendelkezésére is állnak; így például a `haml`, +az `erb`, a `halt` stb. + +### Nem található + +Amikor a `Sinatra::NotFound` kivétel fellép, vagy a válasz HTTP +státuszkódja 404-es, mindig a `not_found` metódus hívódik meg. + +```ruby + not_found do + 'Sehol sem találom, amit keresel' + end +``` + +### Hiba + +Az `error` metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy +előszűrő kivételt vált ki. A kivétel objektum lehívható a +`sinatra.error` Rack változótól: + +```ruby + error do + 'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].message + end +``` + +Egyéni hibakezelés: + +```ruby + error MyCustomError do + 'Szóval az van, hogy...' + env['sinatra.error'].message + end +``` + +És amikor fellép: + +```ruby + get '/' do + raise MyCustomError, 'valami nem stimmel!' + end +``` + +Ez fog megjelenni: + + Szóval az van, hogy... valami nem stimmel! + +A Sinatra speciális `not_found` és `error` hibakezelőket +használ, amikor a futtatási környezet fejlesztői módba van kapcsolva. + +## Mime típusok + +A `send_file` metódus használatakor, vagy statikus fájlok +kiszolgálásakor előfordulhat, hogy a Sinatra nem ismeri fel a fájlok +mime típusát. Ilyenkor használd a +mime_type+ kapcsolót a fájlkiterjesztés +bevezetéséhez: + +```ruby + mime_type :foo, 'text/foo' +``` + +## Rack Middleware + +A Sinatra egy Ruby keretrendszerek számára kifejlesztett egyszerű és szabványos +interfészre, a [Rack](http://rack.github.io/) -re épül. A Rack fejlesztői +szempontból egyik legérdekesebb jellemzője, hogy támogatja az úgynevezett +"middleware" elnevezésű komponenseket, amelyek beékelődnek a szerver és az +alkalmazás közé, így képesek megfigyelni és/vagy módosítani a HTTP +kéréseket és válaszokat. Segítségükkel különféle, egységesen működő +funkciókat építhetünk be rendszerünkbe. + +A Sinatra keretrendszerben gyerekjáték a Rack middleware-ek behúzása a +`use` metódus segítségével: + +```ruby + require 'sinatra' + require 'my_custom_middleware' + + use Rack::Lint + use MyCustomMiddleware + + get '/hello' do + 'Helló Világ' + end +``` + +A `use` metódus szemantikája megegyezik a +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) DSL-ben +használt +use+ metóduséval (az említett DSL-t leginkább rackup állományokban +használják). Hogy egy példát említsünk, a `use` metódus elfogad +változókat és blokkokat egyaránt, akár kombinálva is ezeket: + +```ruby + use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'titkos' + end +``` + +A Rack terjesztéssel egy csomó alap middleware komponens is érkezik, +amelyekkel a naplózás, URL útvonalak megadása, autentikáció és +munkamenet-kezelés könnyen megvalósítható. A Sinatra ezek közül elég +sokat automatikusan felhasznál a beállításoktól függően, így ezek +explicit betöltésével (+use+) nem kell bajlódnod. + +## Tesztelés + +Sinatra teszteket bármely Rack alapú tesztelő könyvtárral vagy +keretrendszerrel készíthetsz. Mi a [Rack::Test](http://gitrdoc.com/brynary/rack-test) +könyvtárat ajánljuk: + +```ruby + require 'my_sinatra_app' + require 'rack/test' + + class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Helló Világ!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frici' + assert_equal 'Helló Frici!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbird-öt használsz!", last_response.body + end + end +``` + +Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok +a 0.9.2-es kiadástól kezdve elavultnak számítanak. + +## Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások + +Az alkalmazást felső szinten építeni megfelelhet mondjuk egy kisebb +app esetén, ám kifejezetten károsnak bizonyulhat olyan komolyabb, +újra felhasználható komponensek készítésekor, mint például egy Rack +middleware, Rails metal, egyszerűbb kiszolgáló komponenssel bíró +könyvtárak vagy éppen Sinatra kiterjesztések. A felső szintű DSL +bepiszkítja az Objektum névteret, ráadásul kisalkalmazásokra szabott +beállításokat feltételez (így például egyetlen alkalmazásfájl, +`./public` +és `./views` könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). +Itt jön a képbe a Sinatra::Base osztály: + +```ruby + require 'sinatra/base' + + class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Helló Világ!' + end + end +``` + +A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként +vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül +használhatod (`use`) vagy futtathatod (`run`) az osztályodat egy rackup +konfigurációs állományban (`config.ru`), vagy egy szerverkomponenst +tartalmazó könyvtár vezérlésekor: + +```ruby + MyApp.run! :host => 'localhost', :port => 9090 +``` + +A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső +szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű +alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben: + +* A fájlban nem a `sinatra`, hanem a `sinatra/base` osztályt kell + beimportálni, mert egyébként az összes Sinatra DSL metódus a fő + névtérbe kerül. +* Az alkalmazás útvonalait, hibakezelőit, szűrőit és beállításait + a Sinatra::Base osztály gyermekosztályaiban kell megadni. + +A `Sinatra::Base` osztály igazából egy üres lap: a legtöbb funkció +alapból ki van kapcsolva, beleértve a beépített szervert is. A +beállításokkal és az egyes kapcsolók hatásával az +[Options and Configuration](http://sinatra.github.com/configuration.html) lap +foglalkozik. + +Széljegyzet: A Sinatra felső szintű DSL-je egy egyszerű delegációs +rendszerre épül. A Sinatra::Application osztály - a Sinatra::Base egy +speciális osztályaként - fogadja az összes :get, :put, :post, +:delete, :before, :error, :not_found, :configure és :set üzenetet, +ami csak a felső szintre beérkezik. Érdemes utánanézned a kódban, +miképp [kerül be](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25) +a [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064) +a fő névtérbe. + +## Parancssori lehetőségek + +Sinatra alkalmazásokat közvetlenül futtathatunk: + +``` + ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER] +``` + +Az alábbi kapcsolókat ismeri fel a rendszer: + + -h # segítség + -p # a port beállítása (alapértelmezés szerint ez a 4567-es) + -e # a környezet beállítása (alapértelmezés szerint ez a development) + -s # a rack szerver/handler beállítása (alapértelmezetten ez a thin) + -x # a mutex lock bekapcsolása (alapértelmezetten ki van kapcsolva) + +## Fejlesztői változat + +Ha a Sinatra legfrissebb, fejlesztői változatát szeretnéd használni, +készíts egy helyi másolatot és indítsd az alkalmazásodat úgy, +hogy a `sinatra/lib` könyvtár elérhető legyen a +`LOAD_PATH`-on: + +``` + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb +``` + +De hozzá is adhatod a sinatra/lib könyvtárat a LOAD_PATH-hoz +az alkalmazásodban: + +```ruby + $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' + require 'rubygems' + require 'sinatra' + + get '/about' do + "A következő változatot futtatom " + Sinatra::VERSION + end +``` + +A Sinatra frissítését később így végezheted el: + +``` + cd myproject/sinatra + git pull +``` + +## További információk + +* [A projekt weboldala](http://sinatra.github.com/) - Kiegészítő dokumentáció, + hírek, hasznos linkek +* [Közreműködés](http://sinatra.github.com/contributing.html) - Hibát találtál? + Segítségre van szükséged? Foltot küldenél be? +* [Lighthouse](http://sinatra.lighthouseapp.com) - Hibakövetés és kiadások +* [Twitter](http://twitter.com/sinatra) +* [Levelezőlista](http://groups.google.com/group/sinatrarb) +* [IRC: #sinatra](irc://chat.freenode.net/#sinatra) a http://freenode.net címen diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ja.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ja.md new file mode 100644 index 000000000..843caf884 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ja.md @@ -0,0 +1,2724 @@ +# Sinatra + +*注) +本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照して下さい。* + +Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るための[DSL](http://ja.wikipedia.org/wiki/ドメイン固有言語)です。 + +``` ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +gemをインストールし、 + +``` shell +gem install sinatra +``` + +次のように実行します。 + +``` shell +ruby myapp.rb +``` + +[localhost:4567](http://localhost:4567) を開きます。 + +ThinがあればSinatraはこれを利用するので、`gem install thin`することをお薦めします。 + +## 目次 + +* [Sinatra](#sinatra) + * [目次](#目次) + * [ルーティング(Routes)](#ルーティングroutes) + * [条件(Conditions)](#条件conditions) + * [戻り値(Return Values)](#戻り値return-values) + * [カスタムルーティングマッチャー(Custom Route Matchers)](#カスタムルーティングマッチャーcustom-route-matchers) + * [静的ファイル(Static Files)](#静的ファイルstatic-files) + * [ビュー / テンプレート(Views / Templates)](#ビュー--テンプレートviews--templates) + * [リテラルテンプレート(Literal Templates)](#リテラルテンプレートliteral-templates) + * [利用可能なテンプレート言語](#利用可能なテンプレート言語) + * [Haml テンプレート](#haml-テンプレート) + * [Erb テンプレート](#erb-テンプレート) + * [Builder テンプレート](#builder-テンプレート) + * [Nokogiri テンプレート](#nokogiri-テンプレート) + * [Sass テンプレート](#sass-テンプレート) + * [SCSS テンプレート](#scss-テンプレート) + * [Less テンプレート](#less-テンプレート) + * [Liquid テンプレート](#liquid-テンプレート) + * [Markdown テンプレート](#markdown-テンプレート) + * [Textile テンプレート](#textile-テンプレート) + * [RDoc テンプレート](#rdoc-テンプレート) + * [AsciiDoc テンプレート](#asciidoc-テンプレート) + * [Radius テンプレート](#radius-テンプレート) + * [Markaby テンプレート](#markaby-テンプレート) + * [RABL テンプレート](#rabl-テンプレート) + * [Slim テンプレート](#slim-テンプレート) + * [Creole テンプレート](#creole-テンプレート) + * [MediaWiki テンプレート](#mediawiki-テンプレート) + * [CoffeeScript テンプレート](#coffeescript-テンプレート) + * [Stylus テンプレート](#stylus-テンプレート) + * [Yajl テンプレート](#yajl-テンプレート) + * [WLang テンプレート](#wlang-テンプレート) + * [テンプレート内での変数へのアクセス](#テンプレート内での変数へのアクセス) + * [`yield`を伴うテンプレートとネストしたレイアウト](#yieldを伴うテンプレートとネストしたレイアウト) + * [インラインテンプレート(Inline Templates)](#インラインテンプレートinline-templates) + * [名前付きテンプレート(Named Templates)](#名前付きテンプレートnamed-templates) + * [ファイル拡張子の関連付け](#ファイル拡張子の関連付け) + * [オリジナルテンプレートエンジンの追加](#オリジナルテンプレートエンジンの追加) + * [フィルタ(Filters)](#フィルタfilters) + * [ヘルパー(Helpers)](#ヘルパーhelpers) + * [セッションの使用](#セッションの使用) + * [停止(Halting)](#停止halting) + * [パッシング(Passing)](#パッシングpassing) + * [別ルーティングの誘発](#別ルーティングの誘発) + * [ボディ、ステータスコードおよびヘッダの設定](#ボディステータスコードおよびヘッダの設定) + * [ストリーミングレスポンス(Streaming Responses)](#ストリーミングレスポンスstreaming-responses) + * [ロギング(Logging)](#ロギングlogging) + * [MIMEタイプ(Mime Types)](#mimeタイプmime-types) + * [URLの生成](#urlの生成) + * [ブラウザリダイレクト(Browser Redirect)](#ブラウザリダイレクトbrowser-redirect) + * [キャッシュ制御(Cache Control)](#キャッシュ制御cache-control) + * [ファイルの送信](#ファイルの送信) + * [リクエストオブジェクトへのアクセス](#リクエストオブジェクトへのアクセス) + * [アタッチメント(Attachments)](#アタッチメントattachments) + * [日付と時刻の取り扱い](#日付と時刻の取り扱い) + * [テンプレートファイルの探索](#テンプレートファイルの探索) + * [コンフィギュレーション(Configuration)](#コンフィギュレーションconfiguration) + * [攻撃防御に対する設定](#攻撃防御に対する設定) + * [利用可能な設定](#利用可能な設定) + * [環境設定(Environments)](#環境設定environments) + * [エラーハンドリング(Error Handling)](#エラーハンドリングerror-handling) + * [Not Found](#not-found) + * [エラー(Error)](#エラーerror) + * [Rackミドルウェア(Rack Middleware)](#rackミドルウェアrack-middleware) + * [テスト(Testing)](#テストtesting) + * [Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ](#sinatrabase---ミドルウェアライブラリおよびモジュラーアプリ) + * [モジュラースタイル vs クラッシックスタイル](#モジュラースタイル-vs-クラッシックスタイル) + * [モジュラーアプリケーションの提供](#モジュラーアプリケーションの提供) + * [config.ruを用いたクラッシックスタイルアプリケーションの使用](#configruを用いたクラッシックスタイルアプリケーションの使用) + * [config.ruはいつ使うのか?](#configruはいつ使うのか) + * [Sinatraのミドルウェアとしての利用](#sinatraのミドルウェアとしての利用) + * [動的なアプリケーションの生成](#動的なアプリケーションの生成) + * [スコープとバインディング(Scopes and Binding)](#スコープとバインディングscopes-and-binding) + * [アプリケーション/クラスのスコープ](#アプリケーションクラスのスコープ) + * [リクエスト/インスタンスのスコープ](#リクエストインスタンスのスコープ) + * [デリゲートスコープ](#デリゲートスコープ) + * [コマンドライン](#コマンドライン) + * [必要環境](#必要環境) + * [最新開発版](#最新開発版) + * [Bundlerを使う場合](#bundlerを使う場合) + * [直接組み込む場合](#直接組み込む場合) + * [グローバル環境にインストールする場合](#グローバル環境にインストールする場合) + * [バージョニング(Versioning)](#バージョニングversioning) + * [参考文献](#参考文献) + +## ルーティング(Routes) + +Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。 +ルーティングはブロックに結び付けられています。 + +``` ruby +get '/' do + .. 何か見せる .. +end + +post '/' do + .. 何か生成する .. +end + +put '/' do + .. 何か更新する .. +end + +patch '/' do + .. 何か修正する .. +end + +delete '/' do + .. 何か削除する .. +end + +options '/' do + .. 何か満たす .. +end + +link '/' do + .. 何かリンクを張る .. +end + +unlink '/' do + .. 何かアンリンクする .. +end +``` + +ルーティングは定義された順番にマッチします。 +リクエストに最初にマッチしたルーティングが呼び出されます。 + +ルーティングのパターンは名前付きパラメータを含むことができ、 +`params`ハッシュで取得できます。 + +``` ruby +get '/hello/:name' do + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + "Hello #{params['name']}!" +end +``` + +また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。 + +``` ruby +get '/hello/:name' do |n| + # "GET /hello/foo" と "GET /hello/bar" にマッチ + # params['name'] は 'foo' か 'bar' + # n が params['name'] を保持 + "Hello #{n}!" +end +``` + +ルーティングパターンはアスタリスク(すなわちワイルドカード)を含むこともでき、 +`params['splat']` で取得できます。 + +``` ruby +get '/say/*/to/*' do + # /say/hello/to/world にマッチ + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml にマッチ + params['splat'] # => ["path/to/file", "xml"] +end +``` + +ここで、ブロックパラメータを使うこともできます。 + +``` ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +ルーティングを正規表現にマッチさせることもできます。 + +``` ruby +get /\A\/hello\/([\w]+)\z/ do + "Hello, #{params['captures'].first}!" +end +``` + +ここでも、ブロックパラメータが使えます。 + +``` ruby +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +ルーティングパターンは、オプショナルパラメータを取ることもできます。 + +``` ruby +get '/posts.?:format?' do + # "GET /posts" と "GET /posts.json", "GET /posts.xml" の拡張子などにマッチ +end +``` + +ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、 +ルーティングにマッチする前にリクエストパスが修正される可能性があります。 + +## 条件(Conditions) + +ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。 + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbirdのバージョン #{params['agent'][0]}を使ってます。" +end + +get '/foo' do + # Songbird以外のブラウザにマッチ +end +``` + +ほかに`host_name`と`provides`条件が利用可能です。 + +``` ruby +get '/', :host_name => /^admin\./ do + "Adminエリアです。アクセスを拒否します!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +独自の条件を定義することも簡単にできます。 + +``` ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "あなたの勝ちです!" +end + +get '/win_a_car' do + "残念、あなたの負けです。" +end +``` + +複数の値を取る条件には、アスタリスクを使います。 + +``` ruby +set(:auth) do |*roles| # <- ここでアスタリスクを使う + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "アカウントの詳細" +end + +get "/only/admin/", :auth => :admin do + "ここは管理者だけ!" +end +``` + +## 戻り値(Return Values) + +ルーティングブロックの戻り値は、HTTPクライアントまたはRackスタックでの次のミドルウェアに渡されるレスポンスボディを決定します。 + +これは大抵の場合、上の例のように文字列ですが、それ以外の値も使用することができます。 + +Rackレスポンス、Rackボディオブジェクト、HTTPステータスコードのいずれかとして妥当なオブジェクトであればどのようなオブジェクトでも返すことができます。 + +* 3つの要素を含む配列: + `[ステータス(Fixnum), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]` +* 2つの要素を含む配列: + `[ステータス(Fixnum), レスポンスボディ(#eachに応答する)]` +* `#each`に応答するオブジェクト。通常はそのまま何も返さないが、 +与えられたブロックに文字列を渡す。 +* ステータスコードを表現する整数(Fixnum) + +これにより、例えばストリーミングを簡単に実装することができます。 + +``` ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +後述する`stream`ヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。 + +## カスタムルーティングマッチャー(Custom Route Matchers) + +先述のようにSinatraはルーティングマッチャーとして、文字列パターンと正規表現を使うことをビルトインでサポートしています。しかしこれに留まらず、独自のマッチャーを簡単に定義することもできるのです。 + +``` ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +ノート: この例はオーバースペックであり、以下のようにも書くことができます。 + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +または、否定先読みを使って: + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + + +## 静的ファイル(Static Files) + +静的ファイルは`./public`ディレクトリから配信されます。 +`:public_folder`オプションを指定することで別の場所を指定することができます。 + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +ノート: この静的ファイル用のディレクトリ名はURL中に含まれません。 +例えば、`./public/css/style.css`は`http://example.com/css/style.css`でアクセスできます。 + +`Cache-Control`の設定をヘッダーへ追加するには`:static_cache_control`の設定(下記参照)を加えてください。 + +## ビュー / テンプレート(Views / Templates) + +各テンプレート言語はそれ自身のレンダリングメソッドを通して展開されます。それらのメソッドは単に文字列を返します。 + +``` ruby +get '/' do + erb :index +end +``` + +これは、`views/index.erb`をレンダリングします。 + +テンプレート名を渡す代わりに、直接そのテンプレートの中身を渡すこともできます。 + +``` ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。 + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +これは、`views/post.erb`内に埋め込まれた`views/index.erb`をレンダリングします(デフォルトは`views/layout.erb`があればそれになります)。 + +Sinatraが理解できないオプションは、テンプレートエンジンに渡されることになります。 + + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +テンプレート言語ごとにオプションをセットすることもできます。 + +``` ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +レンダリングメソッドに渡されたオプションは`set`で設定されたオプションを上書きします。 + +利用可能なオプション: + +
+
locals
+
+ ドキュメントに渡されるローカルのリスト。パーシャルに便利。 + 例: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ 文字エンコーディング(不確かな場合に使用される)。デフォルトは、settings.default_encoding。 +
+ +
views
+
+ テンプレートを読み出すビューのディレクトリ。デフォルトは、settings.views。 +
+ +
layout
+
+ レイアウトを使うかの指定(true または false)。値がシンボルの場合は、使用するテンプレートが指定される。例: erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ テンプレートが生成するContent-Type。デフォルトはテンプレート言語ごとに異なる。 +
+ +
scope
+
+ テンプレートをレンダリングするときのスコープ。デフォルトは、アプリケーションのインスタンス。これを変更した場合、インスタンス変数およびヘルパーメソッドが利用できなくなる。 +
+ +
layout_engine
+
+ レイアウトをレンダリングするために使用するテンプレートエンジン。レイアウトをサポートしない言語で有用。デフォルトはテンプレートに使われるエンジン。例: set :rdoc, :layout_engine => :erb +
+ +
layout_options
+
+ レイアウトをレンダリングするときだけに使う特別なオプション。例: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
+
+ +テンプレートは`./views`ディレクトリ下に配置されています。 +他のディレクトリを使用する場合の例: + +``` ruby +set :views, settings.root + '/templates' +``` + +テンプレートはシンボルを使用して参照させることを覚えておいて下さい。 +サブディレクトリでもこの場合は`:'subdir/template'`のようにします。 +レンダリングメソッドは文字列が渡されると、それをそのまま文字列として出力するので、シンボルを使ってください。 + +### リテラルテンプレート(Literal Templates) + +``` ruby +get '/' do + haml '%div.title Hello World' +end +``` + +これはそのテンプレート文字列をレンダリングします。 + +### 利用可能なテンプレート言語 + +いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。 + + +``` ruby +require 'rdiscount' # または require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml テンプレート + + + + + + + + + + + + + + +
依存haml
ファイル拡張子.haml
haml :index, :format => :html5
+ + +#### Erb テンプレート + + + + + + + + + + + + + + +
依存 + erubis + または erb (Rubyに同梱) +
ファイル拡張子.erb, .rhtml or .erubis (Erubisだけ)
erb :index
+ +#### Builder テンプレート + + + + + + + + + + + + + + +
依存 + builder +
ファイル拡張子.builder
builder { |xml| xml.em "hi" }
+ +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### Nokogiri テンプレート + + + + + + + + + + + + + + +
依存nokogiri
ファイル拡張子.nokogiri
nokogiri { |xml| xml.em "hi" }
+ +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + + +#### Sass テンプレート + + + + + + + + + + + + + + +
依存sass
ファイル拡張子.sass
sass :stylesheet, :style => :expanded
+ + +#### Scss テンプレート + + + + + + + + + + + + + + +
依存sass
ファイル拡張子.scss
scss :stylesheet, :style => :expanded
+ +#### Less テンプレート + + + + + + + + + + + + + + +
依存less
ファイル拡張子.less
less :stylesheet
+ +#### Liquid テンプレート + + + + + + + + + + + + + + +
依存liquid
ファイル拡張子.liquid
liquid :index, :locals => { :key => 'value' }
+ +LiquidテンプレートからRubyのメソッド(`yield`を除く)を呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Markdown テンプレート + + + + + + + + + + + + + + +
依存 + 次の何れか: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
ファイル拡張子.markdown, .mkd and .md
markdown :index, :layout_engine => :erb
+ +Markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +``` ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +ノート: 他のテンプレート内で`markdown`メソッドを呼び出せます。 + +``` ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + + +#### Textile テンプレート + + + + + + + + + + + + + + +
依存RedCloth
ファイル拡張子.textile
textile :index, :layout_engine => :erb
+ +Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +``` ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +ノート: 他のテンプレート内で`textile`メソッドを呼び出せます。 + +``` ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +TexttileからはRubyを呼ぶことができないので、Textileで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### RDoc テンプレート + + + + + + + + + + + + + + +
依存RDoc
ファイル拡張子.rdoc
rdoc :README, :layout_engine => :erb
+ +RDocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +``` ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +ノート: 他のテンプレート内で`rdoc`メソッドを呼び出せます。 + + +``` ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDocからはRubyを呼ぶことができないので、RDocで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### AsciiDoc テンプレート + + + + + + + + + + + + + + +
依存Asciidoctor
ファイル拡張子.asciidoc, .adoc and .ad
asciidoc :README, :layout_engine => :erb
+ +AsciiDocテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + +#### Radius テンプレート + + + + + + + + + + + + + + +
依存Radius
ファイル拡張子.radius
radius :index, :locals => { :key => 'value' }
+ +RadiusテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。 + + +#### Markaby テンプレート + + + + + + + + + + + + + + +
依存Markaby
ファイル拡張子.mab
markaby { h1 "Welcome!" }
+ +インラインテンプレート用にブロックを取ることもできます(例を参照)。 + +#### RABL テンプレート + + + + + + + + + + + + + + +
依存Rabl
ファイル拡張子.rabl
rabl :index
+ +#### Slim テンプレート + + + + + + + + + + + + + + +
依存Slim Lang
ファイル拡張子.slim
slim :index
+ +#### Creole テンプレート + + + + + + + + + + + + + + +
依存Creole
ファイル拡張子.creole
creole :wiki, :layout_engine => :erb
+ +Creoleからメソッドを呼び出すことも、localsに変数を渡すこともできません。 +それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。 + +``` ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +ノート: 他のテンプレート内で`creole`メソッドを呼び出せます。 + +``` ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +CreoleからはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 + +#### MediaWiki テンプレート + + + + + + + + + + + + + + +
依存WikiCloth
ファイル拡張子.mediawiki および .mw
mediawiki :wiki, :layout_engine => :erb
+ +MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。 + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +ノート: 他のテンプレートから部分的に`mediawiki`メソッドを呼び出すことも可能です。 + +#### CoffeeScript テンプレート + + + + + + + + + + + + + + +
依存 + + CoffeeScript + および + + JavaScriptの起動方法 + +
ファイル拡張子.coffee
coffee :index
+ +#### Stylus テンプレート + + + + + + + + + + + + + + +
依存 + + Stylus + および + + JavaScriptの起動方法 + +
ファイル拡張子.styl
stylus :index
+ +Stylusテンプレートを使えるようにする前に、まず`stylus`と`stylus/tilt`を読み込む必要があります。 + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl テンプレート + + + + + + + + + + + + + + +
依存yajl-ruby
ファイル拡張子.yajl
+ + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ + +テンプレートのソースはRubyの文字列として評価され、その結果のJSON変数は`#to_json`を使って変換されます。 + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`および`:variable`オプションは、レンダリングされたオブジェクトを装飾するために使うことができます。 + +``` ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +#### WLang テンプレート + + + + + + + + + + + + + + +
依存wlang
ファイル拡張子.wlang
wlang :index, :locals => { :key => 'value' }
+ +WLang内でのRubyメソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトは`yield`をサポートしています。 + +### テンプレート内での変数へのアクセス + +テンプレートはルーティングハンドラと同じコンテキストの中で評価されます。ルーティングハンドラでセットされたインスタンス変数はテンプレート内で直接使うことができます。 + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +また、ローカル変数のハッシュで明示的に指定することもできます。 + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +このやり方は他のテンプレート内で部分テンプレートとして表示する時に典型的に使用されます。 + +### `yield`を伴うテンプレートとネストしたレイアウト + +レイアウトは通常、`yield`を呼ぶ単なるテンプレートに過ぎません。 +そのようなテンプレートは、既に説明した`:template`オプションを通して使われるか、または次のようなブロックを伴ってレンダリングされます。 + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +このコードは、`erb :index, :layout => :post`とほぼ等価です。 + +レンダリングメソッドにブロックを渡すスタイルは、ネストしたレイアウトを作るために最も役立ちます。 + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +これはまた次のより短いコードでも達成できます。 + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +現在、次のレンダリングメソッドがブロックを取れます: `erb`, `haml`, +`liquid`, `slim `, `wlang`。 +また汎用の`render`メソッドもブロックを取れます。 + + +### インラインテンプレート(Inline Templates) + +テンプレートはソースファイルの最後で定義することもできます。 + +``` ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world!!!!! +``` + +ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合には`enable :inline_templates`を明示的に呼んでください。 + +### 名前付きテンプレート(Named Templates) + +テンプレートはトップレベルの`template`メソッドで定義することもできます。 + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +「layout」というテンプレートが存在する場合、そのテンプレートファイルは他のテンプレートがレンダリングされる度に使用されます。`:layout => false`で個別に、または`set :haml, :layout => false`でデフォルトとして、レイアウトを無効にすることができます。 + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### ファイル拡張子の関連付け + +任意のテンプレートエンジンにファイル拡張子を関連付ける場合は、`Tilt.register`を使います。例えば、Textileテンプレートに`tt`というファイル拡張子を使いたい場合は、以下のようにします。 + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### オリジナルテンプレートエンジンの追加 + +まず、Tiltでそのエンジンを登録し、次にレンダリングメソッドを作ります。 + +``` ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +これは、`./views/index.myat`をレンダリングします。Tiltについての詳細は、https://github.com/rtomayko/tilt を参照してください。 + +## フィルタ(Filters) + +beforeフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの前に評価され、それによってリクエストとレスポンスを変更可能にします。フィルタ内でセットされたインスタンス変数はルーティングとテンプレートからアクセスすることができます。 + +``` ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +afterフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの後に評価され、それによってこれもリクエストとレスポンスを変更可能にします。beforeフィルタとルーティング内でセットされたインスタンス変数はafterフィルタからアクセスすることができます。 + +``` ruby +after do + puts response.status +end +``` + +ノート: `body`メソッドを使わずにルーティングから文字列を返すだけの場合、その内容はafterフィルタでまだ利用できず、その後に生成されることになります。 + +フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。 + +``` ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +ルーティング同様、フィルタもまた条件を取ることができます。 + +``` ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## ヘルパー(Helpers) + +トップレベルの`helpers`メソッドを使用してルーティングハンドラやテンプレートで使うヘルパーメソッドを定義できます。 + +``` ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +あるいは、ヘルパーメソッドをモジュール内で個別に定義することもできます。 + +``` ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。 + + +### セッションの使用 + +セッションはリクエスト間での状態維持のために使用されます。その起動により、ユーザセッションごとに一つのセッションハッシュが与えられます。 + +``` ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +ノート: `enable :sessions`は実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合は`enable :sessions`を呼ばずに、選択したミドルウェアを他のミドルウェアのときと同じようにして取り込んでください。 + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session[:value] = params['value'] +end +``` + +セキュリティ向上のため、クッキー内のセッションデータはセッション秘密鍵(session secret)で署名されます。Sinatraによりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。 + +``` ruby +set :session_secret, 'super secret' +``` + +更に、設定変更をしたい場合は、`sessions`の設定においてオプションハッシュを保持することもできます。 + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に *.* を付けます。 + +``` ruby +set :sessions, :domain => '.foo.com' +``` + +### 停止(Halting) + +フィルタまたはルーティング内で直ちにリクエストを止める場合 + +``` ruby +halt +``` + +この際、ステータスを指定することもできます。 + +``` ruby +halt 410 +``` + +body部を指定することも、 + +``` ruby +halt 'ここにbodyを書く' +``` + +ステータスとbody部を指定することも、 + +``` ruby +halt 401, '立ち去れ!' +``` + +ヘッダを付けることもできます。 + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ' +``` + +もちろん、テンプレートを`halt`に結びつけることも可能です。 + +``` ruby +halt erb(:error) +``` + +### パッシング(Passing) + +ルーティングは`pass`を使って次のルーティングに飛ばすことができます。 + +``` ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + "見つかっちゃった!" +end + +get '/guess/*' do + "はずれです!" +end +``` + +ルーティングブロックからすぐに抜け出し、次にマッチするルーティングを実行します。マッチするルーティングが見当たらない場合は404が返されます。 + +### 別ルーティングの誘発 + +`pass`を使ってルーティングを飛ばすのではなく、他のルーティングを呼んだ結果を得たいというときがあります。これを実現するには`call`を使えばいいです。 + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +ノート: 先の例において、テストを楽にしパフォーマンスを改善するには、`"bar"`を単にヘルパーに移し、`/foo`および`/bar`から使えるようにするのがいいです。 + +リクエストが、その複製物でない同じアプリケーションのインスタンスに送られるようにしたいときは、`call`に代えて`call!`を使ってください。 + +`call`についての詳細はRackの仕様書を参照してください。 + + +### ボディ、ステータスコードおよびヘッダの設定 + +ステータスコードおよびレスポンスボディを、ルーティングブロックの戻り値にセットすることが可能であり、これは推奨されています。しかし、あるケースでは実行フローの任意のタイミングでボディをセットしたくなるかもしれません。`body`ヘルパーメソッドを使えばそれができます。そうすると、それ以降、ボディにアクセスするためにそのメソッドを使うことができるようになります。 + +``` ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +また、`body`にはブロックを渡すことができ、これはRackハンドラにより実行されることになります(これはストリーミングを実装するのに使われます。"戻り値"の項を参照してください。) + +ボディと同様に、ステータスコードおよびヘッダもセットできます。 + +``` ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +引数を伴わない`body`、`headers`、`status`などは、それらの現在の値にアクセスするために使えます。 + +### ストリーミングレスポンス(Streaming Responses) + +レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。`stream`ヘルパーを使えば、独自ラッパーを作る必要はありません。 + +``` ruby +get '/' do + stream do |out| + out << "それは伝 -\n" + sleep 0.5 + out << " (少し待つ) \n" + sleep 1 + out << "- 説になる!\n" + end +end +``` + +これはストリーミングAPI、[Server Sent Events](http://dev.w3.org/html5/eventsource/)の実装を可能にし、[WebSockets](http://en.wikipedia.org/wiki/WebSocket)の土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。 + +ノート: ストリーミングの挙動、特に並行リクエスト(cuncurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。WEBRickを含むいくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディは`stream`に渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。 + +オプション引数が`keep_open`にセットされている場合、ストリームオブジェクト上で`close`は呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはThinやRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。 + +``` ruby +# ロングポーリング + +set :server, :thin +connections = [] + +get '/subscribe' do + # サーバイベントにおけるクライアントの関心を登録 + stream(:keep_open) do |out| + connections << out + # 死んでいるコネクションを排除 + connections.reject!(&:closed?) + end +end + +post '/message' do + connections.each do |out| + # クライアントへ新規メッセージ到着の通知 + out << params['message'] << "\n" + + # クライアントへの再接続の指示 + out.close + end + + # 肯定応答 + "message received" +end +``` + +### ロギング(Logging) + +リクエストスコープにおいて、`logger`ヘルパーは`Logger`インスタンスを作り出します。 + + +``` ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。 + +ノート: ロギングは、`Sinatra::Application`に対してのみデフォルトで有効にされているので、`Sinatra::Base`を継承している場合は、ユーザがこれを有効化する必要があります。 + +``` ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +ロギングミドルウェアが設定されてしまうのを避けるには、`logging`設定を`nil`にセットします。しかしこの場合、`logger`が`nil`を返すことを忘れないように。よくあるユースケースは、オリジナルのロガーをセットしたいときです。Sinatraは、とにかく`env['rack.logger']`で見つかるものを使います。 + +### MIMEタイプ(Mime Types) + +`send_file`か静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は `mime_type` を使ってファイル拡張子毎に登録して下さい。 + +``` ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +これは`content_type`ヘルパーで利用することができます: + +``` ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URLの生成 + +URLを生成するためには`url`ヘルパーメソッドが使えます。Hamlではこのようにします。 + +``` ruby +%a{:href => url('/foo')} foo +``` + +これはリバースプロキシおよびRackルーティングを、それらがあれば考慮に入れます。 + +このメソッドには`to`というエイリアスがあります(以下の例を参照)。 + +### ブラウザリダイレクト(Browser Redirect) + +`redirect` ヘルパーメソッドを使うことで、ブラウザをリダイレクトさせることができます。 + +``` ruby +get '/foo' do + redirect to('/bar') +end +``` + +他に追加されるパラメータは、`halt`に渡される引数と同様に取り扱われます。 + +``` ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'wrong place, buddy' +``` + +また、`redirect back`を使えば、簡単にユーザが来たページへ戻るリダイレクトを作れます。 + +``` ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +redirectに引数を渡すには、それをクエリーに追加するか、 + + +``` ruby +redirect to('/bar?sum=42') +``` + +または、セッションを使います。 + +``` ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### キャッシュ制御(Cache Control) + +ヘッダを正しく設定することが、適切なHTTPキャッシングのための基礎となります。 + +キャッシュ制御ヘッダ(Cache-Control header)は、次のように簡単に設定できます。 + +``` ruby +get '/' do + cache_control :public + "キャッシュしました!" +end +``` + +ヒント: キャッシングをbeforeフィルタ内で設定します。 + +``` ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires`ヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。 + +``` ruby +before do + expires 500, :public, :must_revalidate +end +``` + +キャッシュを適切に使うために、`etag`または`last_modified`を使うことを検討してください。これらのヘルパーを、重い仕事をさせる *前* に呼ぶことを推奨します。そうすれば、クライアントが既にキャッシュに最新版を持っている場合はレスポンスを直ちに破棄するようになります。 + +``` ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +また、[weak ETag](http://ja.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)を使うこともできます。 + +``` ruby +etag @article.sha1, :weak +``` + +これらのヘルパーは、キャッシングをしてくれませんが、必要な情報をキャッシュに与えてくれます。もし手早いリバースプロキシキャッシングの解決策をお探しなら、 [rack-cache](https://github.com/rtomayko/rack-cache)を試してください。 + + +``` ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +`:static_cache_control`設定(以下を参照)を、キャッシュ制御ヘッダ情報を静的ファイルに追加するために使ってください。 + +RFC 2616によれば、アプリケーションは、If-MatchまたはIf-None-Matchヘッダが`*`に設定されている場合には、要求されたリソースが既に存在するか否かに応じて、異なる振る舞いをすべきとなっています。Sinatraは、getのような安全なリクエストおよびputのような冪等なリクエストは既に存在しているものとして仮定し、一方で、他のリソース(例えば、postリクエスト)は新たなリソースとして取り扱われるよう仮定します。この振る舞いは、`:new_resource`オプションを渡すことで変更できます。 + +``` ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +ここでもWeak ETagを使いたい場合は、`:kind`オプションを渡してください。 + +``` ruby +etag '', :new_resource => true, :kind => :weak +``` + +### ファイルの送信 + +ファイルを送信するには、`send_file`ヘルパーメソッドを使います。 + +``` ruby +get '/' do + send_file 'foo.png' +end +``` + +これはオプションを取ることもできます。 + +``` ruby +send_file 'foo.png', :type => :jpg +``` + +オプション一覧 + +
+
filename
+
ファイル名。デフォルトは実際のファイル名。
+ +
last_modified
+
Last-Modifiedヘッダの値。デフォルトはファイルのmtime。
+ +
type
+
コンテンツの種類。設定がない場合、ファイル拡張子から類推される。
+ +
disposition
+
+ Content-Dispositionに使われる。許容値: nil (デフォルト)、 + :attachment および :inline +
+ +
length
+
Content-Lengthヘッダ。デフォルトはファイルサイズ。
+ +
status
+
+ 送られるステータスコード。静的ファイルをエラーページとして送るときに便利。 + + Rackハンドラでサポートされている場合は、Rubyプロセスからのストリーミング以外の手段が使われる。このヘルパーメソッドを使うと、Sinatraは自動で範囲リクエスト(range requests)を扱う。 +
+
+ + +### リクエストオブジェクトへのアクセス + +受信するリクエストオブジェクトは、`request`メソッドを通じてリクエストレベル(フィルタ、ルーティング、エラーハンドラ)からアクセスすることができます。 + +``` ruby +# アプリケーションが http://example.com/example で動作している場合 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # クライアントによって送信されたリクエストボディ(下記参照) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.bodyの長さ + request.media_type # request.bodyのメディアタイプ + request.host # "example.com" + request.get? # true (他の動詞にも同種メソッドあり) + request.form_data? # false + request["some_param"] # some_param変数の値。[]はパラメータハッシュのショートカット + request.referrer # クライアントのリファラまたは'/' + request.user_agent # ユーザエージェント (:agent 条件によって使用される) + request.cookies # ブラウザクッキーのハッシュ + request.xhr? # Ajaxリクエストかどうか + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # クライアントのIPアドレス + request.secure? # false (sslではtrueになる) + request.forwarded? # true (リバースプロキシの裏で動いている場合) + request.env # Rackによって渡された生のenvハッシュ +end +``` + +`script_name`や`path_info`などのオプションは次のように利用することもできます。 + +``` ruby +before { request.path_info = "/" } + +get "/" do + "全てのリクエストはここに来る" +end +``` + +`request.body`はIOまたはStringIOのオブジェクトです。 + +``` ruby +post "/api" do + request.body.rewind # 既に読まれているときのため + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### アタッチメント(Attachments) + +`attachment`ヘルパーを使って、レスポンスがブラウザに表示されるのではなく、ディスクに保存されることをブラウザに対し通知することができます。 + +``` ruby +get '/' do + attachment + "保存しました!" +end +``` + +ファイル名を渡すこともできます。 + +``` ruby +get '/' do + attachment "info.txt" + "保存しました!" +end +``` + +### 日付と時刻の取り扱い + +Sinatraは`time_for`ヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまた`DateTime`、`Date`および類似のクラスを変換できます。 + +``` ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "まだ時間がある" +end +``` + +このメソッドは、`expires`、`last_modified`といった種類のものの内部で使われています。そのため、アプリケーションにおいて、`time_for`をオーバーライドすることでそれらのメソッドの挙動を簡単に拡張できます。 + +``` ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### テンプレートファイルの探索 + +`find_template`ヘルパーは、レンダリングのためのテンプレートファイルを見つけるために使われます。 + +``` ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +この例はあまり有益ではありません。しかし、このメソッドを、独自の探索機構で働くようオーバーライドするなら有益になります。例えば、複数のビューディレクトリを使えるようにしたいときがあります。 + + +``` ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +他の例としては、異なるエンジン用の異なるディレクトリを使う場合です。 + +``` ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +これをエクステンションとして書いて、他の人と簡単に共有することもできます! + +ノート: `find_template`はファイルが実際に存在するかのチェックをしませんが、与えられたブロックをすべての可能なパスに対し呼び出します。これがパフォーマンス上の問題にはならないのは、`render`はファイルを見つけると直ちに`break`を使うからです。また、テンプレートの場所(および内容)は、developmentモードでの起動でない限りはキャッシュされます。このことは、複雑なメソッド(a really crazy method)を書いた場合は記憶しておく必要があります。 + +## コンフィギュレーション(Configuration) + +どの環境でも起動時に1回だけ実行されます。 + +``` ruby +configure do + # 1つのオプションをセット + set :option, 'value' + + # 複数のオプションをセット + set :a => 1, :b => 2 + + # `set :option, true`と同じ + enable :option + + # `set :option, false`と同じ + disable :option + + # ブロックを使って動的な設定をすることもできます。 + set(:css_dir) { File.join(views, 'css') } +end +``` + +環境設定(`RACK_ENV`環境変数)が`:production`に設定されている時だけ実行する方法: + +``` ruby +configure :production do + ... +end +``` + +環境設定が`:production`か`:test`に設定されている時だけ実行する方法: + +``` ruby +configure :production, :test do + ... +end +``` + +設定したオプションには`settings`からアクセスできます: + +``` ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 攻撃防御に対する設定 + +Sinatraは、[Rack::Protection](https://github.com/rkh/rack-protection#readme)を使って、アプリケーションを多発する日和見的攻撃から守っています。この挙動は簡単に無効化できます(これはアプリケーションを大量の脆弱性攻撃に晒すことになります)。 + +``` ruby +disable :protection +``` + +単一の防御層を外すためには、`protection`をオプションハッシュにセットします。 + +``` ruby +set :protection, :except => :path_traversal +``` +また配列を渡して、複数の防御を無効にすることもできます。 + +``` ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +デフォルトでSinatraは、`:sessions`が有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、`:session`オプションを渡すことにより、セッションベースの防御を設定することができます。 + +``` ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 利用可能な設定 + +
+
absolute_redirects
+
+ 無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。 +
+
+ アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。 +
+
デフォルトは無効。
+ +
add_charset
+
+ Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。 + settings.add_charset << "application/foobar" +
+ +
app_file
+
+ メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。 +
+ +
bind
+
バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
+ +
default_encoding
+
不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
+ +
dump_errors
+
ログにおけるエラーの表示。
+ +
environment
+
+ 現在の環境。デフォルトはENV['RACK_ENV']、それが無い場合は"development"。 +
+ +
logging
+
ロガーの使用。
+ +
lock
+
+ 各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。 +
+
アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
+ +
method_override
+
+ put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。 +
+ +
port
+
待ち受けポート。ビルトインサーバのみで有効。
+ +
prefixed_redirects
+
+ 絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。 +
+ +
protection
+
Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
+ +
public_dir
+
public_folderのエイリアス。以下を参照。
+ +
public_folder
+
+ publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。 +
+ +
reload_templates
+
+ リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。 +
+ +
root
+
+ プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。 +
+ +
raise_errors
+
+ 例外発生の設定(アプリケーションは止まる)。environment"test"に設定されているときはデフォルトは有効。それ以外は無効。 +
+ +
run
+
+ 有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。 +
+ +
running
+
ビルトインサーバが稼働中か?この設定を変更しないこと!
+ +
server
+
+ ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。 +
+ +
sessions
+
+ Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。 +
+ +
show_exceptions
+
+ 例外発生時にブラウザにスタックトレースを表示する。environment"development"に設定されているときは、デフォルトで有効。それ以外は無効。 +
+
+ また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。 +
+ +
static
+
Sinatraが静的ファイルの提供を取り扱うかの設定。
+
その取り扱いができるサーバを使う場合は無効。
+
無効化でパフォーマンスは改善する
+
+ クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。 +
+ +
static_cache_control
+
+ Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。 +
+
+ 複数の値をセットするときは明示的に配列を使う: + set :static_cache_control, [:public, :max_age => 300] +
+ +
threaded
+
+ trueに設定されているときは、Thinにリクエストを処理するためにEventMachine.deferを使うことを通知する。 +
+ +
views
+
+ ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。 +
+ +
x_cascade
+
+ マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue。 +
+
+ +## 環境設定(Environments) + +3種類の既定環境、`"development"`、`"production"`および`"test"`があります。環境は、`RACK_ENV`環境変数を通して設定できます。デフォルト値は、`"development"`です。`"development"`環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別の`not_found`および`error`ハンドラがブラウザにスタックトレースを表示します。`"production"`および`"test"`環境においては、テンプレートはデフォルトでキャッシュされます。 + +異なる環境を走らせるには、`RACK_ENV`環境変数を設定します。 + +``` shell +RACK_ENV=production ruby my_app.rb +``` + +既定メソッド、`development?`、`test?`および`production?`を、現在の環境設定を確認するために使えます。 + +``` ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## エラーハンドリング(Error Handling) + +エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、`haml`、`erb`、`halt`といった便利なものが全て使えることを意味します。 + +### 未検出(Not Found) + +`Sinatra::NotFound`例外が発生したとき、またはレスポンスのステータスコードが404のときに、`not_found`ハンドラが発動します。 + +``` ruby +not_found do + 'ファイルが存在しません' +end +``` + +### エラー(Error) + +`error`ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。例外オブジェクトはRack変数`sinatra.error`から取得できます。 + +``` ruby +error do + 'エラーが発生しました。 - ' + env['sinatra.error'].message +end +``` + +エラーをカスタマイズする場合は、 + +``` ruby +error MyCustomError do + 'エラーメッセージ...' + env['sinatra.error'].message +end +``` + +と書いておいて、下記のように呼び出します。 + +``` ruby +get '/' do + raise MyCustomError, '何かがまずかったようです' +end +``` + +そうするとこうなります。 + +``` +エラーメッセージ... 何かがまずかったようです +``` + +あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。 + +``` ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +範囲指定もできます。 + +``` ruby +error 400..510 do + 'Boom' +end +``` + +Sinatraを開発環境の下で実行している場合は、特別な`not_found`および`error`ハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。 + + +## Rackミドルウェア(Rack Middleware) + +SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースである[Rack](http://rack.github.io/)上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。 + +Sinatraはトップレベルの`use`メソッドを通して、Rackミドルウェアパイプラインの構築を楽にします。 + +``` ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`の文法は、[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder)DSLで定義されているそれ(rackupファイルで最もよく使われる)と同じです。例えば `use`メソッドは複数の引数、そしてブロックも取ることができます。 + +``` ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rackは、ロギング、デバッギング、URLルーティング、認証、セッション管理など、多様な標準的ミドルウェアを共に配布されています。Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、通常、それらを`use`で明示的に指定する必要はありません。 + +便利なミドルウェアを以下で見つけられます。 + +[rack](https://github.com/rack/rack/tree/master/lib/rack)、 +[rack-contrib](https://github.com/rack/rack-contrib#readm)、 +または[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)。 + +## テスト(Testing) + +SinatraでのテストはRackベースのテストライブラリまたはフレームワークを使って書くことができます。[Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames)をお薦めします。 + +``` ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Songbirdを使ってます!", last_response.body + end +end +``` + +ノート: モジュラースタイルでSinatraを使う場合は、上記`Sinatra::Application`をアプリケーションのクラス名に置き換えてください。 + +## Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ + +軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、`./public`および`./views`ディレクトリ、ロギング、例外詳細ページなど)を仮定しています。そこで`Sinatra::Base`の出番です。 + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base`のサブクラスで利用できるメソッドは、トップレベルDSLで利用できるものと全く同じです。ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することで`Sinatra::Base`コンポーネントに変えることができます。 + +* `sinatra`の代わりに`sinatra/base`を読み込む + (そうしない場合、SinatraのDSLメソッドの全てがmainの名前空間にインポートされます) +* ルーティング、エラーハンドラ、フィルタ、オプションを`Sinatra::Base`のサブクラスに書く + +`Sinatra::Base`はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルトで無効になっています。利用可能なオプションとその挙動の詳細については[Configuring Settings](http://sinatra.github.com/configuration.html)(英語)をご覧下さい。 + +もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、`Sinatra::Application`をサブクラス化させてください。 + +```ruby +require "sinatra/base" + +class MyApp < Sinatra::Application + get "/" do + 'Hello world!' + end +end +``` + +### モジュラースタイル vs クラッシックスタイル + +一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。それがそのアプリケーションに合っているのであれば、モジュラーアプリケーションに移行する必要はありません。 + +モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。複数が必要な場合はモジュラースタイルに移行してください。モジュラースタイルとクラッシックスタイルを混合できないということはありません。 + +一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
設定クラッシックモジュラーモジュラー
app_filesinatraを読み込むファイルSinatra::Baseをサブクラス化したファイルSinatra::Applicationをサブクラス化したファイル
run$0 == app_filefalsefalse
loggingtruefalsetrue
method_overridetruefalsetrue
inline_templatestruefalsetrue
statictruefalsetrue
+ +### モジュラーアプリケーションの提供 + +モジュラーアプリケーションを開始、つまり`run!`を使って開始させる二種類のやり方があります。 + +``` ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... アプリケーションのコードを書く ... + + # Rubyファイルが直接実行されたらサーバを立ち上げる + run! if app_file == $0 +end +``` + +として、次のように起動するか、 + +``` shell +ruby my_app.rb +``` + +または、Rackハンドラを使えるようにする`config.ru`ファイルを書いて、 + +``` ruby +# config.ru (rackupで起動) +require './my_app' +run MyApp +``` + +起動します。 + +``` shell +rackup -p 4567 +``` + +### config.ruを用いたクラッシックスタイルアプリケーションの使用 + +アプリケーションファイルと、 + +``` ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +対応する`config.ru`を書きます。 + +``` ruby +require './app' +run Sinatra::Application +``` + +### config.ruはいつ使うのか? + +`config.ru`ファイルは、以下の場合に適しています。 + +* 異なるRackハンドラ(Passenger, Unicorn, Herokuなど)でデプロイしたいとき +* `Sinatra::Base`の複数のサブクラスを使いたいとき +* Sinatraをミドルウェアとして利用し、エンドポイントとしては利用しないとき + +**モジュラースタイルに移行したという理由だけで、`config.ru`に移行する必要はなく、`config.ru`で起動するためにモジュラースタイルを使う必要はありません。** + +### Sinatraのミドルウェアとしての利用 + +Sinatraは他のRackミドルウェアを利用することができるだけでなく、 +全てのSinatraアプリケーションは、それ自体ミドルウェアとして別のRackエンドポイントの前に追加することが可能です。 + +このエンドポイントには、別のSinatraアプリケーションまたは他のRackベースのアプリケーション(Rails/Ramaze/Camping/…)が用いられるでしょう。 + +``` ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] = 'admin' and params['password'] = 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # ミドルウェアはbeforeフィルタの前に実行される + use LoginScreen + + before do + unless session['user_name'] + halt "アクセスは拒否されました。ログインしてください。" + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 動的なアプリケーションの生成 + +新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。`Sinatra.new`を使えばそれができます。 + +``` ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +これは省略できる引数として、それが継承するアプリケーションを取ります。 + +```ruby +# config.ru (rackupで起動) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +これは特にSinatraのextensionをテストするときや、Sinatraを自身のライブラリで利用する場合に役立ちます。 + +これはまた、Sinatraをミドルウェアとして利用することを極めて簡単にします。 + +``` ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## スコープとバインディング(Scopes and Binding) + +現在のスコープはどのメソッドや変数が利用可能かを決定します。 + +### アプリケーション/クラスのスコープ + +全てのSinatraアプリケーションはSinatra::Baseのサブクラスに相当します。 +もしトップレベルDSLを利用しているならば(`require 'sinatra'`)このクラスはSinatra::Applicationであり、 +そうでなければ、あなたが明示的に作成したサブクラスです。 +クラスレベルでは`get`や`before`のようなメソッドを持っています。 +しかし`request`や`session`オブジェクトには、全てのリクエストに対する単一のアプリケーションクラスがあるだけなので、アクセスできません。 + +`set`によって作られたオプションはクラスレベルのメソッドです。 + +``` ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + set :foo, 42 + foo # => 42 + + get '/foo' do + # もうアプリケーションスコープの中にいないよ! + end +end +``` + +次の場所ではアプリケーションスコープバインディングを持ちます。 + +* アプリケーションクラス本体 +* 拡張によって定義されたメソッド +* `helpers`に渡されたブロック +* `set`の値として使われるProcまたはブロック +* `Sinatra.new`に渡されたブロック + +このスコープオブジェクト(クラス)は次のように利用できます。 + +* configureブロックに渡されたオブジェクト経由(`configure { |c| ... }`) +* リクエストスコープの中での`settings` + +### リクエスト/インスタンスのスコープ + +やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 +このスコープの内側からは`request`や`session`オブジェクトにアクセスすることができ、`erb`や`haml`のようなレンダリングメソッドを呼び出すことができます。 +リクエストスコープの内側からは、`settings`ヘルパーによってアプリケーションスコープにアクセスすることができます。 + +``` ruby +class MyApp < Sinatra::Base + # アプリケーションスコープの中だよ! + get '/define_route/:name' do + # '/define_route/:name'のためのリクエストスコープ + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"のためのリクエストスコープ + @value # => nil (not the same request) + end + + "ルーティングが定義された!" + end +end +``` + +次の場所ではリクエストスコープバインディングを持ちます。 + +* get/head/post/put/delete/options/patch/link/unlink ブロック +* before/after フィルタ +* helper メソッド +* テンプレート/ビュー + +### デリゲートスコープ + +デリゲートスコープは、単にクラススコープにメソッドを転送します。 +しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません。 +委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注: +異なった`self`を持っています)。 +`Sinatra::Delegator.delegate :method_name`を呼び出すことによってデリゲートするメソッドを明示的に追加することができます。 + +次の場所ではデリゲートスコープを持ちます。 + +* もし`require "sinatra"`しているならば、トップレベルバインディング +* `Sinatra::Delegator` mixinでextendされたオブジェクト + +コードをご覧ください: ここでは [Sinatra::Delegator +mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)は[mainオブジェクトにextendされています](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。 + +## コマンドライン + +Sinatraアプリケーションは直接実行できます。 + +``` shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +オプション: + +``` +-h # ヘルプ +-p # ポート指定(デフォルトは4567) +-o # ホスト指定(デフォルトは0.0.0.0) +-e # 環境を指定 (デフォルトはdevelopment) +-s # rackserver/handlerを指定 (デフォルトはthin) +-x # mutex lockを付ける (デフォルトはoff) +``` + +## 必要環境 + +次のRubyバージョンが公式にサポートされています。 + +
+
Ruby 1.8.7
+
+ 1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。 +
+ +
Ruby 1.9.2
+
+ 1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。 +
+ +
Ruby 1.9.3
+
+ 1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。 +
+ +
Ruby 2.0.0
+
+ 2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。 +
+ +
Rubinius
+
+ Rubiniusは公式にサポートされています(Rubinius >= 2.x)。 + gem install pumaすることが推奨されています。 +
+ +
JRuby
+
+ JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。 + gem install trinidadすることが推奨されています。 +
+
+ +開発チームは常に最新となるRubyバージョンに注視しています。 + +次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。 + +* JRubyとRubiniusの古いバージョン +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません) + +公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。 + +開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。 + +Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。 + +MacRubyを使う場合は、`gem install control_tower`してください。 + +Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。 + +## 最新開発版 + +Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、 + +``` shell +gem install sinatra --pre +``` + +すれば、最新の機能のいくつかを利用できます。 + +### Bundlerを使う場合 + +最新のSinatraでアプリケーションを動作させたい場合には、[Bundler](http://gembundler.com/)を使うのがお薦めのやり方です。 + +まず、Bundlerがなければそれをインストールします。 + +``` shell +gem install bundler +``` + +そして、プロジェクトのディレクトリで、`Gemfile`を作ります。 + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 他の依存ライブラリ +gem 'haml' # Hamlを使う場合 +gem 'activerecord', '~> 3.0' # ActiveRecord 3.xが必要かもしれません +``` + +ノート: `Gemfile`にアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。 + +これで、以下のようにしてアプリケーションを起動することができます。 + +``` shell +bundle exec ruby myapp.rb +``` + +### 直接組み込む場合 + +ローカルにクローンを作って、`sinatra/lib`ディレクトリを`$LOAD_PATH`に追加してアプリケーションを起動します。 + +``` shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +追ってSinatraのソースを更新する方法。 + +``` shell +cd myapp/sinatra +git pull +``` + +### グローバル環境にインストールする場合 + +Sinatraのgemを自身でビルドすることもできます。 + +``` shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +gemをルートとしてインストールする場合は、最後のステップはこうなります。 + +``` shell +sudo rake install +``` + +## バージョニング(Versioning) + +Sinatraは、[Semantic Versioning](http://semver.org/)におけるSemVerおよびSemVerTagの両方に準拠しています。 + +## 参考文献 + +* [プロジェクトサイト](http://sinatra.github.com/) - ドキュメント、ニュース、他のリソースへのリンクがあります。 +* [プロジェクトに参加(貢献)する](http://sinatra.github.com/contributing.html) - バグレポート パッチの送信、サポートなど +* [Issue tracker](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [メーリングリスト](http://groups.google.com/group/sinatrarb/topics) +* http://freenode.net上のIRC: [#sinatra](irc://chat.freenode.net/#sinatra) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) クックブック、チュートリアル +* [Sinatra Recipes](http://recipes.sinatrarb.com/) コミュニティによるレシピ集 +* http://rubydoc.info上のAPIドキュメント: [最新版(latest release)用](http://rubydoc.info/gems/sinatra)または[現在のHEAD用](http://rubydoc.info/github/sinatra/sinatra) +* [CIサーバ](http://travis-ci.org/sinatra/sinatra) +* [Greenbear Laboratory Rack日本語マニュアル](http://route477.net/w/RackReferenceJa.html) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ko.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ko.md new file mode 100644 index 000000000..b1f859202 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ko.md @@ -0,0 +1,2928 @@ +# Sinatra + +*주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있습니다.* + +Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게 +해 주는 [DSL](http://en.wikipedia.org/wiki/Domain-specific_language)입니다. + +``` ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +젬을 설치합니다. + +``` shell +gem install sinatra +``` + +실행합니다. + +``` shell +ruby myapp.rb +``` + +`http://localhost:4567`를 확인해 보세요. + +`gem install thin`도 함께 실행하기를 권장합니다. +thin이 설치되어 있을 경우 Sinatra는 thin을 통해 실행합니다. + +## 목차 + +* [Sinatra](#sinatra) + * [목차](#목차) + * [라우터(Routes)](#라우터routes) + * [조건(Conditions)](#조건conditions) + * [반환값(Return Values)](#반환값return-values) + * [커스텀 라우터 매처(Custom Route Matchers)](#커스텀-라우터-매처custom-route-matchers) + * [정적 파일(Static Files)](#정적-파일static-files) + * [뷰 / 템플릿(Views / Templates)](#뷰--템플릿views--templates) + * [리터럴 템플릿(Literal Templates)](#리터럴-템플릿literal-templates) + * [가능한 템플릿 언어들(Available Template Languages)](#가능한-템플릿-언어들available-template-languages) + * [Haml 템플릿](#haml-템플릿) + * [Erb 템플릿](#erb-템플릿) + * [Builder 템플릿](#builder-템플릿) + * [Nokogiri 템플릿](#nokogiri-템플릿) + * [Sass 템플릿](#sass-템플릿) + * [SCSS 템플릿](#scss-템플릿) + * [Less 템플릿](#less-템플릿) + * [Liquid 템플릿](#liquid-템플릿) + * [Markdown 템플릿](#markdown-템플릿) + * [Textile 템플릿](#textile-템플릿) + * [RDoc 템플릿](#rdoc-템플릿) + * [AsciiDoc 템플릿](#asciidoc-템플릿) + * [Radius 템플릿](#radius-템플릿) + * [Markaby 템플릿](#markaby-템플릿) + * [RABL 템플릿](#rabl-템플릿) + * [Slim 템플릿](#slim-템플릿) + * [Creole 템플릿](#creole-템플릿) + * [MediaWiki 템플릿](#mediawiki-템플릿) + * [CoffeeScript 템플릿](#coffeescript-템플릿) + * [Stylus 템플릿](#stylus-템플릿) + * [Yajl 템플릿](#yajl-템플릿) + * [WLang 템플릿](#wlang-템플릿) + * [템플릿에서 변수에 접근하기](#템플릿에서-변수에-접근하기) + * [템플릿에서의 `yield` 와 중첩 레이아웃](#템플릿에서의-yield-와-중첩-레이아웃) + * [인라인 템플릿](#인라인-템플릿) + * [이름을 가지는 템플릿(Named Templates)](#이름을-가지는-템플릿named-templates) + * [파일 확장자 연결하기](#파일-확장자-연결하기) + * [나만의 고유한 템플릿 엔진 추가하기](#나만의-고유한-템플릿-엔진-추가하기) + * [템플릿 검사를 위한 커스텀 로직 사용하기](#템플릿-검사를-위한-커스텀-로직-사용하기) + * [필터(Filters)](#필터filters) + * [헬퍼(Helpers)](#헬퍼helpers) + * [세션(Sessions) 사용하기](#세션sessions-사용하기) + * [중단하기(Halting)](#중단하기halting) + * [넘기기(Passing)](#넘기기passing) + * [다른 라우터 부르기(Triggering Another Route)](#다른-라우터-부르기triggering-another-route) + * [본문, 상태 코드 및 헤더 설정하기](#본문-상태-코드-및-헤더-설정하기) + * [응답 스트리밍(Streaming Responses)](#응답-스트리밍streaming-responses) + * [로깅(Logging)](#로깅logging) + * [마임 타입(Mime Types)](#마임-타입mime-types) + * [URL 생성하기](#url-생성하기) + * [브라우저 재지정(Browser Redirect)](#브라우저-재지정browser-redirect) + * [캐시 컨트롤(Cache Control)](#캐시-컨트롤cache-control) + * [파일 전송하기(Sending Files)](#파일-전송하기sending-files) + * [요청 객체에 접근하기(Accessing the Request Object)](#요청-객체에-접근하기accessing-the-request-object) + * [첨부(Attachments)](#첨부attachments) + * [날짜와 시간 다루기](#날짜와-시간-다루기) + * [템플릿 파일 참조하기](#템플릿-파일-참조하기) + * [설정(Configuration)](#설정configuration) + * [공격 방어 설정하기(Configuring attack protection)](#공격-방어-설정하기configuring-attack-protection) + * [가능한 설정들(Available Settings)](#가능한-설정들available-settings) + * [환경(Environments)](#환경environments) + * [에러 처리(Error Handling)](#에러-처리error-handling) + * [찾을 수 없음(Not Found)](#찾을-수-없음not-found) + * [에러](#에러) + * [Rack 미들웨어(Rack Middleware)](#rack-미들웨어rack-middleware) + * [테스팅(Testing)](#테스팅testing) + * [Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps)](#sinatrabase---미들웨어middleware-라이브러리libraries-그리고-모듈-앱modular-apps) + * [모듈(Modular) vs. 전통적 방식(Classic Style)](#모듈modular-vs-전통적-방식classic-style) + * [모듈 애플리케이션(Modular Application) 제공하기](#모듈-애플리케이션modular-application-제공하기) + * [config.ru로 전통적 방식의 애플리케이션 사용하기](#configru로-전통적-방식의-애플리케이션-사용하기) + * [언제 config.ru를 사용할까?](#언제-configru를-사용할까) + * [Sinatra를 미들웨어로 사용하기](#sinatra를-미들웨어로-사용하기) + * [동적인 애플리케이션 생성(Dynamic Application Creation)](#동적인-애플리케이션-생성dynamic-application-creation) + * [범위(Scopes)와 바인딩(Binding)](#범위scopes와-바인딩binding) + * [애플리케이션/클래스 범위](#애플리케이션클래스-범위) + * [요청/인스턴스 범위](#요청인스턴스-범위) + * [위임 범위(Delegation Scope)](#위임-범위delegation-scope) + * [명령행(Command Line)](#명령행command-line) + * [요구사항(Requirement)](#요구사항requirement) + * [최신(The Bleeding Edge)](#최신the-bleeding-edge) + * [Bundler를 사용하여](#bundler를-사용하여) + * [직접 하기(Roll Your Own)](#직접-하기roll-your-own) + * [전역으로 설치(Install Globally)](#전역으로-설치install-globally) + * [버저닝(Versioning)](#버저닝versioning) + * [더 읽을 거리(Further Reading)](#더-읽을-거리further-reading) + +## 라우터(Routes) + +Sinatra에서, 라우터(route)는 URL-매칭 패턴과 쌍을 이루는 HTTP 메서드입니다. +각각의 라우터는 블록과 연결됩니다. + +``` ruby +get '/' do + .. 무언가 보여주기(show) .. +end + +post '/' do + .. 무언가 만들기(create) .. +end + +put '/' do + .. 무언가 대체하기(replace) .. +end + +patch '/' do + .. 무언가 수정하기(modify) .. +end + +delete '/' do + .. 무언가 없애기(annihilate) .. +end + +options '/' do + .. 무언가 주기(appease) .. +end + +link '/' do + .. 무언가 관계맺기(affiliate) .. +end + +unlink '/' do + .. 무언가 격리하기(separate) .. +end +``` + +라우터는 정의된 순서에 따라 매치되고 요청에 대해 가장 먼저 매칭된 라우터가 호출됩니다. + +라우터 패턴에는 이름을 가진 매개변수가 포함될 수 있으며, `params` 해시로 접근할 수 있습니다. + +``` ruby +get '/hello/:name' do + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + "Hello #{params['name']}!" +end +``` + +또한 블록 매개변수를 통하여도 이름을 가진 매개변수에 접근할 수 있습니다. + +``` ruby +get '/hello/:name' do |n| + # "GET /hello/foo" 및 "GET /hello/bar"와 매치 + # params['name']은 'foo' 또는 'bar' + # n 에는 params['name']가 저장 + "Hello #{n}!" +end +``` + +라우터 패턴에는 스플랫(splat, 또는 와일드카드)도 매개변수도 포함될 수 있으며, 이럴 경우 `params['splat']` 배열을 통해 접근할 수 있습니다. + +``` ruby +get '/say/*/to/*' do + # /say/hello/to/world와 매치 + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # /download/path/to/file.xml과 매치 + params['splat'] # => ["path/to/file", "xml"] +end +``` + +블록 매개변수로도 접근할 수 있습니다. + +``` ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +라우터는 정규표현식으로 매치할 수 있습니다. + +``` ruby +get /\A\/hello\/([\w]+)\z/ do + "Hello, #{params['captures'].first}!" +end +``` + +블록 매개변수로도 사용가능합니다. + +``` ruby +get %r{/hello/([\w]+)} do |c| + # "GET /meta/hello/world", "GET /hello/world/1234" 등과 매치 + "Hello, #{c}!" +end +``` + +라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있습니다. + +``` ruby +get '/posts.?:format?' do + # "GET /posts" 는 물론 "GET /posts.json", "GET /posts.xml" 와 같은 어떤 확장자와도 매칭 +end +``` + +쿼리 파라메터로도 이용가능 합니다. + +``` ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +한편, 경로 탐색 공격 방지(path traversal attack protection, 아래 참조)를 비활성화시키지 않았다면, +요청 경로는 라우터와 매칭되기 이전에 수정될 수 있습니다. + +### 조건(Conditions) + +라우터는 사용자 에이전트(user agent)같은 다양한 매칭 조건을 포함할 수 있습니다. + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Songbird 버전 #{params['agent'][0]}을 사용하는군요!" +end + +get '/foo' do + # songbird 브라우저가 아닌 경우 매치 +end +``` + +다른 가능한 조건에는 `host_name`과 `provides`가 있습니다. + +``` ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides`는 request의 허용된 해더를 검색합니다. + +사용자 정의 조건도 쉽게 만들 수 있습니다. + +``` ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "내가 졌소!" +end + +get '/win_a_car' do + "미안해서 어쩌나." +end +``` + +여러 값을 받는 조건에는 스플랫(splat)을 사용합니다. + +``` ruby +set(:auth) do |*roles| # <- 이게 스플랫 + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "내 계정 정보" +end + +get "/only/admin/", :auth => :admin do + "관리자 외 접근불가!" +end +``` + +### 반환값(Return Values) + +라우터 블록의 반환 값은 HTTP 클라이언트로 전달되는 응답 본문만을 결정하거나, 또는 Rack 스택에서 다음 번 미들웨어만를 결정합니다. +위의 예제에서 볼 수 있지만 대부분의 경우 이 반환값은 문자열입니다.하지만 다른 값도 허용됩니다. + +유효한 Rack 응답, Rack 본문 객체 또는 HTTP 상태 코드가 되는 어떠한 객체라도 반환할 수 있습니다. + +* 세 요소를 가진 배열: `[상태 (Fixnum), 헤더 (Hash), 응답 본문 (#each에 반응)]` +* 두 요소를 가진 배열: `[상태 (Fixnum), 응답 본문 (#each에 반응)]` +* `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체 +* 상태 코드를 의미하는 Fixnum + +이것을 이용한 예를 들자면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있습니다. + +``` ruby +class Stream + def each +100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +`stream` 헬퍼 메서드(아래 참조)를 사용하면 이런 번거로움을 줄이고 스트리밍 로직을 라우터 속에 포함 시킬 수도 있습니다. + +### 커스텀 라우터 매처(Custom Route Matchers) + +위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있습니다. +하지만, 그게 끝은 아닙니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있습니다. + +``` ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +사실 위의 예제는 조금 과하게 작성된 면이 있습니다. 간단하게 표현할 수도 있어요. + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +또는 거꾸로 탐색(negative look ahead)할 수도 있습니다. + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## 정적 파일(Static Files) + +정적 파일들은 `./public` 디렉터리에서 제공됩니다. 위치를 다른 곳으로 +변경하려면 `:public_folder` 옵션을 지정하면 됩니다. + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +public 디렉터리명은 URL에 포함되지 않는다는 점에 주의하세요. +`./public/css/style.css` 파일은 아마 `http://example.com/css/style.css` 로 접근할 수 있을 것 입니다. + +`Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하면 됩니다. + +## 뷰 / 템플릿(Views / Templates) + +템플릿 언어들은 각각의 렌더링 메서드를 통해 표출됩니다. +이들 메서드는 문자열을 반환할 뿐입니다. + +``` ruby +get '/' do + erb :index +end +``` + +이 구문은 `views/index.erb`를 렌더합니다. + +템플릿 이름 대신 템플릿의 내용을 직접 넘길 수도 있습니다. + +``` ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +템플릿은 두 번째 인자로 옵션값의 해시를 받습니다. + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +이 구문은 `views/post.erb` 속에 내장된 `views/index.erb`를 렌더합니다. +(`views/layout.erb`파일이 존재할 경우 기본값은 `views/layout.erb`입니다.) + +Sinatra가 이해하지 못하는 모든 옵션값들은 템플릿 엔진으로 전달됩니다. + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +옵션값은 템플릿 언어별로 전역적으로 설정할 수도 있습니다. + +``` ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +render 메서드에서 전달된 옵션값들은 `set`을 통해 설정한 옵션값보다 우선합니다. + +가능한 옵션값들은 다음과 같습니다. + +
+
locals
+
+ 문서로 전달되는 local 목록. 파셜과 함께 사용하기 좋음. + 예제: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ 불확실한 경우에 사용할 문자열 인코딩. + 기본값은 settings.default_encoding. +
+ +
views
+
+ 템플릿을 로드할 뷰 폴더. + 기본값은 settings.views. +
+ +
layout
+
+ 레이아웃을 사용할지 여부 (true 또는 false), 만약 + 이 값이 심볼일 경우, 사용할 템플릿을 지정. 예제: + erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ 템플릿이 생성하는 Content-Type, 기본값은 템플릿 언어에 의존. +
+ +
scope
+
+ 템플릿을 렌더링하는 범위. 기본값은 어플리케이션 인스턴스. + 만약 이 값을 변경하면, 인스턴스 변수와 헬퍼 메서드들을 사용할 수 없게 됨. +
+ +
layout_engine
+
+ 레이아웃 렌더링에 사용할 템플릿 엔진. 레이아웃을 지원하지 않는 언어인 경우에 유용. + 기본값은 템플릿에서 사용하는 엔진. 예제: set :rdoc, :layout_engine => :erb +
+
+ + +템플릿은 `./views` 디렉터리에 있는 것으로 가정됩니다. 뷰 디렉터리를 +다른 곳으로 하고 싶으시면 이렇게 하세요. + +``` ruby +set :views, settings.root + '/templates' +``` + +템플릿은 언제나 심볼로 참조되어야 한다는 것에 주의하세요. +템플릿이 하위 디렉터리에 위치한 경우(그럴 경우에는 `:'subdir/template'`을 +사용)에도 예외는 없습니다. 반드시 심볼이어야 하는 이유는, 문자열을 넘기면 +렌더링 메서드가 전달된 문자열을 직접 렌더하기 때문입니다. + +### 리터럴 템플릿(Literal Templates) + +``` ruby +get '/' do + haml '%div.title Hello World' +end +``` + +템플릿 문자열을 렌더합니다. + +### 가능한 템플릿 언어들(Available Template Languages) + +일부 언어는 여러 개의 구현이 있습니다. (스레드에 안전하게 thread-safe) 어느 구현을 +사용할지 저정하려면, 먼저 require 하기만 하면 됩니다. + +``` ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml 템플릿 + + + + + + + + + + + + + + +
의존성haml
파일 확장자.haml
예제haml :index, :format => :html5
+ +#### Erb 템플릿 + + + + + + + + + + + + + + +
의존성erubis 또는 erb (루비 속에 포함)
파일 확장자.erb, .rhtml, .erubis (Erubis만 해당)
예제erb :index
+ +#### Builder 템플릿 + + + + + + + + + + + + + + +
의존성builder
파일 확장자.builder
예제builder { |xml| xml.em "hi" }
+ +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Nokogiri 템플릿 + + + + + + + + + + + + + + +
의존성nokogiri
파일 확장자.nokogiri
예제nokogiri { |xml| xml.em "hi" }
+ +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### Sass 템플릿 + + + + + + + + + + + + + + +
의존성sass
파일 확장자.sass
예제sass :stylesheet, :style => :expanded
+ +#### SCSS 템플릿 + + + + + + + + + + + + + + +
의존성sass
파일 확장자.scss
예제scss :stylesheet, :style => :expanded
+ +#### Less 템플릿 + + + + + + + + + + + + + + +
의존성less
파일 확장자.less
예제less :stylesheet
+ +#### Liquid 템플릿 + + + + + + + + + + + + + + +
의존성liquid
파일 확장자.liquid
예제liquid :index, :locals => { :key => 'value' }
+ +Liquid 템플릿에서는 루비 메서드(`yield` 제외)를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markdown 템플릿 + + + + + + + + + + + + + + +
의존성 + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku + 중 아무거나 +
파일 확장.markdown, .mkd, .md
예제markdown :index, :layout_engine => :erb
+ +Markdown에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +``` ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +다른 템플릿 속에서 `markdown` 메서드를 호출할 수도 있습니다. + +``` ruby +%h1 안녕 Haml! +%p= markdown(:greetings) +``` + +Markdown에서 루비를 호출할 수 없기 때문에, Markdown으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### Textile 템플릿 + + + + + + + + + + + + + + +
의존성RedCloth
파일 확장자.textile
예제textile :index, :layout_engine => :erb
+ +Textile에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +``` ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +다른 템플릿 속에서 `textile` 메서드를 호출할 수도 있습니다. + +``` ruby +%h1 안녕 Haml! +%p= textile(:greetings) +``` + +Textile에서 루비를 호출할 수 없기 때문에, Textile으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### RDoc 템플릿 + + + + + + + + + + + + + + +
의존성rdoc
파일 확장자.rdoc
예제rdoc :README, :layout_engine => :erb
+ +RDoc에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +``` ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +다른 템플릿 속에서 `rdoc` 메서드를 호출할 수도 있습니다. + +``` ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +RDoc에서 루비를 호출할 수 없기 때문에, RDoc으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### AsciiDoc 템플릿 + + + + + + + + + + + + + + +
의존성Asciidoctor
파일 확장자.asciidoc, .adoc and .ad
예제asciidoc :README, :layout_engine => :erb
+ +AsciiDoc 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Radius 템플릿 + + + + + + + + + + + + + + +
의존성radius
파일 확장자.radius
예제radius :index, :locals => { :key => 'value' }
+ +Radius 템플릿에서는 루비 메서드를 호출할 수 없기 +때문에, 거의 대부분의 경우 locals를 전달해야 합니다. + +#### Markaby 템플릿 + + + + + + + + + + + + + + +
의존성markaby
파일확장.mab
예제markaby { h1 "Welcome!" }
+ +인라인 템플릿으로 블록을 받을 수도 있습니다(예제 참조). + +#### RABL 템플릿 + + + + + + + + + + + + + + +
의존성rabl
파일 확장자.rabl
예제rabl :index
+ +#### Slim 템플릿 + + + + + + + + + + + + + + +
의존성slim
파일 확장자.slim
예제slim :index
+ +#### Creole 템플릿 + + + + + + + + + + + + + + +
의존성creole
파일 확장자.creole
예제creole :wiki, :layout_engine => :erb
+ +Creole에서는 메서드 호출 뿐 아니라 locals 전달도 안됩니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +``` ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +다른 템플릿 속에서 `creole` 메서드를 호출할 수도 있습니다. + +``` ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Creole에서 루비를 호출할 수 없기 때문에, Creole으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### MediaWiki 템플릿 + + + + + + + + + + + + + + +
의존성WikiCloth
파일 확장자.mediawiki and .mw
예제mediawiki :wiki, :layout_engine => :erb
+ +MediaWiki 마크업에서는 메서드 호출 뿐 아니라 locals 전달도 불가능합니다. +따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다. + +``` ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +다른 템플릿 속에서 `mediawiki` 메서드를 호출할 수도 있습니다. + +``` ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +MediaWiki에서 루비를 호출할 수 없기 때문에, MediaWiki으로 작성된 레이아웃은 +사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을 +다른 렌더링 엔진으로 렌더링 할 수는 있습니다. + +#### CoffeeScript 템플릿 + + + + + + + + + + + + + + +
의존성 + + CoffeeScript + 와 + + 자바스크립트 실행법 + +
파일 확장자.coffee
예제coffee :index
+ +#### Stylus 템플릿 + + + + + + + + + + + + + + +
의존성 + + Stylus + 와 + + 자바스크립트 실행법 + +
파일 확장자.styl
예제stylus :index
+ +Stylus 템플릿을 사용가능하게 하려면, 먼저 `stylus`와 `stylus/tilt`를 로드 +해야합니다. + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl 템플릿 + + + + + + + + + + + + + + +
의존성yajl-ruby
파일 확장자.yajl
예제 + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ +템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 `#to_json`으로 변환됩니다. + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +`:callback`과 `:variable` 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있습니다. + +```javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang 템플릿 + + + + + + + + + + + + + + +
의존성WLang
파일 확장자.wlang
예제wlang :index, :locals => { :key => 'value' }
+ +WLang 템플릿에서는 루비 메서드를 사용하는게 일반적이지 않기 +때문에, 거의 대부분의 경우 locals를 전달합니다. 그래도 +WLang으로 쓰여진 레이아웃과 `yield`는 지원합니다. + +### 템플릿에서 변수에 접근하기 + +템플릿은 라우터 핸들러와 같은 맥락(context)에서 평가됩니다. 라우터 +핸들러에서 설정한 인스턴스 변수들은 템플릿에서 직접 접근 가능합니다. + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +명시적으로 로컬 변수의 해시를 지정할 수도 있습니다. + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +이 방법은 주로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할 +때 사용됩니다. + +### 템플릿에서의 `yield` 와 중첩 레이아웃 + +레이아웃은 보통 `yield`만 호출하는 템플릿입니다. +위에 설명된 `:template` 옵션을 통해 템플릿을 사용하거나, +다음 예제처럼 블록으로 렌더링 할 수 있습니다. + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +위 코드는 `erb :index, :layout => :post`와 대부분 동일합니다. + +렌더링 메서드에 블록 넘기기는 중첩 레이아웃을 만들때 유용합니다. + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +위의 코드도 줄일 수 있습니다. + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +현재, `erb`, `haml`, `liquid`, `slim `, `wlang`는 블럭을 지원합니다. +일반적인 `render` 메소드도 블럭을 지원합니다. + +### 인라인 템플릿 + +템플릿은 소스 파일의 마지막에서 정의할 수도 있습니다. + +``` ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html += yield + +@@ index +%div.title Hello world. +``` + +참고: sinatra를 require한 소스 파일에 정의된 인라인 템플릿은 자동으로 +로드됩니다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로 +`enable :inline_templates`을 호출하면 됩니다. + +### 이름을 가지는 템플릿(Named Templates) + +템플릿은 톱 레벨(top-level)에서 `template`메서드로도 정의할 수 있습니다. + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +"layout"이라는 이름의 템플릿이 존재하면, 템플릿이 렌더될 때마다 사용됩니다. +레이아웃을 비활성화할 때는 `:layout => false`를 전달하여 개별적으로 +비활성시키거나 `set :haml, :layout => false`으로 기본값을 비활성으로 둘 수 +있습니다. + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### 파일 확장자 연결하기 + +어떤 파일 확장자를 특정 템플릿 엔진과 연결하려면, `Tilt.register`를 사용하면 +됩니다. 예를 들어, `tt`라는 파일 확장자를 Textile 템플릿과 연결하고 싶다면, +다음과 같이 하면 됩니다. + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### 나만의 고유한 템플릿 엔진 추가하기 + +우선, Tilt로 여러분 엔진을 등록하고, 렌더링 메서드를 생성합니다. + +``` ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +위 코드는 `./views/index.myat` 를 렌더합니다. +Tilt에 대한 더 자세한 내용은 https://github.com/rtomayko/tilt 참조하세요. + +### 템플릿 검사를 위한 커스텀 로직 사용하기 + +고유한 템플릿 룩업을 구현하기 위해서는 `#find_template` 메서드를 만드셔야 합니다. + +``` ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## 필터(Filters) + +사전 필터(before filter)는 라우터와 동일한 맥락에서 매 요청 전에 평가되며 +요청과 응답을 변형할 수 있습니다. 필터에서 설정된 인스턴스 변수들은 라우터와 +템플릿에서 접근 가능합니다. + +``` ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +사후 필터(after filter)는 라우터와 동일한 맥락에서 매 요청 이후에 평가되며 +마찬가지로 요청과 응답을 변형할 수 있습니다. 사전 필터와 라우터에서 설정된 +인스턴스 변수들은 사후 필터에서 접근 가능합니다. + +``` ruby +after do + puts response.status +end +``` + +참고: 만약 라우터에서 `body` 메서드를 사용하지 않고 그냥 문자열만 반환한 +경우라면, body는 나중에 생성되는 탓에, 아직 사후 필터에서 사용할 수 없을 +것입니다. + +필터는 패턴을 취할 수도 있으며, 이 경우 요청 경로가 그 패턴과 매치할 +경우에만 필터가 평가될 것입니다. + +``` ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +라우터와 마찬가지로, 필터 역시 조건을 취할 수 있습니다. + +``` ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## 헬퍼(Helpers) + +톱-레벨의 `helpers` 메서드를 사용하여 라우터 핸들러와 템플릿에서 사용할 헬퍼 +메서드들을 정의할 수 있습니다. + +``` ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +또는, 헬퍼 메서드는 별도의 모듈 속에 정의할 수도 있습니다. + +``` ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +이 것은 모듈을 애플리케이션 클래스에 포함(include)시킨 것과 같습니다. + +### 세션(Sessions) 사용하기 + +세션은 요청 동안에 상태를 유지하기 위해 사용합니다. +세션이 활성화되면, 사용자 세션 당 세션 해시 하나씩을 갖게 됩니다. + +``` ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +`enable :sessions`은 실은 모든 데이터를 쿠키 속에 저장하는 것에 주의하세요. +이 방식이 바람직하지 않을 수도 있습니다. (예를 들어, 많은 양의 데이터를 +저장하게 되면 트래픽이 늘어납니다). +이런 경우에는 랙 세션 미들웨어(Rack session middleware)를 사용할 수 있습니다. +`enable :sessions`을 호출하지 **않는** 대신에, 선택한 미들웨어를 다른 +미들웨어들처럼 포함시키면 됩니다. + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +보안 강화을 위해서, 쿠키 속의 세션 데이터는 세션 시크릿(secret)으로 +사인(sign)됩니다. Sinatra는 무작위 시크릿을 생성합니다. 하지만, 이 +시크릿은 애플리케이션 시작 시마다 변경되기 때문에, 애플리케이션의 +모든 인스턴스들이 공유할 시크릿을 직접 만들 수도 있습니다. + +``` ruby +set :session_secret, 'super secret' +``` + +조금 더 세부적인 설정이 필요하다면, `sessions` 설정에서 옵션이 있는 +해시를 저장할 수도 있습니다. + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +세션을 다른 foo.com의 서브도메인 들과 공유하기 원한다면, 다음에 나오는 +것 처럼 도메인 앞에 *.*을 붙이셔야 합니다. + +``` ruby +set :sessions, :domain => '.foo.com' +``` + +### 중단하기(Halting) + +필터나 라우터에서 요청을 즉각 중단하고 싶을 때 사용하합니다. + +``` ruby +halt +``` + +중단할 때 상태를 지정할 수도 있습니다. + +``` ruby +halt 410 +``` + +본문을 넣을 수도 있습니다. + +``` ruby +halt 'this will be the body' +``` + +둘 다 할 수도 있습니다. + +``` ruby +halt 401, 'go away!' +``` + +헤더를 추가할 경우에는 다음과 같이 하면 됩니다. + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +당연히 `halt`와 템플릿은 같이 사용할 수 있습니다. + +``` ruby +halt erb(:error) +``` + +### 넘기기(Passing) + +라우터는 `pass`를 사용하여 다음 번 매칭되는 라우터로 처리를 넘길 수 있습니다. + +``` ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +이 때 라우터 블록에서 즉각 빠져나오게 되고 제어는 다음 번 매칭되는 라우터로 +넘어갑니다. 만약 매칭되는 라우터를 찾지 못하면, 404가 반환됩니다. + +### 다른 라우터 부르기(Triggering Another Route) + +때로는 `pass`가 아니라, 다른 라우터를 호출한 결과를 얻고 싶을 때도 +있습니다. 이럴때는 간단하게 `call`을 사용하면 됩니다. + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do +"bar" +end +``` + +위 예제의 경우, `"bar"`를 헬퍼로 옮겨 `/foo`와 `/bar` 모두에서 사용하도록 +하면 테스팅을 쉽게 하고 성능을 높일 수 있습니다. + +요청의 사본이 아닌 바로 그 인스턴스로 보내지도록 하고 싶다면, +`call` 대신 `call!`을 사용하면 됩니다. + +`call`에 대한 더 자세한 내용은 Rack 명세를 참고하세요. + +### 본문, 상태 코드 및 헤더 설정하기 + +라우터 블록의 반환값과 함께 상태 코드(status code)와 응답 본문(response body)을 +설정할수 있고 권장됩니다. 하지만, 경우에 따라서는 본문을 실행 흐름 중의 임의 +지점에서 설정해야 할때도 있습니다. 이런 경우 `body` 헬퍼 메서드를 사용하면 +됩니다. 이렇게 하면, 그 순간부터 본문에 접근할 때 그 메서드를 사용할 수가 있습니다. + +``` ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +`body`로 블록을 전달하는 것도 가능하며, 이 블록은 랙(Rack) 핸들러에 의해 +실행됩니다. (이 방법은 스트리밍을 구현할 때 사용할 수 있습니다. "값 +반환하기"를 참고하세요). + +본문와 마찬가지로, 상태코드와 헤더도 설정할 수 있습니다. + +``` ruby +get '/foo' do + status 418 + headers \ +"Allow" => "BREW, POST, GET, PROPFIND, WHEN", +"Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +`body`처럼, `header`와 `status`도 매개변수 없이 사용하여 현재 값을 +액세스할 수 있습니다. + +### 응답 스트리밍(Streaming Responses) + +응답 본문의 일정 부분을 계속 생성하는 가운데 데이터를 내보내기 시작하고 +싶을 경우가 있습니다. 극단적인 예제로, 클라이언트가 접속을 끊기 전까지 +계속 데이터를 내보내고 싶을 경우도 있죠. 여러분만의 래퍼(wrapper)를 +만들지 않으려면 `stream` 헬퍼를 사용하면 됩니다. + +``` ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +이렇게 스트리밍 API나 [서버 발송 이벤트Server Sent +Events](http://dev.w3.org/html5/eventsource/)를 구현할 수 있고, 이 방법은 +[WebSockets](http://en.wikipedia.org/wiki/WebSocket)을 위한 기반으로 사용됩니다. +이 방법은 일부 콘텐츠가 느린 자원에 의존하는 경우에 스로풋(throughtput)을 +높이기 위해 사용되기도 합니다. + +스트리밍 동작, 특히 동시 요청의 수는 애플리케이션을 서빙하는 웹서버에 크게 +의존합니다. WEBRick서버 같은 일부의 경우 아예 스트리밍을 지원하지 조차 않습니다. +만약 서버가 스트리밍을 지원하지 않는다면, 본문은 `stream` 으로 전달된 블록이 +수행을 마친 후에 한꺼번에 반환됩니다. 이런 한번에 쏘는 샷건같은 방식으로는 +스트리밍은 움직이지 않습니다. + +선택적 매개변수 `keep_open`이 설정되어 있다면, 스트림 객체에서 `close`를 +호출하지 않을 것이고, 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수 +있습니다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동하고 +다른 서버들은 여전히 스트림을 닫습니다. + +``` ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +### 로깅(Logging) + +요청 스코프(request scope) 내에서, `Logger`의 인스턴스인 `logger` +헬퍼를 사용할 수 있습니다. + +``` ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +이 로거는 자동으로 Rack 핸들러에서 설정한 로그설정을 참고합니다. +만약 로깅이 비활성상태라면, 이 메서드는 더미(dummy) 객체를 반환하기 때문에, +라우터나 필터에서 이 부분에 대해 걱정할 필요는 없습니다. + +로깅은 `Sinatra::Application`에서만 기본으로 활성화되어 있음에 유의합시다. +만약 `Sinatra::Base`로부터 상속받은 경우라면 직접 활성화시켜 줘야 합니다. + +``` ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +로깅 미들웨어를 사용하지 않으려면, `logging` 설정을 `nil`로 두면 됩니다. +하지만, 이 경우 주의할 것은 `logger`는 `nil`을 반환하는 것입니다. +통상적인 유스케이스는 여러분만의 로거를 사용하고자 할 경우일 것입니다. +Sinatra는 `env['rack.logger']`에서 찾은 로거를 사용할 것입니다. + +### 마임 타입(Mime Types) + +`send_file`이나 정적인 파일을 사용할 때에 Sinatra가 인식하지 못하는 +마임 타입이 있을 수 있습니다. 이 경우 `mime_type`을 사용하여 파일 +확장자를 등록합니다. + +``` ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +`content_type` 헬퍼로 쓸 수도 있습니다. + +``` ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### URL 생성하기 + +URL을 생성할때 `url` 헬퍼 메서드를 사용합니다. 예를 들어 Haml에서는 이렇게 +합니다. + +``` ruby +%a{:href => url('/foo')} foo +``` + +이것은 리버스 프록시(reverse proxies)와 Rack 라우터가 있다면 참고합니다. + +이 메서드는 `to`라는 별칭으로도 사용할 수 있습니다. (아래 예제 참조) + +### 브라우저 재지정(Browser Redirect) + +`redirect` 헬퍼 메서드를 사용하여 브라우저를 리다이렉트 시킬 수 있습니다. + +``` ruby +get '/foo' do + redirect to('/bar') +end +``` + +다른 부가적인 매개변수들은 `halt`에 전달하는 인자들과 비슷합니다. + +``` ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'wrong place, buddy' +``` + +`redirect back`을 사용하면 쉽게 사용자가 왔던 페이지로 다시 돌아가게 +할 수 있습니다. + +``` ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +리다이렉트와 함께 인자를 전달하려면, 쿼리로 붙이거나, + +``` ruby +redirect to('/bar?sum=42') +``` + +세션을 사용하면 됩니다. + +``` ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### 캐시 컨트롤(Cache Control) + +헤더를 정확하게 설정하는 것은 적절한 HTTP 캐싱의 기본입니다. + +Cache-Control 헤더를 다음과 같이 간단하게 설정할 수 있습니다. + +``` ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +프로 팁: 캐싱은 사전 필터에서 설정하세요. + +``` ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +`expires` 헬퍼를 사용하여 그에 상응하는 헤더를 설정한다면, +`Cache-Control`이 자동으로 설정됩니다. + +``` ruby +before do + expires 500, :public, :must_revalidate +end +``` + +캐시를 잘 사용하려면, `etag` 또는 `last_modified`을 사용해 보세요. +무거운 작업을 하기 *전*에 이들 헬퍼를 호출하길 권장합니다. 이렇게 하면, +클라이언트 캐시에 현재 버전이 이미 들어 있을 경우엔 즉각 응답을 +뿌릴(flush) 것입니다. + +``` ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +[약한 ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)를 +사용할 수 도 있습니다. + +``` ruby +etag @article.sha1, :weak +``` + +이들 헬퍼는 어떠한 캐싱도 하지 않으며, 대신 캐시에 필요한 정보를 제공합니다. +손쉬운 리버스 프록시(reverse-proxy) 캐싱 솔루션을 찾고 있다면, +[rack-cache](https://github.com/rtomayko/rack-cache)를 써보세요. + +``` ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +정적 파일에 `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` +설정(아래 참조)을 쓰세요. + +RFC 2616에 따르면 If-Match 또는 If-None-Match 헤더가 `*`로 설정된 경우 요청한 +리소스(resource)가 이미 존재하느냐 여부에 따라 다르게 취급해야 한다고 되어 +있습니다. Sinatra는 (get 처럼) 안전하거나 (put 처럼) 멱등인 요청에 대한 리소스는 +이미 존재한다고 가정하지만, 다른 리소스(예를 들면 post 요청 같은)의 경우는 +새 리소스로 취급합니다. 이 행동은 `:new_resource` 옵션을 전달하여 변경할 수 있습니다. + +``` ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +약한 ETag를 사용하고자 한다면, `:kind`으로 전달합시다. + +``` ruby +etag '', :new_resource => true, :kind => :weak +``` + +### 파일 전송하기(Sending Files) + +응답(response)으로 파일의 컨탠츠를 리턴하려면, `send_file` 헬퍼 메서드를 사용하면 됩니다. + +``` ruby +get '/' do + send_file 'foo.png' +end +``` + +이 메서드는 몇 가지 옵션을 받습니다. + +``` ruby +send_file 'foo.png', :type => :jpg +``` + +옵션들: + +
+
filename
+
응답에서 사용되는 파일명. 기본값은 실제 파일명.
+ +
last_modified
+
Last-Modified 헤더값. 기본값은 파일의 mtime.
+ +
type
+
Content-Type 헤더값. 없으면 파일 확장자로부터 유추.
+ +
disposition
+
+ Content-Disposition 헤더값. 가능한 값들: nil (기본값), + :attachment:inline +
+ +
length
+
Content-Length 헤더값, 기본값은 파일 크기.
+ +
status
+
+ 전송할 상태 코드. 오류 페이지로 정적 파일을 전송할 경우에 유용. + + Rack 핸들러가 지원할 경우, Ruby 프로세스로부터의 스트리밍이 아닌 + 다른 수단이 사용가능함. 이 헬퍼 메서드를 사용하게 되면, Sinatra는 + 자동으로 범위 요청(range request)을 처리함. +
+
+ + +### 요청 객체에 접근하기(Accessing the Request Object) + +들어오는 요청 객에는 요청 레벨(필터, 라우터, 오류 핸들러)에서 `request` +메서드를 통해 접근 가능합니다. + +``` ruby +# http://example.com/example 상에서 실행 중인 앱 +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # 클라이언트로부터 전송된 요청 본문 (아래 참조) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body의 길이 + request.media_type # request.body의 미디어 유형 + request.host # "example.com" + request.get? # true (다른 동사에 대해 유사한 메서드 있음) + request.form_data? # false + request["SOME_HEADER"] # SOME_HEADER 헤더의 값 + request.referrer # 클라이언트의 리퍼러 또는 '/' + request.user_agent # 사용자 에이전트 (:agent 조건에서 사용됨) + request.cookies # 브라우저 쿠키의 해시 + request.xhr? # 이게 ajax 요청인가요? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 클라이언트 IP 주소 + request.secure? # false (ssl 접속인 경우 true) + request.forwarded? # true (리버스 프록시 하에서 작동 중이라면) + request.env # Rack에 의해 처리되는 로우(raw) env 해시 +end +``` + +`script_name`, `path_info`같은 일부 옵션들은 이렇게 쓸 수도 있습니다. + +``` ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body`는 IO 객체이거나 StringIO 객체입니다. + +``` ruby +post "/api" do + request.body.rewind # 누군가 이미 읽은 경우 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### 첨부(Attachments) + +`attachment` 헬퍼를 사용하여 응답이 브라우저에 표시하는 대신 +디스크에 저장되어야 함을 블라우저에게 알릴 수 있습니다. + +``` ruby +get '/' do + attachment + "store it!" +end +``` + +파일명을 전달할 수도 있습니다. + +``` ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### 날짜와 시간 다루기 + +Sinatra는 `time_for_` 헬퍼 메서드를 제공합니다. 이 메서드는 +주어진 값으로부터 Time 객체를 생성한다. `DateTime`, `Date` 같은 +비슷한 클래스들도 변환됩니다. + +``` ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +이 메서드는 내부적으로 `expires` 나 `last_modified` 같은 곳에서 사용됩니다. +따라서 여러분은 애플리케이션에서 `time_for`를 오버라이딩하여 이들 메서드의 +동작을 쉽게 확장할 수 있습니다. + +``` ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### 템플릿 파일 참조하기 + +`find_template`는 렌더링할 템플릿 파일을 찾는데 사용됩니다. + +``` ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +이것만으로는 그렇게 유용하지는 않습니다만, 이 메서드를 오버라이드하여 여러분만의 +참조 메커니즘에서 가로채게 하면 유용해집니다. 예를 들어, 하나 이상의 뷰 디렉터리를 +사용하고자 한다면 이렇게 하세요. + +``` ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +다른 예제는 각 엔진마다 다른 디렉터리를 사용할 경우입니다. + +``` ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +여러분은 이것을 간단하게 확장(extension)으로 만들어 다른 사람들과 공유할 수 있다! + +`find_template`은 그 파일이 실제 존재하는지 검사하지 않음에 유의합니다. +모든 가능한 경로에 대해 주어진 블록을 호출할 뿐입니다. 이것은 성능 문제는 +되지 않습니다. 왜냐하면 `render`는 파일이 발견되는 즉시 `break`하기 때문입니다. +또한, 템플릿 위치(그리고 콘텐츠)는 개발 모드에서 실행 중이 아니라면 캐시될 것입니다. +정말로 멋진 메세드를 작성하고 싶다면 이 점을 명심하세요. + +## 설정(Configuration) + +모든 환경에서, 시작될 때, 한번만 실행되게 하려면 이렇게 하면 됩니다. + +``` ruby +configure do + # 옵션 하나 설정 + set :option, 'value' + + # 여러 옵션 설정 + set :a => 1, :b => 2 + + # `set :option, true`와 동일 + enable :option + + # `set :option, false`와 동일 + disable :option + + # 블록으로 동적인 설정을 할 수도 있음 + set(:css_dir) { File.join(views, 'css') } +end +``` + +환경(RACK_ENV 환경 변수)이 `:production`일 때만 실행되게 하려면 이렇게 하면 됩니다. + +``` ruby +configure :production do + ... +end +``` + +환경이 `:production` 또는 `:test`일 때 실행되게 하려면 이렇게 하면 됩니다. + +``` ruby +configure :production, :test do + ... +end +``` + +이 옵션들은 `settings`를 통해 접근 가능합니다. + +``` ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### 공격 방어 설정하기(Configuring attack protection) + +Sinatra는 [Rack::Protection](https://github.com/rkh/rack-protection#readme)을 사용하여 +일반적이고 일어날 수 있는 공격에 대비합니다. 이 모듈은 간단하게 비활성시킬 수 있습니다. +(하지만 애플리케이션에 엄청나게 많은 취약성을 야기합니다.) + +``` ruby +disable :protection +``` + +하나의 방어층만 스킵하려면, 옵션 해시에 `protection`을 설정하면 됩니다. + +``` ruby +set :protection, :except => :path_traversal +``` + +배열로 넘김으로써 방어층 여러 개를 비활성화할 수 있습니다. + +``` ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +기본적으로 `:sessions`가 활성 중일 때만 Sinatra는 방어층을 설정합니다. +때로는 자신만의 세션을 설정할 때도 있습니다. 이런 경우 `:session` 옵션을 +넘겨줌으로써 세션을 기반으로한 방어층을 설정 할 수 있습니다. + +``` ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### 가능한 설정들(Available Settings) + +
+
absolute_redirects
+
+ 만약 비활성이면, Sinatra는 상대경로 리다이렉트를 허용할 것이지만, + 이렇게 되면 Sinatra는 더 이상 오직 절대경로 리다이렉트만 허용하고 있는 + RFC 2616(HTTP 1.1)에 위배됨. +
+
+ 적정하게 설정되지 않은 리버스 프록시 하에서 앱을 실행 중이라면 활성화시킬 것. + rul 헬퍼는, 만약 두 번째 매개변수로 false를 전달하지만 않는다면, + 여전히 절대경로 URL을 생성할 것임에 유의. +
+
기본값은 비활성.
+ +
add_charset
+
+ content_type가 문자셋 정보에 자동으로 추가하게 될 마임(mime) 타입. + 이 옵션은 오버라이딩하지 말고 추가해야 함. + settings.add_charset << "application/foobar" +
+ +
app_file
+
+ 메인 애플리케이션 파일의 경로. 프로젝트 루트, 뷰, public 폴더, + 인라인 템플릿을 파악할 때 사용됨. +
+ +
bind
+
바인드할 IP 주소(기본값: 0.0.0.0 이나 + `environment`가 개발로 설정 되어있으면 localhost). 오직 + 빌트인(built-in) 서버에서만 사용됨.
+ +
default_encoding
+
인코딩을 알 수 없을 때 인코딩(기본값은 "utf-8").
+ +
dump_errors
+
로그안의 에러 출력.
+ +
environment
+
+ 현재 환경, 기본값은 ENV['RACK_ENV'] ENV에 없을 경우엔 "development". +
+ +
logging
+
로거(logger) 사용.
+ +
lock
+
+ Ruby 프로세스 당 요청을 동시에 할 경우에만 매 요청에 걸쳐 잠금(lock)을 설정. +
+
앱이 스레드에 안전(thread-safe)하지 않으면 활성화시킬 것. 기본값은 비활성.
+ +
method_override
+
+ put/delete를 지원하지 않는 브라우저에서 put/delete 폼을 허용하는 + _method 꼼수 사용. +
+ +
port
+
접속 포트. 빌트인 서버에서만 사용됨.
+ +
prefixed_redirects
+
+ 절대경로가 주어지지 않은 리다이렉트에 request.script_name를 + 삽입할지 여부를 결정. 활성화 하면 redirect '/foo'는 + redirect to('/foo')처럼 동작. 기본값은 비활성. +
+ +
protection
+
웹 공격 방어를 활성화시킬 건지 여부. 위의 보안 섹션 참조.
+ +
public_dir
+
public_folder의 별칭. 밑을 참조.
+ +
public_folder
+
+ public 파일이 제공될 폴더의 경로. + static 파일 제공이 활성화된 경우만 사용됨(아래 static참조). + 만약 설정이 없으면 app_file로부터 유추됨. +
+ +
reload_templates
+
+ 요청 간에 템플릿을 리로드(reload)할 건지 여부. 개발 모드에서는 활성됨. +
+ +
root
+
+ 프로젝트 루트 디렉터리 경로. 설정이 없으면 app_file 설정으로부터 유추됨. +
+ +
raise_errors
+
+ 예외 발생(애플리케이션은 중단됨). + 기본값은 environment"test"인 경우는 활성, 그렇지 않으면 비활성. +
+ +
run
+
+ 활성화되면, Sinatra가 웹서버의 시작을 핸들링. + rackup 또는 다른 도구를 사용하는 경우라면 활성화시키지 말 것. +
+ +
running
+
빌트인 서버가 실행 중인가? 이 설정은 변경하지 말 것!
+ +
server
+
+ 빌트인 서버로 사용할 서버 또는 서버 목록. + 기본값은 루비구현에 따라 다르며 순서는 우선순위를 의미. +
+ +
sessions
+
+ Rack::Session::Cookie를 사용한 쿠키 기반 세션 활성화. + 보다 자세한 정보는 '세션 사용하기' 참조. +
+ +
show_exceptions
+
+ 예외 발생 시에 브라우저에 스택 추적을 보임. + 기본값은 environment"development"인 + 경우는 활성, 나머지는 비활성. +
+
+ :after_handler를 설정함으로써 브라우저에서 + 스택 트레이스를 보여주기 전에 앱에 특화된 에러 핸들링을 + 할 수도 있음. +
+ +
static
+
Sinatra가 정적(static) 파일을 핸들링할 지 여부를 설정.
+
이 기능이 가능한 서버를 사용하는 경우라면 비활성시킬 것.
+
비활성시키면 성능이 올라감.
+
+ 기본값은 전통적 방식에서는 활성, 모듈 앱에서는 비활성. +
+ +
static_cache_control
+
+ Sinatra가 정적 파일을 제공하는 경우, 응답에 Cache-Control 헤더를 + 추가할 때 설정. cache_control 헬퍼를 사용. + 기본값은 비활성. +
+
+ 여러 값을 설정할 경우는 명시적으로 배열을 사용할 것: + set :static_cache_control, [:public, :max_age => 300] +
+ +
threaded
+
+ true로 설정하면, Thin이 요청을 처리하는데 있어 + EventMachine.defer를 사용하도록 함. +
+ +
views
+
+ 뷰 폴더 경로. 설정하지 않은 경우 app_file로부터 유추됨. +
+ +
x_cascade
+
+ 라우트를 찾지못했을 때의 X-Cascade 해더를 설정여부. + 기본값은 true +
+
+ + +## 환경(Environments) + +3가지의 미리 정의된 `environments` `"development"`, `"production"`, `"test"` +가 있습니다. 환경은 `RACK_ENV` 환경 변수를 통해서도 설정됩니다. 기본값은 +`"development"`입니다. `"development"` 모드에서는 모든 템플릿들은 요청 간에 +리로드됩니다. 또, `"development"` 모드에서는 특별한 `not_found` 와 `error` +핸들러가 브라우저에서 스택 트레이스를 볼 수 있게합니다. +`"production"`과 `"test"`에서는 기본적으로 템플릿은 캐시됩니다. + +다른 환경으로 실행시키려면 `RACK_ENV` 환경 변수를 사용하세요. + +``` shell +RACK_ENV=production ruby my_app.rb +``` + +현재 설정된 환경이 무엇인지 검사하기 위해서는 준비된 `development?`, `test?`, +`production?` 메서드를 사용할 수 있습니다. + +``` ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## 에러 처리(Error Handling) + +예외 핸들러는 라우터 및 사전 필터와 동일한 맥락에서 실행됩니다. +이 말인즉, `haml`, `erb`, `halt`같은 이들이 제공하는 모든 것들을 사용할 수 +있다는 뜻입니다. + +### 찾을 수 없음(Not Found) + +`Sinatra::NotFound` 예외가 발생하거나 또는 응답의 상태 코드가 404라면, +`not_found` 핸들러가 호출됩니다. + +``` ruby +not_found do + '아무 곳에도 찾을 수 없습니다.' +end +``` + +### 에러 + +`error` 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출됩니다. +하지만 개발 환경에서는 예외 확인 옵션을 `:after_handler`로 설정되어 있을 경우에만 +실행됨을 주의하세요. + +``` ruby +set :show_exceptions, :after_handler +``` + +예외 객체는 Rack 변수 `sinatra.error`로부터 얻을 수 있습니다. + +``` ruby +error do + '고약한 오류가 발생했군요 - ' + env['sinatra.error'].message +end +``` + +사용자 정의 오류는 이렇게 정의합니다. + +``` ruby +error MyCustomError do + '무슨 일이 생겼나면요...' + env['sinatra.error'].message +end +``` + +그런 다음, 이 오류가 발생하면 이렇게 처리합니다. + +``` ruby +get '/' do + raise MyCustomError, '안좋은 일' +end +``` + +결과는 이렇습니다. + +``` +무슨 일이 생겼냐면요... 안좋은 일 +``` + +상태 코드에 대해 오류 핸들러를 설치할 수도 있습니다. + +``` ruby +error 403 do + '액세스가 금지됨' +end + +get '/secret' do + 403 +end +``` + +범위로 지정할 수도 있습니다. + +``` ruby +error 400..510 do + '어이쿠' +end +``` + +Sinatra는 개발 환경에서 동작할 때 브라우저에 괜찮은 스택 트레이스와 추가적인 +디버그 정보를 보여주기 위해 특별한 `not_found` 와 `error` 핸들러를 설치합니다. + +## Rack 미들웨어(Middleware) + +Sinatra는 [Rack](http://rack.github.io/) 위에서 동작하며, Rack은 루비 웹 +프레임워크를 위한 최소한의 표준 인터페이스입니다. Rack이 애플리케이션 개발자들에게 +제공하는 가장 흥미로운 기능은 "미들웨어(middleware)"에 대한 지원입니다. +여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을 +모니터링하거나/조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트입니다. + +Sinatra는 톱레벨의 `use` 메서드를 사용하여 Rack 미들웨어의 파이프라인을 만드는 일을 +식은 죽 먹기로 만듭니다. + +``` ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +`use`문법은 [Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(rackup 파일에서 가장 많이 사용)에서 정의한 것과 동일합니다. 예를 들어, `use` 메서드는 +블록이나 여러 개의/가변적인 인자도 받을 수 있습니다. + +``` ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack은 로깅, 디버깅, URL 라우팅, 인증, 그리고 세센 핸들링을 위한 다양한 표준 +미들웨어로 분산되어 있습니다. Sinatra는 설정에 기반하여 이들 컴포넌트들 중 +많은 것들을 자동으로 사용하며, 따라서 여러분은 일반적으로는 `use`를 명시적으로 +사용할 필요가 없을 것입니다. + +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) +에서 유용한 미들웨어들을 찾을 수 있습니다. + +## 테스팅(Testing) + +Sinatra 테스트는 많은 Rack 기반 테스팅 라이브러리, 프레임워크를 사용하여 작성가능합니다. +그 중 [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames)를 권장합니다. + +``` ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +주의: Sinatra를 모듈러 방식으로 사용한다면, `Sinatra::Application` +를 앱에서 사용하는 클래스 이름으로 바꾸세요. + +## Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps) + +톱레벨에서 앱을 정의하는 것은 마이크로 앱(micro-app) 수준에서는 잘 동작하지만, +Rack 미들웨어나, Rails 메탈(metal) 또는 서버 컴포넌트를 갖는 간단한 라이브러리, +또는 더 나아가 Sinatra 익스텐션(extension) 같은 재사용 가능한 컴포넌트들을 구축할 +경우에는 심각한 약점이 있습니다. 톱레벨은 마이크로 앱 스타일의 설정을 가정하는 것 +입니다. (즉, 하나의 단일 애플리케이션 파일과 `./public` 및 `./views` 디렉터리, +로깅, 예외 상세 페이지 등등). 이 곳에서 `Sinatra::Base`가 필요합니다. + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +`Sinatra::Base` 서브클래스에서 사용가능한 메서드들은 톱레벨 DSL로 접근 가능한 것들과 +동일합니다. 대부분의 톱레벨 앱들은 다음 두 가지만 수정하면 `Sinatra::Base` 컴포넌트로 +변환 가능합니다. + +* 파일은 `sinatra`가 아닌 `sinatra/base`를 require해야 합니다. + 그렇지 않으면 모든 Sinatra의 DSL 메서드들이 메인 네임스페이스에 불러지게 + 됩니다. +* 앱의 라우터, 예외 핸들러, 필터, 옵션은 `Sinatra::Base`의 서브클래스에 두어야 + 합니다. + +`Sinatra::Base`는 백지상태(blank slate)입니다. 빌트인 서버를 비롯한 대부분의 옵션들이 +기본값으로 꺼져 있습니다. 가능한 옵션들과 그 작동에 대한 상세는 [옵션과 +설정](http://sinatra.github.com/configuration.html)을 참조하세요. + +### 모듈(Modular) vs. 전통적 방식(Classic Style) + +일반적인 믿음과는 반대로, 전통적 방식에 잘못된 부분은 없습니다. 여러분 애플리케이션에 +맞다면, 모듈 애플리케이션으로 전환할 필요는 없습니다. + +모듈 방식이 아닌 전통적 방식을 사용할 경우 생기는 주된 단점은 루비 프로세스 당 +하나의 Sinatra 애플리케이션만 사용할 수 있다는 점입니다. 만약 하나 이상을 사용할 +계획이라면 모듈 방식으로 전환하세요. 모듈 방식과 전통적 방식을 섞어쓰지 못할 +이유는 없습니다. + +방식을 전환할 경우에는, 기본값 설정의 미묘한 차이에 유의해야 합니다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
설정전통적 방식모듈
app_filesinatra를 로딩하는 파일Sinatra::Base를 서브클래싱한 파일
run$0 == app_filefalse
logging truefalse
method_overridetruefalse
inline_templatestruefalse
statictruefalse
+ +### 모듈 애플리케이션(Modular Application) 제공하기 + +모듈 앱을 시작하는 두 가지 일반적인 옵션이 있습니다. +`run!`으로 능동적으로 시작하는 방법은 이렇습니다. + +``` ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... 여기에 앱 코드가 온다 ... + + # 루비 파일이 직접 실행될 경우에 서버를 시작 + run! if app_file == $0 +end +``` + +이렇게 시작할 수도 있습니다. + +``` shell +ruby my_app.rb +``` + +`config.ru`와 함께 사용할수도 있습니다. 이 경우는 어떠한 Rack 핸들러도 사용할 수 있도록 +허용 합다. + +``` ruby +# config.ru +require './my_app' +run MyApp +``` + +실행은 이렇게 합니다. + +``` shell +rackup -p 4567 +``` + +### config.ru로 전통적 방식의 애플리케이션 사용하기 + +앱 파일을 다음과 같이 작성합니다. + +``` ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +대응하는 `config.ru`는 다음과 같이 작성합니다. + +``` ruby +require './app' +run Sinatra::Application +``` + +### 언제 config.ru를 사용할까? + +`config.ru`는 다음 경우에 권장 됩니다. + +* 다른 Rack 핸들러(Passenger, Unicorn, Heroku, ...)로 배포하고자 할 때. +* 하나 이상의 `Sinatra::Base` 서브클래스를 사용하고자 할 때. +* Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때. + +**모듈 방식으로 전환했다는 이유만으로 `config.ru`로 전환할 필요는 없으며, +또한 `config.ru`를 사용한다고 해서 모듈 방식을 사용해야 하는 것도 아닙니다.** + +### Sinatra를 미들웨어로 사용하기 + +Sinatra에서 다른 Rack 미들웨어를 사용할 수 있을 뿐 아니라, +어떤 Sinatra 애플리케이션에서도 순차로 어떠한 Rack 종착점 앞에 미들웨어로 +추가될 수 있습니다. 이 종착점은 다른 Sinatra 애플리케이션이 될 수도 있고, +또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/...)이 될 수도 +있습니다. + +``` ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 미들웨어는 사전 필터보다 앞서 실행됨 + use LoginScreen + + before do +unless session['user_name'] + halt "접근 거부됨, 로그인 하세요." +end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### 동적인 애플리케이션 생성(Dynamic Application Creation) + +어떤 상수에 할당하지 않고 런타임에서 새 애플리케이션들을 생성하려면, +`Sinatra.new`를 쓰면 됩니다. + +``` ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +선택적 인자로 상속할 애플리케이션을 받을 수 있습니다. + +``` ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +이 방법은 Sintra 익스텐션을 테스팅하거나 또는 여러분의 라이브러리에서 Sinatra를 +사용할 경우에 특히 유용합니다. + +이 방법은 Sinatra를 미들웨어로 사용하는 것을 아주 쉽게 만들어 주기도 합니다. + +``` ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## 범위(Scopes)와 바인딩(Binding) + +현재 어느 범위에 있느냐가 어떤 메서드와 변수를 사용할 수 있는지를 결정합니다. + +### 애플리케이션/클래스 범위 + +모든 Sinatra 애플리케이션은 `Sinatra::Base`의 서브클래스에 대응됩니다. +만약 톱레벨 DSL (`require 'sinatra'`)을 사용한다면, 이 클래스는 +`Sinatra::Application`이며, 그렇지 않을 경우라면 여러분이 명시적으로 생성한 +그 서브클래스가 됩니다. 클래스 레벨에서는 `get` 이나 `before` 같은 메서드들을 +가지나, `request` 객체나 `session` 에는 접근할 수 없습니다. 왜냐면 모든 요청에 +대해 애플리케이션 클래스는 오직 하나이기 때문입니다. + +`set`으로 생성한 옵션들은 클래스 레벨의 메서드들입니다. + +``` ruby +class MyApp < Sinatra::Base + # 저기요, 저는 애플리케이션 범위에 있다구요! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요! + end +end +``` + +애플리케이션 범위에는 이런 것들이 있습니다. + +* 애플리케이션 클래스 본문 +* 확장으로 정의된 메서드 +* `helpers`로 전달된 블록 +* `set`의 값으로 사용된 Procs/blocks +* `Sinatra.new`로 전달된 블록 + +범위 객체 (클래스)는 다음과 같이 접근할 수 있습니다. + +* configure 블록으로 전달된 객체를 통해(`configure { |c| ... }`) +* 요청 범위 내에서 `settings` + +### 요청/인스턴스 범위 + +매 요청마다, 애플리케이션 클래스의 새 인스턴스가 생성되고 모든 핸들러 블록은 +그 범위 내에서 실행됩니다. 범위 내에서 여러분은 `request` 와 `session` 객체에 +접근하거나 `erb` 나 `haml` 같은 렌더링 메서드를 호출할 수 있습니다. 요청 범위 +내에서 `settings` 헬퍼를 통해 애플리케이션 범위에 접근 가능합니다. + +``` ruby +class MyApp < Sinatra::Base + # 이봐요, 전 애플리케이션 범위에 있다구요! + get '/define_route/:name' do + # '/define_route/:name'의 요청 범위 + @value = 42 + + settings.get("/#{params['name']}") do + # "/#{params['name']}"의 요청 범위 + @value # => nil (동일한 요청이 아님) + end + + "라우터가 정의됨!" + end +end +``` + +요청 범위에는 이런 것들이 있습니다. + +* get/head/post/put/delete/options 블록 +* before/after 필터 +* 헬퍼(helper) 메서드 +* 템플릿/뷰 + +### 위임 범위(Delegation Scope) + +위임 범위(delegation scope)는 메서드를 단순히 클래스 범위로 보냅니다(forward). +하지만 클래스 바인딩을 갖지 않기에 완전히 클래스 범위처럼 동작하지는 않습니다. +오직 명시적으로 위임(delegation) 표시된 메서드들만 사용 가능하고, +또한 클래스 범위와 변수/상태를 공유하지 않습니다 (유의: `self`가 다름). +`Sinatra::Delegator.delegate :method_name`을 호출하여 메서드 위임을 명시적으로 +추가할 수 있습니다. + +위임 범위에는 이런 것들이 있습니다. + +* 톱레벨 바인딩, `require "sinatra"`를 한 경우 +* `Sinatra::Delegator` 믹스인으로 확장된 객체 + +직접 코드를 살펴보길 바랍니다. +[Sinatra::Delegator 믹스인](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +은 [메인 객체를 확장한 것](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30) 입니다. + +## 명령행(Command Line) + +Sinatra 애플리케이션은 직접 실행할 수 있습니다. + +``` shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +옵션들은 다음과 같습니다. + +``` +-h # 도움말 +-p # 포트 설정 (기본값은 4567) +-o # 호스트 설정 (기본값은 0.0.0.0) +-e # 환경 설정 (기본값은 development) +-s # rack 서버/핸들러 지정 (기본값은 thin) +-x # mutex 잠금 켜기 (기본값은 off) +``` + +## 요구사항(Requirement) + +다음의 루비 버전은 공식적으로 지원됩니다. +
+
Ruby 1.8.7
+
+ 1.8.7은 완전하게 지원되지만, 꼭 그래야할 특별한 이유가 없다면, + 1.9.2로 업그레이드하거나 또는 JRuby나 Rubinius로 전환할 것을 권장합니다. + 1.8.7에 대한 지원은 Sinatra 2.0 이전에는 중단되지 않을 것입니다. + Ruby 1.8.6은 더이상 지원하지 않습니다. +
+ +
Ruby 1.9.2
+
+ 1.9.2는 완전하게 지원됩니다. 1.9.2p0은, Sinatra를 실행했을 때 세그먼트 오류가 + 발생할수 있으므로 쓰지 마세요. 공식 지원은 Sinatra 1.5 이전에는 중단되지 않을 + 것입니다. +
+ +
Ruby 1.9.3
+
+ 1.9.3은 완전하게 지원되고 권장합니다. 이전 버전에서 1.9.3으로 전환할 경우 모든 + 세션이 무효화되므로 주의하세요. 1.9.3에 대한 지원은 Sinatra 2.0 이전에는 + 중단되지 않을 것입니다. +
+ +
Ruby 2.x
+
+ 2.x은 완전하게 지원되고 권장합니다. 현재 공식 지원 중지 계획은 없습니다. +
+ +
Rubinius
+
+ Rubinius는 공식적으로 지원됩니다. (Rubinius >= 2.x) + gem install puma를 권장합니다. +
+ +
JRuby
+
+ JRuby의 마지막 안정판은 공식적으로 지원됩니다. C 확장을 JRuby와 사용하는 + 것은 권장되지 않습니다. + gem install trinidad를 권장합니다. +
+
+ +새로 나오는 루비 버전도 주시하고 있습니다. + +다음 루비 구현체들은 공식적으로 지원하지 않지만 +여전히 Sinatra를 실행할 수 있는 것으로 알려져 있습니다. + +* JRuby와 Rubinius 예전 버전 +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 및 1.9.1 (이 버전들은 사용하지 말 것을 권합니다) + +공식적으로 지원하지 않는다는 것의 의미는 무언가가 그 플랫폼에서만 잘못되고 +지원되는 플랫폼에서는 그러지 않을 경우, 우리의 문제가 아니라 그 플랫폼의 문제로 +간주한다는 뜻입니다. + +또한 우리는 CI를 ruby-head (MRI의 이후 릴리즈) 브랜치에 맞춰 실행하지만, +계속해서 변하고 있기 때문에 아무 것도 보장할 수는 없습니다. +앞으로 나올 2.x가 완전히 지원되길 기대합시다. + +Sinatra는 선택한 루비 구현체가 지원하는 어떠한 운영체제에서도 작동해야 +합니다. + +현재 Cardinal, SmallRuby, BlueRuby 또는 1.8.7 이전의 루비 버전에서는 +Sinatra를 실행할 수 없을 것입니다. + +## 최신(The Bleeding Edge) + +Sinatra의 가장 최근 코드를 사용하고자 한다면, 애플리케이션을 마스터 브랜치에 맞춰 +실행하면 되므로 부담가지지 마세요. 하지만 덜 안정적일 것 입니다. + +주기적으로 사전배포(prerelease) 젬을 푸시하기 때문에, 최신 기능들을 얻기 위해 +다음과 같이 할 수도 있습니다. + +``` shell +gem install sinatra --pre +``` + +### Bundler를 사용하여 + +여러분 애플리케이션을 최신 Sinatra로 실행하고자 한다면, +[Bundler](http://gembundler.com/)를 사용할 것을 권장합니다. + +우선, 아직 설치하지 않았다면 bundler를 설치합니다. + +``` shell +gem install bundler +``` + +그런 다음, 프로젝트 디렉터리에서, `Gemfile`을 만듭니다. + +``` ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# 다른 의존관계들 +gem 'haml' # 예를 들어, haml을 사용한다면 +gem 'activerecord', '~> 3.0' # 아마도 ActiveRecord 3.x도 필요할 것 +``` + +`Gemfile`안에 애플리케이션의 모든 의존성을 적어야 합니다. +하지만, Sinatra가 직접적인 의존관계에 있는 것들(Rack과 Tilt)은 +Bundler가 자동으로 찾아서 추가할 것입니다. + +이제 앱을 실행할 수 있습니다. + +``` shell +bundle exec ruby myapp.rb +``` + +### 직접 하기(Roll Your Own) + +로컬 클론(clone)을 생성한 다음 `$LOAD_PATH`에 `sinatra/lib` 디렉터리를 주고 +여러분 앱을 실행합니다. + +``` shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +이후에 Sinatra 소스를 업데이트하려면 이렇게 하세요. + +``` shell +cd myapp/sinatra +git pull +``` + +### 전역으로 설치(Install Globally) + +젬을 직접 빌드할 수 있습니다. + +``` shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 합니다. + +``` shell +sudo rake install +``` + +## 버저닝(Versioning) + +Sinatra는 [시맨틱 버저닝Semantic Versioning](http://semver.org/) +[(번역)](http://surpreem.com/archives/380)의 SemVer, +SemVerTag를 준수합니다. + +## 더 읽을 거리(Further Reading) + +* [프로젝트 웹사이트](http://www.sinatrarb.com/) - 추가 문서들, 뉴스, + 그리고 다른 리소스들에 대한 링크. +* [기여하기](http://www.sinatrarb.com/contributing) - 버그를 찾았나요? + 도움이 필요한가요? 패치를 하셨나요? +* [이슈 트래커](http://github.com/sinatra/sinatra/issues) +* [트위터](http://twitter.com/sinatra) +* [메일링 리스트](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 튜토리얼 +* [Sinatra Recipes](http://recipes.sinatrarb.com/) 커뮤니티가 만드는 레시피 +* http://rubydoc.info에 있는 [최종 릴리스](http://rubydoc.info/gems/sinatra) + 또는 [current HEAD](http://rubydoc.info/github/sinatra/sinatra)에 대한 API 문서 +* [CI server](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.md new file mode 100644 index 000000000..bcce5ee42 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.md @@ -0,0 +1,3004 @@ +# Sinatra + +Sinatra is a [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) for +quickly creating web applications in Ruby with minimal effort: + +``` ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Install the gem: + +``` shell +gem install sinatra +``` + +And run with: + +``` shell +ruby myapp.rb +``` + +View at: http://localhost:4567 + +It is recommended to also run `gem install thin`, which Sinatra will +pick up if available. + +## Table of Contents + +* [Sinatra](#sinatra) + * [Table of Contents](#table-of-contents) + * [Routes](#routes) + * [Conditions](#conditions) + * [Return Values](#return-values) + * [Custom Route Matchers](#custom-route-matchers) + * [Static Files](#static-files) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Available Template Languages](#available-template-languages) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Accessing Variables in Templates](#accessing-variables-in-templates) + * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) + * [Inline Templates](#inline-templates) + * [Named Templates](#named-templates) + * [Associating File Extensions](#associating-file-extensions) + * [Adding Your Own Template Engine](#adding-your-own-template-engine) + * [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup) + * [Filters](#filters) + * [Helpers](#helpers) + * [Using Sessions](#using-sessions) + * [Halting](#halting) + * [Passing](#passing) + * [Triggering Another Route](#triggering-another-route) + * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) + * [Streaming Responses](#streaming-responses) + * [Logging](#logging) + * [Mime Types](#mime-types) + * [Generating URLs](#generating-urls) + * [Browser Redirect](#browser-redirect) + * [Cache Control](#cache-control) + * [Sending Files](#sending-files) + * [Accessing the Request Object](#accessing-the-request-object) + * [Attachments](#attachments) + * [Dealing with Date and Time](#dealing-with-date-and-time) + * [Looking Up Template Files](#looking-up-template-files) + * [Configuration](#configuration) + * [Configuring attack protection](#configuring-attack-protection) + * [Available Settings](#available-settings) + * [Environments](#environments) + * [Error Handling](#error-handling) + * [Not Found](#not-found) + * [Error](#error) + * [Rack Middleware](#rack-middleware) + * [Testing](#testing) + * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) + * [Modular vs. Classic Style](#modular-vs-classic-style) + * [Serving a Modular Application](#serving-a-modular-application) + * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) + * [When to use a config.ru?](#when-to-use-a-configru) + * [Using Sinatra as Middleware](#using-sinatra-as-middleware) + * [Dynamic Application Creation](#dynamic-application-creation) + * [Scopes and Binding](#scopes-and-binding) + * [Application/Class Scope](#applicationclass-scope) + * [Request/Instance Scope](#requestinstance-scope) + * [Delegation Scope](#delegation-scope) + * [Command Line](#command-line) + * [Requirement](#requirement) + * [The Bleeding Edge](#the-bleeding-edge) + * [With Bundler](#with-bundler) + * [Roll Your Own](#roll-your-own) + * [Install Globally](#install-globally) + * [Versioning](#versioning) + * [Further Reading](#further-reading) + +## Routes + +In Sinatra, a route is an HTTP method paired with a URL-matching pattern. +Each route is associated with a block: + +``` ruby +get '/' do + .. show something .. +end + +post '/' do + .. create something .. +end + +put '/' do + .. replace something .. +end + +patch '/' do + .. modify something .. +end + +delete '/' do + .. annihilate something .. +end + +options '/' do + .. appease something .. +end + +link '/' do + .. affiliate something .. +end + +unlink '/' do + .. separate something .. +end +``` + +Routes are matched in the order they are defined. The first route that +matches the request is invoked. + +Route patterns may include named parameters, accessible via the +`params` hash: + +``` ruby +get '/hello/:name' do + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + "Hello #{params['name']}!" +end +``` + +You can also access named parameters via block parameters: + +``` ruby +get '/hello/:name' do |n| + # matches "GET /hello/foo" and "GET /hello/bar" + # params['name'] is 'foo' or 'bar' + # n stores params['name'] + "Hello #{n}!" +end +``` + +Route patterns may also include splat (or wildcard) parameters, accessible +via the `params['splat']` array: + +``` ruby +get '/say/*/to/*' do + # matches /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # matches /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Or with block parameters: + +``` ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Route matching with Regular Expressions: + +``` ruby +get /\A\/hello\/([\w]+)\z/ do + "Hello, #{params['captures'].first}!" +end +``` + +Or with a block parameter: + +``` ruby +get %r{/hello/([\w]+)} do |c| + # Matches "GET /meta/hello/world", "GET /hello/world/1234" etc. + "Hello, #{c}!" +end +``` + +Route patterns may have optional parameters: + +``` ruby +get '/posts.?:format?' do + # matches "GET /posts" and any extension "GET /posts.json", "GET /posts.xml" etc. +end +``` + +Routes may also utilize query parameters: + +``` ruby +get '/posts' do + # matches "GET /posts?title=foo&author=bar" + title = params['title'] + author = params['author'] + # uses title and author variables; query is optional to the /posts route +end +``` + +By the way, unless you disable the path traversal attack protection (see below), +the request path might be modified before matching against your routes. + +## Conditions + +Routes may include a variety of matching conditions, such as the user agent: + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # Matches non-songbird browsers +end +``` + +Other available conditions are `host_name` and `provides`: + +``` ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` searches the request's Accept header. + +You can easily define your own conditions: + +``` ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +For a condition that takes multiple values use a splat: + +``` ruby +set(:auth) do |*roles| # <- notice the splat here + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +## Return Values + +The return value of a route block determines at least the response body passed +on to the HTTP client, or at least the next middleware in the Rack stack. +Most commonly, this is a string, as in the above examples. But other values are +also accepted. + +You can return any object that would either be a valid Rack response, Rack +body object or HTTP status code: + +* An Array with three elements: `[status (Fixnum), headers (Hash), response + body (responds to #each)]` +* An Array with two elements: `[status (Fixnum), response body (responds to + #each)]` +* An object that responds to `#each` and passes nothing but strings to + the given block +* A Fixnum representing the status code + +That way we can, for instance, easily implement a streaming example: + +``` ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +You can also use the `stream` helper method (described below) to reduce boiler +plate and embed the streaming logic in the route. + +## Custom Route Matchers + +As shown above, Sinatra ships with built-in support for using String patterns +and regular expressions as route matches. However, it does not stop there. You +can easily define your own matchers: + +``` ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note that the above example might be over-engineered, as it can also be +expressed as: + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Or, using negative look ahead: + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## Static Files + +Static files are served from the `./public` directory. You can specify +a different location by setting the `:public_folder` option: + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +Note that the public directory name is not included in the URL. A file +`./public/css/style.css` is made available as +`http://example.com/css/style.css`. + +Use the `:static_cache_control` setting (see below) to add +`Cache-Control` header info. + +## Views / Templates + +Each template language is exposed via its own rendering method. These +methods simply return a string: + +``` ruby +get '/' do + erb :index +end +``` + +This renders `views/index.erb`. + +Instead of a template name, you can also just pass in the template content +directly: + +``` ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates take a second argument, the options hash: + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +This will render `views/index.erb` embedded in the +`views/post.erb` (default is `views/layout.erb`, if it exists). + +Any options not understood by Sinatra will be passed on to the template +engine: + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +You can also set options per template language in general: + +``` ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Options passed to the render method override options set via `set`. + +Available Options: + +
+
locals
+
+ List of locals passed to the document. Handy with partials. + Example: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ String encoding to use if uncertain. Defaults to + settings.default_encoding. +
+ +
views
+
+ Views folder to load templates from. Defaults to settings.views. +
+ +
layout
+
+ Whether to use a layout (true or false). If it's a + Symbol, specifies what template to use. Example: + erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ Content-Type the template produces. Default depends on template language. +
+ +
scope
+
+ Scope to render template under. Defaults to the application instance. If you + change this, instance variables and helper methods will not be available. +
+ +
layout_engine
+
+ Template engine to use for rendering the layout. Useful for languages that + do not support layouts otherwise. Defaults to the engine used for the + template. Example: set :rdoc, :layout_engine => :erb +
+ +
layout_options
+
+ Special options only used for rendering the layout. Example: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
+
+ +Templates are assumed to be located directly under the `./views` directory. To +use a different views directory: + +``` ruby +set :views, settings.root + '/templates' +``` + + +One important thing to remember is that you always have to reference templates +with symbols, even if they're in a subdirectory (in this case, use: +`:'subdir/template'` or `'subdir/template'.to_sym`). You must use a symbol +because otherwise rendering methods will render any strings passed to them +directly. + +### Literal Templates + +``` ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Renders the template string. + +### Available Template Languages + +Some languages have multiple implementations. To specify what implementation +to use (and to be thread-safe), you should simply require it first: + +``` ruby +require 'rdiscount' # or require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
Dependencyhaml
File Extension.haml
Examplehaml :index, :format => :html5
+ +#### Erb Templates + + + + + + + + + + + + + + +
Dependency + erubis + or erb (included in Ruby) +
File Extensions.erb, .rhtml or .erubis (Erubis only)
Exampleerb :index
+ +#### Builder Templates + + + + + + + + + + + + + + +
Dependency + builder +
File Extension.builder
Examplebuilder { |xml| xml.em "hi" }
+ +It also takes a block for inline templates (see example). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
Dependencynokogiri
File Extension.nokogiri
Examplenokogiri { |xml| xml.em "hi" }
+ +It also takes a block for inline templates (see example). + +#### Sass Templates + + + + + + + + + + + + + + +
Dependencysass
File Extension.sass
Examplesass :stylesheet, :style => :expanded
+ +#### SCSS Templates + + + + + + + + + + + + + + +
Dependencysass
File Extension.scss
Examplescss :stylesheet, :style => :expanded
+ +#### Less Templates + + + + + + + + + + + + + + +
Dependencyless
File Extension.less
Exampleless :stylesheet
+ +#### Liquid Templates + + + + + + + + + + + + + + +
Dependencyliquid
File Extension.liquid
Exampleliquid :index, :locals => { :key => 'value' }
+ +Since you cannot call Ruby methods (except for `yield`) from a Liquid +template, you almost always want to pass locals to it. + +#### Markdown Templates + + + + + + + + + + + + + + +
Dependency + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
File Extensions.markdown, .mkd and .md
Examplemarkdown :index, :layout_engine => :erb
+ +It is not possible to call methods from markdown, nor to pass locals to it. +You therefore will usually use it in combination with another rendering +engine: + +``` ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Note that you may also call the `markdown` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Since you cannot call Ruby from Markdown, you cannot use layouts written in +Markdown. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### Textile Templates + + + + + + + + + + + + + + +
DependencyRedCloth
File Extension.textile
Exampletextile :index, :layout_engine => :erb
+ +It is not possible to call methods from textile, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +``` ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Note that you may also call the `textile` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Since you cannot call Ruby from Textile, you cannot use layouts written in +Textile. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### RDoc Templates + + + + + + + + + + + + + + +
DependencyRDoc
File Extension.rdoc
Examplerdoc :README, :layout_engine => :erb
+ +It is not possible to call methods from rdoc, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +``` ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Note that you may also call the `rdoc` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Since you cannot call Ruby from RDoc, you cannot use layouts written in +RDoc. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
DependencyAsciidoctor
File Extension.asciidoc, .adoc and .ad
Exampleasciidoc :README, :layout_engine => :erb
+ +Since you cannot call Ruby methods directly from an AsciiDoc template, you +almost always want to pass locals to it. + +#### Radius Templates + + + + + + + + + + + + + + +
DependencyRadius
File Extension.radius
Exampleradius :index, :locals => { :key => 'value' }
+ +Since you cannot call Ruby methods directly from a Radius template, you almost +always want to pass locals to it. + +#### Markaby Templates + + + + + + + + + + + + + + +
DependencyMarkaby
File Extension.mab
Examplemarkaby { h1 "Welcome!" }
+ +It also takes a block for inline templates (see example). + +#### RABL Templates + + + + + + + + + + + + + + +
DependencyRabl
File Extension.rabl
Examplerabl :index
+ +#### Slim Templates + + + + + + + + + + + + + + +
DependencySlim Lang
File Extension.slim
Exampleslim :index
+ +#### Creole Templates + + + + + + + + + + + + + + +
DependencyCreole
File Extension.creole
Examplecreole :wiki, :layout_engine => :erb
+ +It is not possible to call methods from creole, nor to pass locals to it. You +therefore will usually use it in combination with another rendering engine: + +``` ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note that you may also call the `creole` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Since you cannot call Ruby from Creole, you cannot use layouts written in +Creole. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
DependencyWikiCloth
File Extension.mediawiki and .mw
Examplemediawiki :wiki, :layout_engine => :erb
+ +It is not possible to call methods from MediaWiki markup, nor to pass locals to +it. You therefore will usually use it in combination with another rendering +engine: + +``` ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Since you cannot call Ruby from MediaWiki, you cannot use layouts written in +MediaWiki. However, it is possible to use another rendering engine for the +template than for the layout by passing the `:layout_engine` option. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
Dependency + + CoffeeScript + and a + + way to execute javascript + +
File Extension.coffee
Examplecoffee :index
+ +#### Stylus Templates + + + + + + + + + + + + + + +
Dependency + + Stylus + and a + + way to execute javascript + +
File Extension.styl
Examplestylus :index
+ +Before being able to use Stylus templates, you need to load `stylus` and +`stylus/tilt` first: + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
Dependencyyajl-ruby
File Extension.yajl
Example + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ + +The template source is evaluated as a Ruby string, and the +resulting json variable is converted using `#to_json`: + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +The `:callback` and `:variable` options can be used to decorate the rendered +object: + +``` javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
DependencyWLang
File Extension.wlang
Examplewlang :index, :locals => { :key => 'value' }
+ +Since calling ruby methods is not idiomatic in WLang, you almost always want to +pass locals to it. Layouts written in WLang and `yield` are supported, though. + +### Accessing Variables in Templates + +Templates are evaluated within the same context as route handlers. Instance +variables set in route handlers are directly accessible by templates: + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Or, specify an explicit Hash of local variables: + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +This is typically used when rendering templates as partials from within +other templates. + +### Templates with `yield` and nested layouts + +A layout is usually just a template that calls `yield`. +Such a template can be used either through the `:template` option as +described above, or it can be rendered with a block as follows: + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +This code is mostly equivalent to `erb :index, :layout => :post`. + +Passing blocks to rendering methods is most useful for creating nested layouts: + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +This can also be done in fewer lines of code with: + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Currently, the following rendering methods accept a block: `erb`, `haml`, +`liquid`, `slim `, `wlang`. Also the general `render` method accepts a block. + +### Inline Templates + +Templates may be defined at the end of the source file: + +``` ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +NOTE: Inline templates defined in the source file that requires sinatra are +automatically loaded. Call `enable :inline_templates` explicitly if you +have inline templates in other source files. + +### Named Templates + +Templates may also be defined using the top-level `template` method: + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +If a template named "layout" exists, it will be used each time a template +is rendered. You can individually disable layouts by passing +`:layout => false` or disable them by default via +`set :haml, :layout => false`: + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associating File Extensions + +To associate a file extension with a template engine, use +`Tilt.register`. For instance, if you like to use the file extension +`tt` for Textile templates, you can do the following: + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adding Your Own Template Engine + +First, register your engine with Tilt, then create a rendering method: + +``` ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renders `./views/index.myat`. See https://github.com/rtomayko/tilt to +learn more about Tilt. + +### Using Custom Logic for Template Lookup + +To implement your own template lookup mechanism you can write your +own `#find_template` method: + +``` ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filters + +Before filters are evaluated before each request within the same +context as the routes will be and can modify the request and response. Instance +variables set in filters are accessible by routes and templates: + +``` ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +After filters are evaluated after each request within the same context as the +routes will be and can also modify the request and response. Instance variables +set in before filters and routes are accessible by after filters: + +``` ruby +after do + puts response.status +end +``` + +Note: Unless you use the `body` method rather than just returning a String from +the routes, the body will not yet be available in the after filter, since it is +generated later on. + +Filters optionally take a pattern, causing them to be evaluated only if the +request path matches that pattern: + +``` ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +Like routes, filters also take conditions: + +``` ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Helpers + +Use the top-level `helpers` method to define helper methods for use in +route handlers and templates: + +``` ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Alternatively, helper methods can be separately defined in a module: + +``` ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +The effect is the same as including the modules in the application class. + +### Using Sessions + +A session is used to keep state during requests. If activated, you have one +session hash per user session: + +``` ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +Note that `enable :sessions` actually stores all data in a cookie. This +might not always be what you want (storing lots of data will increase your +traffic, for instance). You can use any Rack session middleware: in order to +do so, do **not** call `enable :sessions`, but instead pull in your +middleware of choice as you would any other middleware: + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +To improve security, the session data in the cookie is signed with a session +secret. A random secret is generated for you by Sinatra. However, since this +secret will change with every start of your application, you might want to +set the secret yourself, so all your application instances share it: + +``` ruby +set :session_secret, 'super secret' +``` + +If you want to configure it further, you may also store a hash with options in +the `sessions` setting: + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +To share your session across other apps on subdomains of foo.com, prefix the +domain with a *.* like this instead: + +``` ruby +set :sessions, :domain => '.foo.com' +``` + +### Halting + +To immediately stop a request within a filter or route use: + +``` ruby +halt +``` + +You can also specify the status when halting: + +``` ruby +halt 410 +``` + +Or the body: + +``` ruby +halt 'this will be the body' +``` + +Or both: + +``` ruby +halt 401, 'go away!' +``` + +With headers: + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +It is of course possible to combine a template with `halt`: + +``` ruby +halt erb(:error) +``` + +### Passing + +A route can punt processing to the next matching route using `pass`: + +``` ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +The route block is immediately exited and control continues with the next +matching route. If no matching route is found, a 404 is returned. + +### Triggering Another Route + +Sometimes `pass` is not what you want, instead you would like to get the result +of calling another route. Simply use `call` to achieve this: + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note that in the example above, you would ease testing and increase performance +by simply moving `"bar"` into a helper used by both `/foo` and `/bar`. + +If you want the request to be sent to the same application instance rather than +a duplicate, use `call!` instead of `call`. + +Check out the Rack specification if you want to learn more about `call`. + +### Setting Body, Status Code and Headers + +It is possible and recommended to set the status code and response body with the +return value of the route block. However, in some scenarios you might want to +set the body at an arbitrary point in the execution flow. You can do so with the +`body` helper method. If you do so, you can use that method from there on to +access the body: + +``` ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +It is also possible to pass a block to `body`, which will be executed by the +Rack handler (this can be used to implement streaming, see "Return Values"). + +Similar to the body, you can also set the status code and headers: + +``` ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Like `body`, `headers` and `status` with no arguments can be used to access +their current values. + +### Streaming Responses + +Sometimes you want to start sending out data while still generating parts of +the response body. In extreme examples, you want to keep sending data until +the client closes the connection. You can use the `stream` helper to avoid +creating your own wrapper: + +``` ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +This allows you to implement streaming APIs, +[Server Sent Events](http://dev.w3.org/html5/eventsource/), and can be used as +the basis for [WebSockets](http://en.wikipedia.org/wiki/WebSocket). It can also be +used to increase throughput if some but not all content depends on a slow +resource. + +Note that the streaming behavior, especially the number of concurrent requests, +highly depends on the web server used to serve the application. Some servers, +like WEBRick, might not even support streaming at all. If the server does not +support streaming, the body will be sent all at once after the block passed to +`stream` finishes executing. Streaming does not work at all with Shotgun. + +If the optional parameter is set to `keep_open`, it will not call `close` on +the stream object, allowing you to close it at any later point in the +execution flow. This only works on evented servers, like Thin and Rainbows. +Other servers will still close the stream: + +``` ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # register a client's interest in server events + stream(:keep_open) do |out| + connections << out + # purge dead connections + connections.reject!(&:closed?) + end +end + +post '/:message' do + connections.each do |out| + # notify client that a new message has arrived + out << params['message'] << "\n" + + # indicate client to connect again + out.close + end + + # acknowledge + "message received" +end +``` + +### Logging + +In the request scope, the `logger` helper exposes a `Logger` instance: + +``` ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +This logger will automatically take your Rack handler's logging settings into +account. If logging is disabled, this method will return a dummy object, so +you do not have to worry about it in your routes and filters. + +Note that logging is only enabled for `Sinatra::Application` by default, so if +you inherit from `Sinatra::Base`, you probably want to enable it yourself: + +``` ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +To avoid any logging middleware to be set up, set the `logging` setting to +`nil`. However, keep in mind that `logger` will in that case return `nil`. A +common use case is when you want to set your own logger. Sinatra will use +whatever it will find in `env['rack.logger']`. + +### Mime Types + +When using `send_file` or static files you may have mime types Sinatra +doesn't understand. Use `mime_type` to register them by file extension: + +``` ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +You can also use it with the `content_type` helper: + +``` ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Generating URLs + +For generating URLs you should use the `url` helper method, for instance, in +Haml: + +``` ruby +%a{:href => url('/foo')} foo +``` + +It takes reverse proxies and Rack routers into account, if present. + +This method is also aliased to `to` (see below for an example). + +### Browser Redirect + +You can trigger a browser redirect with the `redirect` helper method: + +``` ruby +get '/foo' do + redirect to('/bar') +end +``` + +Any additional parameters are handled like arguments passed to `halt`: + +``` ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'wrong place, buddy' +``` + +You can also easily redirect back to the page the user came from with +`redirect back`: + +``` ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +To pass arguments with a redirect, either add them to the query: + +``` ruby +redirect to('/bar?sum=42') +``` + +Or use a session: + +``` ruby +enable :sessions + +get '/foo' do + session[:secret] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session[:secret] +end +``` + +### Cache Control + +Setting your headers correctly is the foundation for proper HTTP caching. + +You can easily set the Cache-Control header like this: + +``` ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Pro tip: Set up caching in a before filter: + +``` ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +If you are using the `expires` helper to set the corresponding header, +`Cache-Control` will be set automatically for you: + +``` ruby +before do + expires 500, :public, :must_revalidate +end +``` + +To properly use caches, you should consider using `etag` or `last_modified`. +It is recommended to call those helpers *before* doing any heavy lifting, as +they will immediately flush a response if the client already has the current +version in its cache: + +``` ruby +get "/article/:id" do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +It is also possible to use a +[weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +``` ruby +etag @article.sha1, :weak +``` + +These helpers will not do any caching for you, but rather feed the necessary +information to your cache. If you are looking for a quick reverse-proxy caching +solution, try [rack-cache](https://github.com/rtomayko/rack-cache): + +``` ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Use the `:static_cache_control` setting (see below) to add +`Cache-Control` header info to static files. + +According to RFC 2616, your application should behave differently if the If-Match +or If-None-Match header is set to `*`, depending on whether the resource +requested is already in existence. Sinatra assumes resources for safe (like get) +and idempotent (like put) requests are already in existence, whereas other +resources (for instance post requests) are treated as new resources. You +can change this behavior by passing in a `:new_resource` option: + +``` ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +If you still want to use a weak ETag, pass in a `:kind` option: + +``` ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Sending Files + +To return the contents of a file as the response, you can use the `send_file` +helper method: + +``` ruby +get '/' do + send_file 'foo.png' +end +``` + +It also takes options: + +``` ruby +send_file 'foo.png', :type => :jpg +``` + +The options are: + +
+
filename
+
File name to be used in the response, defaults to the real file name.
+ +
last_modified
+
Value for Last-Modified header, defaults to the file's mtime.
+ +
type
+
Value for Content-Type header, guessed from the file extension if + missing.
+ +
disposition
+
+ Value for Content-Disposition header, possible values: nil + (default), :attachment and :inline +
+ +
length
+
Value for Content-Length header, defaults to file size.
+ +
status
+
+ Status code to be sent. Useful when sending a static file as an error page. + + If supported by the Rack handler, other means than streaming from the Ruby + process will be used. If you use this helper method, Sinatra will + automatically handle range requests. +
+
+ +### Accessing the Request Object + +The incoming request object can be accessed from request level (filter, routes, +error handlers) through the `request` method: + +``` ruby +# app running on http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # request body sent by the client (see below) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # length of request.body + request.media_type # media type of request.body + request.host # "example.com" + request.get? # true (similar methods for other verbs) + request.form_data? # false + request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. + request.referrer # the referrer of the client or '/' + request.user_agent # user agent (used by :agent condition) + request.cookies # hash of browser cookies + request.xhr? # is this an ajax request? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # client IP address + request.secure? # false (would be true over ssl) + request.forwarded? # true (if running behind a reverse proxy) + request.env # raw env hash handed in by Rack +end +``` + +Some options, like `script_name` or `path_info`, can also be written: + +``` ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +The `request.body` is an IO or StringIO object: + +``` ruby +post "/api" do + request.body.rewind # in case someone already read it + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Attachments + +You can use the `attachment` helper to tell the browser the response should be +stored on disk rather than displayed in the browser: + +``` ruby +get '/' do + attachment + "store it!" +end +``` + +You can also pass it a file name: + +``` ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Dealing with Date and Time + +Sinatra offers a `time_for` helper method that generates a Time object from the +given value. It is also able to convert `DateTime`, `Date` and similar classes: + +``` ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +This method is used internally by `expires`, `last_modified` and akin. You can +therefore easily extend the behavior of those methods by overriding `time_for` +in your application: + +``` ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Looking Up Template Files + +The `find_template` helper is used to find template files for rendering: + +``` ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +This is not really useful. But it is useful that you can actually override this +method to hook in your own lookup mechanism. For instance, if you want to be +able to use more than one view directory: + +``` ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Another example would be using different directories for different engines: + +``` ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +You can also easily wrap this up in an extension and share with others! + +Note that `find_template` does not check if the file really exists but +rather calls the given block for all possible paths. This is not a performance +issue, since `render` will use `break` as soon as a file is found. Also, +template locations (and content) will be cached if you are not running in +development mode. You should keep that in mind if you write a really crazy +method. + +## Configuration + +Run once, at startup, in any environment: + +``` ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +``` + +Run only when the environment (`RACK_ENV` environment variable) is set to +`:production`: + +``` ruby +configure :production do + ... +end +``` + +Run when the environment is set to either `:production` or `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +You can access those options via `settings`: + +``` ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Configuring attack protection + +Sinatra is using +[Rack::Protection](https://github.com/rkh/rack-protection#readme) to defend +your application against common, opportunistic attacks. You can easily disable +this behavior (which will open up your application to tons of common +vulnerabilities): + +``` ruby +disable :protection +``` + +To skip a single defense layer, set `protection` to an options hash: + +``` ruby +set :protection, :except => :path_traversal +``` +You can also hand in an array in order to disable a list of protections: + +``` ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +By default, Sinatra will only set up session based protection if `:sessions` +has been enabled. Sometimes you want to set up sessions on your own, though. In +that case you can get it to set up session based protections by passing the +`:session` option: + +``` ruby +use Rack::Session::Pool +set :protection, :session => true +``` + +### Available Settings + +
+
absolute_redirects
+
+ If disabled, Sinatra will allow relative redirects, however, Sinatra will no + longer conform with RFC 2616 (HTTP 1.1), which only allows absolute redirects. +
+
+ Enable if your app is running behind a reverse proxy that has not been set up + properly. Note that the url helper will still produce absolute URLs, unless you + pass in false as the second parameter. +
+
Disabled by default.
+ +
add_charset
+
+ Mime types the content_type helper will automatically add the charset info to. + You should add to it rather than overriding this option: + settings.add_charset << "application/foobar" +
+ +
app_file
+
+ Path to the main application file, used to detect project root, views and public + folder and inline templates. +
+ +
bind
+
IP address to bind to (default: 0.0.0.0 or + localhost if your `environment` is set to development). Only used + for built-in server.
+ +
default_encoding
+
Encoding to assume if unknown (defaults to "utf-8").
+ +
dump_errors
+
Display errors in the log.
+ +
environment
+
+ Current environment. Defaults to ENV['RACK_ENV'], or + "development" if not available. +
+ +
logging
+
Use the logger.
+ +
lock
+
+ Places a lock around every request, only running processing on request + per Ruby process concurrently. +
+
Enabled if your app is not thread-safe. Disabled per default.
+ +
method_override
+
+ Use _method magic to allow put/delete forms in browsers that + don't support it. +
+ +
port
+
Port to listen on. Only used for built-in server.
+ +
prefixed_redirects
+
+ Whether or not to insert request.script_name into redirects if no + absolute path is given. That way redirect '/foo' would behave like + redirect to('/foo'). Disabled per default. +
+ +
protection
+
Whether or not to enable web attack protections. See protection section + above.
+ +
public_dir
+
Alias for public_folder. See below.
+ +
public_folder
+
+ Path to the folder public files are served from. Only used if static + file serving is enabled (see static setting below). Inferred from + app_file setting if not set. +
+ +
reload_templates
+
+ Whether or not to reload templates between requests. Enabled in development + mode. +
+ +
root
+
+ Path to project root folder. Inferred from app_file setting if not + set. +
+ +
raise_errors
+
+ Raise exceptions (will stop application). Enabled by default when + environment is set to "test", disabled otherwise. +
+ +
run
+
+ If enabled, Sinatra will handle starting the web server. Do not + enable if using rackup or other means. +
+ +
running
+
Is the built-in server running now? Do not change this setting!
+ +
server
+
+ Server or list of servers to use for built-in server. Order indicates + priority, default depends on Ruby implementation. +
+ +
sessions
+
+ Enable cookie-based sessions support using Rack::Session::Cookie. + See 'Using Sessions' section for more information. +
+ +
show_exceptions
+
+ Show a stack trace in the browser when an exception happens. Enabled by + default when environment is set to "development", + disabled otherwise. +
+
+ Can also be set to :after_handler to trigger app-specified error + handling before showing a stack trace in the browser. +
+ +
static
+
Whether Sinatra should handle serving static files.
+
Disable when using a server able to do this on its own.
+
Disabling will boost performance.
+
+ Enabled per default in classic style, disabled for modular apps. +
+ +
static_cache_control
+
+ When Sinatra is serving static files, set this to add Cache-Control + headers to the responses. Uses the cache_control helper. Disabled + by default. +
+
+ Use an explicit array when setting multiple values: + set :static_cache_control, [:public, :max_age => 300] +
+ +
threaded
+
+ If set to true, will tell Thin to use EventMachine.defer + for processing the request. +
+ +
traps
+
Whether Sinatra should handle system signals.
+ +
views
+
+ Path to the views folder. Inferred from app_file setting if + not set. +
+ +
x_cascade
+
+ Whether or not to set the X-Cascade header if no route matches. + Defaults to true. +
+
+ +## Environments + +There are three predefined `environments`: `"development"`, `"production"` and +`"test"`. Environments can be set through the `RACK_ENV` environment variable. +The default value is `"development"`. In the `"development"` environment all +templates are reloaded between requests, and special `not_found` and `error` +handlers display stack traces in your browser. In the `"production"` and +`"test"` environments, templates are cached by default. + +To run different environments, set the `RACK_ENV` environment variable: + +``` shell +RACK_ENV=production ruby my_app.rb +``` + +You can use predefined methods: `development?`, `test?` and `production?` to +check the current environment setting: + +``` ruby +get '/' do + if settings.development? + "development!" + else + "not development!" + end +end +``` + +## Error Handling + +Error handlers run within the same context as routes and before filters, which +means you get all the goodies it has to offer, like `haml`, +`erb`, `halt`, etc. + +### Not Found + +When a `Sinatra::NotFound` exception is raised, or the response's status +code is 404, the `not_found` handler is invoked: + +``` ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +The `error` handler is invoked any time an exception is raised from a route +block or a filter. But note in development it will only run if you set the +show exceptions option to `:after_handler`: + +```ruby +set :show_exceptions, :after_handler +``` + +The exception object can be obtained from the `sinatra.error` Rack variable: + +``` ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Custom errors: + +``` ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Then, if this happens: + +``` ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +You get this: + +``` +So what happened was... something bad +``` + +Alternatively, you can install an error handler for a status code: + +``` ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Or a range: + +``` ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra installs special `not_found` and `error` handlers when +running under the development environment to display nice stack traces +and additional debugging information in your browser. + +## Rack Middleware + +Sinatra rides on [Rack](http://rack.github.io/), a minimal standard +interface for Ruby web frameworks. One of Rack's most interesting capabilities +for application developers is support for "middleware" -- components that sit +between the server and your application monitoring and/or manipulating the +HTTP request/response to provide various types of common functionality. + +Sinatra makes building Rack middleware pipelines a cinch via a top-level +`use` method: + +``` ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +The semantics of `use` are identical to those defined for the +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(most frequently used from rackup files). For example, the `use` method +accepts multiple/variable args as well as blocks: + +``` ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack is distributed with a variety of standard middleware for logging, +debugging, URL routing, authentication, and session handling. Sinatra uses +many of these components automatically based on configuration so you +typically don't have to `use` them explicitly. + +You can find useful middleware in +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readm), +or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Testing + +Sinatra tests can be written using any Rack-based testing library or framework. +[Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) +is recommended: + +``` ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +Note: If you are using Sinatra in the modular style, replace +`Sinatra::Application` above with the class name of your app. + +## Sinatra::Base - Middleware, Libraries, and Modular Apps + +Defining your app at the top-level works well for micro-apps but has +considerable drawbacks when building reusable components such as Rack +middleware, Rails metal, simple libraries with a server component, or even +Sinatra extensions. The top-level assumes a micro-app style configuration +(e.g., a single application file, `./public` and `./views` +directories, logging, exception detail page, etc.). That's where +`Sinatra::Base` comes into play: + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +The methods available to `Sinatra::Base` subclasses are exactly the same as +those available via the top-level DSL. Most top-level apps can be converted to +`Sinatra::Base` components with two modifications: + +* Your file should require `sinatra/base` instead of `sinatra`; + otherwise, all of Sinatra's DSL methods are imported into the main + namespace. +* Put your app's routes, error handlers, filters, and options in a subclass + of `Sinatra::Base`. + +`Sinatra::Base` is a blank slate. Most options are disabled by default, +including the built-in server. See +[Configuring Settings](http://sinatra.github.com/configuration.html) +for details on available options and their behavior. If you want +behavior more similar to when you define your app at the top level (also +known as Classic style), you +can subclass `Sinatra::Application`. + +``` ruby +require 'sinatra/base' + +class MyApp < Sinatra::Application + get '/' do + 'Hello world!' + end +end +``` + +### Modular vs. Classic Style + +Contrary to common belief, there is nothing wrong with the classic style. If it +suits your application, you do not have to switch to a modular application. + +The main disadvantage of using the classic style rather than the modular style +is that you will only have one Sinatra application per Ruby process. If you +plan to use more than one, switch to the modular style. There is no reason you +cannot mix the modular and the classic styles. + +If switching from one style to the other, you should be aware of slightly +different default settings: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingClassicModularModular
app_filefile loading sinatrafile subclassing Sinatra::Basefile subclassing Sinatra::Application
run$0 == app_filefalsefalse
loggingtruefalsetrue
method_overridetruefalsetrue
inline_templatestruefalsetrue
statictruefalsetrue
+ +### Serving a Modular Application + +There are two common options for starting a modular app, actively starting with +`run!`: + +``` ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +``` + +Start with: + +``` shell +ruby my_app.rb +``` + +Or with a `config.ru` file, which allows using any Rack handler: + +``` ruby +# config.ru (run with rackup) +require './my_app' +run MyApp +``` + +Run: + +``` shell +rackup -p 4567 +``` + +### Using a Classic Style Application with a config.ru + +Write your app file: + +``` ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +And a corresponding `config.ru`: + +``` ruby +require './app' +run Sinatra::Application +``` + +### When to use a config.ru? + +A `config.ru` file is recommended if: + +* You want to deploy with a different Rack handler (Passenger, Unicorn, + Heroku, ...). +* You want to use more than one subclass of `Sinatra::Base`. +* You want to use Sinatra only for middleware, and not as an endpoint. + +**There is no need to switch to a `config.ru` simply because you switched to +the modular style, and you don't have to use the modular style for running with +a `config.ru`.** + +### Using Sinatra as Middleware + +Not only is Sinatra able to use other Rack middleware, any Sinatra application +can in turn be added in front of any Rack endpoint as middleware itself. This +endpoint could be another Sinatra application, or any other Rack-based +application (Rails/Ramaze/Camping/...): + +``` ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # middleware will run before filters + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Dynamic Application Creation + +Sometimes you want to create new applications at runtime without having to +assign them to a constant. You can do this with `Sinatra.new`: + +``` ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +It takes the application to inherit from as an optional argument: + +```ruby +# config.ru (run with rackup) +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +This is especially useful for testing Sinatra extensions or using Sinatra in +your own library. + +This also makes using Sinatra as middleware extremely easy: + +``` ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Scopes and Binding + +The scope you are currently in determines what methods and variables are +available. + +### Application/Class Scope + +Every Sinatra application corresponds to a subclass of `Sinatra::Base`. +If you are using the top-level DSL (`require 'sinatra'`), then this +class is `Sinatra::Application`, otherwise it is the subclass you +created explicitly. At class level you have methods like `get` or `before`, but +you cannot access the `request` or `session` objects, as there is only a +single application class for all requests. + +Options created via `set` are methods at class level: + +``` ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Hey, I'm no longer in the application scope! + end +end +``` + +You have the application scope binding inside: + +* Your application class body +* Methods defined by extensions +* The block passed to `helpers` +* Procs/blocks used as value for `set` +* The block passed to `Sinatra.new` + +You can reach the scope object (the class) like this: + +* Via the object passed to configure blocks (`configure { |c| ... }`) +* `settings` from within the request scope + +### Request/Instance Scope + +For every incoming request, a new instance of your application class is +created, and all handler blocks run in that scope. From within this scope you +can access the `request` and `session` objects or call rendering methods like +`erb` or `haml`. You can access the application scope from within the request +scope via the `settings` helper: + +``` ruby +class MyApp < Sinatra::Base + # Hey, I'm in the application scope! + get '/define_route/:name' do + # Request scope for '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Request scope for "/#{params['name']}" + @value # => nil (not the same request) + end + + "Route defined!" + end +end +``` + +You have the request scope binding inside: + +* get, head, post, put, delete, options, patch, link, and unlink blocks +* before and after filters +* helper methods +* templates/views + +### Delegation Scope + +The delegation scope just forwards methods to the class scope. However, it +does not behave exactly like the class scope, as you do not have the class +binding. Only methods explicitly marked for delegation are available, and you +do not share variables/state with the class scope (read: you have a different +`self`). You can explicitly add method delegations by calling +`Sinatra::Delegator.delegate :method_name`. + +You have the delegate scope binding inside: + +* The top level binding, if you did `require "sinatra"` +* An object extended with the `Sinatra::Delegator` mixin + +Have a look at the code for yourself: here's the +[Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Command Line + +Sinatra applications can be run directly: + +``` shell +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Options are: + +``` +-h # help +-p # set the port (default is 4567) +-o # set the host (default is 0.0.0.0) +-e # set the environment (default is development) +-s # specify rack server/handler (default is thin) +-x # turn on the mutex lock (default is off) +``` + +## Requirement + +The following Ruby versions are officially supported: +
+
Ruby 1.8.7
+
+ 1.8.7 is fully supported, however, if nothing is keeping you from it, we + recommend upgrading or switching to JRuby or Rubinius. Support for 1.8.7 + will not be dropped before Sinatra 2.0. Ruby 1.8.6 is no longer supported. +
+ +
Ruby 1.9.2
+
+ 1.9.2 is fully supported. Do not use 1.9.2p0, as it is known to cause + segmentation faults when running Sinatra. Official support will continue + at least until the release of Sinatra 1.5. +
+ +
Ruby 1.9.3
+
+ 1.9.3 is fully supported and recommended. Please note that switching to 1.9.3 + from an earlier version will invalidate all sessions. 1.9.3 will be supported + until the release of Sinatra 2.0. +
+ +
Ruby 2.x
+
+ 2.x is fully supported and recommended. There are currently no plans to drop + official support for it. +
+ +
Rubinius
+
+ Rubinius is officially supported (Rubinius >= 2.x). It is recommended to + gem install puma. +
+ +
JRuby
+
+ The latest stable release of JRuby is officially supported. It is not + recommended to use C extensions with JRuby. It is recommended to + gem install trinidad. +
+
+ +We also keep an eye on upcoming Ruby versions. + +The following Ruby implementations are not officially supported but still are +known to run Sinatra: + +* Older versions of JRuby and Rubinius +* Ruby Enterprise Edition +* MacRuby, Maglev, IronRuby +* Ruby 1.9.0 and 1.9.1 (but we do recommend against using those) + +Not being officially supported means if things only break there and not on a +supported platform, we assume it's not our issue but theirs. + +We also run our CI against ruby-head (future releases of MRI), but we can't +guarantee anything, since it is constantly moving. Expect upcoming 2.x releases +to be fully supported. + +Sinatra should work on any operating system supported by the chosen Ruby +implementation. + +If you run MacRuby, you should `gem install control_tower`. + +Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any +Ruby version prior to 1.8.7. + +## The Bleeding Edge + +If you would like to use Sinatra's latest bleeding-edge code, feel free to run your +application against the master branch, it should be rather stable. + +We also push out prerelease gems from time to time, so you can do a + +``` shell +gem install sinatra --pre +``` + +to get some of the latest features. + +### With Bundler + +If you want to run your application with the latest Sinatra, using +[Bundler](http://gembundler.com/) is the recommended way. + +First, install bundler, if you haven't: + +``` shell +gem install bundler +``` + +Then, in your project directory, create a `Gemfile`: + +```ruby +source 'https://rubygems.org' +gem 'sinatra', :github => "sinatra/sinatra" + +# other dependencies +gem 'haml' # for instance, if you use haml +gem 'activerecord', '~> 3.0' # maybe you also need ActiveRecord 3.x +``` + +Note that you will have to list all your application's dependencies in the `Gemfile`. +Sinatra's direct dependencies (Rack and Tilt) will, however, be automatically +fetched and added by Bundler. + +Now you can run your app like this: + +``` shell +bundle exec ruby myapp.rb +``` + +### Roll Your Own + +Create a local clone and run your app with the `sinatra/lib` directory +on the `$LOAD_PATH`: + +``` shell +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib myapp.rb +``` + +To update the Sinatra sources in the future: + +``` shell +cd myapp/sinatra +git pull +``` + +### Install Globally + +You can build the gem on your own: + +``` shell +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +If you install gems as root, the last step should be: + +``` shell +sudo rake install +``` + +## Versioning + +Sinatra follows [Semantic Versioning](http://semver.org/), both SemVer and +SemVerTag. + +## Further Reading + +* [Project Website](http://www.sinatrarb.com/) - Additional documentation, + news, and links to other resources. +* [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need + help? Have a patch? +* [Issue tracker](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Mailing List](http://groups.google.com/group/sinatrarb/topics) +* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook Tutorial +* [Sinatra Recipes](http://recipes.sinatrarb.com/) Community + contributed recipes +* API documentation for the [latest release](http://rubydoc.info/gems/sinatra) + or the [current HEAD](http://rubydoc.info/github/sinatra/sinatra) on + http://rubydoc.info +* [CI server](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-br.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-br.md new file mode 100644 index 000000000..c7c59d543 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-br.md @@ -0,0 +1,1726 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Alguns dos trechos de código a seguir utilizam caracteres UTF-8. Então, caso esteja utilizando uma versão de ruby inferior à `2.0.0`, adicione o encoding no início de seus arquivos: + +```ruby +# encoding: utf-8 +``` + +Sinatra é uma [DSL](http://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar aplicações web em Ruby com o mínimo de esforço e rapidez: + +``` ruby +# minha_app.rb +require 'sinatra' + +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem: + +``` shell +gem install sinatra +``` + +Em seguida execute: + +``` shell +ruby minha_app.rb +``` + +Acesse: [localhost:4567](http://localhost:4567) + +É recomendado também executar `gem install thin`. Caso esta gem esteja disponível, o +Sinatra irá utilizá-la. + +## Conteúdo + +* [Sinatra](#sinatra) + * [Conteúdo](#conteúdo) + * [Rotas](#rotas) + * [Condições](#condições) + * [Retorno de valores](#retorno-de-valores) + * [Validadores de rota personalizados](#validadores-de-rota-personalizados) + * [Arquivos estáticos](#arquivos-estáticos) + * [Views / Templates](#views--templates) + * [Literal Templates](#literal-templates) + * [Linguagens de template disponíveis](#linguagens-de-template-disponíveis) + * [Haml Templates](#haml-templates) + * [Erb Templates](#erb-templates) + * [Builder Templates](#builder-templates) + * [Nokogiri Templates](#nokogiri-templates) + * [Sass Templates](#sass-templates) + * [SCSS Templates](#scss-templates) + * [Less Templates](#less-templates) + * [Liquid Templates](#liquid-templates) + * [Markdown Templates](#markdown-templates) + * [Textile Templates](#textile-templates) + * [RDoc Templates](#rdoc-templates) + * [AsciiDoc Templates](#asciidoc-templates) + * [Radius Templates](#radius-templates) + * [Markaby Templates](#markaby-templates) + * [RABL Templates](#rabl-templates) + * [Slim Templates](#slim-templates) + * [Creole Templates](#creole-templates) + * [MediaWiki Templates](#mediawiki-templates) + * [CoffeeScript Templates](#coffeescript-templates) + * [Stylus Templates](#stylus-templates) + * [Yajl Templates](#yajl-templates) + * [WLang Templates](#wlang-templates) + * [Acessando Variáveis nos Templates](#acessando-variáveis-nos-templates) + * [Templates com `yield` e layouts aninhados](#templates-com-yield-e-layouts-aninhados) + * [Templates Inline](#templates-inline) + * [Templates Nomeados](#templates-nomeados) + * [Associando extensões de arquivos](#associando-extensões-de-arquivos) + * [Adicionando seu Próprio Engine de Template](#adicionando-seu-próprio-engine-de-template) + * [Customizando lógica para encontrar templates](#customizando-lógica-para-encontrar-templates) + * [Filtros](#filtros) + * [Helpers](#helpers) + * [Utilizando Sessões](#utilizando-sessões) + * [Halting](#halting) + * [Passing](#passing) + * [Desencadeando Outra Rota](#desencadeando-outra-rota) + * [Configuração](#configuração) + * [Tratamento de Erros](#tratamento-de-erros) + * [Erro](#erro) + * [Mime Types](#mime-types) + * [Rack Middleware](#rack-middleware) + * [Testando](#testando) + * [Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares](#sinatrabase---middleware-bibliotecas-e-aplicativos-modulares) + * [Linha de comando](#linha-de-comando) + * [A última versão](#a-última-versão) + * [Mais](#mais) + +## Rotas + +No Sinatra, uma rota é um método HTTP emparelhado com um padrão de URL. +Cada rota possui um bloco de execução: + +``` ruby +get '/' do + .. mostrando alguma coisa .. +end + +post '/' do + .. criando alguma coisa .. +end + +put '/' do + .. atualizando alguma coisa .. +end + +patch '/' do + .. modificando alguma coisa .. +end + +delete '/' do + .. removendo alguma coisa .. +end + +options '/' do + .. estabelecendo alguma coisa ..pe +end +``` + +As rotas são interpretadas na ordem em que são definidas. A primeira +rota encontrada responde a requisição. + +Padrões de rota podem conter parâmetros nomeados, acessíveis por meio do +hash `params`: + +``` ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Você também pode acessar parâmetros nomeados por meio dos parâmetros de +um bloco: + +``` ruby +get '/ola/:nome' do |n| + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + # n guarda o valor de params['nome'] + "Olá #{n}!" +end +``` + +Padrões de rota também podem conter parâmetros splat (curinga), +acessível por meio do array `params['splat']`: + +``` ruby +get '/diga/*/para/*' do + # corresponde a /diga/ola/para/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/caminho/do/arquivo.xml + params['splat'] # => ["caminho/do/arquivo", "xml"] +end +``` + +Ou com parâmetros de um bloco: + +``` ruby +get '/download/*.*' do |caminho, ext| + [caminho, ext] # => ["caminho/do/arquivo", "xml"] +end +``` + +Rotas podem casar com expressões regulares: + +``` ruby +get /\A\/ola\/([\w]+)\z/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com parâmetros de um bloco: + +``` ruby +get %r{/ola/([\w]+)} do |c| + # corresponde a "GET /meta/ola/mundo", "GET /ola/mundo/1234" etc. + "Olá, #{c}!" +end +``` + +Padrões de rota podem contar com parâmetros opcionais: + +``` ruby +get '/posts.?:formato?' do + # corresponde a "GET /posts" e qualquer extensão "GET /posts.json", "GET /posts.xml", etc. +end +``` + +Rotas também podem utilizar query strings: + +``` ruby +get '/posts' do + # corresponde a "GET /posts?titulo=foo&autor=bar" + titulo = params['titulo'] + autor = params['autor'] + # utiliza as variaveis titulo e autor; a query é opicional para a rota /posts +end +``` + +A propósito, a menos que você desative a proteção contra ataques (veja +abaixo), o caminho solicitado pode ser alterado antes de concluir a +comparação com as suas rotas. + +## Condições + +Rotas podem incluir uma variedade de condições, tal como o `user agent`: + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está usando o Songbird versão #{params['agent'][0]}" +end + +get '/foo' do + # Correspondente a navegadores que não sejam Songbird +end +``` + +Outras condições disponíveis são `host_name` e `provides`: + +``` ruby +get '/', :host_name => /^admin\./ do + "Área administrativa. Acesso negado!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` +`provides` procura pelos Accept header das requisições + +Você pode facilmente definir suas próprias condições: + +``` ruby +set(:probabilidade) { |valor| condition { rand <= valor } } + +get '/ganha_um_carro', :probabilidade => 0.1 do + "Você ganhou!" +end + +get '/ganha_um_carro' do + "Sinto muito, você perdeu." +end +``` + +Use splat, para uma condição que leva vários valores: + +``` ruby +set(:auth) do |*roles| # <- observe o splat aqui + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/minha/conta/", :auth => [:usuario, :administrador] do + "Detalhes da sua conta" +end + +get "/apenas/administrador/", :auth => :administrador do + "Apenas administradores são permitidos aqui!" +end +``` + +## Retorno de valores + +O valor de retorno do bloco de uma rota determina pelo menos o corpo da +resposta passado para o cliente HTTP, ou pelo menos o próximo middleware +na pilha Rack. Frequentemente, isto é uma `string`, tal como nos +exemplos acima. Entretanto, outros valores também são aceitos. + +Você pode retornar uma resposta válida ou um objeto para o Rack, sendo +eles de qualquer tipo de objeto que queira. Além disso, é possível +retornar um código de status HTTP. + +* Um array com três elementros: `[status (Fixnum), cabecalho (Hash), + corpo da resposta (responde à #each)]` + +* Um array com dois elementros: `[status (Fixnum), corpo da resposta + (responde à #each)]` + +* Um objeto que responda à `#each` sem passar nada, mas, sim, `strings` + para um dado bloco + +* Um objeto `Fixnum` representando o código de status + +Dessa forma, podemos implementar facilmente um exemplo de streaming: + +``` ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Você também pode usar o método auxiliar `stream` (descrito abaixo) para +incorporar a lógica de streaming na rota. + +## Validadores de Rota Personalizados + +Como apresentado acima, a estrutura do Sinatra conta com suporte +embutido para uso de padrões de String e expressões regulares como +validadores de rota. No entanto, ele não pára por aí. Você pode +facilmente definir os seus próprios validadores: + +``` ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Note que o exemplo acima pode ser robusto e complicado em excesso. Pode +também ser implementado como: + +``` ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Ou, usando algo mais denso à frente: + +``` ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do diretório +`./public`. Você pode especificar um local diferente pela opção +`:public_folder` + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/estatico' +``` + +Note que o nome do diretório público não é incluido na URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://exemplo.com/css/style.css`. + +## Views / Templates + +Cada linguagem de template é exposta através de seu próprio método de renderização. Estes metodos simplesmente retornam uma string: + +``` ruby +get '/' do + erb :index +end +``` + +Isto renderiza `views/index.rb` + +Ao invés do nome do template, você também pode passar direto o conteúdo do template: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Templates também aceitam um segundo argumento, um hash de opções: + +``` ruby +get '/' do + erb :index, :layout => :post +end +``` + +Isto irá renderizar a `views/index.erb` inclusa dentro da `views/post.erb` (o padrão é a `views/layout.erb`, se existir). + +Qualquer opção não reconhecida pelo Sinatra será passada adiante para o engine de template: + +``` ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Você também pode definir opções padrões para um tipo de template: + +``` ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Opções passadas para o método de renderização sobrescreve as opções definitas através do método `set`. + +Opções disponíveis: + +
+
locals
+
+ Lista de locais passado para o documento. Conveniente para *partials* + Exemplo: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ String encoding para ser utilizada em caso de incerteza. o padrão é settings.default_encoding. +
+ +
views
+
+ Diretório de onde os templates são carregados. O padrão é settings.views. +
+ +
layout
+
+ Para definir quando utilizar ou não um + layout (true ou false). E se for um + Symbol, especifica qual template usar. Exemplo: + erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ O *Content-Type* que o template produz. O padrão depente + da linguagem de template utilizada. +
+ +
scope
+
+ Escopo em que o template será renderizado. Padrão é a + instancia da aplicação. Se você mudar isto as variáveis + de instânciae metodos auxiliares não serão + disponibilizados. +
+ +
layout_engine
+
+ A engine de template utilizada para renderizar seu layout. + Útil para linguagens que não suportam templates de outra + forma. O padrão é a engine do template utilizado. Exemplo: + set :rdoc, :layout_engine => :erb +
+ +
layout_options
+
+ Opções especiais utilizadas apenas para renderizar o + layout. Exemplo: + set :rdoc, :layout_options => { :views => 'views/layouts' } +
+
+ +É pressuposto que os templates estarão localizados direto sob o diretório `./views`. Para usar um diretório diferente: + +```ruby +set :views, settings.root + '/templates' +``` + +Uma coisa importante para se lembrar é que você sempre deve +referenciar os templates utilizando *symbols*, mesmo que +eles estejam em um subdiretório (neste caso use: +`:'subdir/template'` or `'subdir/template'.to_sym`). Você deve +utilizar um *symbol* porque senão o método de renderização irá +renderizar qualquer outra string que você passe diretamente +para ele + +### Literal Templates + +``` ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza um template string. + +### Linguagens de template disponíveis + +Algumas linguagens possuem multiplas implementações. Para especificar qual implementação deverá ser utilizada (e para ser *thread-safe*), você deve simplesmente requere-la primeiro: + +``` ruby +require 'rdiscount' # ou require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml Templates + + + + + + + + + + + + + + +
Dependenciahaml
Extencao do Arquivo.haml
Exemplohaml :index, :format => :html5
+ +#### Erb Templates + + + + + + + + + + + + + + +
Dependencia + erubis + or erb (included in Ruby) +
Extencao do Arquivos.erb, .rhtml or .erubis (Erubis only)
Exemploerb :index
+ +#### Builder Templates + + + + + + + + + + + + + + +
Dependencia + builder +
Extencao do Arquivo.builder
Exemplobuilder { |xml| xml.em "hi" }
+ +It also takes a block for inline templates (see exemplo). + +#### Nokogiri Templates + + + + + + + + + + + + + + +
Dependencianokogiri
Extencao do Arquivo.nokogiri
Exemplonokogiri { |xml| xml.em "hi" }
+ +It also takes a block for inline templates (see exemplo). + +#### Sass Templates + + + + + + + + + + + + + + +
Dependenciasass
Extencao do Arquivo.sass
Exemplosass :stylesheet, :style => :expanded
+ +#### SCSS Templates + + + + + + + + + + + + + + +
Dependenciasass
Extencao do Arquivo.scss
Exemploscss :stylesheet, :style => :expanded
+ +#### Less Templates + + + + + + + + + + + + + + +
Dependencialess
Extencao do Arquivo.less
Exemploless :stylesheet
+ +#### Liquid Templates + + + + + + + + + + + + + + +
Dependencialiquid
Extencao do Arquivo.liquid
Exemploliquid :index, :locals => { :key => 'value' }
+ +Já que você não pode chamar o Ruby (exceto pelo método `yield`) pelo template Liquid, +você quase sempre precisará passar o `locals` para ele. + +#### Markdown Templates + + + + + + + + + + + + + + +
Dependencia + Anyone of: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
Extencao do Arquivos.markdown, .mkd and .md
Exemplomarkdown :index, :layout_engine => :erb
+ +Não é possível chamar métodos por este template, nem passar *locals* para o mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +``` ruby +erb :overview, :locals => { :text => markdown(:introducao) } +``` + +Note que vcoê também pode chamar o método `markdown` dentro de outros templates: + +``` ruby +%h1 Olá do Haml! +%p= markdown(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Markdown, você não +pode utilizar um layout escrito em Markdown. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + + + + + + + + + + + + + + +
DependenciaRedCloth
Extencao do Arquivo.textile
Exemplotextile :index, :layout_engine => :erb
+ +Não é possível chamar métodos por este template, nem passar *locals* para o mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +``` ruby +erb :overview, :locals => { :text => textile(:introducao) } +``` + +Note que vcoê também pode chamar o método `textile` dentro de outros templates: + +``` ruby +%h1 Olá do Haml! +%p= textile(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Textile, você não +pode utilizar um layout escrito em Textile. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### RDoc Templates + + + + + + + + + + + + + + +
DependenciaRDoc
Extencao do Arquivo.rdoc
Exemplordoc :README, :layout_engine => :erb
+ +Não é possível chamar métodos por este template, nem passar *locals* para o mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +``` ruby +erb :overview, :locals => { :text => rdoc(:introducao) } +``` + +Note que vcoê também pode chamar o método `rdoc` dentro de outros templates: + +``` ruby +%h1 Olá do Haml! +%p= rdoc(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo RDoc, você não +pode utilizar um layout escrito em RDoc. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### AsciiDoc Templates + + + + + + + + + + + + + + +
DependenciaAsciidoctor
Extencao do Arquivo.asciidoc, .adoc and .ad
Exemploasciidoc :README, :layout_engine => :erb
+ +Já que você não pode chamar o Ruby pelo template AsciiDoc, +você quase sempre precisará passar o `locals` para ele. + +#### Radius Templates + + + + + + + + + + + + + + +
DependenciaRadius
Extencao do Arquivo.radius
Exemploradius :index, :locals => { :key => 'value' }
+ +Já que você não pode chamar o Ruby pelo template Radius, +você quase sempre precisará passar o `locals` para ele. + +#### Markaby Templates + + + + + + + + + + + + + + +
DependenciaMarkaby
Extencao do Arquivo.mab
Exemplomarkaby { h1 "Welcome!" }
+ +Este também recebe um bloco para templates (veja o exemplo). + +#### RABL Templates + + + + + + + + + + + + + + +
DependenciaRabl
Extencao do Arquivo.rabl
Exemplorabl :index
+ +#### Slim Templates + + + + + + + + + + + + + + +
DependenciaSlim Lang
Extencao do Arquivo.slim
Exemploslim :index
+ +#### Creole Templates + + + + + + + + + + + + + + +
DependenciaCreole
Extencao do Arquivo.creole
Exemplocreole :wiki, :layout_engine => :erb
+ +Não é possível chamar métodos por este template, nem passar *locals* para o mesmo. Portanto normalmente é utilizado junto a outra engine de renderização: + +``` ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Note que vcoê também pode chamar o método `creole` dentro de outros templates: + +``` ruby +%h1 Olá do Haml! +%p= creole(:saudacoes) +``` + +Já que você não pode chamar o Ruby pelo Creole, você não +pode utilizar um layout escrito em Creole. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### MediaWiki Templates + + + + + + + + + + + + + + +
DependenciaWikiCloth
Extencao do Arquivo.mediawiki and .mw
Exemplomediawiki :wiki, :layout_engine => :erb
+ +It is not possible to call methods from MediaWiki markup, nor to pass locals to +it. You therefore will usually use it in combination with another rendering +engine: + +``` ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Note that you may also call the `mediawiki` method from within other templates: + +``` ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Já que você não pode chamar o Ruby pelo MediaWiki, você não +pode utilizar um layout escrito em MediaWiki. Contudo é +possível utilizar outra engine de renderização como template, +deve-se passar a `:layout_engine` como opção. + +#### CoffeeScript Templates + + + + + + + + + + + + + + +
Dependencia + + CoffeeScript + and a + + way to execute javascript + +
Extencao do Arquivo.coffee
Exemplocoffee :index
+ +#### Stylus Templates + + + + + + + + + + + + + + +
Dependencia + + Stylus + and a + + way to execute javascript + +
Extencao do Arquivo.styl
Exemplostylus :index
+ +Antes que vcoê possa utilizar o template Stylus primeiro você deve carregar `stylus` e `stylus/tilt`: + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :exemplo +end +``` + +#### Yajl Templates + + + + + + + + + + + + + + +
Dependenciayajl-ruby
Extencao do Arquivo.yajl
Exemplo + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ +O código-fonte do template é executado como uma string Ruby e a variável resultante em json é convertida utilizando `#to_json`: + +``` ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +O `:callback` e `:variable` são opções que podem ser utilizadas para o objeto de renderização: + +``` javascript +var resource = {"foo":"bar","baz":"qux"}; +present(resource); +``` + +#### WLang Templates + + + + + + + + + + + + + + +
DependenciaWLang
Extencao do Arquivo.wlang
Exemplowlang :index, :locals => { :key => 'value' }
+ +Já que você não pode chamar o Ruby (exceto pelo método +`yield`) pelo template WLang, +você quase sempre precisará passar o `locals` para ele. + +## Acessando Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto como manipuladores de +rota. Variáveis de instância setadas em rotas manipuladas são +diretamente acessadas por templates: + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito para variáveis locais: + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizando quando renderizamos templates como +partials dentro de outros templates. + +### Templates com `yield` e layouts aninhados + +O layout geralmente é apenas um template que executa `yield`. +Tal template pode ser utilizado pela opção `:template` descrita acima ou pode ser renderizado através de um bloco, como a seguir: + +``` ruby +erb :post, :layout => false do + erb :index +end +``` + +Este código é quase equivalente a `erb :index, :layout => :post` + +Passando blocos para os métodos de renderização é útil para criar layouts aninhados: + +``` ruby +erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end +end +``` + +Também pode ser feito com menos linhas de código: + +``` ruby +erb :admin_layout, :layout => :main_layout do + erb :user +end +``` + +Atualmente os métodos listados aceitam blocos: `erb`, `haml`, +`liquid`, `slim `, `wlang`. E também o método `render`. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte(.rb): + +``` ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo. +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo sinatra. Digite \`enable :inline\_templates\` se você +tem templates inline no outro arquivo fonte. + +### Templates nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado toda vez +que um template for renderizado. Você pode desabilitar layouts passando +`:layout => false`. + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Associando extensões de arquivos + +Para associar uma extensão de arquivo com um engine de template use o método `Tilt.register`. Por exemplo, se você quiser usar a extensão `tt` para os templates Textile você pode fazer o seguinte: + +``` ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Adicionando seu Próprio Engine de Template + +Primeiro registre seu engine utilizando o Tilt, e então crie um método de renderização: + +``` ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Renderize `./views/index.myat`. Veja +https://github.com/rtomayko/tilt para saber mais sobre Tilt. + +### Customizando lógica para encontrar templates + +Para implementar sua própria lógica para busca de templates você pode escrever seu próprio método `#find_template` + +``` ruby +configure do + set :views [ './views/a', './views/b' ] +end + +def find_template(views, name, engine, &block) + Array(views).each do |v| + super(v, name, engine, &block) + end +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância setadas nos filtros são acessadas através de rotas e +templates: + +``` ruby +before do + @nota = 'Oi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Oi!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do contexto da +requisição e também podem modificar a requisição e a resposta. Variáveis de +instância e rotas definidas nos filtros before são acessadas através dos +filtros after: + +``` ruby +after do + puts response.status +end +``` + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +``` ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +``` ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +### Utilizando Sessões + +Sessões são usadas para manter um estado durante uma requisição. Se ativa, você terá disponível um hash de sessão para cada sessão de usuário: + +``` ruby +enable :sessions + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +Note que `enable :sessions` utilizará um cookie para guardar todos os dados da sessão. Isso nem sempre pode ser o que você quer (guardar muitos dados irá aumentar o seu tráfego, por exemplo). Você pode utilizar qualquer Rack middleware de sessão: para fazer isso **não** utilize o método `enable :sessions`, ao invés disso utilize seu middleware de sessão como utilizaria qualquer outro: + +``` ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session[:value].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +Para melhorar a segurança, os dados da sessão guardados no cookie é assinado com uma chave secreta da sessão. Uma chave aleatória é gerada para você pelo Sinatra. Contudo, já que a chave mudará cada vez que você inicia sua aplicação, você talvez queira defini-la você mesmo para que todas as instâncias da aplicação compartilhe-a: + +``` ruby +set :session_secret, 'super secret' +``` + +Se você quiser fazer outras configurações, você também pode guardar um hash com as opções nas configurações da `session`: + +``` ruby +set :sessions, :domain => 'foo.com' +``` + +Para compartilhar sua sessão entre outros aplicativos em um subdomínio de foo.com, utilize o prefixo *.*: + +``` ruby +set :sessions, :domain => '.foo.com' +``` + +### Halting + +Para parar imediatamente uma requisição com um filtro ou rota utilize: + +``` ruby +halt +``` + +Você também pode especificar o status quando parar… + +``` ruby +halt 410 +``` + +Ou com corpo de texto… + +``` ruby +halt 'isso será o corpo do texto' +``` + +Ou também… + +``` ruby +halt 401, 'vamos embora!' +``` + +Com cabeçalhos… + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +É obviamente possivel combinar um template com o `halt`: + +``` ruby +halt erb(:error) +``` + +### Passing + +Uma rota pode processar aposta para a próxima rota correspondente usando +`pass`: + +``` ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Você me pegou!' +end + +get '/adivinhar/*' do + 'Você falhou!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um +404 é retornado. + +### Desencadeando Outra Rota + +As vezes o `pass` não é o que você quer, ao invés dele talvez você queira obter o resultado chamando outra rota. Utilize o método `call` neste caso: + +``` ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Note que no exemplo acima você ganharia performance ao simplemente mover o `"bar"` em um helper usado por ambos `/foo` e `/bar`. + +Se você quer que a requisição seja enviada para a mesma instancia da aplicação ao invês de uma duplicada, use `call!` no lugar de `call`. + +Veja a especificação do Rack se você quer aprender mais sobre o `call`. + +## Configuração + +Rodando uma vez, na inicialização, em qualquer ambiente: + +``` ruby +configure do + ... +end +``` + +Rodando somente quando o ambiente (`RACK_ENV` environment variável) é +setado para `:production`: + +``` ruby +configure :production do + ... +end +``` + +Rodando quando o ambiente é setado para `:production` ou `:test`: + +``` ruby +configure :production, :test do + ... +end +``` + +## Tratamento de Erros + +Tratamento de erros rodam dentro do mesmo contexto como rotas e filtros +before, o que significa que você pega todos os presentes que tem para +oferecer, como `haml`, `erb`, `halt`, etc. + +### Não Encontrado + +Quando um `Sinatra::NotFound` exception é levantado, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +``` ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado toda a vez que uma exceção é lançada a +partir de um bloco de rota ou um filtro. O objeto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +``` ruby +error do + 'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros customizados: + +``` ruby +error MeuErroCustomizado do + 'Então que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +``` ruby +get '/' do + raise MeuErroCustomizado, 'alguma coisa ruim' +end +``` + +Você receberá isso: + + Então que aconteceu foi... alguma coisa ruim + +Alternativamente, você pode instalar manipulador de erro para um código +de status: + +``` ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um range: + +``` ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra instala os manipuladores especiais `not_found` e `error` +quando roda sobre o ambiente de desenvolvimento. + +## Mime Types + +Quando utilizamos `send_file` ou arquivos estáticos você pode ter mime +types Sinatra não entendidos. Use `mime_type` para registrar eles por +extensão de arquivos: + +``` ruby +mime_type :foo, 'text/foo' +``` + +Você também pode utilizar isto com o helper `content_type`: + +``` ruby +content_type :foo +``` + +## Rack Middleware + +O Sinatra roda no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Um das capacidades mais +interessantes do Rack para desenvolver aplicativos é suporte a +“middleware” – componentes que ficam entre o servidor e sua aplicação +monitorando e/ou manipulando o request/response do HTTP para prover +vários tipos de funcionalidades comuns. + +O Sinatra faz construtores pipelines do middleware Rack facilmente em um +nível superior utilizando o método `use`: + +``` ruby +require 'sinatra' +require 'meu_middleware_customizado' + +use Rack::Lint +use MeuMiddlewareCustomizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis bem como blocos: + +``` ruby +use Rack::Auth::Basic do |usuario, senha| + usuario == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuido com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão. Sinatra +utilizada muitos desses componentes automaticamente baseando sobre +configuração, então, tipicamente você não tem `use` explicitamente. + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +``` ruby +require 'minha_aplicacao_sinatra' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parametros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Os módulos de classe embutidos `Sinatra::Test` e +`Sinatra::TestHarness` são depreciados na versão 0.9.2. + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação em um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis incovenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: uma simples +arquivo de aplicação, diretórios `./public` e `./views`, logs, página de +detalhes de exceção, etc.). É onde o `Sinatra::Base` entra em jogo: + +``` ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Ola mundo!' + end +end +``` + +A classe `MinhaApp` é um componente Rack independente que pode agir como +um middleware Rack, uma aplicação Rack, ou metal Rails. Você pode +utilizar ou executar esta classe com um arquivo rackup `config.ru`; +ou, controlar um componente de servidor fornecendo como biblioteca: + +``` ruby +MinhaApp.run! :host => 'localhost', :port => 9090 +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +- Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; + outra coisa, todos os métodos DSL do Sinatra são importados para o + espaço principal. + +- Coloque as rotas da sua aplicação, manipuladores de erro, filtros e + opções na subclasse de um `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desabilitadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://sinatra.github.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. + +SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples +sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial +da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, +`:before`, `:error`, `:not_found`, `:configure`, e `:set messages` enviados +para o alto nível. Dê uma olhada no código você mesmo: aqui está o +[Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) +sendo [incluido dentro de um espaço +principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) + +## Linha de Comando + +Aplicações Sinatra podem ser executadas diretamente: + +``` shell +ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # ativa o bloqueio (padrão é desligado) +``` + +## A última versão + +Se você gostaria de utilizar o código da última versão do Sinatra, crie +um clone local e execute sua aplicação com o diretório `sinatra/lib` no +`LOAD_PATH`: + +``` shell +cd minhaapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib minhaapp.rb +``` + +Alternativamente, você pode adicionar o diretório do `sinatra/lib` no +`LOAD_PATH` do seu aplicativo: + +``` ruby +$LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' +require 'rubygems' +require 'sinatra' + +get '/sobre' do + "Estou rodando a versão" + Sinatra::VERSION +end +``` + +Para atualizar o código do Sinatra no futuro: + +``` shell +cd meuprojeto/sinatra +git pull +``` + +## Mais + +* [Website do Projeto](http://www.sinatrarb.com/) - Documentação + adicional, novidades e links para outros recursos. +* [Contribuir](http://www.sinatrarb.com/contributing) - Encontrar um + bug? Precisa de ajuda? Tem um patch? +* [Acompanhar Questões](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Lista de Email](http://groups.google.com/group/sinatrarb/topics) +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Livro de Receitas +* Documentação da API para a [última release](http://rubydoc.info/gems/sinatra) +* [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em + [freenode.net](http://freenode.net) +* [Servidor de CI](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-pt.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-pt.md new file mode 100644 index 000000000..474d50ab1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.pt-pt.md @@ -0,0 +1,791 @@ +# Sinatra + +*Atenção: Este documento é apenas uma tradução da versão em inglês e +pode estar desatualizado.* + +Sinatra é uma +[DSL](http://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para +criar rapidamente aplicações web em Ruby com o mínimo de esforço: + +``` ruby +# minhaapp.rb +require 'rubygems' +require 'sinatra' +get '/' do + 'Olá Mundo!' +end +``` + +Instale a gem e execute com: + +``` shell +sudo gem install sinatra +ruby minhaapp.rb +``` + +Aceda em: [localhost:4567](http://localhost:4567) + +## Rotas + +No Sinatra, uma rota é um metodo HTTP associado a uma URL correspondente +padrão. Cada rota é associada a um bloco: + +``` ruby +get '/' do + .. mostrar algo .. +end + +post '/' do + .. criar algo .. +end + +put '/' do + .. atualizar algo .. +end + +delete '/' do + .. apagar algo .. +end +``` + +Rotas são encontradas na ordem em que são definidas. A primeira rota que +é encontrada invoca o pedido. + +Padrões de rota podem incluir parâmetros nomeados, acessíveis através da +hash `params`: + +``` ruby +get '/ola/:nome' do + # corresponde a "GET /ola/foo" e "GET /ola/bar" + # params['nome'] é 'foo' ou 'bar' + "Olá #{params['nome']}!" +end +``` + +Pode também aceder a parâmetros nomeados através do bloco de parâmetros: + +``` ruby +get '/ola/:nome' do |n| + "Olá #{n}!" +end +``` + +Padrões de rota podem também incluir parâmetros splat (asteriscos), +acessíveis através do array `params['splat']`. + +``` ruby +get '/diga/*/ao/*' do + # corresponde a /diga/ola/ao/mundo + params['splat'] # => ["ola", "mundo"] +end + +get '/download/*.*' do + # corresponde a /download/pasta/do/arquivo.xml + params['splat'] # => ["pasta/do/arquivo", "xml"] +end +``` + +Rotas correspondem-se com expressões regulares: + +``` ruby +get /\A\/ola\/([\w]+)\z/ do + "Olá, #{params['captures'].first}!" +end +``` + +Ou com um bloco de parâmetro: + +``` ruby +get %r{/ola/([\w]+)} do |c| + "Olá, #{c}!" +end +``` + +Rotas podem incluir uma variedade de condições correspondentes, por +exemplo, o agente usuário: + +``` ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "Você está a utilizar a versão #{params['agent'][0]} do Songbird." +end + +get '/foo' do + # Corresponde a um navegador não Songbird +end +``` + +## Arquivos estáticos + +Arquivos estáticos são disponibilizados a partir do directório +`./public`. Você pode especificar um local diferente através da opção +`:public_folder` + +``` ruby +set :public_folder, File.dirname(__FILE__) + '/estatico' +``` + +Note que o nome do directório público não é incluido no URL. Um arquivo +`./public/css/style.css` é disponibilizado como +`http://example.com/css/style.css`. + +## Views / Templates + +Templates presumem-se estar localizados sob o directório `./views`. Para +utilizar um directório de views diferente: + +``` ruby +set :views, File.dirname(__FILE__) + '/modelo' +``` + +Uma coisa importante a ser lembrada é que você sempre tem as referências +dos templates como símbolos, mesmo se eles estiverem num sub-directório +(nesse caso utilize `:'subdir/template'`). Métodos de renderização irão +processar qualquer string passada directamente para elas. + +### Haml Templates + +A gem/biblioteca haml é necessária para renderizar templates HAML: + +``` ruby +# É necessário requerir 'haml' na aplicação. +require 'haml' + +get '/' do + haml :index +end +``` + +Renderiza `./views/index.haml`. + +[Opções +Haml](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +``` ruby +set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml + +get '/' do + haml :index, :haml_options => {:format => :html4 } # substituido +end +``` + +### Erb Templates + +``` ruby +# É necessário requerir 'erb' na aplicação. +require 'erb' + +get '/' do + erb :index +end +``` + +Renderiza `./views/index.erb` + +### Erubis + +A gem/biblioteca erubis é necessária para renderizar templates erubis: + +``` ruby +# É necessário requerir 'erubis' na aplicação. +require 'erubis' + +get '/' do + erubis :index +end +``` + +Renderiza `./views/index.erubis` + +### Builder Templates + +A gem/biblioteca builder é necessária para renderizar templates builder: + +``` ruby +# É necessário requerir 'builder' na aplicação. +require 'builder' + +get '/' do + content_type 'application/xml', :charset => 'utf-8' + builder :index +end +``` + +Renderiza `./views/index.builder`. + +### Sass Templates + +A gem/biblioteca sass é necessária para renderizar templates sass: + +``` ruby +# É necessário requerir 'haml' ou 'sass' na aplicação. +require 'sass' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet +end +``` + +Renderiza `./views/stylesheet.sass`. + +[Opções +Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) +podem ser definidas globalmente através das configurações do sinatra, +veja [Opções e +Configurações](http://www.sinatrarb.com/configuration.html), e substitua +em uma requisição individual. + +``` ruby +set :sass, {:style => :compact } # o estilo padrão do Sass é :nested + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + sass :stylesheet, :style => :expanded # substituido +end +``` + +### Less Templates + +A gem/biblioteca less é necessária para renderizar templates Less: + +``` ruby +# É necessário requerir 'less' na aplicação. +require 'less' + +get '/stylesheet.css' do + content_type 'text/css', :charset => 'utf-8' + less :stylesheet +end +``` + +Renderiza `./views/stylesheet.less`. + +### Templates Inline + +``` ruby +get '/' do + haml '%div.title Olá Mundo' +end +``` + +Renderiza a string, em uma linha, no template. + +### Acedendo a Variáveis nos Templates + +Templates são avaliados dentro do mesmo contexto que os manipuladores de +rota. Variáveis de instância definidas em rotas manipuladas são +directamente acedidas por templates: + +``` ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.nome' +end +``` + +Ou, especifique um hash explícito para variáveis locais: + +``` ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.nome', :locals => { :foo => foo } +end +``` + +Isso é tipicamente utilizado quando renderizamos templates parciais +(partials) dentro de outros templates. + +### Templates Inline + +Templates podem ser definidos no final do arquivo fonte(.rb): + +``` ruby +require 'rubygems' +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Olá Mundo!!!!! +``` + +NOTA: Templates inline definidos no arquivo fonte são automaticamente +carregados pelo sinatra. Digite \`enable :inline\_templates\` se tem +templates inline no outro arquivo fonte. + +### Templates nomeados + +Templates também podem ser definidos utilizando o método top-level +`template`: + +``` ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Olá Mundo!' +end + +get '/' do + haml :index +end +``` + +Se existir um template com nome “layout”, ele será utilizado sempre que +um template for renderizado. Pode desactivar layouts usando +`:layout => false`. + +``` ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +## Helpers + +Use o método de alto nível `helpers` para definir métodos auxiliares +para utilizar em manipuladores de rotas e modelos: + +``` ruby +helpers do + def bar(nome) + "#{nome}bar" + end +end + +get '/:nome' do + bar(params['nome']) +end +``` + +## Filtros + +Filtros Before são avaliados antes de cada requisição dentro do contexto +da requisição e podem modificar a requisição e a reposta. Variáveis de +instância definidas nos filtros são acedidas através de rotas e +templates: + +``` ruby +before do + @nota = 'Olá!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @nota #=> 'Olá!' + params['splat'] #=> 'bar/baz' +end +``` + +Filtros After são avaliados após cada requisição dentro do contexto da +requisição e também podem modificar o pedido e a resposta. Variáveis de +instância definidas nos filtros before e rotas são acedidas através dos +filtros after: + +``` ruby +after do + puts response.status +end +``` + +Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados +somente se o caminho do pedido coincidir com esse padrão: + +``` ruby +before '/protected/*' do + autenticar! +end + +after '/create/:slug' do |slug| + session[:last_slug] = slug +end +``` + +## Halting + +Para parar imediatamente uma requisição dentro de um filtro ou rota +utilize: + +``` ruby +halt +``` + +Pode também especificar o status ao parar… + +``` ruby +halt 410 +``` + +Ou com um corpo de texto… + +``` ruby +halt 'isto será o corpo de texto' +``` + +Ou também… + +``` ruby +halt 401, 'vamos embora!' +``` + +Com cabeçalhos… + +``` ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revanche' +``` + +## Passing + +Dentro de uma rota, pode passar para a próxima rota correspondente +usando `pass`: + +``` ruby +get '/adivinhar/:quem' do + pass unless params['quem'] == 'Frank' + 'Apanhaste-me!' +end + +get '/adivinhar/*' do + 'Falhaste!' +end +``` + +O bloqueio da rota é imediatamente encerrado e o controle continua com a +próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um +404 é retornado. + +## Configuração + +Correndo uma vez, na inicialização, em qualquer ambiente: + +``` ruby +configure do + ... +end +``` + +Correndo somente quando o ambiente (`RACK_ENV` environment variável) é +definido para `:production`: + +``` ruby +configure :production do + ... +end +``` + +Correndo quando o ambiente é definido para `:production` ou `:test`: + +``` ruby +configure :production, :test do + ... +end +``` + +## Lidar com Erros + +Lida-se com erros no mesmo contexto das rotas e filtros before, o que +signifca que `haml`, `erb`, etc, estão disponíveis. + +### Não Encontrado + +Quando um `Sinatra::NotFound` exception é levantado, ou o código de +status da reposta é 404, o manipulador `not_found` é invocado: + +``` ruby +not_found do + 'Isto está longe de ser encontrado' +end +``` + +### Erro + +O manipulador `error` é invocado sempre que uma exceção é lançada a +partir de um bloco de rota ou um filtro. O objecto da exceção pode ser +obtido a partir da variável Rack `sinatra.error`: + +``` ruby +error do + 'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].message +end +``` + +Erros personalizados: + +``` ruby +error MeuErroPersonalizado do + 'O que aconteceu foi...' + env['sinatra.error'].message +end +``` + +Então, se isso acontecer: + +``` ruby +get '/' do + raise MeuErroPersonalizado, 'alguma coisa desagradável' +end +``` + +O resultado será: + +``` +O que aconteceu foi...alguma coisa desagradável +``` + +Alternativamente, pode definir um manipulador de erro para um código de +status: + +``` ruby +error 403 do + 'Accesso negado' +end + +get '/secreto' do + 403 +end +``` + +Ou um range (alcance): + +``` ruby +error 400..510 do + 'Boom' +end +``` + +O Sinatra define os manipuladores especiais `not_found` e `error` quando +corre no ambiente de desenvolvimento. + +## Mime Types + +Quando utilizamos `send_file` ou arquivos estáticos pode ter mime types +Sinatra não entendidos. Use `mime_type` para os registar por extensão de +arquivos: + +``` ruby +mime_type :foo, 'text/foo' +``` + +Pode também utilizar isto com o helper `content_type`: + +``` ruby +content_type :foo +``` + +## Middleware Rack + +O Sinatra corre no [Rack](http://rack.github.io/), uma interface +padrão mínima para frameworks web em Ruby. Uma das capacidades mais +interessantes do Rack, para desenvolver aplicações, é o suporte de +“middleware” – componentes que residem entre o servidor e a aplicação, +monitorizando e/ou manipulando o pedido/resposta (request/response) HTTP +para providenciar varios tipos de funcionalidades comuns. + +O Sinatra torna a construção de pipelines do middleware Rack fácil a um +nível superior utilizando o método `use`: + +``` ruby +require 'sinatra' +require 'meu_middleware_personalizado' + +use Rack::Lint +use MeuMiddlewarePersonalizado + +get '/ola' do + 'Olá mundo' +end +``` + +A semântica de `use` é idêntica aquela definida para a DSL +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) +(mais frequentemente utilizada para arquivos rackup). Por exemplo, o +método `use` aceita múltiplos argumentos/variáveis, bem como blocos: + +``` ruby +use Rack::Auth::Basic do |utilizador, senha| + utilizador == 'admin' && senha == 'secreto' +end +``` + +O Rack é distribuido com uma variedade de middleware padrões para logs, +debugs, rotas de URL, autenticação, e manipuladores de sessão.Sinatra +utiliza muitos desses componentes automaticamente dependendo da +configuração, por isso, tipicamente nao é necessário utilizar `use` +explicitamente. + +## Testando + +Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou +framework de teste baseados no Rack. +[Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: + +``` ruby +require 'minha_aplicacao_sinatra' +require 'rack/test' + +class MinhaAplicacaoTeste < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def meu_test_default + get '/' + assert_equal 'Ola Mundo!', last_response.body + end + + def teste_com_parametros + get '/atender', :name => 'Frank' + assert_equal 'Olá Frank!', last_response.bodymeet + end + + def test_com_ambiente_rack + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "Você está utilizando o Songbird!", last_response.body + end +end +``` + +NOTA: Os módulos de classe embutidos `Sinatra::Test` e +`Sinatra::TestHarness` são depreciados na versão 0.9.2. + +## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares + +Definir sua aplicação a um nível superior de trabalho funciona bem para +micro aplicativos, mas tem consideráveis incovenientes na construção de +componentes reutilizáveis como um middleware Rack, metal Rails, +bibliotecas simples como um componente de servidor, ou mesmo extensões +Sinatra. A DSL de nível superior polui o espaço do objeto e assume um +estilo de configuração de micro aplicativos (exemplo: um simples arquivo +de aplicação, directórios `./public` e `./views`, logs, página de detalhes +de excepção, etc.). É onde o Sinatra::Base entra em jogo: + +``` ruby +require 'sinatra/base' + +class MinhaApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Olá mundo!' + end +end +``` + +A classe MinhaApp é um componente Rack independente que pode utilizar +como um middleware Rack, uma aplicação Rack, ou metal Rails. Pode +utilizar ou executar esta classe com um arquivo rackup `config.ru`; +ou, controlar um componente de servidor fornecendo como biblioteca: + +``` ruby +MinhaApp.run! :host => 'localhost', :port => 9090 +``` + +Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como +aqueles disponíveis via a DSL de nível superior. Aplicações de nível +mais alto podem ser convertidas para componentes `Sinatra::Base` com duas +modificações: + +- Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; + outra coisa, todos os métodos DSL do Sinatra são importados para o + espaço principal. + +- Coloque as rotas da sua aplicação, manipuladores de erro, filtros e + opções na subclasse de um `Sinatra::Base`. + +`Sinatra::Base` é um quadro branco. Muitas opções são desactivadas por +padrão, incluindo o servidor embutido. Veja [Opções e +Configurações](http://sinatra.github.com/configuration.html) para +detalhes de opções disponíveis e seus comportamentos. + +SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples +sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial +da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, +`:before`, `:error`, `:not_found`, `:configure`, e `:set` messages enviados +para o alto nível. Dê você mesmo uma vista de olhos ao código: aqui está o +[Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) +sendo [incluido dentro de um espaço +principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) + +## Linha de Comandos + +As aplicações Sinatra podem ser executadas directamente: + +``` shell +ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] +``` + +As opções são: + +``` +-h # ajuda +-p # define a porta (padrão é 4567) +-o # define o host (padrão é 0.0.0.0) +-e # define o ambiente (padrão é development) +-s # especifica o servidor/manipulador rack (padrão é thin) +-x # activa o bloqueio (padrão é desligado) +``` + +## A última versão + +Se gostaria de utilizar o código da última versão do Sinatra, crie um +clone local e execute sua aplicação com o directório `sinatra/lib` no +`LOAD_PATH`: + +``` shell +cd minhaapp +git clone git://github.com/sinatra/sinatra.git +ruby -I sinatra/lib minhaapp.rb +``` + +Alternativamente, pode adicionar o directório do `sinatra/lib` no +`LOAD_PATH` do seu aplicativo: + +``` ruby +$LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' +require 'rubygems' +require 'sinatra' + +get '/sobre' do + "Estou correndo a versão" + Sinatra::VERSION +end +``` + +Para actualizar o código do Sinatra no futuro: + +``` shell +cd meuprojeto/sinatra +git pull +``` + +## Mais + +- [Website do Projeto](http://www.sinatrarb.com/) - Documentação + adicional, novidades e links para outros recursos. + +- [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um + bug? Precisa de ajuda? Tem um patch? + +- [Acompanhar Questões](http://github.com/sinatra/sinatra/issues) + +- [Twitter](http://twitter.com/sinatra) + +- [Lista de Email](http://groups.google.com/group/sinatrarb/topics) + +- [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em + [freenode.net](http://freenode.net) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ru.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ru.md new file mode 100644 index 000000000..503b5ffca --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.ru.md @@ -0,0 +1,2835 @@ +# Sinatra + +*Внимание: Этот документ является переводом английской версии и может быть +устаревшим* + +Sinatra — это предметно-ориентированный каркас +([DSL](http://ru.wikipedia.org/wiki/Предметно-ориентированный_язык_программирования)) +для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий: + +```ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +Установите gem: + +``` shell +gem install sinatra +``` + +и запустите приложение с помощью: + +``` shell +ruby myapp.rb +``` + +Оцените результат: http://localhost:4567 + +Рекомендуется также установить Thin, сделать это можно командой: `gem install +thin`. Thin — это более производительный и функциональный сервер для +разработки приложений на Sinatra. + +## Маршруты + +В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут +связан с блоком кода: + +```ruby +get '/' do + # .. что-то показать .. +end + +post '/' do + # .. что-то создать .. +end + +put '/' do + # .. что-то заменить .. +end + +patch '/' do + # .. что-то изменить .. +end + +delete '/' do + # .. что-то удалить .. +end + +options '/' do + # .. что-то ответить .. +end + +link '/' do + .. что-то подключить .. +end + +unlink '/' do + .. что-то отключить .. +end +``` + +Маршруты сверяются с запросом в порядке очередности их записи в файле +приложения. Первый же совпавший с запросом маршрут и будет вызван. + +Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше +`params`: + +```ruby +get '/hello/:name' do + # соответствует "GET /hello/foo" и "GET /hello/bar", + # где params['name'] 'foo' или 'bar' + "Hello #{params['name']}!" +end +``` + +Также можно использовать именованные параметры в качестве переменных блока: + +```ruby +get '/hello/:name' do |n| + "Hello #{n}!" +end +``` + +Шаблоны маршрутов также могут включать в себя splat (или '*' маску, +обозначающую любой символ) параметры, доступные в массиве `params['splat']`: + +```ruby +get '/say/*/to/*' do + # соответствует /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # соответствует /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +``` + +Или с параметрами блока: + +```ruby +get '/download/*.*' do |path, ext| + [path, ext] # => ["path/to/file", "xml"] +end +``` + +Регулярные выражения в качестве шаблонов маршрутов: + +```ruby +get /\A\/hello\/([\w]+)\z/ do + "Hello, #{params['captures'].first}!" +end +``` + +Или с параметром блока: + +```ruby +# Находит "GET /meta/hello/world", "GET /hello/world/1234" и так далее +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +``` + +Шаблоны маршрутов могут иметь необязательные параметры: + +```ruby +get '/posts.?:format?' do + # соответствует "GET /posts", "GET /posts.json", "GET /posts.xml" и т.д. +end +``` + +Кстати, если вы не отключите защиту от обратного пути в директориях (path +traversal, см. ниже), путь запроса может быть изменен до начала поиска +подходящего маршрута. + +### Условия + +Маршруты могут включать различные условия совпадений, например, клиентское +приложение (user agent): + +```ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "You're using Songbird version #{params['agent'][0]}" +end + +get '/foo' do + # соответствует не-songbird браузерам +end +``` + +Другими доступными условиями являются `host_name` и `provides`: + +```ruby +get '/', :host_name => /^admin\./ do + "Admin Area, Access denied!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +``` + +Вы можете задать собственные условия: + +```ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +``` + +Для условия, которое принимает несколько параметров, используйте звездочку: + +```ruby +set(:auth) do |*roles| # <- обратите внимание на звездочку + condition do + unless logged_in? && roles.any? {|role| current_user.in_role? role } + redirect "/login/", 303 + end + end +end + +get "/my/account/", :auth => [:user, :admin] do + "Your Account Details" +end + +get "/only/admin/", :auth => :admin do + "Only admins are allowed here!" +end +``` + +### Возвращаемые значения + +Возвращаемое значение блока маршрута ограничивается телом ответа, которое +будет передано HTTP клиенту, или следующей "прослойкой" (middleware) в Rack +стеке. Чаще всего это строка, как в примерах выше. Но также приемлемы и +другие значения. + +Вы можете вернуть любой объект, который будет либо корректным Rack ответом, +объектом Rack body, либо кодом состояния HTTP: + +* массив с тремя переменными: `[код (Fixnum), заголовки (Hash), тело ответа + (должно отвечать на #each)]`; +* массив с двумя переменными: `[код (Fixnum), тело ответа (должно отвечать + на #each)]`; +* объект, отвечающий на `#each`, который передает только строковые типы + данных в этот блок; +* Fixnum, представляющий код состояния HTTP. + + +Таким образом, легко можно реализовать, например, поточный пример: + +```ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +``` + +Вы также можете использовать метод `stream` (описываемый ниже), чтобы +уменьшить количество дублируемого кода и держать логику стриминга прямо в +маршруте. + +### Собственные детекторы совпадений для маршрутов + +Как показано выше, Sinatra поставляется со встроенной поддержкой строк и +регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете +легко определить свои собственные детекторы совпадений (matchers) для +маршрутов: + +```ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +``` + +Заметьте, что предыдущий пример, возможно, чересчур усложнен, потому что он +может быть реализован так: + +```ruby +get // do + pass if request.path_info == "/index" + # ... +end +``` + +Или с использованием негативного просмотра вперед: + +```ruby +get %r{^(?!/index$)} do + # ... +end +``` + +## Статические файлы + +Статические файлы отдаются из `./public` директории. Вы можете указать другое +место, используя опцию `:public_folder`: + +```ruby +set :public_folder, File.dirname(__FILE__) + '/static' +``` + +Учтите, что имя директории со статическими файлами не включено в URL. +Например, файл `./public/css/style.css` будет доступен как +`http://example.com/css/style.css`. + +Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок +`Cache-Control`. + +## Представления / Шаблоны + +Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту +возвращают строку: + +```ruby +get '/' do + erb :index +end +``` + +Отобразит `views/index.erb`. + +Вместо имени шаблона вы так же можете передавать непосредственно само +содержимое шаблона: + +```ruby +get '/' do + code = "<%= Time.now %>" + erb code +end +``` + +Эти методы принимают второй аргумент, хеш с опциями: + +```ruby +get '/' do + erb :index, :layout => :post +end +``` + +Отобразит `views/index.erb`, вложенным в `views/post.erb` (по умолчанию: +`views/layout.erb`, если существует). + +Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор: + +```ruby +get '/' do + haml :index, :format => :html5 +end +``` + +Вы также можете задавать опции для шаблонизаторов в общем: + +```ruby +set :haml, :format => :html5 + +get '/' do + haml :index +end +``` + +Опции, переданные в метод, переопределяют опции, заданные с помощью `set`. + +Доступные опции: + +
+
locals
+
+ Список локальных переменных, передаваемых в документ. + Например: erb "<%= foo %>", :locals => {:foo => "bar"} +
+ +
default_encoding
+
+ Кодировка, которую следует использовать, если не удалось определить + оригинальную. По умолчанию: settings.default_encoding. +
+ +
views
+
+ Директория с шаблонами. По умолчанию: settings.views. +
+ +
layout
+
+ Использовать или нет лэйаут (true или false). Если же значение Symbol, + то указывает, какой шаблон использовать в качестве лэйаута. Например: + erb :index, :layout => !request.xhr? +
+ +
content_type
+
+ Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором. +
+ +
scope
+
+ Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр + приложения. Если вы измените эту опцию, то переменные экземпляра и + методы-помощники станут недоступными в ваших шаблонах. +
+ +
layout_engine
+
+ Шаблонизатор, который следует использовать для отображения лэйаута. + Полезная опция для шаблонизаторов, в которых нет никакой поддержки + лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого + шаблона. Пример: set :rdoc, :layout_engine => :erb +
+
+ +По умолчанию считается, что шаблоны находятся в директории `./views`. Чтобы +использовать другую директорию с шаблонами: + +```ruby +set :views, settings.root + '/templates' +``` + +Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов +(Symbol), даже когда они в поддиректории (в этом случае используйте +`:'subdir/template'`). Вы должны использовать символы, потому что иначе +шаблонизаторы попросту отображают любые строки, переданные им. + +### Буквальные шаблоны + +```ruby +get '/' do + haml '%div.title Hello World' +end +``` + +Отобразит шаблон, переданный строкой. + +### Доступные шаблонизаторы + +Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую +реализацию использовать, вам следует просто подключить нужную библиотеку: + +```ruby +require 'rdiscount' # или require 'bluecloth' +get('/') { markdown :index } +``` + +#### Haml шаблоны + + + + + + + + + + + + + + +
Зависимостиhaml
Расширения файлов.haml
Примерhaml :index, :format => :html5
+ +#### Erb шаблоны + + + + + + + + + + + + + + +
Зависимости + erubis + или erb (включен в Ruby) +
Расширения файлов.erb, .rhtml or .erubis (только Erubis)
Примерerb :index
+ +#### Builder шаблоны + + + + + + + + + + + + + + +
Зависимости + builder +
Расширения файлов.builder
Примерbuilder { |xml| xml.em "hi" }
+ +Блок также используется и для встроенных шаблонов (см. пример). + +#### Nokogiri шаблоны + + + + + + + + + + + + + + +
Зависимостиnokogiri
Расширения файлов.nokogiri
Примерnokogiri { |xml| xml.em "hi" }
+ +Блок также используется и для встроенных шаблонов (см. пример). + +#### Sass шаблоны + + + + + + + + + + + + + + +
Зависимостиsass
Расширения файлов.sass
Примерsass :stylesheet, :style => :expanded
+ +#### SCSS шаблоны + + + + + + + + + + + + + + +
Зависимостиsass
Расширения файлов.scss
Примерscss :stylesheet, :style => :expanded
+ +#### Less шаблоны + + + + + + + + + + + + + + +
Зависимостиless
Расширения файлов.less
Примерless :stylesheet
+ +#### Liquid шаблоны + + + + + + + + + + + + + + +
Зависимостиliquid
Расширения файлов.liquid
Примерliquid :index, :locals => { :key => 'value' }
+ +Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме `yield`), то +вы почти всегда будете передавать в шаблон локальные переменные. + +#### Markdown шаблоны + + + + + + + + + + + + + + +
Зависимости + Любая из библиотек: + RDiscount, + RedCarpet, + BlueCloth, + kramdown, + maruku +
Расширения файлов.markdown, .mkd and .md
Примерmarkdown :index, :layout_engine => :erb
+ +В Markdown невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придется использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => markdown(:introduction) } +``` + +Заметьте, что вы можете вызывать метод `markdown` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +``` + +Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете +использовать лэйауты на Markdown. Тем не менее, есть возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью +опции `:layout_engine`. + +#### Textile шаблоны + + + + + + + + + + + + + + +
ЗависимостиRedCloth
Расширения файлов.textile
Примерtextile :index, :layout_engine => :erb
+ +В Textile невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придется использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => textile(:introduction) } +``` + +Заметьте, что вы можете вызывать метод `textile` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= textile(:greetings) +``` + +Вы не можете вызывать Ruby из Textile, соответственно, вы не можете +использовать лэйауты на Textile. Тем не менее, есть возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью +опции `:layout_engine`. + +#### RDoc шаблоны + + + + + + + + + + + + + + +
ЗависимостиRDoc
Расширения файлов.rdoc
Примерrdoc :README, :layout_engine => :erb
+ +В RDoc невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придется использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +``` + +Заметьте, что вы можете вызывать метод `rdoc` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +``` + +Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать +лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор +для отображения шаблона, а другой для лэйаута с помощью опции +`:layout_engine`. + +#### AsciiDoc шаблоны + + + + + + + + + + + + + + +
ЗависимостиAsciidoctor
Расширения файлов.asciidoc, .adoc и .ad
Примерasciidoc :README, :layout_engine => :erb
+ +Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Radius шаблоны + + + + + + + + + + + + + + +
ЗависимостиRadius
Расширения файлов.radius
Примерradius :index, :locals => { :key => 'value' }
+ +Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы +почти всегда будете передавать в шаблон локальные переменные. + +#### Markaby шаблоны + + + + + + + + + + + + + + +
ЗависимостиMarkaby
Расширения файлов.mab
Примерmarkaby { h1 "Welcome!" }
+ +Блок также используется и для встроенных шаблонов (см. пример). + +#### RABL шаблоны + + + + + + + + + + + + + + +
ЗависимостиRabl
Расширения файлов.rabl
Примерrabl :index
+ +#### Slim шаблоны + + + + + + + + + + + + + + +
ЗависимостиSlim Lang
Расширения файлов.slim
Примерslim :index
+ +#### Creole шаблоны + + + + + + + + + + + + + + +
ЗависимостиCreole
Расширения файлов.creole
Примерcreole :wiki, :layout_engine => :erb
+ +В Creole невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придется использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => creole(:introduction) } +``` + +Заметьте, что вы можете вызывать метод `creole` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= creole(:greetings) +``` + +Вы не можете вызывать Ruby из Creole, соответственно, вы не можете +использовать лэйауты на Creole. Тем не менее, есть возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью +опции `:layout_engine`. + +#### MediaWiki шаблоны + + + + + + + + + + + + + + +
ЗависимостиWikiCloth
Расширения файлов.mediawiki и .mw
Примерmediawiki :wiki, :layout_engine => :erb
+ +В разметке MediaWiki невозможно вызывать методы или передавать локальные переменные. +Следовательно, вам, скорее всего, придется использовать этот шаблон совместно +с другим шаблонизатором: + +```ruby +erb :overview, :locals => { :text => mediawiki(:introduction) } +``` + +Заметьте, что вы можете вызывать метод `mediawiki` из других шаблонов: + +```ruby +%h1 Hello From Haml! +%p= mediawiki(:greetings) +``` + +Вы не можете вызывать Ruby из MediaWiki, соответственно, вы не можете +использовать лэйауты на MediaWiki. Тем не менее, есть возможность использовать +один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью +опции `:layout_engine`. + +#### CoffeeScript шаблоны + + + + + + + + + + + + + + +
Зависимости + + CoffeeScript + и + + способ запускать JavaScript + +
Расширения файлов.coffee
Примерcoffee :index
+ +#### Stylus шаблоны + + + + + + + + + + + + + + +
Зависимости + + Stylus + и + + способ запускать JavaScript + +
Расширение файла.styl
Примерstylus :index
+ +Перед тем, как использовать шаблоны стилус, загрузите `stylus` и +`stylus/tilt`: + +``` ruby +require 'sinatra' +require 'stylus' +require 'stylus/tilt' + +get '/' do + stylus :example +end +``` + +#### Yajl шаблоны + + + + + + + + + + + + + + +
Зависимостиyajl-ruby
Расширения файлов.yajl
Пример + + yajl :index, + :locals => { :key => 'qux' }, + :callback => 'present', + :variable => 'resource' + +
+ +Содержимое шаблона интерпретируется как код на Ruby, а результирующая +переменная json затем конвертируется с помощью `#to_json`. + +```ruby +json = { :foo => 'bar' } +json[:baz] = key +``` + +Опции `:callback` и `:variable` используются для "декорирования" итогового +объекта. + +```ruby +var resource = {"foo":"bar","baz":"qux"}; present(resource); +``` + +#### WLang шаблоны + + + + + + + + + + + + + + +
Зависимостиwlang
Расширения файлов.wlang
Примерwlang :index, :locals => { :key => 'value' }
+ +Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за +исключением `yield`), то вы почти всегда будете передавать в шаблон локальные +переменные. + +### Доступ к переменным в шаблонах + +Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. +Переменные экземпляра, установленные в процессе обработки маршрутов, будут +доступны напрямую в шаблонах: + +```ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +``` + +Либо установите их через хеш локальных переменных: + +```ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= bar.name', :locals => { :bar => foo } +end +``` + +Это обычный подход, когда шаблоны рендерятся как части других шаблонов. + +### Шаблоны с `yield` и вложенные раскладки (layout) + +Раскладка (layout) обычно представляет собой шаблон, который исполняет +`yield`. +Такой шаблон может быть либо использован с помощью опции `:template`, +как описано выше, либо он может быть дополнен блоком: + +```ruby + erb :post, :layout => false do + erb :index + end +``` + +Эти инструкции в основном эквивалентны `erb :index, :layout => :post`. + +Передача блоков интерпретирующим шаблоны методам наиболее полезна для +создания вложенных раскладок: + +```ruby + erb :main_layout, :layout => false do + erb :admin_layout do + erb :user + end + end +``` + +Это же самое может быть сделано короче: + +```ruby + erb :admin_layout, :layout => :main_layout do + erb :user + end +``` + +В настоящее время, следующие интерпретирующие шаблоны методы +принимают блок: +`erb`, `haml`, `liquid`, `slim `, `wlang`. +Общий метод заполнения шаблонов `render` также принимает блок. + +### Включённые шаблоны + +Шаблоны также могут быть определены в конце исходного файла: + +```ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world. +``` + +Заметьте: включённые шаблоны, определенные в исходном файле, который подключил +Sinatra, будут загружены автоматически. Вызовите `enable :inline_templates` +напрямую, если используете включённые шаблоны в других файлах. + +### Именованные шаблоны + +Шаблоны также могут быть определены при помощи `template` метода: + +```ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +``` + +Если шаблон с именем "layout" существует, то он будет использоваться каждый +раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае с +помощью `:layout => false` или отключить его для всего приложения: `set :haml, +:layout => false`: + +```ruby +get '/' do + haml :index, :layout => !request.xhr? +end +``` + +### Привязка файловых расширений + +Чтобы связать расширение файла с движком рендеринга, используйте +`Tilt.register`. Например, если вы хотите использовать расширение `tt` для +шаблонов Textile: + +```ruby +Tilt.register :tt, Tilt[:textile] +``` + +### Добавление собственного движка рендеринга + +Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод, отвечающий +за рендеринг: + +```ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +``` + +Отобразит `./views/index.myat`. Чтобы узнать больше о Tilt, смотрите +https://github.com/rtomayko/tilt + +## Фильтры + +`before`-фильтры выполняются перед каждым запросом в том же контексте, что и +маршруты, и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: + +```ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +``` + +`after`-фильтры выполняются после каждого запроса в том же контексте +и могут изменять как запрос, так и ответ на него. Переменные +экземпляра, установленные в `before`-фильтрах и маршрутах, будут доступны в +`after`-фильтрах: + +```ruby +after do + puts response.status +end +``` + +Заметьте: если вы используете метод `body`, а не просто возвращаете строку из +маршрута, то тело ответа не будет доступно в `after`-фильтрах, так как оно +будет сгенерировано позднее. + +Фильтры могут использовать шаблоны URL и будут интерпретированы, только если +путь запроса совпадет с этим шаблоном: + +```ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +``` + +Как и маршруты, фильтры могут использовать условия: + +```ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +``` + +## Методы-помощники + +Используйте метод `helpers`, чтобы определить методы-помощники, которые в +дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах: + +```ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +``` + +Также методы-помощники могут быть заданы в отдельных модулях: + +```ruby +module FooUtils + def foo(name) "#{name}foo" end +end + +module BarUtils + def bar(name) "#{name}bar" end +end + +helpers FooUtils, BarUtils +``` + +Эффект равносилен включению модулей в класс приложения. + +### Использование сессий + +Сессия используется, чтобы сохранять состояние между запросами. Если эта опция +включена, то у вас будет один хеш сессии на одну пользовательскую сессию: + +```ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +Заметьте, что при использовании `enable :sessions` все данные сохраняются в +куках (cookies). Это может быть не совсем то, что вы хотите (например, +сохранение больших объемов данных увеличит ваш трафик). В таком случае вы +можете использовать альтернативную Rack "прослойку" (middleware), реализующую +механизм сессий. Для этого *не надо* вызывать `enable :sessions`, вместо этого +следует подключить ее так же, как и любую другую "прослойку": + +```ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +``` + +Для повышения безопасности данные сессии в куках подписываются секретным +ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ +будет меняться с каждым запуском приложения, вы, возможно, захотите установить +ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же +ключ: + +```ruby +set :session_secret, 'super secret' +``` + +Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш +опций в параметр `sessions`: + +```ruby +set :sessions, :domain => 'foo.com' +``` + +Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах +foo.com, добавьте *.* перед доменом: + +``` ruby +set :sessions, :domain => '.foo.com' +``` + +### Прерывание + +Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, +используйте: + +```ruby +halt +``` + +Можно также указать статус при прерывании: + +```ruby +halt 410 +``` + +Тело: + +```ruby +halt 'this will be the body' +``` + +И то, и другое: + +```ruby +halt 401, 'go away!' +``` + +Можно указать заголовки: + +```ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +``` + +И, конечно, можно использовать шаблоны с `halt`: + +```ruby +halt erb(:error) +``` + +### Передача + +Маршрут может передать обработку запроса следующему совпадающему маршруту, +используя `pass`: + +```ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +``` + +Блок маршрута сразу же прерывается, и контроль переходит к следующему +совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на +запрос будет 404. + +### Вызов другого маршрута + +Иногда `pass` не подходит, например, если вы хотите получить результат вызова +другого обработчика маршрута. В таком случае просто используйте `call`: + +```ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +``` + +Заметьте, что в предыдущем примере можно облегчить тестирование и повысить +производительность, перенеся `"bar"` в метод-помощник, используемый и в +`/foo`, и в `/bar`. + +Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, а не +в его копию, используйте `call!` вместо `call`. + +Если хотите узнать больше о `call`, смотрите спецификацию Rack. + +### Задание тела, кода и заголовков ответа + +Хорошим тоном является установка кода состояния HTTP и тела ответа в +возвращаемом значении обработчика маршрута. Тем не менее, в некоторых +ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке +потока исполнения. Вы можете сделать это с помощью метода-помощника `body`. +Если вы задействуете метод `body`, то вы можете использовать его и в +дальнейшем, чтобы получить доступ к телу ответа. + +```ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +``` + +Также можно передать блок в метод `body`, который затем будет вызван +обработчиком Rack (такой подход может быть использован для реализации +поточного ответа, см. "Возвращаемые значения"). + +Аналогично вы можете установить код ответа и его заголовки: + +```ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +``` + +Как и `body`, методы `headers` и `status`, вызванные без аргументов, +возвращают свои текущие значения. + +### Стриминг ответов + +Иногда требуется начать отправлять данные клиенту прямо в процессе +генерирования частей этих данных. В особых случаях требуется постоянно +отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете +использовать метод `stream` вместо написания собственных "оберток". + +```ruby +get '/' do + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end +end +``` + +Что позволяет вам реализовать стриминговые API, +[Server Sent Events](http://dev.w3.org/html5/eventsource/), +и может служить основой для [WebSockets](http://en.wikipedia.org/wiki/WebSocket). +Также такой подход можно использовать для увеличения производительности в случае, +когда какая-то часть контента зависит от медленного ресурса. + +Заметьте, что возможности стриминга, особенно количество одновременно +обслуживаемых запросов, очень сильно зависят от используемого веб-сервера. +Некоторые серверы, например, WEBRick, могут и вовсе не поддерживать стриминг. +Если сервер не поддерживает стриминг, то все данные будут отправлены за один +раз сразу после того, как блок, переданный в `stream`, завершится. Стриминг +вообще не работает при использовании Shotgun. + +Если метод используется с параметром `keep_open`, то он не будет вызывать +`close` у объекта потока, что позволит вам закрыть его позже в любом другом +месте. Это работает только с событийными серверами, например, с Thin и +Rainbows. Другие же серверы все равно будут закрывать поток: + +```ruby +# long polling + +set :server, :thin +connections = [] + +get '/subscribe' do + # регистрация клиента + stream(:keep_open) do |out| + connections << out } + # удаление "мертвых клиентов" + connections.reject!(&:closed?) + end +end + +post '/message' do + connections.each do |out| + # уведомить клиента о новом сообщении + out << params['message'] << "\n" + + # указать клиенту на необходимость снова соединиться + out.close + end + + # допуск + "message received" +end +``` + +### Логирование + +В области видимости запроса метод `logger` предоставляет доступ к экземпляру +`Logger`: + +```ruby +get '/' do + logger.info "loading data" + # ... +end +``` + +Этот логер автоматически учитывает ваши настройки логирования в Rack. Если +логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы +можете смело использовать его в маршрутах и фильтрах. + +Заметьте, что логирование включено по умолчанию только для +`Sinatra::Application`, а если ваше приложение — подкласс `Sinatra::Base`, то +вы, наверное, захотите включить его вручную: + +```ruby +class MyApp < Sinatra::Base + configure :production, :development do + enable :logging + end +end +``` + +Чтобы избежать использования любой логирующей "прослойки", задайте опции +`logging` значение `nil`. Тем не менее, не забывайте, что в такой ситуации +`logger` вернет `nil`. Чаще всего так делают, когда задают свой собственный +логер. Sinatra будет использовать то, что находится в `env['rack.logger']`. + +### Mime-типы + +Когда вы используете `send_file` или статические файлы, у вас могут быть +mime-типы, которые Sinatra не понимает по умолчанию. Используйте `mime_type` +для их регистрации по расширению файла: + +```ruby +configure do + mime_type :foo, 'text/foo' +end +``` + +Вы также можете использовать это в `content_type` методе-помощнике: + +```ruby +get '/' do + content_type :foo + "foo foo foo" +end +``` + +### Генерирование URL + +Чтобы сформировать URL, вам следует использовать метод `url`, например, в Haml: + +```ruby +%a{:href => url('/foo')} foo +``` + +Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они +присутствуют. + +Наряду с `url` вы можете использовать `to` (смотрите пример ниже). + +### Перенаправление (редирект) + +Вы можете перенаправить браузер пользователя с помощью метода `redirect`: + +```ruby +get '/foo' do + redirect to('/bar') +end +``` + +Любые дополнительные параметры используются по аналогии с аргументами метода +`halt`: + +```ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'wrong place, buddy' +``` + +Вы также можете перенаправить пользователя обратно, на страницу, с которой он +пришел, с помощью `redirect back`: + +```ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +``` + +Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте +их в строку запроса: + +```ruby +redirect to('/bar?sum=42') +``` + +либо используйте сессию: + +```ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +``` + +### Управление кэшированием + +Установка корректных заголовков — основа правильного HTTP кэширования. + +Вы можете легко выставить заголовок Cache-Control таким образом: + +```ruby +get '/' do + cache_control :public + "cache it!" +end +``` + +Совет: задавайте кэширование в `before`-фильтре: + +```ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +``` + +Если вы используете метод `expires` для задания соответствующего заголовка, то +`Cache-Control` будет выставлен автоматически: + +```ruby +before do + expires 500, :public, :must_revalidate +end +``` + +Чтобы как следует использовать кэширование, вам следует подумать об +использовании `etag` или `last_modified`. Рекомендуется использовать эти +методы-помощники *до* выполнения ресурсоемких вычислений, так как они +немедленно отправят ответ клиенту, если текущая версия уже есть в их кэше: + +```ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +``` + +Также вы можете использовать +[weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): + +```ruby +etag @article.sha1, :weak +``` + +Эти методы-помощники не станут ничего кэшировать для вас, но они дадут +необходимую информацию для вашего кэша. Если вы ищете легкое решение для +кэширования, попробуйте [rack-cache](https://github.com/rtomayko/rack-cache): + +```ruby +require 'rack/cache' +require 'sinatra' + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +``` + +Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок +`Cache-Control` к статическим файлам. + +В соответствии с RFC 2616 ваше приложение должно вести себя по-разному, когда +заголовки If-Match или If-None-Match имеют значение `*`, в зависимости от +того, существует или нет запрашиваемый ресурс. Sinatra предполагает, что +ресурсы, к которым обращаются с помощью безопасных (GET) и идемпотентных (PUT) +методов, уже существуют, а остальные ресурсы (к которым обращаются, например, +с помощью POST) считает новыми. Вы можете изменить данное поведение с помощью +опции `:new_resource`: + +```ruby +get '/create' do + etag '', :new_resource => true + Article.create + erb :new_article +end +``` + +Если вы хотите использовать weak ETag, задайте опцию `:kind`: + +```ruby +etag '', :new_resource => true, :kind => :weak +``` + +### Отправка файлов + +Для отправки файлов пользователю вы можете использовать метод `send_file`: + +```ruby +get '/' do + send_file 'foo.png' +end +``` + +Этот метод имеет несколько опций: + +```ruby +send_file 'foo.png', :type => :jpg +``` + +Возможные опции: + +
+
filename
+
имя файла, по умолчанию: реальное имя файла.
+ +
last_modified
+
значение для заголовка Last-Modified, по умолчанию: mtime (время + изменения) файла.
+ +
type
+
тип файла, по умолчанию: определяется по расширению файла.
+ +
disposition
+
используется для заголовка Content-Disposition, возможные значения: nil + (по умолчанию), :attachment и :inline.
+ +
length
+
значения для заголовка Content-Length, по умолчанию: размер файла.
+ +
status
+
Код ответа. Полезно, когда отдается статический файл в качестве страницы с + сообщением об ошибке.
+
+ +Этот метод будет использовать возможности Rack сервера для отправки файлов, +если они доступны, в противном случае будет напрямую отдавать файл из Ruby +процесса. Метод `send_file` также обеспечивает автоматическую обработку +частичных (range) запросов с помощью Sinatra. + +### Доступ к объекту запроса + +Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, +маршрутах, обработчиках ошибок) с помощью `request` метода: + +```ruby +# приложение запущено на http://example.com/example +get '/foo' do + t = %w[text/css text/html application/javascript] + request.accept # ['text/html', '*/*'] + request.accept? 'text/xml' # true + request.preferred_type(t) # 'text/html' + request.body # тело запроса, посланное клиентом (см. ниже) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # длина тела запроса + request.media_type # медиатип тела запроса + request.host # "example.com" + request.get? # true (есть аналоги для других методов HTTP) + request.form_data? # false + request["some_param"] # значение параметра some_param. Шорткат для хеша params + request.referrer # источник запроса клиента либо '/' + request.user_agent # user agent (используется для :agent условия) + request.cookies # хеш, содержащий cookies браузера + request.xhr? # является ли запрос ajax запросом? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # IP-адрес клиента + request.secure? # false (true, если запрос сделан через SSL) + request.forwarded? # true (если сервер работает за обратным прокси) + request.env # "сырой" env хеш, полученный Rack +end +``` + +Некоторые опции, такие как `script_name` или `path_info`, доступны для +изменения: + +```ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +``` + +`request.body` является IO или StringIO объектом: + +```ruby +post "/api" do + request.body.rewind # в случае, если кто-то уже прочитал тело запроса + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +``` + +### Вложения + +Вы можете использовать метод `attachment`, чтобы сказать браузеру, что ответ +сервера должен быть сохранен на диск, а не отображен: + +```ruby +get '/' do + attachment + "store it!" +end +``` + +Вы также можете указать имя файла: + +```ruby +get '/' do + attachment "info.txt" + "store it!" +end +``` + +### Работа со временем и датами + +Sinatra предлагает метод-помощник `time_for`, который из заданного значения +создает объект Time. Он также может конвертировать `DateTime`, `Date` и +подобные классы: + +```ruby +get '/' do + pass if Time.now > time_for('Dec 23, 2012') + "still time" +end +``` + +Этот метод используется внутри Sinatra методами `expires`, `last_modified` и +им подобными. Поэтому вы легко можете изменить и дополнить поведение этих методов, +переопределив `time_for` в своем приложении: + +```ruby +helpers do + def time_for(value) + case value + when :yesterday then Time.now - 24*60*60 + when :tomorrow then Time.now + 24*60*60 + else super + end + end +end + +get '/' do + last_modified :yesterday + expires :tomorrow + "hello" +end +``` + +### Поиск шаблонов + +Для поиска шаблонов и их последующего рендеринга используется метод +`find_template`: + +```ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +``` + +Это не слишком полезный пример. Зато полезен тот факт, что вы можете +переопределить этот метод, чтобы использовать свой собственный механизм +поиска. Например, если вы хотите, чтобы можно было использовать несколько +директорий с шаблонами: + +```ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +``` + +Другой пример, в котором используются разные директории для движков +рендеринга: + +```ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +``` + +Вы можете легко вынести этот код в расширение и поделиться им с остальными! + +Заметьте, что `find_template` не проверяет, существует ли файл на самом деле, +а вызывает заданный блок для всех возможных путей. Дело тут не в +производительности, дело в том, что `render` вызовет `break`, как только файл +не будет найден. Содержимое и местонахождение шаблонов будет закэшировано, +если приложение запущено не в режиме разработки (`set :environment, +:development`). Вы должны помнить об этих нюансах, если пишите по-настоящему +"сумасшедший" метод. + +## Конфигурация + +Этот блок исполняется один раз при старте в любом окружении, режиме +(environment): + +```ruby +configure do + # задание одной опции + set :option, 'value' + + # устанавливаем несколько опций + set :a => 1, :b => 2 + + # то же самое, что и `set :option, true` + enable :option + + # то же самое, что и `set :option, false` + disable :option + + # у вас могут быть "динамические" опции с блоками + set(:css_dir) { File.join(views, 'css') } +end +``` + +Будет запущено, когда окружение (RACK_ENV переменная) `:production`: + +```ruby +configure :production do + ... +end +``` + +Будет запущено, когда окружение `:production` или `:test`: + +```ruby +configure :production, :test do + ... +end +``` + +Вы можете получить доступ к этим опциям с помощью `settings`: + +```ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +``` + +### Настройка защиты от атак + +Sinatra использует +[Rack::Protection](https://github.com/rkh/rack-protection#readme) для защиты +приложения от простых атак. Вы можете легко выключить эту защиту (что сделает +ваше приложение чрезвычайно уязвимым): + +```ruby +disable :protection +``` + +Чтобы пропустить какой-либо уровень защиты, передайте хеш опций в параметр +`protection`: + +```ruby +set :protection, :except => :path_traversal +``` + +Вы также можете отключить сразу несколько уровней защиты: + +```ruby +set :protection, :except => [:path_traversal, :session_hijacking] +``` + +### Доступные настройки + +
+
absolute_redirects
+
+ если отключено, то Sinatra будет позволять использование относительных + перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP + 1.1), который разрешает только абсолютные перенаправления. +
+
+ Включайте эту опцию, если ваше приложение работает за обратным прокси, + который настроен не совсем корректно. Обратите внимание, метод url все + равно будет генерировать абсолютные URL, если вы не передадите false + вторым аргументом. +
+
Отключено по умолчанию.
+ +
add_charset
+
+ mime-типы, к которым метод content_type будет автоматически добавлять + информацию о кодировке. Вам следует добавлять значения к этой опции + вместо ее переопределения: settings.add_charset << "application/foobar" +
+ +
app_file
+
+ путь к главному файлу приложения, используется для нахождения корневой + директории проекта, директорий с шаблонами и статическими файлами, + вложенных шаблонов. +
+ +
bind
+
+ используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только + встроенным сервером. +
+ +
default_encoding
+
кодировка, если неизвестна (по умолчанию: "utf-8").
+ +
dump_errors
+
отображать ошибки в логе.
+ +
environment
+
+ текущее окружение, по умолчанию, значение ENV['RACK_ENV'] или + "development", если ENV['RACK_ENV'] недоступна. +
+ +
logging
+
использовать логер.
+ +
lock
+
+ создает блокировку для каждого запроса, которая гарантирует обработку + только одного запроса в текущий момент времени в Ruby процессе. +
+
+ Включайте, если ваше приложение не потоко-безопасно (thread-safe). + Отключено по умолчанию.
+ +
method_override
+
+ использовать "магический" параметр _method, для поддержки + PUT/DELETE форм в браузерах, которые не поддерживают эти методы. +
+ +
port
+
+ порт, на котором будет работать сервер. + Используется только встроенным сервером. +
+ +
prefixed_redirects
+
+ добавлять или нет параметр request.script_name к редиректам, если не + задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя + как redirect to('/foo'). Отключено по умолчанию. +
+ +
protection
+
включена или нет защита от атак. Смотрите секцию выше.
+ +
public_dir
+
Алиас для public_folder.
+ +
public_folder
+
+ путь к директории, откуда будут раздаваться статические файлы. + Используется, только если включена раздача статических файлов + (см. опцию static ниже). +
+ +
reload_templates
+
+ перезагружать или нет шаблоны на каждый запрос. Включено в режиме + разработки. +
+ +
root
+
путь к корневой директории проекта.
+ +
raise_errors
+
+ выбрасывать исключения (будет останавливать приложение). + По умолчанию включено только в окружении test. +
+ +
run
+
+ если включено, Sinatra будет самостоятельно запускать веб-сервер. Не + включайте, если используете rackup или аналогичные средства. +
+ +
running
+
работает ли сейчас встроенный сервер? Не меняйте эту опцию!
+ +
server
+
+ сервер или список серверов, которые следует использовать в качестве + встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'], порядок + задает приоритет.
+ +
sessions
+
+ включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. + Смотрите секцию "Использование сессий" выше. +
+ +
show_exceptions
+
+ показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию + включено только в окружении development. +
+
+ Может быть установлено в + :after_handler для запуска специфичной для приложения обработки ошибок, + перед показом трассировки стека в браузере. +
+ +
static
+
должна ли Sinatra осуществлять раздачу статических файлов.
+
Отключите, когда используете какой-либо веб-сервер для этой цели.
+
Отключение значительно улучшит производительность приложения.
+
По умолчанию включено в классических и отключено в модульных приложениях.
+ +
static_cache_control
+
+ когда Sinatra отдает статические файлы, используйте эту опцию, чтобы + добавить им заголовок Cache-Control. Для этого используется + метод-помощник cache_control. По умолчанию отключено. +
+
+ Используйте массив, когда надо задать несколько значений: + set :static_cache_control, [:public, :max_age => 300] +
+ +
threaded
+
+ если включено, то Thin будет использовать EventMachine.defer для + обработки запросов. +
+ +
traps
+
должна ли Синатра обрабатывать системные сигналы или нет.
+ +
views
+
путь к директории с шаблонами.
+
+ +## Режим, окружение + +Есть 3 предопределенных режима, окружения: `"development"`, `"production"` и +`"test"`. Режим может быть задан через переменную окружения `RACK_ENV`. +Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны +перезагружаются между запросами. А также задаются специальные обработчики +`not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях +`"production"` и `"test"` шаблоны по умолчанию кэшируются. + +Для запуска приложения в определенном окружении используйте ключ `-e` + +``` +ruby my_app.rb -e [ENVIRONMENT] +``` + +Вы можете использовать предопределенные методы `development?`, `test?` и ++production?, чтобы определить текущее окружение. + +## Обработка ошибок + +Обработчики ошибок исполняются в том же контексте, что и маршруты, и +`before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`, +`halt` и т.д. доступны и им. + +### Not Found + +Когда выброшено исключение `Sinatra::NotFound`, или кодом ответа является 404, +то будет вызван `not_found` обработчик: + +```ruby +not_found do + 'This is nowhere to be found.' +end +``` + +### Error + +Обработчик ошибок `error` будет вызван, когда исключение выброшено из блока +маршрута, либо из фильтра. Объект-исключение доступен как переменная +`sinatra.error` в Rack: + +```ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +``` + +Конкретные ошибки: + +```ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +``` + +Тогда, если это произошло: + +```ruby +get '/' do + raise MyCustomError, 'something bad' +end +``` + +То вы получите: + +``` +So what happened was... something bad +``` + +Также вы можете установить обработчик ошибок для кода состояния HTTP: + +```ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +``` + +Либо набора кодов: + +```ruby +error 400..510 do + 'Boom' +end +``` + +Sinatra устанавливает специальные `not_found` и `error` обработчики, когда +приложение запущено в режиме разработки (окружение `:development`). + +## Rack "прослойки" + +Sinatra использует [Rack](http://rack.github.io/), минимальный стандартный +интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для +разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — +компонентов, находящихся "между" сервером и вашим приложением, которые +отслеживают и/или манипулируют HTTP запросами/ответами для предоставления +различной функциональности. + +В Sinatra очень просто использовать такие "прослойки" с помощью метода `use`: + +```ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +``` + +Семантика `use` идентична той, что определена для +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) DSL +(чаще всего используется в rackup файлах). Например, метод `use` принимает как +множественные переменные, так и блоки: + +```ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +``` + +Rack распространяется с различными стандартными "прослойками" для логирования, +отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra +использует многие из этих компонентов автоматически, основываясь на +конфигурации, чтобы вам не приходилось подключать (`use`) их вручную. + +Вы можете найти полезные прослойки в +[rack](https://github.com/rack/rack/tree/master/lib/rack), +[rack-contrib](https://github.com/rack/rack-contrib#readme), +или в +[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). + +## Тестирование + +Тесты для Sinatra приложений могут быть написаны с помощью библиотек, +фреймворков, поддерживающих тестирование Rack. +[Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) +рекомендован: + +```ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +``` + +## Sinatra::Base — "прослойки", библиотеки и модульные приложения + +Описание своего приложения самым простейшим способом (с помощью DSL верхнего +уровня, классический стиль) отлично работает для крохотных приложений. В таких +случаях используется конфигурация, рассчитанная на микро-приложения +(единственный файл приложения, `./public` и `./views` директории, логирование, +страница информации об исключении и т.д.). Тем не менее, такой метод имеет +множество недостатков при создании компонентов, таких как Rack middleware +("прослоек"), Rails metal, простых библиотек с серверными компонентами, +расширений Sinatra. И тут на помощь приходит `Sinatra::Base`: + +```ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +``` + +Методы, доступные `Sinatra::Base` подклассам идентичны тем, что доступны +приложениям в DSL верхнего уровня. Большинство таких приложений могут быть +конвертированы в `Sinatra::Base` компоненты с помощью двух модификаций: + +* Вы должны подключать `sinatra/base` вместо `sinatra`, иначе все методы, + предоставляемые Sinatra, будут импортированы в глобальное пространство + имен. +* Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс + `Sinatra::Base`. + +`Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный +сервер, по умолчанию отключены. Смотрите +[Опции и конфигурация](http://www.sinatrarb.com/configuration.html) +для детальной информации об опциях и их поведении. + +### Модульные приложения против классических + +Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего +плохого. Если этот стиль подходит вашему приложению, вы не обязаны +переписывать его в модульное приложение. + +Основным недостатком классического стиля является тот факт, что у вас может +быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете +использовать больше, переключайтесь на модульный стиль. Вы можете смело +смешивать модульный и классический стили. + +Переходя с одного стиля на другой, примите во внимание следующие изменения в +настройках: + + Опция Классический Модульный + + app_file файл с приложением файл с подклассом Sinatra::Base + run $0 == app_file false + logging true false + method_override true false + inline_templates true false + static true false + +### Запуск модульных приложений + +Есть два общепринятых способа запускать модульные приложения: запуск напрямую +с помощью `run!`: + +```ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... здесь код приложения ... + + # запускаем сервер, если исполняется текущий файл + run! if app_file == $0 +end +``` + +Затем: + +``` +ruby my_app.rb +``` + +Или с помощью конфигурационного файла `config.ru`, который позволяет +использовать любой Rack-совместимый сервер приложений. + +```ruby +# config.ru +require './my_app' +run MyApp +``` + +Запускаем: + +``` +rackup -p 4567 +``` + +### Запуск классических приложений с config.ru + +Файл приложения: + +```ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +``` + +И соответствующий `config.ru`: + +```ruby +require './app' +run Sinatra::Application +``` + +### Когда использовать config.ru? + +Вот несколько причин, по которым вы, возможно, захотите использовать +`config.ru`: + +* вы хотите разворачивать свое приложение на различных Rack-совместимых + серверах (Passenger, Unicorn, Heroku, ...); +* вы хотите использовать более одного подкласса `Sinatra::Base`; +* вы хотите использовать Sinatra только в качестве "прослойки" Rack. + +**Совсем необязательно переходить на использование `config.ru` лишь потому, +что вы стали использовать модульный стиль приложения. И необязательно +использовать модульный стиль, чтобы запускать приложение с помощью +`config.ru`.** + +### Использование Sinatra в качестве "прослойки" + +Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra +приложение само может быть добавлено к любому Rack endpoint в качестве +"прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra +приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...): + +```ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] == 'admin' && params['password'] == 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # "прослойка" будет запущена перед фильтрами + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +``` + +### Создание приложений "на лету" + +Иногда требуется создавать Sinatra приложения "на лету" (например, из другого +приложения). Это возможно с помощью `Sinatra.new`: + +```ruby +require 'sinatra/base' +my_app = Sinatra.new { get('/') { "hi" } } +my_app.run! +``` + +Этот метод может принимать аргументом приложение, от которого следует +наследоваться: + +```ruby +# config.ru +require 'sinatra/base' + +controller = Sinatra.new do + enable :logging + helpers MyHelpers +end + +map('/a') do + run Sinatra.new(controller) { get('/') { 'a' } } +end + +map('/b') do + run Sinatra.new(controller) { get('/') { 'b' } } +end +``` + +Это особенно полезно для тестирования расширений Sinatra и при использовании +Sinatra внутри вашей библиотеки. + +Благодаря этому, использовать Sinatra как "прослойку" очень просто: + +```ruby +require 'sinatra/base' + +use Sinatra do + get('/') { ... } +end + +run RailsProject::Application +``` + +## Области видимости и привязка + +Текущая область видимости определяет методы и переменные, доступные в данный +момент. + +### Область видимости приложения / класса + +Любое Sinatra приложение соответствует подклассу `Sinatra::Base`. Если вы +используете DSL верхнего уровня (`require 'sinatra'`), то этим классом будет +`Sinatra::Application`, иначе это будет подкласс, который вы создали вручную. +На уровне класса вам будут доступны такие методы, как `get` или `before`, но +вы не сможете получить доступ к объектам `request` или `session`, так как +существует только один класс приложения для всех запросов. + +Опции, созданные с помощью `set`, являются методами уровня класса: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + set :foo, 42 + foo # => 42 + + get '/foo' do + # Я больше не в области видимости приложения! + end +end +``` + +У вас будет область видимости приложения внутри: + +* тела вашего класса приложения; +* методов, определенных расширениями; +* блока, переданного в `helpers`; +* блоков, использованных как значения для `set`; +* блока, переданного в `Sinatra.new`. + +Вы можете получить доступ к объекту области видимости (классу приложения) +следующими способами: + +* через объект, переданный блокам конфигурации (`configure { |c| ... }`); +* `settings` внутри области видимости запроса. + +### Область видимости запроса/экземпляра + +Для каждого входящего запроса будет создан новый экземпляр вашего приложения, +и все блоки обработчика будут запущены в этом контексте. В этой области +видимости вам доступны `request` и `session` объекты, вызовы методов +рендеринга, такие как `erb` или `haml`. Вы можете получить доступ к области +видимости приложения из контекста запроса, используя метод-помощник +`settings`: + +```ruby +class MyApp < Sinatra::Base + # Я в области видимости приложения! + get '/define_route/:name' do + # Область видимости запроса '/define_route/:name' + @value = 42 + + settings.get("/#{params['name']}") do + # Область видимости запроса "/#{params['name']}" + @value # => nil (другой запрос) + end + + "Route defined!" + end +end +``` + +У вас будет область видимости запроса в: + +* get/head/post/put/delete/options блоках; +* before/after фильтрах; +* методах-помощниках; +* шаблонах/отображениях. + +### Область видимости делегирования + +Область видимости делегирования просто перенаправляет методы в область +видимости класса. Однако, она не полностью ведет себя как область видимости +класса, так как у вас нет привязки к классу. Только методы, явно помеченные +для делегирования, будут доступны, а переменных/состояний области видимости +класса не будет (иначе говоря, у вас будет другой `self` объект). Вы можете +непосредственно добавить методы делегирования, используя +`Sinatra::Delegator.delegate :method_name`. + +У вас будет контекст делегирования внутри: + +* привязки верхнего уровня, если вы сделали `require 'sinatra'`; +* объекта, расширенного с помощью `Sinatra::Delegator`. + +Посмотрите сами в код: вот +[примесь Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) +[расширяет главный объект](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). + +## Командная строка + +Sinatra приложения могут быть запущены напрямую: + +``` +ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] +``` + +Опции включают: + +``` +-h # раздел помощи +-p # указание порта (по умолчанию 4567) +-o # указание хоста (по умолчанию 0.0.0.0) +-e # указание окружения, режима (по умолчанию development) +-s # указание rack сервера/обработчика (по умолчанию thin) +-x # включить мьютекс-блокировку (по умолчанию выключена) +``` + +## Системные требования + +Следующие версии Ruby официально поддерживаются: + +
+
Ruby 1.8.7
+
1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на + этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или + Rubinius. Поддержка 1.8.7 не будет прекращена до выхода Sinatra 2.0 и Ruby + 2.0, разве что в случае релиза 1.8.8 (что маловероятно). Но даже тогда, + возможно, поддержка не будет прекращена. Ruby 1.8.6 больше не + поддерживается. Если вы хотите использовать 1.8.6, откатитесь до Sinatra + 1.2, которая будет получать все исправления ошибок до тех пор, пока не + будет выпущена Sinatra 1.4.0.
+ +
Ruby 1.9.2
+
1.9.2 полностью поддерживается и рекомендована к использованию. + Не используйте 1.9.2p0, + известно, что эта версия очень нестабильна при использовании Sinatra. Эта + версия будет поддерживаться по крайней мере до выхода Ruby 1.9.4/2.0, а + поддержка последней версии 1.9 будет осуществляться до тех пор, пока она + поддерживается командой разработчиков Ruby.
+ +
Ruby 1.9.3
+
1.9.3 полностью поддерживается. Заметьте, что переход на 1.9.3 с + ранних версий сделает недействительными все сессии.
+ +
Rubinius
+
Rubinius официально поддерживается (Rubinius >= 1.2.4), всё, включая все + языки шаблонов, работает. Предстоящий релиз 2.0 также поддерживается.
+ +
JRuby
+
JRuby официально поддерживается (JRuby >= 1.6.5). Нет никаких проблем с + использованием альтернативных шаблонов. Тем не менее, если вы выбираете + JRuby, то, пожалуйста, посмотрите на JRuby Rack-серверы, так как Thin не + поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все + еще экспериментальная, что на данный момент затрагивает только RDiscount, + Redcarpet и RedCloth.
+
+ +Мы также следим за предстоящими к выходу версиями Ruby. + +Следующие реализации Ruby не поддерживаются официально, но известно, что на +них запускается Sinatra: + +* старые версии JRuby и Rubinius; +* Ruby Enterprise Edition; +* MacRuby, Maglev, IronRuby; +* Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию). + +То, что версия официально не поддерживается, означает, что, если что-то не +работает на этой версии, а на поддерживаемой работает — это не наша проблема, +а их. + +Мы также запускаем наши CI-тесты на версии Ruby, находящейся в разработке +(предстоящей 2.0.0), и на 1.9.4, но мы не можем ничего гарантировать, так как +они находятся в разработке. Предполагается, что 1.9.4p0 и 2.0.0p0 будут +поддерживаться. + +Sinatra должна работать на любой операционной системе, в которой есть одна из +указанных выше версий Ruby. + +Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой +версии Ruby до 1.8.7. + +## На острие + +Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать +свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна. + +Мы также время от времени выпускаем предварительные версии, так что вы можете +делать так: + +``` +gem install sinatra --pre +``` + +Чтобы воспользоваться некоторыми самыми последними возможностями. + +### С помощью Bundler + +Если вы хотите запускать свое приложение с последней версией Sinatra, то +рекомендуем использовать [Bundler](http://gembundler.com/). + +Сначала установите Bundler, если у вас его еще нет: + +``` +gem install bundler +``` + +Затем создайте файл `Gemfile` в директории вашего проекта: + +```ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# другие зависимости +gem 'haml' # например, если используете haml +gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x +``` + +Обратите внимание, вам нужно будет указывать все зависимости вашего приложения +в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) +Bundler автоматически скачает и добавит. + +Теперь вы можете запускать свое приложение так: + +``` +bundle exec ruby myapp.rb +``` + +### Вручную + +Создайте локальный клон репозитория и запускайте свое приложение с +`sinatra/lib` директорией в `$LOAD_PATH`: + +``` +cd myapp +git clone git://github.com/sinatra/sinatra.git +ruby -Isinatra/lib myapp.rb +``` + +Чтобы обновить исходники Sinatra: + +``` +cd myapp/sinatra +git pull +``` + +### Установка глобально + +Вы можете самостоятельно собрать gem: + +``` +git clone git://github.com/sinatra/sinatra.git +cd sinatra +rake sinatra.gemspec +rake install +``` + +Если вы устанавливаете пакеты (gem) от пользователя root, то вашим последним +шагом должна быть команда + +``` +sudo rake install +``` + +## Версии + +Sinatra использует [Semantic Versioning](http://semver.org/), SemVer и +SemVerTag. + +## Дальнейшее чтение + +* [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная + документация, новости и ссылки на другие ресурсы. +* [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили + баг? Нужна помощь? Написали патч? +* [Слежение за проблемами/ошибками](http://github.com/sinatra/sinatra/issues) +* [Twitter](http://twitter.com/sinatra) +* [Группы рассылки](http://groups.google.com/group/sinatrarb/topics) +* [#sinatra](irc://chat.freenode.net/#sinatra) на http://freenode.net +* [Sinatra Book](https://github.com/sinatra/sinatra-book/) учебник и сборник рецептов +* [Sinatra Recipes](http://recipes.sinatrarb.com/) сборник рецептов +* API документация к [последнему релизу](http://rubydoc.info/gems/sinatra) + или [текущему HEAD](http://rubydoc.info/github/sinatra/sinatra) на + http://rubydoc.info +* [Сервер непрерывной интеграции](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.zh.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.zh.md new file mode 100644 index 000000000..6ed584db0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/README.zh.md @@ -0,0 +1,2166 @@ +# Sinatra + +*注:本文档是英文版的翻译,内容更新有可能不及时。 +如有不一致的地方,请以英文版为准。* + +Sinatra是一个基于Ruby语言的[DSL](http://en.wikipedia.org/wiki/Domain-specific_language)( +领域专属语言),可以轻松、快速的创建web应用。 + +~~~~ ruby +# myapp.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +~~~~ + +安装gem,然后运行: + +~~~~ shell +gem install sinatra +ruby myapp.rb +~~~~ + +在该地址查看: http://localhost:4567 + +这个时候访问地址将绑定到 127.0.0.1 和 localhost ,如果使用 vagrant 进行开发,访问会失败,此时就需要进行 ip 绑定了: + +~~~~ shell +ruby myapp.rb -o 0.0.0.0 +~~~~ + +```-o``` 这个参数就是进行 Listening 时候监听的绑定,能从通过 IP、127.0.0.1、localhost + 端口号进行访问。 + +安装Sintra后,最好再运行`gem install thin`安装Thin。这样,Sinatra会优先选择Thin作为服务器。 + +## 路由(route) + +在Sinatra中,一个路由分为两部分:HTTP方法(GET, POST等)和URL匹配范式。 +每个路由都有一个要执行的代码块: + +~~~~ ruby +get '/' do + .. 显示内容 .. +end + +post '/' do + .. 创建内容 .. +end + +put '/' do + .. 更新内容 .. +end + +delete '/' do + .. 删除内容 .. +end + +options '/' do + .. 显示命令列表 .. +end + +link '/' do + .. 建立某种联系 .. +end + +unlink '/' do + .. 解除某种联系 .. +end + + +~~~~ + +路由按照它们被定义的顺序进行匹配。 第一个与请求匹配的路由会被调用。 + +路由范式可以包括具名参数,可通过`params`哈希表获得: + +~~~~ ruby +get '/hello/:name' do + # 匹配 "GET /hello/foo" 和 "GET /hello/bar" + # params['name'] 的值是 'foo' 或者 'bar' + "Hello #{params['name']}!" +end +~~~~ + +你同样可以通过代码块参数获得具名参数: + +~~~~ ruby +get '/hello/:name' do |n| + "Hello #{n}!" +end +~~~~ + +路由范式也可以包含通配符参数, 可以通过`params['splat']`数组获得。 + +~~~~ ruby +get '/say/*/to/*' do + # 匹配 /say/hello/to/world + params['splat'] # => ["hello", "world"] +end + +get '/download/*.*' do + # 匹配 /download/path/to/file.xml + params['splat'] # => ["path/to/file", "xml"] +end +~~~~ + +通过正则表达式匹配的路由: + +~~~~ ruby +get /\A\/hello\/([\w]+)\z/ do + "Hello, #{params['captures'].first}!" +end +~~~~ + +或者使用代码块参数: + +~~~~ ruby +get %r{/hello/([\w]+)} do |c| + "Hello, #{c}!" +end +~~~~ + +### 条件 + +路由也可以包含多样的匹配条件,比如user agent: + +~~~~ ruby +get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do + "你正在使用Songbird,版本是 #{params['agent'][0]}" +end + +get '/foo' do + # 匹配除Songbird以外的浏览器 +end +~~~~ + +其他可选的条件是 `host_name` 和 `provides`: + +~~~~ ruby +get '/', :host_name => /^admin\./ do + "管理员区域,无权进入!" +end + +get '/', :provides => 'html' do + haml :index +end + +get '/', :provides => ['rss', 'atom', 'xml'] do + builder :feed +end +~~~~ + +你也可以自定义条件: + +~~~~ ruby +set(:probability) { |value| condition { rand <= value } } + +get '/win_a_car', :probability => 0.1 do + "You won!" +end + +get '/win_a_car' do + "Sorry, you lost." +end +~~~~ + +### 返回值 + +路由代码块的返回值至少决定了返回给HTTP客户端的响应体, +或者至少决定了在Rack堆栈中的下一个中间件。 +大多数情况下,将是一个字符串,就像上面的例子中的一样。 +但是其他值也是可以接受的。 + +你可以返回任何对象,或者是一个合理的Rack响应, Rack +body对象或者HTTP状态码: + +- 一个包含三个元素的数组: + `[状态 (Fixnum), 头 (Hash), 响应体 (回应 #each)]` + +- 一个包含两个元素的数组: `[状态 (Fixnum), 响应体 (回应 #each)]` + +- 一个能够回应 `#each` ,只传回字符串的对象 + +- 一个代表状态码的数字 + +那样,我们可以轻松的实现例如流式传输的例子: + +~~~~ ruby +class Stream + def each + 100.times { |i| yield "#{i}\n" } + end +end + +get('/') { Stream.new } +~~~~ + +### 自定义路由匹配器 + +如上显示,Sinatra内置了对于使用字符串和正则表达式作为路由匹配的支持。 +但是,它并没有只限于此。 你可以非常容易地定义你自己的匹配器: + +~~~~ ruby +class AllButPattern + Match = Struct.new(:captures) + + def initialize(except) + @except = except + @captures = Match.new([]) + end + + def match(str) + @captures unless @except === str + end +end + +def all_but(pattern) + AllButPattern.new(pattern) +end + +get all_but("/index") do + # ... +end +~~~~ + +上面的例子可能太繁琐了, 因为它也可以用更简单的方式表述: + +~~~~ ruby +get // do + pass if request.path_info == "/index" + # ... +end +~~~~ + +或者,使用消极向前查找: + +~~~~ ruby +get %r{^(?!/index$)} do + # ... +end +~~~~ + +## 静态文件 + +静态文件是从 `./public_folder` 目录提供服务。你可以通过设置`:public` +选项设定一个不同的位置: + +~~~~ ruby +set :public_folder, File.dirname(__FILE__) + '/static' +~~~~ + +请注意public目录名并没有被包含在URL之中。文件 +`./public/css/style.css`是通过 +`http://example.com/css/style.css`地址访问的。 + +## 视图 / 模板 + +模板被假定直接位于`./views`目录。 要使用不同的视图目录: + +~~~~ ruby +set :views, File.dirname(__FILE__) + '/templates' +~~~~ + +重要提示:你只可以通过符号引用模板, 即使它们在子目录下 +(在这种情况下,使用 `:'subdir/template'`)。 如果你不用符号、而用字符串的话, +填充方法会只把你传入的字符串当成内容显示出来,而不调用模板。 + +### Haml模板 + +需要引入 `haml` gem/library以填充 HAML 模板: + +~~~~ ruby +# 你需要在你的应用中引入 haml +require 'haml' + +get '/' do + haml :index +end +~~~~ + +填充 `./views/index.haml`。 + +[Haml的选项](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) +可以通过Sinatra的配置全局设定, 参见 +[选项和配置](http://www.sinatrarb.com/configuration.html), +也可以个别的被覆盖。 + +~~~~ ruby +set :haml, {:format => :html5 } # 默认的Haml输出格式是 :xhtml + +get '/' do + haml :index, :haml_options => {:format => :html4 } # 被覆盖,变成:html4 +end +~~~~ + +### Erb模板 + +~~~~ ruby +# 你需要在你的应用中引入 erb +require 'erb' + +get '/' do + erb :index +end +~~~~ + +这里调用的是 `./views/index.erb` + +### Erubis + +需要引入 `erubis` gem/library以填充 erubis 模板: + +~~~~ ruby +# 你需要在你的应用中引入 erubis +require 'erubis' + +get '/' do + erubis :index +end +~~~~ + +这里调用的是 `./views/index.erubis` + +使用Erubis代替Erb也是可能的: + +~~~~ ruby +require 'erubis' +Tilt.register :erb, Tilt[:erubis] + +get '/' do + erb :index +end +~~~~ + +使用Erubis来填充 `./views/index.erb`。 + +### Builder 模板 + +需要引入 `builder` gem/library 以填充 builder templates: + +~~~~ ruby +# 需要在你的应用中引入builder +require 'builder' + +get '/' do + builder :index +end +~~~~ + +这里调用的是 `./views/index.builder`。 + +### Nokogiri 模板 + +需要引入 `nokogiri` gem/library 以填充 nokogiri 模板: + +~~~~ ruby +# 需要在你的应用中引入 nokogiri +require 'nokogiri' + +get '/' do + nokogiri :index +end +~~~~ + +这里调用的是 `./views/index.nokogiri`。 + +### Sass 模板 + +需要引入 `haml` 或者 `sass` gem/library 以填充 Sass 模板: + +~~~~ ruby +# 需要在你的应用中引入 haml 或者 sass +require 'sass' + +get '/stylesheet.css' do + sass :stylesheet +end +~~~~ + +这里调用的是 `./views/stylesheet.sass`。 + +[Sass +的选项](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) +可以通过Sinatra选项全局设定, 参考 +[选项和配置(英文)](http://www.sinatrarb.com/configuration.html), +也可以在个体的基础上覆盖。 + +~~~~ ruby +set :sass, {:style => :compact } # 默认的 Sass 样式是 :nested + +get '/stylesheet.css' do + sass :stylesheet, :style => :expanded # 覆盖 +end +~~~~ + +### Scss 模板 + +需要引入 `haml` 或者 `sass` gem/library 来填充 Scss templates: + +~~~~ ruby +# 需要在你的应用中引入 haml 或者 sass +require 'sass' + +get '/stylesheet.css' do + scss :stylesheet +end +~~~~ + +这里调用的是 `./views/stylesheet.scss`。 + +[Scss的选项](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) +可以通过Sinatra选项全局设定, 参考 +[选项和配置(英文)](http://www.sinatrarb.com/configuration.html), +也可以在个体的基础上覆盖。 + +~~~~ ruby +set :scss, :style => :compact # default Scss style is :nested + +get '/stylesheet.css' do + scss :stylesheet, :style => :expanded # overridden +end +~~~~ + +### Less 模板 + +需要引入 `less` gem/library 以填充 Less 模板: + +~~~~ ruby +# 需要在你的应用中引入 less +require 'less' + +get '/stylesheet.css' do + less :stylesheet +end +~~~~ + +这里调用的是 `./views/stylesheet.less`。 + +### Liquid 模板 + +需要引入 `liquid` gem/library 来填充 Liquid 模板: + +~~~~ ruby +# 需要在你的应用中引入 liquid +require 'liquid' + +get '/' do + liquid :index +end +~~~~ + +这里调用的是 `./views/index.liquid`。 + +因为你不能在Liquid 模板中调用 Ruby 方法 (除了 `yield`) , +你几乎总是需要传递locals给它: + +~~~~ ruby +liquid :index, :locals => { :key => 'value' } +~~~~ + +### Markdown 模板 + +需要引入 `rdiscount` gem/library 以填充 Markdown 模板: + +~~~~ ruby +# 需要在你的应用中引入rdiscount +require "rdiscount" + +get '/' do + markdown :index +end +~~~~ + +这里调用的是 `./views/index.markdown` (`md` 和 `mkd` 也是合理的文件扩展名)。 + +在markdown中是不可以调用方法的,也不可以传递 locals给它。 +你因此一般会结合其他的填充引擎来使用它: + +~~~~ ruby +erb :overview, :locals => { :text => markdown(:introduction) } +~~~~ + +请注意你也可以从其他模板中调用 markdown 方法: + +~~~~ ruby +%h1 Hello From Haml! +%p= markdown(:greetings) +~~~~ + +既然你不能在Markdown中调用Ruby,你不能使用Markdown编写的布局。 +不过,使用其他填充引擎作为模版的布局是可能的, +通过传递`:layout_engine`选项: + +~~~~ ruby +get '/' do + markdown :index, :layout_engine => :erb +end +~~~~ + +这将会调用 `./views/index.md` 并使用 `./views/layout.erb` 作为布局。 + +请记住你可以全局设定这个选项: + +~~~~ ruby +set :markdown, :layout_engine => :haml, :layout => :post + +get '/' do + markdown :index +end +~~~~ + +这将会调用 `./views/index.markdown` (和任何其他的 Markdown 模版) 并使用 +`./views/post.haml` 作为布局. + +也可能使用BlueCloth而不是RDiscount来解析Markdown文件: + +~~~~ ruby +require 'bluecloth' + +Tilt.register 'markdown', BlueClothTemplate +Tilt.register 'mkd', BlueClothTemplate +Tilt.register 'md', BlueClothTemplate + +get '/' do + markdown :index +end +~~~~ + +使用BlueCloth来填充 `./views/index.md` 。 + +### Textile 模板 + +需要引入 `RedCloth` gem/library 以填充 Textile 模板: + +~~~~ ruby +# 在你的应用中引入redcloth +require "redcloth" + +get '/' do + textile :index +end +~~~~ + +这里调用的是 `./views/index.textile`。 + +在textile中是不可以调用方法的,也不可以传递 locals给它。 +你因此一般会结合其他的填充引擎来使用它: + +~~~~ ruby +erb :overview, :locals => { :text => textile(:introduction) } +~~~~ + +请注意你也可以从其他模板中调用`textile`方法: + +~~~~ ruby +%h1 Hello From Haml! +%p= textile(:greetings) +~~~~ + +既然你不能在Textile中调用Ruby,你不能使用Textile编写的布局。 +不过,使用其他填充引擎作为模版的布局是可能的, +通过传递`:layout_engine`选项: + +~~~~ ruby +get '/' do + textile :index, :layout_engine => :erb +end +~~~~ + +这将会填充 `./views/index.textile` 并使用 `./views/layout.erb` +作为布局。 + +请记住你可以全局设定这个选项: + +~~~~ ruby +set :textile, :layout_engine => :haml, :layout => :post + +get '/' do + textile :index +end +~~~~ + +这将会调用 `./views/index.textile` (和任何其他的 Textile 模版) 并使用 +`./views/post.haml` 作为布局. + +### RDoc 模板 + +需要引入 `RDoc` gem/library 以填充RDoc模板: + +~~~~ ruby +# 需要在你的应用中引入rdoc/markup/to_html +require "rdoc" +require "rdoc/markup/to_html" + +get '/' do + rdoc :index +end +~~~~ + +这里调用的是 `./views/index.rdoc`。 + +在rdoc中是不可以调用方法的,也不可以传递locals给它。 +你因此一般会结合其他的填充引擎来使用它: + +~~~~ ruby +erb :overview, :locals => { :text => rdoc(:introduction) } +~~~~ + +请注意你也可以从其他模板中调用`rdoc`方法: + +~~~~ ruby +%h1 Hello From Haml! +%p= rdoc(:greetings) +~~~~ + +既然你不能在RDoc中调用Ruby,你不能使用RDoc编写的布局。 +不过,使用其他填充引擎作为模版的布局是可能的, +通过传递`:layout_engine`选项: + +~~~~ ruby +get '/' do + rdoc :index, :layout_engine => :erb +end +~~~~ + +这将会调用 `./views/index.rdoc` 并使用 `./views/layout.erb` 作为布局。 + +请记住你可以全局设定这个选项: + +~~~~ ruby +set :rdoc, :layout_engine => :haml, :layout => :post + +get '/' do + rdoc :index +end +~~~~ + +这将会调用 `./views/index.rdoc` (和任何其他的 RDoc 模版) 并使用 +`./views/post.haml` 作为布局. + +### Radius 模板 + +需要引入 `radius` gem/library 以填充 Radius 模板: + +~~~~ ruby +# 需要在你的应用中引入radius +require 'radius' + +get '/' do + radius :index +end +~~~~ + +这里调用的是 `./views/index.radius`。 + +因为你不能在Radius 模板中调用 Ruby 方法 (除了 `yield`) , +你几乎总是需要传递locals给它: + +~~~~ ruby +radius :index, :locals => { :key => 'value' } +~~~~ + +### Markaby 模板 + +需要引入`markaby` gem/library以填充Markaby模板: + +~~~~ ruby +#需要在你的应用中引入 markaby +require 'markaby' + +get '/' do + markaby :index +end +~~~~ + +这里调用的是 `./views/index.mab`。 + +你也可以使用嵌入的 Markaby: + +~~~~ ruby +get '/' do + markaby { h1 "Welcome!" } +end +~~~~ + +### Slim 模板 + +需要引入 `slim` gem/library 来填充 Slim 模板: + +~~~~ ruby +# 需要在你的应用中引入 slim +require 'slim' + +get '/' do + slim :index +end +~~~~ + +这里调用的是 `./views/index.slim`。 + +### Creole 模板 + +需要引入 `creole` gem/library 来填充 Creole 模板: + +~~~~ ruby +# 需要在你的应用中引入 creole +require 'creole' + +get '/' do + creole :index +end +~~~~ + +这里调用的是 `./views/index.creole`。 + +### CoffeeScript 模板 + +需要引入 `coffee-script` gem/library 并至少满足下面条件一项 +以执行Javascript: + +- `node` (来自 Node.js) 在你的路径中 + +- 你正在运行 OSX + +- `therubyracer` gem/library + +请察看 +[github.com/josh/ruby-coffee-script](http://github.com/josh/ruby-coffee-script) +获取更新的选项。 + +现在你可以调用 CoffeeScript 模版了: + +~~~~ ruby +# 需要在你的应用中引入coffee-script +require 'coffee-script' + +get '/application.js' do + coffee :application +end +~~~~ + +这里调用的是 `./views/application.coffee`。 + +### 嵌入模板字符串 + +~~~~ ruby +get '/' do + haml '%div.title Hello World' +end +~~~~ + +调用嵌入模板字符串。 + +### 在模板中访问变量 + +模板和路由执行器在同样的上下文求值。 +在路由执行器中赋值的实例变量可以直接被模板访问。 + +~~~~ ruby +get '/:id' do + @foo = Foo.find(params['id']) + haml '%h1= @foo.name' +end +~~~~ + +或者,显式地指定一个本地变量的哈希: + +~~~~ ruby +get '/:id' do + foo = Foo.find(params['id']) + haml '%h1= foo.name', :locals => { :foo => foo } +end +~~~~ + +典型的使用情况是在别的模板中按照局部模板的方式来填充。 + +### 内联模板 + +模板可以在源文件的末尾定义: + +~~~~ ruby +require 'sinatra' + +get '/' do + haml :index +end + +__END__ + +@@ layout +%html + = yield + +@@ index +%div.title Hello world!!!!! +~~~~ + +注意:引入sinatra的源文件中定义的内联模板才能被自动载入。 +如果你在其他源文件中有内联模板, +需要显式执行调用`enable :inline_templates`。 + +### 具名模板 + +模板可以通过使用顶层 `template` 方法定义: + +~~~~ ruby +template :layout do + "%html\n =yield\n" +end + +template :index do + '%div.title Hello World!' +end + +get '/' do + haml :index +end +~~~~ + +如果存在名为“layout”的模板,该模板会在每个模板填充的时候被使用。 +你可以单独地通过传送 `:layout => false`来禁用, +或者通过`set :haml, :layout => false`来禁用他们。 + +~~~~ ruby +get '/' do + haml :index, :layout => !request.xhr? +end +~~~~ + +### 关联文件扩展名 + +为了关联一个文件扩展名到一个模版引擎,使用 +`Tilt.register`。比如,如果你喜欢使用 `tt` +作为Textile模版的扩展名,你可以这样做: + +~~~~ ruby +Tilt.register :tt, Tilt[:textile] +~~~~ + +### 添加你自己的模版引擎 + +首先,通过Tilt注册你自己的引擎,然后创建一个填充方法: + +~~~~ ruby +Tilt.register :myat, MyAwesomeTemplateEngine + +helpers do + def myat(*args) render(:myat, *args) end +end + +get '/' do + myat :index +end +~~~~ + +这里调用的是 `./views/index.myat`。察看 +[github.com/rtomayko/tilt](https://github.com/rtomayko/tilt) +来更多了解Tilt. + +## 过滤器 + +前置过滤器在每个请求前,在请求的上下文环境中被执行, +而且可以修改请求和响应。 在过滤器中设定的实例变量可以被路由和模板访问: + +~~~~ ruby +before do + @note = 'Hi!' + request.path_info = '/foo/bar/baz' +end + +get '/foo/*' do + @note #=> 'Hi!' + params['splat'] #=> 'bar/baz' +end +~~~~ + +后置过滤器在每个请求之后,在请求的上下文环境中执行, +而且可以修改请求和响应。 +在前置过滤器和路由中设定的实例变量可以被后置过滤器访问: + +~~~~ ruby +after do + puts response.status +end +~~~~ + +请注意:除非你显式使用 `body` 方法,而不是在路由中直接返回字符串, +消息体在后置过滤器是不可用的, 因为它在之后才会生成。 + +过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行: + +~~~~ ruby +before '/protected/*' do + authenticate! +end + +after '/create/:slug' do |slug| + session['last_slug'] = slug +end +~~~~ + +和路由一样,过滤器也可以带有条件: + +~~~~ ruby +before :agent => /Songbird/ do + # ... +end + +after '/blog/*', :host_name => 'example.com' do + # ... +end +~~~~ + +## 辅助方法 + +使用顶层的 `helpers` 方法来定义辅助方法, 以便在路由处理器和模板中使用: + +~~~~ ruby +helpers do + def bar(name) + "#{name}bar" + end +end + +get '/:name' do + bar(params['name']) +end +~~~~ + +### 使用 Sessions + +Session被用来在请求之间保持状态。如果被激活,每一个用户会话 +对应有一个session哈希: + +~~~~ ruby +enable :sessions + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +~~~~ + +请注意 `enable :sessions` 实际上保存所有的数据在一个cookie之中。 +这可能不会总是做你想要的(比如,保存大量的数据会增加你的流量)。 +你可以使用任何的Rack session中间件,为了这么做, \*不要\*调用 +`enable :sessions`,而是 按照自己的需要引入你的中间件: + +~~~~ ruby +use Rack::Session::Pool, :expire_after => 2592000 + +get '/' do + "value = " << session['value'].inspect +end + +get '/:value' do + session['value'] = params['value'] +end +~~~~ + +### 挂起 + +要想直接地停止请求,在过滤器或者路由中使用: + +~~~~ ruby +halt +~~~~ + +你也可以指定挂起时的状态码: + +~~~~ ruby +halt 410 +~~~~ + +或者消息体: + +~~~~ ruby +halt 'this will be the body' +~~~~ + +或者两者; + +~~~~ ruby +halt 401, 'go away!' +~~~~ + +也可以带消息头: + +~~~~ ruby +halt 402, {'Content-Type' => 'text/plain'}, 'revenge' +~~~~ + +### 让路 + +一个路由可以放弃处理,将处理让给下一个匹配的路由,使用 `pass`: + +~~~~ ruby +get '/guess/:who' do + pass unless params['who'] == 'Frank' + 'You got me!' +end + +get '/guess/*' do + 'You missed!' +end +~~~~ + +路由代码块被直接退出,控制流继续前进到下一个匹配的路由。 +如果没有匹配的路由,将返回404。 + +### 触发另一个路由 + +有些时候,`pass` 并不是你想要的,你希望得到的是另一个路由的结果 +。简单的使用 `call` 可以做到这一点: + +~~~~ ruby +get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.map(&:upcase)] +end + +get '/bar' do + "bar" +end +~~~~ + +请注意在以上例子中,你可以更加简化测试并增加性能,只要简单的移动 + + "bar"到一个被/foo + +和 `/bar`同时使用的helper。 + +如果你希望请求被发送到同一个应用,而不是副本, 使用 `call!` 而不是 +`call`. + +如果想更多了解 `call`,请察看 Rack specification。 + +### 设定 消息体,状态码和消息头 + +通过路由代码块的返回值来设定状态码和消息体不仅是可能的,而且是推荐的。 +但是,在某些场景中你可能想在作业流程中的特定点上设置消息体。 你可以通过 +`body` 辅助方法这么做。 如果你这样做了, +你可以在那以后使用该方法获得消息体: + +~~~~ ruby +get '/foo' do + body "bar" +end + +after do + puts body +end +~~~~ + +也可以传一个代码块给 `body`,它将会被Rack处理器执行( +这将可以被用来实现streaming,参见“返回值”)。 + +和消息体类似,你也可以设定状态码和消息头: + +~~~~ ruby +get '/foo' do + status 418 + headers \ + "Allow" => "BREW, POST, GET, PROPFIND, WHEN", + "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" + body "I'm a tea pot!" +end +~~~~ + +如同 `body`, 不带参数的 `headers` 和 `status` 可以用来访问 +他们你的当前值. + +### 媒体(MIME)类型 + +使用 `send_file` 或者静态文件的时候,Sinatra可能不能识别你的媒体类型。 +使用 `mime_type` 通过文件扩展名来注册它们: + +~~~~ ruby +mime_type :foo, 'text/foo' +~~~~ + +你也可以使用 `content_type` 辅助方法: + +~~~~ ruby +get '/' do + content_type :foo + "foo foo foo" +end +~~~~ + +### 生成 URL + +为了生成URL,你需要使用 `url` 辅助方法, 例如,在Haml中: + +~~~~ ruby +%a{:href => url('/foo')} foo +~~~~ + +如果使用反向代理和Rack路由,生成URL的时候会考虑这些因素。 + +这个方法还有一个别名 `to` (见下面的例子). + +### 浏览器重定向 + +你可以通过 `redirect` 辅助方法触发浏览器重定向: + +~~~~ ruby +get '/foo' do + redirect to('/bar') +end +~~~~ + +其他参数的用法,与 `halt`相同: + +~~~~ ruby +redirect to('/bar'), 303 +redirect 'http://google.com', 'wrong place, buddy' +~~~~ + +用 `redirect back`可以把用户重定向到原始页面: + +~~~~ ruby +get '/foo' do + "do something" +end + +get '/bar' do + do_something + redirect back +end +~~~~ + +如果想传递参数给redirect,可以用query string: + +~~~~ ruby +redirect to('/bar?sum=42') +~~~~ + +或者用session: + +~~~~ ruby +enable :sessions + +get '/foo' do + session['secret'] = 'foo' + redirect to('/bar') +end + +get '/bar' do + session['secret'] +end +~~~~ + +### 缓存控制 + +要使用HTTP缓存,必须正确地设定消息头。 + +你可以这样设定 Cache-Control 消息头: + +~~~~ ruby +get '/' do + cache_control :public + "cache it!" +end +~~~~ + +核心提示: 在前置过滤器中设定缓存. + +~~~~ ruby +before do + cache_control :public, :must_revalidate, :max_age => 60 +end +~~~~ + +如果你正在用 `expires` 辅助方法设定对应的消息头 `Cache-Control` +会自动设定: + +~~~~ ruby +before do + expires 500, :public, :must_revalidate +end +~~~~ + +为了合适地使用缓存,你应该考虑使用 `etag` 和 `last_modified`方法。 +推荐在执行繁重任务\*之前\*使用这些helpers,这样一来, +如果客户端在缓存中已经有相关内容,就会立即得到显示。 + + +~~~~ ruby +get '/article/:id' do + @article = Article.find params['id'] + last_modified @article.updated_at + etag @article.sha1 + erb :article +end +~~~~ + +使用 [weak +ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) +也是有可能的: + +~~~~ ruby +etag @article.sha1, :weak +~~~~ + +这些辅助方法并不会为你做任何缓存,而是将必要的信息传送给你的缓存 +如果你在寻找缓存的快速解决方案,试试 +[rack-cache](https://github.com/rtomayko/rack-cache): + +~~~~ ruby +require "rack/cache" +require "sinatra" + +use Rack::Cache + +get '/' do + cache_control :public, :max_age => 36000 + sleep 5 + "hello" +end +~~~~ + +### 发送文件 + +为了发送文件,你可以使用 `send_file` 辅助方法: + +~~~~ ruby +get '/' do + send_file 'foo.png' +end +~~~~ + +也可以带一些选项: + +~~~~ ruby +send_file 'foo.png', :type => :jpg +~~~~ + +可用的选项有: + +
+
filename
+
响应中的文件名,默认是真实文件的名字。
+ +
last_modified
+
Last-Modified 消息头的值,默认是文件的mtime(修改时间)。
+ +
type
+
使用的内容类型,如果没有会从文件扩展名猜测。
+ +
disposition
+
+ 用于 Content-Disposition,可能的包括: nil (默认), :attachment 和 + :inline +
+ +
length
+
Content-Length 的值,默认是文件的大小。
+
+ +如果Rack处理器支持的话,Ruby进程也能使用除streaming以外的方法。 +如果你使用这个辅助方法, Sinatra会自动处理range请求。 + +### 访问请求对象 + +传入的请求对象可以在请求层(过滤器,路由,错误处理) 通过 `request` +方法被访问: + +~~~~ ruby +# 在 http://example.com/example 上运行的应用 +get '/foo' do + request.body # 被客户端设定的请求体(见下) + request.scheme # "http" + request.script_name # "/example" + request.path_info # "/foo" + request.port # 80 + request.request_method # "GET" + request.query_string # "" + request.content_length # request.body的长度 + request.media_type # request.body的媒体类型 + request.host # "example.com" + request.get? # true (其他动词也具有类似方法) + request.form_data? # false + request["SOME_HEADER"] # SOME_HEADER header的值 + request.referrer # 客户端的referrer 或者 '/' + request.user_agent # user agent (被 :agent 条件使用) + request.cookies # 浏览器 cookies 哈希 + request.xhr? # 这是否是ajax请求? + request.url # "http://example.com/example/foo" + request.path # "/example/foo" + request.ip # 客户端IP地址 + request.secure? # false(如果是ssl则为true) + request.forwarded? # true (如果是运行在反向代理之后) + request.env # Rack中使用的未处理的env哈希 +end +~~~~ + +一些选项,例如 `script_name` 或者 `path_info` 也是可写的: + +~~~~ ruby +before { request.path_info = "/" } + +get "/" do + "all requests end up here" +end +~~~~ + +`request.body` 是一个IO或者StringIO对象: + +~~~~ ruby +post "/api" do + request.body.rewind # 如果已经有人读了它 + data = JSON.parse request.body.read + "Hello #{data['name']}!" +end +~~~~ + +### 附件 + +你可以使用 `attachment` 辅助方法来告诉浏览器响应 +应当被写入磁盘而不是在浏览器中显示。 + +~~~~ ruby +get '/' do + attachment + "store it!" +end +~~~~ + +你也可以传递一个文件名: + +~~~~ ruby +get '/' do + attachment "info.txt" + "store it!" +end +~~~~ + +### 查找模板文件 + +`find_template` 辅助方法被用于在填充时查找模板文件: + +~~~~ ruby +find_template settings.views, 'foo', Tilt[:haml] do |file| + puts "could be #{file}" +end +~~~~ + +这并不是很有用。但是在你需要重载这个方法 +来实现你自己的查找机制的时候有用。 比如,如果你想支持多于一个视图目录: + +~~~~ ruby +set :views, ['views', 'templates'] + +helpers do + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end +end +~~~~ + +另一个例子是为不同的引擎使用不同的目录: + +~~~~ ruby +set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' + +helpers do + def find_template(views, name, engine, &block) + _, folder = views.detect { |k,v| engine == Tilt[k] } + folder ||= views[:default] + super(folder, name, engine, &block) + end +end +~~~~ + +你可以很容易地包装成一个扩展然后与他人分享! + +请注意 `find_template` 并不会检查文件真的存在, +而是对任何可能的路径调用给入的代码块。这并不会带来性能问题, 因为 +`render` 会在找到文件的时候马上使用 `break` 。 +同样的,模板的路径(和内容)会在除development mode以外的场合 +被缓存。你应该时刻提醒自己这一点, 如果你真的想写一个非常疯狂的方法。 + +## 配置 + +运行一次,在启动的时候,在任何环境下: + +~~~~ ruby +configure do + # setting one option + set :option, 'value' + + # setting multiple options + set :a => 1, :b => 2 + + # same as `set :option, true` + enable :option + + # same as `set :option, false` + disable :option + + # you can also have dynamic settings with blocks + set(:css_dir) { File.join(views, 'css') } +end +~~~~ + +只当环境 (RACK\_ENV environment 变量) 被设定为 `:production`的时候运行: + +~~~~ ruby +configure :production do + ... +end +~~~~ + +当环境被设定为 `:production` 或者 `:test`的时候运行: + +~~~~ ruby +configure :production, :test do + ... +end +~~~~ + +你可以使用 `settings` 获得这些配置: + +~~~~ ruby +configure do + set :foo, 'bar' +end + +get '/' do + settings.foo? # => true + settings.foo # => 'bar' + ... +end +~~~~ + +### 可选的设置 + +
+
absolute_redirects
+
+

+ 如果被禁用,Sinatra会允许使用相对路径重定向, 但是,Sinatra就不再遵守 + RFC 2616标准 (HTTP 1.1), 该标准只允许绝对路径重定向。 +

+ +

+ 如果你的应用运行在一个未恰当设置的反向代理之后, + 你需要启用这个选项。注意 url 辅助方法 仍然会生成绝对 URL,除非你传入 + false 作为第二参数。 +

+

+ 默认禁用。 +

+
+ +
add_charset
+
+

+ 设定 content_type 辅助方法会 自动加上字符集信息的多媒体类型。 +

+ +

+ 你应该添加而不是覆盖这个选项: + settings.add_charset << "application/foobar" +

+
+ +
app_file
+
+ 主应用文件,用来检测项目的根路径, views和public文件夹和内联模板。 +
+ +
bind
+
+ 绑定的IP 地址 (默认: 0.0.0.0)。 仅对于内置的服务器有用。 +
+ +
default_encoding
+
+ 默认编码 (默认为 "utf-8")。 +
+ +
dump_errors
+
+ 在log中显示错误。 +
+ +
environment
+
+ 当前环境,默认是 ENV['RACK_ENV'], 或者 "development" 如果不可用。 +
+ +
logging
+
+ 使用logger +
+ +
lock
+
+

+ 对每一个请求放置一个锁, 只使用进程并发处理请求。 +

+ +

+ 如果你的应用不是线程安全则需启动。 默认禁用。 +

+
+ +
method_override
+
+ 使用 _method 魔法以允许在旧的浏览器中在 表单中使用 put/delete 方法 +
+ +
port
+
+ 监听的端口号。只对内置服务器有用。 +
+ +
prefixed_redirects
+
+ 是否添加 request.script_name 到 + 重定向请求,如果没有设定绝对路径。那样的话 redirect '/foo' 会和 + redirect to('/foo')起相同作用。默认禁用。 +
+ +
public_folder
+
+ public文件夹的位置。 +
+ +
reload_templates
+
+ 是否每个请求都重新载入模板。 在development mode和 Ruby 1.8.6 + 中被企业(用来 消除一个Ruby内存泄漏的bug)。 +
+ +
root
+
+ 项目的根目录。 +
+ +
raise_errors
+
+ 抛出异常(应用会停下)。 +
+ +
run
+
+ 如果启用,Sinatra会开启web服务器。 如果使用rackup或其他方式则不要启用。 +
+ +
running
+
+ 内置的服务器在运行吗? 不要修改这个设置! +
+ +
server
+
+ 服务器,或用于内置服务器的列表。 默认是 [‘thin’, ‘mongrel’, ‘webrick’], + 顺序表明了 优先级。 +
+ +
sessions
+
+ 开启基于cookie的sesson。 +
+ +
show_exceptions
+
+ 在浏览器中显示一个stack trace。 +
+ +
static
+
+ Sinatra是否处理静态文件。 当服务器能够处理则禁用。 禁用会增强性能。 + 默认开启。 +
+ +
views
+
+ views 文件夹。 +
+
+ + +## 错误处理 + +错误处理在与路由和前置过滤器相同的上下文中运行, +这意味着你可以使用许多好东西,比如 `haml`, `erb`, `halt`,等等。 + +### 未找到 + +当一个 `Sinatra::NotFound` 错误被抛出的时候, +或者响应状态码是404,`not_found` 处理器会被调用: + +~~~~ ruby +not_found do + 'This is nowhere to be found' +end +~~~~ + +### 错误 + +`error` 处理器,在任何路由代码块或者过滤器抛出异常的时候会被调用。 +异常对象可以通过`sinatra.error` Rack 变量获得: + +~~~~ ruby +error do + 'Sorry there was a nasty error - ' + env['sinatra.error'].message +end +~~~~ + +自定义错误: + +~~~~ ruby +error MyCustomError do + 'So what happened was...' + env['sinatra.error'].message +end +~~~~ + +那么,当这个发生的时候: + +~~~~ ruby +get '/' do + raise MyCustomError, 'something bad' +end +~~~~ + +你会得到: + + So what happened was... something bad + +另一种替代方法是,为一个状态码安装错误处理器: + +~~~~ ruby +error 403 do + 'Access forbidden' +end + +get '/secret' do + 403 +end +~~~~ + +或者一个范围: + +~~~~ ruby +error 400..510 do + 'Boom' +end +~~~~ + +在运行在development环境下时,Sinatra会安装特殊的 `not_found` 和 `error` +处理器。 + +## Rack 中间件 + +Sinatra 依靠 [Rack](http://rack.github.io/), 一个面向Ruby +web框架的最小标准接口。 +Rack的一个最有趣的面向应用开发者的能力是支持“中间件”——坐落在服务器和你的应用之间, +监视 并/或 操作HTTP请求/响应以 提供多样类型的常用功能。 + +Sinatra 让建立Rack中间件管道异常简单, 通过顶层的 `use` 方法: + +~~~~ ruby +require 'sinatra' +require 'my_custom_middleware' + +use Rack::Lint +use MyCustomMiddleware + +get '/hello' do + 'Hello World' +end +~~~~ + +`use` 的语义和在 +[Rack::Builder](http://rubydoc.info/github/rack/rack/master/Rack/Builder) +DSL(在rack文件中最频繁使用)中定义的完全一样。例如,`use` 方法接受 +多个/可变 参数,包括代码块: + +~~~~ ruby +use Rack::Auth::Basic do |username, password| + username == 'admin' && password == 'secret' +end +~~~~ + +Rack中分布有多样的标准中间件,针对日志, +调试,URL路由,认证和session处理。 Sinatra会自动使用这里面的大部分组件, +所以你一般不需要显示地 `use` 他们。 + +## 测试 + +Sinatra的测试可以使用任何基于Rack的测试程序库或者框架来编写。 +[Rack::Test](http://gitrdoc.com/brynary/rack-test) 是推荐候选: + +~~~~ ruby +require 'my_sinatra_app' +require 'minitest/autorun' +require 'rack/test' + +class MyAppTest < Minitest::Test + include Rack::Test::Methods + + def app + Sinatra::Application + end + + def test_my_default + get '/' + assert_equal 'Hello World!', last_response.body + end + + def test_with_params + get '/meet', :name => 'Frank' + assert_equal 'Hello Frank!', last_response.body + end + + def test_with_rack_env + get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' + assert_equal "You're using Songbird!", last_response.body + end +end +~~~~ + +请注意: 内置的 Sinatra::Test 模块和 Sinatra::TestHarness 类 在 0.9.2 +版本已废弃。 + +## Sinatra::Base - 中间件,程序库和模块化应用 + +把你的应用定义在顶层,对于微型应用这会工作得很好, +但是在构建可复用的组件时候会带来客观的不利, 比如构建Rack中间件,Rails +metal,带有服务器组件的简单程序库, +或者甚至是Sinatra扩展。顶层的DSL污染了Object命名空间, +并假定了一个微型应用风格的配置 (例如, 单一的应用文件, ./public 和 +./views 目录,日志,异常细节页面,等等)。 这时应该让 Sinatra::Base +走到台前了: + +~~~~ ruby +require 'sinatra/base' + +class MyApp < Sinatra::Base + set :sessions, true + set :foo, 'bar' + + get '/' do + 'Hello world!' + end +end +~~~~ + +Sinatra::Base子类可用的方法实际上就是通过顶层 DSL 可用的方法。 +大部分顶层应用可以通过两个改变转换成Sinatra::Base组件: + +- 你的文件应当引入 `sinatra/base` 而不是 `sinatra`; + 否则,所有的Sinatra的 DSL 方法将会被引进到 主命名空间。 + +- 把你的应用的路由,错误处理,过滤器和选项放在 + 一个Sinatra::Base的子类中。 + +`+Sinatra::Base+` 是一张白纸。大部分的选项默认是禁用的, +包含内置的服务器。参见 +[选项和配置](http://sinatra.github.com/configuration.html) +查看可用选项的具体细节和他们的行为。 + +### 模块化 vs. 传统的方式 + +与通常的认识相反,传统的方式没有任何错误。 +如果它适合你的应用,你不需要转换到模块化的应用。 + +和模块化方式相比只有两个缺点: + +- 你对每个Ruby进程只能定义一个Sinatra应用,如果你需要更多, + 切换到模块化方式。 + +- 传统方式使用代理方法污染了 Object 。如果你打算 把你的应用封装进一个 + library/gem,转换到模块化方式。 + +没有任何原因阻止你混合模块化和传统方式。 + +如果从一种转换到另一种,你需要注意settings中的 一些微小的不同: + + Setting Classic Modular + + app_file file loading sinatra nil + run $0 == app_file false + logging true false + method_override true false + inline_templates true false + +### 运行一个模块化应用 + +有两种方式运行一个模块化应用,使用 `run!`来运行: + +~~~~ ruby +# my_app.rb +require 'sinatra/base' + +class MyApp < Sinatra::Base + # ... app code here ... + + # start the server if ruby file executed directly + run! if app_file == $0 +end +~~~~ + +运行: + + ruby my_app.rb + +或者使用一个 `config.ru`,允许你使用任何Rack处理器: + +~~~~ ruby +# config.ru +require './my_app' +run MyApp +~~~~ + +运行: + + rackup -p 4567 + +### 使用config.ru运行传统方式的应用 + +编写你的应用: + +~~~~ ruby +# app.rb +require 'sinatra' + +get '/' do + 'Hello world!' +end +~~~~ + +加入相应的 `config.ru`: + +~~~~ ruby +require './app' +run Sinatra::Application +~~~~ + +### 什么时候用 config.ru? + +以下情况你可能需要使用 `config.ru`: + +- 你要使用不同的 Rack 处理器部署 (Passenger, Unicorn, Heroku, …). + +- 你想使用一个或者多个 `Sinatra::Base`的子类. + +- 你只想把Sinatra当作中间件使用,而不是端点。 + +**你并不需要切换到`config.ru`仅仅因为你切换到模块化方式, +你同样不需要切换到模块化方式, 仅仅因为要运行 `config.ru`.** + +### 把Sinatra当成中间件来使用 + +不仅Sinatra有能力使用其他的Rack中间件,任何Sinatra +应用程序都可以反过来自身被当作中间件,被加在任何Rack端点前面。 +这个端点可以是任何Sinatra应用,或者任何基于Rack的应用程序 +(Rails/Ramaze/Camping/…)。 + +~~~~ ruby +require 'sinatra/base' + +class LoginScreen < Sinatra::Base + enable :sessions + + get('/login') { haml :login } + + post('/login') do + if params['name'] = 'admin' and params['password'] = 'admin' + session['user_name'] = params['name'] + else + redirect '/login' + end + end +end + +class MyApp < Sinatra::Base + # 在前置过滤器前运行中间件 + use LoginScreen + + before do + unless session['user_name'] + halt "Access denied, please login." + end + end + + get('/') { "Hello #{session['user_name']}." } +end +~~~~ + +## 变量域和绑定 + +当前所在的变量域决定了哪些方法和变量是可用的。 + +### 应用/类 变量域 + +每个Sinatra应用相当与Sinatra::Base的一个子类。 +如果你在使用顶层DSL(`require 'sinatra'`),那么这个类就是 +Sinatra::Application,或者这个类就是你显式创建的子类。 +在类层面,你具有的方法类似于 \`get\` 或者 \`before\`,但是你不能访问 +\`request\` 对象或者 \`session\`, 因为对于所有的请求, +只有单一的应用类。 + +通过 \`set\` 创建的选项是类层面的方法: + +~~~~ ruby +class MyApp < Sinatra::Base + # 嘿,我在应用变量域! + set :foo, 42 + foo # => 42 + + get '/foo' do + # 嘿,我不再处于应用变量域了! + end +end +~~~~ + +在下列情况下你将拥有应用变量域的绑定: + +- 在应用类中 + +- 在扩展中定义的方法 + +- 传递给 \`helpers\` 的代码块 + +- 用作\`set\`值的过程/代码块 + +你可以访问变量域对象(就是应用类)就像这样: + +- 通过传递给代码块的对象 (`configure { |c| ... }`) + +- 在请求变量域中使用\`settings\` + +### 请求/实例 变量域 + +对于每个进入的请求,一个新的应用类的实例会被创建 +所有的处理器代码块在该变量域被运行。在这个变量域中, 你可以访问 +\`request\` 和 \`session\` 对象,或者调用填充方法比如 \`erb\` 或者 +\`haml\`。你可以在请求变量域当中通过\`settings\`辅助方法 +访问应用变量域: + +~~~~ ruby +class MyApp < Sinatra::Base + # 嘿,我在应用变量域! + get '/define_route/:name' do + # 针对 '/define_route/:name' 的请求变量域 + @value = 42 + + settings.get("/#{params['name']}") do + # 针对 "/#{params['name']}" 的请求变量域 + @value # => nil (并不是相同的请求) + end + + "Route defined!" + end +end +~~~~ + +在以下情况将获得请求变量域: + +- get/head/post/put/delete 代码块 + +- 前置/后置 过滤器 + +- 辅助方法 + +- 模板/视图 + +### 代理变量域 + +代理变量域只是把方法转送到类变量域。可是, +他并非表现得100%类似于类变量域, 因为你并不能获得类的绑定: +只有显式地标记为供代理使用的方法才是可用的, +而且你不能和类变量域共享变量/状态。(解释:你有了一个不同的 \`self\`)。 +你可以显式地增加方法代理,通过调用 +`Sinatra::Delegator.delegate :method_name`。 + +在以下情况将获得代理变量域: + +- 顶层的绑定,如果你做过 `require "sinatra"` + +- 在扩展了 \`Sinatra::Delegator\` mixin的对象 + +自己在这里看一下代码: [Sinatra::Delegator +mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) +已经 +[被包含进了主命名空间](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28)。 + +## 命令行 + +Sinatra 应用可以被直接运行: + + ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] + +选项是: + + -h # help + -p # 设定端口 (默认是 4567) + -o # 设定主机名 (默认是 0.0.0.0) + -e # 设定环境 (默认是 development) + -s # 限定 rack 服务器/处理器 (默认是 thin) + -x # 打开互斥锁 (默认是 off) + +## 必要条件 + +推荐在 Ruby 1.8.7, 1.9.2, JRuby 或者 Rubinius 上安装Sinatra。 + +下面的Ruby版本是官方支持的: + +
+
Ruby 1.8.6
+
+ 不推荐在1.8.6上安装Sinatra, 但是直到Sinatra + 1.3.0发布才会放弃对它的支持。 RDoc 和 + CoffeScript模板不被这个Ruby版本支持。 + 1.8.6在它的Hash实现中包含一个内存泄漏问题, + 该问题会被1.1.1版本之前的Sinatra引发。 + 当前版本使用性能下降的代价排除了这个问题。你需要把Rack降级到1.1.x, + 因为Rack \>= 1.2不再支持1.8.6。 +
+ +
Ruby 1.8.7
+
+ 1.8.7 被完全支持,但是,如果没有特别原因, 我们推荐你升级到 1.9.2 + 或者切换到 JRuby 或者 Rubinius. +
+ +
Ruby 1.9.2
+
+ 1.9.2 被支持而且推荐。注意 Radius 和 Markaby 模板并不和1.9兼容。不要使用 + 1.9.2p0, 它被已知会产生 segmentation faults. +
+ +
Rubinius
+
+ Rubinius 被官方支持 (Rubinius \>= 1.2.2), 除了Textile模板。 +
+ +
JRuby
+
+ JRuby 被官方支持 (JRuby \>= 1.5.6)。 目前未知和第三方模板库有关的问题, + 但是,如果你选择了JRuby,请查看一下JRuby rack 处理器, 因为 Thin web + 服务器还没有在JRuby上获得支持。 +
+
+ +我们也会时刻关注新的Ruby版本。 + +下面的 Ruby 实现没有被官方支持, 但是已知可以运行 Sinatra: + +- JRuby 和 Rubinius 老版本 + +- MacRuby + +- Maglev + +- IronRuby + +- Ruby 1.9.0 and 1.9.1 + +不被官方支持的意思是,如果在不被支持的平台上有运行错误, +我们假定不是我们的问题,而是平台的问题。 + +Sinatra应该会运行在任何支持上述Ruby实现的操作系统。 + +## 紧追前沿 + +如果你喜欢使用 Sinatra 的最新鲜的代码,请放心的使用 master +分支来运行你的程序,它会非常的稳定。 + + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb + +我们也会不定期的发布预发布gems,所以你也可以运行 + + gem install sinatra --pre + +来获得最新的特性。 + +### 通过Bundler + +如果你想使用最新的Sinatra运行你的应用,通过 +[Bundler](http://gembundler.com/) 是推荐的方式。 + +首先,安装bundler,如果你还没有安装: + + gem install bundler + +然后,在你的项目目录下,创建一个 `Gemfile`: + +~~~~ ruby +source :rubygems +gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" + +# 其他的依赖关系 +gem 'haml' # 举例,如果你想用haml +gem 'activerecord', '~> 3.0' # 也许你还需要 ActiveRecord 3.x +~~~~ + +请注意在这里你需要列出你的应用的所有依赖关系。 Sinatra的直接依赖关系 +(Rack and Tilt) 将会, 自动被Bundler获取和添加。 + +现在你可以像这样运行你的应用: + + bundle exec ruby myapp.rb + +### 使用自己的 + +创建一个本地克隆并通过 `sinatra/lib` 目录运行你的应用, 通过 +`$LOAD_PATH`: + + cd myapp + git clone git://github.com/sinatra/sinatra.git + ruby -Isinatra/lib myapp.rb + +为了在未来更新 Sinatra 源代码: + + cd myapp/sinatra + git pull + +### 全局安装 + +你可以自行编译 gem : + + git clone git://github.com/sinatra/sinatra.git + cd sinatra + rake sinatra.gemspec + rake install + +如果你以root身份安装 gems,最后一步应该是 + + sudo rake install + +## 更多 + +- [项目主页(英文)](http://www.sinatrarb.com/) - 更多的文档, + 新闻,和其他资源的链接。 + +- [贡献](http://www.sinatrarb.com/contributing) - 找到了一个bug? + 需要帮助?有了一个 patch? + +- [问题追踪](http://github.com/sinatra/sinatra/issues) + +- [Twitter](http://twitter.com/sinatra) + +- [邮件列表](http://groups.google.com/group/sinatrarb/topics) + +- [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) on + [freenode.net](http://freenode.net) + +- [Sinatra宝典](https://github.com/sinatra/sinatra-book/) Cookbook教程 + +- [Sinatra使用技巧](http://recipes.sinatrarb.com/) 网友贡献的实用技巧 + +- [最新版本](http://rubydoc.info/gems/sinatra)API文档;[http://rubydoc.info](http://rubydoc.info)的[当前HEAD](http://rubydoc.info/github/sinatra/sinatra) + +- [CI服务器](http://travis-ci.org/sinatra/sinatra) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Rakefile b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Rakefile new file mode 100644 index 000000000..d4a6bf12c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/Rakefile @@ -0,0 +1,199 @@ +require 'rake/clean' +require 'rake/testtask' +require 'fileutils' +require 'date' + +# CI Reporter is only needed for the CI +begin + require 'ci/reporter/rake/test_unit' +rescue LoadError +end + +task :default => :test +task :spec => :test + +CLEAN.include "**/*.rbc" + +def source_version + @source_version ||= begin + load './lib/sinatra/version.rb' + Sinatra::VERSION + end +end + +def prev_feature + source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s } +end + +def prev_version + return prev_feature + '.0' if source_version.end_with? '.0' + source_version.gsub(/\d+$/) { |s| s.to_i - 1 } +end + +# SPECS =============================================================== + +task :test do + ENV['LANG'] = 'C' + ENV.delete 'LC_CTYPE' +end + +Rake::TestTask.new(:test) do |t| + t.test_files = FileList['test/*_test.rb'] + t.ruby_opts = ['-rubygems'] if defined? Gem + t.ruby_opts << '-I.' + t.warning = true +end + +Rake::TestTask.new(:"test:core") do |t| + core_tests = %w[base delegator encoding extensions filter + helpers mapped_error middleware radius rdoc + readme request response result route_added_hook + routing server settings sinatra static templates] + t.test_files = core_tests.map {|n| "test/#{n}_test.rb"} + t.ruby_opts = ["-rubygems"] if defined? Gem + t.ruby_opts << "-I." + t.warning = true +end + +# Rcov ================================================================ + +namespace :test do + desc 'Measures test coverage' + task :coverage do + rm_f "coverage" + sh "rcov -Ilib test/*_test.rb" + end +end + +# Website ============================================================= + +desc 'Generate RDoc under doc/api' +task 'doc' => ['doc:api'] +task('doc:api') { sh "yardoc -o doc/api" } +CLEAN.include 'doc/api' + +# README =============================================================== + +task :add_template, [:name] do |t, args| + Dir.glob('README.*') do |file| + code = File.read(file) + if code =~ /^===.*#{args.name.capitalize}/ + puts "Already covered in #{file}" + else + template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] + if !template + puts "Liquid not found in #{file}" + else + puts "Adding section to #{file}" + template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) + code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" + File.open(file, "w") { |f| f << code } + end + end + end +end + +# Thanks in announcement =============================================== + +team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase"] +desc "list of contributors" +task :thanks, [:release,:backports] do |t, a| + a.with_defaults :release => "#{prev_version}..HEAD", + :backports => "#{prev_feature}.0..#{prev_feature}.x" + included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } + excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } + commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } + authors = commits.keys.sort_by { |n| - commits[n].size } - team + puts authors[0..-2].join(', ') << " and " << authors.last, + "(based on commits included in #{a.release}, but not in #{a.backports})" +end + +desc "list of authors" +task :authors, [:commit_range, :format, :sep] do |t, a| + a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' + authors = Hash.new(0) + blake = "Blake Mizerany" + overall = 0 + mapping = { + "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, + "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", + "Wu Jiang (nouse)" => "Wu Jiang" } + `git shortlog -s #{a.commit_range}`.lines.map do |line| + line = line.force_encoding 'binary' if line.respond_to? :force_encoding + num, name = line.split("\t", 2).map(&:strip) + authors[mapping[name] || name] += num.to_i + overall += num.to_i + end + puts "#{overall} commits by #{authors.count} authors:" + puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) +end + +desc "generates TOC" +task :toc, [:readme] do |t, a| + a.with_defaults :readme => 'README.md' + + def self.link(title) + title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') + end + + puts "* [Sinatra](#sinatra)" + title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain + File.binread(a.readme).scan(/^##.*/) do |line| + puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } + end +end + +# 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 'release' => ['test', package('.gem')] do + if File.binread("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i + fail 'please update changes first' unless %x{git symbolic-ref HEAD} == "refs/heads/prerelease\n" + end + + sh <<-SH + gem install #{package('.gem')} --local && + gem push #{package('.gem')} && + git commit --allow-empty -a -m '#{source_version} release' && + git tag -s v#{source_version} -m '#{source_version} release' && + git tag -s #{source_version} -m '#{source_version} release' && + git push && (git push sinatra || true) && + git push --tags && (git push sinatra --tags || true) + SH + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/chat.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/chat.rb new file mode 100755 index 000000000..8e47b7d1b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/chat.rb @@ -0,0 +1,61 @@ +#!/usr/bin/env ruby -I ../lib -I lib +# coding: utf-8 +require 'sinatra' +set :server, 'thin' +connections = [] + +get '/' do + halt erb(:login) unless params[:user] + erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } +end + +get '/stream', :provides => 'text/event-stream' do + stream :keep_open do |out| + connections << out + out.callback { connections.delete(out) } + end +end + +post '/' do + connections.each { |out| out << "data: #{params[:msg]}\n\n" } + 204 # response without entity body +end + +__END__ + +@@ layout + + + Super Simple Chat with Sinatra + + + + <%= yield %> + + +@@ login +
+ + + +
+ +@@ chat +

+
+ +
+ + + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/simple.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/simple.rb new file mode 100755 index 000000000..2697f94bf --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/simple.rb @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby -I ../lib -I lib +require 'sinatra' +get('/') { 'this is a simple app' } diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/stream.ru b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/stream.ru new file mode 100644 index 000000000..074c66b99 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/examples/stream.ru @@ -0,0 +1,26 @@ +# this example does *not* work properly with WEBrick +# +# run *one* of these: +# +# rackup -s mongrel stream.ru # gem install mongrel +# thin -R stream.ru start # gem install thin +# unicorn stream.ru # gem install unicorn +# puma stream.ru # gem install puma + +require 'sinatra/base' + +class Stream < Sinatra::Base + get '/' do + content_type :txt + + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + sleep 1 + out << "- dary!\n" + end + end +end + +run Stream diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra.rb new file mode 100644 index 000000000..68261380c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra.rb @@ -0,0 +1,3 @@ +require 'sinatra/main' + +enable :inline_templates diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/base.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/base.rb new file mode 100644 index 000000000..8c37a99f4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/base.rb @@ -0,0 +1,2050 @@ +# external dependencies +require 'rack' +require 'tilt' +require 'rack/protection' + +# stdlib dependencies +require 'thread' +require 'time' +require 'uri' + +# other files we need +require 'sinatra/show_exceptions' +require 'sinatra/version' + +module Sinatra + # The request object. See Rack::Request for more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Request + class Request < Rack::Request + HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ + HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ + + # Returns an array of acceptable media types for the response + def accept + @env['sinatra.accept'] ||= begin + if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != '' + @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS). + map! { |e| AcceptEntry.new(e) }.sort + else + [AcceptEntry.new('*/*')] + end + end + end + + def accept?(type) + preferred_type(type).to_s.include?(type) + end + + def preferred_type(*types) + accepts = accept # just evaluate once + return accepts.first if types.empty? + types.flatten! + return types.first if accepts.empty? + accepts.detect do |pattern| + type = types.detect { |t| File.fnmatch(pattern, t) } + return type if type + end + end + + alias secure? ssl? + + def forwarded? + @env.include? "HTTP_X_FORWARDED_HOST" + end + + def safe? + get? or head? or options? or trace? + end + + def idempotent? + safe? or put? or delete? or link? or unlink? + end + + def link? + request_method == "LINK" + end + + def unlink? + request_method == "UNLINK" + end + + private + + class AcceptEntry + attr_accessor :params + attr_reader :entry + + def initialize(entry) + params = entry.scan(HEADER_PARAM).map! do |s| + key, value = s.strip.split('=', 2) + value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') + [key, value] + end + + @entry = entry + @type = entry[/[^;]+/].delete(' ') + @params = Hash[params] + @q = @params.delete('q') { 1.0 }.to_f + end + + def <=>(other) + other.priority <=> self.priority + end + + def priority + # We sort in descending order; better matches should be higher. + [ @q, -@type.count('*'), @params.size ] + end + + def to_str + @type + end + + def to_s(full = false) + full ? entry : to_str + end + + def respond_to?(*args) + super or to_str.respond_to?(*args) + end + + def method_missing(*args, &block) + to_str.send(*args, &block) + end + end + end + + # The response object. See Rack::Response and Rack::Response::Helpers for + # more info: + # http://rubydoc.info/github/rack/rack/master/Rack/Response + # http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers + class Response < Rack::Response + DROP_BODY_RESPONSES = [204, 205, 304] + def initialize(*) + super + headers['Content-Type'] ||= 'text/html' + end + + def body=(value) + value = value.body while Rack::Response === value + @body = String === value ? [value.to_str] : value + end + + def each + block_given? ? super : enum_for(:each) + end + + def finish + result = body + + if drop_content_info? + headers.delete "Content-Length" + headers.delete "Content-Type" + end + + if drop_body? + close + result = [] + end + + if calculate_content_length? + # if some other code has already set Content-Length, don't muck with it + # currently, this would be the static file-handler + headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s + end + + [status.to_i, headers, result] + end + + private + + def calculate_content_length? + headers["Content-Type"] and not headers["Content-Length"] and Array === body + end + + def drop_content_info? + status.to_i / 100 == 1 or drop_body? + end + + def drop_body? + DROP_BODY_RESPONSES.include?(status.to_i) + end + end + + # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however, + # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. + # This middleware will detect an extended body object and will make sure it reaches the + # handler directly. We do this here, so our middleware and middleware set up by the app will + # still be able to run. + class ExtendedRack < Struct.new(:app) + def call(env) + result, callback = app.call(env), env['async.callback'] + return result unless callback and async?(*result) + after_response { callback.call result } + setup_close(env, *result) + throw :async + end + + private + + def setup_close(env, status, headers, body) + return unless body.respond_to? :close and env.include? 'async.close' + env['async.close'].callback { body.close } + env['async.close'].errback { body.close } + end + + def after_response(&block) + raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine + EventMachine.next_tick(&block) + end + + def async?(status, headers, body) + return true if status == -1 + body.respond_to? :callback and body.respond_to? :errback + end + end + + # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing, + # if another CommonLogger is already in the middleware chain. + class CommonLogger < Rack::CommonLogger + def call(env) + env['sinatra.commonlogger'] ? @app.call(env) : super + end + + superclass.class_eval do + alias call_without_check call unless method_defined? :call_without_check + def call(env) + env['sinatra.commonlogger'] = true + call_without_check(env) + end + end + end + + class NotFound < NameError #:nodoc: + def http_status; 404 end + end + + # Methods available to routes, before/after filters, and views. + module Helpers + # Set or retrieve the response status code. + def status(value = nil) + response.status = value if value + response.status + end + + # Set or retrieve the response body. When a block is given, + # evaluation is deferred until the body is read with #each. + def body(value = nil, &block) + if block_given? + def block.each; yield(call) end + response.body = block + elsif value + headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream) + response.body = value + else + response.body + end + end + + # Halt processing and redirect to the URI provided. + def redirect(uri, *args) + if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' + status 303 + else + status 302 + end + + # According to RFC 2616 section 14.30, "the field value consists of a + # single absolute URI" + response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) + halt(*args) + end + + # Generates the absolute URI for a given path in the app. + # Takes Rack routers and reverse proxies into account. + def uri(addr = nil, absolute = true, add_script_name = true) + return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/ + uri = [host = ""] + if absolute + host << "http#{'s' if request.secure?}://" + if request.forwarded? or request.port != (request.secure? ? 443 : 80) + host << request.host_with_port + else + host << request.host + end + end + uri << request.script_name.to_s if add_script_name + uri << (addr ? addr : request.path_info).to_s + File.join uri + end + + alias url uri + alias to uri + + # Halt processing and return the error status provided. + def error(code, body = nil) + code, body = 500, code.to_str if code.respond_to? :to_str + response.body = body unless body.nil? + halt code + end + + # Halt processing and return a 404 Not Found. + def not_found(body = nil) + error 404, body + end + + # Set multiple response headers with Hash. + def headers(hash = nil) + response.headers.merge! hash if hash + response.headers + end + + # Access the underlying Rack session. + def session + request.session + end + + # Access shared logger object. + def logger + request.logger + end + + # Look up a media type by file extension in Rack's mime registry. + def mime_type(type) + Base.mime_type(type) + end + + # Set the Content-Type of the response body given a media type or file + # extension. + def content_type(type = nil, params = {}) + return response['Content-Type'] unless type + default = params.delete :default + mime_type = mime_type(type) || default + fail "Unknown media type: %p" % type if mime_type.nil? + mime_type = mime_type.dup + unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } + params[:charset] = params.delete('charset') || settings.default_encoding + end + params.delete :charset if mime_type.include? 'charset' + unless params.empty? + mime_type << (mime_type.include?(';') ? ', ' : ';') + mime_type << params.map do |key, val| + val = val.inspect if val =~ /[";,]/ + "#{key}=#{val}" + end.join(', ') + end + response['Content-Type'] = mime_type + end + + # Set the Content-Disposition to "attachment" with the specified filename, + # instructing the user agents to prompt to save. + def attachment(filename = nil, disposition = 'attachment') + response['Content-Disposition'] = disposition.to_s + if filename + params = '; filename="%s"' % File.basename(filename) + response['Content-Disposition'] << params + ext = File.extname(filename) + content_type(ext) unless response['Content-Type'] or ext.empty? + end + end + + # Use the contents of the file at +path+ as the response body. + def send_file(path, opts = {}) + if opts[:type] or not response['Content-Type'] + content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' + end + + disposition = opts[:disposition] + filename = opts[:filename] + disposition = 'attachment' if disposition.nil? and filename + filename = path if filename.nil? + attachment(filename, disposition) if disposition + + last_modified opts[:last_modified] if opts[:last_modified] + + file = Rack::File.new nil + file.path = path + result = file.serving env + result[1].each { |k,v| headers[k] ||= v } + headers['Content-Length'] = result[1]['Content-Length'] + opts[:status] &&= Integer(opts[:status]) + halt opts[:status] || result[0], result[2] + rescue Errno::ENOENT + not_found + end + + # Class of the response body in case you use #stream. + # + # Three things really matter: The front and back block (back being the + # block generating content, front the one sending it to the client) and + # the scheduler, integrating with whatever concurrency feature the Rack + # handler is using. + # + # Scheduler has to respond to defer and schedule. + class Stream + def self.schedule(*) yield end + def self.defer(*) yield end + + def initialize(scheduler = self.class, keep_open = false, &back) + @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open + @callbacks, @closed = [], false + end + + def close + return if closed? + @closed = true + @scheduler.schedule { @callbacks.each { |c| c.call }} + end + + def each(&front) + @front = front + @scheduler.defer do + begin + @back.call(self) + rescue Exception => e + @scheduler.schedule { raise e } + end + close unless @keep_open + end + end + + def <<(data) + @scheduler.schedule { @front.call(data.to_s) } + self + end + + def callback(&block) + return yield if closed? + @callbacks << block + end + + alias errback callback + + def closed? + @closed + end + end + + # Allows to start sending data to the client even though later parts of + # the response body have not yet been generated. + # + # The close parameter specifies whether Stream#close should be called + # after the block has been executed. This is only relevant for evented + # servers like Thin or Rainbows. + def stream(keep_open = false) + scheduler = env['async.callback'] ? EventMachine : Stream + current = @params.dup + body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } } + end + + # Specify response freshness policy for HTTP caches (Cache-Control header). + # Any number of non-value directives (:public, :private, :no_cache, + # :no_store, :must_revalidate, :proxy_revalidate) may be passed along with + # a Hash of value directives (:max_age, :min_stale, :s_max_age). + # + # cache_control :public, :must_revalidate, :max_age => 60 + # => Cache-Control: public, must-revalidate, max-age=60 + # + # See RFC 2616 / 14.9 for more on standard cache control directives: + # http://tools.ietf.org/html/rfc2616#section-14.9.1 + def cache_control(*values) + if values.last.kind_of?(Hash) + hash = values.pop + hash.reject! { |k,v| v == false } + hash.reject! { |k,v| values << k if v == true } + else + hash = {} + end + + values.map! { |value| value.to_s.tr('_','-') } + hash.each do |key, value| + key = key.to_s.tr('_', '-') + value = value.to_i if key == "max-age" + values << "#{key}=#{value}" + end + + response['Cache-Control'] = values.join(', ') if values.any? + end + + # Set the Expires header and Cache-Control/max-age directive. Amount + # can be an integer number of seconds in the future or a Time object + # indicating when the response should be considered "stale". The remaining + # "values" arguments are passed to the #cache_control helper: + # + # expires 500, :public, :must_revalidate + # => Cache-Control: public, must-revalidate, max-age=60 + # => Expires: Mon, 08 Jun 2009 08:50:17 GMT + # + def expires(amount, *values) + values << {} unless values.last.kind_of?(Hash) + + if amount.is_a? Integer + time = Time.now + amount.to_i + max_age = amount + else + time = time_for amount + max_age = time - Time.now + end + + values.last.merge!(:max_age => max_age) + cache_control(*values) + + response['Expires'] = time.httpdate + end + + # Set the last modified time of the resource (HTTP 'Last-Modified' header) + # and halt if conditional GET matches. The +time+ argument is a Time, + # DateTime, or other object that responds to +to_time+. + # + # When the current request includes an 'If-Modified-Since' header that is + # equal or later than the time specified, execution is immediately halted + # with a '304 Not Modified' response. + def last_modified(time) + return unless time + time = time_for time + response['Last-Modified'] = time.httpdate + return if env['HTTP_IF_NONE_MATCH'] + + if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i + halt 304 if since >= time.to_i + end + + if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] + # compare based on seconds since epoch + since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i + halt 412 if since < time.to_i + end + rescue ArgumentError + end + + ETAG_KINDS = [:strong, :weak] + # Set the response entity tag (HTTP 'ETag' header) and halt if conditional + # GET matches. The +value+ argument is an identifier that uniquely + # identifies the current version of the resource. The +kind+ argument + # indicates whether the etag should be used as a :strong (default) or :weak + # cache validator. + # + # When the current request includes an 'If-None-Match' header with a + # matching etag, execution is immediately halted. If the request method is + # GET or HEAD, a '304 Not Modified' response is sent. + def etag(value, options = {}) + # Before touching this code, please double check RFC 2616 14.24 and 14.26. + options = {:kind => options} unless Hash === options + kind = options[:kind] || :strong + new_resource = options.fetch(:new_resource) { request.post? } + + unless ETAG_KINDS.include?(kind) + raise ArgumentError, ":strong or :weak expected" + end + + value = '"%s"' % value + value = "W/#{value}" if kind == :weak + response['ETag'] = value + + if success? or status == 304 + if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource + halt(request.safe? ? 304 : 412) + end + + if env['HTTP_IF_MATCH'] + halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource + end + end + end + + # Sugar for redirect (example: redirect back) + def back + request.referer + end + + # whether or not the status is set to 1xx + def informational? + status.between? 100, 199 + end + + # whether or not the status is set to 2xx + def success? + status.between? 200, 299 + end + + # whether or not the status is set to 3xx + def redirect? + status.between? 300, 399 + end + + # whether or not the status is set to 4xx + def client_error? + status.between? 400, 499 + end + + # whether or not the status is set to 5xx + def server_error? + status.between? 500, 599 + end + + # whether or not the status is set to 404 + def not_found? + status == 404 + end + + # Generates a Time object from the given value. + # Used by #expires and #last_modified. + def time_for(value) + if value.respond_to? :to_time + value.to_time + elsif value.is_a? Time + value + elsif value.respond_to? :new_offset + # DateTime#to_time does the same on 1.9 + d = value.new_offset 0 + t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction + t.getlocal + elsif value.respond_to? :mday + # Date#to_time does the same on 1.9 + Time.local(value.year, value.mon, value.mday) + elsif value.is_a? Numeric + Time.at value + else + Time.parse value.to_s + end + rescue ArgumentError => boom + raise boom + rescue Exception + raise ArgumentError, "unable to convert #{value.inspect} to a Time object" + end + + private + + # Helper method checking if a ETag value list includes the current ETag. + def etag_matches?(list, new_resource = request.post?) + return !new_resource if list == '*' + list.to_s.split(/\s*,\s*/).include? response['ETag'] + end + + def with_params(temp_params) + original, @params = @params, temp_params + yield + ensure + @params = original if original + end + end + + private + + # Template rendering methods. Each method takes the name of a template + # to render as a Symbol and returns a String with the rendered output, + # as well as an optional hash with additional options. + # + # `template` is either the name or path of the template as symbol + # (Use `:'subdir/myview'` for views in subdirectories), or a string + # that will be rendered. + # + # Possible options are: + # :content_type The content type to use, same arguments as content_type. + # :layout If set to something falsy, no layout is rendered, otherwise + # the specified layout is used (Ignored for `sass` and `less`) + # :layout_engine Engine to use for rendering the layout. + # :locals A hash with local variables that should be available + # in the template + # :scope If set, template is evaluate with the binding of the given + # object rather than the application instance. + # :views Views directory to use. + module Templates + module ContentTyped + attr_accessor :content_type + end + + def initialize + super + @default_layout = :layout + @preferred_extension = nil + end + + def erb(template, options = {}, locals = {}, &block) + render(:erb, template, options, locals, &block) + end + + def erubis(template, options = {}, locals = {}) + warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ + "If you have Erubis installed, it will be used automatically." + render :erubis, template, options, locals + end + + def haml(template, options = {}, locals = {}, &block) + render(:haml, template, options, locals, &block) + end + + def sass(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :sass, template, options, locals + end + + def scss(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :scss, template, options, locals + end + + def less(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :css + render :less, template, options, locals + end + + def stylus(template, options={}, locals={}) + options.merge! :layout => false, :default_content_type => :css + render :styl, template, options, locals + end + + def builder(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:builder, template, options, locals, &block) + end + + def liquid(template, options = {}, locals = {}, &block) + render(:liquid, template, options, locals, &block) + end + + def markdown(template, options = {}, locals = {}) + render :markdown, template, options, locals + end + + def textile(template, options = {}, locals = {}) + render :textile, template, options, locals + end + + def rdoc(template, options = {}, locals = {}) + render :rdoc, template, options, locals + end + + def asciidoc(template, options = {}, locals = {}) + render :asciidoc, template, options, locals + end + + def radius(template, options = {}, locals = {}) + render :radius, template, options, locals + end + + def markaby(template = nil, options = {}, locals = {}, &block) + render_ruby(:mab, template, options, locals, &block) + end + + def coffee(template, options = {}, locals = {}) + options.merge! :layout => false, :default_content_type => :js + render :coffee, template, options, locals + end + + def nokogiri(template = nil, options = {}, locals = {}, &block) + options[:default_content_type] = :xml + render_ruby(:nokogiri, template, options, locals, &block) + end + + def slim(template, options = {}, locals = {}, &block) + render(:slim, template, options, locals, &block) + end + + def creole(template, options = {}, locals = {}) + render :creole, template, options, locals + end + + def mediawiki(template, options = {}, locals = {}) + render :mediawiki, template, options, locals + end + + def wlang(template, options = {}, locals = {}, &block) + render(:wlang, template, options, locals, &block) + end + + def yajl(template, options = {}, locals = {}) + options[:default_content_type] = :json + render :yajl, template, options, locals + end + + def rabl(template, options = {}, locals = {}) + Rabl.register! + render :rabl, template, options, locals + end + + # Calls the given block for every possible template file in views, + # named name.ext, where ext is registered on engine. + def find_template(views, name, engine) + yield ::File.join(views, "#{name}.#{@preferred_extension}") + + if Tilt.respond_to?(:mappings) + Tilt.mappings.each do |ext, engines| + next unless ext != @preferred_extension and engines.include? engine + yield ::File.join(views, "#{name}.#{ext}") + end + else + Tilt.default_mapping.extensions_for(engine).each do |ext| + yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension + end + end + end + + private + + # logic shared between builder and nokogiri + def render_ruby(engine, template, options = {}, locals = {}, &block) + options, template = template, nil if template.is_a?(Hash) + template = Proc.new { block } if template.nil? + render engine, template, options, locals + end + + def render(engine, data, options = {}, locals = {}, &block) + # merge app-level options + engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} + options.merge!(engine_options) { |key, v1, v2| v1 } + + # extract generic options + locals = options.delete(:locals) || locals || {} + views = options.delete(:views) || settings.views || "./views" + layout = options[:layout] + layout = false if layout.nil? && options.include?(:layout) + eat_errors = layout.nil? + layout = engine_options[:layout] if layout.nil? or (layout == true && engine_options[:layout] != false) + layout = @default_layout if layout.nil? or layout == true + layout_options = options.delete(:layout_options) || {} + content_type = options.delete(:content_type) || options.delete(:default_content_type) + layout_engine = options.delete(:layout_engine) || engine + scope = options.delete(:scope) || self + options.delete(:layout) + + # set some defaults + options[:outvar] ||= '@_out_buf' + options[:default_encoding] ||= settings.default_encoding + + # compile and render template + begin + layout_was = @default_layout + @default_layout = false + template = compile_template(engine, data, options, views) + output = template.render(scope, locals, &block) + ensure + @default_layout = layout_was + end + + # render layout + if layout + options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). + merge!(layout_options) + catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } + end + + output.extend(ContentTyped).content_type = content_type if content_type + output + end + + def compile_template(engine, data, options, views) + eat_errors = options.delete :eat_errors + template_cache.fetch engine, data, options, views do + template = Tilt[engine] + raise "Template engine not found: #{engine}" if template.nil? + + case data + when Symbol + body, path, line = settings.templates[data] + if body + body = body.call if body.respond_to?(:call) + template.new(path, line.to_i, options) { body } + else + found = false + @preferred_extension = engine.to_s + find_template(views, data, template) do |file| + path ||= file # keep the initial path rather than the last one + if found = File.exist?(file) + path = file + break + end + end + throw :layout_missing if eat_errors and not found + template.new(path, 1, options) + end + when Proc, String + body = data.is_a?(String) ? Proc.new { data } : data + path, line = settings.caller_locations.first + template.new(path, line.to_i, options, &body) + else + raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." + end + end + end + end + + # Base class for all Sinatra applications and middleware. + class Base + include Rack::Utils + include Helpers + include Templates + + URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI + + attr_accessor :app, :env, :request, :response, :params + attr_reader :template_cache + + def initialize(app = nil) + super() + @app = app + @template_cache = Tilt::Cache.new + yield self if block_given? + end + + # Rack call interface. + def call(env) + dup.call!(env) + end + + def call!(env) # :nodoc: + @env = env + @request = Request.new(env) + @response = Response.new + @params = indifferent_params(@request.params) + template_cache.clear if settings.reload_templates + force_encoding(@params) + + @response['Content-Type'] = nil + invoke { dispatch! } + invoke { error_block!(response.status) } unless @env['sinatra.error'] + + unless @response['Content-Type'] + if Array === body and body[0].respond_to? :content_type + content_type body[0].content_type + else + content_type :html + end + end + + @response.finish + end + + # Access settings defined with Base.set. + def self.settings + self + end + + # Access settings defined with Base.set. + def settings + self.class.settings + end + + def options + warn "Sinatra::Base#options is deprecated and will be removed, " \ + "use #settings instead." + settings + end + + # Exit the current block, halts any further processing + # of the request, and returns the specified response. + def halt(*response) + response = response.first if response.length == 1 + throw :halt, response + end + + # Pass control to the next matching route. + # If there are no more matching routes, Sinatra will + # return a 404 response. + def pass(&block) + throw :pass, block + end + + # Forward the request to the downstream app -- middleware only. + def forward + fail "downstream app not set" unless @app.respond_to? :call + status, headers, body = @app.call env + @response.status = status + @response.body = body + @response.headers.merge! headers + nil + end + + private + + # Run filters defined on the class and all superclasses. + def filter!(type, base = settings) + filter! type, base.superclass if base.superclass.respond_to?(:filters) + base.filters[type].each { |args| process_route(*args) } + end + + # Run routes defined on the class and all superclasses. + def route!(base = settings, pass_block = nil) + if routes = base.routes[@request.request_method] + routes.each do |pattern, keys, conditions, block| + returned_pass_block = process_route(pattern, keys, conditions) do |*args| + env['sinatra.route'] = block.instance_variable_get(:@route_name) + route_eval { block[*args] } + end + + # don't wipe out pass_block in superclass + pass_block = returned_pass_block if returned_pass_block + end + end + + # Run routes defined in superclass. + if base.superclass.respond_to?(:routes) + return route!(base.superclass, pass_block) + end + + route_eval(&pass_block) if pass_block + route_missing + end + + # Run a route block and throw :halt with the result. + def route_eval + throw :halt, yield + end + + # If the current request matches pattern and conditions, fill params + # with keys and call the given block. + # Revert params afterwards. + # + # Returns pass block. + def process_route(pattern, keys, conditions, block = nil, values = []) + route = @request.path_info + route = '/' if route.empty? and not settings.empty_path_info? + return unless match = pattern.match(route) + values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v } + + if values.any? + original, @params = params, params.merge('splat' => [], 'captures' => values) + keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v } + end + + catch(:pass) do + conditions.each { |c| throw :pass if c.bind(self).call == false } + block ? block[self, values] : yield(self, values) + end + ensure + @params = original if original + end + + # No matching route was found or all routes passed. The default + # implementation is to forward the request downstream when running + # as middleware (@app is non-nil); when no downstream app is set, raise + # a NotFound exception. Subclasses can override this method to perform + # custom route miss logic. + def route_missing + if @app + forward + else + raise NotFound + end + end + + # Attempt to serve static files from public directory. Throws :halt when + # a matching file is found, returns nil otherwise. + def static!(options = {}) + return if (public_dir = settings.public_folder).nil? + path = File.expand_path("#{public_dir}#{unescape(request.path_info)}" ) + return unless File.file?(path) + + env['sinatra.static_file'] = path + cache_control(*settings.static_cache_control) if settings.static_cache_control? + send_file path, options.merge(:disposition => nil) + end + + # Enable string or symbol key access to the nested params hash. + def indifferent_params(object) + case object + when Hash + new_hash = indifferent_hash + object.each { |key, value| new_hash[key] = indifferent_params(value) } + new_hash + when Array + object.map { |item| indifferent_params(item) } + else + object + end + end + + # Creates a Hash with indifferent access. + def indifferent_hash + Hash.new {|hash,key| hash[key.to_s] if Symbol === key } + end + + # Run the block with 'throw :halt' support and apply result to the response. + def invoke + res = catch(:halt) { yield } + res = [res] if Fixnum === res or String === res + if Array === res and Fixnum === res.first + res = res.dup + status(res.shift) + body(res.pop) + headers(*res) + elsif res.respond_to? :each + body res + end + nil # avoid double setting the same response tuple twice + end + + # Dispatch a request with error handling. + def dispatch! + invoke do + static! if settings.static? && (request.get? || request.head?) + filter! :before + route! + end + rescue ::Exception => boom + invoke { handle_exception!(boom) } + ensure + begin + filter! :after unless env['sinatra.static_file'] + rescue ::Exception => boom + invoke { handle_exception!(boom) } unless @env['sinatra.error'] + end + end + + # Error handling during requests. + def handle_exception!(boom) + @env['sinatra.error'] = boom + + if boom.respond_to? :http_status + status(boom.http_status) + elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 + status(boom.code) + else + status(500) + end + + status(500) unless status.between? 400, 599 + + if server_error? + dump_errors! boom if settings.dump_errors? + raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler + end + + if not_found? + headers['X-Cascade'] = 'pass' if settings.x_cascade? + body '

Not Found

' + end + + res = error_block!(boom.class, boom) || error_block!(status, boom) + return res if res or not server_error? + raise boom if settings.raise_errors? or settings.show_exceptions? + error_block! Exception, boom + end + + # Find an custom error block for the key(s) specified. + def error_block!(key, *block_params) + base = settings + while base.respond_to?(:errors) + next base = base.superclass unless args_array = base.errors[key] + args_array.reverse_each do |args| + first = args == args_array.first + args += [block_params] + resp = process_route(*args) + return resp unless resp.nil? && !first + end + end + return false unless key.respond_to? :superclass and key.superclass < Exception + error_block!(key.superclass, *block_params) + end + + def dump_errors!(boom) + msg = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") + @env['rack.errors'].puts(msg) + end + + class << self + CALLERS_TO_IGNORE = [ # :nodoc: + /\/sinatra(\/(base|main|show_exceptions))?\.rb$/, # all sinatra code + /lib\/tilt.*\.rb$/, # all tilt code + /^\(.*\)$/, # generated code + /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks + /active_support/, # active_support require hacks + /bundler(\/runtime)?\.rb/, # bundler require hacks + /= 1.9.2 + /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files + ] + + # contrary to what the comment said previously, rubinius never supported this + if defined?(RUBY_IGNORE_CALLERS) + warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" + CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) + end + + attr_reader :routes, :filters, :templates, :errors + + # Removes all routes, filters, middleware and extension hooks from the + # current class (not routes/filters/... defined by its superclass). + def reset! + @conditions = [] + @routes = {} + @filters = {:before => [], :after => []} + @errors = {} + @middleware = [] + @prototype = nil + @extensions = [] + + if superclass.respond_to?(:templates) + @templates = Hash.new { |hash,key| superclass.templates[key] } + else + @templates = {} + end + end + + # Extension modules registered on this class and all superclasses. + def extensions + if superclass.respond_to?(:extensions) + (@extensions + superclass.extensions).uniq + else + @extensions + end + end + + # Middleware used in this class and all superclasses. + def middleware + if superclass.respond_to?(:middleware) + superclass.middleware + @middleware + else + @middleware + end + end + + # Sets an option to the given value. If the value is a proc, + # the proc will be called every time the option is accessed. + def set(option, value = (not_set = true), ignore_setter = false, &block) + raise ArgumentError if block and !not_set + value, not_set = block, false if block + + if not_set + raise ArgumentError unless option.respond_to?(:each) + option.each { |k,v| set(k, v) } + return self + end + + if respond_to?("#{option}=") and not ignore_setter + return __send__("#{option}=", value) + end + + setter = proc { |val| set option, val, true } + getter = proc { value } + + case value + when Proc + getter = value + when Symbol, Fixnum, FalseClass, TrueClass, NilClass + getter = value.inspect + when Hash + setter = proc do |val| + val = value.merge val if Hash === val + set option, val, true + end + end + + define_singleton("#{option}=", setter) if setter + define_singleton(option, getter) if getter + define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" + self + end + + # Same as calling `set :option, true` for each of the given options. + def enable(*opts) + opts.each { |key| set(key, true) } + end + + # Same as calling `set :option, false` for each of the given options. + def disable(*opts) + opts.each { |key| set(key, false) } + end + + # Define a custom error handler. Optionally takes either an Exception + # class, or an HTTP status code to specify which errors should be + # handled. + def error(*codes, &block) + args = compile! "ERROR", //, block + codes = codes.map { |c| Array(c) }.flatten + codes << Exception if codes.empty? + codes.each { |c| (@errors[c] ||= []) << args } + end + + # Sugar for `error(404) { ... }` + def not_found(&block) + error(404, &block) + error(Sinatra::NotFound, &block) + end + + # Define a named template. The block must return the template source. + def template(name, &block) + filename, line = caller_locations.first + templates[name] = [block, filename, line.to_i] + end + + # Define the layout template. The block must return the template source. + def layout(name = :layout, &block) + template name, &block + end + + # Load embedded templates from the file; uses the caller's __FILE__ + # when no file is specified. + def inline_templates=(file = nil) + file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file + + begin + io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) + app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) + rescue Errno::ENOENT + app, data = nil + end + + if data + if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m + encoding = $2 + else + encoding = settings.default_encoding + end + lines = app.count("\n") + 1 + template = nil + force_encoding data, encoding + data.each_line do |line| + lines += 1 + if line =~ /^@@\s*(.*\S)\s*$/ + template = force_encoding('', encoding) + templates[$1.to_sym] = [template, file, lines] + elsif template + template << line + end + end + end + end + + # Lookup or register a mime type in Rack's mime registry. + def mime_type(type, value = nil) + return type if type.nil? + return type.to_s if type.to_s.include?('/') + type = ".#{type}" unless type.to_s[0] == ?. + return Rack::Mime.mime_type(type, nil) unless value + Rack::Mime::MIME_TYPES[type] = value + end + + # provides all mime types matching type, including deprecated types: + # mime_types :html # => ['text/html'] + # mime_types :js # => ['application/javascript', 'text/javascript'] + def mime_types(type) + type = mime_type type + type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] + end + + # Define a before filter; runs before all requests within the same + # context as route handlers and may access/modify the request and + # response. + def before(path = nil, options = {}, &block) + add_filter(:before, path, options, &block) + end + + # Define an after filter; runs after all requests within the same + # context as route handlers and may access/modify the request and + # response. + def after(path = nil, options = {}, &block) + add_filter(:after, path, options, &block) + end + + # add a filter + def add_filter(type, path = nil, options = {}, &block) + path, options = //, path if path.respond_to?(:each_pair) + filters[type] << compile!(type, path || //, block, options) + end + + # Add a route condition. The route is considered non-matching when the + # block returns false. + def condition(name = "#{caller.first[/`.*'/]} condition", &block) + @conditions << generate_method(name, &block) + end + + def public=(value) + warn ":public is no longer used to avoid overloading Module#public, use :public_folder or :public_dir instead" + set(:public_folder, value) + end + + def public_dir=(value) + self.public_folder = value + end + + def public_dir + public_folder + end + + # Defining a `GET` handler also automatically defines + # a `HEAD` handler. + def get(path, opts = {}, &block) + conditions = @conditions.dup + route('GET', path, opts, &block) + + @conditions = conditions + route('HEAD', path, opts, &block) + end + + def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end + def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end + def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end + def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end + def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end + def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end + def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end + def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end + + # Makes the methods defined in the block and in the Modules given + # in `extensions` available to the handlers and templates + def helpers(*extensions, &block) + class_eval(&block) if block_given? + include(*extensions) if extensions.any? + end + + # Register an extension. Alternatively take a block from which an + # extension will be created and registered on the fly. + def register(*extensions, &block) + extensions << Module.new(&block) if block_given? + @extensions += extensions + extensions.each do |extension| + extend extension + extension.registered(self) if extension.respond_to?(:registered) + end + end + + def development?; environment == :development end + def production?; environment == :production end + def test?; environment == :test end + + # Set configuration options for Sinatra and/or the app. + # Allows scoping of settings for certain environments. + def configure(*envs) + yield self if envs.empty? || envs.include?(environment.to_sym) + end + + # Use the specified Rack middleware + def use(middleware, *args, &block) + @prototype = nil + @middleware << [middleware, args, block] + end + + # Stop the self-hosted server if running. + def quit! + return unless running? + # Use Thin's hard #stop! if available, otherwise just #stop. + running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop + $stderr.puts "== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i + set :running_server, nil + set :handler_name, nil + end + + alias_method :stop!, :quit! + + # Run the Sinatra app as a self-hosted server using + # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call + # with the constructed handler once we have taken the stage. + def run!(options = {}, &block) + return if running? + set options + handler = detect_rack_handler + handler_name = handler.name.gsub(/.*::/, '') + server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} + server_settings.merge!(:Port => port, :Host => bind) + + begin + start_server(handler, server_settings, handler_name, &block) + rescue Errno::EADDRINUSE + $stderr.puts "== Someone is already performing on port #{port}!" + raise + ensure + quit! + end + end + + alias_method :start!, :run! + + # Check whether the self-hosted server is running or not. + def running? + running_server? + end + + # The prototype instance used to process requests. + def prototype + @prototype ||= new + end + + # Create a new instance without middleware in front of it. + alias new! new unless method_defined? :new! + + # Create a new instance of the class fronted by its middleware + # pipeline. The object is guaranteed to respond to #call but may not be + # an instance of the class new was called on. + def new(*args, &bk) + instance = new!(*args, &bk) + Wrapper.new(build(instance).to_app, instance) + end + + # Creates a Rack::Builder instance with all the middleware set up and + # the given +app+ as end point. + def build(app) + builder = Rack::Builder.new + setup_default_middleware builder + setup_middleware builder + builder.run app + builder + end + + def call(env) + synchronize { prototype.call(env) } + end + + # Like Kernel#caller but excluding certain magic entries and without + # line / method information; the resulting array contains filenames only. + def caller_files + cleaned_caller(1).flatten + end + + # Like caller_files, but containing Arrays rather than strings with the + # first element being the file, and the second being the line. + def caller_locations + cleaned_caller 2 + end + + private + + # Starts the server by running the Rack Handler. + def start_server(handler, server_settings, handler_name) + handler.run(self, server_settings) do |server| + unless handler_name =~ /cgi/i + $stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}" + end + + setup_traps + set :running_server, server + set :handler_name, handler_name + server.threaded = settings.threaded if server.respond_to? :threaded= + + yield server if block_given? + end + end + + def setup_traps + if traps? + at_exit { quit! } + + [:INT, :TERM].each do |signal| + old_handler = trap(signal) do + quit! + old_handler.call if old_handler.respond_to?(:call) + end + end + + set :traps, false + end + end + + # Dynamically defines a method on settings. + def define_singleton(name, content = Proc.new) + # replace with call to singleton_class once we're 1.9 only + (class << self; self; end).class_eval do + undef_method(name) if method_defined? name + String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) + end + end + + # Condition for matching host name. Parameter might be String or Regexp. + def host_name(pattern) + condition { pattern === request.host } + end + + # Condition for matching user agent. Parameter should be Regexp. + # Will set params[:agent]. + def user_agent(pattern) + condition do + if request.user_agent.to_s =~ pattern + @params[:agent] = $~[1..-1] + true + else + false + end + end + end + alias_method :agent, :user_agent + + # Condition for matching mimetypes. Accepts file extensions. + def provides(*types) + types.map! { |t| mime_types(t) } + types.flatten! + condition do + if type = response['Content-Type'] + types.include? type or types.include? type[/^[^;]+/] + elsif type = request.preferred_type(types) + params = (type.respond_to?(:params) ? type.params : {}) + content_type(type, params) + true + else + false + end + end + end + + def route(verb, path, options = {}, &block) + # Because of self.options.host + host_name(options.delete(:host)) if options.key?(:host) + enable :empty_path_info if path == "" and empty_path_info.nil? + signature = compile!(verb, path, block, options) + (@routes[verb] ||= []) << signature + invoke_hook(:route_added, verb, path, block) + signature + end + + def invoke_hook(name, *args) + extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } + end + + def generate_method(method_name, &block) + method_name = method_name.to_sym + define_method(method_name, &block) + method = instance_method method_name + remove_method method_name + method + end + + def compile!(verb, path, block, options = {}) + options.each_pair { |option, args| send(option, *args) } + method_name = "#{verb} #{path}" + unbound_method = generate_method(method_name, &block) + pattern, keys = compile path + conditions, @conditions = @conditions, [] + + wrapper = block.arity != 0 ? + proc { |a,p| unbound_method.bind(a).call(*p) } : + proc { |a,p| unbound_method.bind(a).call } + wrapper.instance_variable_set(:@route_name, method_name) + + [ pattern, keys, conditions, wrapper ] + end + + def compile(path) + if path.respond_to? :to_str + keys = [] + + # We append a / at the end if there was one. + # Reason: Splitting does not split off an empty + # string at the end if the split separator + # is at the end. + # + postfix = '/' if path =~ /\/\z/ + + # Split the path into pieces in between forward slashes. + # + segments = path.split('/').map! do |segment| + ignore = [] + + # Special character handling. + # + pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c| + ignore << escaped(c).join if c.match(/[\.@]/) + patt = encoded(c) + patt.gsub(/%[\da-fA-F]{2}/) do |match| + match.split(//).map! {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join + end + end + + ignore = ignore.uniq.join + + # Key handling. + # + pattern.gsub(/((:\w+)|\*)/) do |match| + if match == "*" + keys << 'splat' + "(.*?)" + else + keys << $2[1..-1] + ignore_pattern = safe_ignore(ignore) + + ignore_pattern + end + end + end + + # Special case handling. + # + if segment = segments.pop + if segment.match(/\[\^\\\./) + parts = segment.rpartition(/\[\^\\\./) + parts[1] = '[^' + segments << parts.join + else + segments << segment + end + end + [/\A#{segments.join('/')}#{postfix}\z/, keys] + elsif path.respond_to?(:keys) && path.respond_to?(:match) + [path, path.keys] + elsif path.respond_to?(:names) && path.respond_to?(:match) + [path, path.names] + elsif path.respond_to? :match + [path, []] + else + raise TypeError, path + end + end + + def encoded(char) + enc = URI_INSTANCE.escape(char) + enc = "(?:#{escaped(char, enc).join('|')})" if enc == char + enc = "(?:#{enc}|#{encoded('+')})" if char == " " + enc + end + + def escaped(char, enc = URI_INSTANCE.escape(char)) + [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)] + end + + def safe_ignore(ignore) + unsafe_ignore = [] + ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex| + unsafe_ignore << hex[1..2] + '' + end + unsafe_patterns = unsafe_ignore.map! do |unsafe| + chars = unsafe.split(//).map! do |char| + if char =~ /[A-Z]/ + char <<= char.tr('A-Z', 'a-z') + end + char + end + + "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])" + end + if unsafe_patterns.length > 0 + "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)" + else + "([^#{ignore}/?#]+)" + end + end + + def setup_default_middleware(builder) + builder.use ExtendedRack + builder.use ShowExceptions if show_exceptions? + builder.use Rack::MethodOverride if method_override? + builder.use Rack::Head + setup_logging builder + setup_sessions builder + setup_protection builder + end + + def setup_middleware(builder) + middleware.each { |c,a,b| builder.use(c, *a, &b) } + end + + def setup_logging(builder) + if logging? + setup_common_logger(builder) + setup_custom_logger(builder) + elsif logging == false + setup_null_logger(builder) + end + end + + def setup_null_logger(builder) + builder.use Rack::NullLogger + end + + def setup_common_logger(builder) + builder.use Sinatra::CommonLogger + end + + def setup_custom_logger(builder) + if logging.respond_to? :to_int + builder.use Rack::Logger, logging + else + builder.use Rack::Logger + end + end + + def setup_protection(builder) + return unless protection? + options = Hash === protection ? protection.dup : {} + protect_session = options.fetch(:session) { sessions? } + options[:except] = Array options[:except] + options[:except] += [:session_hijacking, :remote_token] unless protect_session + options[:reaction] ||= :drop_session + builder.use Rack::Protection, options + end + + def setup_sessions(builder) + return unless sessions? + options = {} + options[:secret] = session_secret if session_secret? + options.merge! sessions.to_hash if sessions.respond_to? :to_hash + builder.use Rack::Session::Cookie, options + end + + def detect_rack_handler + servers = Array(server) + servers.each do |server_name| + begin + return Rack::Handler.get(server_name.to_s) + rescue LoadError, NameError + end + end + fail "Server handler (#{servers.join(',')}) not found." + end + + def inherited(subclass) + subclass.reset! + subclass.set :app_file, caller_files.first unless subclass.app_file? + super + end + + @@mutex = Mutex.new + def synchronize(&block) + if lock? + @@mutex.synchronize(&block) + else + yield + end + end + + # used for deprecation warnings + def warn(message) + super message + "\n\tfrom #{cleaned_caller.first.join(':')}" + end + + # Like Kernel#caller but excluding certain magic entries + def cleaned_caller(keep = 3) + caller(1). + map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. + reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } + end + end + + # Fixes encoding issues by + # * defaulting to UTF-8 + # * casting params to Encoding.default_external + # + # The latter might not be necessary if Rack handles it one day. + # Keep an eye on Rack's LH #100. + def force_encoding(*args) settings.force_encoding(*args) end + if defined? Encoding + def self.force_encoding(data, encoding = default_encoding) + return if data == settings || data.is_a?(Tempfile) + if data.respond_to? :force_encoding + data.force_encoding(encoding).encode! + elsif data.respond_to? :each_value + data.each_value { |v| force_encoding(v, encoding) } + elsif data.respond_to? :each + data.each { |v| force_encoding(v, encoding) } + end + data + end + else + def self.force_encoding(data, *) data end + end + + reset! + + set :environment, (ENV['RACK_ENV'] || :development).to_sym + set :raise_errors, Proc.new { test? } + set :dump_errors, Proc.new { !test? } + set :show_exceptions, Proc.new { development? } + set :sessions, false + set :logging, false + set :protection, true + set :method_override, false + set :use_code, false + set :default_encoding, "utf-8" + set :x_cascade, true + set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } + settings.add_charset << /^text\// + + # explicitly generating a session secret eagerly to play nice with preforking + begin + require 'securerandom' + set :session_secret, SecureRandom.hex(64) + rescue LoadError, NotImplementedError + # SecureRandom raises a NotImplementedError if no random device is available + set :session_secret, "%064x" % Kernel.rand(2**256-1) + end + + class << self + alias_method :methodoverride?, :method_override? + alias_method :methodoverride=, :method_override= + end + + set :run, false # start server via at-exit hook? + set :running_server, nil + set :handler_name, nil + set :traps, true + set :server, %w[HTTP webrick] + set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } + set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) + + ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE + + if ruby_engine == 'macruby' + server.unshift 'control_tower' + else + server.unshift 'reel' + server.unshift 'mongrel' if ruby_engine.nil? + server.unshift 'puma' if ruby_engine != 'rbx' + server.unshift 'thin' if ruby_engine != 'jruby' + server.unshift 'puma' if ruby_engine == 'rbx' + server.unshift 'trinidad' if ruby_engine == 'jruby' + end + + set :absolute_redirects, true + set :prefixed_redirects, false + set :empty_path_info, nil + + 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 :reload_templates, Proc.new { development? } + set :lock, false + set :threaded, true + + set :public_folder, Proc.new { root && File.join(root, 'public') } + set :static, Proc.new { public_folder && File.exist?(public_folder) } + set :static_cache_control, false + + error ::Exception do + response.status = 500 + content_type 'text/html' + '

Internal Server Error

' + end + + configure :development do + get '/__sinatra__/:image.png' do + filename = File.dirname(__FILE__) + "/images/#{params[:image].to_i}.png" + content_type :png + send_file filename + end + + error NotFound do + content_type 'text/html' + + if self.class == Sinatra::Application + code = <<-RUBY.gsub(/^ {12}/, '') + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + RUBY + else + code = <<-RUBY.gsub(/^ {12}/, '') + class #{self.class} + #{request.request_method.downcase} '#{request.path_info}' do + "Hello World" + end + end + RUBY + + file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') + code = "# in #{file}\n#{code}" unless file.empty? + end + + (<<-HTML).gsub(/^ {10}/, '') + + + + + + +

Sinatra doesn’t know this ditty.

+ +
+ Try this: +
#{Rack::Utils.escape_html(code)}
+
+ + + HTML + end + end + end + + # 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 highly recommended for + # modular applications. + class Application < Base + set :logging, Proc.new { ! test? } + set :method_override, true + set :run, Proc.new { ! test? } + set :session_secret, Proc.new { super() unless development? } + set :app_file, nil + + def self.register(*extensions, &block) #:nodoc: + added_methods = extensions.map {|m| m.public_instance_methods }.flatten + Delegator.delegate(*added_methods) + super(*extensions, &block) + end + 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. + module Delegator #:nodoc: + def self.delegate(*methods) + methods.each do |method_name| + define_method(method_name) do |*args, &block| + return super(*args, &block) if respond_to? method_name + Delegator.target.send(method_name, *args, &block) + end + private method_name + end + end + + delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, + :template, :layout, :before, :after, :error, :not_found, :configure, + :set, :mime_type, :enable, :disable, :use, :development?, :test?, + :production?, :helpers, :settings, :register + + class << self + attr_accessor :target + end + + self.target = Application + end + + class Wrapper + def initialize(stack, instance) + @stack, @instance = stack, instance + end + + def settings + @instance.settings + end + + def helpers + @instance + end + + def call(env) + @stack.call(env) + end + + def inspect + "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" + end + end + + # Create a new Sinatra application; the block is evaluated in the class scope. + def self.new(base = Base, &block) + base = Class.new(base) + base.class_eval(&block) if block_given? + base + end + + # Extend the top-level DSL with the modules provided. + def self.register(*extensions, &block) + Delegator.target.register(*extensions, &block) + end + + # Include the helper modules provided in Sinatra's request context. + def self.helpers(*extensions, &block) + Delegator.target.helpers(*extensions, &block) + end + + # Use the middleware for classic applications. + def self.use(*args, &block) + Delegator.target.use(*args, &block) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/404.png b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/404.png new file mode 100644 index 000000000..f16a914ff Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/404.png differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/500.png b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/500.png new file mode 100644 index 000000000..e08b17d9e Binary files /dev/null and b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/images/500.png differ diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/main.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/main.rb new file mode 100644 index 000000000..76a55261a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/main.rb @@ -0,0 +1,34 @@ +require 'sinatra/base' + +module Sinatra + class Application < Base + + # we assume that the first file that requires 'sinatra' is the + # app_file. all other path related options are calculated based + # on this path by default. + set :app_file, caller_files.first || $0 + + set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } + + if run? && ARGV.any? + require 'optparse' + OptionParser.new { |op| + op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) } + op.on('-o addr', "set the host (default is #{bind})") { |val| set :bind, val } + op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym } + op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val } + op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true } + }.parse!(ARGV.dup) + end + end + + at_exit { Application.run! if $!.nil? && Application.run? } +end + +# include would include the module in Object +# extend only extends the `main` object +extend Sinatra::Delegator + +class Rack::Builder + include Sinatra::Delegator +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/show_exceptions.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/show_exceptions.rb new file mode 100644 index 000000000..d7ff54ea0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/show_exceptions.rb @@ -0,0 +1,351 @@ +require 'rack/showexceptions' + +module Sinatra + # Sinatra::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 < Rack::ShowExceptions + @@eats_errors = Object.new + def @@eats_errors.flush(*) end + def @@eats_errors.puts(*) end + + def initialize(app) + @app = app + @template = ERB.new(TEMPLATE) + end + + def call(env) + @app.call(env) + rescue Exception => e + errors, env["rack.errors"] = env["rack.errors"], @@eats_errors + + if prefers_plain_text?(env) + content_type = "text/plain" + exception_string = dump_exception(e) + else + content_type = "text/html" + exception_string = pretty(env, e) + end + + env["rack.errors"] = errors + + # Post 893a2c50 in rack/rack, the #pretty method above, implemented in + # Rack::ShowExceptions, returns a String instead of an array. + body = Array(exception_string) + + [ + 500, + {"Content-Type" => content_type, + "Content-Length" => Rack::Utils.bytesize(body.join).to_s}, + body + ] + end + + private + + def prefers_plain_text?(env) + !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && + [/curl/].index{|item| item =~ env["HTTP_USER_AGENT"]} + end + + def frame_class(frame) + if frame.filename =~ /lib\/sinatra.*\.rb/ + "framework" + elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || + frame.filename =~ /\/bin\/(\w+)$/ + "system" + else + "app" + end + end + +TEMPLATE = <<-HTML # :nodoc: + + + + + <%=h exception.class %> at <%=h path %> + + + + + + +
+ + +
+

BACKTRACE

+

(expand)

+ +
+ +
    + + <% id = 1 %> + <% frames.each do |frame| %> + <% if frame.context_line && frame.context_line != "#" %> + +
  • + <%=h frame.filename %> in + <%=h frame.function %> +
  • + +
  • + <% if frame.pre_context %> +
      + <% frame.pre_context.each do |line| %> +
    1. <%=h line %>
    2. + <% end %> +
    + <% end %> + +
      +
    1. <%= + h frame.context_line %>
    2. +
    + + <% if frame.post_context %> +
      + <% frame.post_context.each do |line| %> +
    1. <%=h line %>
    2. + <% end %> +
    + <% end %> +
    +
  • + + <% end %> + + <% id += 1 %> + <% end %> + +
+
+ +
+

GET

+ <% if req.GET and not 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

+ <% if req.POST and not 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 have +enabled the show_exceptions setting.

+
+ + +HTML + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/version.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/version.rb new file mode 100644 index 000000000..0554f2000 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/lib/sinatra/version.rb @@ -0,0 +1,3 @@ +module Sinatra + VERSION = '1.4.6' +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/sinatra.gemspec b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/sinatra.gemspec new file mode 100644 index 000000000..26d2e25f0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/sinatra.gemspec @@ -0,0 +1,19 @@ +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) +require 'sinatra/version' + +Gem::Specification.new 'sinatra', Sinatra::VERSION do |s| + s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." + s.summary = "Classy web-development dressed in a DSL" + s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] + s.email = "sinatrarb@googlegroups.com" + s.homepage = "http://www.sinatrarb.com/" + s.license = 'MIT' + s.files = `git ls-files`.split("\n") - %w[.gitignore .travis.yml] + s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } + s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' + s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc --encoding=UTF-8] + + s.add_dependency 'rack', '~> 1.4' + s.add_dependency 'tilt', '>= 1.3', '< 3' + s.add_dependency 'rack-protection', '~> 1.4' +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/asciidoctor_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/asciidoctor_test.rb new file mode 100644 index 000000000..6d64b047d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/asciidoctor_test.rb @@ -0,0 +1,72 @@ +require File.expand_path('../helper', __FILE__) + +begin + require 'asciidoctor' + + class AsciidoctorTest < Minitest::Test + def asciidoc_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline AsciiDoc strings' do + asciidoc_app { asciidoc '== Hiya' } + assert ok? + assert_match %r{Hiya}, body + end + + it 'uses the correct engine' do + engine = Tilt::AsciidoctorTemplate + assert_equal engine, Tilt[:ad] + assert_equal engine, Tilt[:adoc] + assert_equal engine, Tilt[:asciidoc] + end + + it 'renders .asciidoc files in views path' do + asciidoc_app { asciidoc :hello } + assert ok? + assert_match %r{Hello from AsciiDoc}, body + end + + it 'raises error if template not found' do + mock_app { get('/') { asciidoc :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it 'renders with inline layouts' do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { asciidoc 'Sparta', :layout_engine => :str } + end + get '/' + assert ok? + assert_include body, 'THIS. IS.' + assert_include body, '

SPARTA

' + end + + it 'renders with file layouts' do + asciidoc_app do + asciidoc 'Hello World', :layout => :layout2, :layout_engine => :erb + end + assert ok? + assert_include body, 'ERB Layout!' + assert_include body, '

Hello World

' + end + + it 'can be used in a nested fashion for partials and whatnot' do + mock_app do + template(:inner) { 'hi' } + template(:outer) { '<%= asciidoc :inner %>' } + get('/') { erb :outer } + end + get '/' + assert ok? + assert_match %r{.*hi

.*
}m, body + end + end +rescue LoadError + warn "#{$!.to_s}: skipping asciidoc tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/base_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/base_test.rb new file mode 100644 index 000000000..a6730373e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/base_test.rb @@ -0,0 +1,167 @@ +require File.expand_path('../helper', __FILE__) + +class BaseTest < Minitest::Test + describe 'Sinatra::Base subclasses' do + class TestApp < Sinatra::Base + get('/') { 'Hello World' } + end + + it 'include Rack::Utils' do + assert TestApp.included_modules.include?(Rack::Utils) + end + + it 'processes requests with #call' do + assert TestApp.respond_to?(:call) + + request = Rack::MockRequest.new(TestApp) + response = request.get('/') + assert response.ok? + assert_equal 'Hello World', response.body + end + + class TestApp < Sinatra::Base + get '/state' do + @foo ||= "new" + body = "Foo: #{@foo}" + @foo = 'discard' + body + end + end + + it 'does not maintain state between requests' do + request = Rack::MockRequest.new(TestApp) + 2.times do + response = request.get('/state') + assert response.ok? + assert_equal 'Foo: new', response.body + end + end + + it "passes the subclass to configure blocks" do + ref = nil + TestApp.configure { |app| ref = app } + assert_equal TestApp, ref + end + + it "allows the configure block arg to be omitted and does not change context" do + context = nil + TestApp.configure { context = self } + assert_equal self, context + end + end + + describe "Sinatra::Base#new" do + it 'returns a wrapper' do + assert_equal Sinatra::Wrapper, Sinatra::Base.new.class + end + + it 'implements a nice inspect' do + assert_equal '#', Sinatra::Base.new.inspect + end + + it 'exposes settings' do + assert_equal Sinatra::Base.settings, Sinatra::Base.new.settings + end + + it 'exposes helpers' do + assert_equal 'image/jpeg', Sinatra::Base.new.helpers.mime_type(:jpg) + end + end + + describe "Sinatra::Base as Rack middleware" do + app = lambda { |env| + headers = {'X-Downstream' => 'true'} + headers['X-Route-Missing'] = env['sinatra.route-missing'] || '' + [210, headers, ['Hello from downstream']] } + + class TestMiddleware < Sinatra::Base + end + + it 'creates a middleware that responds to #call with .new' do + middleware = TestMiddleware.new(app) + assert middleware.respond_to?(:call) + end + + it 'exposes the downstream app' do + middleware = TestMiddleware.new!(app) + assert_same app, middleware.app + end + + class TestMiddleware < Sinatra::Base + def route_missing + env['sinatra.route-missing'] = '1' + super + end + + get('/') { 'Hello from middleware' } + end + + middleware = TestMiddleware.new(app) + request = Rack::MockRequest.new(middleware) + + it 'intercepts requests' do + response = request.get('/') + assert response.ok? + assert_equal 'Hello from middleware', response.body + end + + it 'automatically forwards requests downstream when no matching route found' do + response = request.get('/missing') + assert_equal 210, response.status + assert_equal 'Hello from downstream', response.body + end + + it 'calls #route_missing before forwarding downstream' do + response = request.get('/missing') + assert_equal '1', response['X-Route-Missing'] + end + + class TestMiddleware < Sinatra::Base + get('/low-level-forward') { app.call(env) } + end + + it 'can call the downstream app directly and return result' do + response = request.get('/low-level-forward') + assert_equal 210, response.status + assert_equal 'true', response['X-Downstream'] + assert_equal 'Hello from downstream', response.body + end + + class TestMiddleware < Sinatra::Base + get '/explicit-forward' do + response['X-Middleware'] = 'true' + res = forward + assert_nil res + assert_equal 210, response.status + assert_equal 'true', response['X-Downstream'] + assert_equal ['Hello from downstream'], response.body + 'Hello after explicit forward' + end + end + + it 'forwards the request downstream and integrates the response into the current context' do + response = request.get('/explicit-forward') + assert_equal 210, response.status + assert_equal 'true', response['X-Downstream'] + assert_equal 'Hello after explicit forward', response.body + assert_equal '28', response['Content-Length'] + end + + app_content_length = lambda {|env| + [200, {'Content-Length' => '16'}, 'From downstream!']} + + class TestMiddlewareContentLength < Sinatra::Base + get '/forward' do + 'From after explicit forward!' + end + end + + middleware_content_length = TestMiddlewareContentLength.new(app_content_length) + request_content_length = Rack::MockRequest.new(middleware_content_length) + + it "sets content length for last response" do + response = request_content_length.get('/forward') + assert_equal '28', response['Content-Length'] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/builder_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/builder_test.rb new file mode 100644 index 000000000..29c6d29c0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/builder_test.rb @@ -0,0 +1,91 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'builder' + +class BuilderTest < Minitest::Test + def builder_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set options + get('/', &block) + end + get '/' + end + + it 'renders inline Builder strings' do + builder_app { builder 'xml.instruct!' } + assert ok? + assert_equal %{\n}, body + end + + it 'defaults content type to xml' do + builder_app { builder 'xml.instruct!' } + assert ok? + assert_equal "application/xml;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + builder_app do + content_type :html + builder 'xml.instruct!' + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + builder_app(:builder => { :content_type => 'html' }) do + builder 'xml.instruct!' + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders inline blocks' do + builder_app do + @name = "Frank & Mary" + builder { |xml| xml.couple @name } + end + assert ok? + assert_equal "Frank & Mary\n", body + end + + it 'renders .builder files in views path' do + builder_app do + @name = "Blue" + builder :hello + end + assert ok? + assert_equal %(You're my boy, Blue!\n), body + end + + it "renders with inline layouts" do + mock_app do + layout { %(xml.layout { xml << yield }) } + get('/') { builder %(xml.em 'Hello World') } + end + get '/' + assert ok? + assert_equal "\nHello World\n\n", body + end + + it "renders with file layouts" do + builder_app do + builder %(xml.em 'Hello World'), :layout => :layout2 + end + assert ok? + assert_equal "\nHello World\n\n", body + end + + it "raises error if template not found" do + mock_app do + get('/') { builder :no_such_template } + end + assert_raises(Errno::ENOENT) { get('/') } + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping builder tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/coffee_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/coffee_test.rb new file mode 100644 index 000000000..8a5bee1d5 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/coffee_test.rb @@ -0,0 +1,96 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'coffee-script' +require 'execjs' + +begin + ExecJS.compile '1' +rescue Exception + raise LoadError, 'unable to execute JavaScript' +end + +class CoffeeTest < Minitest::Test + def coffee_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set(options) + get('/', &block) + end + get '/' + end + + it 'renders inline Coffee strings' do + coffee_app { coffee "alert 'Aye!'\n" } + assert ok? + assert body.include?("alert('Aye!');") + end + + it 'defaults content type to javascript' do + coffee_app { coffee "alert 'Aye!'\n" } + assert ok? + assert_equal "application/javascript;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + coffee_app do + content_type :html + coffee "alert 'Aye!'\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + coffee_app(:coffee => { :content_type => 'html' }) do + coffee "alert 'Aye!'\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders .coffee files in views path' do + coffee_app { coffee :hello } + assert ok? + assert_include body, "alert(\"Aye!\");" + end + + it 'ignores the layout option' do + coffee_app { coffee :hello, :layout => :layout2 } + assert ok? + assert_include body, "alert(\"Aye!\");" + end + + it "raises error if template not found" do + mock_app { + get('/') { coffee :no_such_template } + } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes coffee options to the coffee engine" do + coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true } + assert ok? + assert_body "alert('Aye!');" + end + + it "passes default coffee options to the coffee engine" do + mock_app do + set :coffee, :no_wrap => true # default coffee style is :nested + get('/') { coffee "alert 'Aye!'\n" } + end + get '/' + assert ok? + assert_body "alert('Aye!');" + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping coffee tests" +rescue + if $!.class.name == 'ExecJS::RuntimeUnavailable' + warn "#{$!.to_s}: skipping coffee tests" + else + raise + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/compile_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/compile_test.rb new file mode 100644 index 000000000..baa3a4c3d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/compile_test.rb @@ -0,0 +1,183 @@ +# I like coding: UTF-8 +require File.expand_path('../helper', __FILE__) + +class CompileTest < Minitest::Test + + def self.converts pattern, expected_regexp + it "generates #{expected_regexp.source} from #{pattern}" do + compiled, _ = compiled pattern + assert_equal expected_regexp, compiled, "Pattern #{pattern} is not compiled into #{expected_regexp.source}. Was #{compiled.source}." + end + end + def self.parses pattern, example, expected_params + it "parses #{example} with #{pattern} into params #{expected_params}" do + compiled, keys = compiled pattern + match = compiled.match(example) + fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match + + # Aggregate e.g. multiple splat values into one array. + # + params = keys.zip(match.captures).reduce({}) do |hash, mapping| + key, value = mapping + hash[key] = if existing = hash[key] + existing.respond_to?(:to_ary) ? existing << value : [existing, value] + else + value + end + hash + end + + assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}." + end + end + def self.fails pattern, example + it "does not parse #{example} with #{pattern}" do + compiled, _ = compiled pattern + match = compiled.match(example) + fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match + end + end + def compiled pattern + app ||= mock_app {} + compiled, keys = app.send(:compile, pattern) + [compiled, keys] + end + + converts "/", %r{\A/\z} + parses "/", "/", {} + + converts "/foo", %r{\A/foo\z} + parses "/foo", "/foo", {} + + converts "/:foo", %r{\A/([^/?#]+)\z} + parses "/:foo", "/foo", "foo" => "foo" + parses "/:foo", "/foo.bar", "foo" => "foo.bar" + parses "/:foo", "/foo%2Fbar", "foo" => "foo%2Fbar" + parses "/:foo", "/%0Afoo", "foo" => "%0Afoo" + fails "/:foo", "/foo?" + fails "/:foo", "/foo/bar" + fails "/:foo", "/" + fails "/:foo", "/foo/" + + converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z} + parses "/föö", "/f%C3%B6%C3%B6", {} + + converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} + parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar" + + converts "/hello/:person", %r{\A/hello/([^/?#]+)\z} + parses "/hello/:person", "/hello/Frank", "person" => "Frank" + + converts "/?:foo?/?:bar?", %r{\A/?([^/?#]+)?/?([^/?#]+)?\z} + parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world" + parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil + parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil + parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil + + converts "/*", %r{\A/(.*?)\z} + parses "/*", "/", "splat" => "" + parses "/*", "/foo", "splat" => "foo" + parses "/*", "/foo/bar", "splat" => "foo/bar" + + converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} + parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => "bar/baz" + + converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} + parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name" + + converts "/test$/", %r{\A/test(?:\$|%24)/\z} + parses "/test$/", "/test$/", {} + + converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z} + parses "/te+st/", "/te+st/", {} + fails "/te+st/", "/test/" + fails "/te+st/", "/teeest/" + + converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z} + parses "/test(bar)/", "/test(bar)/", {} + + converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z} + parses "/path with spaces", "/path%20with%20spaces", {} + parses "/path with spaces", "/path%2Bwith%2Bspaces", {} + parses "/path with spaces", "/path+with+spaces", {} + + converts "/foo&bar", %r{\A/foo(?:&|%26)bar\z} + parses "/foo&bar", "/foo&bar", {} + + converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} + parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello%20world", "splat" => "how%20are%20you" + + converts "/*/foo/*/*", %r{\A/(.*?)/foo/(.*?)/(.*?)\z} + parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"] + fails "/*/foo/*/*", "/bar/foo/baz" + + converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z} + parses "/test.bar", "/test.bar", {} + fails "/test.bar", "/test0bar" + + converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} + parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg" + parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg" + fails "/:file.:ext", "/.jpg" + + converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)?\z} + parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil + parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar" + parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar" + parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar" + parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar" + parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar" + fails "/:name.:format", "/.bar" + fails "/:name.?:format?", "/.bar" + + converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z} + parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar" + parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar" + parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar" + + # From https://gist.github.com/2154980#gistcomment-169469. + # + # converts "/:name(.:format)?", %r{\A/([^\.%2E/?#]+)(?:\(|%28)(?:\.|%2E)([^\.%2E/?#]+)(?:\)|%29)?\z} + # parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil + # parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar" + fails "/:name(.:format)?", "/foo." + + parses "/:id/test.bar", "/3/test.bar", {"id" => "3"} + parses "/:id/test.bar", "/2/test.bar", {"id" => "2"} + parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"} + parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"} + parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"} + + parses '/10/:id', '/10/test', "id" => "test" + parses '/10/:id', '/10/te.st', "id" => "te.st" + + parses '/10.1/:id', '/10.1/test', "id" => "test" + parses '/10.1/:id', '/10.1/te.st', "id" => "te.st" + parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st" + parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st" + parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st" + fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy. + + parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil + parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c" + parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil + parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d" + fails '/:a/:b.?:c?', '/a.b/c.d/e' + + parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg" + parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg" + parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg" + parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg" + parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg" + parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg" + fails "/:file.:ext", "/pony正.%2ejpg" + + converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} + parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz" + parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz" + parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz" + + # From issue #688. + # + parses "/articles/10.1103/:doi", "/articles/10.1103/PhysRevLett.110.026401", "doi" => "PhysRevLett.110.026401" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/contest.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/contest.rb new file mode 100644 index 000000000..2b68c4148 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/contest.rb @@ -0,0 +1,91 @@ +# Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte +# +# 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 OR COPYRIGHT HOLDERS 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. +require "rubygems" +require "minitest/autorun" + +# Contest adds +teardown+, +test+ and +context+ as class methods, and the +# instance methods +setup+ and +teardown+ now iterate on the corresponding +# blocks. Note that all setup and teardown blocks must be defined with the +# block syntax. Adding setup or teardown instance methods defeats the purpose +# of this library. +class Minitest::Test + def self.setup(&block) setup_blocks << block end + def self.teardown(&block) teardown_blocks << block end + def self.setup_blocks() @setup_blocks ||= [] end + def self.teardown_blocks() @teardown_blocks ||= [] end + + def setup_blocks(base = self.class) + setup_blocks base.superclass if base.superclass.respond_to? :setup_blocks + base.setup_blocks.each do |block| + instance_eval(&block) + end + end + + def teardown_blocks(base = self.class) + teardown_blocks base.superclass if base.superclass.respond_to? :teardown_blocks + base.teardown_blocks.each do |block| + instance_eval(&block) + end + end + + alias setup setup_blocks + alias teardown teardown_blocks + + def self.context(*name, &block) + subclass = Class.new(self) + remove_tests(subclass) + subclass.class_eval(&block) if block_given? + const_set(context_name(name.join(" ")), subclass) + end + + def self.test(name, &block) + define_method(test_name(name), &block) + end + + class << self + alias_method :should, :test + alias_method :describe, :context + end + +private + + def self.context_name(name) + # "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym + name = "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}" + name.tr(" ", "_").to_sym + end + + def self.test_name(name) + name = "test_#{sanitize_name(name).gsub(/\s+/,'_')}_0" + name = name.succ while method_defined? name + name.to_sym + end + + def self.sanitize_name(name) + # name.gsub(/\W+/, ' ').strip + name.gsub(/\W+/, ' ') + end + + def self.remove_tests(subclass) + subclass.public_instance_methods.grep(/^test_/).each do |meth| + subclass.send(:undef_method, meth.to_sym) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/creole_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/creole_test.rb new file mode 100644 index 000000000..76b305f89 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/creole_test.rb @@ -0,0 +1,65 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'creole' + +class CreoleTest < Minitest::Test + def creole_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline creole strings' do + creole_app { creole '= Hiya' } + assert ok? + assert_body "

Hiya

" + end + + it 'renders .creole files in views path' do + creole_app { creole :hello } + assert ok? + assert_body "

Hello From Creole

" + end + + it "raises error if template not found" do + mock_app { get('/') { creole :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { creole 'Sparta', :layout_engine => :str } + end + get '/' + assert ok? + assert_like 'THIS. IS.

SPARTA

!', body + end + + it "renders with file layouts" do + creole_app do + creole 'Hello World', :layout => :layout2, :layout_engine => :erb + end + assert ok? + assert_body "ERB Layout!\n

Hello World

" + end + + it "can be used in a nested fashion for partials and whatnot" do + mock_app do + template(:inner) { "hi" } + template(:outer) { "<%= creole :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_like '

hi

', body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping creole tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/delegator_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/delegator_test.rb new file mode 100644 index 000000000..bd4425eb6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/delegator_test.rb @@ -0,0 +1,160 @@ +require File.expand_path('../helper', __FILE__) + +class DelegatorTest < Minitest::Test + class Mirror + attr_reader :last_call + def method_missing(*a, &b) + @last_call = [*a.map(&:to_s)] + @last_call << b if b + end + end + + def self.delegates(name) + it "delegates #{name}" do + m = mirror { send name } + assert_equal [name.to_s], m.last_call + end + + it "delegates #{name} with arguments" do + m = mirror { send name, "foo", "bar" } + assert_equal [name.to_s, "foo", "bar"], m.last_call + end + + it "delegates #{name} with block" do + block = proc { } + m = mirror { send(name, &block) } + assert_equal [name.to_s, block], m.last_call + end + end + + setup do + @target_was = Sinatra::Delegator.target + end + + def teardown + Sinatra::Delegator.target = @target_was + end + + def delegation_app(&block) + mock_app { Sinatra::Delegator.target = self } + delegate(&block) + end + + def mirror(&block) + mirror = Mirror.new + Sinatra::Delegator.target = mirror + delegate(&block) + end + + def delegate(&block) + assert Sinatra::Delegator.target != Sinatra::Application + Object.new.extend(Sinatra::Delegator).instance_eval(&block) if block + Sinatra::Delegator.target + end + + def target + Sinatra::Delegator.target + end + + it 'defaults to Sinatra::Application as target' do + assert_equal Sinatra::Application, Sinatra::Delegator.target + end + + %w[get put post delete options patch link unlink].each do |verb| + it "delegates #{verb} correctly" do + delegation_app do + send(verb, '/hello') { 'Hello World' } + end + + request = Rack::MockRequest.new(@app) + response = request.request(verb.upcase, '/hello', {}) + assert response.ok? + assert_equal 'Hello World', response.body + end + end + + it "delegates head correctly" do + delegation_app do + head '/hello' do + response['X-Hello'] = 'World!' + 'remove me' + end + end + + request = Rack::MockRequest.new(@app) + response = request.request('HEAD', '/hello', {}) + assert response.ok? + assert_equal 'World!', response['X-Hello'] + assert_equal '', response.body + end + + it "registers extensions with the delegation target" do + app, mixin = mirror, Module.new + Sinatra.register mixin + assert_equal ["register", mixin.to_s], app.last_call + end + + it "registers helpers with the delegation target" do + app, mixin = mirror, Module.new + Sinatra.helpers mixin + assert_equal ["helpers", mixin.to_s], app.last_call + end + + it "registers middleware with the delegation target" do + app, mixin = mirror, Module.new + Sinatra.use mixin + assert_equal ["use", mixin.to_s], app.last_call + end + + it "should work with method_missing proxies for options" do + mixin = Module.new do + def respond_to?(method, *) + method.to_sym == :options or super + end + + def method_missing(method, *args, &block) + return super unless method.to_sym == :options + {:some => :option} + end + end + + value = nil + mirror do + extend mixin + value = options + end + + assert_equal({:some => :option}, value) + end + + it "delegates crazy method names" do + Sinatra::Delegator.delegate "foo:bar:" + method = mirror { send "foo:bar:" }.last_call.first + assert_equal "foo:bar:", method + end + + delegates 'get' + delegates 'patch' + delegates 'put' + delegates 'post' + delegates 'delete' + delegates 'head' + delegates 'options' + delegates 'template' + delegates 'layout' + delegates 'before' + delegates 'after' + delegates 'error' + delegates 'not_found' + delegates 'configure' + delegates 'set' + delegates 'mime_type' + delegates 'enable' + delegates 'disable' + delegates 'use' + delegates 'development?' + delegates 'test?' + delegates 'production?' + delegates 'helpers' + delegates 'settings' +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/encoding_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/encoding_test.rb new file mode 100644 index 000000000..61a9f9338 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/encoding_test.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 +require File.expand_path('../helper', __FILE__) +require 'erb' + +class BaseTest < Minitest::Test + setup do + @base = Sinatra.new(Sinatra::Base) + @base.set :views, File.dirname(__FILE__) + "/views" + end + + it 'allows unicode strings in ascii templates per default (1.9)' do + next unless defined? Encoding + @base.new!.erb(File.read(@base.views + "/ascii.erb").encode("ASCII"), {}, :value => "åkej") + end + + it 'allows ascii strings in unicode templates per default (1.9)' do + next unless defined? Encoding + @base.new!.erb(:utf8, {}, :value => "Some Lyrics".encode("ASCII")) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/erb_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/erb_test.rb new file mode 100644 index 000000000..325da9c4a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/erb_test.rb @@ -0,0 +1,116 @@ +require File.expand_path('../helper', __FILE__) + +class ERBTest < Minitest::Test + def engine + Tilt::ERBTemplate + end + + def setup + Tilt.prefer engine, :erb + super + end + + def erb_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'uses the correct engine' do + assert_equal engine, Tilt[:erb] + end + + it 'renders inline ERB strings' do + erb_app { erb '<%= 1 + 1 %>' } + assert ok? + assert_equal '2', body + end + + it 'renders .erb files in views path' do + erb_app { erb :hello } + assert ok? + assert_equal "Hello World\n", body + end + + it 'takes a :locals option' do + erb_app do + locals = {:foo => 'Bar'} + erb '<%= foo %>', :locals => locals + end + assert ok? + assert_equal 'Bar', body + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. <%= yield.upcase %>!' } + get('/') { erb 'Sparta' } + end + get '/' + assert ok? + assert_equal 'THIS. IS. SPARTA!', body + end + + it "renders with file layouts" do + erb_app { erb 'Hello World', :layout => :layout2 } + assert ok? + assert_body "ERB Layout!\nHello World" + end + + it "renders erb with blocks" do + mock_app do + def container + @_out_buf << "THIS." + yield + @_out_buf << "SPARTA!" + end + def is; "IS." end + get('/') { erb '<% container do %> <%= is %> <% end %>' } + end + get '/' + assert ok? + assert_equal 'THIS. IS. SPARTA!', body + end + + it "can be used in a nested fashion for partials and whatnot" do + mock_app do + template(:inner) { "<%= 'hi' %>" } + template(:outer) { "<%= erb :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_equal 'hi', body + end + + it "can render truly nested layouts by accepting a layout and a block with the contents" do + mock_app do + template(:main_outer_layout) { "

Title

\n<%= yield %>" } + template(:an_inner_layout) { "

Subtitle

\n<%= yield %>" } + template(:a_page) { "

Contents.

\n" } + get('/') do + erb :main_outer_layout, :layout => false do + erb :an_inner_layout do + erb :a_page + end + end + end + end + get '/' + assert ok? + assert_body "

Title

\n

Subtitle

\n

Contents.

\n" + end +end + + +begin + require 'erubis' + class ErubisTest < ERBTest + def engine; Tilt::ErubisTemplate end + end +rescue LoadError + warn "#{$!.to_s}: skipping erubis tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/extensions_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/extensions_test.rb new file mode 100644 index 000000000..b0d7e8457 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/extensions_test.rb @@ -0,0 +1,98 @@ +require File.expand_path('../helper', __FILE__) + +class ExtensionsTest < Minitest::Test + module FooExtensions + def foo + end + + private + def im_hiding_in_ur_foos + end + end + + module BarExtensions + def bar + end + end + + module BazExtensions + def baz + end + end + + module QuuxExtensions + def quux + end + end + + module PainExtensions + def foo=(name); end + def bar?(name); end + def fizz!(name); end + end + + it 'will add the methods to the DSL for the class in which you register them and its subclasses' do + Sinatra::Base.register FooExtensions + assert Sinatra::Base.respond_to?(:foo) + + Sinatra::Application.register BarExtensions + assert Sinatra::Application.respond_to?(:bar) + assert Sinatra::Application.respond_to?(:foo) + assert !Sinatra::Base.respond_to?(:bar) + end + + it 'allows extending by passing a block' do + Sinatra::Base.register { def im_in_ur_anonymous_module; end } + assert Sinatra::Base.respond_to?(:im_in_ur_anonymous_module) + end + + it 'will make sure any public methods added via Application#register are delegated to Sinatra::Delegator' do + Sinatra::Application.register FooExtensions + assert Sinatra::Delegator.private_instance_methods. + map { |m| m.to_sym }.include?(:foo) + assert !Sinatra::Delegator.private_instance_methods. + map { |m| m.to_sym }.include?(:im_hiding_in_ur_foos) + end + + it 'will handle special method names' do + Sinatra::Application.register PainExtensions + assert Sinatra::Delegator.private_instance_methods. + map { |m| m.to_sym }.include?(:foo=) + assert Sinatra::Delegator.private_instance_methods. + map { |m| m.to_sym }.include?(:bar?) + assert Sinatra::Delegator.private_instance_methods. + map { |m| m.to_sym }.include?(:fizz!) + end + + it 'will not delegate methods on Base#register' do + Sinatra::Base.register QuuxExtensions + assert !Sinatra::Delegator.private_instance_methods.include?("quux") + end + + it 'will extend the Sinatra::Application application by default' do + Sinatra.register BazExtensions + assert !Sinatra::Base.respond_to?(:baz) + assert Sinatra::Application.respond_to?(:baz) + end + + module BizzleExtension + def bizzle + bizzle_option + end + + def self.registered(base) + fail "base should be BizzleApp" unless base == BizzleApp + fail "base should have already extended BizzleExtension" unless base.respond_to?(:bizzle) + base.set :bizzle_option, 'bizzle!' + end + end + + class BizzleApp < Sinatra::Base + end + + it 'sends .registered to the extension module after extending the class' do + BizzleApp.register BizzleExtension + assert_equal 'bizzle!', BizzleApp.bizzle_option + assert_equal 'bizzle!', BizzleApp.bizzle + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/filter_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/filter_test.rb new file mode 100644 index 000000000..f708015bd --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/filter_test.rb @@ -0,0 +1,487 @@ +require File.expand_path('../helper', __FILE__) + +class BeforeFilterTest < Minitest::Test + it "executes filters in the order defined" do + count = 0 + mock_app do + get('/') { 'Hello World' } + before do + assert_equal 0, count + count = 1 + end + before do + assert_equal 1, count + count = 2 + end + end + + get '/' + assert ok? + assert_equal 2, count + assert_equal 'Hello World', body + end + + it "can modify the request" do + mock_app do + get('/foo') { 'foo' } + get('/bar') { 'bar' } + before { request.path_info = '/bar' } + end + + get '/foo' + assert ok? + assert_equal 'bar', body + end + + it "can modify instance variables available to routes" do + mock_app do + before { @foo = 'bar' } + get('/foo') { @foo } + end + + get '/foo' + assert ok? + assert_equal 'bar', body + end + + it "allows redirects" do + mock_app do + before { redirect '/bar' } + get('/foo') do + fail 'before block should have halted processing' + 'ORLY?!' + end + end + + get '/foo' + assert redirect? + assert_equal 'http://example.org/bar', response['Location'] + assert_equal '', body + end + + it "does not modify the response with its return value" do + mock_app do + before { 'Hello World!' } + get('/foo') do + assert_equal [], response.body + 'cool' + end + end + + get '/foo' + assert ok? + assert_equal 'cool', body + end + + it "does modify the response with halt" do + mock_app do + before { halt 302, 'Hi' } + get '/foo' do + "should not happen" + end + end + + get '/foo' + assert_equal 302, response.status + assert_equal 'Hi', body + end + + it "gives you access to params" do + mock_app do + before { @foo = params['foo'] } + get('/foo') { @foo } + end + + get '/foo?foo=cool' + assert ok? + assert_equal 'cool', body + end + + it "properly unescapes parameters" do + mock_app do + before { @foo = params['foo'] } + get('/foo') { @foo } + end + + get '/foo?foo=bar%3Abaz%2Fbend' + assert ok? + assert_equal 'bar:baz/bend', body + end + + it "runs filters defined in superclasses" do + base = Class.new(Sinatra::Base) + base.before { @foo = 'hello from superclass' } + + mock_app(base) { get('/foo') { @foo } } + + get '/foo' + assert_equal 'hello from superclass', body + end + + it 'does not run before filter when serving static files' do + ran_filter = false + mock_app do + before { ran_filter = true } + set :static, true + set :public_folder, File.dirname(__FILE__) + end + get "/#{File.basename(__FILE__)}" + assert ok? + assert_equal File.read(__FILE__), body + assert !ran_filter + end + + it 'takes an optional route pattern' do + ran_filter = false + mock_app do + before("/b*") { ran_filter = true } + get('/foo') { } + get('/bar') { } + end + get '/foo' + assert !ran_filter + get '/bar' + assert ran_filter + end + + it 'generates block arguments from route pattern' do + subpath = nil + mock_app do + before("/foo/:sub") { |s| subpath = s } + get('/foo/*') { } + end + get '/foo/bar' + assert_equal subpath, 'bar' + end + + it 'can catch exceptions in before filters and handle them properly' do + doodle = '' + mock_app do + before do + doodle += 'This begins' + raise StandardError, "before" + end + get "/" do + doodle = 'and runs' + end + error 500 do + "Error handled #{env['sinatra.error'].message}" + end + end + + doodle = '' + get '/' + assert_equal 'Error handled before', body + assert_equal 'This begins', doodle + end +end + +class AfterFilterTest < Minitest::Test + it "executes before and after filters in correct order" do + invoked = 0 + mock_app do + before { invoked = 2 } + get('/') { invoked += 2; 'hello' } + after { invoked *= 2 } + end + + get '/' + assert ok? + + assert_equal 8, invoked + end + + it "executes filters in the order defined" do + count = 0 + mock_app do + get('/') { 'Hello World' } + after do + assert_equal 0, count + count = 1 + end + after do + assert_equal 1, count + count = 2 + end + end + + get '/' + assert ok? + assert_equal 2, count + assert_equal 'Hello World', body + end + + it "allows redirects" do + mock_app do + get('/foo') { 'ORLY' } + after { redirect '/bar' } + end + + get '/foo' + assert redirect? + assert_equal 'http://example.org/bar', response['Location'] + assert_equal '', body + end + + it "does not modify the response with its return value" do + mock_app do + get('/foo') { 'cool' } + after { 'Hello World!' } + end + + get '/foo' + assert ok? + assert_equal 'cool', body + end + + it "does modify the response with halt" do + mock_app do + get '/foo' do + "should not be returned" + end + after { halt 302, 'Hi' } + end + + get '/foo' + assert_equal 302, response.status + assert_equal 'Hi', body + end + + it "runs filters defined in superclasses" do + count = 2 + base = Class.new(Sinatra::Base) + base.after { count *= 2 } + mock_app(base) do + get('/foo') do + count += 2 + "ok" + end + end + + get '/foo' + assert_equal 8, count + end + + it 'does not run after filter when serving static files' do + ran_filter = false + mock_app do + after { ran_filter = true } + set :static, true + set :public_folder, File.dirname(__FILE__) + end + get "/#{File.basename(__FILE__)}" + assert ok? + assert_equal File.read(__FILE__), body + assert !ran_filter + end + + it 'takes an optional route pattern' do + ran_filter = false + mock_app do + after("/b*") { ran_filter = true } + get('/foo') { } + get('/bar') { } + end + get '/foo' + assert !ran_filter + get '/bar' + assert ran_filter + end + + it 'changes to path_info from a pattern matching before filter are respected when routing' do + mock_app do + before('/foo') { request.path_info = '/bar' } + get('/bar') { 'blah' } + end + get '/foo' + assert ok? + assert_equal 'blah', body + end + + it 'generates block arguments from route pattern' do + subpath = nil + mock_app do + after("/foo/:sub") { |s| subpath = s } + get('/foo/*') { } + end + get '/foo/bar' + assert_equal subpath, 'bar' + end + + it 'is possible to access url params from the route param' do + ran = false + mock_app do + get('/foo/*') { } + before('/foo/:sub') do + assert_equal params[:sub], 'bar' + ran = true + end + end + get '/foo/bar' + assert ran + end + + it 'is possible to apply host_name conditions to before filters with no path' do + ran = false + mock_app do + before(:host_name => 'example.com') { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_HOST' => 'example.org' }) + assert !ran + get('/', {}, { 'HTTP_HOST' => 'example.com' }) + assert ran + end + + it 'is possible to apply host_name conditions to before filters with a path' do + ran = false + mock_app do + before('/foo', :host_name => 'example.com') { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_HOST' => 'example.com' }) + assert !ran + get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) + assert !ran + get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) + assert ran + end + + it 'is possible to apply host_name conditions to after filters with no path' do + ran = false + mock_app do + after(:host_name => 'example.com') { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_HOST' => 'example.org' }) + assert !ran + get('/', {}, { 'HTTP_HOST' => 'example.com' }) + assert ran + end + + it 'is possible to apply host_name conditions to after filters with a path' do + ran = false + mock_app do + after('/foo', :host_name => 'example.com') { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_HOST' => 'example.com' }) + assert !ran + get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) + assert !ran + get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) + assert ran + end + + it 'is possible to apply user_agent conditions to before filters with no path' do + ran = false + mock_app do + before(:user_agent => /foo/) { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) + assert !ran + get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert ran + end + + it 'is possible to apply user_agent conditions to before filters with a path' do + ran = false + mock_app do + before('/foo', :user_agent => /foo/) { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert !ran + get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) + assert !ran + get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert ran + end + + it 'can add params' do + mock_app do + before { params['foo'] = 'bar' } + get('/') { params['foo'] } + end + + get '/' + assert_body 'bar' + end + + it 'can remove params' do + mock_app do + before { params.delete('foo') } + get('/') { params['foo'].to_s } + end + + get '/?foo=bar' + assert_body '' + end + + it 'is possible to apply user_agent conditions to after filters with no path' do + ran = false + mock_app do + after(:user_agent => /foo/) { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) + assert !ran + get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert ran + end + + it 'is possible to apply user_agent conditions to after filters with a path' do + ran = false + mock_app do + after('/foo', :user_agent => /foo/) { ran = true } + get('/') { 'welcome' } + end + get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert !ran + get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) + assert !ran + get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) + assert ran + end + + it 'only triggers provides condition if conforms with current Content-Type' do + mock_app do + before(:provides => :txt) { @type = 'txt' } + before(:provides => :html) { @type = 'html' } + get('/') { @type } + end + + get('/', {}, { 'HTTP_ACCEPT' => '*/*' }) + assert_body 'txt' + end + + it 'can catch exceptions in after filters and handle them properly' do + doodle = '' + mock_app do + after do + doodle += ' and after' + raise StandardError, "after" + end + get "/foo" do + doodle = 'Been now' + raise StandardError, "now" + end + get "/" do + doodle = 'Been now' + end + error 500 do + "Error handled #{env['sinatra.error'].message}" + end + end + + get '/foo' + assert_equal 'Error handled now', body + assert_equal 'Been now and after', doodle + + doodle = '' + get '/' + assert_equal 'Error handled after', body + assert_equal 'Been now and after', doodle + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/haml_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/haml_test.rb new file mode 100644 index 000000000..c3a32cc19 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/haml_test.rb @@ -0,0 +1,109 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'haml' + +class HAMLTest < Minitest::Test + def haml_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline HAML strings' do + haml_app { haml '%h1 Hiya' } + assert ok? + assert_equal "

Hiya

\n", body + end + + it 'renders .haml files in views path' do + haml_app { haml :hello } + assert ok? + assert_equal "

Hello From Haml

\n", body + end + + it "renders with inline layouts" do + mock_app do + layout { %q(%h1= 'THIS. IS. ' + yield.upcase) } + get('/') { haml '%em Sparta' } + end + get '/' + assert ok? + assert_equal "

THIS. IS. SPARTA

\n", body + end + + it "renders with file layouts" do + haml_app { haml 'Hello World', :layout => :layout2 } + assert ok? + assert_equal "

HAML Layout!

\n

Hello World

\n", body + end + + it "raises error if template not found" do + mock_app { get('/') { haml :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes HAML options to the Haml engine" do + mock_app { + get('/') { haml "!!!\n%h1 Hello World", :format => :html5 } + } + get '/' + assert ok? + assert_equal "\n

Hello World

\n", body + end + + it "passes default HAML options to the Haml engine" do + mock_app do + set :haml, {:format => :html5} + get('/') { haml "!!!\n%h1 Hello World" } + end + get '/' + assert ok? + assert_equal "\n

Hello World

\n", body + end + + it "merges the default HAML options with the overrides and passes them to the Haml engine" do + mock_app do + set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are + get('/') { haml "!!!\n%h1{:class => :header} Hello World" } + get('/html4') { + haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4 + } + end + get '/' + assert ok? + assert_equal "\n

Hello World

\n", body + get '/html4' + assert ok? + assert_match(/^ { :foo => 'bar' }} + assert_equal "bar\n", body + end + + it "can render truly nested layouts by accepting a layout and a block with the contents" do + mock_app do + template(:main_outer_layout) { "%h1 Title\n= yield" } + template(:an_inner_layout) { "%h2 Subtitle\n= yield" } + template(:a_page) { "%p Contents." } + get('/') do + haml :main_outer_layout, :layout => false do + haml :an_inner_layout do + haml :a_page + end + end + end + end + get '/' + assert ok? + assert_body "

Title

\n

Subtitle

\n

Contents.

\n" + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping haml tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helper.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helper.rb new file mode 100644 index 000000000..1e1e665b7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helper.rb @@ -0,0 +1,132 @@ +ENV['RACK_ENV'] = 'test' +Encoding.default_external = "UTF-8" if defined? Encoding + +RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE + +begin + require 'rack' +rescue LoadError + require 'rubygems' + require 'rack' +end + +testdir = File.dirname(__FILE__) +$LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) + +libdir = File.dirname(File.dirname(__FILE__)) + '/lib' +$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) + +require 'minitest' +require 'contest' +require 'rack/test' +require 'sinatra/base' + +class Sinatra::Base + include Minitest::Assertions + # Allow assertions in request context + def assertions + @assertions ||= 0 + end + + attr_writer :assertions +end + +class Rack::Builder + def include?(middleware) + @ins.any? { |m| p m ; middleware === m } + end +end + +Sinatra::Base.set :environment, :test + +class Minitest::Test + include Rack::Test::Methods + + class << self + alias_method :it, :test + alias_method :section, :context + end + + def self.example(desc = nil, &block) + @example_count = 0 unless instance_variable_defined? :@example_count + @example_count += 1 + it(desc || "Example #{@example_count}", &block) + end + + alias_method :response, :last_response + + setup do + Sinatra::Base.set :environment, :test + end + + # Sets up a Sinatra::Base subclass defined with the block + # given. Used in setup or individual spec methods to establish + # the application. + def mock_app(base=Sinatra::Base, &block) + @app = Sinatra.new(base, &block) + end + + def app + Rack::Lint.new(@app) + end + + def body + response.body.to_s + end + + def assert_body(value) + if value.respond_to? :to_str + assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "") + else + assert_match value, body + end + end + + def assert_status(expected) + assert_equal Integer(expected), Integer(status) + end + + def assert_like(a,b) + pattern = /id=['"][^"']*["']|\s+/ + assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "") + end + + def assert_include(str, substr) + assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}" + end + + def options(uri, params = {}, env = {}, &block) + request(uri, env.merge(:method => "OPTIONS", :params => params), &block) + end + + def patch(uri, params = {}, env = {}, &block) + request(uri, env.merge(:method => "PATCH", :params => params), &block) + end + + def link(uri, params = {}, env = {}, &block) + request(uri, env.merge(:method => "LINK", :params => params), &block) + end + + def unlink(uri, params = {}, env = {}, &block) + request(uri, env.merge(:method => "UNLINK", :params => params), &block) + end + + # Delegate other missing methods to response. + def method_missing(name, *args, &block) + if response && response.respond_to?(name) + response.send(name, *args, &block) + else + super + end + rescue Rack::Test::Error + super + end + + # Do not output warnings for the duration of the block. + def silence_warnings + $VERBOSE, v = nil, $VERBOSE + yield + ensure + $VERBOSE = v + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helpers_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helpers_test.rb new file mode 100644 index 000000000..f56fe24ce --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/helpers_test.rb @@ -0,0 +1,1917 @@ +require File.expand_path('../helper', __FILE__) +require 'date' +require 'json' + +class HelpersTest < Minitest::Test + def test_default + assert true + end + + def status_app(code, &block) + code += 2 if [204, 205, 304].include? code + block ||= proc { } + mock_app do + get('/') do + status code + instance_eval(&block).inspect + end + end + get '/' + end + + describe 'status' do + it 'sets the response status code' do + status_app 207 + assert_equal 207, response.status + end + end + + describe 'not_found?' do + it 'is true for status == 404' do + status_app(404) { not_found? } + assert_body 'true' + end + + it 'is false for status gt 404' do + status_app(405) { not_found? } + assert_body 'false' + end + + it 'is false for status lt 404' do + status_app(403) { not_found? } + assert_body 'false' + end + end + + describe 'informational?' do + it 'is true for 1xx status' do + status_app(100 + rand(100)) { informational? } + assert_body 'true' + end + + it 'is false for status > 199' do + status_app(200 + rand(400)) { informational? } + assert_body 'false' + end + end + + describe 'success?' do + it 'is true for 2xx status' do + status_app(200 + rand(100)) { success? } + assert_body 'true' + end + + it 'is false for status < 200' do + status_app(100 + rand(100)) { success? } + assert_body 'false' + end + + it 'is false for status > 299' do + status_app(300 + rand(300)) { success? } + assert_body 'false' + end + end + + describe 'redirect?' do + it 'is true for 3xx status' do + status_app(300 + rand(100)) { redirect? } + assert_body 'true' + end + + it 'is false for status < 300' do + status_app(200 + rand(100)) { redirect? } + assert_body 'false' + end + + it 'is false for status > 399' do + status_app(400 + rand(200)) { redirect? } + assert_body 'false' + end + end + + describe 'client_error?' do + it 'is true for 4xx status' do + status_app(400 + rand(100)) { client_error? } + assert_body 'true' + end + + it 'is false for status < 400' do + status_app(200 + rand(200)) { client_error? } + assert_body 'false' + end + + it 'is false for status > 499' do + status_app(500 + rand(100)) { client_error? } + assert_body 'false' + end + end + + describe 'server_error?' do + it 'is true for 5xx status' do + status_app(500 + rand(100)) { server_error? } + assert_body 'true' + end + + it 'is false for status < 500' do + status_app(200 + rand(300)) { server_error? } + assert_body 'false' + end + end + + describe 'body' do + it 'takes a block for deferred body generation' do + mock_app do + get('/') { body { 'Hello World' } } + end + + get '/' + assert_equal 'Hello World', body + end + + it 'takes a String, Array, or other object responding to #each' do + mock_app { get('/') { body 'Hello World' } } + + get '/' + assert_equal 'Hello World', body + end + + it 'can be used with other objects' do + mock_app do + get '/' do + body :hello => 'from json' + end + + after do + if Hash === response.body + body response.body[:hello] + end + end + end + + get '/' + assert_body 'from json' + end + + it 'can be set in after filter' do + mock_app do + get('/') { body 'route' } + after { body 'filter' } + end + + get '/' + assert_body 'filter' + end + end + + describe 'redirect' do + it 'uses a 302 when only a path is given' do + mock_app do + get('/') do + redirect '/foo' + fail 'redirect should halt' + end + end + + get '/' + assert_equal 302, status + assert_equal '', body + assert_equal 'http://example.org/foo', response['Location'] + end + + it 'uses the code given when specified' do + mock_app do + get('/') do + redirect '/foo', 301 + fail 'redirect should halt' + end + end + + get '/' + assert_equal 301, status + assert_equal '', body + assert_equal 'http://example.org/foo', response['Location'] + end + + it 'redirects back to request.referer when passed back' do + mock_app { get('/try_redirect') { redirect back } } + + request = Rack::MockRequest.new(@app) + response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo') + assert_equal 302, response.status + assert_equal 'http://example.org/foo', response['Location'] + end + + it 'redirects using a non-standard HTTP port' do + mock_app { get('/') { redirect '/foo' } } + + request = Rack::MockRequest.new(@app) + response = request.get('/', 'SERVER_PORT' => '81') + assert_equal 'http://example.org:81/foo', response['Location'] + end + + it 'redirects using a non-standard HTTPS port' do + mock_app { get('/') { redirect '/foo' } } + + request = Rack::MockRequest.new(@app) + response = request.get('/', 'SERVER_PORT' => '444') + assert_equal 'http://example.org:444/foo', response['Location'] + end + + it 'uses 303 for post requests if request is HTTP 1.1' do + mock_app { post('/') { redirect '/'} } + post('/', {}, 'HTTP_VERSION' => 'HTTP/1.1') + assert_equal 303, status + assert_equal '', body + assert_equal 'http://example.org/', response['Location'] + end + + it 'uses 302 for post requests if request is HTTP 1.0' do + mock_app { post('/') { redirect '/'} } + post('/', {}, 'HTTP_VERSION' => 'HTTP/1.0') + assert_equal 302, status + assert_equal '', body + assert_equal 'http://example.org/', response['Location'] + end + + it 'works behind a reverse proxy' do + mock_app { get('/') { redirect '/foo' } } + + request = Rack::MockRequest.new(@app) + response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080') + assert_equal 'http://example.com/foo', response['Location'] + end + + it 'accepts absolute URIs' do + mock_app do + get('/') do + redirect 'http://google.com' + fail 'redirect should halt' + end + end + + get '/' + assert_equal 302, status + assert_equal '', body + assert_equal 'http://google.com', response['Location'] + end + + it 'accepts absolute URIs with a different schema' do + mock_app do + get('/') do + redirect 'mailto:jsmith@example.com' + fail 'redirect should halt' + end + end + + get '/' + assert_equal 302, status + assert_equal '', body + assert_equal 'mailto:jsmith@example.com', response['Location'] + end + + it 'accepts a URI object instead of a String' do + mock_app do + get('/') { redirect URI.parse('http://sinatrarb.com') } + end + + get '/' + assert_equal 302, status + assert_equal '', body + assert_equal 'http://sinatrarb.com', response['Location'] + end + end + + describe 'error' do + it 'sets a status code and halts' do + mock_app do + get('/') do + error 501 + fail 'error should halt' + end + end + + get '/' + assert_equal 501, status + assert_equal '', body + end + + it 'takes an optional body' do + mock_app do + get('/') do + error 501, 'FAIL' + fail 'error should halt' + end + end + + get '/' + assert_equal 501, status + assert_equal 'FAIL', body + end + + it 'should not invoke error handler when setting status inside an error handler' do + mock_app do + disable :raise_errors + not_found do + body "not_found handler" + status 404 + end + + error do + body "error handler" + status 404 + end + + get '/' do + raise + end + end + + get '/' + assert_equal 404, status + assert_equal 'error handler', body + end + + it 'should not reset the content-type to html for error handlers' do + mock_app do + disable :raise_errors + before { content_type "application/json" } + not_found { JSON.dump("error" => "Not Found") } + end + + get '/' + assert_equal 404, status + assert_equal 'application/json', response.content_type + end + + it 'should not invoke error handler when halting with 500 inside an error handler' do + mock_app do + disable :raise_errors + not_found do + body "not_found handler" + halt 404 + end + + error do + body "error handler" + halt 404 + end + + get '/' do + raise + end + end + + get '/' + assert_equal 404, status + assert_equal 'error handler', body + end + + it 'should not invoke not_found handler when halting with 404 inside a not found handler' do + mock_app do + disable :raise_errors + + not_found do + body "not_found handler" + halt 500 + end + + error do + body "error handler" + halt 500 + end + end + + get '/' + assert_equal 500, status + assert_equal 'not_found handler', body + end + + it 'uses a 500 status code when first argument is a body' do + mock_app do + get('/') do + error 'FAIL' + fail 'error should halt' + end + end + + get '/' + assert_equal 500, status + assert_equal 'FAIL', body + end + end + + describe 'not_found' do + it 'halts with a 404 status' do + mock_app do + get('/') do + not_found + fail 'not_found should halt' + end + end + + get '/' + assert_equal 404, status + assert_equal '', body + end + + it 'does not set a X-Cascade header' do + mock_app do + get('/') do + not_found + fail 'not_found should halt' + end + end + + get '/' + assert_equal 404, status + assert_equal nil, response.headers['X-Cascade'] + end + end + + describe 'headers' do + it 'sets headers on the response object when given a Hash' do + mock_app do + get('/') do + headers 'X-Foo' => 'bar', 'X-Baz' => 'bling' + 'kthx' + end + end + + get '/' + assert ok? + assert_equal 'bar', response['X-Foo'] + assert_equal 'bling', response['X-Baz'] + assert_equal 'kthx', body + end + + it 'returns the response headers hash when no hash provided' do + mock_app do + get('/') do + headers['X-Foo'] = 'bar' + 'kthx' + end + end + + get '/' + assert ok? + assert_equal 'bar', response['X-Foo'] + end + end + + describe 'session' do + it 'uses the existing rack.session' do + mock_app do + get('/') do + session[:foo] + end + end + + get('/', {}, { 'rack.session' => { :foo => 'bar' } }) + assert_equal 'bar', body + end + + it 'creates a new session when none provided' do + mock_app do + enable :sessions + + get('/') do + assert session[:foo].nil? + session[:foo] = 'bar' + redirect '/hi' + end + + get('/hi') do + "hi #{session[:foo]}" + end + end + + get '/' + follow_redirect! + assert_equal 'hi bar', body + end + + it 'inserts session middleware' do + mock_app do + enable :sessions + + get('/') do + assert env['rack.session'] + assert env['rack.session.options'] + 'ok' + end + end + + get '/' + assert_body 'ok' + end + + it 'sets a default session secret' do + mock_app do + enable :sessions + + get('/') do + secret = env['rack.session.options'][:secret] + assert secret + assert_equal secret, settings.session_secret + 'ok' + end + end + + get '/' + assert_body 'ok' + end + + it 'allows disabling session secret' do + mock_app do + enable :sessions + disable :session_secret + + get('/') do + assert !env['rack.session.options'].include?(:session_secret) + 'ok' + end + end + + # Silence warnings since Rack::Session::Cookie complains about the non-present session secret + silence_warnings do + get '/' + end + assert_body 'ok' + end + + it 'accepts an options hash' do + mock_app do + set :sessions, :foo => :bar + + get('/') do + assert_equal env['rack.session.options'][:foo], :bar + 'ok' + end + end + + get '/' + assert_body 'ok' + end + end + + describe 'mime_type' do + include Sinatra::Helpers + + it "looks up mime types in Rack's MIME registry" do + Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' + assert_equal 'application/foo', mime_type('foo') + assert_equal 'application/foo', mime_type('.foo') + assert_equal 'application/foo', mime_type(:foo) + end + + it 'returns nil when given nil' do + assert mime_type(nil).nil? + end + + it 'returns nil when media type not registered' do + assert mime_type(:bizzle).nil? + end + + it 'returns the argument when given a media type string' do + assert_equal 'text/plain', mime_type('text/plain') + end + + it 'turns AcceptEntry into String' do + type = mime_type(Sinatra::Request::AcceptEntry.new('text/plain')) + assert_equal String, type.class + assert_equal 'text/plain', type + end + end + + test 'Base.mime_type registers mime type' do + mock_app do + mime_type :foo, 'application/foo' + + get('/') do + "foo is #{mime_type(:foo)}" + end + end + + get '/' + assert_equal 'foo is application/foo', body + end + + describe 'content_type' do + it 'sets the Content-Type header' do + mock_app do + get('/') do + content_type 'text/plain' + 'Hello World' + end + end + + get '/' + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] + assert_equal 'Hello World', body + end + + it 'takes media type parameters (like charset=)' do + mock_app do + get('/') do + content_type 'text/html', :charset => 'latin1' + "

Hello, World

" + end + end + + get '/' + assert ok? + assert_equal 'text/html;charset=latin1', response['Content-Type'] + assert_equal "

Hello, World

", body + end + + it "looks up symbols in Rack's mime types dictionary" do + Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' + mock_app do + get('/foo.xml') do + content_type :foo + "I AM FOO" + end + end + + get '/foo.xml' + assert ok? + assert_equal 'application/foo', response['Content-Type'] + assert_equal 'I AM FOO', body + end + + it 'fails when no mime type is registered for the argument provided' do + mock_app do + get('/foo.xml') do + content_type :bizzle + "I AM FOO" + end + end + + assert_raises(RuntimeError) { get '/foo.xml' } + end + + it 'only sets default charset for specific mime types' do + tests_ran = false + mock_app do + mime_type :foo, 'text/foo' + mime_type :bar, 'application/bar' + mime_type :baz, 'application/baz' + add_charset << mime_type(:baz) + get('/') do + assert_equal content_type(:txt), 'text/plain;charset=utf-8' + assert_equal content_type(:css), 'text/css;charset=utf-8' + assert_equal content_type(:html), 'text/html;charset=utf-8' + assert_equal content_type(:foo), 'text/foo;charset=utf-8' + assert_equal content_type(:xml), 'application/xml;charset=utf-8' + assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8' + assert_equal content_type(:js), 'application/javascript;charset=utf-8' + assert_equal content_type(:json), 'application/json' + assert_equal content_type(:bar), 'application/bar' + assert_equal content_type(:png), 'image/png' + assert_equal content_type(:baz), 'application/baz;charset=utf-8' + tests_ran = true + "done" + end + end + + get '/' + assert tests_ran + end + + it 'handles already present params' do + mock_app do + get('/') do + content_type 'foo/bar;level=1', :charset => 'utf-8' + 'ok' + end + end + + get '/' + assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type'] + end + + it 'does not add charset if present' do + mock_app do + get('/') do + content_type 'text/plain;charset=utf-16' + 'ok' + end + end + + get '/' + assert_equal 'text/plain;charset=utf-16', response['Content-Type'] + end + + it 'properly encodes parameters with delimiter characters' do + mock_app do + before '/comma' do + content_type 'image/png', :comment => 'Hello, world!' + end + before '/semicolon' do + content_type 'image/png', :comment => 'semi;colon' + end + before '/quote' do + content_type 'image/png', :comment => '"Whatever."' + end + + get('*') { 'ok' } + end + + get '/comma' + assert_equal 'image/png;comment="Hello, world!"', response['Content-Type'] + get '/semicolon' + assert_equal 'image/png;comment="semi;colon"', response['Content-Type'] + get '/quote' + assert_equal 'image/png;comment="\"Whatever.\""', response['Content-Type'] + end + end + + describe 'attachment' do + def attachment_app(filename=nil) + mock_app do + get('/attachment') do + attachment filename + response.write("") + end + end + end + + it 'sets the Content-Type response header' do + attachment_app('test.xml') + get '/attachment' + assert_equal 'application/xml;charset=utf-8', response['Content-Type'] + assert_equal '', body + end + + it 'sets the Content-Type response header without extname' do + attachment_app('test') + get '/attachment' + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + assert_equal '', body + end + + it 'sets the Content-Type response header with extname' do + mock_app do + get('/attachment') do + content_type :atom + attachment 'test.xml' + response.write("") + end + end + + get '/attachment' + assert_equal 'application/atom+xml', response['Content-Type'] + assert_equal '', body + end + + end + + describe 'send_file' do + setup do + @file = File.dirname(__FILE__) + '/file.txt' + File.open(@file, 'wb') { |io| io.write('Hello World') } + end + + def teardown + File.unlink @file + @file = nil + end + + def send_file_app(opts={}) + path = @file + mock_app { + get '/file.txt' do + send_file path, opts + end + } + end + + it "sends the contents of the file" do + send_file_app + get '/file.txt' + assert ok? + assert_equal 'Hello World', body + end + + it 'sets the Content-Type response header if a mime-type can be located' do + send_file_app + get '/file.txt' + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] + end + + it 'sets the Content-Type response header if type option is set to a file extension' do + send_file_app :type => 'html' + get '/file.txt' + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + + it 'sets the Content-Type response header if type option is set to a mime type' do + send_file_app :type => 'application/octet-stream' + get '/file.txt' + assert_equal 'application/octet-stream', response['Content-Type'] + end + + it 'sets the Content-Length response header' do + send_file_app + get '/file.txt' + assert_equal 'Hello World'.length.to_s, response['Content-Length'] + end + + it 'sets the Last-Modified response header' do + send_file_app + get '/file.txt' + assert_equal File.mtime(@file).httpdate, response['Last-Modified'] + end + + it 'allows passing in a different Last-Modified response header with :last_modified' do + time = Time.now + send_file_app :last_modified => time + get '/file.txt' + assert_equal time.httpdate, response['Last-Modified'] + end + + it "returns a 404 when not found" do + mock_app { + get('/') { send_file 'this-file-does-not-exist.txt' } + } + get '/' + assert not_found? + end + + it "does not set the Content-Disposition header by default" do + send_file_app + get '/file.txt' + assert_nil response['Content-Disposition'] + end + + it "sets the Content-Disposition header when :disposition set to 'attachment'" do + send_file_app :disposition => 'attachment' + get '/file.txt' + assert_equal 'attachment; filename="file.txt"', response['Content-Disposition'] + end + + it "does not set add a file name if filename is false" do + send_file_app :disposition => 'inline', :filename => false + get '/file.txt' + assert_equal 'inline', response['Content-Disposition'] + end + + it "sets the Content-Disposition header when :disposition set to 'inline'" do + send_file_app :disposition => 'inline' + get '/file.txt' + assert_equal 'inline; filename="file.txt"', response['Content-Disposition'] + end + + it "sets the Content-Disposition header when :filename provided" do + send_file_app :filename => 'foo.txt' + get '/file.txt' + assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition'] + end + + it 'allows setting a custom status code' do + send_file_app :status => 201 + get '/file.txt' + assert_status 201 + end + + it "is able to send files with unknown mime type" do + @file = File.dirname(__FILE__) + '/file.foobar' + File.open(@file, 'wb') { |io| io.write('Hello World') } + send_file_app + get '/file.txt' + assert_equal 'application/octet-stream', response['Content-Type'] + end + + it "does not override Content-Type if already set and no explicit type is given" do + path = @file + mock_app do + get('/') do + content_type :png + send_file path + end + end + get '/' + assert_equal 'image/png', response['Content-Type'] + end + + it "does override Content-Type even if already set, if explicit type is given" do + path = @file + mock_app do + get('/') do + content_type :png + send_file path, :type => :gif + end + end + get '/' + assert_equal 'image/gif', response['Content-Type'] + end + + it 'can have :status option as a string' do + path = @file + mock_app do + post '/' do + send_file path, :status => '422' + end + end + post '/' + assert_equal response.status, 422 + end + end + + describe 'cache_control' do + setup do + mock_app do + get('/foo') do + cache_control :public, :no_cache, :max_age => 60.0 + 'Hello World' + end + + get('/bar') do + cache_control :public, :no_cache + 'Hello World' + end + end + end + + it 'sets the Cache-Control header' do + get '/foo' + assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') + end + + it 'last argument does not have to be a hash' do + get '/bar' + assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ') + end + end + + describe 'expires' do + setup do + mock_app do + get('/foo') do + expires 60, :public, :no_cache + 'Hello World' + end + + get('/bar') { expires Time.now } + + get('/baz') { expires Time.at(0) } + + get('/blah') do + obj = Object.new + def obj.method_missing(*a, &b) 60.send(*a, &b) end + def obj.is_a?(thing) 60.is_a?(thing) end + expires obj, :public, :no_cache + 'Hello World' + end + + get('/boom') { expires '9999' } + end + end + + it 'sets the Cache-Control header' do + get '/foo' + assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') + end + + it 'sets the Expires header' do + get '/foo' + refute_nil response['Expires'] + end + + it 'allows passing Time.now objects' do + get '/bar' + refute_nil response['Expires'] + end + + it 'allows passing Time.at objects' do + get '/baz' + assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires'] + end + + it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do + get '/blah' + assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') + end + + it 'fails when Time.parse raises an ArgumentError' do + assert_raises(ArgumentError) { get '/boom' } + end + end + + describe 'last_modified' do + it 'ignores nil' do + mock_app { get('/') { last_modified nil; 200; } } + + get '/' + assert ! response['Last-Modified'] + end + + it 'does not change a status other than 200' do + mock_app do + get('/') do + status 299 + last_modified Time.at(0) + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT') + assert_status 299 + assert_body 'ok' + end + + [Time.now, DateTime.now, Date.today, Time.now.to_i, + Struct.new(:to_time).new(Time.now) ].each do |last_modified_time| + describe "with #{last_modified_time.class.name}" do + setup do + mock_app do + get('/') do + last_modified last_modified_time + 'Boo!' + end + end + wrapper = Object.new.extend Sinatra::Helpers + @last_modified_time = wrapper.time_for last_modified_time + end + + # fixes strange missing test error when running complete test suite. + it("does not complain about missing tests") { } + + context "when there's no If-Modified-Since header" do + it 'sets the Last-Modified header to a valid RFC 2616 date value' do + get '/' + assert_equal @last_modified_time.httpdate, response['Last-Modified'] + end + + it 'conditional GET misses and returns a body' do + get '/' + assert_equal 200, status + assert_equal 'Boo!', body + end + end + + context "when there's an invalid If-Modified-Since header" do + it 'sets the Last-Modified header to a valid RFC 2616 date value' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) + assert_equal @last_modified_time.httpdate, response['Last-Modified'] + end + + it 'conditional GET misses and returns a body' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) + assert_equal 200, status + assert_equal 'Boo!', body + end + end + + context "when the resource has been modified since the If-Modified-Since header date" do + it 'sets the Last-Modified header to a valid RFC 2616 date value' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) + assert_equal @last_modified_time.httpdate, response['Last-Modified'] + end + + it 'conditional GET misses and returns a body' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) + assert_equal 200, status + assert_equal 'Boo!', body + end + + it 'does not rely on string comparison' do + mock_app do + get('/compare') do + last_modified "Mon, 18 Oct 2010 20:57:11 GMT" + "foo" + end + end + + get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }) + assert_equal 200, status + assert_equal 'foo', body + get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) + assert_equal 304, status + assert_equal '', body + end + end + + context "when the resource has been modified on the exact If-Modified-Since header date" do + it 'sets the Last-Modified header to a valid RFC 2616 date value' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) + assert_equal @last_modified_time.httpdate, response['Last-Modified'] + end + + it 'conditional GET matches and halts' do + get( '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) + assert_equal 304, status + assert_equal '', body + end + end + + context "when the resource hasn't been modified since the If-Modified-Since header date" do + it 'sets the Last-Modified header to a valid RFC 2616 date value' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) + assert_equal @last_modified_time.httpdate, response['Last-Modified'] + end + + it 'conditional GET matches and halts' do + get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) + assert_equal 304, status + assert_equal '', body + end + end + + context "If-Unmodified-Since" do + it 'results in 200 if resource has not been modified' do + get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) + assert_equal 200, status + assert_equal 'Boo!', body + end + + it 'results in 412 if resource has been modified' do + get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate }) + assert_equal 412, status + assert_equal '', body + end + end + end + end + end + + describe 'etag' do + context "safe requests" do + it 'returns 200 for normal requests' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get '/' + assert_status 200 + assert_body 'ok' + end + + context "If-None-Match" do + it 'returns 304 when If-None-Match is *' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 304 + assert_body '' + end + + it 'returns 200 when If-None-Match is * for new resources' do + mock_app do + get('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 304 when If-None-Match is * for existing resources' do + mock_app do + get('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 304 + assert_body '' + end + + it 'returns 304 when If-None-Match is the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 304 + assert_body '' + end + + it 'returns 304 when If-None-Match includes the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') + assert_status 304 + assert_body '' + end + + it 'returns 200 when If-None-Match does not include the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + + it 'ignores If-Modified-Since if If-None-Match does not match' do + mock_app do + get('/') do + etag 'foo' + last_modified Time.at(0) + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + + it 'does not change a status code other than 2xx or 304' do + mock_app do + get('/') do + status 499 + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 499 + assert_body 'ok' + end + + it 'does change 2xx status codes' do + mock_app do + get('/') do + status 299 + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 304 + assert_body '' + end + + it 'does not send a body on 304 status codes' do + mock_app do + get('/') do + status 304 + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 304 + assert_body '' + end + end + + context "If-Match" do + it 'returns 200 when If-Match is the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '"foo"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-Match includes the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-Match is *' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match is * for new resources' do + mock_app do + get('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-Match is * for existing resources' do + mock_app do + get('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match does not include the etag' do + mock_app do + get('/') do + etag 'foo' + 'ok' + end + end + + get('/', {}, 'HTTP_IF_MATCH' => '"bar"') + assert_status 412 + assert_body '' + end + end + end + + context "idempotent requests" do + it 'returns 200 for normal requests' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put '/' + assert_status 200 + assert_body 'ok' + end + + context "If-None-Match" do + it 'returns 412 when If-None-Match is *' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-None-Match is * for new resources' do + mock_app do + put('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-None-Match is * for existing resources' do + mock_app do + put('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 412 when If-None-Match is the etag' do + mock_app do + put '/' do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 412 + assert_body '' + end + + it 'returns 412 when If-None-Match includes the etag' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-None-Match does not include the etag' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + + it 'ignores If-Modified-Since if If-None-Match does not match' do + mock_app do + put('/') do + etag 'foo' + last_modified Time.at(0) + 'ok' + end + end + + put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + end + + context "If-Match" do + it 'returns 200 when If-Match is the etag' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '"foo"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-Match includes the etag' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-Match is *' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match is * for new resources' do + mock_app do + put('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-Match is * for existing resources' do + mock_app do + put('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match does not include the etag' do + mock_app do + put('/') do + etag 'foo' + 'ok' + end + end + + put('/', {}, 'HTTP_IF_MATCH' => '"bar"') + assert_status 412 + assert_body '' + end + end + end + + context "post requests" do + it 'returns 200 for normal requests' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/') + assert_status 200 + assert_body 'ok' + end + + context "If-None-Match" do + it 'returns 200 when If-None-Match is *' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-None-Match is * for new resources' do + mock_app do + post('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-None-Match is * for existing resources' do + mock_app do + post('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 412 when If-None-Match is the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') + assert_status 412 + assert_body '' + end + + it 'returns 412 when If-None-Match includes the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-None-Match does not include the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + + it 'ignores If-Modified-Since if If-None-Match does not match' do + mock_app do + post('/') do + etag 'foo' + last_modified Time.at(0) + 'ok' + end + end + + post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') + assert_status 200 + assert_body 'ok' + end + end + + context "If-Match" do + it 'returns 200 when If-Match is the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '"foo"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 200 when If-Match includes the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match is *' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 412 when If-Match is * for new resources' do + mock_app do + post('/') do + etag 'foo', :new_resource => true + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 412 + assert_body '' + end + + it 'returns 200 when If-Match is * for existing resources' do + mock_app do + post('/') do + etag 'foo', :new_resource => false + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '*') + assert_status 200 + assert_body 'ok' + end + + it 'returns 412 when If-Match does not include the etag' do + mock_app do + post('/') do + etag 'foo' + 'ok' + end + end + + post('/', {}, 'HTTP_IF_MATCH' => '"bar"') + assert_status 412 + assert_body '' + end + end + end + + it 'uses a weak etag with the :weak option' do + mock_app do + get('/') do + etag 'FOO', :weak + "that's weak, dude." + end + end + get '/' + assert_equal 'W/"FOO"', response['ETag'] + end + + it 'raises an ArgumentError for an invalid strength' do + mock_app do + get('/') do + etag 'FOO', :w00t + "that's weak, dude." + end + end + assert_raises(ArgumentError) { get('/') } + end + end + + describe 'back' do + it "makes redirecting back pretty" do + mock_app { get('/foo') { redirect back } } + + get('/foo', {}, 'HTTP_REFERER' => 'http://github.com') + assert redirect? + assert_equal "http://github.com", response.location + end + end + + describe 'uri' do + it 'generates absolute urls' do + mock_app { get('/') { uri }} + get '/' + assert_equal 'http://example.org/', body + end + + it 'includes path_info' do + mock_app { get('/:name') { uri }} + get '/foo' + assert_equal 'http://example.org/foo', body + end + + it 'allows passing an alternative to path_info' do + mock_app { get('/:name') { uri '/bar' }} + get '/foo' + assert_equal 'http://example.org/bar', body + end + + it 'includes script_name' do + mock_app { get('/:name') { uri '/bar' }} + get '/foo', {}, { "SCRIPT_NAME" => '/foo' } + assert_equal 'http://example.org/foo/bar', body + end + + it 'handles absolute URIs' do + mock_app { get('/') { uri 'http://google.com' }} + get '/' + assert_equal 'http://google.com', body + end + + it 'handles different protocols' do + mock_app { get('/') { uri 'mailto:jsmith@example.com' }} + get '/' + assert_equal 'mailto:jsmith@example.com', body + end + + it 'is aliased to #url' do + mock_app { get('/') { url }} + get '/' + assert_equal 'http://example.org/', body + end + + it 'is aliased to #to' do + mock_app { get('/') { to }} + get '/' + assert_equal 'http://example.org/', body + end + end + + describe 'logger' do + it 'logging works when logging is enabled' do + mock_app do + enable :logging + get('/') do + logger.info "Program started" + logger.warn "Nothing to do!" + end + end + io = StringIO.new + get '/', {}, 'rack.errors' => io + assert io.string.include?("INFO -- : Program started") + assert io.string.include?("WARN -- : Nothing to do") + end + + it 'logging works when logging is disable, but no output is produced' do + mock_app do + disable :logging + get('/') do + logger.info "Program started" + logger.warn "Nothing to do!" + end + end + io = StringIO.new + get '/', {}, 'rack.errors' => io + assert !io.string.include?("INFO -- : Program started") + assert !io.string.include?("WARN -- : Nothing to do") + end + + it 'does not create a logger when logging is set to nil' do + mock_app do + set :logging, nil + get('/') { logger.inspect } + end + + get '/' + assert_body 'nil' + end + end + + module ::HelperOne; def one; '1'; end; end + module ::HelperTwo; def two; '2'; end; end + + describe 'Adding new helpers' do + it 'takes a list of modules to mix into the app' do + mock_app do + helpers ::HelperOne, ::HelperTwo + + get('/one') { one } + + get('/two') { two } + end + + get '/one' + assert_equal '1', body + + get '/two' + assert_equal '2', body + end + + it 'takes a block to mix into the app' do + mock_app do + helpers do + def foo + 'foo' + end + end + + get('/') { foo } + end + + get '/' + assert_equal 'foo', body + end + + it 'evaluates the block in class context so that methods can be aliased' do + mock_app do + helpers { alias_method :h, :escape_html } + + get('/') { h('42 < 43') } + end + + get '/' + assert ok? + assert_equal '42 < 43', body + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration/app.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration/app.rb new file mode 100644 index 000000000..3e87a00aa --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration/app.rb @@ -0,0 +1,79 @@ +$stderr.puts "loading" +require 'sinatra' + +configure do + set :foo, :bar +end + +get '/app_file' do + content_type :txt + settings.app_file +end + +get '/ping' do + 'pong' +end + +get '/stream' do + stream do |out| + sleep 0.1 + out << "a" + sleep 1.2 + out << "b" + end +end + +get '/mainonly' do + object = Object.new + begin + object.send(:get, '/foo') { } + 'false' + rescue NameError + 'true' + end +end + +set :out, nil +get '/async' do + stream(:keep_open) { |o| (settings.out = o) << "hi!" } +end + +get '/send' do + settings.out << params[:msg] if params[:msg] + settings.out.close if params[:close] + "ok" +end + +get '/send_file' do + file = File.expand_path '../../views/a/in_a.str', __FILE__ + send_file file +end + +get '/streaming' do + headers['Content-Length'] = '46' + stream do |out| + out << "It's gonna be legen -\n" + sleep 0.5 + out << " (wait for it) \n" + puts headers + sleep 1 + out << "- dary!\n" + end +end + +class Subclass < Sinatra::Base + set :out, nil + get '/subclass/async' do + stream(:keep_open) { |o| (settings.out = o) << "hi!" } + end + + get '/subclass/send' do + settings.out << params[:msg] if params[:msg] + settings.out.close if params[:close] + "ok" + end +end + +use Subclass + +$stderr.puts "starting" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_helper.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_helper.rb new file mode 100644 index 000000000..f74717544 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_helper.rb @@ -0,0 +1,236 @@ +require 'sinatra/base' +require 'rbconfig' +require 'open-uri' +require 'net/http' +require 'timeout' + +module IntegrationHelper + class BaseServer + extend Enumerable + attr_accessor :server, :port, :pipe + alias name server + + def self.all + @all ||= [] + end + + def self.each(&block) + all.each(&block) + end + + def self.run(server, port) + new(server, port).run + end + + def app_file + File.expand_path('../integration/app.rb', __FILE__) + end + + def environment + "development" + end + + def initialize(server, port) + @installed, @pipe, @server, @port = nil, nil, server, port + Server.all << self + end + + def run + return unless installed? + kill + @log = "" + @pipe = IO.popen(command) + @started = Time.now + warn "#{server} up and running on port #{port}" if ping + at_exit { kill } + end + + def ping(timeout = 30) + loop do + return if alive? + if Time.now - @started > timeout + $stderr.puts command, log + fail "timeout" + else + sleep 0.1 + end + end + end + + def alive? + 3.times { get('/ping') } + true + rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error + false + end + + def get_stream(url = "/stream", &block) + Net::HTTP.start '127.0.0.1', port do |http| + request = Net::HTTP::Get.new url + http.request request do |response| + response.read_body(&block) + end + end + end + + def get_response(url) + Net::HTTP.start '127.0.0.1', port do |http| + request = Net::HTTP::Get.new url + http.request request do |response| + response + end + end + end + + def get(url) + Timeout.timeout(1) { open("http://127.0.0.1:#{port}#{url}").read } + end + + def log + @log ||= "" + loop { @log << @pipe.read_nonblock(1) } + rescue Exception + @log + end + + def installed? + return @installed unless @installed.nil? + s = server == 'HTTP' ? 'net/http/server' : server + require s + @installed = true + rescue LoadError + warn "#{server} is not installed, skipping integration tests" + @installed = false + end + + def command + @command ||= begin + cmd = ["RACK_ENV=#{environment}", "exec"] + if RbConfig.respond_to? :ruby + cmd << RbConfig.ruby.inspect + else + file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir') + cmd << File.expand_path(file, dir).inspect + end + cmd << "-w" unless thin? || net_http_server? + cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect + cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port + cmd << "-e" << environment.to_s << '2>&1' + cmd.join " " + end + end + + def kill + return unless pipe + Process.kill("KILL", pipe.pid) + rescue NotImplementedError + system "kill -9 #{pipe.pid}" + rescue Errno::ESRCH + end + + def webrick? + name.to_s == "webrick" + end + + def thin? + name.to_s == "thin" + end + + def puma? + name.to_s == "puma" + end + + def trinidad? + name.to_s == "trinidad" + end + + def net_http_server? + name.to_s == 'HTTP' + end + + def warnings + log.scan(%r[(?:\(eval|lib/sinatra).*warning:.*$]) + end + + def run_test(target, &block) + retries ||= 3 + target.server = self + run unless alive? + target.instance_eval(&block) + rescue Exception => error + retries -= 1 + kill + retries < 0 ? retry : raise(error) + end + end + + if RUBY_ENGINE == "jruby" + class JRubyServer < BaseServer + def start_vm + require 'java' + # Create a new container, set load paths and env + # SINGLETHREAD means create a new runtime + vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD) + vm.load_paths = [File.expand_path('../../lib', __FILE__)] + vm.environment = ENV.merge('RACK_ENV' => environment.to_s) + + # This ensures processing of RUBYOPT which activates Bundler + vm.provider.ruby_instance_config.process_arguments [] + vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s] + + # Set stdout/stderr so we can retrieve log + @pipe = java.io.ByteArrayOutputStream.new + vm.output = java.io.PrintStream.new(@pipe) + vm.error = java.io.PrintStream.new(@pipe) + + Thread.new do + # Hack to ensure that Kernel#caller has the same info as + # when run from command-line, for Sinatra::Application.app_file. + # Also, line numbers are zero-based in JRuby's parser + vm.provider.runtime.current_context.set_file_and_line(app_file, 0) + # Run the app + vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file + # terminate launches at_exit hooks which start server + vm.terminate + end + end + + def run + return unless installed? + kill + @thread = start_vm + @started = Time.now + warn "#{server} up and running on port #{port}" if ping + at_exit { kill } + end + + def log + String.from_java_bytes @pipe.to_byte_array + end + + def kill + @thread.kill if @thread + @thread = nil + end + end + Server = JRubyServer + else + Server = BaseServer + end + + def it(message, &block) + Server.each do |server| + next unless server.installed? + super("with #{server.name}: #{message}") { server.run_test(self, &block) } + end + end + + def self.extend_object(obj) + super + + base_port = 5000 + Process.pid % 100 + Sinatra::Base.server.each_with_index do |server, index| + Server.run(server, base_port+index) + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_test.rb new file mode 100644 index 000000000..5246639e9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/integration_test.rb @@ -0,0 +1,104 @@ +require File.expand_path('../helper', __FILE__) +require File.expand_path('../integration_helper', __FILE__) + +# These tests start a real server and talk to it over TCP. +# Every test runs with every detected server. +# +# See test/integration/app.rb for the code of the app we test against. +class IntegrationTest < Minitest::Test + extend IntegrationHelper + attr_accessor :server + + it('sets the app_file') { assert_equal server.app_file, server.get("/app_file") } + it('only extends main') { assert_equal "true", server.get("/mainonly") } + + it 'logs once in development mode' do + next if server.puma? or RUBY_ENGINE == 'jruby' + random = "%064x" % Kernel.rand(2**256-1) + server.get "/ping?x=#{random}" + count = server.log.scan("GET /ping?x=#{random}").count + if server.net_http_server? + assert_equal 0, count + elsif server.webrick? + assert(count > 0) + else + assert_equal(1, count) + end + end + + it 'streams' do + next if server.webrick? or server.trinidad? + times, chunks = [Time.now], [] + server.get_stream do |chunk| + next if chunk.empty? + chunks << chunk + times << Time.now + end + assert_equal ["a", "b"], chunks + assert times[1] - times[0] < 1 + assert times[2] - times[1] > 1 + end + + it 'streams async' do + next unless server.thin? + + Timeout.timeout(3) do + chunks = [] + server.get_stream '/async' do |chunk| + next if chunk.empty? + chunks << chunk + case chunk + when "hi!" then server.get "/send?msg=hello" + when "hello" then server.get "/send?close=1" + end + end + + assert_equal ['hi!', 'hello'], chunks + end + end + + it 'streams async from subclass' do + next unless server.thin? + + Timeout.timeout(3) do + chunks = [] + server.get_stream '/subclass/async' do |chunk| + next if chunk.empty? + chunks << chunk + case chunk + when "hi!" then server.get "/subclass/send?msg=hello" + when "hello" then server.get "/subclass/send?close=1" + end + end + + assert_equal ['hi!', 'hello'], chunks + end + end + + it 'starts the correct server' do + exp = %r{ + ==\sSinatra\s\(v#{Sinatra::VERSION}\)\s + has\staken\sthe\sstage\son\s\d+\sfor\sdevelopment\s + with\sbackup\sfrom\s#{server} + }ix + + # because Net HTTP Server logs to $stderr by default + assert_match exp, server.log unless server.net_http_server? + end + + it 'does not generate warnings' do + assert_raises(OpenURI::HTTPError) { server.get '/' } + server.get '/app_file' + assert_equal [], server.warnings + end + + it 'sets the Content-Length response header when sending files' do + response = server.get_response '/send_file' + assert response['Content-Length'] + end + + it "doesn't ignore Content-Length header when streaming" do + response = server.get_response '/streaming' + assert_equal '46', response['Content-Length'] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/less_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/less_test.rb new file mode 100644 index 000000000..10276fd15 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/less_test.rb @@ -0,0 +1,69 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'less' + +class LessTest < Minitest::Test + def less_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set options + get('/', &block) + end + get '/' + end + + it 'renders inline Less strings' do + less_app { + less "@white_color: #fff; #main { background-color: @white_color }" + } + assert ok? + assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") + end + + it 'defaults content type to css' do + less_app { + less "@white_color: #fff; #main { background-color: @white_color }" + } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + less_app do + content_type :html + less "@white_color: #fff; #main { background-color: @white_color }" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + less_app(:less => { :content_type => 'html' }) do + less "@white_color: #fff; #main { background-color: @white_color }" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders .less files in views path' do + less_app { less :hello } + assert ok? + assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") + end + + it 'ignores the layout option' do + less_app { less :hello, :layout => :layout2 } + assert ok? + assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") + end + + it "raises error if template not found" do + mock_app { get('/') { less :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping less tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/liquid_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/liquid_test.rb new file mode 100644 index 000000000..57fcb6c94 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/liquid_test.rb @@ -0,0 +1,77 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'liquid' + +class LiquidTest < Minitest::Test + def liquid_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline liquid strings' do + liquid_app { liquid '

Hiya

' } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders .liquid files in views path' do + liquid_app { liquid :hello } + assert ok? + assert_equal "

Hello From Liquid

\n", body + end + + it "renders with inline layouts" do + mock_app do + layout { "

THIS. IS. {{ yield }}

" } + get('/') { liquid 'SPARTA' } + end + get '/' + assert ok? + assert_equal "

THIS. IS. SPARTA

", body + end + + it "renders with file layouts" do + liquid_app { liquid 'Hello World', :layout => :layout2 } + assert ok? + assert_equal "

Liquid Layout!

\n

Hello World

\n", body + end + + it "raises error if template not found" do + mock_app { get('/') { liquid :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "allows passing locals" do + liquid_app { + liquid '{{ value }}', :locals => { :value => 'foo' } + } + assert ok? + assert_equal 'foo', body + end + + it "can render truly nested layouts by accepting a layout and a block with the contents" do + mock_app do + template(:main_outer_layout) { "

Title

\n{{ yield }}" } + template(:an_inner_layout) { "

Subtitle

\n{{ yield }}" } + template(:a_page) { "

Contents.

\n" } + get('/') do + liquid :main_outer_layout, :layout => false do + liquid :an_inner_layout do + liquid :a_page + end + end + end + end + get '/' + assert ok? + assert_body "

Title

\n

Subtitle

\n

Contents.

\n" + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping liquid tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mapped_error_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mapped_error_test.rb new file mode 100644 index 000000000..cb158a268 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mapped_error_test.rb @@ -0,0 +1,285 @@ +require File.expand_path('../helper', __FILE__) + +class FooError < RuntimeError +end + +class FooNotFound < Sinatra::NotFound +end + +class FooSpecialError < RuntimeError + def http_status; 501 end +end + +class FooStatusOutOfRangeError < RuntimeError + def code; 4000 end +end + +class FooWithCode < RuntimeError + def code; 419 end +end + +class FirstError < RuntimeError; end +class SecondError < RuntimeError; end + +class MappedErrorTest < Minitest::Test + def test_default + assert true + end + + describe 'Exception Mappings' do + it 'invokes handlers registered with ::error when raised' do + mock_app do + set :raise_errors, false + error(FooError) { 'Foo!' } + get('/') { raise FooError } + end + get '/' + assert_equal 500, status + assert_equal 'Foo!', body + end + + it 'passes the exception object to the error handler' do + mock_app do + set :raise_errors, false + error(FooError) { |e| assert_equal(FooError, e.class) } + get('/') { raise FooError } + end + get('/') + end + + it 'uses the Exception handler if no matching handler found' do + mock_app do + set :raise_errors, false + error(Exception) { 'Exception!' } + get('/') { raise FooError } + end + + get '/' + assert_equal 500, status + assert_equal 'Exception!', body + end + + it 'walks down inheritance chain for errors' do + mock_app do + set :raise_errors, false + error(RuntimeError) { 'Exception!' } + get('/') { raise FooError } + end + + get '/' + assert_equal 500, status + assert_equal 'Exception!', body + end + + it 'favors subclass handler over superclass handler if available' do + mock_app do + set :raise_errors, false + error(Exception) { 'Exception!' } + error(FooError) { 'FooError!' } + error(RuntimeError) { 'Exception!' } + get('/') { raise FooError } + end + + get '/' + assert_equal 500, status + assert_equal 'FooError!', body + end + + it "sets env['sinatra.error'] to the rescued exception" do + mock_app do + set :raise_errors, false + error(FooError) do + assert env.include?('sinatra.error') + assert env['sinatra.error'].kind_of?(FooError) + 'looks good' + end + get('/') { raise FooError } + end + get '/' + assert_equal 'looks good', body + end + + it "raises errors from the app when raise_errors set and no handler defined" do + mock_app do + set :raise_errors, true + get('/') { raise FooError } + end + assert_raises(FooError) { get '/' } + end + + it "calls error handlers before raising errors even when raise_errors is set" do + mock_app do + set :raise_errors, true + error(FooError) { "she's there." } + get('/') { raise FooError } + end + get '/' + assert_equal 500, status + end + + it "never raises Sinatra::NotFound beyond the application" do + mock_app(Sinatra::Application) do + get('/') { raise Sinatra::NotFound } + end + get '/' + assert_equal 404, status + end + + it "cascades for subclasses of Sinatra::NotFound" do + mock_app do + set :raise_errors, true + error(FooNotFound) { "foo! not found." } + get('/') { raise FooNotFound } + end + get '/' + assert_equal 404, status + assert_equal 'foo! not found.', body + end + + it 'has a not_found method for backwards compatibility' do + mock_app { not_found { "Lost, are we?" } } + + get '/test' + assert_equal 404, status + assert_equal "Lost, are we?", body + end + + it 'inherits error mappings from base class' do + base = Class.new(Sinatra::Base) + base.error(FooError) { 'base class' } + + mock_app(base) do + set :raise_errors, false + get('/') { raise FooError } + end + + get '/' + assert_equal 'base class', body + end + + it 'overrides error mappings in base class' do + base = Class.new(Sinatra::Base) + base.error(FooError) { 'base class' } + + mock_app(base) do + set :raise_errors, false + error(FooError) { 'subclass' } + get('/') { raise FooError } + end + + get '/' + assert_equal 'subclass', body + end + + it 'honors Exception#http_status if present' do + mock_app do + set :raise_errors, false + error(501) { 'Foo!' } + get('/') { raise FooSpecialError } + end + get '/' + assert_equal 501, status + assert_equal 'Foo!', body + end + + it 'does not use Exception#code by default' do + mock_app do + set :raise_errors, false + get('/') { raise FooWithCode } + end + get '/' + assert_equal 500, status + end + + it 'uses Exception#code if use_code is enabled' do + mock_app do + set :raise_errors, false + set :use_code, true + get('/') { raise FooWithCode } + end + get '/' + assert_equal 419, status + end + + it 'does not rely on Exception#code for invalid codes' do + mock_app do + set :raise_errors, false + set :use_code, true + get('/') { raise FooStatusOutOfRangeError } + end + get '/' + assert_equal 500, status + end + + it "allows a stack of exception_handlers" do + mock_app do + set :raise_errors, false + error(FirstError) { 'First!' } + error(SecondError) { 'Second!' } + get('/'){ raise SecondError } + end + get '/' + assert_equal 500, status + assert_equal 'Second!', body + end + + it "allows an exception handler to pass control to the next exception handler" do + mock_app do + set :raise_errors, false + error(500, FirstError) { 'First!' } + error(500, SecondError) { pass } + get('/') { raise 500 } + end + get '/' + assert_equal 500, status + assert_equal 'First!', body + end + + it "allows an exception handler to handle the exception" do + mock_app do + set :raise_errors, false + error(500, FirstError) { 'First!' } + error(500, SecondError) { 'Second!' } + get('/') { raise 500 } + end + get '/' + assert_equal 500, status + assert_equal 'Second!', body + end + end + + describe 'Custom Error Pages' do + it 'allows numeric status code mappings to be registered with ::error' do + mock_app do + set :raise_errors, false + error(500) { 'Foo!' } + get('/') { [500, {}, 'Internal Foo Error'] } + end + get '/' + assert_equal 500, status + assert_equal 'Foo!', body + end + + it 'allows ranges of status code mappings to be registered with :error' do + mock_app do + set :raise_errors, false + error(500..550) { "Error: #{response.status}" } + get('/') { [507, {}, 'A very special error'] } + end + get '/' + assert_equal 507, status + assert_equal 'Error: 507', body + end + + it 'allows passing more than one range' do + mock_app do + set :raise_errors, false + error(409..411, 503..509) { "Error: #{response.status}" } + get('/') { [507, {}, 'A very special error'] } + end + get '/' + assert_equal 507, status + assert_equal 'Error: 507', body + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markaby_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markaby_test.rb new file mode 100644 index 000000000..fcc7d3330 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markaby_test.rb @@ -0,0 +1,80 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'markaby' + +class MarkabyTest < Minitest::Test + def markaby_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline markaby strings' do + markaby_app { markaby 'h1 "Hiya"' } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders .markaby files in views path' do + markaby_app { markaby :hello } + assert ok? + assert_equal "

Hello From Markaby

", body + end + + it "renders with inline layouts" do + mock_app do + layout { 'h1 { text "THIS. IS. "; yield }' } + get('/') { markaby 'em "SPARTA"' } + end + get '/' + assert ok? + assert_equal "

THIS. IS. SPARTA

", body + end + + it "renders with file layouts" do + markaby_app { markaby 'text "Hello World"', :layout => :layout2 } + assert ok? + assert_equal "

Markaby Layout!

Hello World

", body + end + + it 'renders inline markaby blocks' do + markaby_app { markaby { h1 'Hiya' } } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders inline markaby blocks with inline layouts' do + markaby_app do + settings.layout { 'h1 { text "THIS. IS. "; yield }' } + markaby { em 'SPARTA' } + end + assert ok? + assert_equal "

THIS. IS. SPARTA

", body + end + + it 'renders inline markaby blocks with file layouts' do + markaby_app { markaby(:layout => :layout2) { text "Hello World" } } + assert ok? + assert_equal "

Markaby Layout!

Hello World

", body + end + + it "raises error if template not found" do + mock_app { get('/') { markaby :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "allows passing locals" do + markaby_app { + markaby 'text value', :locals => { :value => 'foo' } + } + assert ok? + assert_equal 'foo', body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping markaby tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markdown_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markdown_test.rb new file mode 100644 index 000000000..7c7deecf3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/markdown_test.rb @@ -0,0 +1,85 @@ +require File.expand_path('../helper', __FILE__) + +MarkdownTest = proc do + def markdown_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + def setup + Tilt.prefer engine, 'markdown', 'mkd', 'md' + super + end + + it 'uses the correct engine' do + assert_equal engine, Tilt[:md] + assert_equal engine, Tilt[:mkd] + assert_equal engine, Tilt[:markdown] + end + + it 'renders inline markdown strings' do + markdown_app { markdown '# Hiya' } + assert ok? + assert_like "

Hiya

\n", body + end + + it 'renders .markdown files in views path' do + markdown_app { markdown :hello } + assert ok? + assert_like "

Hello From Markdown

", body + end + + it "raises error if template not found" do + mock_app { get('/') { markdown :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { markdown 'Sparta', :layout_engine => :str } + end + get '/' + assert ok? + assert_like 'THIS. IS.

SPARTA

!', body + end + + it "renders with file layouts" do + markdown_app { + markdown 'Hello World', :layout => :layout2, :layout_engine => :erb + } + assert ok? + assert_body "ERB Layout!\n

Hello World

" + end + + it "can be used in a nested fashion for partials and whatnot" do + mock_app do + template(:inner) { "hi" } + template(:outer) { "<%= markdown :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_like '

hi

', body + end +end + +# Will generate RDiscountTest, KramdownTest, etc. +map = Tilt.respond_to?(:lazy_map) ? Tilt.lazy_map['md'].map(&:first) : Tilt.mappings['md'] + +map.each do |t| + begin + t = eval(t) if t.is_a? String + t.new { "" } + klass = Class.new(Minitest::Test) { define_method(:engine) { t }} + klass.class_eval(&MarkdownTest) + name = t.name[/[^:]+$/].sub(/Template$/, '') << "Test" + Object.const_set name, klass + rescue LoadError, NameError + warn "#{$!}: skipping markdown tests with #{t}" + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mediawiki_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mediawiki_test.rb new file mode 100644 index 000000000..759aa2355 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/mediawiki_test.rb @@ -0,0 +1,68 @@ +require File.expand_path('../helper', __FILE__) + +begin + require 'wikicloth' + + class MediaWikiTest < Minitest::Test + def mediawiki_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'supports both .mw and .mediawiki extensions' do + assert_equal Tilt[:mw], Tilt[:mediawiki] + end + + it 'renders inline mediawiki strings' do + mediawiki_app { mediawiki "''Hiya''" } + assert ok? + assert_include body, 'Hiya' + end + + it 'renders .mediawiki files in views path' do + mediawiki_app { mediawiki :hello } + assert ok? + assert_include body, "Hello from MediaWiki" + end + + it 'raises error if template not found' do + mock_app { get('/') { mediawiki :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it 'renders with inline layouts' do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { mediawiki 'Sparta', :layout_engine => :str } + end + get '/' + assert ok? + assert_like 'THIS. IS.

SPARTA

!', body + end + + it 'renders with file layouts' do + mediawiki_app do + mediawiki 'Hello World', :layout => :layout2, :layout_engine => :erb + end + assert ok? + assert_body "ERB Layout!\n

Hello World

" + end + + it 'can be used in a nested fashion for partials and whatnot' do + mock_app do + template(:inner) { "hi" } + template(:outer) { "<%= mediawiki :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_like '

hi

', body + end + end +rescue LoadError + warn "#{$!.to_s}: skipping mediawiki tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/middleware_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/middleware_test.rb new file mode 100644 index 000000000..8a6e836c4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/middleware_test.rb @@ -0,0 +1,68 @@ +require File.expand_path('../helper', __FILE__) + +class MiddlewareTest < Minitest::Test + setup do + @app = mock_app(Sinatra::Application) do + get('/*')do + response.headers['X-Tests'] = env['test.ran']. + map { |n| n.split('::').last }. + join(', ') + env['PATH_INFO'] + end + end + end + + class MockMiddleware < Struct.new(:app) + def call(env) + (env['test.ran'] ||= []) << self.class.to_s + app.call(env) + end + end + + class UpcaseMiddleware < MockMiddleware + def call(env) + env['PATH_INFO'] = env['PATH_INFO'].upcase + super + end + end + + it "is added with Sinatra::Application.use" do + @app.use UpcaseMiddleware + get '/hello-world' + assert ok? + assert_equal '/HELLO-WORLD', body + end + + class DowncaseMiddleware < MockMiddleware + def call(env) + env['PATH_INFO'] = env['PATH_INFO'].downcase + super + end + end + + it "runs in the order defined" do + @app.use UpcaseMiddleware + @app.use DowncaseMiddleware + get '/Foo' + assert_equal "/foo", body + assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] + end + + it "resets the prebuilt pipeline when new middleware is added" do + @app.use UpcaseMiddleware + get '/Foo' + assert_equal "/FOO", body + @app.use DowncaseMiddleware + get '/Foo' + assert_equal '/foo', body + assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] + end + + it "works when app is used as middleware" do + @app.use UpcaseMiddleware + @app = @app.new + get '/Foo' + assert_equal "/FOO", body + assert_equal "UpcaseMiddleware", response['X-Tests'] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/nokogiri_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/nokogiri_test.rb new file mode 100644 index 000000000..e2a1c135c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/nokogiri_test.rb @@ -0,0 +1,67 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'nokogiri' + +class NokogiriTest < Minitest::Test + def nokogiri_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline Nokogiri strings' do + nokogiri_app { nokogiri 'xml' } + assert ok? + assert_body %(\n) + end + + it 'renders inline blocks' do + nokogiri_app do + @name = "Frank & Mary" + nokogiri { |xml| xml.couple @name } + end + assert ok? + assert_body %(\nFrank & Mary\n) + end + + it 'renders .nokogiri files in views path' do + nokogiri_app do + @name = "Blue" + nokogiri :hello + end + assert ok? + assert_body "\nYou're my boy, Blue!\n" + end + + it "renders with inline layouts" do + next if Tilt::VERSION <= "1.1" + mock_app do + layout { %(xml.layout { xml << yield }) } + get('/') { nokogiri %(xml.em 'Hello World') } + end + get '/' + assert ok? + assert_body %(\n\n Hello World\n\n) + end + + it "renders with file layouts" do + next if Tilt::VERSION <= "1.1" + nokogiri_app { + nokogiri %(xml.em 'Hello World'), :layout => :layout2 + } + assert ok? + assert_body %(\n\n Hello World\n\n) + end + + it "raises error if template not found" do + mock_app { get('/') { nokogiri :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping nokogiri tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/public/favicon.ico b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/public/favicon.ico new file mode 100644 index 000000000..e69de29bb diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rabl_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rabl_test.rb new file mode 100644 index 000000000..c9303a12c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rabl_test.rb @@ -0,0 +1,89 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'rabl' +require 'ostruct' +require 'json' +require 'active_support/core_ext/hash/conversions' + +class RablTest < Minitest::Test + def rabl_app(&block) + mock_app { + set :views, File.dirname(__FILE__) + '/views' + get '/', &block + } + get '/' + end + + it 'renders inline rabl strings' do + rabl_app do + @foo = OpenStruct.new(:baz => 'w00t') + rabl %q{ + object @foo + attributes :baz + } + end + assert ok? + assert_equal '{"openstruct":{"baz":"w00t"}}', body + end + it 'renders .rabl files in views path' do + rabl_app do + @foo = OpenStruct.new(:bar => 'baz') + rabl :hello + end + assert ok? + assert_equal '{"openstruct":{"bar":"baz"}}', body + end + + it "renders with file layouts" do + rabl_app { + @foo = OpenStruct.new(:bar => 'baz') + rabl :hello, :layout => :layout2 + } + assert ok? + assert_equal '{"qux":{"openstruct":{"bar":"baz"}}}', body + end + + it "raises error if template not found" do + mock_app { + get('/') { rabl :no_such_template } + } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes rabl options to the rabl engine" do + mock_app do + get('/') do + @foo = OpenStruct.new(:bar => 'baz') + rabl %q{ + object @foo + attributes :bar + }, :format => 'xml' + end + end + get '/' + assert ok? + assert_body 'baz' + end + + it "passes default rabl options to the rabl engine" do + mock_app do + set :rabl, :format => 'xml' + get('/') do + @foo = OpenStruct.new(:bar => 'baz') + rabl %q{ + object @foo + attributes :bar + } + end + end + get '/' + assert ok? + assert_body 'baz' + end + +end + +rescue LoadError + warn "#{$!.to_s}: skipping rabl tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rack_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rack_test.rb new file mode 100644 index 000000000..8ac81d420 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rack_test.rb @@ -0,0 +1,45 @@ +require File.expand_path('../helper', __FILE__) +require 'rack' + +class RackTest < Minitest::Test + setup do + @foo = Sinatra.new { get('/foo') { 'foo' }} + @bar = Sinatra.new { get('/bar') { 'bar' }} + end + + def build(*middleware) + endpoint = middleware.pop + @app = Rack::Builder.app do + middleware.each { |m| use m } + run endpoint + end + end + + def check(*middleware) + build(*middleware) + assert get('/foo').ok? + assert_body 'foo' + assert get('/bar').ok? + assert_body 'bar' + end + + it 'works as middleware in front of Rack::Lock, with lock enabled' do + @foo.enable :lock + check(@foo, Rack::Lock, @bar) + end + + it 'works as middleware behind Rack::Lock, with lock enabled' do + @foo.enable :lock + check(Rack::Lock, @foo, @bar) + end + + it 'works as middleware in front of Rack::Lock, with lock disabled' do + @foo.disable :lock + check(@foo, Rack::Lock, @bar) + end + + it 'works as middleware behind Rack::Lock, with lock disabled' do + @foo.disable :lock + check(Rack::Lock, @foo, @bar) + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/radius_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/radius_test.rb new file mode 100644 index 000000000..214cd4ebd --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/radius_test.rb @@ -0,0 +1,59 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'radius' + +class RadiusTest < Minitest::Test + def radius_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline radius strings' do + radius_app { radius '

Hiya

' } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders .radius files in views path' do + radius_app { radius :hello } + assert ok? + assert_equal "

Hello From Radius

\n", body + end + + it "renders with inline layouts" do + mock_app do + layout { "

THIS. IS.

" } + get('/') { radius 'SPARTA' } + end + get '/' + assert ok? + assert_equal "

THIS. IS. SPARTA

", body + end + + it "renders with file layouts" do + radius_app { radius 'Hello World', :layout => :layout2 } + assert ok? + assert_equal "

Radius Layout!

\n

Hello World

\n", body + end + + it "raises error if template not found" do + mock_app { get('/') { radius :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "allows passing locals" do + radius_app { + radius '', :locals => { :value => 'foo' } + } + assert ok? + assert_equal 'foo', body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping radius tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rdoc_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rdoc_test.rb new file mode 100644 index 000000000..cb928dd0c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/rdoc_test.rb @@ -0,0 +1,66 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'rdoc' +require 'rdoc/markup/to_html' + +class RdocTest < Minitest::Test + def rdoc_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline rdoc strings' do + rdoc_app { rdoc '= Hiya' } + assert ok? + assert_body(/]*>Hiya(¶<\/a> ↑<\/a><\/span>)?<\/h1>/) + end + + it 'renders .rdoc files in views path' do + rdoc_app { rdoc :hello } + assert ok? + assert_body(/]*>Hello From RDoc(¶<\/a> ↑<\/a><\/span>)?<\/h1>/) + end + + it "raises error if template not found" do + mock_app { get('/') { rdoc :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { rdoc 'Sparta', :layout_engine => :str } + end + get '/' + assert ok? + assert_like 'THIS. IS.

SPARTA

!', body + end + + it "renders with file layouts" do + rdoc_app { + rdoc 'Hello World', :layout => :layout2, :layout_engine => :erb + } + assert ok? + assert_body "ERB Layout!\n

Hello World

" + end + + it "can be used in a nested fashion for partials and whatnot" do + mock_app do + template(:inner) { "hi" } + template(:outer) { "<%= rdoc :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_like '

hi

', body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping rdoc tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/readme_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/readme_test.rb new file mode 100644 index 000000000..4da41038b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/readme_test.rb @@ -0,0 +1,130 @@ +# Tests to check if all the README examples work. +require File.expand_path('../helper', __FILE__) + +class ReadmeTest < Minitest::Test + example do + mock_app { get('/') { 'Hello world!' } } + get '/' + assert_body 'Hello world!' + end + + section "Routes" do + example do + mock_app do + get('/') { ".. show something .." } + + post('/') { ".. create something .." } + + put('/') { ".. replace something .." } + + patch('/') { ".. modify something .." } + + delete('/') { ".. annihilate something .." } + + options('/') { ".. appease something .." } + + link('/') { ".. affiliate something .." } + + unlink('/') { ".. separate something .." } + end + + get '/' + assert_body '.. show something ..' + + post '/' + assert_body '.. create something ..' + + put '/' + assert_body '.. replace something ..' + + patch '/' + assert_body '.. modify something ..' + + delete '/' + assert_body '.. annihilate something ..' + + options '/' + assert_body '.. appease something ..' + + link '/' + assert_body '.. affiliate something ..' + + unlink '/' + assert_body '.. separate something ..' + end + + example do + mock_app do + get('/hello/:name') do + # matches "GET /hello/foo" and "GET /hello/bar" + # params[:name] is 'foo' or 'bar' + "Hello #{params[:name]}!" + end + end + + get '/hello/foo' + assert_body 'Hello foo!' + + get '/hello/bar' + assert_body 'Hello bar!' + end + + example do + mock_app { get('/hello/:name') { |n| "Hello #{n}!" } } + + get '/hello/foo' + assert_body 'Hello foo!' + + get '/hello/bar' + assert_body 'Hello bar!' + end + + example do + mock_app do + get('/say/*/to/*') do + # matches /say/hello/to/world + params[:splat].inspect # => ["hello", "world"] + end + + get('/download/*.*') do + # matches /download/path/to/file.xml + params[:splat].inspect # => ["path/to/file", "xml"] + end + end + + get "/say/hello/to/world" + assert_body '["hello", "world"]' + + get "/download/path/to/file.xml" + assert_body '["path/to/file", "xml"]' + end + + example do + mock_app do + get(%r{/hello/([\w]+)}) { + "Hello, #{params[:captures].first}!" + } + end + + get '/hello/foo' + assert_body 'Hello, foo!' + + get '/hello/bar' + assert_body 'Hello, bar!' + end + + example do + mock_app do + get( %r{/hello/([\w]+)}) { |c| + "Hello, #{c}!" + } + end + + get '/hello/foo' + assert_body 'Hello, foo!' + + get '/hello/bar' + assert_body 'Hello, bar!' + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/request_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/request_test.rb new file mode 100644 index 000000000..4434589c3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/request_test.rb @@ -0,0 +1,100 @@ +require File.expand_path('../helper', __FILE__) +require 'stringio' + +class RequestTest < Minitest::Test + it 'responds to #user_agent' do + request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'}) + assert request.respond_to?(:user_agent) + assert_equal 'Test', request.user_agent + end + + it 'parses POST params when Content-Type is form-dataish' do + request = Sinatra::Request.new( + 'REQUEST_METHOD' => 'PUT', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'rack.input' => StringIO.new('foo=bar') + ) + assert_equal 'bar', request.params['foo'] + end + + it 'is secure when the url scheme is https' do + request = Sinatra::Request.new('rack.url_scheme' => 'https') + assert request.secure? + end + + it 'is not secure when the url scheme is http' do + request = Sinatra::Request.new('rack.url_scheme' => 'http') + assert !request.secure? + end + + it 'respects X-Forwarded-Proto header for proxied SSL' do + request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https') + assert request.secure? + end + + it 'is possible to marshal params' do + request = Sinatra::Request.new( + 'REQUEST_METHOD' => 'PUT', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'rack.input' => StringIO.new('foo=bar') + ) + Sinatra::Base.new!.send(:indifferent_hash).replace(request.params) + dumped = Marshal.dump(request.params) + assert_equal 'bar', Marshal.load(dumped)['foo'] + end + + it "exposes the preferred type's parameters" do + request = Sinatra::Request.new( + 'HTTP_ACCEPT' => 'image/jpeg; compress=0.25' + ) + assert_equal({ 'compress' => '0.25' }, request.preferred_type.params) + end + + it "makes accept types behave like strings" do + request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/jpeg; compress=0.25') + assert request.accept?('image/jpeg') + assert_equal 'image/jpeg', request.preferred_type.to_s + assert_equal 'image/jpeg; compress=0.25', request.preferred_type.to_s(true) + assert_equal 'image/jpeg', request.preferred_type.to_str + assert_equal 'image', request.preferred_type.split('/').first + + String.instance_methods.each do |method| + next unless "".respond_to? method + assert request.preferred_type.respond_to?(method), "responds to #{method}" + end + end + + it "accepts types when wildcards are requested" do + request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/*') + assert request.accept?('image/jpeg') + end + + it "properly decodes MIME type parameters" do + request = Sinatra::Request.new( + 'HTTP_ACCEPT' => 'image/jpeg;unquoted=0.25;quoted="0.25";chartest="\";,\x"' + ) + expected = { 'unquoted' => '0.25', 'quoted' => '0.25', 'chartest' => '";,x' } + assert_equal(expected, request.preferred_type.params) + end + + it 'accepts */* when HTTP_ACCEPT is not present in the request' do + request = Sinatra::Request.new Hash.new + assert_equal 1, request.accept.size + assert request.accept?('text/html') + assert_equal '*/*', request.preferred_type.to_s + assert_equal '*/*', request.preferred_type.to_s(true) + end + + it 'accepts */* when HTTP_ACCEPT is blank in the request' do + request = Sinatra::Request.new 'HTTP_ACCEPT' => '' + assert_equal 1, request.accept.size + assert request.accept?('text/html') + assert_equal '*/*', request.preferred_type.to_s + assert_equal '*/*', request.preferred_type.to_s(true) + end + + it 'will not accept types not specified in HTTP_ACCEPT when HTTP_ACCEPT is provided' do + request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/json' + assert !request.accept?('text/html') + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/response_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/response_test.rb new file mode 100644 index 000000000..ae4608d79 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/response_test.rb @@ -0,0 +1,63 @@ +# encoding: utf-8 + +require File.expand_path('../helper', __FILE__) + +class ResponseTest < Minitest::Test + setup { @response = Sinatra::Response.new } + + def assert_same_body(a, b) + assert_equal a.to_enum(:each).to_a, b.to_enum(:each).to_a + end + + it "initializes with 200, text/html, and empty body" do + assert_equal 200, @response.status + assert_equal 'text/html', @response['Content-Type'] + assert_equal [], @response.body + end + + it 'uses case insensitive headers' do + @response['content-type'] = 'application/foo' + assert_equal 'application/foo', @response['Content-Type'] + assert_equal 'application/foo', @response['CONTENT-TYPE'] + end + + it 'writes to body' do + @response.body = 'Hello' + @response.write ' World' + assert_equal 'Hello World', @response.body.join + end + + [204, 304].each do |status_code| + it "removes the Content-Type header and body when response status is #{status_code}" do + @response.status = status_code + @response.body = ['Hello World'] + assert_equal [status_code, {}, []], @response.finish + end + end + + it 'Calculates the Content-Length using the bytesize of the body' do + @response.body = ['Hello', 'World!', '✈'] + _, headers, body = @response.finish + assert_equal '14', headers['Content-Length'] + assert_same_body @response.body, body + end + + it 'does not call #to_ary or #inject on the body' do + object = Object.new + def object.inject(*) fail 'called' end + def object.to_ary(*) fail 'called' end + def object.each(*) end + @response.body = object + assert @response.finish + end + + it 'does not nest a Sinatra::Response' do + @response.body = Sinatra::Response.new ["foo"] + assert_same_body @response.body, ["foo"] + end + + it 'does not nest a Rack::Response' do + @response.body = Rack::Response.new ["foo"] + assert_same_body @response.body, ["foo"] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/result_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/result_test.rb new file mode 100644 index 000000000..dd1eb818f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/result_test.rb @@ -0,0 +1,76 @@ +require File.expand_path('../helper', __FILE__) + +class ResultTest < Minitest::Test + it "sets response.body when result is a String" do + mock_app { get('/') { 'Hello World' } } + + get '/' + assert ok? + assert_equal 'Hello World', body + end + + it "sets response.body when result is an Array of Strings" do + mock_app { get('/') { ['Hello', 'World'] } } + + get '/' + assert ok? + assert_equal 'HelloWorld', body + end + + it "sets response.body when result responds to #each" do + mock_app do + get('/') do + res = lambda { 'Hello World' } + def res.each ; yield call ; end + return res + end + end + + get '/' + assert ok? + assert_equal 'Hello World', body + end + + it "sets response.body to [] when result is nil" do + mock_app { get( '/') { nil } } + + get '/' + assert ok? + assert_equal '', body + end + + it "sets status, headers, and body when result is a Rack response tuple" do + mock_app { + get('/') { [203, {'Content-Type' => 'foo/bar'}, 'Hello World'] } + } + + get '/' + assert_equal 203, status + assert_equal 'foo/bar', response['Content-Type'] + assert_equal 'Hello World', body + end + + it "sets status and body when result is a two-tuple" do + mock_app { get('/') { [409, 'formula of'] } } + + get '/' + assert_equal 409, status + assert_equal 'formula of', body + end + + it "raises a ArgumentError when result is a non two or three tuple Array" do + mock_app { + get('/') { [409, 'formula of', 'something else', 'even more'] } + } + + assert_raises(ArgumentError) { get '/' } + end + + it "sets status when result is a Fixnum status code" do + mock_app { get('/') { 205 } } + + get '/' + assert_equal 205, status + assert_equal '', body + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/route_added_hook_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/route_added_hook_test.rb new file mode 100644 index 000000000..2356166f3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/route_added_hook_test.rb @@ -0,0 +1,59 @@ +require File.expand_path('../helper', __FILE__) + +module RouteAddedTest + @routes, @procs = [], [] + def self.routes ; @routes ; end + def self.procs ; @procs ; end + def self.route_added(verb, path, proc) + @routes << [verb, path] + @procs << proc + end +end + +class RouteAddedHookTest < Minitest::Test + setup do + RouteAddedTest.routes.clear + RouteAddedTest.procs.clear + end + + it "should be notified of an added route" do + mock_app(Class.new(Sinatra::Base)) do + register RouteAddedTest + get('/') {} + end + + assert_equal [["GET", "/"], ["HEAD", "/"]], + RouteAddedTest.routes + end + + it "should include hooks from superclass" do + a = Class.new(Class.new(Sinatra::Base)) + b = Class.new(a) + + a.register RouteAddedTest + b.class_eval { post("/sub_app_route") {} } + + assert_equal [["POST", "/sub_app_route"]], + RouteAddedTest.routes + end + + it "should only run once per extension" do + mock_app(Class.new(Sinatra::Base)) do + register RouteAddedTest + register RouteAddedTest + get('/') {} + end + + assert_equal [["GET", "/"], ["HEAD", "/"]], + RouteAddedTest.routes + end + + it "should pass route blocks as an argument" do + mock_app(Class.new(Sinatra::Base)) do + register RouteAddedTest + get('/') {} + end + + assert_kind_of Proc, RouteAddedTest.procs.first + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/routing_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/routing_test.rb new file mode 100644 index 000000000..695b1d68d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/routing_test.rb @@ -0,0 +1,1412 @@ +# I like coding: UTF-8 +require File.expand_path('../helper', __FILE__) + +# Helper method for easy route pattern matching testing +def route_def(pattern) + mock_app { get(pattern) { } } +end + +class RegexpLookAlike + class MatchData + def captures + ["this", "is", "a", "test"] + end + end + + def match(string) + ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/" + end + + def keys + ["one", "two", "three", "four"] + end +end + +class RoutingTest < Minitest::Test + %w[get put post delete options patch link unlink].each do |verb| + it "defines #{verb.upcase} request handlers with #{verb}" do + mock_app { + send verb, '/hello' do + 'Hello World' + end + } + + request = Rack::MockRequest.new(@app) + response = request.request(verb.upcase, '/hello', {}) + assert response.ok? + assert_equal 'Hello World', response.body + end + end + + it "defines HEAD request handlers with HEAD" do + mock_app { + head '/hello' do + response['X-Hello'] = 'World!' + 'remove me' + end + } + + request = Rack::MockRequest.new(@app) + response = request.request('HEAD', '/hello', {}) + assert response.ok? + assert_equal 'World!', response['X-Hello'] + assert_equal '', response.body + end + + it "404s when no route satisfies the request" do + mock_app { + get('/foo') { } + } + get '/bar' + assert_equal 404, status + end + + it "404s and sets X-Cascade header when no route satisfies the request" do + mock_app { + get('/foo') { } + } + get '/bar' + assert_equal 404, status + assert_equal 'pass', response.headers['X-Cascade'] + end + + it "404s and does not set X-Cascade header when no route satisfies the request and x_cascade has been disabled" do + mock_app { + disable :x_cascade + get('/foo') { } + } + get '/bar' + assert_equal 404, status + assert_equal nil, response.headers['X-Cascade'] + end + + + it "allows using unicode" do + mock_app do + get('/föö') { } + end + get '/f%C3%B6%C3%B6' + assert_equal 200, status + end + + it "it handles encoded slashes correctly" do + mock_app { + set :protection, :except => :path_traversal + get("/:a") { |a| a } + } + get '/foo%2Fbar' + assert_equal 200, status + assert_body "foo/bar" + end + + it "overrides the content-type in error handlers" do + mock_app { + before { content_type 'text/plain' } + error Sinatra::NotFound do + content_type "text/html" + "

Not Found

" + end + } + + get '/foo' + assert_equal 404, status + assert_equal 'text/html;charset=utf-8', response["Content-Type"] + assert_equal "

Not Found

", response.body + end + + it "recalculates body length correctly for 404 response" do + mock_app { + get '/' do + @response["Content-Length"] = "30" + raise Sinatra::NotFound + end + } + + get "/" + assert_equal "18", response["Content-Length"] + assert_equal 404, status + end + + it 'matches empty PATH_INFO to "/" if no route is defined for ""' do + mock_app do + get '/' do + 'worked' + end + end + + get '/', {}, "PATH_INFO" => "" + assert ok? + assert_equal 'worked', body + end + + it 'matches empty PATH_INFO to "" if a route is defined for ""' do + mock_app do + disable :protection + + get '/' do + 'did not work' + end + + get '' do + 'worked' + end + end + + get '/', {}, "PATH_INFO" => "" + assert ok? + assert_equal 'worked', body + end + + it 'takes multiple definitions of a route' do + mock_app { + user_agent(/Foo/) + get '/foo' do + 'foo' + end + + get '/foo' do + 'not foo' + end + } + + get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo' + assert ok? + assert_equal 'foo', body + + get '/foo' + assert ok? + assert_equal 'not foo', body + end + + it "exposes params with indifferent hash" do + mock_app { + get '/:foo' do + assert_equal 'bar', params['foo'] + assert_equal 'bar', params[:foo] + 'well, alright' + end + } + get '/bar' + assert_equal 'well, alright', body + end + + it "merges named params and query string params in params" do + mock_app { + get '/:foo' do + assert_equal 'bar', params['foo'] + assert_equal 'biz', params['baz'] + end + } + get '/bar?baz=biz' + assert ok? + end + + it "supports named params like /hello/:person" do + mock_app { + get '/hello/:person' do + "Hello #{params['person']}" + end + } + get '/hello/Frank' + assert_equal 'Hello Frank', body + end + + it "supports optional named params like /?:foo?/?:bar?" do + mock_app { + get '/?:foo?/?:bar?' do + "foo=#{params[:foo]};bar=#{params[:bar]}" + end + } + + get '/hello/world' + assert ok? + assert_equal "foo=hello;bar=world", body + + get '/hello' + assert ok? + assert_equal "foo=hello;bar=", body + + get '/' + assert ok? + assert_equal "foo=;bar=", body + end + + it "supports named captures like %r{/hello/(?[^/?#]+)} on Ruby >= 1.9" do + next if RUBY_VERSION < '1.9' + mock_app { + get Regexp.new('/hello/(?[^/?#]+)') do + "Hello #{params['person']}" + end + } + get '/hello/Frank' + assert_equal 'Hello Frank', body + end + + it "supports optional named captures like %r{/page(?.[^/?#]+)?} on Ruby >= 1.9" do + next if RUBY_VERSION < '1.9' + mock_app { + get Regexp.new('/page(?.[^/?#]+)?') do + "format=#{params[:format]}" + end + } + + get '/page.html' + assert ok? + assert_equal "format=.html", body + + get '/page.xml' + assert ok? + assert_equal "format=.xml", body + + get '/page' + assert ok? + assert_equal "format=", body + end + + it 'does not concatenate params with the same name' do + mock_app { get('/:foo') { params[:foo] } } + get '/a?foo=b' + assert_body 'a' + end + + it "supports single splat params like /*" do + mock_app { + get '/*' do + assert params['splat'].kind_of?(Array) + params['splat'].join "\n" + end + } + + get '/foo' + assert_equal "foo", body + + get '/foo/bar/baz' + assert_equal "foo/bar/baz", body + end + + it "supports mixing multiple splat params like /*/foo/*/*" do + mock_app { + get '/*/foo/*/*' do + assert params['splat'].kind_of?(Array) + params['splat'].join "\n" + end + } + + get '/bar/foo/bling/baz/boom' + assert_equal "bar\nbling\nbaz/boom", body + + get '/bar/foo/baz' + assert not_found? + end + + it "supports mixing named and splat params like /:foo/*" do + mock_app { + get '/:foo/*' do + assert_equal 'foo', params['foo'] + assert_equal ['bar/baz'], params['splat'] + end + } + + get '/foo/bar/baz' + assert ok? + end + + it "matches a dot ('.') as part of a named param" do + mock_app { + get '/:foo/:bar' do + params[:foo] + end + } + + get '/user@example.com/name' + assert_equal 200, response.status + assert_equal 'user@example.com', body + end + + it "matches a literal dot ('.') outside of named params" do + mock_app { + get '/:file.:ext' do + assert_equal 'pony', params[:file] + assert_equal 'jpg', params[:ext] + 'right on' + end + } + + get '/pony.jpg' + assert_equal 200, response.status + assert_equal 'right on', body + end + + it "literally matches dot in paths" do + route_def '/test.bar' + + get '/test.bar' + assert ok? + get 'test0bar' + assert not_found? + end + + it "literally matches dollar sign in paths" do + route_def '/test$/' + + get '/test$/' + assert ok? + end + + it "literally matches plus sign in paths" do + route_def '/te+st/' + + get '/te%2Bst/' + assert ok? + get '/teeeeeeest/' + assert not_found? + end + + it "does not convert plus sign into space as the value of a named param" do + mock_app do + get '/:test' do + params["test"] + end + end + get '/bob+ross' + assert ok? + assert_equal 'bob+ross', body + end + + it "literally matches parens in paths" do + route_def '/test(bar)/' + + get '/test(bar)/' + assert ok? + end + + it "supports basic nested params" do + mock_app { + get '/hi' do + params["person"]["name"] + end + } + + get "/hi?person[name]=John+Doe" + assert ok? + assert_equal "John Doe", body + end + + it "exposes nested params with indifferent hash" do + mock_app { + get '/testme' do + assert_equal 'baz', params['bar']['foo'] + assert_equal 'baz', params['bar'][:foo] + 'well, alright' + end + } + get '/testme?bar[foo]=baz' + assert_equal 'well, alright', body + end + + it "exposes params nested within arrays with indifferent hash" do + mock_app { + get '/testme' do + assert_equal 'baz', params['bar'][0]['foo'] + assert_equal 'baz', params['bar'][0][:foo] + 'well, alright' + end + } + get '/testme?bar[][foo]=baz' + assert_equal 'well, alright', body + end + + it "supports arrays within params" do + mock_app { + get '/foo' do + assert_equal ['A', 'B'], params['bar'] + 'looks good' + end + } + get '/foo?bar[]=A&bar[]=B' + assert ok? + assert_equal 'looks good', body + end + + it "supports deeply nested params" do + expected_params = { + "emacs" => { + "map" => { "goto-line" => "M-g g" }, + "version" => "22.3.1" + }, + "browser" => { + "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}}, + "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}} + }, + "paste" => {"name"=>"hello world", "syntax"=>"ruby"} + } + mock_app { + get '/foo' do + assert_equal expected_params, params + 'looks good' + end + } + get '/foo', expected_params + assert ok? + assert_equal 'looks good', body + end + + it "preserves non-nested params" do + mock_app { + get '/foo' do + assert_equal "2", params["article_id"] + assert_equal "awesome", params['comment']['body'] + assert_nil params['comment[body]'] + 'looks good' + end + } + + get '/foo?article_id=2&comment[body]=awesome' + assert ok? + assert_equal 'looks good', body + end + + it "matches paths that include spaces encoded with %20" do + mock_app { + get '/path with spaces' do + 'looks good' + end + } + + get '/path%20with%20spaces' + assert ok? + assert_equal 'looks good', body + end + + it "matches paths that include spaces encoded with +" do + mock_app { + get '/path with spaces' do + 'looks good' + end + } + + get '/path+with+spaces' + assert ok? + assert_equal 'looks good', body + end + + it "matches paths that include ampersands" do + mock_app { + get '/:name' do + 'looks good' + end + } + + get '/foo&bar' + assert ok? + assert_equal 'looks good', body + end + + it "URL decodes named parameters and splats" do + mock_app { + get '/:foo/*' do + assert_equal 'hello world', params['foo'] + assert_equal ['how are you'], params['splat'] + nil + end + } + + get '/hello%20world/how%20are%20you' + assert ok? + end + + it 'supports regular expressions' do + mock_app { + get(/^\/foo...\/bar$/) do + 'Hello World' + end + } + + get '/foooom/bar' + assert ok? + assert_equal 'Hello World', body + end + + it 'makes regular expression captures available in params[:captures]' do + mock_app { + get(/^\/fo(.*)\/ba(.*)/) do + assert_equal ['orooomma', 'f'], params[:captures] + 'right on' + end + } + + get '/foorooomma/baf' + assert ok? + assert_equal 'right on', body + end + + it 'supports regular expression look-alike routes' do + mock_app { + get(RegexpLookAlike.new) do + assert_equal 'this', params[:one] + assert_equal 'is', params[:two] + assert_equal 'a', params[:three] + assert_equal 'test', params[:four] + 'right on' + end + } + + get '/this/is/a/test/' + assert ok? + assert_equal 'right on', body + end + + it 'raises a TypeError when pattern is not a String or Regexp' do + assert_raises(TypeError) { + mock_app { get(42){} } + } + end + + it "returns response immediately on halt" do + mock_app { + get '/' do + halt 'Hello World' + 'Boo-hoo World' + end + } + + get '/' + assert ok? + assert_equal 'Hello World', body + end + + it "halts with a response tuple" do + mock_app { + get '/' do + halt 295, {'Content-Type' => 'text/plain'}, 'Hello World' + end + } + + get '/' + assert_equal 295, status + assert_equal 'text/plain', response['Content-Type'] + assert_equal 'Hello World', body + end + + it "halts with an array of strings" do + mock_app { + get '/' do + halt %w[Hello World How Are You] + end + } + + get '/' + assert_equal 'HelloWorldHowAreYou', body + end + + it 'sets response.status with halt' do + status_was = nil + mock_app do + after { status_was = status } + get('/') { halt 500, 'error' } + end + get '/' + assert_status 500 + assert_equal 500, status_was + end + + it "transitions to the next matching route on pass" do + mock_app { + get '/:foo' do + pass + 'Hello Foo' + end + + get '/*' do + assert !params.include?('foo') + 'Hello World' + end + } + + get '/bar' + assert ok? + assert_equal 'Hello World', body + end + + it "transitions to 404 when passed and no subsequent route matches" do + mock_app { + get '/:foo' do + pass + 'Hello Foo' + end + } + + get '/bar' + assert not_found? + end + + it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do + mock_app { + get '/:foo' do + pass + 'Hello Foo' + end + + get '/bar' do + 'Hello Bar' + end + } + + get '/foo' + assert not_found? + assert_equal 'pass', response.headers['X-Cascade'] + end + + it "uses optional block passed to pass as route block if no other route is found" do + mock_app { + get "/" do + pass do + "this" + end + "not this" + end + } + + get "/" + assert ok? + assert "this", body + end + + it "uses optional block passed to pass as route block if no other route is found and superclass has non-matching routes" do + base = Class.new(Sinatra::Base) + base.get('/foo') { 'foo in baseclass' } + + mock_app(base) { + get "/" do + pass do + "this" + end + "not this" + end + } + + get "/" + assert_equal 200, status + assert "this", body + end + + it "passes when matching condition returns false" do + mock_app { + condition { params[:foo] == 'bar' } + get '/:foo' do + 'Hello World' + end + } + + get '/bar' + assert ok? + assert_equal 'Hello World', body + + get '/foo' + assert not_found? + end + + it "does not pass when matching condition returns nil" do + mock_app { + condition { nil } + get '/:foo' do + 'Hello World' + end + } + + get '/bar' + assert ok? + assert_equal 'Hello World', body + end + + it "passes to next route when condition calls pass explicitly" do + mock_app { + condition { pass unless params[:foo] == 'bar' } + get '/:foo' do + 'Hello World' + end + } + + get '/bar' + assert ok? + assert_equal 'Hello World', body + + get '/foo' + assert not_found? + end + + it "passes to the next route when host_name does not match" do + mock_app { + host_name 'example.com' + get '/foo' do + 'Hello World' + end + } + get '/foo' + assert not_found? + + get '/foo', {}, { 'HTTP_HOST' => 'example.com' } + assert_equal 200, status + assert_equal 'Hello World', body + end + + it "passes to the next route when user_agent does not match" do + mock_app { + user_agent(/Foo/) + get '/foo' do + 'Hello World' + end + } + get '/foo' + assert not_found? + + get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } + assert_equal 200, status + assert_equal 'Hello World', body + end + + it "treats missing user agent like an empty string" do + mock_app do + user_agent(/.*/) + get '/' do + "Hello World" + end + end + get '/' + assert_equal 200, status + assert_equal 'Hello World', body + end + + it "makes captures in user agent pattern available in params[:agent]" do + mock_app { + user_agent(/Foo (.*)/) + get '/foo' do + 'Hello ' + params[:agent].first + end + } + get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } + assert_equal 200, status + assert_equal 'Hello Bar', body + end + + it 'matches mime_types with dots, hyphens and plus signs' do + mime_types = %w( + application/atom+xml + application/ecmascript + application/EDI-X12 + application/EDIFACT + application/json + application/javascript + application/octet-stream + application/ogg + application/pdf + application/postscript + application/rdf+xml + application/rss+xml + application/soap+xml + application/font-woff + application/xhtml+xml + application/xml + application/xml-dtd + application/xop+xml + application/zip + application/gzip + audio/basic + audio/L24 + audio/mp4 + audio/mpeg + audio/ogg + audio/vorbis + audio/vnd.rn-realaudio + audio/vnd.wave + audio/webm + image/gif + image/jpeg + image/pjpeg + image/png + image/svg+xml + image/tiff + image/vnd.microsoft.icon + message/http + message/imdn+xml + message/partial + message/rfc822 + model/example + model/iges + model/mesh + model/vrml + model/x3d+binary + model/x3d+vrml + model/x3d+xml + multipart/mixed + multipart/alternative + multipart/related + multipart/form-data + multipart/signed + multipart/encrypted + text/cmd + text/css + text/csv + text/html + text/javascript + application/javascript + text/plain + text/vcard + text/xml + video/mpeg + video/mp4 + video/ogg + video/quicktime + video/webm + video/x-matroska + video/x-ms-wmv + video/x-flv + application/vnd.oasis.opendocument.text + application/vnd.oasis.opendocument.spreadsheet + application/vnd.oasis.opendocument.presentation + application/vnd.oasis.opendocument.graphics + application/vnd.ms-excel + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + application/vnd.ms-powerpoint + application/vnd.openxmlformats-officedocument.presentationml.presentation + application/vnd.openxmlformats-officedocument.wordprocessingml.document + application/vnd.mozilla.xul+xml + application/vnd.google-earth.kml+xml + application/x-deb + application/x-dvi + application/x-font-ttf + application/x-javascript + application/x-latex + application/x-mpegURL + application/x-rar-compressed + application/x-shockwave-flash + application/x-stuffit + application/x-tar + application/x-www-form-urlencoded + application/x-xpinstall + audio/x-aac + audio/x-caf + image/x-xcf + text/x-gwt-rpc + text/x-jquery-tmpl + application/x-pkcs12 + application/x-pkcs12 + application/x-pkcs7-certificates + application/x-pkcs7-certificates + application/x-pkcs7-certreqresp + application/x-pkcs7-mime + application/x-pkcs7-mime + application/x-pkcs7-signature + ) + + mime_types.each { |mime_type| assert mime_type.match(Sinatra::Request::HEADER_VALUE_WITH_PARAMS) } + end + + it "filters by accept header" do + mock_app { + get '/', :provides => :xml do + env['HTTP_ACCEPT'] + end + get '/foo', :provides => :html do + env['HTTP_ACCEPT'] + end + get '/stream', :provides => 'text/event-stream' do + env['HTTP_ACCEPT'] + end + } + + get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } + assert ok? + assert_equal 'application/xml', body + assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] + + get '/', {}, {} + assert ok? + assert_equal '', body + assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] + + get '/', {}, { 'HTTP_ACCEPT' => '*/*' } + assert ok? + assert_equal '*/*', body + assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] + + get '/', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } + assert !ok? + + get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } + assert ok? + assert_equal 'text/html;q=0.9', body + + get '/foo', {}, { 'HTTP_ACCEPT' => '' } + assert ok? + assert_equal '', body + + get '/foo', {}, { 'HTTP_ACCEPT' => '*/*' } + assert ok? + assert_equal '*/*', body + + get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' } + assert !ok? + + get '/stream', {}, { 'HTTP_ACCEPT' => 'text/event-stream' } + assert ok? + assert_equal 'text/event-stream', body + + get '/stream', {}, { 'HTTP_ACCEPT' => '' } + assert ok? + assert_equal '', body + + get '/stream', {}, { 'HTTP_ACCEPT' => '*/*' } + assert ok? + assert_equal '*/*', body + + get '/stream', {}, { 'HTTP_ACCEPT' => 'application/xml' } + assert !ok? + end + + it "filters by current Content-Type" do + mock_app do + before('/txt') { content_type :txt } + get('*', :provides => :txt) { 'txt' } + + before('/html') { content_type :html } + get('*', :provides => :html) { 'html' } + end + + get '/', {}, { 'HTTP_ACCEPT' => '*/*' } + assert ok? + assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] + assert_body 'txt' + + get '/txt', {}, { 'HTTP_ACCEPT' => 'text/plain' } + assert ok? + assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] + assert_body 'txt' + + get '/', {}, { 'HTTP_ACCEPT' => 'text/html' } + assert ok? + assert_equal 'text/html;charset=utf-8', response.headers['Content-Type'] + assert_body 'html' + end + + it "allows multiple mime types for accept header" do + types = ['image/jpeg', 'image/pjpeg'] + + mock_app { + get '/', :provides => types do + env['HTTP_ACCEPT'] + end + } + + types.each do |type| + get '/', {}, { 'HTTP_ACCEPT' => type } + assert ok? + assert_equal type, body + assert_equal type, response.headers['Content-Type'] + end + end + + it 'respects user agent preferences for the content type' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' } + assert_body 'text/html;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' } + assert_body 'image/png' + end + + it 'accepts generic types' do + mock_app do + get('/', :provides => :xml) { content_type } + get('/') { 'no match' } + end + get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' } + assert_body 'no match' + get '/', {}, { 'HTTP_ACCEPT' => 'application/*' } + assert_body 'application/xml;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => '*/*' } + assert_body 'application/xml;charset=utf-8' + end + + it 'prefers concrete over partly generic types' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' } + assert_body 'text/html;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' } + assert_body 'image/png' + end + + it 'prefers concrete over fully generic types' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' } + assert_body 'text/html;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' } + assert_body 'image/png' + end + + it 'prefers partly generic over fully generic types' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' } + assert_body 'text/html;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' } + assert_body 'image/png' + end + + it 'respects quality with generic types' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' } + assert_body 'image/png' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' } + assert_body 'text/html;charset=utf-8' + end + + it 'supplies a default quality of 1.0' do + mock_app { get('/', :provides => [:png, :html]) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*' } + assert_body 'text/html;charset=utf-8' + end + + it 'orders types with equal quality by parameter count' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + lo_png = 'image/png;q=0.5' + hi_png = 'image/png;q=0.5;profile=FOGRA40;gamma=0.8' + jpeg = 'image/jpeg;q=0.5;compress=0.25' + + get '/', {}, { 'HTTP_ACCEPT' => "#{lo_png}, #{jpeg}" } + assert_body 'image/jpeg' + get '/', {}, { 'HTTP_ACCEPT' => "#{hi_png}, #{jpeg}" } + assert_body 'image/png' + end + + it 'ignores the quality parameter when ordering by parameter count' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + lo_png = 'image/png' + hi_png = 'image/png;profile=FOGRA40;gamma=0.8' + jpeg = 'image/jpeg;q=1.0;compress=0.25' + + get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{lo_png}" } + assert_body 'image/jpeg' + get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{hi_png}" } + assert_body 'image/png' + end + + it 'properly handles quoted strings in parameters' do + mock_app do + get('/', :provides => [:png, :jpg]) { content_type } + end + + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5;profile=",image/jpeg,"' } + assert_body 'image/png' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x=";q=1.0"' } + assert_body 'image/png' + get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x="\";q=1.0"' } + assert_body 'image/png' + end + + it 'accepts both text/javascript and application/javascript for js' do + mock_app { get('/', :provides => :js) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' } + assert_body 'application/javascript;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' } + assert_body 'text/javascript;charset=utf-8' + end + + it 'accepts both text/xml and application/xml for xml' do + mock_app { get('/', :provides => :xml) { content_type }} + get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } + assert_body 'application/xml;charset=utf-8' + get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' } + assert_body 'text/xml;charset=utf-8' + end + + it 'passes a single url param as block parameters when one param is specified' do + mock_app { + get '/:foo' do |foo| + assert_equal 'bar', foo + end + } + + get '/bar' + assert ok? + end + + it 'passes multiple params as block parameters when many are specified' do + mock_app { + get '/:foo/:bar/:baz' do |foo, bar, baz| + assert_equal 'abc', foo + assert_equal 'def', bar + assert_equal 'ghi', baz + end + } + + get '/abc/def/ghi' + assert ok? + end + + it 'passes regular expression captures as block parameters' do + mock_app { + get(/^\/fo(.*)\/ba(.*)/) do |foo, bar| + assert_equal 'orooomma', foo + assert_equal 'f', bar + 'looks good' + end + } + + get '/foorooomma/baf' + assert ok? + assert_equal 'looks good', body + end + + it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do + mock_app { + get '/*/foo/*/*' do |foo, bar, baz| + assert_equal 'bar', foo + assert_equal 'bling', bar + assert_equal 'baz/boom', baz + 'looks good' + end + } + + get '/bar/foo/bling/baz/boom' + assert ok? + assert_equal 'looks good', body + end + + it 'raises an ArgumentError with block arity > 1 and too many values' do + mock_app do + get '/:foo/:bar/:baz' do |foo, bar| + 'quux' + end + end + + assert_raises(ArgumentError) { get '/a/b/c' } + end + + it 'raises an ArgumentError with block param arity > 1 and too few values' do + mock_app { + get '/:foo/:bar' do |foo, bar, baz| + 'quux' + end + } + + assert_raises(ArgumentError) { get '/a/b' } + end + + it 'succeeds if no block parameters are specified' do + mock_app { + get '/:foo/:bar' do + 'quux' + end + } + + get '/a/b' + assert ok? + assert_equal 'quux', body + end + + it 'passes all params with block param arity -1 (splat args)' do + mock_app { + get '/:foo/:bar' do |*args| + args.join + end + } + + get '/a/b' + assert ok? + assert_equal 'ab', body + end + + it 'allows custom route-conditions to be set via route options' do + protector = Module.new { + def protect(*args) + condition { + unless authorize(params["user"], params["password"]) + halt 403, "go away" + end + } + end + } + + mock_app { + register protector + + helpers do + def authorize(username, password) + username == "foo" && password == "bar" + end + end + + get "/", :protect => true do + "hey" + end + } + + get "/" + assert forbidden? + assert_equal "go away", body + + get "/", :user => "foo", :password => "bar" + assert ok? + assert_equal "hey", body + end + + # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block + # param arity is lax: declaring a mismatched number of block params results + # in a warning. Under 1.9, block param arity is strict: mismatched block + # arity raises an ArgumentError. + + if RUBY_VERSION >= '1.9' + + it 'raises an ArgumentError with block param arity 1 and no values' do + mock_app { + get '/foo' do |foo| + 'quux' + end + } + + assert_raises(ArgumentError) { get '/foo' } + end + + it 'raises an ArgumentError with block param arity 1 and too many values' do + mock_app { + get '/:foo/:bar/:baz' do |foo| + 'quux' + end + } + + assert_raises(ArgumentError) { get '/a/b/c' } + end + + else + + it 'does not raise an ArgumentError with block param arity 1 and no values' do + mock_app { + get '/foo' do |foo| + 'quux' + end + } + + silence_warnings { get '/foo' } + assert ok? + assert_equal 'quux', body + end + + it 'does not raise an ArgumentError with block param arity 1 and too many values' do + mock_app { + get '/:foo/:bar/:baz' do |foo| + 'quux' + end + } + + silence_warnings { get '/a/b/c' } + assert ok? + assert_equal 'quux', body + end + + end + + it "matches routes defined in superclasses" do + base = Class.new(Sinatra::Base) + base.get('/foo') { 'foo in baseclass' } + + mock_app(base) { + get('/bar') { 'bar in subclass' } + } + + get '/foo' + assert ok? + assert_equal 'foo in baseclass', body + + get '/bar' + assert ok? + assert_equal 'bar in subclass', body + end + + it "matches routes in subclasses before superclasses" do + base = Class.new(Sinatra::Base) + base.get('/foo') { 'foo in baseclass' } + base.get('/bar') { 'bar in baseclass' } + + mock_app(base) { + get('/foo') { 'foo in subclass' } + } + + get '/foo' + assert ok? + assert_equal 'foo in subclass', body + + get '/bar' + assert ok? + assert_equal 'bar in baseclass', body + end + + it "adds hostname condition when it is in options" do + mock_app { + get '/foo', :host => 'host' do + 'foo' + end + } + + get '/foo' + assert not_found? + end + + it 'allows using call to fire another request internally' do + mock_app do + get '/foo' do + status, headers, body = call env.merge("PATH_INFO" => '/bar') + [status, headers, body.each.map(&:upcase)] + end + + get '/bar' do + "bar" + end + end + + get '/foo' + assert ok? + assert_body "BAR" + end + + it 'plays well with other routing middleware' do + middleware = Sinatra.new + inner_app = Sinatra.new { get('/foo') { 'hello' } } + builder = Rack::Builder.new do + use middleware + map('/test') { run inner_app } + end + + @app = builder.to_app + get '/test/foo' + assert ok? + assert_body 'hello' + end + + it 'returns the route signature' do + signature = list = nil + + mock_app do + signature = post('/') { } + list = routes['POST'] + end + + assert_equal Array, signature.class + assert_equal 4, signature.length + assert list.include?(signature) + end + + it "sets env['sinatra.route'] to the matched route" do + mock_app do + after do + assert_equal 'GET /users/:id/status', env['sinatra.route'] + end + get('/users/:id/status') { 'ok' } + end + get '/users/1/status' + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sass_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sass_test.rb new file mode 100644 index 000000000..a8cbf6fe1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sass_test.rb @@ -0,0 +1,115 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'sass' + +class SassTest < Minitest::Test + def sass_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set options + get('/', &block) + end + get '/' + end + + it 'renders inline Sass strings' do + sass_app { sass "#sass\n :background-color white\n" } + assert ok? + assert_equal "#sass {\n background-color: white; }\n", body + end + + it 'defaults content type to css' do + sass_app { sass "#sass\n :background-color white\n" } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + sass_app do + content_type :html + sass "#sass\n :background-color white\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + sass_app(:sass => { :content_type => 'html' }) { + sass "#sass\n :background-color white\n" + } + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders .sass files in views path' do + sass_app { sass :hello } + assert ok? + assert_equal "#sass {\n background-color: white; }\n", body + end + + it 'ignores the layout option' do + sass_app { sass :hello, :layout => :layout2 } + assert ok? + assert_equal "#sass {\n background-color: white; }\n", body + end + + it "raises error if template not found" do + mock_app { get('/') { sass :no_such_template } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes SASS options to the Sass engine" do + sass_app do + sass( + "#sass\n :background-color white\n :color black\n", + :style => :compact + ) + end + assert ok? + assert_equal("#sass { background-color: white; color: black; }\n", body) + end + + it "passes default SASS options to the Sass engine" do + mock_app do + set :sass, {:style => :compact} # default Sass style is :nested + get('/') { sass("#sass\n :background-color white\n :color black\n") } + end + get '/' + assert ok? + assert_equal "#sass { background-color: white; color: black; }\n", body + end + + it "merges the default SASS options with the overrides" do + mock_app do + # default Sass attribute_syntax is :normal (with : in front) + set :sass, {:style => :compact, :attribute_syntax => :alternate } + get('/') { sass("#sass\n background-color: white\n color: black\n") } + get('/raised') do + # retains global attribute_syntax settings + sass( + "#sass\n :background-color white\n :color black\n", + :style => :expanded + ) + end + get('/expanded_normal') do + sass( + "#sass\n :background-color white\n :color black\n", + :style => :expanded, :attribute_syntax => :normal + ) + end + end + get '/' + assert ok? + assert_equal "#sass { background-color: white; color: black; }\n", body + assert_raises(Sass::SyntaxError) { get('/raised') } + get '/expanded_normal' + assert ok? + assert_equal "#sass {\n background-color: white;\n color: black;\n}\n", + body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping sass tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/scss_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/scss_test.rb new file mode 100644 index 000000000..1db2effd7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/scss_test.rb @@ -0,0 +1,88 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'sass' + +class ScssTest < Minitest::Test + def scss_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set options + get('/', &block) + end + get '/' + end + + it 'renders inline Scss strings' do + scss_app { scss "#scss {\n background-color: white; }\n" } + assert ok? + assert_equal "#scss {\n background-color: white; }\n", body + end + + it 'defaults content type to css' do + scss_app { scss "#scss {\n background-color: white; }\n" } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + scss_app do + content_type :html + scss "#scss {\n background-color: white; }\n" + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + scss_app(:scss => { :content_type => 'html' }) { + scss "#scss {\n background-color: white; }\n" + } + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders .scss files in views path' do + scss_app { scss :hello } + assert ok? + assert_equal "#scss {\n background-color: white; }\n", body + end + + it 'ignores the layout option' do + scss_app { scss :hello, :layout => :layout2 } + assert ok? + assert_equal "#scss {\n background-color: white; }\n", body + end + + it "raises error if template not found" do + mock_app { get('/') { scss(:no_such_template) } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes scss options to the scss engine" do + scss_app do + scss( + "#scss {\n background-color: white;\n color: black\n}", + :style => :compact + ) + end + assert ok? + assert_equal "#scss { background-color: white; color: black; }\n", body + end + + it "passes default scss options to the scss engine" do + mock_app do + set :scss, {:style => :compact} # default scss style is :nested + get('/') { + scss("#scss {\n background-color: white;\n color: black;\n}") + } + end + get '/' + assert ok? + assert_equal "#scss { background-color: white; color: black; }\n", body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping scss tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/server_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/server_test.rb new file mode 100644 index 000000000..cdd243bae --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/server_test.rb @@ -0,0 +1,56 @@ +require File.expand_path('../helper', __FILE__) +require 'stringio' + +module Rack::Handler + class Mock + extend Minitest::Assertions + # Allow assertions in request context + def self.assertions + @assertions ||= 0 + end + + def self.assertions= assertions + @assertions = assertions + end + + def self.run(app, options={}) + assert(app < Sinatra::Base) + assert_equal 9001, options[:Port] + assert_equal 'foo.local', options[:Host] + yield new + end + + def stop + end + end + + register 'mock', 'Rack::Handler::Mock' +end + +class ServerTest < Minitest::Test + setup do + mock_app do + set :server, 'mock' + set :bind, 'foo.local' + set :port, 9001 + end + $stderr = StringIO.new + end + + def teardown + $stderr = STDERR + end + + it "locates the appropriate Rack handler and calls ::run" do + @app.run! + end + + it "sets options on the app before running" do + @app.run! :sessions => true + assert @app.sessions? + end + + it "falls back on the next server handler when not found" do + @app.run! :server => %w[foo bar mock] + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/settings_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/settings_test.rb new file mode 100644 index 000000000..7ec03c681 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/settings_test.rb @@ -0,0 +1,582 @@ +require File.expand_path('../helper', __FILE__) + +class SettingsTest < Minitest::Test + setup do + @base = Sinatra.new(Sinatra::Base) + @base.set :environment => :foo, :app_file => nil + + @application = Sinatra.new(Sinatra::Application) + @application.set :environment => :foo, :app_file => nil + end + + it 'sets settings to literal values' do + @base.set(:foo, 'bar') + assert @base.respond_to?(:foo) + assert_equal 'bar', @base.foo + end + + it 'sets settings to Procs' do + @base.set(:foo, Proc.new { 'baz' }) + assert @base.respond_to?(:foo) + assert_equal 'baz', @base.foo + end + + it 'sets settings using a block' do + @base.set(:foo){ 'baz' } + assert @base.respond_to?(:foo) + assert_equal 'baz', @base.foo + end + + it 'raises an error with a value and a block' do + assert_raises ArgumentError do + @base.set(:fiz, 'boom!'){ 'baz' } + end + assert !@base.respond_to?(:fiz) + end + + it 'raises an error without value and block' do + assert_raises(ArgumentError) { @base.set(:fiz) } + assert !@base.respond_to?(:fiz) + end + + it 'allows setting a value to the app class' do + @base.set :base, @base + assert @base.respond_to?(:base) + assert_equal @base, @base.base + end + + it 'raises an error with the app class as value and a block' do + assert_raises ArgumentError do + @base.set(:fiz, @base) { 'baz' } + end + assert !@base.respond_to?(:fiz) + end + + it "sets multiple settings with a Hash" do + @base.set :foo => 1234, + :bar => 'Hello World', + :baz => Proc.new { 'bizzle' } + assert_equal 1234, @base.foo + assert_equal 'Hello World', @base.bar + assert_equal 'bizzle', @base.baz + end + + it 'sets multiple settings using #each' do + @base.set [["foo", "bar"]] + assert_equal "bar", @base.foo + end + + it 'inherits settings methods when subclassed' do + @base.set :foo, 'bar' + @base.set :biz, Proc.new { 'baz' } + + sub = Class.new(@base) + assert sub.respond_to?(:foo) + assert_equal 'bar', sub.foo + assert sub.respond_to?(:biz) + assert_equal 'baz', sub.biz + end + + it 'overrides settings in subclass' do + @base.set :foo, 'bar' + @base.set :biz, Proc.new { 'baz' } + sub = Class.new(@base) + sub.set :foo, 'bling' + assert_equal 'bling', sub.foo + assert_equal 'bar', @base.foo + end + + it 'creates setter methods when first defined' do + @base.set :foo, 'bar' + assert @base.respond_to?('foo=') + @base.foo = 'biz' + assert_equal 'biz', @base.foo + end + + it 'creates predicate methods when first defined' do + @base.set :foo, 'hello world' + assert @base.respond_to?(:foo?) + assert @base.foo? + @base.set :foo, nil + assert !@base.foo? + end + + it 'uses existing setter methods if detected' do + class << @base + def foo + @foo + end + def foo=(value) + @foo = 'oops' + end + end + + @base.set :foo, 'bam' + assert_equal 'oops', @base.foo + end + + it 'merges values of multiple set calls if those are hashes' do + @base.set :foo, :a => 1 + sub = Class.new(@base) + sub.set :foo, :b => 2 + assert_equal({:a => 1, :b => 2}, sub.foo) + end + + it 'merging does not affect the superclass' do + @base.set :foo, :a => 1 + sub = Class.new(@base) + sub.set :foo, :b => 2 + assert_equal({:a => 1}, @base.foo) + end + + it 'is possible to change a value from a hash to something else' do + @base.set :foo, :a => 1 + @base.set :foo, :bar + assert_equal(:bar, @base.foo) + end + + it 'merges values with values of the superclass if those are hashes' do + @base.set :foo, :a => 1 + @base.set :foo, :b => 2 + assert_equal({:a => 1, :b => 2}, @base.foo) + end + + it "sets multiple settings to true with #enable" do + @base.enable :sessions, :foo, :bar + assert @base.sessions + assert @base.foo + assert @base.bar + end + + it "sets multiple settings to false with #disable" do + @base.disable :sessions, :foo, :bar + assert !@base.sessions + assert !@base.foo + assert !@base.bar + end + + it 'is accessible from instances via #settings' do + assert_equal :foo, @base.new!.settings.environment + end + + it 'is accessible from class via #settings' do + assert_equal :foo, @base.settings.environment + end + + describe 'methodoverride' do + it 'is disabled on Base' do + assert ! @base.method_override? + end + + it 'is enabled on Application' do + assert @application.method_override? + end + + it 'enables MethodOverride middleware' do + @base.set :method_override, true + @base.put('/') { 'okay' } + @app = @base + post '/', {'_method'=>'PUT'}, {} + assert_equal 200, status + assert_equal 'okay', body + end + + it 'is backward compatible with methodoverride' do + assert ! @base.methodoverride? + @base.enable :methodoverride + assert @base.methodoverride? + end + end + + describe 'run' do + it 'is disabled on Base' do + assert ! @base.run? + end + + it 'is enabled on Application except in test environment' do + assert @application.run? + + @application.set :environment, :test + assert ! @application.run? + end + end + + describe 'raise_errors' do + it 'is enabled on Base only in test' do + assert ! @base.raise_errors? + + @base.set(:environment, :test) + assert @base.raise_errors? + end + + it 'is enabled on Application only in test' do + assert ! @application.raise_errors? + + @application.set(:environment, :test) + assert @application.raise_errors? + end + end + + describe 'show_exceptions' do + it 'is disabled on Base except under development' do + assert ! @base.show_exceptions? + @base.environment = :development + assert @base.show_exceptions? + end + + it 'is disabled on Application except in development' do + assert ! @application.show_exceptions? + + @application.set(:environment, :development) + assert @application.show_exceptions? + end + + it 'returns a friendly 500' do + klass = Sinatra.new(Sinatra::Application) + mock_app(klass) { + enable :show_exceptions + + get '/' do + raise StandardError + end + } + + get '/' + assert_equal 500, status + assert body.include?("StandardError") + assert body.include?("show_exceptions setting") + end + + it 'does not override app-specified error handling when set to :after_handler' do + ran = false + mock_app do + set :show_exceptions, :after_handler + error(RuntimeError) { ran = true } + get('/') { raise RuntimeError } + end + + get '/' + assert_equal 500, status + assert ran + end + + it 'does catch any other exceptions when set to :after_handler' do + ran = false + mock_app do + set :show_exceptions, :after_handler + error(RuntimeError) { ran = true } + get('/') { raise ArgumentError } + end + + get '/' + assert_equal 500, status + assert !ran + end + end + + describe 'dump_errors' do + it 'is disabled on Base in test' do + @base.environment = :test + assert ! @base.dump_errors? + @base.environment = :development + assert @base.dump_errors? + @base.environment = :production + assert @base.dump_errors? + end + + it 'dumps exception with backtrace to rack.errors' do + klass = Sinatra.new(Sinatra::Application) + + mock_app(klass) { + enable :dump_errors + disable :raise_errors + + error do + error = @env['rack.errors'].instance_variable_get(:@error) + error.rewind + + error.read + end + + get '/' do + raise + end + } + + get '/' + assert body.include?("RuntimeError") && body.include?("settings_test.rb") + end + + it 'does not dump 404 errors' do + klass = Sinatra.new(Sinatra::Application) + + mock_app(klass) { + enable :dump_errors + disable :raise_errors + + error do + error = @env['rack.errors'].instance_variable_get(:@error) + error.rewind + + error.read + end + + get '/' do + raise Sinatra::NotFound + end + } + + get '/' + assert !body.include?("NotFound") && !body.include?("settings_test.rb") + end + end + + describe 'sessions' do + it 'is disabled on Base' do + assert ! @base.sessions? + end + + it 'is disabled on Application' do + assert ! @application.sessions? + end + end + + describe 'logging' do + it 'is disabled on Base' do + assert ! @base.logging? + end + + it 'is enabled on Application except in test environment' do + assert @application.logging? + + @application.set :environment, :test + assert ! @application.logging + end + end + + describe 'static' do + it 'is disabled on Base by default' do + assert ! @base.static? + end + + it 'is enabled on Base when public_folder is set and exists' do + @base.set :environment, :development + @base.set :public_folder, File.dirname(__FILE__) + assert @base.static? + end + + it 'is enabled on Base when root is set and root/public_folder exists' do + @base.set :environment, :development + @base.set :root, File.dirname(__FILE__) + assert @base.static? + end + + it 'is disabled on Application by default' do + assert ! @application.static? + end + + it 'is enabled on Application when public_folder is set and exists' do + @application.set :environment, :development + @application.set :public_folder, File.dirname(__FILE__) + assert @application.static? + end + + it 'is enabled on Application when root is set and root/public_folder exists' do + @application.set :environment, :development + @application.set :root, File.dirname(__FILE__) + assert @application.static? + end + + it 'is possible to use Module#public' do + @base.send(:define_method, :foo) { } + @base.send(:private, :foo) + assert !@base.public_method_defined?(:foo) + @base.send(:public, :foo) + assert @base.public_method_defined?(:foo) + end + + it 'is possible to use the keyword public in a sinatra app' do + app = Sinatra.new do + private + def priv; end + public + def pub; end + end + assert !app.public_method_defined?(:priv) + assert app.public_method_defined?(:pub) + end + end + + describe 'bind' do + it 'defaults to 0.0.0.0' do + assert_equal '0.0.0.0', @base.bind + assert_equal '0.0.0.0', @application.bind + end + end + + describe 'port' do + it 'defaults to 4567' do + assert_equal 4567, @base.port + assert_equal 4567, @application.port + end + end + + describe 'server' do + it 'includes webrick' do + assert @base.server.include?('webrick') + assert @application.server.include?('webrick') + end + + it 'includes puma' do + assert @base.server.include?('puma') + assert @application.server.include?('puma') + end + + it 'includes thin' do + next if RUBY_ENGINE == 'jruby' + assert @base.server.include?('thin') + assert @application.server.include?('thin') + end + end + + describe 'app_file' do + it 'is nil for base classes' do + assert_nil Sinatra::Base.app_file + assert_nil Sinatra::Application.app_file + end + + it 'defaults to the file subclassing' do + assert_equal File.expand_path(__FILE__), Sinatra.new.app_file + end + end + + describe 'root' do + it 'is nil if app_file is not set' do + assert @base.root.nil? + assert @application.root.nil? + end + + it 'is equal to the expanded basename of app_file' do + @base.app_file = __FILE__ + assert_equal File.expand_path(File.dirname(__FILE__)), @base.root + + @application.app_file = __FILE__ + assert_equal File.expand_path(File.dirname(__FILE__)), @application.root + end + end + + describe 'views' do + it 'is nil if root is not set' do + assert @base.views.nil? + assert @application.views.nil? + end + + it 'is set to root joined with views/' do + @base.root = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__) + "/views", @base.views + + @application.root = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__) + "/views", @application.views + end + end + + describe 'public_folder' do + it 'is nil if root is not set' do + assert @base.public_folder.nil? + assert @application.public_folder.nil? + end + + it 'is set to root joined with public/' do + @base.root = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__) + "/public", @base.public_folder + + @application.root = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__) + "/public", @application.public_folder + end + end + + describe 'public_dir' do + it 'is an alias for public_folder' do + @base.public_dir = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__), @base.public_dir + assert_equal @base.public_folder, @base.public_dir + + @application.public_dir = File.dirname(__FILE__) + assert_equal File.dirname(__FILE__), @application.public_dir + assert_equal @application.public_folder, @application.public_dir + end + end + + describe 'lock' do + it 'is disabled by default' do + assert ! @base.lock? + assert ! @application.lock? + end + end + + describe 'protection' do + class MiddlewareTracker < Rack::Builder + def self.track + Rack.send :remove_const, :Builder + Rack.const_set :Builder, MiddlewareTracker + MiddlewareTracker.used.clear + yield + ensure + Rack.send :remove_const, :Builder + Rack.const_set :Builder, MiddlewareTracker.superclass + end + + def self.used + @used ||= [] + end + + def use(middleware, *) + MiddlewareTracker.used << middleware + super + end + end + + it 'sets up Rack::Protection' do + MiddlewareTracker.track do + Sinatra::Base.new + assert_include MiddlewareTracker.used, Rack::Protection + end + end + + it 'sets up Rack::Protection::PathTraversal' do + MiddlewareTracker.track do + Sinatra::Base.new + assert_include MiddlewareTracker.used, Rack::Protection::PathTraversal + end + end + + it 'does not set up Rack::Protection::PathTraversal when disabling it' do + MiddlewareTracker.track do + Sinatra.new { set :protection, :except => :path_traversal }.new + assert_include MiddlewareTracker.used, Rack::Protection + assert !MiddlewareTracker.used.include?(Rack::Protection::PathTraversal) + end + end + + it 'sets up RemoteToken if sessions are enabled' do + MiddlewareTracker.track do + Sinatra.new { enable :sessions }.new + assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken + end + end + + it 'does not set up RemoteToken if sessions are disabled' do + MiddlewareTracker.track do + Sinatra.new.new + assert !MiddlewareTracker.used.include?(Rack::Protection::RemoteToken) + end + end + + it 'sets up RemoteToken if it is configured to' do + MiddlewareTracker.track do + Sinatra.new { set :protection, :session => true }.new + assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sinatra_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sinatra_test.rb new file mode 100644 index 000000000..b27120a06 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/sinatra_test.rb @@ -0,0 +1,12 @@ +require File.expand_path('../helper', __FILE__) + +class SinatraTest < Minitest::Test + it 'creates a new Sinatra::Base subclass on new' do + app = Sinatra.new { get('/') { 'Hello World' } } + assert_same Sinatra::Base, app.superclass + end + + it "responds to #template_cache" do + assert_kind_of Tilt::Cache, Sinatra::Base.new!.template_cache + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/slim_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/slim_test.rb new file mode 100644 index 000000000..a3ad73975 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/slim_test.rb @@ -0,0 +1,102 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'slim' + +class SlimTest < Minitest::Test + def slim_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline slim strings' do + slim_app { slim "h1 Hiya\n" } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders .slim files in views path' do + slim_app { slim :hello } + assert ok? + assert_equal "

Hello From Slim

", body + end + + it "renders with inline layouts" do + mock_app do + layout { %(h1\n | THIS. IS. \n == yield.upcase ) } + get('/') { slim 'em Sparta' } + end + get '/' + assert ok? + assert_equal "

THIS. IS. SPARTA

", body + end + + it "renders with file layouts" do + slim_app { slim('| Hello World', :layout => :layout2) } + assert ok? + assert_equal "

Slim Layout!

Hello World

", body + end + + it "raises error if template not found" do + mock_app { get('/') { slim(:no_such_template) } } + assert_raises(Errno::ENOENT) { get('/') } + end + + HTML4_DOCTYPE = "" + + it "passes slim options to the slim engine" do + mock_app { get('/') { slim("x foo='bar'", :attr_quote => "'") }} + get '/' + assert ok? + assert_body "" + end + + it "passes default slim options to the slim engine" do + mock_app do + set :slim, :attr_quote => "'" + get('/') { slim("x foo='bar'") } + end + get '/' + assert ok? + assert_body "" + end + + it "merges the default slim options with the overrides and passes them to the slim engine" do + mock_app do + set :slim, :attr_quote => "'" + get('/') { slim("x foo='bar'") } + get('/other') { slim("x foo='bar'", :attr_quote => '"') } + end + get '/' + assert ok? + assert_body "" + get '/other' + assert ok? + assert_body '' + end + + it "can render truly nested layouts by accepting a layout and a block with the contents" do + mock_app do + template(:main_outer_layout) { "h1 Title\n== yield" } + template(:an_inner_layout) { "h2 Subtitle\n== yield" } + template(:a_page) { "p Contents." } + get('/') do + slim :main_outer_layout, :layout => false do + slim :an_inner_layout do + slim :a_page + end + end + end + end + get '/' + assert ok? + assert_body "

Title

\n

Subtitle

\n

Contents.

\n" + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping slim tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/static_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/static_test.rb new file mode 100644 index 000000000..988ef041d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/static_test.rb @@ -0,0 +1,236 @@ +require File.expand_path('../helper', __FILE__) + +class StaticTest < Minitest::Test + setup do + mock_app do + set :static, true + set :public_folder, File.dirname(__FILE__) + end + end + + it 'serves GET requests for files in the public directory' do + get "/#{File.basename(__FILE__)}" + assert ok? + assert_equal File.read(__FILE__), body + assert_equal File.size(__FILE__).to_s, response['Content-Length'] + assert response.headers.include?('Last-Modified') + end + + it 'produces a body that can be iterated over multiple times' do + env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") + _, _, body = @app.call(env) + buf1, buf2 = [], [] + body.each { |part| buf1 << part } + body.each { |part| buf2 << part } + assert_equal buf1.join, buf2.join + assert_equal File.read(__FILE__), buf1.join + end + + it 'sets the sinatra.static_file env variable if served' do + env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") + @app.call(env) + assert_equal File.expand_path(__FILE__), env['sinatra.static_file'] + end + + it 'serves HEAD requests for files in the public directory' do + head "/#{File.basename(__FILE__)}" + assert ok? + assert_equal '', body + assert response.headers.include?('Last-Modified') + assert_equal File.size(__FILE__).to_s, response['Content-Length'] + end + + %w[POST PUT DELETE].each do |verb| + it "does not serve #{verb} requests" do + send verb.downcase, "/#{File.basename(__FILE__)}" + assert_equal 404, status + end + end + + it 'serves files in preference to custom routes' do + @app.get("/#{File.basename(__FILE__)}") { 'Hello World' } + get "/#{File.basename(__FILE__)}" + assert ok? + assert body != 'Hello World' + end + + it 'does not serve directories' do + get "/" + assert not_found? + end + + it 'passes to the next handler when the static option is disabled' do + @app.set :static, false + get "/#{File.basename(__FILE__)}" + assert not_found? + end + + it 'passes to the next handler when the public option is nil' do + @app.set :public_folder, nil + get "/#{File.basename(__FILE__)}" + assert not_found? + end + + it '404s when a file is not found' do + get "/foobarbaz.txt" + assert not_found? + end + + it 'serves files when .. path traverses within public directory' do + get "/data/../#{File.basename(__FILE__)}" + assert ok? + assert_equal File.read(__FILE__), body + end + + it '404s when .. path traverses outside of public directory' do + mock_app do + set :static, true + set :public_folder, File.dirname(__FILE__) + '/data' + end + get "/../#{File.basename(__FILE__)}" + assert not_found? + end + + def assert_valid_range(http_range, range, path, file) + request = Rack::MockRequest.new(@app) + response = request.get("/#{File.basename(path)}", 'HTTP_RANGE' => http_range) + + should_be = file[range] + expected_range = "bytes #{range.begin}-#{range.end}/#{file.length}" + + assert_equal( + 206,response.status, + "Should be HTTP/1.1 206 Partial content" + ) + assert_equal( + should_be.length, + response.body.length, + "Unexpected response length for #{http_range}" + ) + assert_equal( + should_be, + response.body, + "Unexpected response data for #{http_range}" + ) + assert_equal( + should_be.length.to_s, + response['Content-Length'], + "Incorrect Content-Length for #{http_range}" + ) + assert_equal( + expected_range, + response['Content-Range'], + "Incorrect Content-Range for #{http_range}" + ) + end + + it 'handles valid byte ranges correctly' do + # Use the biggest file in this dir so we can test ranges > 8k bytes. (StaticFile sends in 8k chunks.) + path = File.dirname(__FILE__) + '/helpers_test.rb' # currently 16k bytes + file = File.read(path) + length = file.length + assert length > 9000, "The test file #{path} is too short (#{length} bytes) to run these tests" + + [0..0, 42..88, 1234..1234, 100..9000, 0..(length-1), (length-1)..(length-1)].each do |range| + assert_valid_range("bytes=#{range.begin}-#{range.end}", range, path, file) + end + + [0, 100, length-100, length-1].each do |start| + assert_valid_range("bytes=#{start}-", (start..length-1), path, file) + end + + [1, 100, length-100, length-1, length].each do |range_length| + assert_valid_range("bytes=-#{range_length}", (length-range_length..length-1), path, file) + end + + # Some valid ranges that exceed the length of the file: + assert_valid_range("bytes=100-999999", (100..length-1), path, file) + assert_valid_range("bytes=100-#{length}", (100..length-1), path, file) + assert_valid_range("bytes=-#{length}", (0..length-1), path, file) + assert_valid_range("bytes=-#{length+1}", (0..length-1), path, file) + assert_valid_range("bytes=-999999", (0..length-1), path, file) + end + + it 'correctly ignores syntactically invalid range requests' do + # ...and also ignores multi-range requests, which aren't supported yet + ["bytes=45-40", "bytes=IV-LXVI", "octets=10-20", "bytes=-", "bytes=1-2,3-4"].each do |http_range| + request = Rack::MockRequest.new(@app) + response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) + + assert_equal( + 200, + response.status, + "Invalid range '#{http_range}' should be ignored" + ) + assert_equal( + nil, + response['Content-Range'], + "Invalid range '#{http_range}' should be ignored" + ) + end + end + + it 'returns error 416 for unsatisfiable range requests' do + # An unsatisfiable request is one that specifies a start that's at or past the end of the file. + length = File.read(__FILE__).length + ["bytes=888888-", "bytes=888888-999999", "bytes=#{length}-#{length}"].each do |http_range| + request = Rack::MockRequest.new(@app) + response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) + + assert_equal( + 416, + response.status, + "Unsatisfiable range '#{http_range}' should return 416" + ) + assert_equal( + "bytes */#{length}", + response['Content-Range'], + "416 response should include actual length" + ) + end + end + + it 'does not include static cache control headers by default' do + env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") + _, headers, _ = @app.call(env) + assert !headers.has_key?('Cache-Control') + end + + it 'sets cache control headers on static files if set' do + @app.set :static_cache_control, :public + env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") + status, headers, body = @app.call(env) + assert headers.has_key?('Cache-Control') + assert_equal headers['Cache-Control'], 'public' + + @app.set( + :static_cache_control, + [:public, :must_revalidate, {:max_age => 300}] + ) + env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") + status, headers, body = @app.call(env) + assert headers.has_key?('Cache-Control') + assert_equal( + headers['Cache-Control'], + 'public, must-revalidate, max-age=300' + ) + end + + it 'renders static assets with custom status via options' do + mock_app do + set :static, true + set :public_folder, File.dirname(__FILE__) + + post '/*' do + static!(:status => params[:status]) + end + end + + post "/#{File.basename(__FILE__)}?status=422" + assert_equal response.status, 422 + assert_equal File.read(__FILE__), body + assert_equal File.size(__FILE__).to_s, response['Content-Length'] + assert response.headers.include?('Last-Modified') + end + +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/streaming_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/streaming_test.rb new file mode 100644 index 000000000..cc1abe703 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/streaming_test.rb @@ -0,0 +1,149 @@ +require File.expand_path('../helper', __FILE__) + +class StreamingTest < Minitest::Test + Stream = Sinatra::Helpers::Stream + + it 'returns the concatenated body' do + mock_app do + get('/') do + stream do |out| + out << "Hello" << " " + out << "World!" + end + end + end + + get('/') + assert_body "Hello World!" + end + + it 'always yields strings' do + stream = Stream.new { |out| out << :foo } + stream.each { |str| assert_equal 'foo', str } + end + + it 'postpones body generation' do + step = 0 + + stream = Stream.new do |out| + 10.times do + out << step + step += 1 + end + end + + stream.each do |s| + assert_equal s, step.to_s + step += 1 + end + end + + it 'calls the callback after it is done' do + step = 0 + final = 0 + stream = Stream.new { |_| 10.times { step += 1 }} + stream.callback { final = step } + stream.each {|_|} + assert_equal 10, final + end + + it 'does not trigger the callback if close is set to :keep_open' do + step = 0 + final = 0 + stream = Stream.new(Stream, :keep_open) { |_| 10.times { step += 1 } } + stream.callback { final = step } + stream.each {|_|} + assert_equal 0, final + end + + it 'allows adding more than one callback' do + a = b = false + stream = Stream.new { } + stream.callback { a = true } + stream.callback { b = true } + stream.each {|_| } + assert a, 'should trigger first callback' + assert b, 'should trigger second callback' + end + + class MockScheduler + def initialize(*) @schedule, @defer = [], [] end + def schedule(&block) @schedule << block end + def defer(&block) @defer << block end + def schedule!(*) @schedule.pop.call until @schedule.empty? end + def defer!(*) @defer.pop.call until @defer.empty? end + end + + it 'allows dropping in another scheduler' do + scheduler = MockScheduler.new + processing = sending = done = false + + stream = Stream.new(scheduler) do |out| + processing = true + out << :foo + end + + stream.each { sending = true} + stream.callback { done = true } + + scheduler.schedule! + assert !processing + assert !sending + assert !done + + scheduler.defer! + assert processing + assert !sending + assert !done + + scheduler.schedule! + assert sending + assert done + end + + it 'schedules exceptions to be raised on the main thread/event loop/...' do + scheduler = MockScheduler.new + Stream.new(scheduler) { fail 'should be caught' }.each { } + scheduler.defer! + assert_raises(RuntimeError) { scheduler.schedule! } + end + + it 'does not trigger an infinite loop if you call close in a callback' do + stream = Stream.new { |out| out.callback { out.close }} + stream.each { |_| } + end + + it 'gives access to route specific params' do + mock_app do + get('/:name') do + stream { |o| o << params[:name] } + end + end + get '/foo' + assert_body 'foo' + end + + it 'sets up async.close if available' do + ran = false + mock_app do + get('/') do + close = Object.new + def close.callback; yield end + def close.errback; end + env['async.close'] = close + stream(:keep_open) do |out| + out.callback { ran = true } + end + end + end + get '/' + assert ran + end + + it 'has a public interface to inspect its open/closed state' do + stream = Stream.new(Stream) { |out| out << :foo } + assert !stream.closed? + stream.close + assert stream.closed? + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/stylus_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/stylus_test.rb new file mode 100644 index 000000000..9c7e7fd0c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/stylus_test.rb @@ -0,0 +1,90 @@ +require File.expand_path('../helper', __FILE__) + +begin + require 'stylus' + require 'stylus/tilt' + + begin + Stylus.compile '1' + rescue RuntimeError + raise LoadError, 'unable to find Stylus compiler' + end + + class StylusTest < Minitest::Test + def stylus_app(options = {}, &block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + set(options) + get('/', &block) + end + get '/' + end + + it 'renders inline Stylus strings' do + stylus_app { stylus "a\n margin auto\n" } + assert ok? + assert body.include?("a {\n margin: auto;\n}\n") + end + + it 'defaults content type to css' do + stylus_app { stylus :hello } + assert ok? + assert_equal "text/css;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type per route' do + stylus_app do + content_type :html + stylus :hello + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'defaults allows setting content type globally' do + stylus_app(:styl => { :content_type => 'html' }) do + stylus :hello + end + assert ok? + assert_equal "text/html;charset=utf-8", response['Content-Type'] + end + + it 'renders .styl files in views path' do + stylus_app { stylus :hello } + assert ok? + assert_include body, "a {\n margin: auto;\n}\n" + end + + it 'ignores the layout option' do + stylus_app { stylus :hello, :layout => :layout2 } + assert ok? + assert_include body, "a {\n margin: auto;\n}\n" + end + + it "raises error if template not found" do + mock_app { + get('/') { stylus :no_such_template } + } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "passes stylus options to the stylus engine" do + stylus_app { stylus :hello, :no_wrap => true } + assert ok? + assert_body "a {\n margin: auto;\n}\n" + end + + it "passes default stylus options to the stylus engine" do + mock_app do + set :stylus, :no_wrap => true # default stylus style is :nested + get('/') { stylus :hello } + end + get '/' + assert ok? + assert_body "a {\n margin: auto;\n}\n" + end + end + +rescue LoadError + warn "#{$!.to_s}: skipping stylus tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/templates_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/templates_test.rb new file mode 100644 index 000000000..3548f8f5b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/templates_test.rb @@ -0,0 +1,382 @@ +# encoding: UTF-8 +require File.expand_path('../helper', __FILE__) +File.delete(File.dirname(__FILE__) + '/views/layout.test') rescue nil + +class TestTemplate < Tilt::Template + def prepare + end + + def evaluate(scope, locals={}, &block) + inner = block ? block.call : '' + data + inner + end + + Tilt.register 'test', self +end + +class TemplatesTest < Minitest::Test + def render_app(base=Sinatra::Base, options = {}, &block) + base, options = Sinatra::Base, base if base.is_a? Hash + mock_app(base) do + set :views, File.dirname(__FILE__) + '/views' + set options + get('/', &block) + template(:layout3) { "Layout 3!\n" } + end + get '/' + end + + def with_default_layout + layout = File.dirname(__FILE__) + '/views/layout.test' + File.open(layout, 'wb') { |io| io.write "Layout!\n" } + yield + ensure + File.unlink(layout) rescue nil + end + + it 'falls back to engine layout' do + mock_app do + template(:layout3) { 'Layout 3!<%= yield %>' } + set :erb, :layout => :layout3 + + get('/') do + erb('Hello World!', { :layout => true }) + end + end + + get '/' + assert ok? + assert_equal "Layout 3!Hello World!", body + end + + it 'falls back to default layout if engine layout is true' do + mock_app do + template(:layout) { 'Layout!!! <%= yield %>' } + set :erb, :layout => true + + get('/') do + erb('Hello World!', { :layout => true }) + end + end + + get '/' + assert ok? + assert_equal "Layout!!! Hello World!", body + end + + it 'renders no layout if layout if falsy' do + mock_app do + template(:layout) { 'Layout!!! <%= yield %>' } + set :erb, :layout => true + + get('/') do + erb('Hello World!', { :layout => nil }) + end + end + + get '/' + assert ok? + assert_equal "Hello World!", body + end + + it 'allows overriding false default layout with explicit true' do + mock_app do + template(:layout) { 'Layout!!! <%= yield %>' } + set :erb, :layout => false + + get('/') do + erb('Hello World!', { :layout => true }) + end + end + + get '/' + assert ok? + assert_equal "Layout!!! Hello World!", body + end + + it 'renders String templates directly' do + render_app { render(:test, 'Hello World') } + assert ok? + assert_equal 'Hello World', body + end + + it 'renders Proc templates using the call result' do + render_app { render(:test, Proc.new {'Hello World'}) } + assert ok? + assert_equal 'Hello World', body + end + + it 'looks up Symbol templates in views directory' do + render_app { render(:test, :hello) } + assert ok? + assert_equal "Hello World!\n", body + end + + it 'uses the default layout template if not explicitly overridden' do + with_default_layout do + render_app { render(:test, :hello) } + assert ok? + assert_equal "Layout!\nHello World!\n", body + end + end + + it 'uses the default layout template if not really overridden' do + with_default_layout do + render_app { render(:test, :hello, :layout => true) } + assert ok? + assert_equal "Layout!\nHello World!\n", body + end + end + + it 'uses the layout template specified' do + render_app { render(:test, :hello, :layout => :layout2) } + assert ok? + assert_equal "Layout 2!\nHello World!\n", body + end + + it 'uses layout templates defined with the #template method' do + render_app { render(:test, :hello, :layout => :layout3) } + assert ok? + assert_equal "Layout 3!\nHello World!\n", body + end + + it 'avoids wrapping layouts around nested templates' do + render_app { render(:str, :nested, :layout => :layout2) } + assert ok? + assert_equal( + "

String Layout!

\n

Hello From String

", + body + ) + end + + it 'allows explicitly wrapping layouts around nested templates' do + render_app { render(:str, :explicitly_nested, :layout => :layout2) } + assert ok? + assert_equal( + "

String Layout!

\n

String Layout!

\n

Hello From String

", + body + ) + end + + it 'two independent render calls do not disable layouts' do + render_app do + render :str, :explicitly_nested, :layout => :layout2 + render :str, :nested, :layout => :layout2 + end + assert ok? + assert_equal( + "

String Layout!

\n

Hello From String

", + body + ) + end + + it 'is possible to use partials in layouts' do + render_app do + settings.layout { "<%= erb 'foo' %><%= yield %>" } + erb 'bar' + end + assert ok? + assert_equal "foobar", body + end + + it 'loads templates from source file' do + mock_app { enable(:inline_templates) } + assert_equal "this is foo\n\n", @app.templates[:foo][0] + assert_equal "X\n= yield\nX\n", @app.templates[:layout][0] + end + + it 'ignores spaces after names of inline templates' do + mock_app { enable(:inline_templates) } + assert_equal "There's a space after 'bar'!\n\n", @app.templates[:bar][0] + assert_equal "this is not foo\n\n", @app.templates[:"foo bar"][0] + end + + it 'loads templates from given source file' do + mock_app { set(:inline_templates, __FILE__) } + assert_equal "this is foo\n\n", @app.templates[:foo][0] + end + + test 'inline_templates ignores IO errors' do + mock_app { set(:inline_templates, '/foo/bar') } + + assert @app.templates.empty? + end + + it 'allows unicode in inline templates' do + mock_app { set(:inline_templates, __FILE__) } + assert_equal( + "Den som tror at hemma det är där man bor har aldrig vart hos mig.\n\n", + @app.templates[:umlaut][0] + ) + end + + it 'loads templates from specified views directory' do + render_app { render(:test, :hello, :views => settings.views + '/foo') } + + assert_equal "from another views directory\n", body + end + + it 'takes views directory into consideration for caching' do + render_app do + render(:test, :hello) + render(:test, :hello, :views => settings.views + '/foo') + end + assert_equal "Hello World!\nfrom another views directory\n", body + end + + it 'passes locals to the layout' do + mock_app do + template(:my_layout) { 'Hello <%= name %>!<%= yield %>' } + + get('/') do + erb('

content

', { :layout => :my_layout }, { :name => 'Mike'}) + end + end + + get '/' + assert ok? + assert_equal 'Hello Mike!

content

', body + end + + it 'sets layout-only options via layout_options' do + render_app do + render(:str, :in_a, + :views => settings.views + '/a', + :layout_options => { :views => settings.views }, + :layout => :layout2) + end + + assert ok? + assert_equal "

String Layout!

\nGimme an A!\n", body + end + + it 'loads templates defined in subclasses' do + base = Class.new(Sinatra::Base) + base.template(:foo) { 'bar' } + render_app(base) { render(:test, :foo) } + assert ok? + assert_equal 'bar', body + end + + it 'allows setting default content type per template engine' do + render_app(:str => { :content_type => :txt }) { + render :str, 'foo' + } + assert_equal 'text/plain;charset=utf-8', response['Content-Type'] + end + + it 'setting default content type does not affect other template engines' do + render_app(:str => { :content_type => :txt }) { + render :test, 'foo' + } + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + + it 'setting default content type per template engine does not override content_type' do + render_app :str => { :content_type => :txt } do + content_type :html + render :str, 'foo' + end + assert_equal 'text/html;charset=utf-8', response['Content-Type'] + end + + it 'uses templates in superclasses before subclasses' do + base = Class.new(Sinatra::Base) + base.template(:foo) { 'template in superclass' } + assert_equal 'template in superclass', base.templates[:foo].first.call + + mock_app(base) do + set :views, File.dirname(__FILE__) + '/views' + template(:foo) { 'template in subclass' } + get('/') { render :test, :foo } + end + assert_equal 'template in subclass', @app.templates[:foo].first.call + + get '/' + assert ok? + assert_equal 'template in subclass', body + end + + it "is possible to use a different engine for the layout than for the template itself explicitly" do + render_app do + settings.template(:layout) { 'Hello <%= yield %>!' } + render :str, "<%= 'World' %>", :layout_engine => :erb + end + assert_equal "Hello <%= 'World' %>!", body + end + + it "is possible to use a different engine for the layout than for the template itself globally" do + render_app :str => { :layout_engine => :erb } do + settings.template(:layout) { 'Hello <%= yield %>!' } + render :str, "<%= 'World' %>" + end + assert_equal "Hello <%= 'World' %>!", body + end + + it "does not leak the content type to the template" do + render_app :str => { :layout_engine => :erb } do + settings.template(:layout) { 'Hello <%= yield %>!' } + render :str, "<%= 'World' %>", :content_type => :txt + end + assert_equal "text/html;charset=utf-8", headers['Content-Type'] + end + + it "is possible to register another template" do + Tilt.register "html.erb", Tilt[:erb] + render_app { render :erb, :calc } + assert_equal '2', body + end + + it "passes scope to the template" do + mock_app do + template(:scoped) { 'Hello <%= foo %>' } + + get('/') do + some_scope = Object.new + def some_scope.foo() 'World!' end + erb :scoped, :scope => some_scope + end + end + + get '/' + assert ok? + assert_equal 'Hello World!', body + end + + it "is possible to use custom logic for finding template files" do + mock_app do + set :views, ["a", "b"].map { |d| File.dirname(__FILE__) + '/views/' + d } + def find_template(views, name, engine, &block) + Array(views).each { |v| super(v, name, engine, &block) } + end + + get('/:name') { render(:str, params[:name].to_sym) } + end + + get '/in_a' + assert_body 'Gimme an A!' + + get '/in_b' + assert_body 'Gimme a B!' + end +end + +# __END__ : this is not the real end of the script. + +__END__ + +@@ foo +this is foo + +@@ bar +There's a space after 'bar'! + +@@ foo bar +this is not foo + +@@ umlaut +Den som tror at hemma det är där man bor har aldrig vart hos mig. + +@@ layout +X += yield +X diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/textile_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/textile_test.rb new file mode 100644 index 000000000..33e01e000 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/textile_test.rb @@ -0,0 +1,65 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'redcloth' + +class TextileTest < Minitest::Test + def textile_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline textile strings' do + textile_app { textile('h1. Hiya') } + assert ok? + assert_equal "

Hiya

", body + end + + it 'renders .textile files in views path' do + textile_app { textile(:hello) } + assert ok? + assert_equal "

Hello From Textile

", body + end + + it "raises error if template not found" do + mock_app { get('/') { textile(:no_such_template) } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. #{yield.upcase}!' } + get('/') { textile('Sparta', :layout_engine => :str) } + end + get '/' + assert ok? + assert_like 'THIS. IS.

SPARTA

!', body + end + + it "renders with file layouts" do + textile_app { + textile('Hello World', :layout => :layout2, :layout_engine => :erb) + } + assert ok? + assert_body "ERB Layout!\n

Hello World

" + end + + it "can be used in a nested fashion for partials and whatnot" do + mock_app do + template(:inner) { "hi" } + template(:outer) { "<%= textile :inner %>" } + get('/') { erb :outer } + end + + get '/' + assert ok? + assert_like '

hi

', body + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping textile tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/a/in_a.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/a/in_a.str new file mode 100644 index 000000000..88805dc49 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/a/in_a.str @@ -0,0 +1 @@ +Gimme an A! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/ascii.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/ascii.erb new file mode 100644 index 000000000..094e6a8e2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/ascii.erb @@ -0,0 +1,2 @@ +This file has no unicode in it! +<%= value %> diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/b/in_b.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/b/in_b.str new file mode 100644 index 000000000..156de2115 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/b/in_b.str @@ -0,0 +1 @@ +Gimme a B! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/calc.html.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/calc.html.erb new file mode 100644 index 000000000..ff47b8475 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/calc.html.erb @@ -0,0 +1 @@ +<%= 1 + 1 %> \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.builder b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.builder new file mode 100644 index 000000000..9cf87d5eb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.builder @@ -0,0 +1,3 @@ +xml.error do + raise "goodbye" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.erb new file mode 100644 index 000000000..b48d1f060 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.erb @@ -0,0 +1,3 @@ +Hello <%= 'World' %> +<% raise 'Goodbye' unless defined?(french) && french %> +<% raise 'Au revoir' if defined?(french) && french %> diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.haml b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.haml new file mode 100644 index 000000000..6019007b2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.haml @@ -0,0 +1,3 @@ +%h1 Hello From Haml += raise 'goodbye' unless defined?(french) && french += raise 'au revoir' if defined?(french) && french diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.sass b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.sass new file mode 100644 index 000000000..42fc56b34 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/error.sass @@ -0,0 +1,2 @@ +#sass + +argle-bargle diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/explicitly_nested.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/explicitly_nested.str new file mode 100644 index 000000000..a3b0875b6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/explicitly_nested.str @@ -0,0 +1 @@ +#{render :str, :hello, :layout => :layout2} \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/foo/hello.test b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/foo/hello.test new file mode 100644 index 000000000..2aba63411 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/foo/hello.test @@ -0,0 +1 @@ +from another views directory diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.asciidoc b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.asciidoc new file mode 100644 index 000000000..1afd1605f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.asciidoc @@ -0,0 +1 @@ +== Hello from AsciiDoc diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.builder b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.builder new file mode 100644 index 000000000..16b86d036 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.builder @@ -0,0 +1 @@ +xml.exclaim "You're my boy, #{@name}!" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.coffee b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.coffee new file mode 100644 index 000000000..b21e7d632 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.coffee @@ -0,0 +1 @@ +alert "Aye!" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.creole b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.creole new file mode 100644 index 000000000..50db5f85a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.creole @@ -0,0 +1 @@ += Hello From Creole diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.erb new file mode 100644 index 000000000..bcbbc926f --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.erb @@ -0,0 +1 @@ +Hello <%= 'World' %> diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.haml b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.haml new file mode 100644 index 000000000..d6852a609 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.haml @@ -0,0 +1 @@ +%h1 Hello From Haml diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.less b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.less new file mode 100644 index 000000000..9eb99ddc6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.less @@ -0,0 +1,5 @@ +@white_colour: #fff; + +#main { + background-color: @white_colour; +} \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.liquid b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.liquid new file mode 100644 index 000000000..00d7d26cc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.liquid @@ -0,0 +1 @@ +

Hello From Liquid

diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mab b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mab new file mode 100644 index 000000000..6e0c9cbf0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mab @@ -0,0 +1 @@ +h1 "Hello From Markaby" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.md b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.md new file mode 100644 index 000000000..31dc9cc29 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.md @@ -0,0 +1 @@ +# Hello From Markdown \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mediawiki b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mediawiki new file mode 100644 index 000000000..55e65133a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.mediawiki @@ -0,0 +1 @@ +''Hello from MediaWiki'' diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.nokogiri b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.nokogiri new file mode 100644 index 000000000..16b86d036 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.nokogiri @@ -0,0 +1 @@ +xml.exclaim "You're my boy, #{@name}!" diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rabl b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rabl new file mode 100644 index 000000000..ebde74d26 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rabl @@ -0,0 +1,2 @@ +object @foo +attributes :bar diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.radius b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.radius new file mode 100644 index 000000000..98e35f37e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.radius @@ -0,0 +1 @@ +

Hello From Radius

diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rdoc b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rdoc new file mode 100644 index 000000000..cfba71486 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.rdoc @@ -0,0 +1 @@ += Hello From RDoc diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.sass b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.sass new file mode 100644 index 000000000..090bd4a0e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.sass @@ -0,0 +1,2 @@ +#sass + :background-color white diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.scss b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.scss new file mode 100644 index 000000000..87d1200fa --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.scss @@ -0,0 +1,3 @@ +#scss { + background-color: white +} \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.slim b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.slim new file mode 100644 index 000000000..067e671cb --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.slim @@ -0,0 +1 @@ +h1 Hello From Slim diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.str new file mode 100644 index 000000000..6c121b377 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.str @@ -0,0 +1 @@ +

Hello From String

\ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.styl b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.styl new file mode 100644 index 000000000..3be6744e3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.styl @@ -0,0 +1,2 @@ +a + margin auto diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.test b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.test new file mode 100644 index 000000000..980a0d5f1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.test @@ -0,0 +1 @@ +Hello World! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.textile b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.textile new file mode 100644 index 000000000..02686a69c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.textile @@ -0,0 +1 @@ +h1. Hello From Textile diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.wlang b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.wlang new file mode 100644 index 000000000..43a86509b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.wlang @@ -0,0 +1 @@ +Hello from wlang! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.yajl b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.yajl new file mode 100644 index 000000000..68ef6a6c8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/hello.yajl @@ -0,0 +1 @@ +json = { :yajl => "hello" } \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.builder b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.builder new file mode 100644 index 000000000..9491f5740 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.builder @@ -0,0 +1,3 @@ +xml.layout do + xml << yield +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.erb new file mode 100644 index 000000000..e097f3b6e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.erb @@ -0,0 +1,2 @@ +ERB Layout! +<%= yield %> diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.haml b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.haml new file mode 100644 index 000000000..58bfc04de --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.haml @@ -0,0 +1,2 @@ +%h1 HAML Layout! +%p= yield diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.liquid b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.liquid new file mode 100644 index 000000000..4b64d4d4d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.liquid @@ -0,0 +1,2 @@ +

Liquid Layout!

+

{{ yield }}

diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.mab b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.mab new file mode 100644 index 000000000..01da339bf --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.mab @@ -0,0 +1,2 @@ +h1 "Markaby Layout!" +p { yield } diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.nokogiri b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.nokogiri new file mode 100644 index 000000000..9491f5740 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.nokogiri @@ -0,0 +1,3 @@ +xml.layout do + xml << yield +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.rabl b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.rabl new file mode 100644 index 000000000..ecd27a244 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.rabl @@ -0,0 +1,3 @@ +node(:qux) do + ::JSON.parse(yield) +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.radius b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.radius new file mode 100644 index 000000000..57a06c04e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.radius @@ -0,0 +1,2 @@ +

Radius Layout!

+

diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.slim b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.slim new file mode 100644 index 000000000..d2966864a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.slim @@ -0,0 +1,3 @@ +h1 Slim Layout! +p + == yield diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.str new file mode 100644 index 000000000..c63918117 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.str @@ -0,0 +1,2 @@ +

String Layout!

+#{yield} \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.test b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.test new file mode 100644 index 000000000..fb432e3a7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.test @@ -0,0 +1 @@ +Layout 2! diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.wlang b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.wlang new file mode 100644 index 000000000..6055260e1 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/layout2.wlang @@ -0,0 +1,2 @@ +WLang Layout! ++{yield} diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/nested.str b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/nested.str new file mode 100644 index 000000000..603038a4c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/nested.str @@ -0,0 +1 @@ +#{render :str, :hello} \ No newline at end of file diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/utf8.erb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/utf8.erb new file mode 100644 index 000000000..de743c5be --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/views/utf8.erb @@ -0,0 +1,2 @@ +

<%= value %>

+Ingen vill veta var du köpt din tröja. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/wlang_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/wlang_test.rb new file mode 100644 index 000000000..142ca571a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/wlang_test.rb @@ -0,0 +1,87 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'wlang' + +class WLangTest < Minitest::Test + def engine + Tilt::WLangTemplate + end + + def wlang_app(&block) + mock_app { + set :views, File.dirname(__FILE__) + '/views' + get '/', &block + } + get '/' + end + + it 'uses the correct engine' do + assert_equal engine, Tilt[:wlang] + end + + it 'renders .wlang files in views path' do + wlang_app { wlang :hello } + assert ok? + assert_equal "Hello from wlang!\n", body + end + + it 'renders in the app instance scope' do + mock_app do + helpers do + def who; "world"; end + end + get('/') { wlang 'Hello +{who}!' } + end + get '/' + assert ok? + assert_equal 'Hello world!', body + end + + it 'takes a :locals option' do + wlang_app do + locals = {:foo => 'Bar'} + wlang 'Hello ${foo}!', :locals => locals + end + assert ok? + assert_equal 'Hello Bar!', body + end + + it "renders with inline layouts" do + mock_app do + layout { 'THIS. IS. +{yield.upcase}!' } + get('/') { wlang 'Sparta' } + end + get '/' + assert ok? + assert_equal 'THIS. IS. SPARTA!', body + end + + it "renders with file layouts" do + wlang_app { wlang 'Hello World', :layout => :layout2 } + assert ok? + assert_body "WLang Layout!\nHello World" + end + + it "can rendered truly nested layouts by accepting a layout and a block with the contents" do + mock_app do + template(:main_outer_layout) { "

Title

\n>{ yield }" } + template(:an_inner_layout) { "

Subtitle

\n>{ yield }" } + template(:a_page) { "

Contents.

\n" } + get('/') do + wlang :main_outer_layout, :layout => false do + wlang :an_inner_layout do + wlang :a_page + end + end + end + end + get '/' + assert ok? + assert_body "

Title

\n

Subtitle

\n

Contents.

\n" + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping wlang tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/yajl_test.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/yajl_test.rb new file mode 100644 index 000000000..2f00f069d --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/sinatra-1.4.6/test/yajl_test.rb @@ -0,0 +1,86 @@ +require File.expand_path('../helper', __FILE__) + +begin +require 'yajl' + +class YajlTest < Minitest::Test + def yajl_app(&block) + mock_app do + set :views, File.dirname(__FILE__) + '/views' + get('/', &block) + end + get '/' + end + + it 'renders inline Yajl strings' do + yajl_app { yajl('json = { :foo => "bar" }') } + assert ok? + assert_body '{"foo":"bar"}' + end + + it 'renders .yajl files in views path' do + yajl_app { yajl(:hello) } + assert ok? + assert_body '{"yajl":"hello"}' + end + + it 'raises error if template not found' do + mock_app { get('/') { yajl(:no_such_template) } } + assert_raises(Errno::ENOENT) { get('/') } + end + + it 'accepts a :locals option' do + yajl_app do + locals = { :object => { :foo => 'bar' } } + yajl 'json = object', :locals => locals + end + assert ok? + assert_body '{"foo":"bar"}' + end + + it 'accepts a :scope option' do + yajl_app do + scope = { :object => { :foo => 'bar' } } + yajl 'json = self[:object]', :scope => scope + end + assert ok? + assert_body '{"foo":"bar"}' + end + + it 'decorates the json with a callback' do + yajl_app do + yajl( + 'json = { :foo => "bar" }', + { :callback => 'baz' } + ) + end + assert ok? + assert_body 'baz({"foo":"bar"});' + end + + it 'decorates the json with a variable' do + yajl_app do + yajl( + 'json = { :foo => "bar" }', + { :variable => 'qux' } + ) + end + assert ok? + assert_body 'var qux = {"foo":"bar"};' + end + + it 'decorates the json with a callback and a variable' do + yajl_app do + yajl( + 'json = { :foo => "bar" }', + { :callback => 'baz', :variable => 'qux' } + ) + end + assert ok? + assert_body 'var qux = {"foo":"bar"}; baz(qux);' + end +end + +rescue LoadError + warn "#{$!.to_s}: skipping yajl tests" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/CHANGELOG.md b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/CHANGELOG.md new file mode 100644 index 000000000..75434a8ea --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/CHANGELOG.md @@ -0,0 +1,72 @@ +## master + +## 2.0.1 (2014-03-21) + +* Fix Tilt::Mapping bug in Ruby 2.1.0 (9589652c569760298f2647f7a0f9ed4f85129f20) +* Fix `tilt --list` (#223, Achrome) +* Fix circular require (#221, amarshall) + +## 2.0.0 (2013-11-30) + +* Support Pathname in Template#new (#219, kabturek) +* Add Mapping#templates_for (judofyr) +* Support old-style #register (judofyr) +* Add Handlebars as external template engine (#204, judofyr, jimothyGator) +* Add org-ruby as external template engine (#207, judofyr, minad) +* Documentation typo (#208, elgalu) + +## 2.0.0.beta1 (2013-07-16) + +* Documentation typo (#202, chip) +* Use YARD for documentation (#189, judofyr) +* Add Slim as an external template engine (judofyr) +* Add Tilt.templates_for (#121, judofyr) +* Add Tilt.current_template (#151, judofyr) +* Avoid loading all files in tilt.rb (#160, #187, judofyr) +* Implement lazily required templates classes (#178, #187, judofyr) +* Move #allows_script and default_mime_type to metadata (#187, judofyr) +* Introduce Tilt::Mapping (#187, judofyr) +* Make template compilation thread-safe (#191, judofyr) + +## 1.4.1 (2013-05-08) + +* Support Arrays in pre/postambles (#193, jbwiv) + +## 1.4.0 (2013-05-01) + +* Better encoding support + +## 1.3.7 (2013-04-09) + +* Erubis: Check for the correct constant (#183, mattwildig) +* Don't fail when BasicObject is defined in 1.8 (#182, technobrat, judofyr) + +## 1.3.6 (2013-03-17) + +* Accept Hash that implements #path as options (#180, lawso017) +* Changed extension for CsvTemplate from '.csv' to '.rcsv' (#177, alexgb) + +## 1.3.5 (2013-03-06) + +* Fixed extension for PlainTemplate (judofyr) +* Improved local variables regexp (#174, razorinc) +* Added CHANGELOG.md + +## 1.3.4 (2013-02-28) + +* Support RDoc 4.0 (#168, judofyr) +* Add mention of Org-Mode support (#165, aslakknutsen) +* Add AsciiDoctorTemplate (#163, #164, aslakknutsen) +* Add PlainTextTemplate (nathanaeljones) +* Restrict locals to valid variable names (#158, thinkerbot) +* ERB: Improve trim mode support (#156, ssimeonov) +* Add CSVTemplate (#153, alexgb) +* Remove special case for 1.9.1 (#147, guilleiguaran) +* Add allows\_script? method to Template (#143, bhollis) +* Default to using Redcarpet2 (#139, DAddYE) +* Allow File/Tempfile as filenames (#134, jamesotron) +* Add EtanniTemplate (#131, manveru) +* Support RDoc 3.10 (#112, timfel) +* Always compile templates; remove old source evaluator (rtomayko) +* Less: Options are now being passed to the parser (#106, cowboyd) + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/COPYING b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/COPYING new file mode 100644 index 000000000..2f99dc1e4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/COPYING @@ -0,0 +1,18 @@ +Copyright (c) 2010 Ryan Tomayko + +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/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Gemfile b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Gemfile new file mode 100644 index 000000000..de97bc8d9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Gemfile @@ -0,0 +1,44 @@ +source 'https://rubygems.org' + +gem 'yard', '~> 0.8.6' +gem 'minitest', '~> 5.0' + +gem 'rake' + +group :engines do + gem 'asciidoctor', '>= 0.1.0' + gem 'builder' + gem 'coffee-script' + gem 'contest' + gem 'creole' + gem 'erubis' + gem 'haml', '>= 2.2.11', '< 4' + gem 'kramdown' + gem 'less' + gem 'liquid' + gem 'markaby' + gem 'maruku' + gem 'nokogiri' if RUBY_VERSION > '1.9.2' + gem 'radius' + gem 'sass' + gem 'rdoc', (ENV['RDOC_VERSION'] || '> 0') + + platform :ruby do + gem 'wikicloth' + gem 'yajl-ruby' + gem 'redcarpet' if RUBY_VERSION > '1.8.7' + gem 'rdiscount', '>= 2.1.6' if RUBY_VERSION != '1.9.2' + gem 'RedCloth' + end + + platform :mri do + gem 'therubyracer' + gem 'bluecloth' if ENV['BLUECLOTH'] + end +end + +## WHY do I have to do this?!? +platform :rbx do + gem 'rubysl' +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/HACKING b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/HACKING new file mode 100644 index 000000000..46e35f606 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/HACKING @@ -0,0 +1,16 @@ +Clone: + + git clone git://github.com/rtomayko/tilt.git + cd tilt + +Install needed packages under ./vendor and run tests (requires bundler): + + rake + +Run tests under your current gem environment. Do not install anything: + + rake test + +Only install needed packages under ./vendor: + + rake setup diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/README.md b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/README.md new file mode 100644 index 000000000..03df23bf0 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/README.md @@ -0,0 +1,206 @@ +Tilt [![Build Status](https://secure.travis-ci.org/rtomayko/tilt.png)](http://travis-ci.org/rtomayko/tilt) [![Dependency Status](https://gemnasium.com/rtomayko/tilt.png)](https://gemnasium.com/rtomayko/tilt) +==== + +**NOTE** The following file documents the current release of Tilt (2.0). See +https://github.com/rtomayko/tilt/tree/tilt-1 for documentation for Tilt 1.4. + +Tilt is a thin interface over a bunch of different Ruby template engines in +an attempt to make their usage as generic as possible. This is useful for web +frameworks, static site generators, and other systems that support multiple +template engines but don't want to code for each of them individually. + +The following features are supported for all template engines (assuming the +feature is relevant to the engine): + + * Custom template evaluation scopes / bindings + * Ability to pass locals to template evaluation + * Support for passing a block to template evaluation for "yield" + * Backtraces with correct filenames and line numbers + * Template file caching and reloading + * Fast, method-based template source compilation + +The primary goal is to get all of the things listed above right for all +template engines included in the distribution. + +Support for these template engines is included with the package: + + ENGINE FILE EXTENSIONS REQUIRED LIBRARIES + -------------------------- ----------------------- ---------------------------- + Asciidoctor .ad, .adoc, .asciidoc asciidoctor (>= 0.1.0) + ERB .erb, .rhtml none (included ruby stdlib) + Interpolated String .str none (included ruby core) + Erubis .erb, .rhtml, .erubis erubis + Haml .haml haml + Sass .sass haml (< 3.1) or sass (>= 3.1) + Scss .scss haml (< 3.1) or sass (>= 3.1) + Less CSS .less less + Builder .builder builder + Liquid .liquid liquid + RDiscount .markdown, .mkd, .md rdiscount + Redcarpet .markdown, .mkd, .md redcarpet + BlueCloth .markdown, .mkd, .md bluecloth + Kramdown .markdown, .mkd, .md kramdown + Maruku .markdown, .mkd, .md maruku + RedCloth .textile redcloth + RDoc .rdoc rdoc + Radius .radius radius + Markaby .mab markaby + Nokogiri .nokogiri nokogiri + CoffeeScript .coffee coffee-script (+ javascript) + Creole (Wiki markup) .wiki, .creole creole + WikiCloth (Wiki markup) .wiki, .mediawiki, .mw wikicloth + Yajl .yajl yajl-ruby + CSV .rcsv none (Ruby >= 1.9), fastercsv (Ruby < 1.9) + +These template engines ship with their own Tilt integration: + + ENGINE FILE EXTENSIONS REQUIRED LIBRARIES + -------------------------- ----------------- ---------------------------- + Slim .slim slim (>= 0.7) + Embedded JavaScript sprockets + Embedded CoffeeScript sprockets + JST sprockets + Org-mode .org org-ruby (>= 0.6.2) + Handlebars .hbs, .handlebars tilt-handlebars + +See [TEMPLATES.md][t] for detailed information on template engine +options and supported features. + +[t]: http://github.com/rtomayko/tilt/blob/master/docs/TEMPLATES.md + "Tilt Template Engine Documentation" + +Basic Usage +----------- + +Instant gratification: + + require 'erb' + require 'tilt' + template = Tilt.new('templates/foo.erb') + => # + output = template.render + => "Hello world!" + +It's recommended that calling programs explicitly require template engine +libraries (like 'erb' above) at load time. Tilt attempts to lazy require the +template engine library the first time a template is created but this is +prone to error in threaded environments. + +The {Tilt} module contains generic implementation classes for all supported +template engines. Each template class adheres to the same interface for +creation and rendering. In the instant gratification example, we let Tilt +determine the template implementation class based on the filename, but +{Tilt::Template} implementations can also be used directly: + + require 'tilt/haml' + template = Tilt::HamlTemplate.new('templates/foo.haml') + output = template.render + +The `render` method takes an optional evaluation scope and locals hash +arguments. Here, the template is evaluated within the context of the +`Person` object with locals `x` and `y`: + + require 'tilt/erb' + template = Tilt::ERBTemplate.new('templates/foo.erb') + joe = Person.find('joe') + output = template.render(joe, :x => 35, :y => 42) + +If no scope is provided, the template is evaluated within the context of an +object created with `Object.new`. + +A single `Template` instance's `render` method may be called multiple times +with different scope and locals arguments. Continuing the previous example, +we render the same compiled template but this time in jane's scope: + + jane = Person.find('jane') + output = template.render(jane, :x => 22, :y => nil) + +Blocks can be passed to `render` for templates that support running +arbitrary ruby code (usually with some form of `yield`). For instance, +assuming the following in `foo.erb`: + + Hey <%= yield %>! + +The block passed to `render` is called on `yield`: + + template = Tilt::ERBTemplate.new('foo.erb') + template.render { 'Joe' } + # => "Hey Joe!" + +Template Mappings +----------------- + +The {Tilt::Mapping} class includes methods for associating template +implementation classes with filename patterns and for locating/instantiating +template classes based on those associations. + +The {Tilt} module has a global instance of `Mapping` that is populated with the +table of template engines above. + +The {Tilt.register} method associates a filename pattern with a specific +template implementation. To use ERB for files ending in a `.bar` extension: + + >> Tilt.register Tilt::ERBTemplate, 'bar' + >> Tilt.new('views/foo.bar') + => # + +Retrieving the template class for a file or file extension: + + >> Tilt['foo.bar'] + => Tilt::ERBTemplate + >> Tilt['haml'] + => Tilt::HamlTemplate + +Retrieving a list of template classes for a file: + + >> Tilt.templates_for('foo.bar') + => [Tilt::ERBTemplate] + >> Tilt.templates_for('foo.haml.bar') + => [Tilt::ERBTemplate, Tilt::HamlTemplate] + +The template class is determined by searching for a series of decreasingly +specific name patterns. When creating a new template with +`Tilt.new('views/foo.html.erb')`, we check for the following template +mappings: + + 1. `views/foo.html.erb` + 2. `foo.html.erb` + 3. `html.erb` + 4. `erb` + +Encodings +--------- + +Tilt needs to know the encoding of the template in order to work properly: + +Tilt will use `Encoding.default_external` as the encoding when reading external +files. If you're mostly working with one encoding (e.g. UTF-8) we *highly* +recommend setting this option. When providing a custom reader block (`Tilt.new +{ custom_string }`) you'll have ensure the string is properly encoded yourself. + +Most of the template engines in Tilt also allows you to override the encoding +using the `:default_encoding`-option: + +```ruby +tmpl = Tilt.new('hello.erb', :default_encoding => 'Big5') +``` + +Ultimately it's up to the template engine how to handle the encoding: It might +respect `:default_encoding`, it might always assume it's UTF-8 (like +CoffeeScript), or it can do its own encoding detection. + +Template Compilation +-------------------- + +Tilt compiles generated Ruby source code produced by template engines and reuses +it on subsequent template invocations. Benchmarks show this yields a 5x-10x +performance increase over evaluating the Ruby source on each invocation. + +Template compilation is currently supported for these template engines: +StringTemplate, ERB, Erubis, Haml, Nokogiri, Builder and Yajl. + +LICENSE +------- + +Tilt is Copyright (c) 2010 [Ryan Tomayko](http://tomayko.com/about) and +distributed under the MIT license. See the `COPYING` file for more info. diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Rakefile b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Rakefile new file mode 100644 index 000000000..db34c47e3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/Rakefile @@ -0,0 +1,95 @@ +require 'bundler' +Bundler.setup + +require 'rake/testtask' +task :default => [:test] + +# SPECS ===================================================================== + +desc 'Run tests (default)' +Rake::TestTask.new(:test) do |t| + t.test_files = FileList['test/*_test.rb'] + t.ruby_opts = ['-Itest'] + t.ruby_opts << '-rubygems' if defined? Gem +end + +# DOCUMENTATION ============================================================= + +require 'yard' +YARD::Rake::YardocTask.new do |t| + t.files = [ + 'lib/tilt.rb', 'lib/tilt/mapping.rb', 'lib/tilt/template.rb', + '-', + '*.md', 'docs/*.md', + ] + + t.options << + '--no-private' << + '--protected' << + '-m' << 'markdown' << + '--asset' << 'docs/common.css:css/common.css' +end + +# PACKAGING ================================================================= + +begin + require 'rubygems' +rescue LoadError +end + +if defined?(Gem) + SPEC = eval(File.read('tilt.gemspec')) + + def package(ext='') + "pkg/tilt-#{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/' + + file package('.gem') => %w[pkg/ tilt.gemspec] + SPEC.files do |f| + sh "gem build tilt.gemspec" + mv File.basename(f.name), f.name + end + + file package('.tar.gz') => %w[pkg/] + SPEC.files do |f| + sh "git archive --format=tar HEAD | gzip > #{f.name}" + end + + desc 'Upload gem and tar.gz distributables to rubyforge' + task :release => [package('.gem'), package('.tar.gz')] do |t| + sh <<-SH + rubyforge add_release sinatra tilt #{SPEC.version} #{package('.gem')} && + rubyforge add_file sinatra tilt #{SPEC.version} #{package('.tar.gz')} + SH + end +end + +# GEMSPEC =================================================================== + +file 'tilt.gemspec' => FileList['{lib,test}/**','Rakefile'] do |f| + # read version from tilt.rb + version = File.read('lib/tilt.rb')[/VERSION = '(.*)'/] && $1 + # read spec file and split out manifest section + spec = File. + read(f.name). + sub(/s\.version\s*=\s*'.*'/, "s.version = '#{version}'") + parts = spec.split(" # = MANIFEST =\n") + # determine file list from git ls-files + files = `git ls-files`. + split("\n").sort.reject{ |file| file =~ /^\./ }. + map{ |file| " #{file}" }.join("\n") + # piece file back together and write... + parts[1] = " s.files = %w[\n#{files}\n ]\n" + spec = parts.join(" # = MANIFEST =\n") + spec.sub!(/s.date = '.*'/, "s.date = '#{Time.now.strftime("%Y-%m-%d")}'") + File.open(f.name, 'w') { |io| io.write(spec) } + puts "updated #{f.name}" +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/bin/tilt b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/bin/tilt new file mode 100755 index 000000000..7ef901ff2 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/bin/tilt @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby +require 'ostruct' +require 'optparse' +require 'tilt' + +usage = < +Process template and write output to stdout. With no or +when is '-', read template from stdin and use the --type option +to determine the template's type. + +Options + -l, --list List template engines + file patterns and exit + -t, --type= Use this template engine; required if no + -y, --layout= Use as a layout template + + -D= Define variable as + --vars= Evaluate to Hash and use for variables + + -h, --help Show this help message + +Convert markdown to HTML: + $ tilt foo.markdown > foo.html + +Process ERB template: + $ echo "Answer: <%= 2 + 2 %>" | tilt -t erb + Answer: 4 + +Define variables: + $ echo "Answer: <%= 2 + n %>" | tilt -t erb --vars="{:n=>40}" + Answer: 42 + $ echo "Answer: <%= 2 + n.to_i %>" | tilt -t erb -Dn=40 + Answer: 42 +USAGE + +script_name = File.basename($0) +pattern = nil +layout = nil +locals = {} + +ARGV.options do |o| + o.program_name = script_name + + # list all available template engines + o.on("-l", "--list") do + groups = {} + Tilt.lazy_map.each do |pattern,engines| + engines.each do |engine| + key = engine[0].split('::').last.sub(/Template$/, '') + (groups[key] ||= []) << pattern + end + end + groups.sort { |(k1,v1),(k2,v2)| k1 <=> k2 }.each do |engine,files| + printf "%-15s %s\n", engine, files.sort.join(', ') + end + exit + end + + # the template type / pattern + o.on("-t", "--type=PATTERN", String) do |val| + abort "unknown template type: #{val}" if Tilt[val].nil? + pattern = val + end + + # pass template output into the specified layout template + o.on("-y", "--layout=FILE", String) do |file| + paths = [file, "~/.tilt/#{file}", "/etc/tilt/#{file}"] + layout = paths. + map { |p| File.expand_path(p) }. + find { |p| File.exist?(p) } + abort "no such layout: #{file}" if layout.nil? + end + + # define a local variable + o.on("-D", "--define=PAIR", String) do |pair| + key, value = pair.split(/[=:]/, 2) + locals[key.to_sym] = value + end + + # define local variables using a Ruby hash + o.on("--vars=RUBY") do |ruby| + hash = eval(ruby) + abort "vars must be a Hash, not #{hash.inspect}" if !hash.is_a?(Hash) + hash.each { |key, value| locals[key.to_sym] = value } + end + + o.on_tail("-h", "--help") { puts usage; exit } + + o.parse! +end + +file = ARGV.first || '-' +pattern = file if pattern.nil? +abort "template type not given. see: #{$0} --help" if ['-', ''].include?(pattern) + +engine = Tilt[pattern] +abort "template engine not found for: #{pattern}" if engine.nil? + +template = + engine.new(file) { + if file == '-' + $stdin.read + else + File.read(file) + end + } +output = template.render(self, locals) + +# process layout +output = Tilt.new(layout).render(self, locals) { output } if layout + +$stdout.write(output) diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/TEMPLATES.md b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/TEMPLATES.md new file mode 100644 index 000000000..2762e567e --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/TEMPLATES.md @@ -0,0 +1,523 @@ +Tilt Templates +============== + +(See for a rendered, +HTML-version of this file). + +While all Tilt templates use the same basic interface for template loading and +evaluation, each varies in its capabilities and available options. Detailed +documentation on each supported template engine is provided below. + +There are also some file extensions that have several implementations +(currently ERB and Markdown). These template classes have certain features +which are guaranteed to work across all the implementations. If you wish to be +compatible with all of these template classes, you should only depend on the +cross-implementation features. + + * [ERB](#erb) - Generic ERB implementation (backed by erb.rb or Erubis) + * [erb.rb](#erbrb) - `Tilt::ERBTemplate` + * [Erubis](#erubis) - `Tilt::ErubisTemplate` + * [Haml](#haml) - `Tilt::HamlTemplate` + * [Liquid](#liquid) - `Tilt::LiquidTemplate` + * Nokogiri - `Tilt::NokogiriTemplate` + * Builder - `Tilt::BuilderTemplate` + * Markaby - `Tilt::MarkabyTemplate` + * [Radius](#radius) - `Tilt::RadiusTemplate` + +Tilt also includes support for CSS processors like [LessCSS][lesscss] and +[Sass][sass], [CoffeeScript][coffee-script] and some simple text formats. + + * Less - `Tilt::LessTemplate` + * Sass - `Tilt::SassTemplate` + * Scss - `Tilt::ScssTemplate` + * CoffeeScript - `Tilt::CoffeeScriptTemplate` + * [Textile](#redcloth) - `Tilt::RedClothTemplate` + * Creole - `Tilt::CreoleTemplate` + * [RDoc](#rdoc) - `Tilt::RDocTemplate` + +Tilt has extensive support for Markdown, backed by one of four different +implementations (depending on which are available on your system): + + * [Markdown](#markdown) - Generic Markdown implementation + * [RDiscount](#rdiscount) - `Tilt::RDiscountTemplate` + * Redcarpet - `Tilt::RedcarpetTemplate` + * BlueCloth - `Tilt::BlueClothTemplate` + * Kramdown - `Tilt::KramdownTemplate` + * Maruku - `Tilt::MarukuTemplate` + +
+ERB (`erb`, `rhtml`) +-------------------- + +ERB is a simple but powerful template languge for Ruby. In Tilt it's backed by +[Erubis](#erubis) (if installed on your system) or by [erb.rb](#erbrb) (which +is included in Ruby's standard library). This documentation applies to both +implementations. + +### Example + + Hello <%= world %>! + +### Usage + +ERB templates support custom evaluation scopes and locals: + + >> require 'erb' + >> template = Tilt.new('hello.html.erb') + >> template.render(self, :world => 'World!') + => "Hello World!" + +Or, use `Tilt['erb']` directly to process strings: + + template = Tilt['erb'].new { "Hello <%= world %>!" } + template.render(self, :world => 'World!') + +### Options + +#### `:trim => trim` + +Omits newlines and spaces around certain lines (usually those that starts with +`<%` and ends with `%>`). There isn't a specification for how trimming in ERB +should work, so if you need more control over the whitespace, you should use +[erb.rb](#erbrb) or [Erubis](#erubis) directly. + + +#### `:outvar => '_erbout'` + +The name of the variable used to accumulate template output. This can be +any valid Ruby expression but must be assignable. By default a local +variable named `_erbout` is used. + + +erb.rb (`erb`, `rhtml`) +----------------------- + +[ERB](#erb) implementation available in Ruby's standard library. + +All the documentation of [ERB](#erb) applies in addition to the following: + +### Usage + +The `Tilt::ERBTemplate` class is registered for all files ending in `.erb` or +`.rhtml` by default, but with a *lower* priority than ErubisTemplate. If you +specifically want to use ERB, it's recommended to use `#prefer`: + + Tilt.prefer Tilt::ERBTemplate + +__NOTE:__ It's suggested that your program `require 'erb'` at load time when +using this template engine within a threaded environment. + +### Options + +#### `:trim => true` + +The ERB trim mode flags. This is a string consisting of any combination of the +following characters: + + * `'>'` omits newlines for lines ending in `>` + * `'<>'` omits newlines for lines starting with `<%` and ending in `%>` + * `'%'` enables processing of lines beginning with `%` + * `true` is an alias of `<>` + +#### `:safe => nil` + +The `$SAFE` level; when set, ERB code will be run in a +separate thread with `$SAFE` set to the provided level. + +#### `:outvar => '_erbout'` + +The name of the variable used to accumulate template output. This can be +any valid Ruby expression but must be assignable. By default a local +variable named `_erbout` is used. + +### See also + + * [ERB documentation](http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html) + + + +Erubis (`erb`, `rhtml`, `erubis`) +--------------------------------- + +[Erubis][erubis] is a fast, secure, and very extensible implementation of [ERB](#erb). + +All the documentation of [ERB](#erb) applies in addition to the following: + +### Usage + +The `Tilt::ErubisTemplate` class is registered for all files ending in `.erb` or +`.rhtml` by default, but with a *higher* priority than `ERBTemplate`. If you +specifically want to use Erubis, it's recommended to use `#prefer`: + + Tilt.prefer Tilt::ErubisTemplate + +__NOTE:__ It's suggested that your program `require 'erubis'` at load time when +using this template engine within a threaded environment. + +### Options + +#### `:engine_class => Erubis::Eruby` + +Allows you to specify a custom engine class to use instead of the +default which is `Erubis::Eruby`. + +#### `:escape_html => false` + +When `true`, `Erubis::EscapedEruby` will be used as the engine class +instead of the default. All content within `<%= %>` blocks will be +automatically html escaped. + +#### `:outvar => '_erbout'` + +The name of the variable used to accumulate template output. This can be +any valid Ruby expression but must be assignable. By default a local +variable named `_erbout` is used. + +#### `:pattern => '<% %>'` + +Set pattern for embedded Ruby code. + +#### `:trim => true` + +Delete spaces around `<% %>`. (But, spaces around `<%= %>` are preserved.) + +### See also + + * [Erubis Home][erubis] + * [Erubis User's Guide](http://www.kuwata-lab.com/erubis/users-guide.html) + + + +Haml (`haml`) +------------- + +[Haml][haml] is a markup language that’s used to cleanly and simply describe +the HTML of any web document without the use of inline code. Haml functions as +a replacement for inline page templating systems such as PHP, ASP, and ERB, the +templating language used in most Ruby on Rails applications. However, Haml +avoids the need for explicitly coding HTML into the template, because it itself +is a description of the HTML, with some code to generate dynamic content. +([more](http://haml.info/about.html)) + + +### Example + + %html + %head + %title= @title + %body + %h1 + Hello + = world + '!' + +### Usage + +The `Tilt::HamlTemplate` class is registered for all files ending in `.haml` +by default. Haml templates support custom evaluation scopes and locals: + + >> require 'haml' + >> template = Tilt.new('hello.haml') + => # + >> @title = "Hello Haml!" + >> template.render(self, :world => 'Haml!') + => " + + + Hello Haml! + + +

Hello Haml!

+ + " + +Or, use the `Tilt::HamlTemplate` class directly to process strings: + + >> require 'haml' + >> template = Tilt::HamlTemplate.new { "%h1= 'Hello Haml!'" } + => # + >> template.render + => "

Hello Haml!

" + +__NOTE:__ It's suggested that your program `require 'haml'` at load time when +using this template engine within a threaded environment. + +### Options + +Please see the [Haml Reference](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) for all available options. + +### See also + + * [#haml.docs](http://haml.info/docs.html) + * [Haml Tutorial](http://haml.info/tutorial.html) + * [Haml Reference](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html) + + + +Liquid (`liquid`) +----------------- + +[Liquid][liquid] is for rendering safe templates which cannot affect the +security of the server they are rendered on. + +### Example + + + + {{ title }} + + +

Hello {{ world }}!

+ + + +### Usage + +`Tilt::LiquidTemplate` is registered for all files ending in `.liquid` by +default. Liquid templates support locals and objects that respond to +`#to_h` as scopes: + + >> require 'liquid' + >> require 'tilt' + >> template = Tilt.new('hello.liquid') + => # + >> scope = { :title => "Hello Liquid Templates" } + >> template.render(nil, :world => "Liquid") + => " + + + Hello Liquid Templates + + +

Hello Liquid!

+ + " + +Or, use `Tilt::LiquidTemplate` directly to process strings: + + >> require 'liquid' + >> template = Tilt::LiquidTemplate.new { "

Hello Liquid!

" } + => # + >> template.render + => "

Hello Liquid!

" + +__NOTE:__ It's suggested that your program `require 'liquid'` at load +time when using this template engine within a threaded environment. + +### See also + + * [Liquid for Programmers](https://wiki.github.com/Shopify/liquid/liquid-for-programmers) + * [Liquid Docs](http://liquid.rubyforge.org/) + * GitHub: [Shopify/liquid](https://github.com/Shopify/liquid/) + + + +Radius (`radius`) +----------------- + +[Radius][radius] is the template language used by [Radiant CMS][radiant]. It is +a tag language designed to be valid XML/HTML. + +### Example + + + +

+
    + +
  • !
  • +
    +
+ + + + +### Usage + +To render a template such as the one above. + + scope = OpenStruct.new + scope.title = "Radius Example" + scope.hello = "Hello, World!" + + require 'radius' + template = Tilt::RadiusTemplate.new('example.radius', :tag_prefix=>'r') + template.render(scope, :type=>'hlist'){ "Jackpot!" } + +The result will be: + + + +

Radius Example

+
    +
  • Hello, World!
  • +
  • Hello, World!
  • +
  • Hello, World!
  • +
+ Jackpot! + + + +### See also + + * [Radius][radius] + * [Radiant CMS][radiant] + + + +Textile (`textile`) +------------------- + +Textile is a lightweight markup language originally developed by Dean Allen and +billed as a "humane Web text generator". Textile converts its marked-up text +input to valid, well-formed XHTML and also inserts character entity references +for apostrophes, opening and closing single and double quotation marks, +ellipses and em dashes. + +Textile formatted texts are converted to HTML with the [RedCloth][redcloth] +engine, which is a Ruby extension written in C. + +### Example + + h1. Hello Textile Templates + + Hello World. This is a paragraph. + +### Usage + +__NOTE:__ It's suggested that your program `require 'redcloth'` at load time +when using this template engine in a threaded environment. + +### See Also + + * [RedCloth][redcloth] + + + +RDoc (`rdoc`) +------------- + +[RDoc][rdoc] is the simple text markup system that comes with Ruby's standard +library. + +### Example + + = Hello RDoc Templates + + Hello World. This is a paragraph. + +### Usage + +__NOTE:__ It's suggested that your program `require 'rdoc'`, +`require 'rdoc/markup'`, and `require 'rdoc/markup/to_html'` at load time +when using this template engine in a threaded environment. + +### See also + + * [RDoc][rdoc] + + + +Markdown (`markdown`, `md`, `mkd`) +---------------------------------- + +[Markdown][markdown] is a lightweight markup language, created by John Gruber +and Aaron Swartz. For any markup that is not covered by Markdown’s syntax, HTML +is used. Marking up plain text with Markdown markup is easy and Markdown +formatted texts are readable. + +Markdown formatted texts are converted to HTML with one of these libraries: + + * [RDiscount](#rdiscount) - `Tilt::RDiscountTemplate` + * Redcarpet - `Tilt::RedcarpetTemplate` + * BlueCloth - `Tilt::BlueClothTemplate` + * Kramdown - `Tilt::KramdownTemplate` + * Maruku - `Tilt::MarukuTemplate` + +Tilt will use fallback mode (as documented in the README) for determining which +library to use. RDiscount has highest priority - Maruku has lowest. + +### Example + + Hello Markdown Templates + ======================== + + Hello World. This is a paragraph. + +### Usage + +To wrap a Markdown formatted document with a layout: + + layout = Tilt['erb'].new do + "<%= yield %>" + end + data = Tilt['md'].new { "# hello tilt" } + layout.render { data.render } + # => "

hello tilt

\n" + +### Options + +Every implementation of Markdown *should* support these options, but there are +some known problems with the Kramdown and Maruku engines. + +#### `:smartypants => true|false` + +Set `true` to enable [Smarty Pants][smartypants] style punctuation replacement. + +In Kramdown this option only applies to smart quotes. It will apply a +subset of Smarty Pants (e.g. `...` to `…`) regardless of any option. + +Maruku ignores this option and always applies smart quotes (and nothing else). + +#### `:escape_html => true|false` + +Set `true` disallow raw HTML in Markdown contents. HTML is converted to +literal text by escaping `<` characters. + +Kramdown and Maruku doesn't support this option. + +### See also + + * [Markdown Syntax Documentation](http://daringfireball.net/projects/markdown/syntax/) + + +RDiscount (`markdown`, `md`, `mkd`) +----------------------------------- + +[Discount][discount] is an implementation of the Markdown markup language in C. +[RDiscount][rdiscount] is a Ruby wrapper around Discount. + +All the documentation of [Markdown](#markdown) applies in addition to the following: + +### Usage + +The `Tilt::RDiscountTemplate` class is registered for all files ending in +`.markdown`, `.md` or `.mkd` by default with the highest priority. If you +specifically want to use RDiscount, it's recommended to use `#prefer`: + + Tilt.prefer Tilt::RDiscountTemplate + +__NOTE:__ It's suggested that your program `require 'erubis'` at load time when +using this template engine within a threaded environment. + +### See also + + * [Discount][discount] + * [RDiscount][rdiscount] + * GitHub: [rtomayko/rdiscount][rdiscount] + + +[lesscss]: http://lesscss.org/ "Less CSS" +[sass]: http://sass-lang.com/ "Sass" +[coffee-script]: http://jashkenas.github.com/coffee-script/ "Coffee Script" +[erubis]: http://www.kuwata-lab.com/erubis/ "Erubis" +[haml]: http://haml.info/ "Haml" +[liquid]: http://www.liquidmarkup.org/ "Liquid" +[radius]: http://radius.rubyforge.org/ "Radius" +[radiant]: http://radiantcms.org/ "Radiant CMS" +[redcloth]: http://redcloth.org/ "RedCloth" +[rdoc]: http://rdoc.rubyforge.org/ "RDoc" +[discount]: http://www.pell.portland.or.us/~orc/Code/discount/ "Discount" +[rdiscount]: http://github.com/rtomayko/rdiscount/ "RDiscount" +[smartypants]: http://daringfireball.net/projects/smartypants/ "Smarty Pants" +[markdown]: http://en.wikipedia.org/wiki/Markdown "Markdown" + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/common.css b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/common.css new file mode 100644 index 000000000..da109e9a3 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/docs/common.css @@ -0,0 +1,14 @@ +body { + line-height: 1.5; + font-size: 14px; +} + +.frames #content { + margin: 0; +} + +#content { + margin: 0 auto; + max-width: 720px; +} + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt.rb new file mode 100644 index 000000000..1bb8f103a --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt.rb @@ -0,0 +1,137 @@ +require 'tilt/mapping' +require 'tilt/template' + +# Namespace for Tilt. This module is not intended to be included anywhere. +module Tilt + # Current version. + VERSION = '2.0.1' + + @default_mapping = Mapping.new + + # @return [Tilt::Mapping] the main mapping object + def self.default_mapping + @default_mapping + end + + # @private + def self.lazy_map + default_mapping.lazy_map + end + + # @see Tilt::Mapping#register + def self.register(template_class, *extensions) + default_mapping.register(template_class, *extensions) + end + + # @see Tilt::Mapping#register_lazy + def self.register_lazy(class_name, file, *extensions) + default_mapping.register_lazy(class_name, file, *extensions) + end + + # @deprecated Use {register} instead. + def self.prefer(template_class, *extensions) + register(template_class, *extensions) + end + + # @see Tilt::Mapping#registered? + def self.registered?(ext) + default_mapping.registered?(ext) + end + + # @see Tilt::Mapping#new + def self.new(file, line=nil, options={}, &block) + default_mapping.new(file, line, options, &block) + end + + # @see Tilt::Mapping#[] + def self.[](file) + default_mapping[file] + end + + # @see Tilt::Mapping#template_for + def self.template_for(file) + default_mapping.template_for(file) + end + + # @see Tilt::Mapping#templates_for + def self.templates_for(file) + default_mapping.templates_for(file) + end + + # @return the template object that is currently rendering. + # + # @example + # tmpl = Tilt['index.erb'].new { '<%= Tilt.current_template %>' } + # tmpl.render == tmpl.to_s + # + # @note This is currently an experimental feature and might return nil + # in the future. + def self.current_template + Thread.current[:tilt_current_template] + end + + # Extremely simple template cache implementation. Calling applications + # create a Tilt::Cache instance and use #fetch with any set of hashable + # arguments (such as those to Tilt.new): + # + # cache = Tilt::Cache.new + # cache.fetch(path, line, options) { Tilt.new(path, line, options) } + # + # Subsequent invocations return the already loaded template object. + class Cache + def initialize + @cache = {} + end + + # @see Cache + def fetch(*key) + @cache[key] ||= yield + end + + # Clears the cache. + def clear + @cache = {} + end + end + + + # Template Implementations ================================================ + + # ERB + register_lazy :ERBTemplate, 'tilt/erb', 'erb', 'rhtml' + register_lazy :ErubisTemplate, 'tilt/erubis', 'erb', 'rhtml', 'erubis' + + # Markdown + register_lazy :BlueClothTemplate, 'tilt/bluecloth', 'markdown', 'mkd', 'md' + register_lazy :MarukuTemplate, 'tilt/maruku', 'markdown', 'mkd', 'md' + register_lazy :KramdownTemplate, 'tilt/kramdown', 'markdown', 'mkd', 'md' + register_lazy :RDiscountTemplate, 'tilt/rdiscount', 'markdown', 'mkd', 'md' + register_lazy :RedcarpetTemplate, 'tilt/redcarpet', 'markdown', 'mkd', 'md' + + # Rest (sorted by name) + register_lazy :AsciidoctorTemplate, 'tilt/asciidoc', 'ad', 'adoc', 'asciidoc' + register_lazy :BuilderTemplate, 'tilt/builder', 'builder' + register_lazy :CSVTemplate, 'tilt/csv', 'rcsv' + register_lazy :CoffeeScriptTemplate, 'tilt/coffee', 'coffee' + register_lazy :CreoleTemplate, 'tilt/creole', 'wiki', 'creole' + register_lazy :EtanniTemplate, 'tilt/etanni', 'etn', 'etanni' + register_lazy :HamlTemplate, 'tilt/haml', 'haml' + register_lazy :LessTemplate, 'tilt/less', 'less' + register_lazy :LiquidTemplate, 'tilt/liquid', 'liquid' + register_lazy :MarkabyTemplate, 'tilt/markaby', 'mab' + register_lazy :NokogiriTemplate, 'tilt/nokogiri', 'nokogiri' + register_lazy :PlainTemplate, 'tilt/plain', 'html' + register_lazy :RDocTemplate, 'tilt/rdoc', 'rdoc' + register_lazy :RadiusTemplate, 'tilt/radius', 'radius' + register_lazy :RedClothTemplate, 'tilt/redcloth', 'textile' + register_lazy :SassTemplate, 'tilt/sass', 'sass' + register_lazy :ScssTemplate, 'tilt/sass', 'scss' + register_lazy :StringTemplate, 'tilt/string', 'str' + register_lazy :WikiClothTemplate, 'tilt/wikicloth', 'wiki', 'mediawiki', 'mw' + register_lazy :YajlTemplate, 'tilt/yajl', 'yajl' + + # External template engines + register_lazy 'Slim::Template', 'slim', 'slim' + register_lazy 'Tilt::HandlebarsTemplate', 'tilt/handlebars', 'handlebars', 'hbs' + register_lazy 'Tilt::OrgTemplate', 'org-ruby', 'org' +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/asciidoc.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/asciidoc.rb new file mode 100644 index 000000000..fb1ccc402 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/asciidoc.rb @@ -0,0 +1,27 @@ +require 'tilt/template' +require 'asciidoctor' + +# AsciiDoc see: http://asciidoc.org/ +module Tilt + # Asciidoctor implementation for AsciiDoc see: + # http://asciidoctor.github.com/ + # + # Asciidoctor is an open source, pure-Ruby processor for + # converting AsciiDoc documents or strings into HTML 5, + # DocBook 4.5 and other formats. + class AsciidoctorTemplate < Template + self.default_mime_type = 'text/html' + + def prepare + options[:header_footer] = false if options[:header_footer].nil? + end + + def evaluate(scope, locals, &block) + @output ||= Asciidoctor.render(data, options, &block) + end + + def allows_script? + false + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/bluecloth.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/bluecloth.rb new file mode 100644 index 000000000..7ed59c71b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/bluecloth.rb @@ -0,0 +1,24 @@ +require 'tilt/template' +require 'bluecloth' + +module Tilt + # BlueCloth Markdown implementation. See: + # http://deveiate.org/projects/BlueCloth/ + class BlueClothTemplate < Template + self.default_mime_type = 'text/html' + + def prepare + @engine = BlueCloth.new(data, options) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/builder.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/builder.rb new file mode 100644 index 000000000..5a7ba6ec9 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/builder.rb @@ -0,0 +1,33 @@ +require 'tilt/template' +require 'builder' + +module Tilt + # Builder template implementation. See: + # http://builder.rubyforge.org/ + class BuilderTemplate < Template + self.default_mime_type = 'text/xml' + + def prepare; end + + def evaluate(scope, locals, &block) + return super(scope, locals, &block) if data.respond_to?(:to_str) + xml = ::Builder::XmlMarkup.new(:indent => 2) + data.call(xml) + xml.target! + end + + def precompiled_preamble(locals) + return super if locals.include? :xml + "xml = ::Builder::XmlMarkup.new(:indent => 2)\n#{super}" + end + + def precompiled_postamble(locals) + "xml.target!" + end + + def precompiled_template(locals) + data.to_str + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/coffee.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/coffee.rb new file mode 100644 index 000000000..dd97485a4 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/coffee.rb @@ -0,0 +1,47 @@ +require 'tilt/template' +require 'coffee_script' + +module Tilt + # CoffeeScript template implementation. See: + # http://coffeescript.org/ + # + # CoffeeScript templates do not support object scopes, locals, or yield. + class CoffeeScriptTemplate < Template + self.default_mime_type = 'application/javascript' + + @@default_bare = false + + def self.default_bare + @@default_bare + end + + def self.default_bare=(value) + @@default_bare = value + end + + # DEPRECATED + def self.default_no_wrap + @@default_bare + end + + # DEPRECATED + def self.default_no_wrap=(value) + @@default_bare = value + end + + def prepare + if !options.key?(:bare) and !options.key?(:no_wrap) + options[:bare] = self.class.default_bare + end + end + + def evaluate(scope, locals, &block) + @output ||= CoffeeScript.compile(data, options) + end + + def allows_script? + false + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/creole.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/creole.rb new file mode 100644 index 000000000..04cff16f7 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/creole.rb @@ -0,0 +1,25 @@ +require 'tilt/template' +require 'creole' + +module Tilt + # Creole implementation. See: + # http://www.wikicreole.org/ + class CreoleTemplate < Template + def prepare + opts = {} + [:allowed_schemes, :extensions, :no_escape].each do |k| + opts[k] = options[k] if options[k] + end + @engine = Creole::Parser.new(data, opts) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/csv.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/csv.rb new file mode 100644 index 000000000..501b4e28c --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/csv.rb @@ -0,0 +1,65 @@ +require 'tilt/template' + +if RUBY_VERSION >= '1.9.0' + require 'csv' +else + require 'fastercsv' +end + +module Tilt + + # CSV Template implementation. See: + # http://ruby-doc.org/stdlib/libdoc/csv/rdoc/CSV.html + # + # == Example + # + # # Example of csv template + # tpl = <<-EOS + # # header + # csv << ['NAME', 'ID'] + # + # # data rows + # @people.each do |person| + # csv << [person[:name], person[:id]] + # end + # EOS + # + # @people = [ + # {:name => "Joshua Peek", :id => 1}, + # {:name => "Ryan Tomayko", :id => 2}, + # {:name => "Simone Carletti", :id => 3} + # ] + # + # template = Tilt::CSVTemplate.new { tpl } + # template.render(self) + # + class CSVTemplate < Template + self.default_mime_type = 'text/csv' + + def self.engine + if RUBY_VERSION >= '1.9.0' && defined? ::CSV + ::CSV + elsif defined? ::FasterCSV + ::FasterCSV + end + end + + def prepare + @code =<<-RUBY + #{self.class.engine}.generate do |csv| + #{data} + end + RUBY + end + + def precompiled_template(locals) + @code + end + + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erb.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erb.rb new file mode 100644 index 000000000..1112f6107 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erb.rb @@ -0,0 +1,57 @@ +require 'tilt/template' +require 'erb' + +module Tilt + # ERB template implementation. See: + # http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html + class ERBTemplate < Template + @@default_output_variable = '_erbout' + + def self.default_output_variable + @@default_output_variable + end + + def self.default_output_variable=(name) + warn "#{self}.default_output_variable= has been replaced with the :outvar-option" + @@default_output_variable = name + end + + def prepare + @outvar = options[:outvar] || self.class.default_output_variable + options[:trim] = '<>' if !(options[:trim] == false) && (options[:trim].nil? || options[:trim] == true) + @engine = ::ERB.new(data, options[:safe], options[:trim], @outvar) + end + + def precompiled_template(locals) + source = @engine.src + source + end + + def precompiled_preamble(locals) + <<-RUBY + begin + __original_outvar = #{@outvar} if defined?(#{@outvar}) + #{super} + RUBY + end + + def precompiled_postamble(locals) + <<-RUBY + #{super} + ensure + #{@outvar} = __original_outvar + end + RUBY + end + + # ERB generates a line to specify the character coding of the generated + # source in 1.9. Account for this in the line offset. + if RUBY_VERSION >= '1.9.0' + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erubis.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erubis.rb new file mode 100644 index 000000000..e1df20235 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/erubis.rb @@ -0,0 +1,43 @@ +require 'tilt/erb' +require 'erubis' + +module Tilt + # Erubis template implementation. See: + # http://www.kuwata-lab.com/erubis/ + # + # ErubisTemplate supports the following additional options, which are not + # passed down to the Erubis engine: + # + # :engine_class allows you to specify a custom engine class to use + # instead of the default (which is ::Erubis::Eruby). + # + # :escape_html when true, ::Erubis::EscapedEruby will be used as + # the engine class instead of the default. All content + # within <%= %> blocks will be automatically html escaped. + class ErubisTemplate < ERBTemplate + def prepare + @outvar = options.delete(:outvar) || self.class.default_output_variable + @options.merge!(:preamble => false, :postamble => false, :bufvar => @outvar) + engine_class = options.delete(:engine_class) + engine_class = ::Erubis::EscapedEruby if options.delete(:escape_html) + @engine = (engine_class || ::Erubis::Eruby).new(data, options) + end + + def precompiled_preamble(locals) + [super, "#{@outvar} = _buf = ''"].join("\n") + end + + def precompiled_postamble(locals) + [@outvar, super].join("\n") + end + + # Erubis doesn't have ERB's line-off-by-one under 1.9 problem. + # Override and adjust back. + if RUBY_VERSION >= '1.9.0' + def precompiled(locals) + source, offset = super + [source, offset - 1] + end + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/etanni.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/etanni.rb new file mode 100644 index 000000000..e598ceddc --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/etanni.rb @@ -0,0 +1,27 @@ +require 'tilt/template' + +module Tilt + class EtanniTemplate < Template + def prepare + separator = data.hash.abs + chomp = "<<#{separator}.chomp!" + start = "\n_out_ << #{chomp}\n" + stop = "\n#{separator}\n" + replacement = "#{stop}\\1#{start}" + + temp = data.strip + temp.gsub!(/<\?r\s+(.*?)\s+\?>/m, replacement) + + @code = "_out_ = [<<#{separator}.chomp!]\n#{temp}#{stop}_out_.join" + end + + def precompiled_template(locals) + @code + end + + def precompiled(locals) + source, offset = super + [source, offset + 1] + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/haml.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/haml.rb new file mode 100644 index 000000000..595b4ec39 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/haml.rb @@ -0,0 +1,57 @@ +require 'tilt/template' +require 'haml' + +module Tilt + # Haml template implementation. See: + # http://haml.hamptoncatlin.com/ + class HamlTemplate < Template + self.default_mime_type = 'text/html' + + def prepare + options = @options.merge(:filename => eval_file, :line => line) + @engine = ::Haml::Engine.new(data, options) + end + + def evaluate(scope, locals, &block) + if @engine.respond_to?(:precompiled_method_return_value, true) + super + else + @engine.render(scope, locals, &block) + end + end + + # Precompiled Haml source. Taken from the precompiled_with_ambles + # method in Haml::Precompiler: + # http://github.com/nex3/haml/blob/master/lib/haml/precompiler.rb#L111-126 + def precompiled_template(locals) + @engine.precompiled + end + + def precompiled_preamble(locals) + local_assigns = super + @engine.instance_eval do + <<-RUBY + begin + extend Haml::Helpers + _hamlout = @haml_buffer = Haml::Buffer.new(@haml_buffer, #{options_for_buffer.inspect}) + _erbout = _hamlout.buffer + __in_erb_template = true + _haml_locals = locals + #{local_assigns} + RUBY + end + end + + def precompiled_postamble(locals) + @engine.instance_eval do + <<-RUBY + #{precompiled_method_return_value} + ensure + @haml_buffer = @haml_buffer.upper + end + RUBY + end + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/kramdown.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/kramdown.rb new file mode 100644 index 000000000..6273805a6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/kramdown.rb @@ -0,0 +1,33 @@ +require 'tilt/template' +require 'kramdown' + +module Tilt + # Kramdown Markdown implementation. See: + # http://kramdown.rubyforge.org/ + class KramdownTemplate < Template + DUMB_QUOTES = [39, 39, 34, 34] + + def self.engine_initialized? + defined? ::Kramdown + end + + def initialize_engine + require_template_library 'kramdown' + end + + def prepare + options[:smart_quotes] = DUMB_QUOTES unless options[:smartypants] + @engine = Kramdown::Document.new(data, options) + @output = nil + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_html + end + + def allows_script? + false + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/less.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/less.rb new file mode 100644 index 000000000..1b5e704e8 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/less.rb @@ -0,0 +1,38 @@ +require 'tilt/template' +require 'less' + +module Tilt + # Lessscss template implementation. See: + # http://lesscss.org/ + # + # Less templates do not support object scopes, locals, or yield. + class LessTemplate < Template + self.default_mime_type = 'text/css' + + def self.engine_initialized? + defined? ::Less + end + + def initialize_engine + require_template_library 'less' + end + + def prepare + if ::Less.const_defined? :Engine + @engine = ::Less::Engine.new(data) + else + parser = ::Less::Parser.new(options.merge :filename => eval_file, :line => line) + @engine = parser.parse(data) + end + end + + def evaluate(scope, locals, &block) + @output ||= @engine.to_css(options) + end + + def allows_script? + false + end + end +end + diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/liquid.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/liquid.rb new file mode 100644 index 000000000..1e9d7637b --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/liquid.rb @@ -0,0 +1,38 @@ +require 'tilt/template' +require 'liquid' + +module Tilt + # Liquid template implementation. See: + # http://liquid.rubyforge.org/ + # + # Liquid is designed to be a *safe* template system and threfore + # does not provide direct access to execuatable scopes. In order to + # support a +scope+, the +scope+ must be able to represent itself + # as a hash by responding to #to_h. If the +scope+ does not respond + # to #to_h it will be ignored. + # + # LiquidTemplate does not support yield blocks. + # + # It's suggested that your program require 'liquid' at load + # time when using this template engine. + class LiquidTemplate < Template + def prepare + @engine = ::Liquid::Template.parse(data) + end + + def evaluate(scope, locals, &block) + locals = locals.inject({}){ |h,(k,v)| h[k.to_s] = v ; h } + if scope.respond_to?(:to_h) + scope = scope.to_h.inject({}){ |h,(k,v)| h[k.to_s] = v ; h } + locals = scope.merge(locals) + end + locals['yield'] = block.nil? ? '' : yield + locals['content'] = locals['yield'] + @engine.render(locals) + end + + def allows_script? + false + end + end +end diff --git a/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/mapping.rb b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/mapping.rb new file mode 100644 index 000000000..cca0289d6 --- /dev/null +++ b/hello-sinatra/bundle/ruby/2.2.0/gems/tilt-2.0.1/lib/tilt/mapping.rb @@ -0,0 +1,265 @@ +module Tilt + # Tilt::Mapping associates file extensions with template implementations. + # + # mapping = Tilt::Mapping.new + # mapping.register(Tilt::RDocTemplate, 'rdoc') + # mapping['index.rdoc'] # => Tilt::RDocTemplate + # mapping.new('index.rdoc').render + # + # You can use {#register} to register a template class by file + # extension, {#registered?} to see if a file extension is mapped, + # {#[]} to lookup template classes, and {#new} to instantiate template + # objects. + # + # Mapping also supports *lazy* template implementations. Note that regularly + # registered template implementations *always* have preference over lazily + # registered template implementations. You should use {#register} if you + # depend on a specific template implementation and {#register_lazy} if there + # are multiple alternatives. + # + # mapping = Tilt::Mapping.new + # mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') + # mapping['index.md'] + # # => RDiscount::Template + # + # {#register_lazy} takes a class name, a filename, and a list of file + # extensions. When you try to lookup a template name that matches the + # file extension, Tilt will automatically try to require the filename and + # constantize the class name. + # + # Unlike {#register}, there can be multiple template implementations + # registered lazily to the same file extension. Tilt will attempt to load the + # template implementations in order (registered *last* would be tried first), + # returning the first which doesn't raise LoadError. + # + # If all of the registered template implementations fails, Tilt will raise + # the exception of the first, since that was the most preferred one. + # + # mapping = Tilt::Mapping.new + # mapping.register_lazy('Bluecloth::Template', 'bluecloth/template', 'md') + # mapping.register_lazy('RDiscount::Template', 'rdiscount/template', 'md') + # mapping['index.md'] + # # => RDiscount::Template + # + # In the previous example we say that RDiscount has a *higher priority* than + # BlueCloth. Tilt will first try to `require "rdiscount/template"`, falling + # back to `require "bluecloth/template"`. If none of these are successful, + # the first error will be raised. + class Mapping + # @private + attr_reader :lazy_map, :template_map + + def initialize + @template_map = Hash.new + @lazy_map = Hash.new { |h, k| h[k] = [] } + end + + # @private + def initialize_copy(other) + @template_map = other.template_map.dup + @lazy_map = other.lazy_map.dup + end + + # Registrers a lazy template implementation by file extension. You + # can have multiple lazy template implementations defined on the + # same file extension, in which case the template implementation + # defined *last* will be attempted loaded *first*. + # + # @param class_name [String] Class name of a template class. + # @param file [String] Filename where the template class is defined. + # @param extensions [Array] List of extensions. + # @return [void] + # + # @example + # mapping.register_lazy 'MyEngine::Template', 'my_engine/template', 'mt' + # + # defined?(MyEngine::Template) # => false + # mapping['index.mt'] # => MyEngine::Template + # defined?(MyEngine::Template) # => true + def register_lazy(class_name, file, *extensions) + # Internal API + if class_name.is_a?(Symbol) + Tilt.autoload class_name, file + class_name = "Tilt::#{class_name}" + end + + extensions.each do |ext| + @lazy_map[ext].unshift([class_name, file]) + end + end + + # Registers a template implementation by file extension. There can only be + # one template implementation per file extension, and this method will + # override any existing mapping. + # + # @param template_class + # @param extensions [Array] List of extensions. + # @return [void] + # + # @example + # mapping.register MyEngine::Template, 'mt' + # mapping['index.mt'] # => MyEngine::Template + def register(template_class, *extensions) + if template_class.respond_to?(:to_str) + # Support register(ext, template_class) too + extensions, template_class = [template_class], extensions[0] + end + + extensions.each do |ext| + @template_map[ext.to_s] = template_class + end + end + + # Checks if a file extension is registered (either eagerly or + # lazily) in this mapping. + # + # @param ext [String] File extension. + # + # @example + # mapping.registered?('erb') # => true + # mapping.registered?('nope') # => false + def registered?(ext) + @template_map.has_key?(ext.downcase) or lazy?(ext) + end + + # Instantiates a new template class based on the file. + # + # @raise [RuntimeError] if there is no template class registered for the + # file name. + # + # @example + # mapping.new('index.mt') # => instance of MyEngine::Template + # + # @see Tilt::Template.new + def new(file, line=nil, options={}, &block) + if template_class = self[file] + template_class.new(file, line, options, &block) + else + fail "No template engine registered for #{File.basename(file)}" + end + end + + # Looks up a template class based on file name and/or extension. + # + # @example + # mapping['views/hello.erb'] # => Tilt::ERBTemplate + # mapping['hello.erb'] # => Tilt::ERBTemplate + # mapping['erb'] # => Tilt::ERBTemplate + # + # @return [template class] + def [](file) + _, ext = split(file) + ext && lookup(ext) + end + + alias template_for [] + + # Looks up a list of template classes based on file name. If the file name + # has multiple extensions, it will return all template classes matching the + # extensions from the end. + # + # @example + # mapping.templates_for('views/index.haml.erb') + # # => [Tilt::ERBTemplate, Tilt::HamlTemplate] + # + # @return [Array