Update Vendored Rack to 1.2.0

Also update tests for itextomml 1.3.25.
This commit is contained in:
Jacques Distler 2010-06-13 23:09:24 -05:00
parent 4f8759cdf3
commit 6491d70326
107 changed files with 1463 additions and 2008 deletions

View file

@ -1,4 +1,4 @@
# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen>
# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
#
# Rack is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -20,7 +20,7 @@ module Rack
# Return the Rack release as a dotted string.
def self.release
"1.1"
"1.2"
end
autoload :Builder, "rack/builder"
@ -78,15 +78,4 @@ module Rack
autoload :Pool, "rack/session/pool"
autoload :Memcache, "rack/session/memcache"
end
# *Adapters* connect Rack with third party web frameworks.
#
# Rack includes an adapter for Camping, see README for other
# frameworks supporting Rack in their code bases.
#
# Refer to the submodules for framework-specific calling details.
module Adapter
autoload :Camping, "rack/adapter/camping"
end
end

View file

@ -1,22 +0,0 @@
module Rack
module Adapter
class Camping
def initialize(app)
@app = app
end
def call(env)
env["PATH_INFO"] ||= ""
env["SCRIPT_NAME"] ||= ""
controller = @app.run(env['rack.input'], env)
h = controller.headers
h.each_pair do |k,v|
if v.kind_of? URI
h[k] = v.to_s
end
end
[controller.status, controller.headers, [controller.body.to_s]]
end
end
end
end

View file

@ -35,7 +35,7 @@ module Rack
super k.to_s, v.to_s
end
UNQUOTED = ['qop', 'nc', 'stale']
UNQUOTED = ['nc', 'stale']
def to_s
inject([]) do |parts, (k, v)|

View file

