1
0
Fork 0
mirror of https://github.com/sinatra/sinatra synced 2023-03-27 23:18:01 -04:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
michelc 2011-10-28 14:15:59 +02:00
commit cf27ad4196
22 changed files with 876 additions and 123 deletions

View file

@ -7,9 +7,9 @@ rvm:
- jruby - jruby
- ruby-head - ruby-head
env: env:
- "rack=1.3.0" - "rack=1.3.4"
- "rack=master" - "rack=master"
- "tilt=1.3.2" - "tilt=1.3.3"
- "tilt=master" - "tilt=master"
notifications: notifications:
recipients: recipients:

37
CHANGES
View file

@ -1,4 +1,23 @@
= 1.3.0 / Not Yet Released = 1.3.2 / Not Yet Released
* 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)
* 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.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 * Added `stream` helper method for easily creating streaming APIs, Server
Sent Events or even WebSockets. See README for more on that topic. Sent Events or even WebSockets. See README for more on that topic.
@ -12,7 +31,7 @@
* Added support for HTTP PATCH requests. (Konstantin Haase) * Added support for HTTP PATCH requests. (Konstantin Haase)
* Use rack-protection to defend against common opportunistic attacks. * Use rack-protection to defend against common opportunistic attacks.
(Konstantin Haase) (Josh Lane, Jacob Burkhart, Konstantin Haase)
* Support for Creole templates, Creole is a standardized wiki markup, * Support for Creole templates, Creole is a standardized wiki markup,
supported by many wiki implementations. (Konstanin Haase) supported by many wiki implementations. (Konstanin Haase)
@ -82,6 +101,11 @@
* Conditional requests on `etag` helper now work properly for unsafe HTTP * Conditional requests on `etag` helper now work properly for unsafe HTTP
methods. (Matthew Schinckel, Konstantin Haase) 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 * `Sinatra::Base.run!` now prints to stderr rather than stdout. (Andrew
Armenia) Armenia)
@ -120,7 +144,14 @@
* Fix handling of broken query params when displaying exceptions. (Luke * Fix handling of broken query params when displaying exceptions. (Luke
Jahnke) Jahnke)
= 1.2.7 (backports release) / Not Yet Released = 1.2.8 / Not Yet Released
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: Custom changes:

40
Gemfile
View file

@ -19,9 +19,14 @@ gem 'ci_reporter', :group => :ci
github = "git://github.com/%s.git" github = "git://github.com/%s.git"
repos = { 'tilt' => github % "rtomayko/tilt", 'rack' => github % "rack/rack" } repos = { 'tilt' => github % "rtomayko/tilt", 'rack' => github % "rack/rack" }
%w[tilt rack].each do |lib| %w[tilt rack].each do |lib|
dep = (ENV[lib] || 'stable').sub "#{lib}-", '' dep = case ENV[lib] || 'stable'
dep = nil if dep == 'stable' when 'stable'
dep = {:git => repos[lib], :branch => dep} if dep and dep !~ /(\d+\.)+\d+/ nil
when /(\d+\.)+\d+/
"~> " + ENV[lib].sub("#{lib}-", '')
else
{:git => repos[lib], :branch => dep}
end
gem lib, dep gem lib, dep
end end
@ -29,17 +34,10 @@ gem 'haml', '>= 3.0'
gem 'sass' gem 'sass'
gem 'builder' gem 'builder'
gem 'erubis' gem 'erubis'
gem 'less', '~> 1.0' gem 'liquid'
if RUBY_ENGINE == "maglev"
gem 'liquid', :git => "https://github.com/Shopify/liquid.git"
else
gem 'liquid'
end
gem 'slim', '~> 1.0' gem 'slim', '~> 1.0'
gem 'temple', '!= 0.3.3' gem 'temple', '!= 0.3.3'
gem 'RedCloth' if RUBY_VERSION < "1.9.3" and not RUBY_ENGINE.start_with? 'ma' gem 'RedCloth' if RUBY_VERSION < "1.9.3" and not RUBY_ENGINE == "macruby"
gem 'coffee-script', '>= 2.0' gem 'coffee-script', '>= 2.0'
gem 'rdoc' gem 'rdoc'
gem 'kramdown' gem 'kramdown'
@ -49,10 +47,16 @@ gem 'creole'
if RUBY_ENGINE == 'jruby' if RUBY_ENGINE == 'jruby'
gem 'nokogiri', '!= 1.5.0' gem 'nokogiri', '!= 1.5.0'
gem 'jruby-openssl' gem 'jruby-openssl'
elsif RUBY_ENGINE != 'maglev' else
gem 'nokogiri' gem 'nokogiri'
end end
if RUBY_ENGINE == "ruby"
gem 'less', '~> 2.0'
else
gem 'less', '~> 1.0'
end
unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS'] unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS']
# C extensions # C extensions
gem 'rdiscount' gem 'rdiscount'
@ -62,16 +66,10 @@ unless RUBY_ENGINE == 'jruby' && JRUBY_VERSION < "1.6.1" && !ENV['TRAVIS']
#gem 'bluecloth' #gem 'bluecloth'
end end
if RUBY_ENGINE == 'maglev' platforms :ruby_18, :jruby do
gem 'json', :git => "https://github.com/MagLev/json.git" gem 'json'
gem 'markaby' gem 'markaby'
gem 'radius' gem 'radius'
else
platforms :ruby_18, :jruby do
gem 'json'
gem 'markaby'
gem 'radius'
end
end end
platforms :mri_18 do platforms :mri_18 do