@ -1,480 +0,0 @@
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
gem 'ruby-openid', '~> 2' if defined? Gem
require 'rack/request'
require 'rack/utils'
require 'rack/auth/abstract/handler'
require 'uri'
require 'openid' #gem
require 'openid/extension' #gem
require 'openid/store/memory' #gem
module Rack
class Request
def openid_request
@env['rack.auth.openid.request']
end
def openid_response
@env['rack.auth.openid.response']
end
end
module Auth
# Rack::Auth::OpenID provides a simple method for setting up an OpenID
# Consumer. It requires the ruby-openid library from janrain to operate,
# as well as a rack method of session management.
#
# The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
#
# The OpenID specifications can be found at
# http://openid.net/specs/openid-authentication-1_1.html
# and
# http://openid.net/specs/openid-authentication-2_0.html. Documentation
# for published OpenID extensions and related topics can be found at
# http://openid.net/developers/specs/.
#
# It is recommended to read through the OpenID spec, as well as
# ruby-openid's documentation, to understand what exactly goes on. However
# a setup as simple as the presented examples is enough to provide
# Consumer functionality.
#
# This library strongly intends to utilize the OpenID 2.0 features of the
# ruby-openid library, which provides OpenID 1.0 compatiblity.
#
# NOTE: Due to the amount of data that this library stores in the
# session, Rack::Session::Cookie may fault.
class OpenID
class NoSession < RuntimeError; end
class BadExtension < RuntimeError; end
# Required for ruby-openid
ValidStatus = [:success, :setup_needed, :cancel, :failure]
# = Arguments
#
# The first argument is the realm, identifying the site they are trusting
# with their identity. This is required, also treated as the trust_root
# in OpenID 1.x exchanges.
#
# The optional second argument is a hash of options.
#
# == Options
#
# <tt>:return_to</tt> defines the url to return to after the client
# authenticates with the openid service provider. This url should point
# to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
# provided, return_to will be the current url which allows flexibility
# with caveats.
#
# <tt>:session_key</tt> defines the key to the session hash in the env.
# It defaults to 'rack.session'.
#
# <tt>:openid_param</tt> defines at what key in the request parameters to
# find the identifier to resolve. As per the 2.0 spec, the default is
# 'openid_identifier'.
#
# <tt>:store</tt> defined what OpenID Store to use for persistant
# information. By default a Store::Memory will be used.
#
# <tt>:immediate</tt> as true will make initial requests to be of an
# immediate type. This is false by default. See OpenID specification
# documentation.
#
# <tt>:extensions</tt> should be a hash of openid extension
# implementations. The key should be the extension main module, the value
# should be an array of arguments for extension::Request.new.
# The hash is iterated over and passed to #add_extension for processing.
# Please see #add_extension for further documentation.
#
# == Examples
#
# simple_oid = OpenID.new('http://mysite.com/')
#
# return_oid = OpenID.new('http://mysite.com/', {
# :return_to => 'http://mysite.com/openid'
# })
#
# complex_oid = OpenID.new('http://mysite.com/',
# :immediate => true,
# :extensions => {
# ::OpenID::SReg => [['email'],['nickname']]
# }
# )
#
# = Advanced
#
# Most of the functionality of this library is encapsulated such that
# expansion and overriding functions isn't difficult nor tricky.
# Alternately, to avoid opening up singleton objects or subclassing, a
# wrapper rack middleware can be composed to act upon Auth::OpenID's
# responses. See #check and #finish for locations of pertinent data.
#
# == Responses
#
# To change the responses that Auth::OpenID returns, override the methods
# #redirect, #bad_request, #unauthorized, #access_denied, and
# #foreign_server_failure.
#
# Additionally #confirm_post_params is used when the URI would exceed
# length limits on a GET request when doing the initial verification
# request.
#
# == Processing
#
# To change methods of processing completed transactions, override the
# methods #success, #setup_needed, #cancel, and #failure. Please ensure
# the returned object is a rack compatible response.
#
# The first argument is an OpenID::Response, the second is a
# Rack::Request of the current request, the last is the hash used in
# ruby-openid handling, which can be found manually at
# env['rack.session'][:openid].
#
# This is useful if you wanted to expand the processing done, such as
# setting up user accounts.
#
# oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
# def oid_app.success oid, request, session
# user = Models::User[oid.identity_url]
# user ||= Models::User.create_from_openid oid
# request['rack.session'][:user] = user.id
# redirect MyApp.site_home
# end
#
# site_map['/openid'] = oid_app
# map = Rack::URLMap.new site_map
# ...
def initialize(realm, options={})
realm = URI(realm)
raise ArgumentError, "Invalid realm: #{realm}" \
unless realm.absolute? \
and realm.fragment.nil? \
and realm.scheme =~ /^https?$/ \
and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
realm.path = '/' if realm.path.empty?
@realm = realm.to_s
if ruri = options[:return_to]
ruri = URI(ruri)
raise ArgumentError, "Invalid return_to: #{ruri}" \
unless ruri.absolute? \
and ruri.scheme =~ /^https?$/ \
and ruri.fragment.nil?
raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
unless self.within_realm?(ruri)
@return_to = ruri.to_s
end
@session_key = options[:session_key] || 'rack.session'
@openid_param = options[:openid_param] || 'openid_identifier'
@store = options[:store] || ::OpenID::Store::Memory.new
@immediate = !!options[:immediate]
@extensions = {}
if extensions = options.delete(:extensions)
extensions.each do |ext, args|
add_extension ext, *args
end
end
# Undocumented, semi-experimental
@anonymous = !!options[:anonymous]
end
attr_reader :realm, :return_to, :session_key, :openid_param, :store,
:immediate, :extensions
# Sets up and uses session data at <tt>:openid</tt> within the session.
# Errors in this setup will raise a NoSession exception.
#
# If the parameter 'openid.mode' is set, which implies a followup from
# the openid server, processing is passed to #finish and the result is
# returned. However, if there is no appropriate openid information in the
# session, a 400 error is returned.
#
# If the parameter specified by <tt>options[:openid_param]</tt> is
# present, processing is passed to #check and the result is returned.
#
# If neither of these conditions are met, #unauthorized is called.
def call(env)
env['rack.auth.openid'] = self
env_session = env[@session_key]
unless env_session and env_session.is_a?(Hash)
raise NoSession, 'No compatible session'
end
# let us work in our own namespace...
session = (env_session[:openid] ||= {})
unless session and session.is_a?(Hash)
raise NoSession, 'Incompatible openid session'
end
request = Rack::Request.new(env)
consumer = ::OpenID::Consumer.new(session, @store)
if mode = request.GET['openid.mode']
if session.key?(:openid_param)
finish(consumer, session, request)
else
bad_request
end
elsif request.GET[@openid_param]
check(consumer, session, request)
else
unauthorized
end
end
# As the first part of OpenID consumer action, #check retrieves the data
# required for completion.
#
# If all parameters fit within the max length of a URI, a 303 redirect
# will be returned. Otherwise #confirm_post_params will be called.
#
# Any messages from OpenID's request are logged to env['rack.errors']
#
# <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
# instance.
#
# <tt>session[:openid_param]</tt> is set to the openid identifier
# provided by the user.
#
# <tt>session[:return_to]</tt> is set to the return_to uri given to the
# identity provider.
def check(consumer, session, req)
oid = consumer.begin(req.GET[@openid_param], @anonymous)
req.env['rack.auth.openid.request'] = oid
req.env['rack.errors'].puts(oid.message)
p oid if $DEBUG
## Extension support
extensions.each do |ext,args|
oid.add_extension(ext::Request.new(*args))
end
session[:openid_param] = req.GET[openid_param]
return_to_uri = return_to ? return_to : req.url
session[:return_to] = return_to_uri
immediate = session.key?(:setup_needed) ? false : immediate
if oid.send_redirect?(realm, return_to_uri, immediate)
uri = oid.redirect_url(realm, return_to_uri, immediate)
redirect(uri)
else
confirm_post_params(oid, realm, return_to_uri, immediate)
end
rescue ::OpenID::DiscoveryFailure => e
# thrown from inside OpenID::Consumer#begin by yadis stuff
req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n")
return foreign_server_failure
end
# This is the final portion of authentication.
# If successful, a redirect to the realm is be returned.
# Data gathered from extensions are stored in session[:openid] with the
# extension's namespace uri as the key.
#
# Any messages from OpenID's response are logged to env['rack.errors']
#
# <tt>env['rack.auth.openid.response']</tt> will contain the openid
# response.
def finish(consumer, session, req)
oid = consumer.complete(req.GET, req.url)
req.env['rack.auth.openid.response'] = oid
req.env['rack.errors'].puts(oid.message)
p oid if $DEBUG
raise unless ValidStatus.include?(oid.status)
__send__(oid.status, oid, req, session)
end
# The first argument should be the main extension module.
# The extension module should contain the constants:
# * class Request, should have OpenID::Extension as an ancestor
# * class Response, should have OpenID::Extension as an ancestor
# * string NS_URI, which defining the namespace of the extension
#
# All trailing arguments will be passed to extension::Request.new in
# #check.
# The openid response will be passed to
# extension::Response#from_success_response, #get_extension_args will be
# called on the result to attain the gathered data.
#
# This method returns the key at which the response data will be found in
# the session, which is the namespace uri by default.
def add_extension(ext, *args)
raise BadExtension unless valid_extension?(ext)
extensions[ext] = args
return ext::NS_URI
end
# Checks the validitity, in the context of usage, of a submitted
# extension.
def valid_extension?(ext)
if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
raise ArgumentError, 'Extension is missing constants.'
elsif not ext::Response.respond_to?(:from_success_response)
raise ArgumentError, 'Response is missing required method.'
end
return true
rescue
return false
end
# Checks the provided uri to ensure it'd be considered within the realm.
# is currently not compatible with wildcard realms.
def within_realm? uri
uri = URI.parse(uri.to_s)
realm = URI.parse(self.realm)
return false unless uri.absolute?
return false unless uri.path[0, realm.path.size] == realm.path
return false unless uri.host == realm.host or realm.host[/^\*\./]
# for wildcard support, is awkward with URI limitations
realm_match = Regexp.escape(realm.host).
sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
return false unless uri.host.match(realm_match)
return true
end
alias_method :include?, :within_realm?
protected
### These methods define some of the boilerplate responses.
# Returns an html form page for posting to an Identity Provider if the
# GET request would exceed the upper URI length limit.
def confirm_post_params(oid, realm, return_to, immediate)
Rack::Response.new.finish do |r|
r.write '<html><head><title>Confirm...</title></head><body>'
r.write oid.form_markup(realm, return_to, immediate)
r.write '</body></html>'
end
end
# Returns a 303 redirect with the destination of that provided by the
# argument.
def redirect(uri)
[ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
'Location' => uri},
[] ]
end
# Returns an empty 400 response.
def bad_request
[ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
[''] ]
end
# Returns a basic unauthorized 401 response.
def unauthorized
[ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
['Unauthorized.'] ]
end
# Returns a basic access denied 403 response.
def access_denied
[ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
['Access denied.'] ]
end
# Returns a 503 response to be used if communication with the remote
# OpenID server fails.
def foreign_server_failure
[ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
['Foreign server failure.'] ]
end
private
### These methods are called after a transaction is completed, depending
# on its outcome. These should all return a rack compatible response.
# You'd want to override these to provide additional functionality.
# Called to complete processing on a successful transaction.
# Within the openid session, :openid_identity and :openid_identifier are
# set to the user friendly and the standard representation of the
# validated identity. All other data in the openid session is cleared.
def success(oid, request, session)
session.clear
session[:openid_identity] = oid.display_identifier
session[:openid_identifier] = oid.identity_url
extensions.keys.each do |ext|
label = ext.name[/[^:]+$/].downcase
response = ext::Response.from_success_response(oid)
session[label] = response.data
end
redirect(realm)
end
# Called if the Identity Provider indicates further setup by the user is
# required.
# The identifier is retrived from the openid session at :openid_param.
# And :setup_needed is set to true to prevent looping.
def setup_needed(oid, request, session)
identifier = session[:openid_param]
session[:setup_needed] = true
redirect req.script_name + '?' + openid_param + '=' + identifier
end
# Called if the user indicates they wish to cancel identification.
# Data within openid session is cleared.
def cancel(oid, request, session)
session.clear
access_denied
end
# Called if the Identity Provider indicates the user is unable to confirm
# their identity. Data within the openid session is left alone, in case
# of swarm auth attacks.
def failure(oid, request, session)
unauthorized
end
end
# A class developed out of the request to use OpenID as an authentication
# middleware. The request will be sent to the OpenID instance unless the
# block evaluates to true. For example in rackup, you can use it as such:
#
# use Rack::Session::Pool
# use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
# env['rack.session'][:authkey] == a_string
# end
# run RackApp
#
# Or simply:
#
# app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
class OpenIDAuth < Rack::Auth::AbstractHandler
attr_reader :oid
def initialize(app, realm, options={}, &auth)
@oid = OpenID.new(realm, options)
super(app, &auth)
end
def call(env)
to = auth.call(env) ? @app : @oid
to.call env
end
end
end
end

View file

@ -13,7 +13,7 @@ module Rack
status, headers, body = @app.call(env)
headers = HeaderHash.new(headers)
if !STATUS_WITH_NO_ENTITY_BODY.include?(status) &&
if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
!headers['Content-Length'] &&
!headers['Transfer-Encoding'] &&
(body.respond_to?(:to_ary) || body.respond_to?(:to_str))

View file

@ -11,13 +11,22 @@ module Rack
status, headers, body = @app.call(env)
if !headers.has_key?('ETag')
parts = []
body.each { |part| parts << part.to_s }
headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}")
[status, headers, parts]
else
[status, headers, body]
digest, body = digest_body(body)
headers['ETag'] = %("#{digest}")
end
[status, headers, body]
end
private
def digest_body(body)
digest = Digest::MD5.new
parts = []
body.each do |part|
digest << part
parts << part
end
[digest.hexdigest, parts]
end
end
end

View file

@ -3,8 +3,10 @@ require 'rack/utils'
require 'rack/mime'
module Rack
# Rack::File serves files below the +root+ given, according to the
# 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+.

View file

@ -1,9 +1,11 @@
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
@ -15,8 +17,8 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [1,1],
"rack.input" => $stdin,
env.update({"rack.version" => Rack::VERSION,
"rack.input" => Rack::RewindableInput.new($stdin),
"rack.errors" => $stderr,
"rack.multithread" => false,
@ -40,20 +42,20 @@ module Rack
end
def self.send_headers(status, headers)
STDOUT.print "Status: #{status}\r\n"
$stdout.print "Status: #{status}\r\n"
headers.each { |k, vs|
vs.split("\n").each { |v|
STDOUT.print "#{k}: #{v}\r\n"
$stdout.print "#{k}: #{v}\r\n"
}
}
STDOUT.print "\r\n"
STDOUT.flush
$stdout.print "\r\n"
$stdout.flush
end
def self.send_body(body)
body.each { |part|
STDOUT.print part
STDOUT.flush
$stdout.print part
$stdout.flush
}
end
end

View file

@ -36,7 +36,7 @@ module Rack
rack_input = RewindableInput.new(request.in)
env.update({"rack.version" => [1,1],
env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input,
"rack.errors" => request.err,

View file

@ -20,7 +20,7 @@ module Rack
rack_input = RewindableInput.new($stdin.read.to_s)
env.update(
"rack.version" => [1,1],
"rack.version" => Rack::VERSION,
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => false,

View file

@ -52,7 +52,7 @@ module Rack
rack_input = request.body || StringIO.new('')
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1],
env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input,
"rack.errors" => $stderr,

View file

@ -17,9 +17,6 @@ module Rack
def initialize(settings = {})
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
@log = Object.new
def @log.info(*args); end
def @log.error(*args); end
super(settings)
end
@ -36,7 +33,7 @@ module Rack
rack_input = StringIO.new(input_body)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1],
env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input,
"rack.errors" => $stderr,
"rack.multithread" => true,

View file

@ -7,12 +7,15 @@ module Rack
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host]
server = ::WEBrick::HTTPServer.new(options)
server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { server.shutdown }
trap(:TERM) { server.shutdown }
yield server if block_given?
server.start
@server = ::WEBrick::HTTPServer.new(options)
@server.mount "/", Rack::Handler::WEBrick, app
yield @server if block_given?
@server.start
end
def self.shutdown
@server.shutdown
@server = nil
end
def initialize(server, app)
@ -27,7 +30,7 @@ module Rack
rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1],
env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input,
"rack.errors" => $stderr,

View file

@ -7,6 +7,7 @@ module Rack
class Lint
def initialize(app)
@app = app
@content_length = nil
end
# :stopdoc:
@ -51,15 +52,16 @@ module Rack
check_headers headers
## and the *body*.
check_content_type status, headers
check_content_length status, headers, env
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 true instance of Hash (no
## subclassing allowed) that includes CGI-like headers.
## The application is free to modify the environment.
## 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
}
@ -288,10 +290,6 @@ module Rack
@input = input
end
def size
@input.size
end
## * +gets+ must be called without arguments and return a string,
## or +nil+ on EOF.
def gets(*args)
@ -481,7 +479,7 @@ module Rack
end
## === The Content-Length
def check_content_length(status, headers, env)
def check_content_length(status, headers)
headers.each { |key, value|
if key.downcase == 'content-length'
## There must not be a <tt>Content-Length</tt> header when the
@ -489,49 +487,43 @@ module Rack
assert("Content-Length header found in #{status} response, not allowed") {
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
}
bytes = 0
string_body = true
if @body.respond_to?(:to_ary)
@body.each { |part|
unless part.kind_of?(String)
string_body = false
break
end
bytes += Rack::Utils.bytesize(part)
}
if env["REQUEST_METHOD"] == "HEAD"
assert("Response body was given for HEAD request, but should be empty") {
bytes == 0
}
else
if string_body
assert("Content-Length header was #{value}, but should be #{bytes}") {
value == bytes.to_s
}
end
end
end
return
@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.

View file

@ -87,6 +87,7 @@ module Rack
".gif" => "image/gif",
".gz" => "application/x-gzip",
".h" => "text/x-c",
".htc" => "text/x-component",
".hh" => "text/x-c",
".htm" => "text/html",
".html" => "text/html",
@ -186,6 +187,7 @@ module Rack
".vrml" => "model/vrml",
".war" => "application/java-archive",
".wav" => "audio/x-wav",
".webm" => "video/webm",
".wma" => "audio/x-ms-wma",
".wmv" => "video/x-ms-wmv",
".wmx" => "video/x-ms-wmx",

View file

@ -1,5 +1,6 @@
require 'uri'
require 'stringio'
require 'rack'
require 'rack/lint'
require 'rack/utils'
require 'rack/response'
@ -40,7 +41,7 @@ module Rack
end
DEFAULT_ENV = {
"rack.version" => [1,1],
"rack.version" => Rack::VERSION,
"rack.input" => StringIO.new,
"rack.errors" => StringIO.new,
"rack.multithread" => true,

View file

@ -35,6 +35,10 @@ module Rack
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

View file

@ -80,11 +80,13 @@ module Rack
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
def path_info=(s); @env["PATH_INFO"] = s.to_s end
def get?; request_method == "GET" end
def post?; request_method == "POST" end
def put?; request_method == "PUT" end
def delete?; request_method == "DELETE" end
def head?; request_method == "HEAD" end
def delete?; request_method == "DELETE" end
def get?; request_method == "GET" end
def head?; request_method == "HEAD" end
def options?; request_method == "OPTIONS" end
def post?; request_method == "POST" end
def put?; request_method == "PUT" end
def trace?; request_method == "TRACE" 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
@ -253,7 +255,7 @@ module Rack
def ip
if addr = @env['HTTP_X_FORWARDED_FOR']
addr.split(',').last.strip
(addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
else
@env['REMOTE_ADDR']
end

View file

@ -1,5 +1,6 @@
require 'rack/request'
require 'rack/utils'
require 'time'
module Rack
# Rack::Response provides a convenient interface to create a Rack

View file

@ -1,4 +1,6 @@
# -*- encoding: binary -*-
require 'tempfile'
require 'rack/utils'
module Rack
# Class which can make any IO object rewindable, including non-rewindable ones. It does
@ -16,27 +18,27 @@ module Rack
@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.
@ -52,9 +54,9 @@ module Rack
@rewindable_io = nil
end
end
private
# Ruby's Tempfile class has a bug. Subclass it and fix it.
class Tempfile < ::Tempfile
def _close
@ -74,17 +76,18 @@ module Rack
@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? && !tempfile_unlink_contains_bug?
if filesystem_has_posix_semantics?
@rewindable_io.unlink
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 == buffer.size
entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
if !entire_buffer_written_out
buffer.slice!(0 .. written - 1)
end
@ -92,19 +95,9 @@ module Rack
end
@rewindable_io.rewind
end
def filesystem_has_posix_semantics?
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
end
def tempfile_unlink_contains_bug?
# The tempfile library as included in Ruby 1.9.1-p152 and later
# contains a bug: unlinking an open Tempfile object also closes
# it, which breaks our expected POSIX semantics. This problem
# has been fixed in Ruby 1.9.2, but the Ruby team chose not to
# include the bug fix in later versions of the 1.9.1 series.
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
ruby_engine == "ruby" && RUBY_VERSION == "1.9.1" && RUBY_PATCHLEVEL >= 152
end
end
end

View file

@ -2,7 +2,9 @@ require 'rack/file'
module Rack
class File #:nodoc:
alias :to_path :path
unless instance_methods(false).include?('to_path')
alias :to_path :path
end
end
# = Sendfile
@ -11,7 +13,7 @@ module Rack
# served from a file and replaces it with a server specific X-Sendfile
# header. The web server is then responsible for writing the file contents
# to the client. This can dramatically reduce the amount of work required
# by the Ruby backend and takes advantage of the web servers optimized file
# 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
@ -31,19 +33,19 @@ module Rack
# 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/ {
# location ~ /files/(.*) {
# internal;
# alias /var/www/;
# alias /var/www/$1;
# }
#
# location / {
# proxy_redirect false;
# 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-Sendfile-Type X-Accel-Redirect;
# proxy_set_header X-Accel-Mapping /files/=/var/www/;
#
# proxy_pass http://127.0.0.1:8080/;

View file

@ -57,7 +57,7 @@ module Rack
}
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
options[:pid] = ::File.expand_path(f)
options[:pid] = f
}
opts.separator ""
@ -74,17 +74,66 @@ module Rack
end
end
opt_parser.parse! args
options[:rack_file] = args.last if args.last
options[:config] = args.last if args.last
options
end
end
def self.start
new.start
# 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_accessor :options
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 acess 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
end
@ -100,17 +149,17 @@ module Rack
:Port => 9292,
:Host => "0.0.0.0",
:AccessLog => [],
:rack_file => ::File.expand_path("config.ru")
:config => "config.ru"
}
end
def app
@app ||= begin
if !::File.exist? options[:rack_file]
abort "configuration #{options[:rack_file]} not found"
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
app, options = Rack::Builder.parse_file(self.options[:rack_file], opt_parser)
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
self.options.merge! options
app
end
@ -119,7 +168,7 @@ module Rack
def self.middleware
@middleware ||= begin
m = Hash.new {|h,k| h[k] = []}
m["deployment"].concat [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
m["deployment"].concat [lambda {|server| server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
m
end
@ -143,7 +192,7 @@ module Rack
end
if includes = options[:include]
$LOAD_PATH.unshift *includes
$LOAD_PATH.unshift(*includes)
end
if library = options[:require]
@ -152,11 +201,20 @@ module Rack
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
end
def server
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
end
private
@ -168,6 +226,7 @@ module Rack
args.clear if ENV.include?("REQUEST_METHOD")
options.merge! opt_parser.parse! args
ENV["RACK_ENV"] = options[:environment]
options
end

View file

@ -31,7 +31,7 @@ module Rack
@mutex = Mutex.new
mserv = @default_options[:memcache_server]
mopts = @default_options.
reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k }
reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
@pool = MemCache.new mserv, mopts
unless @pool.active? and @pool.servers.any?{|c| c.alive? }
raise 'No memcache servers'

View file

@ -35,21 +35,20 @@ module Rack
end
def call(env)
path = env["PATH_INFO"].to_s
path = env["PATH_INFO"]
script_name = env['SCRIPT_NAME']
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
@mapping.each { |host, location, match, app|
next unless (hHost == host || sName == host \
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
next unless path =~ match && rest = $1
next unless path.to_s =~ match && rest = $1
next unless rest.empty? || rest[0] == ?/
return app.call(
env.merge(
'SCRIPT_NAME' => (script_name + location),
'PATH_INFO' => rest))
env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
return app.call(env)
}
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
ensure
env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
end
end
end

View file

@ -1,5 +1,6 @@
# -*- encoding: binary -*-
require 'fileutils'
require 'set'
require 'tempfile'
@ -38,9 +39,6 @@ module Rack
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) }
if v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
if cur = params[k]
if cur.class == Array
params[k] << v
@ -69,9 +67,6 @@ module Rack
module_function :parse_nested_query
def normalize_params(params, name, v = nil)
if v and v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
k = $1 || ''
after = $' || ''
@ -133,13 +128,18 @@ module Rack
end
module_function :build_nested_query
ESCAPE_HTML = {
"&" => "&amp;",
"<" => "&lt;",
">" => "&gt;",
"'" => "&#39;",
'"' => "&quot;",
}
ESCAPE_HTML_PATTERN = Regexp.union(ESCAPE_HTML.keys)
# Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string)
string.to_s.gsub("&", "&amp;").
gsub("<", "&lt;").
gsub(">", "&gt;").
gsub("'", "&#39;").
gsub('"', "&quot;")
string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
end
module_function :escape_html
@ -180,8 +180,8 @@ module Rack
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime.
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
expires = "; expires=" +
rfc2822(value[:expires].clone.gmtime) if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
@ -192,12 +192,12 @@ module Rack
"#{domain}#{path}#{expires}#{secure}#{httponly}"
case header["Set-Cookie"]
when Array
header["Set-Cookie"] << cookie
when String
header["Set-Cookie"] = [header["Set-Cookie"], cookie]
when nil
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
@ -205,14 +205,25 @@ module Rack
module_function :set_cookie_header!
def delete_cookie_header!(header, key, value = {})
unless Array === header["Set-Cookie"]
header["Set-Cookie"] = [header["Set-Cookie"]].compact
case header["Set-Cookie"]
when nil, ''
cookies = []
when String
cookies = header["Set-Cookie"].split("\n")
when Array
cookies = header["Set-Cookie"]
end
header["Set-Cookie"].reject! { |cookie|
cookie =~ /\A#{escape(key)}=/
cookies.reject! { |cookie|
if value[:domain]
cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
else
cookie =~ /\A#{escape(key)}=/
end
}
header["Set-Cookie"] = cookies.join("\n")
set_cookie_header!(header, key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
@ -234,6 +245,22 @@ module Rack
end
module_function :bytesize
# 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 ambigous 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 rfc2822(time)
wday = Time::RFC2822_DAY_NAME[time.wday]
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
end
module_function :rfc2822
# 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
@ -291,7 +318,8 @@ module Rack
end
def [](k)
super(@names[k] ||= @names[k.downcase])
super(@names[k]) if @names[k]
super(@names[k.downcase])
end
def []=(k, v)
@ -478,11 +506,31 @@ module Rack
head = buf.slice!(0, i+2) # First \r\n
buf.slice!(0, 2) # Second \r\n
filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1]
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
if head =~ rfc2183
filename = Hash[head.scan(dispparm)]['filename']
filename = $1 if filename and filename =~ /^"(.*)"$/
elsif head =~ broken_quoted
filename = $1
elsif head =~ broken_unquoted
filename = $1
end
if filename && filename !~ /\\[^\\"]/
filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
end
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
if content_type || filename
if filename
body = Tempfile.new("RackMultipart")
body.binmode if body.respond_to?(:binmode)
end
@ -519,8 +567,7 @@ module Rack
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
filename =~ /^(?:.*[:\\\/])?(.*)/m
filename = $1
filename = filename.split(/[\/\\]/).last
data = {:filename => filename, :type => content_type,
:name => name, :tempfile => body, :head => head}