View file

@ -1953,9 +1953,9 @@ SemVer und SemVerTag.
* {Mailing-Liste}[http://groups.google.com/group/sinatrarb] * {Mailing-Liste}[http://groups.google.com/group/sinatrarb]
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] auf http://freenode.net * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] auf http://freenode.net
* {Sinatra Book}[http://sinatra-book.gittr.com] Kochbuch Tutorial * {Sinatra Book}[http://sinatra-book.gittr.com] Kochbuch Tutorial
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Sinatra-Rezepte aus * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Sinatra-Rezepte aus
der Community der Community
* API Dokumentation für die {aktuelle Version}[http://rubydoc.info/gems/sinatra] * API Dokumentation für die {aktuelle Version}[http://rubydoc.info/gems/sinatra]
oder für {HEAD}[http://rubydoc.info/github/sinatra/sinatra] auf oder für {HEAD}[http://rubydoc.info/github/sinatra/sinatra] auf
http://rubydoc.info http://rubydoc.info
* {CI Server}[http://ci.rkh.im/view/Sinatra/] * {CI Server}[http://ci.rkh.im/view/Sinatra/]

View file

@ -916,11 +916,16 @@ para <tt>Sinatra::Application</tt>. Si heredaste de
<tt>Sinatra::Base</tt>, probablemente quieras habilitarlo manualmente: <tt>Sinatra::Base</tt>, probablemente quieras habilitarlo manualmente:
class MiApp < Sinatra::Base class MiApp < Sinatra::Base
configure(:production, :development) do configure :production, :development do
enable :logging enable :logging
end end
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 <tt>env['rack.logger']</tt>.
=== Tipos Mime === Tipos Mime
Cuando usás <tt>send_file</tt> o archivos estáticos tal vez tengas tipos mime Cuando usás <tt>send_file</tt> o archivos estáticos tal vez tengas tipos mime
@ -1055,6 +1060,23 @@ Usá la configuración <tt>:static_cache_control</tt> para agregar el encabezado
<tt>Cache-Control</tt> a archivos estáticos (ver la sección de configuración <tt>Cache-Control</tt> a archivos estáticos (ver la sección de configuración
para más detalles). 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 <tt>*</tt> cuando el
recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get)
e idempotentes (como put) que el recurso existe, mientras que para el resto
(como post), que no. Podes cambiar este comportamiento con la opción
<tt>:new_resource</tt>:
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 <tt>:kind</tt>:
etag '', :new_resource => true, :kind => :weak
=== Enviando Archivos === Enviando Archivos
Para enviar archivos, podés usar el método <tt>send_file</tt>: Para enviar archivos, podés usar el método <tt>send_file</tt>:
@ -1283,7 +1305,7 @@ Podés acceder a estas opciones utilizando el método <tt>settings</tt>:
... ...
end end
==== Configurando la Protección de Ataques === Configurando la Protección de Ataques
Sinatra usa {Rack::Protection}[https://github.com/rkh/rack-protection#readme] Sinatra usa {Rack::Protection}[https://github.com/rkh/rack-protection#readme]
para defender a tu aplicación de los ataques más comunes. Tenés que tener en para defender a tu aplicación de los ataques más comunes. Tenés que tener en
@ -1987,7 +2009,7 @@ siguiendo las especificaciones SemVer y SemVerTag.
* {Lista de Correo}[http://groups.google.com/group/sinatrarb/topics] * {Lista de Correo}[http://groups.google.com/group/sinatrarb/topics]
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] en http://freenode.net * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] en http://freenode.net
* {Sinatra Book}[http://sinatra-book.gittr.com] Tutorial (en inglés). * {Sinatra Book}[http://sinatra-book.gittr.com] Tutorial (en inglés).
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Recetas contribuidas * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Recetas contribuidas
por la comunidad (en inglés). por la comunidad (en inglés).
* Documentación de la API para la * Documentación de la API para la
{última versión liberada}[http://rubydoc.info/gems/sinatra] o para la {última versión liberada}[http://rubydoc.info/gems/sinatra] o para la

View file

@ -1988,9 +1988,9 @@ SemVer que SemVerTag.
* {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] sur http://freenode.net * {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] sur http://freenode.net
* {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net * {IRC : #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
* {Sinatra Book}[http://sinatra-book.gittr.com] Tutoriels et recettes * {Sinatra Book}[http://sinatra-book.gittr.com] Tutoriels et recettes
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Recettes contribuées * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Recettes contribuées
par la communauté par la communauté
* Documentation API de la {dernière version}[http://rubydoc.info/gems/sinatra] * Documentation API de la {dernière version}[http://rubydoc.info/gems/sinatra]
ou du {HEAD courant}[http://rubydoc.info/github/sinatra/sinatra] sur ou du {HEAD courant}[http://rubydoc.info/github/sinatra/sinatra] sur
http://rubydoc.info http://rubydoc.info
* {CI server}[http://ci.rkh.im/view/Sinatra/] * {CI server}[http://ci.rkh.im/view/Sinatra/]

View file

@ -376,6 +376,7 @@ textileからメソッドを呼び出すことも、localsに変数を渡すこ
RDocテンプレートを使うにはRDocライブラリが必要です: RDocテンプレートを使うにはRDocライブラリが必要です:
# rdoc/markup/to_htmlを読み込みます # rdoc/markup/to_htmlを読み込みます
require "rdoc"
require "rdoc/markup/to_html" require "rdoc/markup/to_html"
get '/' do get '/' do

View file

@ -885,11 +885,16 @@ default, so if you inherit from <tt>Sinatra::Base</tt>, you probably want to
enable it yourself: enable it yourself:
class MyApp < Sinatra::Base class MyApp < Sinatra::Base
configure(:production, :development) do configure :production, :development do
enable :logging enable :logging
end end
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 <tt>env['rack.logger']</tt>.
=== Mime Types === Mime Types
When using <tt>send_file</tt> or static files you may have mime types Sinatra When using <tt>send_file</tt> or static files you may have mime types Sinatra
@ -1018,6 +1023,23 @@ try {rack-cache}[http://rtomayko.github.com/rack-cache/]:
Use the <tt>:static_cache_control</tt> setting (see below) to add Use the <tt>:static_cache_control</tt> setting (see below) to add
<tt>Cache-Control</tt> header info to static files. <tt>Cache-Control</tt> 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 <tt>*</tt> 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 for post requests), are treated as new resources. You
can change this behavior by passing in a <tt>:new_resource</tt> option:
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 <tt>:kind</tt> option:
etag '', :new_resource => true, :kind => :weak
=== Sending Files === Sending Files
For sending files, you can use the <tt>send_file</tt> helper method: For sending files, you can use the <tt>send_file</tt> helper method:
@ -1925,7 +1947,7 @@ SemVerTag.
* {Mailing List}[http://groups.google.com/group/sinatrarb/topics] * {Mailing List}[http://groups.google.com/group/sinatrarb/topics]
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] on http://freenode.net
* {Sinatra Book}[http://sinatra-book.gittr.com] Cookbook Tutorial * {Sinatra Book}[http://sinatra-book.gittr.com] Cookbook Tutorial
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] Community * {Sinatra Recipes}[http://recipes.sinatrarb.com/] Community
contributed recipes contributed recipes
* API documentation for the {latest release}[http://rubydoc.info/gems/sinatra] * API documentation for the {latest release}[http://rubydoc.info/gems/sinatra]
or the {current HEAD}[http://rubydoc.info/github/sinatra/sinatra] on or the {current HEAD}[http://rubydoc.info/github/sinatra/sinatra] on

View file

@ -1779,7 +1779,7 @@ SemVerTag.
* {Группы рассылки}[http://groups.google.com/group/sinatrarb/topics] * {Группы рассылки}[http://groups.google.com/group/sinatrarb/topics]
* {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] на http://freenode.net * {IRC: #sinatra}[irc://chat.freenode.net/#sinatra] на http://freenode.net
* {Sinatra Book}[http://sinatra-book.gittr.com] учебник и сборник рецептов * {Sinatra Book}[http://sinatra-book.gittr.com] учебник и сборник рецептов
* {Sinatra Book Contrib}[http://sinatra-book-contrib.com/] сборник рецептов * {Sinatra Recipes}[http://recipes.sinatrarb.com/] сборник рецептов
* API документация к {последнему релизу}[http://rubydoc.info/gems/sinatra] * API документация к {последнему релизу}[http://rubydoc.info/gems/sinatra]
или {текущему HEAD}[http://rubydoc.info/github/sinatra/sinatra] на или {текущему HEAD}[http://rubydoc.info/github/sinatra/sinatra] на
http://rubydoc.info http://rubydoc.info

View file

@ -492,6 +492,7 @@ Rack body对象或者HTTP状态码
需要引入 <tt>RDoc</tt> gem/library 以渲染RDoc模板 需要引入 <tt>RDoc</tt> gem/library 以渲染RDoc模板
# 需要在你的应用中引入rdoc/markup/to_html # 需要在你的应用中引入rdoc/markup/to_html
require "rdoc"
require "rdoc/markup/to_html" require "rdoc/markup/to_html"
get '/' do get '/' do

View file

@ -164,6 +164,10 @@ if defined?(Gem)
end end
task 'release' => ['test', package('.gem')] do task 'release' => ['test', package('.gem')] do
if File.read("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i
fail 'please update changes first'
end
sh <<-SH sh <<-SH
gem install #{package('.gem')} --local && gem install #{package('.gem')} --local &&
gem push #{package('.gem')} && gem push #{package('.gem')} &&

View file

@ -51,7 +51,7 @@ module Sinatra
private private
def accept_entry(entry) def accept_entry(entry)
type, *options = entry.gsub(/\s/, '').split(';') type, *options = entry.delete(' ').split(';')
quality = 0 # we sort smalles first quality = 0 # we sort smalles first
options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
[type, [quality, type.count('*'), 1 - options.size]] [type, [quality, type.count('*'), 1 - options.size]]
@ -247,11 +247,14 @@ module Sinatra
def self.defer(*) yield end def self.defer(*) yield end
def initialize(scheduler = self.class, keep_open = false, &back) def initialize(scheduler = self.class, keep_open = false, &back)
@back, @scheduler, @callback, @keep_open = back.to_proc, scheduler, nil, keep_open @back, @scheduler, @keep_open = back.to_proc, scheduler, keep_open
@callbacks, @closed = [], false
end end
def close def close
@scheduler.schedule { @callback.call if @callback } return if @closed
@closed = true
@scheduler.schedule { @callbacks.each { |c| c.call }}
end end
def each(&front) def each(&front)
@ -272,7 +275,7 @@ module Sinatra
end end
def callback(&block) def callback(&block)
@callback = block @callbacks << block
end end
alias errback callback alias errback callback
@ -308,7 +311,7 @@ module Sinatra
hash = {} hash = {}
end end
values = values.map { |value| value.to_s.tr('_','-') } values.map! { |value| value.to_s.tr('_','-') }
hash.each do |key, value| hash.each do |key, value|
key = key.to_s.tr('_', '-') key = key.to_s.tr('_', '-')
value = value.to_i if key == "max-age" value = value.to_i if key == "max-age"
@ -355,8 +358,19 @@ module Sinatra
return unless time return unless time
time = time_for time time = time_for time
response['Last-Modified'] = time.httpdate response['Last-Modified'] = time.httpdate
# compare based on seconds since epoch return if env['HTTP_IF_NONE_MATCH']
halt 304 if Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i >= time.to_i
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 rescue ArgumentError
end end
@ -369,18 +383,27 @@ module Sinatra
# When the current request includes an 'If-None-Match' header with a # When the current request includes an 'If-None-Match' header with a
# matching etag, execution is immediately halted. If the request method is # matching etag, execution is immediately halted. If the request method is
# GET or HEAD, a '304 Not Modified' response is sent. # GET or HEAD, a '304 Not Modified' response is sent.
def etag(value, kind = :strong) def etag(value, options = {})
raise ArgumentError, ":strong or :weak expected" unless [:strong,:weak].include?(kind) # 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 [:strong, :weak].include?(kind)
raise ArgumentError, ":strong or :weak expected"
end
value = '"%s"' % value value = '"%s"' % value
value = 'W/' + value if kind == :weak value = 'W/' + value if kind == :weak
response['ETag'] = value response['ETag'] = value
if etags = env['HTTP_IF_NONE_MATCH'] if success? or status == 304
etags = etags.split(/\s*,\s*/) if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource
if etags.include?(value) or etags.include?('*') halt(request.safe? ? 304 : 412)
halt 304 if request.safe? end
else
halt 412 unless request.safe? if env['HTTP_IF_MATCH']
halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource
end end
end end
end end
@ -445,6 +468,14 @@ module Sinatra
rescue Exception rescue Exception
raise ArgumentError, "unable to convert #{value.inspect} to a Time object" raise ArgumentError, "unable to convert #{value.inspect} to a Time object"
end 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
end end
private private
@ -588,11 +619,14 @@ module Sinatra
scope = options.delete(:scope) || self scope = options.delete(:scope) || self
# compile and render template # compile and render template
layout_was = @default_layout begin
@default_layout = false layout_was = @default_layout
template = compile_template(engine, data, options, views) @default_layout = false
output = template.render(scope, locals, &block) template = compile_template(engine, data, options, views)
@default_layout = layout_was output = template.render(scope, locals, &block)
ensure
@default_layout = layout_was
end
# render layout # render layout
if layout if layout
@ -817,7 +851,7 @@ module Sinatra
return unless path.start_with?(public_dir) and File.file?(path) return unless path.start_with?(public_dir) and File.file?(path)
env['sinatra.static_file'] = path env['sinatra.static_file'] = path
cache_control *settings.static_cache_control if settings.static_cache_control? cache_control(*settings.static_cache_control) if settings.static_cache_control?
send_file path, :disposition => nil send_file path, :disposition => nil
end end
@ -1184,9 +1218,8 @@ module Sinatra
def compile(path) def compile(path)
keys = [] keys = []
if path.respond_to? :to_str if path.respond_to? :to_str
special_chars = %w{. + ( ) $}
pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) } pattern = path.to_str.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c) }
pattern.gsub! /((:\w+)|\*)/ do |match| pattern.gsub!(/((:\w+)|\*)/) do |match|
if match == "*" if match == "*"
keys << 'splat' keys << 'splat'
"(.*?)" "(.*?)"
@ -1320,21 +1353,34 @@ module Sinatra
def setup_logging(builder) def setup_logging(builder)
if logging? if logging?
builder.use Rack::CommonLogger setup_common_logger(builder)
if logging.respond_to? :to_int setup_custom_logger(builder)
builder.use Rack::Logger, logging elsif logging == false
else setup_null_logger(builder)
builder.use Rack::Logger end
end end
def setup_null_logger(builder)
builder.use Rack::NullLogger
end
def setup_common_logger(builder)
return if ["development", "deployment", nil].include? ENV["RACK_ENV"]
builder.use Rack::CommonLogger
end
def setup_custom_logger(builder)
if logging.respond_to? :to_int
builder.use Rack::Logger, logging
else else
builder.use Rack::NullLogger builder.use Rack::Logger
end end
end end
def setup_protection(builder) def setup_protection(builder)
return unless protection? return unless protection?
options = Hash === protection ? protection.dup : {} options = Hash === protection ? protection.dup : {}
options[:except] = Array options[:except] options[:except] = Array(options[:except] || :escaped_params)
options[:except] += [:session_hijacking, :remote_token] unless sessions? options[:except] += [:session_hijacking, :remote_token] unless sessions?
builder.use Rack::Protection, options builder.use Rack::Protection, options
end end

View file

@ -8,7 +8,7 @@ module Sinatra
# on this path by default. # on this path by default.
set :app_file, caller_files.first || $0 set :app_file, caller_files.first || $0
set :run, Proc.new { $0 == app_file } set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }
if run? && ARGV.any? if run? && ARGV.any?
require 'optparse' require 'optparse'

View file

@ -1,3 +1,3 @@
module Sinatra module Sinatra
VERSION = '1.3.0' VERSION = '1.3.1'
end end

View file

@ -7,12 +7,12 @@ Gem::Specification.new 'sinatra', Sinatra::VERSION do |s|
s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"]
s.email = "sinatrarb@googlegroups.com" s.email = "sinatrarb@googlegroups.com"
s.homepage = "http://www.sinatrarb.com/" s.homepage = "http://www.sinatrarb.com/"
s.files = `git ls-files`.split("\n") s.files = `git ls-files`.split("\n") - %w[.gitignore .travis.yml]
s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ }
s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE'
s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc] s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc]
s.add_dependency 'rack', '~> 1.3' s.add_dependency 'rack', '~> 1.3', '>= 1.3.4'
s.add_dependency 'rack-protection', '~> 1.1' s.add_dependency 'rack-protection', '~> 1.1', '>= 1.1.2'
s.add_dependency 'tilt', '~> 1.3' s.add_dependency 'tilt', '~> 1.3', '>= 1.3.3'
end end

View file

@ -97,6 +97,17 @@ class BeforeFilterTest < Test::Unit::TestCase
assert_equal 'cool', body assert_equal 'cool', body
end end
it "properly unescapes parameters" do
mock_app {
before { @foo = params['foo'] }
get('/foo') { @foo }
}
get '/foo?foo=bar%3Abaz%2Fbend'
assert ok?
assert_equal 'bar:baz/bend', body
end
it "runs filters defined in superclasses" do it "runs filters defined in superclasses" do
base = Class.new(Sinatra::Base) base = Class.new(Sinatra::Base)
base.before { @foo = 'hello from superclass' } base.before { @foo = 'hello from superclass' }

View file

@ -69,7 +69,15 @@ class Test::Unit::TestCase
end end
def assert_body(value) def assert_body(value)
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "") 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 end
def assert_like(a,b) def assert_like(a,b)

View file

@ -858,6 +858,20 @@ class HelpersTest < Test::Unit::TestCase
assert ! response['Last-Modified'] assert ! response['Last-Modified']
end 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, [Time.now, DateTime.now, Date.today, Time.now.to_i,
Struct.new(:to_time).new(Time.now) ].each do |last_modified_time| Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
describe "with #{last_modified_time.class.name}" do describe "with #{last_modified_time.class.name}" do
@ -955,74 +969,641 @@ class HelpersTest < Test::Unit::TestCase
assert_equal '', body assert_equal '', body
end end
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 end
end end
describe 'etag' do describe 'etag' do
setup do context "safe requests" do
mock_app { it 'returns 200 for normal requests' do
get '/' do mock_app do
body { 'Hello World' } get '/' do
etag 'FOO' etag 'foo'
'Boo!' 'ok'
end
end end
post '/' do get('/')
etag 'FOO' assert_status 200
'Matches!' 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 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 end
it 'sets the ETag header' do context "idempotent requests" do
get '/' it 'returns 200 for normal requests' do
assert_equal '"FOO"', response['ETag'] 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 end
it 'returns a body when conditional get misses' do context "post requests" do
get '/' it 'returns 200 for normal requests' do
assert_equal 200, status mock_app do
assert_equal 'Boo!', body post '/' do
end etag 'foo'
'ok'
end
end
it 'returns a body when posting with no If-None-Match header' do post('/')
post '/' assert_status 200
assert_equal 200, status assert_body 'ok'
assert_equal 'Matches!', body end
end
it 'returns a body when conditional post matches' do context "If-None-Match" do
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' } it 'returns 200 when If-None-Match is *' do
assert_equal 200, status mock_app do
assert_equal 'Matches!', body post '/' do
end etag 'foo'
'ok'
end
end
it 'halts with 412 when conditional post misses' do post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR"' } assert_status 200
assert_equal 412, status assert_body 'ok'
assert_equal '', body end
end
it 'halts when a conditional GET matches' do it 'returns 200 when If-None-Match is * for new resources' do
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' } mock_app do
assert_equal 304, status post '/' do
assert_equal '', body etag 'foo', :new_resource => true
end 'ok'
end
end
it 'should handle multiple ETag values in If-None-Match header' do post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' } assert_status 200
assert_equal 304, status assert_body 'ok'
assert_equal '', body 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 end
it 'uses a weak etag with the :weak option' do it 'uses a weak etag with the :weak option' do
mock_app { mock_app do
get '/' do get '/' do
etag 'FOO', :weak etag 'FOO', :weak
"that's weak, dude." "that's weak, dude."
end end
} end
get '/' get '/'
assert_equal 'W/"FOO"', response['ETag'] assert_equal 'W/"FOO"', response['ETag']
end end
@ -1130,6 +1711,16 @@ class HelpersTest < Test::Unit::TestCase
assert !io.string.include?("INFO -- : Program started") assert !io.string.include?("INFO -- : Program started")
assert !io.string.include?("WARN -- : Nothing to do") assert !io.string.include?("WARN -- : Nothing to do")
end 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 end
module ::HelperOne; def one; '1'; end; end module ::HelperOne; def one; '1'; end; end

View file

@ -16,7 +16,7 @@ class LessTest < Test::Unit::TestCase
it 'renders inline Less strings' do it 'renders inline Less strings' do
less_app { less "@white_color: #fff; #main { background-color: @white_color }" } less_app { less "@white_color: #fff; #main { background-color: @white_color }" }
assert ok? assert ok?
assert_equal "#main { background-color: #ffffff; }\n", body assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
end end
it 'defaults content type to css' do it 'defaults content type to css' do
@ -45,13 +45,13 @@ class LessTest < Test::Unit::TestCase
it 'renders .less files in views path' do it 'renders .less files in views path' do
less_app { less :hello } less_app { less :hello }
assert ok? assert ok?
assert_equal "#main { background-color: #ffffff; }\n", body assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
end end
it 'ignores the layout option' do it 'ignores the layout option' do
less_app { less :hello, :layout => :layout2 } less_app { less :hello, :layout => :layout2 }
assert ok? assert ok?
assert_equal "#main { background-color: #ffffff; }\n", body assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "")
end end
it "raises error if template not found" do it "raises error if template not found" do

View file

@ -1,6 +1,7 @@
require File.expand_path('../helper', __FILE__) require File.expand_path('../helper', __FILE__)
begin begin
require 'rdoc'
require 'rdoc/markup/to_html' require 'rdoc/markup/to_html'
class RdocTest < Test::Unit::TestCase class RdocTest < Test::Unit::TestCase
@ -15,13 +16,13 @@ class RdocTest < Test::Unit::TestCase
it 'renders inline rdoc strings' do it 'renders inline rdoc strings' do
rdoc_app { rdoc '= Hiya' } rdoc_app { rdoc '= Hiya' }
assert ok? assert ok?
assert_body "<h1>Hiya</h1>" assert_body /<h1[^>]*>Hiya<\/h1>/
end end
it 'renders .rdoc files in views path' do it 'renders .rdoc files in views path' do
rdoc_app { rdoc :hello } rdoc_app { rdoc :hello }
assert ok? assert ok?
assert_body "<h1>Hello From RDoc</h1>" assert_body /<h1[^>]*>Hello From RDoc<\/h1>/
end end
it "raises error if template not found" do it "raises error if template not found" do

View file

@ -114,6 +114,8 @@ class RoutingTest < Test::Unit::TestCase
it 'matches empty PATH_INFO to "" if a route is defined for ""' do it 'matches empty PATH_INFO to "" if a route is defined for ""' do
mock_app do mock_app do
disable :protection
get '/' do get '/' do
'did not work' 'did not work'
end end

View file

@ -56,6 +56,16 @@ class StreamingTest < Test::Unit::TestCase
assert_equal 0, final assert_equal 0, final
end 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 { |str| }
assert a, 'should trigger first callback'
assert b, 'should trigger second callback'
end
class MockScheduler class MockScheduler
def initialize(*) @schedule, @defer = [], [] end def initialize(*) @schedule, @defer = [], [] end
def schedule(&block) @schedule << block end def schedule(&block) @schedule << block end
@ -97,4 +107,9 @@ class StreamingTest < Test::Unit::TestCase
scheduler.defer! scheduler.defer!
assert_raise(RuntimeError) { scheduler.schedule! } assert_raise(RuntimeError) { scheduler.schedule! }
end 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 { |str| }
end
end end