Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
deb597d6
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
deb597d6
编写于
1月 14, 2009
作者:
P
Pratik Naik
浏览文件
操作
浏览文件
下载
差异文件
Merge commit 'mainstream/master'
上级
e46c8076
9bcf01b2
变更
36
隐藏空白更改
内联
并排
Showing
36 changed file
with
954 addition
and
952 deletion
+954
-952
actionpack/lib/action_controller.rb
actionpack/lib/action_controller.rb
+8
-7
actionpack/lib/action_controller/http_authentication.rb
actionpack/lib/action_controller/http_authentication.rb
+2
-189
actionpack/lib/action_controller/integration.rb
actionpack/lib/action_controller/integration.rb
+1
-94
actionpack/lib/action_controller/middleware_stack.rb
actionpack/lib/action_controller/middleware_stack.rb
+2
-0
actionpack/lib/action_controller/middlewares.rb
actionpack/lib/action_controller/middlewares.rb
+2
-1
actionpack/lib/action_controller/rack_ext.rb
actionpack/lib/action_controller/rack_ext.rb
+22
-0
actionpack/lib/action_controller/rewindable_input.rb
actionpack/lib/action_controller/rewindable_input.rb
+44
-0
actionpack/lib/action_controller/session/cookie_store.rb
actionpack/lib/action_controller/session/cookie_store.rb
+3
-3
actionpack/lib/action_controller/test_process.rb
actionpack/lib/action_controller/test_process.rb
+2
-1
actionpack/lib/action_controller/verb_piggybacking.rb
actionpack/lib/action_controller/verb_piggybacking.rb
+0
-24
actionpack/test/controller/http_digest_authentication_test.rb
...onpack/test/controller/http_digest_authentication_test.rb
+0
-73
actionpack/test/controller/integration_test.rb
actionpack/test/controller/integration_test.rb
+0
-88
actionpack/test/controller/integration_upload_test.rb
actionpack/test/controller/integration_upload_test.rb
+0
-65
actionpack/test/controller/request/multipart_params_parsing_test.rb
.../test/controller/request/multipart_params_parsing_test.rb
+208
-0
actionpack/test/controller/request/query_string_parsing_test.rb
...pack/test/controller/request/query_string_parsing_test.rb
+0
-0
actionpack/test/controller/request/url_encoded_params_parsing_test.rb
...est/controller/request/url_encoded_params_parsing_test.rb
+208
-0
actionpack/test/controller/request_test.rb
actionpack/test/controller/request_test.rb
+0
-305
activerecord/CHANGELOG
activerecord/CHANGELOG
+2
-0
activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
...ecord/connection_adapters/abstract/database_statements.rb
+99
-11
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
...lib/active_record/connection_adapters/abstract_adapter.rb
+23
-0
activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
...rd/lib/active_record/connection_adapters/mysql_adapter.rb
+15
-0
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
...b/active_record/connection_adapters/postgresql_adapter.rb
+19
-34
activerecord/lib/active_record/fixtures.rb
activerecord/lib/active_record/fixtures.rb
+2
-1
activerecord/lib/active_record/session_store.rb
activerecord/lib/active_record/session_store.rb
+0
-5
activerecord/lib/active_record/transactions.rb
activerecord/lib/active_record/transactions.rb
+58
-8
activerecord/test/cases/defaults_test.rb
activerecord/test/cases/defaults_test.rb
+37
-26
activerecord/test/cases/helper.rb
activerecord/test/cases/helper.rb
+1
-1
activerecord/test/cases/transactions_test.rb
activerecord/test/cases/transactions_test.rb
+165
-1
activesupport/CHANGELOG
activesupport/CHANGELOG
+2
-0
activesupport/lib/active_support/core_ext/object/misc.rb
activesupport/lib/active_support/core_ext/object/misc.rb
+1
-1
activesupport/lib/active_support/time_with_zone.rb
activesupport/lib/active_support/time_with_zone.rb
+6
-2
activesupport/test/core_ext/object_and_class_ext_test.rb
activesupport/test/core_ext/object_and_class_ext_test.rb
+10
-9
activesupport/test/core_ext/time_with_zone_test.rb
activesupport/test/core_ext/time_with_zone_test.rb
+9
-0
railties/configs/initializers/session_store.rb
railties/configs/initializers/session_store.rb
+1
-1
railties/doc/guides/source/security.txt
railties/doc/guides/source/security.txt
+1
-1
railties/lib/initializer.rb
railties/lib/initializer.rb
+1
-1
未找到文件。
actionpack/lib/action_controller.rb
浏览文件 @
deb597d6
...
...
@@ -33,6 +33,7 @@
gem
'rack'
,
'>= 0.9.0'
require
'rack'
require
'action_controller/rack_ext'
module
ActionController
# TODO: Review explicit to see if they will automatically be handled by
...
...
@@ -59,16 +60,14 @@ def self.load_all!
autoload
:MiddlewareStack
,
'action_controller/middleware_stack'
autoload
:MimeResponds
,
'action_controller/mime_responds'
autoload
:PolymorphicRoutes
,
'action_controller/polymorphic_routes'
autoload
:Request
,
'action_controller/request'
autoload
:RequestParser
,
'action_controller/request_parser'
autoload
:UrlEncodedPairParser
,
'action_controller/url_encoded_pair_parser'
autoload
:UploadedStringIO
,
'action_controller/uploaded_file'
autoload
:UploadedTempfile
,
'action_controller/uploaded_file'
autoload
:RecordIdentifier
,
'action_controller/record_identifier'
autoload
:Re
sponse
,
'action_controller/response
'
autoload
:Re
quest
,
'action_controller/request
'
autoload
:RequestForgeryProtection
,
'action_controller/request_forgery_protection'
autoload
:RequestParser
,
'action_controller/request_parser'
autoload
:Rescue
,
'action_controller/rescue'
autoload
:Resources
,
'action_controller/resources'
autoload
:Response
,
'action_controller/response'
autoload
:RewindableInput
,
'action_controller/rewindable_input'
autoload
:Routing
,
'action_controller/routing'
autoload
:SessionManagement
,
'action_controller/session_management'
autoload
:StatusCodes
,
'action_controller/status_codes'
...
...
@@ -76,9 +75,11 @@ def self.load_all!
autoload
:TestCase
,
'action_controller/test_case'
autoload
:TestProcess
,
'action_controller/test_process'
autoload
:Translation
,
'action_controller/translation'
autoload
:UploadedStringIO
,
'action_controller/uploaded_file'
autoload
:UploadedTempfile
,
'action_controller/uploaded_file'
autoload
:UrlEncodedPairParser
,
'action_controller/url_encoded_pair_parser'
autoload
:UrlRewriter
,
'action_controller/url_rewriter'
autoload
:UrlWriter
,
'action_controller/url_rewriter'
autoload
:VerbPiggybacking
,
'action_controller/verb_piggybacking'
autoload
:Verification
,
'action_controller/verification'
module
Assertions
...
...
actionpack/lib/action_controller/http_authentication.rb
浏览文件 @
deb597d6
...
...
@@ -55,31 +55,7 @@ module HttpAuthentication
# end
# end
#
# Simple Digest example. Note the block must return the user's password so the framework
# can appropriately hash it to check the user's credentials. Returning nil will cause authentication to fail.
#
# class PostsController < ApplicationController
# Users = {"dhh" => "secret"}
#
# before_filter :authenticate, :except => [ :index ]
#
# def index
# render :text => "Everyone can see me!"
# end
#
# def edit
# render :text => "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_digest(realm) do |user_name|
# Users[user_name]
# end
# end
# end
#
#
#
# In your integration tests, you can do something like this:
#
# def test_access_granted_from_xml
...
...
@@ -132,10 +108,7 @@ def authorization(request)
end
def
decode_credentials
(
request
)
# Properly decode credentials spanning a new-line
auth
=
authorization
(
request
)
auth
.
slice!
(
'Basic '
)
ActiveSupport
::
Base64
.
decode64
(
auth
||
''
)
ActiveSupport
::
Base64
.
decode64
(
authorization
(
request
).
split
.
last
||
''
)
end
def
encode_credentials
(
user_name
,
password
)
...
...
@@ -147,165 +120,5 @@ def authentication_request(controller, realm)
controller
.
__send__
:render
,
:text
=>
"HTTP Basic: Access denied.
\n
"
,
:status
=>
:unauthorized
end
end
module
Digest
extend
self
module
ControllerMethods
def
authenticate_or_request_with_http_digest
(
realm
=
"Application"
,
&
password_procedure
)
begin
authenticate_with_http_digest!
(
realm
,
&
password_procedure
)
rescue
ActionController
::
HttpAuthentication
::
Error
=>
e
msg
=
e
.
message
msg
=
"
#{
msg
}
expected '
#{
e
.
expected
}
' was '
#{
e
.
was
}
'"
unless
e
.
expected
.
nil?
raise
msg
if
e
.
fatal?
request_http_digest_authentication
(
realm
,
msg
)
end
end
# Authenticate using HTTP Digest, throwing ActionController::HttpAuthentication::Error on failure.
# This allows more detailed analysis of authentication failures
# to be relayed to the client.
def
authenticate_with_http_digest!
(
realm
=
"Application"
,
&
login_procedure
)
HttpAuthentication
::
Digest
.
authenticate
(
self
,
realm
,
&
login_procedure
)
end
# Authenticate with HTTP Digest, returns true or false
def
authenticate_with_http_digest
(
realm
=
"Application"
,
&
login_procedure
)
HttpAuthentication
::
Digest
.
authenticate
(
self
,
realm
,
&
login_procedure
)
rescue
false
end
# Render output including the HTTP Digest authentication header
def
request_http_digest_authentication
(
realm
=
"Application"
,
message
=
nil
)
HttpAuthentication
::
Digest
.
authentication_request
(
self
,
realm
,
message
)
end
# Add HTTP Digest authentication header to result headers
def
http_digest_authentication_header
(
realm
=
"Application"
)
HttpAuthentication
::
Digest
.
authentication_header
(
self
,
realm
)
end
end
# Raises error unless authentictaion succeeds, returns true otherwise
def
authenticate
(
controller
,
realm
,
&
password_procedure
)
raise
Error
.
new
(
false
),
"No authorization header found"
unless
authorization
(
controller
.
request
)
validate_digest_response
(
controller
,
realm
,
&
password_procedure
)
true
end
def
authorization
(
request
)
request
.
env
[
'HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X-HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X_HTTP_AUTHORIZATION'
]
||
request
.
env
[
'REDIRECT_X_HTTP_AUTHORIZATION'
]
end
# Raises error unless the request credentials response value matches the expected value.
def
validate_digest_response
(
controller
,
realm
,
&
password_procedure
)
credentials
=
decode_credentials
(
controller
.
request
)
# Check the nonce, opaque and realm.
# Ignore nc, as we have no way to validate the number of times this nonce has been used
validate_nonce
(
controller
.
request
,
credentials
[
:nonce
])
raise
Error
.
new
(
false
,
realm
,
credentials
[
:realm
]),
"Realm doesn't match"
unless
realm
==
credentials
[
:realm
]
raise
Error
.
new
(
true
,
opaque
(
controller
.
request
),
credentials
[
:opaque
]),
"Opaque doesn't match"
unless
opaque
(
controller
.
request
)
==
credentials
[
:opaque
]
password
=
password_procedure
.
call
(
credentials
[
:username
])
raise
Error
.
new
(
false
),
"No password"
if
password
.
nil?
expected
=
expected_response
(
controller
.
request
.
env
[
'REQUEST_METHOD'
],
controller
.
request
.
url
,
credentials
,
password
)
raise
Error
.
new
(
false
,
expected
,
credentials
[
:response
]),
"Invalid response"
unless
expected
==
credentials
[
:response
]
end
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
def
expected_response
(
http_method
,
uri
,
credentials
,
password
)
ha1
=
::
Digest
::
MD5
.
hexdigest
([
credentials
[
:username
],
credentials
[
:realm
],
password
].
join
(
':'
))
ha2
=
::
Digest
::
MD5
.
hexdigest
([
http_method
.
to_s
.
upcase
,
uri
].
join
(
':'
))
::
Digest
::
MD5
.
hexdigest
([
ha1
,
credentials
[
:nonce
],
credentials
[
:nc
],
credentials
[
:cnonce
],
credentials
[
:qop
],
ha2
].
join
(
':'
))
end
def
encode_credentials
(
http_method
,
credentials
,
password
)
credentials
[
:response
]
=
expected_response
(
http_method
,
credentials
[
:uri
],
credentials
,
password
)
"Digest "
+
credentials
.
sort_by
{
|
x
|
x
[
0
].
to_s
}.
inject
([])
{
|
a
,
v
|
a
<<
"
#{
v
[
0
]
}
='
#{
v
[
1
]
}
'"
}.
join
(
', '
)
end
def
decode_credentials
(
request
)
authorization
(
request
).
to_s
.
gsub
(
/^Digest\s+/
,
''
).
split
(
','
).
inject
({})
do
|
hash
,
pair
|
key
,
value
=
pair
.
split
(
'='
,
2
)
hash
[
key
.
strip
.
to_sym
]
=
value
.
to_s
.
gsub
(
/^"|"$/
,
''
).
gsub
(
/'/
,
''
)
hash
end
end
def
authentication_header
(
controller
,
realm
)
controller
.
headers
[
"WWW-Authenticate"
]
=
%(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(controller.request)}", opaque="#{opaque(controller.request)}")
end
def
authentication_request
(
controller
,
realm
,
message
=
"HTTP Digest: Access denied"
)
authentication_header
(
controller
,
realm
)
controller
.
send!
:render
,
:text
=>
message
,
:status
=>
:unauthorized
end
# Uses an MD5 digest based on time to generate a value to be used only once.
#
# A server-specified data string which should be uniquely generated each time a 401 response is made.
# It is recommended that this string be base64 or hexadecimal data.
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
#
# The contents of the nonce are implementation dependent.
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
#
# => time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
# and private-key is data known only to the server.
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
# reject the request if it did not match the nonce from that header or
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
# to limit the reuse of the nonce to the same client that originally got it.
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
# Also, IP address spoofing is not that hard.)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client.
def
nonce
(
request
,
time
=
Time
.
now
)
session_id
=
request
.
is_a?
(
String
)
?
request
:
request
.
session
.
session_id
t
=
time
.
to_i
hashed
=
[
t
,
session_id
]
digest
=
::
Digest
::
MD5
.
hexdigest
(
hashed
.
join
(
":"
))
Base64
.
encode64
(
"
#{
t
}
:
#{
digest
}
"
).
gsub
(
"
\n
"
,
''
)
end
def
validate_nonce
(
request
,
value
)
t
=
Base64
.
decode64
(
value
).
split
(
":"
).
first
.
to_i
raise
Error
.
new
(
true
),
"Stale Nonce"
if
(
t
-
Time
.
now
.
to_i
).
abs
>
10
*
60
n
=
nonce
(
request
,
t
)
raise
Error
.
new
(
true
,
value
,
n
),
"Bad Nonce"
unless
n
==
value
end
# Opaque based on digest of session_id
def
opaque
(
request
)
session_id
=
request
.
is_a?
(
String
)
?
request
:
request
.
session
.
session_id
@opaque
||=
Base64
.
encode64
(
::
Digest
::
MD5
::
hexdigest
(
session_id
)).
gsub
(
"
\n
"
,
''
)
end
end
class
Error
<
RuntimeError
attr_accessor
:expected
,
:was
def
initialize
(
fatal
=
false
,
expected
=
nil
,
was
=
nil
)
@fatal
=
fatal
@expected
=
expected
@was
=
was
end
def
fatal?
;
@fatal
;
end
end
end
end
actionpack/lib/action_controller/integration.rb
浏览文件 @
deb597d6
...
...
@@ -2,17 +2,6 @@
require
'uri'
require
'active_support/test_case'
# Monkey patch Rack::Lint to support rewind
module
Rack
class
Lint
class
InputWrapper
def
rewind
@input
.
rewind
end
end
end
end
module
ActionController
module
Integration
#:nodoc:
# An integration Session instance represents a set of requests and responses
...
...
@@ -68,15 +57,6 @@ class Session
# A running counter of the number of requests processed.
attr_accessor
:request_count
# Nonce value for Digest Authentication, implicitly set on response with WWW-Authentication
attr_accessor
:nonce
# Opaque value for Digest Authentication, implicitly set on response with WWW-Authentication
attr_accessor
:opaque
# Opaque value for Authentication, implicitly set on response with WWW-Authentication
attr_accessor
:realm
class
MultiPartNeededException
<
Exception
end
...
...
@@ -252,53 +232,6 @@ def xml_http_request(request_method, path, parameters = nil, headers = nil)
end
alias
xhr
:xml_http_request
def
request_with_noauth
(
http_method
,
uri
,
parameters
,
headers
)
process_with_auth
http_method
,
uri
,
parameters
,
headers
end
# Performs a request with the given http_method and parameters, including HTTP Basic authorization headers.
# See get() for more details on paramters and headers.
#
# You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_basic, #post_with_basic,
# #put_with_basic, #delete_with_basic, and #head_with_basic.
def
request_with_basic
(
http_method
,
uri
,
parameters
,
headers
,
user_name
,
password
)
process_with_auth
http_method
,
uri
,
parameters
,
headers
.
merge
(
:authorization
=>
ActionController
::
HttpAuthentication
::
Basic
.
encode_credentials
(
user_name
,
password
))
end
# Performs a request with the given http_method and parameters, including HTTP Digest authorization headers.
# See get() for more details on paramters and headers.
#
# You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_digest, #post_with_digest,
# #put_with_digest, #delete_with_digest, and #head_with_digest.
def
request_with_digest
(
http_method
,
uri
,
parameters
,
headers
,
user_name
,
password
)
# Realm, Nonce, and Opaque taken from previoius 401 response
credentials
=
{
:username
=>
user_name
,
:realm
=>
@realm
,
:nonce
=>
@nonce
,
:qop
=>
"auth"
,
:nc
=>
"00000001"
,
:cnonce
=>
"0a4f113b"
,
:opaque
=>
@opaque
,
:uri
=>
uri
}
raise
"Digest request without previous 401 response"
if
@opaque
.
nil?
process_with_auth
http_method
,
uri
,
parameters
,
headers
.
merge
(
:authorization
=>
ActionController
::
HttpAuthentication
::
Digest
.
encode_credentials
(
http_method
,
credentials
,
password
))
end
# def get_with_basic, def post_with_basic, def put_with_basic, def delete_with_basic, def head_with_basic
# def get_with_digest, def post_with_digest, def put_with_digest, def delete_with_digest, def head_with_digest
[
:get
,
:post
,
:put
,
:delete
,
:head
].
each
do
|
method
|
[
:noauth
,
:basic
,
:digest
].
each
do
|
auth_type
|
define_method
(
"
#{
method
}
_with_
#{
auth_type
}
"
)
do
|
uri
,
parameters
,
headers
,
*
auth
|
send
(
"request_with_
#{
auth_type
}
"
,
method
,
uri
,
parameters
,
headers
,
*
auth
)
end
end
end
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def
url_for
(
options
)
...
...
@@ -423,32 +356,6 @@ def process(method, path, parameters = nil, headers = nil)
return
status
end
# Same as process, but handles authentication returns to perform
# Basic or Digest authentication
def
process_with_auth
(
method
,
path
,
parameters
=
nil
,
headers
=
nil
)
status
=
process
(
method
,
path
,
parameters
,
headers
)
if
status
==
401
# Extract authentication information from response
auth_data
=
@response
.
headers
[
'WWW-Authenticate'
]
if
/^Basic /
.
match
(
auth_data
)
# extract realm, to be used in subsequent request
@realm
=
auth_header
.
split
(
' '
)[
1
]
elsif
/^Digest/
.
match
(
auth_data
)
creds
=
auth_data
.
to_s
.
gsub
(
/^Digest\s+/
,
''
).
split
(
','
).
inject
({})
do
|
hash
,
pair
|
key
,
value
=
pair
.
split
(
'='
,
2
)
hash
[
key
.
strip
.
to_sym
]
=
value
.
to_s
.
gsub
(
/^"|"$/
,
''
).
gsub
(
/'/
,
''
)
hash
end
@realm
=
creds
[
:realm
]
@nonce
=
creds
[
:nonce
]
@opaque
=
creds
[
:opaque
]
end
end
return
status
end
# Encode the cookies hash in a format suitable for passing to a
# request.
def
encode_cookies
...
...
@@ -513,7 +420,7 @@ def multipart_requestify(params, first=true)
def
multipart_body
(
params
,
boundary
)
multipart_requestify
(
params
).
map
do
|
key
,
value
|
if
value
.
respond_to?
(
:original_filename
)
File
.
open
(
value
.
path
)
do
|
f
|
File
.
open
(
value
.
path
,
"rb"
)
do
|
f
|
f
.
set_encoding
(
Encoding
::
BINARY
)
if
f
.
respond_to?
(
:set_encoding
)
<<-
EOF
...
...
actionpack/lib/action_controller/middleware_stack.rb
浏览文件 @
deb597d6
...
...
@@ -32,6 +32,8 @@ def klass
else
@klass
.
to_s
.
constantize
end
rescue
NameError
@klass
end
def
active?
...
...
actionpack/lib/action_controller/middlewares.rb
浏览文件 @
deb597d6
...
...
@@ -18,4 +18,5 @@
)
end
use
ActionController
::
VerbPiggybacking
use
ActionController
::
RewindableInput
use
Rack
::
MethodOverride
actionpack/lib/action_controller/rack_ext.rb
0 → 100644
浏览文件 @
deb597d6
module
Rack
module
Utils
module
Multipart
class
<<
self
def
parse_multipart_with_rewind
(
env
)
result
=
parse_multipart_without_rewind
(
env
)
begin
env
[
'rack.input'
].
rewind
if
env
[
'rack.input'
].
respond_to?
(
:rewind
)
rescue
Errno
::
ESPIPE
# Handles exceptions raised by input streams that cannot be rewound
# such as when using plain CGI under Apache
end
result
end
alias_method_chain
:parse_multipart
,
:rewind
end
end
end
end
actionpack/lib/action_controller/rewindable_input.rb
0 → 100644
浏览文件 @
deb597d6
module
ActionController
class
RewindableInput
class
RewindableIO
<
ActiveSupport
::
BasicObject
def
initialize
(
io
)
@io
=
io
end
def
read
(
*
args
)
read_original_io
@io
.
read
(
*
args
)
end
def
rewind
read_original_io
@io
.
rewind
end
def
string
@string
end
def
method_missing
(
method
,
*
args
,
&
block
)
@io
.
send
(
method
,
*
args
,
&
block
)
end
private
def
read_original_io
unless
@string
@string
=
@io
.
read
@io
=
StringIO
.
new
(
@string
)
end
end
end
def
initialize
(
app
)
@app
=
app
end
def
call
(
env
)
env
[
'rack.input'
]
=
RewindableIO
.
new
(
env
[
'rack.input'
])
@app
.
call
(
env
)
end
end
end
actionpack/lib/action_controller/session/cookie_store.rb
浏览文件 @
deb597d6
...
...
@@ -163,9 +163,9 @@ def unmarshal(cookie)
def
ensure_session_key
(
key
)
if
key
.
blank?
raise
ArgumentError
,
'A
session_
key is required to write a '
+
raise
ArgumentError
,
'A key is required to write a '
+
'cookie containing the session data. Use '
+
'config.action_controller.session = { :
session_
key => '
+
'config.action_controller.session = { :key => '
+
'"_myapp_session", :secret => "some secret phrase" } in '
+
'config/environment.rb'
end
...
...
@@ -181,7 +181,7 @@ def ensure_secret_secure(secret)
if
secret
.
blank?
raise
ArgumentError
,
"A secret is required to generate an "
+
"integrity hash for cookie session data. Use "
+
"config.action_controller.session = { :
session_
key => "
+
"config.action_controller.session = { :key => "
+
"
\"
_myapp_session
\"
, :secret =>
\"
some secret phrase of at "
+
"least
#{
SECRET_MIN_LENGTH
}
characters
\"
} "
+
"in config/environment.rb"
...
...
actionpack/lib/action_controller/test_process.rb
浏览文件 @
deb597d6
...
...
@@ -484,7 +484,8 @@ def method_missing(selector, *args, &block)
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
def
fixture_file_upload
(
path
,
mime_type
=
nil
,
binary
=
false
)
ActionController
::
TestUploadedFile
.
new
(
"
#{
ActionController
::
TestCase
.
try
(
:fixture_path
)
}#{
path
}
"
,
mime_type
,
binary
)
fixture_path
=
ActionController
::
TestCase
.
send
(
:fixture_path
)
if
ActionController
::
TestCase
.
respond_to?
(
:fixture_path
)
ActionController
::
TestUploadedFile
.
new
(
"
#{
fixture_path
}#{
path
}
"
,
mime_type
,
binary
)
end
# A helper to make it easier to test different route configurations.
...
...
actionpack/lib/action_controller/verb_piggybacking.rb
已删除
100644 → 0
浏览文件 @
e46c8076
module
ActionController
# TODO: Use Rack::MethodOverride when it is released
class
VerbPiggybacking
HTTP_METHODS
=
%w(GET HEAD PUT POST DELETE OPTIONS)
def
initialize
(
app
)
@app
=
app
end
def
call
(
env
)
if
env
[
"REQUEST_METHOD"
]
==
"POST"
req
=
Request
.
new
(
env
)
if
method
=
(
req
.
parameters
[
:_method
]
||
env
[
"HTTP_X_HTTP_METHOD_OVERRIDE"
])
method
=
method
.
to_s
.
upcase
if
HTTP_METHODS
.
include?
(
method
)
env
[
"REQUEST_METHOD"
]
=
method
end
end
end
@app
.
call
(
env
)
end
end
end
actionpack/test/controller/http_digest_authentication_test.rb
已删除
100644 → 0
浏览文件 @
e46c8076
require
'abstract_unit'
class
HttpDigestAuthenticationTest
<
Test
::
Unit
::
TestCase
include
ActionController
::
HttpAuthentication
::
Digest
class
DummyController
attr_accessor
:headers
,
:renders
,
:request
,
:response
def
initialize
@headers
,
@renders
=
{},
[]
@request
=
ActionController
::
TestRequest
.
new
@response
=
ActionController
::
TestResponse
.
new
request
.
session
.
session_id
=
"test_session"
end
def
render
(
options
)
self
.
renderers
<<
options
end
end
def
setup
@controller
=
DummyController
.
new
@credentials
=
{
:username
=>
"dhh"
,
:realm
=>
"testrealm@host.com"
,
:nonce
=>
ActionController
::
HttpAuthentication
::
Digest
.
nonce
(
@controller
.
request
),
:qop
=>
"auth"
,
:nc
=>
"00000001"
,
:cnonce
=>
"0a4f113b"
,
:opaque
=>
ActionController
::
HttpAuthentication
::
Digest
.
opaque
(
@controller
.
request
),
:uri
=>
"http://test.host/"
}
@encoded_credentials
=
ActionController
::
HttpAuthentication
::
Digest
.
encode_credentials
(
"GET"
,
@credentials
,
"secret"
)
end
def
test_decode_credentials
set_headers
assert_equal
@credentials
,
decode_credentials
(
@controller
.
request
)
end
def
test_nonce_format
assert_nothing_thrown
do
validate_nonce
(
@controller
.
request
,
nonce
(
@controller
.
request
))
end
end
def
test_authenticate_should_raise_for_nil_password
set_headers
ActionController
::
HttpAuthentication
::
Digest
.
encode_credentials
(
:get
,
@credentials
,
nil
)
assert_raise
ActionController
::
HttpAuthentication
::
Error
do
authenticate
(
@controller
,
@credentials
[
:realm
])
{
|
user
|
user
==
"dhh"
&&
"secret"
}
end
end
def
test_authenticate_should_raise_for_incorrect_password
set_headers
assert_raise
ActionController
::
HttpAuthentication
::
Error
do
authenticate
(
@controller
,
@credentials
[
:realm
])
{
|
user
|
user
==
"dhh"
&&
"bad password"
}
end
end
def
test_authenticate_should_not_raise_for_correct_password
set_headers
assert_nothing_thrown
do
authenticate
(
@controller
,
@credentials
[
:realm
])
{
|
user
|
user
==
"dhh"
&&
"secret"
}
end
end
private
def
set_headers
(
value
=
@encoded_credentials
,
name
=
'HTTP_AUTHORIZATION'
,
method
=
"GET"
)
@controller
.
request
.
env
[
name
]
=
value
@controller
.
request
.
env
[
"REQUEST_METHOD"
]
=
method
end
end
actionpack/test/controller/integration_test.rb
浏览文件 @
deb597d6
...
...
@@ -8,25 +8,7 @@ class SessionTest < Test::Unit::TestCase
}
def
setup
@credentials
=
{
:username
=>
"username"
,
:realm
=>
"MyApp"
,
:nonce
=>
ActionController
::
HttpAuthentication
::
Digest
.
nonce
(
"session_id"
),
:qop
=>
"auth"
,
:nc
=>
"00000001"
,
:cnonce
=>
"0a4f113b"
,
:opaque
=>
ActionController
::
HttpAuthentication
::
Digest
.
opaque
(
"session_id"
),
:uri
=>
"/index"
}
@session
=
ActionController
::
Integration
::
Session
.
new
(
StubApp
)
@session
.
nonce
=
@credentials
[
:nonce
]
@session
.
opaque
=
@credentials
[
:opaque
]
@session
.
realm
=
@credentials
[
:realm
]
end
def
encoded_credentials
(
method
)
ActionController
::
HttpAuthentication
::
Digest
.
encode_credentials
(
method
,
@credentials
,
"password"
)
end
def
test_https_bang_works_and_sets_truth_by_default
...
...
@@ -150,76 +132,6 @@ def test_head
@session
.
head
(
path
,
params
,
headers
)
end
def
test_get_with_basic
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=
\n
"
)
@session
.
expects
(
:process
).
with
(
:get
,
path
,
params
,
expected_headers
)
@session
.
get_with_basic
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_post_with_basic
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=
\n
"
)
@session
.
expects
(
:process
).
with
(
:post
,
path
,
params
,
expected_headers
)
@session
.
post_with_basic
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_put_with_basic
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=
\n
"
)
@session
.
expects
(
:process
).
with
(
:put
,
path
,
params
,
expected_headers
)
@session
.
put_with_basic
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_delete_with_basic
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=
\n
"
)
@session
.
expects
(
:process
).
with
(
:delete
,
path
,
params
,
expected_headers
)
@session
.
delete_with_basic
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_head_with_basic
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
"Basic dXNlcm5hbWU6cGFzc3dvcmQ=
\n
"
)
@session
.
expects
(
:process
).
with
(
:head
,
path
,
params
,
expected_headers
)
@session
.
head_with_basic
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_get_with_digest
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
encoded_credentials
(
:get
))
@session
.
expects
(
:process
).
with
(
:get
,
path
,
params
,
expected_headers
)
@session
.
get_with_digest
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_post_with_digest
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
encoded_credentials
(
:post
))
@session
.
expects
(
:process
).
with
(
:post
,
path
,
params
,
expected_headers
)
@session
.
post_with_digest
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_put_with_digest
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
encoded_credentials
(
:put
))
@session
.
expects
(
:process
).
with
(
:put
,
path
,
params
,
expected_headers
)
@session
.
put_with_digest
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_delete_with_digest
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
encoded_credentials
(
:delete
))
@session
.
expects
(
:process
).
with
(
:delete
,
path
,
params
,
expected_headers
)
@session
.
delete_with_digest
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_head_with_digest
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
expected_headers
=
headers
.
merge
(
:authorization
=>
encoded_credentials
(
:head
))
@session
.
expects
(
:process
).
with
(
:head
,
path
,
params
,
expected_headers
)
@session
.
head_with_digest
(
path
,
params
,
headers
,
'username'
,
'password'
)
end
def
test_xml_http_request_get
path
=
"/index"
;
params
=
"blah"
;
headers
=
{
:location
=>
'blah'
}
headers_after_xhr
=
headers
.
merge
(
...
...
actionpack/test/controller/integration_upload_test.rb
已删除
100644 → 0
浏览文件 @
e46c8076
require
'abstract_unit'
unless
defined?
ApplicationController
class
ApplicationController
<
ActionController
::
Base
end
end
class
UploadTestController
<
ActionController
::
Base
def
update
SessionUploadTest
.
last_request_type
=
ActionController
::
Base
.
param_parsers
[
request
.
content_type
]
render
:text
=>
"got here"
end
def
read
render
:text
=>
"File:
#{
params
[
:uploaded_data
].
read
}
"
end
end
class
SessionUploadTest
<
ActionController
::
IntegrationTest
FILES_DIR
=
File
.
dirname
(
__FILE__
)
+
'/../fixtures/multipart'
class
<<
self
attr_accessor
:last_request_type
end
def
test_upload_and_read_file
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FILES_DIR
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
# The lint wrapper is used in integration tests
# instead of a normal StringIO class
InputWrapper
=
Rack
::
Lint
::
InputWrapper
def
test_post_with_upload_with_unrewindable_input
InputWrapper
.
any_instance
.
expects
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FILES_DIR
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
def
test_post_with_upload_with_params_parsing
with_test_routing
do
params
=
{
:uploaded_data
=>
fixture_file_upload
(
FILES_DIR
+
"/mona_lisa.jpg"
,
"image/jpg"
)
}
post
'/update'
,
params
,
:location
=>
'blah'
assert_equal
(
:multipart_form
,
SessionUploadTest
.
last_request_type
)
end
end
private
def
with_test_routing
with_routing
do
|
set
|
set
.
draw
do
|
map
|
map
.
update
'update'
,
:controller
=>
"upload_test"
,
:action
=>
"update"
,
:method
=>
:post
map
.
read
'read'
,
:controller
=>
"upload_test"
,
:action
=>
"read"
,
:method
=>
:post
end
yield
end
end
end
actionpack/test/controller/request/multipart_params_parsing_test.rb
0 → 100644
浏览文件 @
deb597d6
require
'abstract_unit'
class
MultipartParamsParsingTest
<
ActionController
::
IntegrationTest
class
TestController
<
ActionController
::
Base
class
<<
self
attr_accessor
:last_request_parameters
end
def
parse
self
.
class
.
last_request_parameters
=
request
.
request_parameters
head
:ok
end
def
read
render
:text
=>
"File:
#{
params
[
:uploaded_data
].
read
}
"
end
end
FIXTURE_PATH
=
File
.
dirname
(
__FILE__
)
+
'/../../fixtures/multipart'
def
teardown
TestController
.
last_request_parameters
=
nil
end
test
"parses single parameter"
do
assert_equal
({
'foo'
=>
'bar'
},
parse_multipart
(
'single_parameter'
))
end
test
"parses bracketed parameters"
do
assert_equal
({
'foo'
=>
{
'baz'
=>
'bar'
}},
parse_multipart
(
'bracketed_param'
))
end
test
"parses text file"
do
params
=
parse_multipart
(
'text_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
StringIO
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert_equal
'contents'
,
file
.
read
end
test
"parses boundary problem file"
do
params
=
parse_multipart
(
'boundary_problem_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
file
=
params
[
'file'
]
foo
=
params
[
'foo'
]
assert_kind_of
Tempfile
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert_equal
'bar'
,
foo
end
test
"parses large text file"
do
params
=
parse_multipart
(
'large_text_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
Tempfile
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert
(
'a'
*
20480
)
==
file
.
read
end
test
"parses binary file"
do
params
=
parse_multipart
(
'binary_file'
)
assert_equal
%w(file flowers foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
StringIO
,
file
assert_equal
'file.csv'
,
file
.
original_filename
assert_nil
file
.
content_type
assert_equal
'contents'
,
file
.
read
file
=
params
[
'flowers'
]
assert_kind_of
StringIO
,
file
assert_equal
'flowers.jpg'
,
file
.
original_filename
assert_equal
"image/jpeg"
,
file
.
content_type
assert_equal
19512
,
file
.
size
end
test
"parses mixed files"
do
params
=
parse_multipart
(
'mixed_files'
)
assert_equal
%w(files foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
# Ruby CGI doesn't handle multipart/mixed for us.
files
=
params
[
'files'
]
assert_kind_of
String
,
files
files
.
force_encoding
(
'ASCII-8BIT'
)
if
files
.
respond_to?
(
:force_encoding
)
assert_equal
19756
,
files
.
size
end
test
"uploads and reads binary file"
do
with_test_routing
do
fixture
=
FIXTURE_PATH
+
"/mona_lisa.jpg"
params
=
{
:uploaded_data
=>
fixture_file_upload
(
fixture
,
"image/jpg"
)
}
post
'/read'
,
params
expected_length
=
'File: '
.
length
+
File
.
size
(
fixture
)
assert_equal
expected_length
,
response
.
content_length
end
end
test
"uploads and reads file"
do
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FIXTURE_PATH
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
# The lint wrapper is used in integration tests
# instead of a normal StringIO class
InputWrapper
=
Rack
::
Lint
::
InputWrapper
test
"parses unwindable stream"
do
InputWrapper
.
any_instance
.
stubs
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
params
=
parse_multipart
(
'large_text_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
end
test
"uploads and reads file with unwindable input"
do
InputWrapper
.
any_instance
.
stubs
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FIXTURE_PATH
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
test
"passes through rack middleware and uploads file"
do
with_muck_middleware
do
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FIXTURE_PATH
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
end
test
"passes through rack middleware and uploads file with unwindable input"
do
InputWrapper
.
any_instance
.
stubs
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
with_muck_middleware
do
with_test_routing
do
post
'/read'
,
:uploaded_data
=>
fixture_file_upload
(
FIXTURE_PATH
+
"/hello.txt"
,
"text/plain"
)
assert_equal
"File: Hello"
,
response
.
body
end
end
end
private
def
fixture
(
name
)
File
.
open
(
File
.
join
(
FIXTURE_PATH
,
name
),
'rb'
)
do
|
file
|
{
"rack.input"
=>
file
.
read
,
"CONTENT_TYPE"
=>
"multipart/form-data; boundary=AaB03x"
,
"CONTENT_LENGTH"
=>
file
.
stat
.
size
.
to_s
}
end
end
def
parse_multipart
(
name
)
with_test_routing
do
headers
=
fixture
(
name
)
post
"/parse"
,
headers
.
delete
(
"rack.input"
),
headers
assert_response
:ok
TestController
.
last_request_parameters
end
end
def
with_test_routing
with_routing
do
|
set
|
set
.
draw
do
|
map
|
map
.
connect
':action'
,
:controller
=>
"multipart_params_parsing_test/test"
end
yield
end
end
class
MuckMiddleware
def
initialize
(
app
)
@app
=
app
end
def
call
(
env
)
req
=
Rack
::
Request
.
new
(
env
)
req
.
params
# Parse params
@app
.
call
(
env
)
end
end
def
with_muck_middleware
original_middleware
=
ActionController
::
Dispatcher
.
middleware
middleware
=
original_middleware
.
dup
middleware
.
insert_after
ActionController
::
RewindableInput
,
MuckMiddleware
ActionController
::
Dispatcher
.
middleware
=
middleware
yield
ActionController
::
Dispatcher
.
middleware
=
original_middleware
end
end
actionpack/test/controller/query_string_parsing_test.rb
→
actionpack/test/controller/
request/
query_string_parsing_test.rb
浏览文件 @
deb597d6
文件已移动
actionpack/test/controller/request/url_encoded_params_parsing_test.rb
0 → 100644
浏览文件 @
deb597d6
require
'abstract_unit'
class
UrlEncodedParamsParsingTest
<
ActionController
::
IntegrationTest
class
TestController
<
ActionController
::
Base
class
<<
self
attr_accessor
:last_request_parameters
,
:last_request_type
end
def
parse
self
.
class
.
last_request_parameters
=
request
.
request_parameters
head
:ok
end
end
def
teardown
TestController
.
last_request_parameters
=
nil
end
test
"parses unbalanced query string with array"
do
assert_parses
(
{
'location'
=>
[
"1"
,
"2"
],
'age_group'
=>
[
"2"
]},
"location[]=1&location[]=2&age_group[]=2"
)
end
test
"parses nested hash"
do
query
=
[
"note[viewers][viewer][][type]=User"
,
"note[viewers][viewer][][id]=1"
,
"note[viewers][viewer][][type]=Group"
,
"note[viewers][viewer][][id]=2"
].
join
(
"&"
)
expected
=
{
"note"
=>
{
"viewers"
=>
{
"viewer"
=>
[{
"id"
=>
"1"
,
"type"
=>
"User"
},
{
"type"
=>
"Group"
,
"id"
=>
"2"
}
]}
}
}
assert_parses
(
expected
,
query
)
end
test
"parses more complex nesting"
do
query
=
[
"customers[boston][first][name]=David"
,
"customers[boston][first][url]=http://David"
,
"customers[boston][second][name]=Allan"
,
"customers[boston][second][url]=http://Allan"
,
"something_else=blah"
,
"something_nil="
,
"something_empty="
,
"products[first]=Apple Computer"
,
"products[second]=Pc"
,
"=Save"
].
join
(
"&"
)
expected
=
{
"customers"
=>
{
"boston"
=>
{
"first"
=>
{
"name"
=>
"David"
,
"url"
=>
"http://David"
},
"second"
=>
{
"name"
=>
"Allan"
,
"url"
=>
"http://Allan"
}
}
},
"something_else"
=>
"blah"
,
"something_empty"
=>
""
,
"something_nil"
=>
""
,
"products"
=>
{
"first"
=>
"Apple Computer"
,
"second"
=>
"Pc"
}
}
assert_parses
expected
,
query
end
test
"parses params with array"
do
query
=
"selected[]=1&selected[]=2&selected[]=3"
expected
=
{
"selected"
=>
[
"1"
,
"2"
,
"3"
]
}
assert_parses
expected
,
query
end
test
"parses params with non alphanumeric name"
do
query
=
"a/b[c]=d"
expected
=
{
"a/b"
=>
{
"c"
=>
"d"
}}
assert_parses
expected
,
query
end
test
"parses params with single brackets in the middle"
do
query
=
"a/b[c]d=e"
expected
=
{
"a/b"
=>
{}
}
assert_parses
expected
,
query
end
test
"parses params with separated brackets"
do
query
=
"a/b@[c]d[e]=f"
expected
=
{
"a/b@"
=>
{
}}
assert_parses
expected
,
query
end
test
"parses params with separated brackets and array"
do
query
=
"a/b@[c]d[e][]=f"
expected
=
{
"a/b@"
=>
{
}}
assert_parses
expected
,
query
end
test
"parses params with unmatched brackets and array"
do
query
=
"a/b@[c][d[e][]=f"
expected
=
{
"a/b@"
=>
{
"c"
=>
{
}}}
assert_parses
expected
,
query
end
test
"parses params with nil key"
do
query
=
"=&test2=value1"
expected
=
{
"test2"
=>
"value1"
}
assert_parses
expected
,
query
end
test
"parses params with array prefix and hashes"
do
query
=
"a[][b][c]=d"
expected
=
{
"a"
=>
[{
"b"
=>
{
"c"
=>
"d"
}}]}
assert_parses
expected
,
query
end
test
"parses params with complex nesting"
do
query
=
"a[][b][c][][d][]=e"
expected
=
{
"a"
=>
[{
"b"
=>
{
"c"
=>
[{
"d"
=>
[
"e"
]}]}}]}
assert_parses
expected
,
query
end
test
"parses params with file path"
do
query
=
[
"customers[boston][first][name]=David"
,
"something_else=blah"
,
"logo=
#{
File
.
expand_path
(
__FILE__
)
}
"
].
join
(
"&"
)
expected
=
{
"customers"
=>
{
"boston"
=>
{
"first"
=>
{
"name"
=>
"David"
}
}
},
"something_else"
=>
"blah"
,
"logo"
=>
File
.
expand_path
(
__FILE__
),
}
assert_parses
expected
,
query
end
test
"passes through rack middleware and parses params"
do
with_muck_middleware
do
assert_parses
({
"a"
=>
{
"b"
=>
"c"
}
},
"a[b]=c"
)
end
end
# The lint wrapper is used in integration tests
# instead of a normal StringIO class
InputWrapper
=
Rack
::
Lint
::
InputWrapper
test
"passes through rack middleware and parses params with unwindable input"
do
InputWrapper
.
any_instance
.
stubs
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
with_muck_middleware
do
assert_parses
({
"a"
=>
{
"b"
=>
"c"
}
},
"a[b]=c"
)
end
end
private
class
MuckMiddleware
def
initialize
(
app
)
@app
=
app
end
def
call
(
env
)
req
=
Rack
::
Request
.
new
(
env
)
req
.
params
# Parse params
@app
.
call
(
env
)
end
end
def
with_muck_middleware
original_middleware
=
ActionController
::
Dispatcher
.
middleware
middleware
=
original_middleware
.
dup
middleware
.
insert_after
ActionController
::
RewindableInput
,
MuckMiddleware
ActionController
::
Dispatcher
.
middleware
=
middleware
yield
ActionController
::
Dispatcher
.
middleware
=
original_middleware
end
def
with_test_routing
with_routing
do
|
set
|
set
.
draw
do
|
map
|
map
.
connect
':action'
,
:controller
=>
"url_encoded_params_parsing_test/test"
end
yield
end
end
def
assert_parses
(
expected
,
actual
)
with_test_routing
do
post
"/parse"
,
actual
assert_response
:ok
assert_equal
(
expected
,
TestController
.
last_request_parameters
)
end
end
end
actionpack/test/controller/request_test.rb
浏览文件 @
deb597d6
...
...
@@ -405,308 +405,3 @@ def request_method=(method)
@request
.
request_method
(
true
)
end
end
class
UrlEncodedRequestParameterParsingTest
<
ActiveSupport
::
TestCase
def
test_unbalanced_query_string_with_array
assert_equal
(
{
'location'
=>
[
"1"
,
"2"
],
'age_group'
=>
[
"2"
]},
ActionController
::
RequestParser
.
parse_request_parameters
({
'location[]'
=>
[
"1"
,
"2"
],
'age_group[]'
=>
[
"2"
]})
)
end
def
test_request_hash_parsing
query
=
{
"note[viewers][viewer][][type]"
=>
[
"User"
,
"Group"
],
"note[viewers][viewer][][id]"
=>
[
"1"
,
"2"
]
}
expected
=
{
"note"
=>
{
"viewers"
=>
{
"viewer"
=>
[{
"id"
=>
"1"
,
"type"
=>
"User"
},
{
"type"
=>
"Group"
,
"id"
=>
"2"
}
]}
}
}
assert_equal
(
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
query
))
end
def
test_parse_params
input
=
{
"customers[boston][first][name]"
=>
[
"David"
],
"customers[boston][first][url]"
=>
[
"http://David"
],
"customers[boston][second][name]"
=>
[
"Allan"
],
"customers[boston][second][url]"
=>
[
"http://Allan"
],
"something_else"
=>
[
"blah"
],
"something_nil"
=>
[
nil
],
"something_empty"
=>
[
""
],
"products[first]"
=>
[
"Apple Computer"
],
"products[second]"
=>
[
"Pc"
],
""
=>
[
'Save'
]
}
expected_output
=
{
"customers"
=>
{
"boston"
=>
{
"first"
=>
{
"name"
=>
"David"
,
"url"
=>
"http://David"
},
"second"
=>
{
"name"
=>
"Allan"
,
"url"
=>
"http://Allan"
}
}
},
"something_else"
=>
"blah"
,
"something_empty"
=>
""
,
"something_nil"
=>
""
,
"products"
=>
{
"first"
=>
"Apple Computer"
,
"second"
=>
"Pc"
}
}
assert_equal
expected_output
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
UploadedStringIO
=
ActionController
::
UploadedStringIO
class
MockUpload
<
UploadedStringIO
def
initialize
(
content_type
,
original_path
,
*
args
)
self
.
content_type
=
content_type
self
.
original_path
=
original_path
super
*
args
end
end
def
test_parse_params_from_multipart_upload
file
=
MockUpload
.
new
(
'img/jpeg'
,
'foo.jpg'
)
ie_file
=
MockUpload
.
new
(
'img/jpeg'
,
'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg'
)
non_file_text_part
=
MockUpload
.
new
(
'text/plain'
,
''
,
'abc'
)
input
=
{
"something"
=>
[
UploadedStringIO
.
new
(
""
)
],
"array_of_stringios"
=>
[[
UploadedStringIO
.
new
(
"One"
),
UploadedStringIO
.
new
(
"Two"
)
]],
"mixed_types_array"
=>
[[
UploadedStringIO
.
new
(
"Three"
),
"NotStringIO"
]],
"mixed_types_as_checkboxes[strings][nested]"
=>
[[
file
,
"String"
,
UploadedStringIO
.
new
(
"StringIO"
)]],
"ie_mixed_types_as_checkboxes[strings][nested]"
=>
[[
ie_file
,
"String"
,
UploadedStringIO
.
new
(
"StringIO"
)]],
"products[string]"
=>
[
UploadedStringIO
.
new
(
"Apple Computer"
)
],
"products[file]"
=>
[
file
],
"ie_products[string]"
=>
[
UploadedStringIO
.
new
(
"Microsoft"
)
],
"ie_products[file]"
=>
[
ie_file
],
"text_part"
=>
[
non_file_text_part
]
}
expected_output
=
{
"something"
=>
""
,
"array_of_stringios"
=>
[
"One"
,
"Two"
],
"mixed_types_array"
=>
[
"Three"
,
"NotStringIO"
],
"mixed_types_as_checkboxes"
=>
{
"strings"
=>
{
"nested"
=>
[
file
,
"String"
,
"StringIO"
]
},
},
"ie_mixed_types_as_checkboxes"
=>
{
"strings"
=>
{
"nested"
=>
[
ie_file
,
"String"
,
"StringIO"
]
},
},
"products"
=>
{
"string"
=>
"Apple Computer"
,
"file"
=>
file
},
"ie_products"
=>
{
"string"
=>
"Microsoft"
,
"file"
=>
ie_file
},
"text_part"
=>
"abc"
}
params
=
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
assert_equal
expected_output
,
params
# Lone filenames are preserved.
assert_equal
'foo.jpg'
,
params
[
'mixed_types_as_checkboxes'
][
'strings'
][
'nested'
].
first
.
original_filename
assert_equal
'foo.jpg'
,
params
[
'products'
][
'file'
].
original_filename
# But full Windows paths are reduced to their basename.
assert_equal
'bar.jpg'
,
params
[
'ie_mixed_types_as_checkboxes'
][
'strings'
][
'nested'
].
first
.
original_filename
assert_equal
'bar.jpg'
,
params
[
'ie_products'
][
'file'
].
original_filename
end
def
test_parse_params_with_file
input
=
{
"customers[boston][first][name]"
=>
[
"David"
],
"something_else"
=>
[
"blah"
],
"logo"
=>
[
File
.
new
(
File
.
dirname
(
__FILE__
)
+
"/rack_test.rb"
).
path
]
}
expected_output
=
{
"customers"
=>
{
"boston"
=>
{
"first"
=>
{
"name"
=>
"David"
}
}
},
"something_else"
=>
"blah"
,
"logo"
=>
File
.
new
(
File
.
dirname
(
__FILE__
)
+
"/rack_test.rb"
).
path
,
}
assert_equal
expected_output
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_array
input
=
{
"selected[]"
=>
[
"1"
,
"2"
,
"3"
]
}
expected_output
=
{
"selected"
=>
[
"1"
,
"2"
,
"3"
]
}
assert_equal
expected_output
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_non_alphanumeric_name
input
=
{
"a/b[c]"
=>
%w(d)
}
expected
=
{
"a/b"
=>
{
"c"
=>
"d"
}}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_single_brackets_in_middle
input
=
{
"a/b[c]d"
=>
%w(e)
}
expected
=
{
"a/b"
=>
{}
}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_separated_brackets
input
=
{
"a/b@[c]d[e]"
=>
%w(f)
}
expected
=
{
"a/b@"
=>
{
}}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_separated_brackets_and_array
input
=
{
"a/b@[c]d[e][]"
=>
%w(f)
}
expected
=
{
"a/b@"
=>
{
}}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_unmatched_brackets_and_array
input
=
{
"a/b@[c][d[e][]"
=>
%w(f)
}
expected
=
{
"a/b@"
=>
{
"c"
=>
{
}}}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_nil_key
input
=
{
nil
=>
nil
,
"test2"
=>
%w(value1)
}
expected
=
{
"test2"
=>
"value1"
}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_array_prefix_and_hashes
input
=
{
"a[][b][c]"
=>
%w(d)
}
expected
=
{
"a"
=>
[{
"b"
=>
{
"c"
=>
"d"
}}]}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
def
test_parse_params_with_complex_nesting
input
=
{
"a[][b][c][][d][]"
=>
%w(e)
}
expected
=
{
"a"
=>
[{
"b"
=>
{
"c"
=>
[{
"d"
=>
[
"e"
]}]}}]}
assert_equal
expected
,
ActionController
::
RequestParser
.
parse_request_parameters
(
input
)
end
end
class
MultipartRequestParameterParsingTest
<
ActiveSupport
::
TestCase
FIXTURE_PATH
=
File
.
dirname
(
__FILE__
)
+
'/../fixtures/multipart'
def
test_single_parameter
params
=
parse_multipart
(
'single_parameter'
)
assert_equal
({
'foo'
=>
'bar'
},
params
)
end
def
test_bracketed_param
assert_equal
({
'foo'
=>
{
'baz'
=>
'bar'
}},
parse_multipart
(
'bracketed_param'
))
end
def
test_text_file
params
=
parse_multipart
(
'text_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
StringIO
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert_equal
'contents'
,
file
.
read
end
def
test_boundary_problem_file
params
=
parse_multipart
(
'boundary_problem_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
file
=
params
[
'file'
]
foo
=
params
[
'foo'
]
assert_kind_of
Tempfile
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert_equal
'bar'
,
foo
end
def
test_large_text_file
params
=
parse_multipart
(
'large_text_file'
)
assert_equal
%w(file foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
Tempfile
,
file
assert_equal
'file.txt'
,
file
.
original_filename
assert_equal
"text/plain"
,
file
.
content_type
assert
(
'a'
*
20480
)
==
file
.
read
end
uses_mocha
"test_no_rewind_stream"
do
def
test_no_rewind_stream
# Ensures that parse_multipart_form_parameters works with streams that cannot be rewound
file
=
File
.
open
(
File
.
join
(
FIXTURE_PATH
,
'large_text_file'
),
'rb'
)
file
.
expects
(
:rewind
).
raises
(
Errno
::
ESPIPE
)
params
=
ActionController
::
RequestParser
.
parse_multipart_form_parameters
(
file
,
'AaB03x'
,
file
.
stat
.
size
,
{})
assert_not_equal
0
,
file
.
pos
# file was not rewound after reading
end
end
def
test_binary_file
params
=
parse_multipart
(
'binary_file'
)
assert_equal
%w(file flowers foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
file
=
params
[
'file'
]
assert_kind_of
StringIO
,
file
assert_equal
'file.csv'
,
file
.
original_filename
assert_nil
file
.
content_type
assert_equal
'contents'
,
file
.
read
file
=
params
[
'flowers'
]
assert_kind_of
StringIO
,
file
assert_equal
'flowers.jpg'
,
file
.
original_filename
assert_equal
"image/jpeg"
,
file
.
content_type
assert_equal
19512
,
file
.
size
#assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
end
def
test_mixed_files
params
=
parse_multipart
(
'mixed_files'
)
assert_equal
%w(files foo)
,
params
.
keys
.
sort
assert_equal
'bar'
,
params
[
'foo'
]
# Ruby CGI doesn't handle multipart/mixed for us.
files
=
params
[
'files'
]
assert_kind_of
String
,
files
files
.
force_encoding
(
'ASCII-8BIT'
)
if
files
.
respond_to?
(
:force_encoding
)
assert_equal
19756
,
files
.
size
end
private
def
parse_multipart
(
name
)
File
.
open
(
File
.
join
(
FIXTURE_PATH
,
name
),
'rb'
)
do
|
file
|
params
=
ActionController
::
RequestParser
.
parse_multipart_form_parameters
(
file
,
'AaB03x'
,
file
.
stat
.
size
,
{})
assert_equal
0
,
file
.
pos
# file was rewound after reading
params
end
end
end
activerecord/CHANGELOG
浏览文件 @
deb597d6
*2.3.0/3.0*
* Support nested transactions using database savepoints. #383 [Jonathan Viney, Hongli Lai]
* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
...
...
activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
浏览文件 @
deb597d6
...
...
@@ -53,36 +53,124 @@ def update(sql, name = nil)
def
delete
(
sql
,
name
=
nil
)
delete_sql
(
sql
,
name
)
end
# Checks whether there is currently no transaction active. This is done
# by querying the database driver, and does not use the transaction
# house-keeping information recorded by #increment_open_transactions and
# friends.
#
# Returns true if there is no transaction active, false if there is a
# transaction active, and nil if this information is unknown.
#
# Not all adapters supports transaction state introspection. Currently,
# only the PostgreSQL adapter supports this.
def
outside_transaction?
nil
end
# Runs the given block in a database transaction, and returns the result
# of the block.
#
# == Nested transactions support
#
# Most databases don't support true nested transactions. At the time of
# writing, the only database that supports true nested transactions that
# we're aware of, is MS-SQL.
#
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
#
# It is safe to call this method if a database transaction is already open,
# i.e. if #transaction is called within another #transaction block. In case
# of a nested call, #transaction will behave as follows:
#
# - The block will be run without doing anything. All database statements
# that happen within the block are effectively appended to the already
# open database transaction.
# - However, if +:requires_new+ is set, the block will be wrapped in a
# database savepoint acting as a sub-transaction.
#
# === Caveats
#
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
# then any created savepoints will be automatically released. For example,
# if you've created a savepoint, then you execute a CREATE TABLE statement,
# then the savepoint that was created will be automatically released.
#
# This means that, on MySQL, you shouldn't execute DDL operations inside
# a #transaction call that you know might create a savepoint. Otherwise,
# #transaction will raise exceptions when it tries to release the
# already-automatically-released savepoints:
#
# Model.connection.transaction do # BEGIN
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
# Model.connection.create_table(...)
# # active_record_1 now automatically released
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
# end
def
transaction
(
options
=
{})
options
.
assert_valid_keys
:requires_new
,
:joinable
last_transaction_joinable
=
@transaction_joinable
if
options
.
has_key?
(
:joinable
)
@transaction_joinable
=
options
[
:joinable
]
else
@transaction_joinable
=
true
end
requires_new
=
options
[
:requires_new
]
||
!
last_transaction_joinable
# Wrap a block in a transaction. Returns result of block.
def
transaction
(
start_db_transaction
=
true
)
transaction_open
=
false
begin
if
block_given?
if
start_db_transaction
begin_db_transaction
if
requires_new
||
open_transactions
==
0
if
open_transactions
==
0
begin_db_transaction
elsif
requires_new
create_savepoint
end
increment_open_transactions
transaction_open
=
true
end
yield
end
rescue
Exception
=>
database_transaction_rollback
if
transaction_open
if
transaction_open
&&
!
outside_transaction?
transaction_open
=
false
rollback_db_transaction
decrement_open_transactions
if
open_transactions
==
0
rollback_db_transaction
else
rollback_to_savepoint
end
end
raise
unless
database_transaction_rollback
.
is_a?
ActiveRecord
::
Rollback
raise
unless
database_transaction_rollback
.
is_a?
(
ActiveRecord
::
Rollback
)
end
ensure
if
transaction_open
@transaction_joinable
=
last_transaction_joinable
if
outside_transaction?
@open_transactions
=
0
elsif
transaction_open
decrement_open_transactions
begin
commit_db_transaction
if
open_transactions
==
0
commit_db_transaction
else
release_savepoint
end
rescue
Exception
=>
database_transaction_rollback
rollback_db_transaction
if
open_transactions
==
0
rollback_db_transaction
else
rollback_to_savepoint
end
raise
end
end
end
# Begins the transaction (and turns off auto-committing).
def
begin_db_transaction
()
end
...
...
activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
浏览文件 @
deb597d6
...
...
@@ -66,6 +66,12 @@ def supports_count_distinct?
def
supports_ddl_transactions?
false
end
# Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
# does not.
def
supports_savepoints?
false
end
# Should primary key values be selected from their corresponding
# sequence before the insert statement? If true, next_sequence_value
...
...
@@ -160,6 +166,23 @@ def decrement_open_transactions
@open_transactions
-=
1
end
def
transaction_joinable
=
(
joinable
)
@transaction_joinable
=
joinable
end
def
create_savepoint
end
def
rollback_to_savepoint
end
def
release_savepoint
end
def
current_savepoint_name
"active_record_
#{
open_transactions
}
"
end
def
log_info
(
sql
,
name
,
ms
)
if
@logger
&&
@logger
.
debug?
name
=
'%s (%.1fms)'
%
[
name
||
'SQL'
,
ms
]
...
...
activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
浏览文件 @
deb597d6
...
...
@@ -210,6 +210,10 @@ def adapter_name #:nodoc:
def
supports_migrations?
#:nodoc:
true
end
def
supports_savepoints?
#:nodoc:
true
end
def
native_database_types
#:nodoc:
NATIVE_DATABASE_TYPES
...
...
@@ -349,6 +353,17 @@ def rollback_db_transaction #:nodoc:
# Transactions aren't supported
end
def
create_savepoint
execute
(
"SAVEPOINT
#{
current_savepoint_name
}
"
)
end
def
rollback_to_savepoint
execute
(
"ROLLBACK TO SAVEPOINT
#{
current_savepoint_name
}
"
)
end
def
release_savepoint
execute
(
"RELEASE SAVEPOINT
#{
current_savepoint_name
}
"
)
end
def
add_limit_offset!
(
sql
,
options
)
#:nodoc:
if
limit
=
options
[
:limit
]
...
...
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
浏览文件 @
deb597d6
...
...
@@ -272,6 +272,10 @@ def supports_insert_with_returning?
def
supports_ddl_transactions?
true
end
def
supports_savepoints?
true
end
# Returns the configured supported identifier length supported by PostgreSQL,
# or report the default of 63 on PostgreSQL 7.x.
...
...
@@ -528,45 +532,26 @@ def commit_db_transaction
def
rollback_db_transaction
execute
"ROLLBACK"
end
if
defined?
(
PGconn
::
PQTRANS_IDLE
)
# The ruby-pg driver supports inspecting the transaction status,
# while the ruby-postgres driver does not.
def
outside_transaction?
@connection
.
transaction_status
==
PGconn
::
PQTRANS_IDLE
end
end
# ruby-pg defines Ruby constants for transaction status,
# ruby-postgres does not.
PQTRANS_IDLE
=
defined?
(
PGconn
::
PQTRANS_IDLE
)
?
PGconn
::
PQTRANS_IDLE
:
0
# Check whether a transaction is active.
def
transaction_active?
@connection
.
transaction_status
!=
PQTRANS_IDLE
def
create_savepoint
execute
(
"SAVEPOINT
#{
current_savepoint_name
}
"
)
end
# Wrap a block in a transaction. Returns result of block.
def
transaction
(
start_db_transaction
=
true
)
transaction_open
=
false
begin
if
block_given?
if
start_db_transaction
begin_db_transaction
transaction_open
=
true
end
yield
end
rescue
Exception
=>
database_transaction_rollback
if
transaction_open
&&
transaction_active?
transaction_open
=
false
rollback_db_transaction
end
raise
unless
database_transaction_rollback
.
is_a?
ActiveRecord
::
Rollback
end
ensure
if
transaction_open
&&
transaction_active?
begin
commit_db_transaction
rescue
Exception
=>
database_transaction_rollback
rollback_db_transaction
raise
end
end
def
rollback_to_savepoint
execute
(
"ROLLBACK TO SAVEPOINT
#{
current_savepoint_name
}
"
)
end
def
release_savepoint
execute
(
"RELEASE SAVEPOINT
#{
current_savepoint_name
}
"
)
end
# SCHEMA STATEMENTS ========================================
...
...
activerecord/lib/active_record/fixtures.rb
浏览文件 @
deb597d6
...
...
@@ -516,7 +516,7 @@ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
all_loaded_fixtures
.
update
(
fixtures_map
)
connection
.
transaction
(
connection
.
open_transactions
.
zero?
)
do
connection
.
transaction
(
:requires_new
=>
true
)
do
fixtures
.
reverse
.
each
{
|
fixture
|
fixture
.
delete_existing_fixtures
}
fixtures
.
each
{
|
fixture
|
fixture
.
insert_fixtures
}
...
...
@@ -937,6 +937,7 @@ def setup_fixtures
@@already_loaded_fixtures
[
self
.
class
]
=
@loaded_fixtures
end
ActiveRecord
::
Base
.
connection
.
increment_open_transactions
ActiveRecord
::
Base
.
connection
.
transaction_joinable
=
false
ActiveRecord
::
Base
.
connection
.
begin_db_transaction
# Load fixtures for every test.
else
...
...
activerecord/lib/active_record/session_store.rb
浏览文件 @
deb597d6
...
...
@@ -53,11 +53,6 @@ class Session < ActiveRecord::Base
before_save
:raise_on_session_data_overflow!
class
<<
self
# Don't try to reload ARStore::Session in dev mode.
def
reloadable?
#:nodoc:
false
end
def
data_column_size_limit
@data_column_size_limit
||=
columns_hash
[
@@data_column_name
].
limit
end
...
...
activerecord/lib/active_record/transactions.rb
浏览文件 @
deb597d6
...
...
@@ -120,16 +120,66 @@ def self.included(base)
# end
#
# One should restart the entire transaction if a StatementError occurred.
#
# == Nested transactions
#
# #transaction calls can be nested. By default, this makes all database
# statements in the nested transaction block become part of the parent
# transaction. For example:
#
# User.transaction do
# User.create(:username => 'Kotori')
# User.transaction do
# User.create(:username => 'Nemu')
# raise ActiveRecord::Rollback
# end
# end
#
# User.find(:all) # => empty
#
# It is also possible to requires a sub-transaction by passing
# <tt>:requires_new => true</tt>. If anything goes wrong, the
# database rolls back to the beginning of the sub-transaction
# without rolling back the parent transaction. For example:
#
# User.transaction do
# User.create(:username => 'Kotori')
# User.transaction(:requires_new => true) do
# User.create(:username => 'Nemu')
# raise ActiveRecord::Rollback
# end
# end
#
# User.find(:all) # => Returns only Kotori
#
# Most databases don't support true nested transactions. At the time of
# writing, the only database that we're aware of that supports true nested
# transactions, is MS-SQL. Because of this, Active Record emulates nested
# transactions by using savepoints. See
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
# for more information about savepoints.
#
# === Caveats
#
# If you're on MySQL, then do not use DDL operations in nested transactions
# blocks that are emulated with savepoints. That is, do not execute statements
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
# releases all savepoints upon executing a DDL operation. When #transaction
# is finished and tries to release the savepoint it created earlier, a
# database error will occur because the savepoint has already been
# automatically released. The following example demonstrates the problem:
#
# Model.connection.transaction do # BEGIN
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
# Model.connection.create_table(...) # active_record_1 now automatically released
# end # RELEASE savepoint active_record_1
# # ^^^^ BOOM! database error!
# end
module
ClassMethods
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
def
transaction
(
&
block
)
connection
.
increment_open_transactions
begin
connection
.
transaction
(
connection
.
open_transactions
==
1
,
&
block
)
ensure
connection
.
decrement_open_transactions
end
def
transaction
(
options
=
{},
&
block
)
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
connection
.
transaction
(
options
,
&
block
)
end
end
...
...
activerecord/test/cases/defaults_test.rb
浏览文件 @
deb597d6
...
...
@@ -18,11 +18,43 @@ def test_nil_defaults_for_not_null_columns
end
end
if
current_adapter?
(
:MysqlAdapter
)
if
current_adapter?
(
:PostgreSQLAdapter
,
:FirebirdAdapter
,
:OpenBaseAdapter
,
:OracleAdapter
)
def
test_default_integers
default
=
Default
.
new
assert_instance_of
Fixnum
,
default
.
positive_integer
assert_equal
1
,
default
.
positive_integer
assert_instance_of
Fixnum
,
default
.
negative_integer
assert_equal
-
1
,
default
.
negative_integer
assert_instance_of
BigDecimal
,
default
.
decimal_number
assert_equal
BigDecimal
.
new
(
"2.78"
),
default
.
decimal_number
end
end
if
current_adapter?
(
:PostgreSQLAdapter
)
def
test_multiline_default_text
# older postgres versions represent the default with escapes ("\\012" for a newline)
assert
(
"--- []
\n\n
"
==
Default
.
columns_hash
[
'multiline_default'
].
default
||
"--- []
\\
012
\\
012"
==
Default
.
columns_hash
[
'multiline_default'
].
default
)
end
end
end
#MySQL 5 and higher is quirky with not null text/blob columns.
#With MySQL Text/blob columns cannot have defaults. If the column is not null MySQL will report that the column has a null default
#but it behaves as though the column had a default of ''
if
current_adapter?
(
:MysqlAdapter
)
class
DefaultsTestWithoutTransactionalFixtures
<
ActiveRecord
::
TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional fixtures mode, this will
# cause ActiveRecord to create a new savepoint. However, since MySQL doesn't
# support DDL transactions, creating a table will result in any created
# savepoints to be automatically released. This in turn causes the savepoint
# release code in AbstractAdapter#transaction to fail.
#
# We don't want that to happen, so we disable transactional fixtures here.
self
.
use_transactional_fixtures
=
false
# MySQL 5 and higher is quirky with not null text/blob columns.
# With MySQL Text/blob columns cannot have defaults. If the column is not
# null MySQL will report that the column has a null default
# but it behaves as though the column had a default of ''
def
test_mysql_text_not_null_defaults
klass
=
Class
.
new
(
ActiveRecord
::
Base
)
klass
.
table_name
=
'test_mysql_text_not_null_defaults'
...
...
@@ -48,8 +80,7 @@ def test_mysql_text_not_null_defaults
ensure
klass
.
connection
.
drop_table
(
klass
.
table_name
)
rescue
nil
end
# MySQL uses an implicit default 0 rather than NULL unless in strict mode.
# We use an implicit NULL so schema.rb is compatible with other databases.
def
test_mysql_integer_not_null_defaults
...
...
@@ -77,24 +108,4 @@ def test_mysql_integer_not_null_defaults
klass
.
connection
.
drop_table
(
klass
.
table_name
)
rescue
nil
end
end
if
current_adapter?
(
:PostgreSQLAdapter
,
:FirebirdAdapter
,
:OpenBaseAdapter
,
:OracleAdapter
)
def
test_default_integers
default
=
Default
.
new
assert_instance_of
Fixnum
,
default
.
positive_integer
assert_equal
1
,
default
.
positive_integer
assert_instance_of
Fixnum
,
default
.
negative_integer
assert_equal
-
1
,
default
.
negative_integer
assert_instance_of
BigDecimal
,
default
.
decimal_number
assert_equal
BigDecimal
.
new
(
"2.78"
),
default
.
decimal_number
end
end
if
current_adapter?
(
:PostgreSQLAdapter
)
def
test_multiline_default_text
# older postgres versions represent the default with escapes ("\\012" for a newline)
assert
(
"--- []
\n\n
"
==
Default
.
columns_hash
[
'multiline_default'
].
default
||
"--- []
\\
012
\\
012"
==
Default
.
columns_hash
[
'multiline_default'
].
default
)
end
end
end
activerecord/test/cases/helper.rb
浏览文件 @
deb597d6
...
...
@@ -34,7 +34,7 @@ def uses_mocha(description)
end
ActiveRecord
::
Base
.
connection
.
class
.
class_eval
do
IGNORED_SQL
=
[
/^PRAGMA/
,
/^SELECT currval/
,
/^SELECT CAST/
,
/^SELECT @@IDENTITY/
,
/^SELECT @@ROWCOUNT/
]
IGNORED_SQL
=
[
/^PRAGMA/
,
/^SELECT currval/
,
/^SELECT CAST/
,
/^SELECT @@IDENTITY/
,
/^SELECT @@ROWCOUNT/
,
/^SAVEPOINT/
,
/^ROLLBACK TO SAVEPOINT/
,
/^RELEASE SAVEPOINT/
]
def
execute_with_query_record
(
sql
,
name
=
nil
,
&
block
)
$queries_executed
||=
[]
...
...
activerecord/test/cases/transactions_test.rb
浏览文件 @
deb597d6
...
...
@@ -213,11 +213,104 @@ def test_manually_rolling_back_a_transaction
assert
Topic
.
find
(
2
).
approved?
,
"Second should still be approved"
end
def
test_invalid_keys_for_transaction
assert_raises
ArgumentError
do
Topic
.
transaction
:nested
=>
true
do
end
end
end
def
test_force_savepoint_in_nested_transaction
Topic
.
transaction
do
@first
.
approved
=
true
@second
.
approved
=
false
@first
.
save!
@second
.
save!
begin
Topic
.
transaction
:requires_new
=>
true
do
@first
.
happy
=
false
@first
.
save!
raise
end
rescue
end
end
assert
@first
.
reload
.
approved?
assert
!
@second
.
reload
.
approved?
end
if
Topic
.
connection
.
supports_savepoints?
def
test_no_savepoint_in_nested_transaction_without_force
Topic
.
transaction
do
@first
.
approved
=
true
@second
.
approved
=
false
@first
.
save!
@second
.
save!
begin
Topic
.
transaction
do
@first
.
approved
=
false
@first
.
save!
raise
end
rescue
end
end
assert
!
@first
.
reload
.
approved?
assert
!
@second
.
reload
.
approved?
end
if
Topic
.
connection
.
supports_savepoints?
def
test_many_savepoints
Topic
.
transaction
do
@first
.
content
=
"One"
@first
.
save!
begin
Topic
.
transaction
:requires_new
=>
true
do
@first
.
content
=
"Two"
@first
.
save!
begin
Topic
.
transaction
:requires_new
=>
true
do
@first
.
content
=
"Three"
@first
.
save!
begin
Topic
.
transaction
:requires_new
=>
true
do
@first
.
content
=
"Four"
@first
.
save!
raise
end
rescue
end
@three
=
@first
.
reload
.
content
raise
end
rescue
end
@two
=
@first
.
reload
.
content
raise
end
rescue
end
@one
=
@first
.
reload
.
content
end
assert_equal
"One"
,
@one
assert_equal
"Two"
,
@two
assert_equal
"Three"
,
@three
end
if
Topic
.
connection
.
supports_savepoints?
uses_mocha
'mocking connection.commit_db_transaction'
do
def
test_rollback_when_commit_raises
Topic
.
connection
.
expects
(
:begin_db_transaction
)
Topic
.
connection
.
expects
(
:transaction_active?
).
returns
(
true
)
if
current_adapter?
(
:PostgreSQLAdapter
)
Topic
.
connection
.
expects
(
:commit_db_transaction
).
raises
(
'OH NOES'
)
Topic
.
connection
.
expects
(
:outside_transaction?
).
returns
(
false
)
Topic
.
connection
.
expects
(
:rollback_db_transaction
)
assert_raise
RuntimeError
do
...
...
@@ -227,6 +320,38 @@ def test_rollback_when_commit_raises
end
end
end
if
current_adapter?
(
:PostgreSQLAdapter
)
&&
defined?
(
PGconn
::
PQTRANS_IDLE
)
def
test_outside_transaction_works
assert
Topic
.
connection
.
outside_transaction?
Topic
.
connection
.
begin_db_transaction
assert
!
Topic
.
connection
.
outside_transaction?
Topic
.
connection
.
rollback_db_transaction
assert
Topic
.
connection
.
outside_transaction?
end
uses_mocha
'mocking connection.rollback_db_transaction'
do
def
test_rollback_wont_be_executed_if_no_transaction_active
assert_raise
RuntimeError
do
Topic
.
transaction
do
Topic
.
connection
.
rollback_db_transaction
Topic
.
connection
.
expects
(
:rollback_db_transaction
).
never
raise
"Rails doesn't scale!"
end
end
end
end
def
test_open_transactions_count_is_reset_to_zero_if_no_transaction_active
Topic
.
transaction
do
Topic
.
transaction
do
Topic
.
connection
.
rollback_db_transaction
end
assert_equal
0
,
Topic
.
connection
.
open_transactions
end
assert_equal
0
,
Topic
.
connection
.
open_transactions
end
end
def
test_sqlite_add_column_in_transaction_raises_statement_invalid
return
true
unless
current_adapter?
(
:SQLite3Adapter
,
:SQLiteAdapter
)
...
...
@@ -282,6 +407,45 @@ def remove_exception_raising_after_create_callback_to_topic
end
end
class
TransactionsWithTransactionalFixturesTest
<
ActiveRecord
::
TestCase
self
.
use_transactional_fixtures
=
true
fixtures
:topics
def
test_automatic_savepoint_in_outer_transaction
@first
=
Topic
.
find
(
1
)
begin
Topic
.
transaction
do
@first
.
approved
=
true
@first
.
save!
raise
end
rescue
assert
!
@first
.
reload
.
approved?
end
end
def
test_no_automatic_savepoint_for_inner_transaction
@first
=
Topic
.
find
(
1
)
Topic
.
transaction
do
@first
.
approved
=
true
@first
.
save!
begin
Topic
.
transaction
do
@first
.
approved
=
false
@first
.
save!
raise
end
rescue
end
end
assert
!
@first
.
reload
.
approved?
end
end
if
Topic
.
connection
.
supports_savepoints?
if
current_adapter?
(
:PostgreSQLAdapter
)
class
ConcurrentTransactionTest
<
TransactionTest
use_concurrent_connections
...
...
activesupport/CHANGELOG
浏览文件 @
deb597d6
*2.3.0 [Edge]*
* TimeWithZone#xmlschema accepts optional fraction_digits argument [#1725 state:resolved] [Nicholas Dainty]
* Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. [Jeremy Kemper]
array.select { ... }.tap(&:inspect).map { ... }
...
...
activesupport/lib/active_support/core_ext/object/misc.rb
浏览文件 @
deb597d6
...
...
@@ -102,6 +102,6 @@ def acts_like?(duck)
# Person.try(:find, 1)
# @people.try(:map) {|p| p.name}
def
try
(
method
,
*
args
,
&
block
)
send
(
method
,
*
args
,
&
block
)
if
respond_to?
(
method
,
true
)
send
(
method
,
*
args
,
&
block
)
unless
self
.
nil?
end
end
activesupport/lib/active_support/time_with_zone.rb
浏览文件 @
deb597d6
...
...
@@ -99,8 +99,12 @@ def inspect
"
#{
time
.
strftime
(
'%a, %d %b %Y %H:%M:%S'
)
}
#{
zone
}
#{
formatted_offset
}
"
end
def
xmlschema
"
#{
time
.
strftime
(
"%Y-%m-%dT%H:%M:%S"
)
}#{
formatted_offset
(
true
,
'Z'
)
}
"
def
xmlschema
(
fraction_digits
=
0
)
fraction
=
if
fraction_digits
>
0
".%i"
%
time
.
usec
.
to_s
[
0
,
fraction_digits
]
end
"
#{
time
.
strftime
(
"%Y-%m-%dT%H:%M:%S"
)
}#{
fraction
}#{
formatted_offset
(
true
,
'Z'
)
}
"
end
alias_method
:iso8601
,
:xmlschema
...
...
activesupport/test/core_ext/object_and_class_ext_test.rb
浏览文件 @
deb597d6
...
...
@@ -256,21 +256,13 @@ def setup
def
test_nonexisting_method
method
=
:undefined_method
assert
!
@string
.
respond_to?
(
method
)
assert_
nil
@string
.
try
(
method
)
assert_
raises
(
NoMethodError
)
{
@string
.
try
(
method
)
}
end
def
test_valid_method
assert_equal
5
,
@string
.
try
(
:size
)
end
def
test_valid_private_method
class
<<
@string
private
:size
end
assert_equal
5
,
@string
.
try
(
:size
)
end
def
test_argument_forwarding
assert_equal
'Hey'
,
@string
.
try
(
:sub
,
'llo'
,
'y'
)
end
...
...
@@ -278,4 +270,13 @@ def test_argument_forwarding
def
test_block_forwarding
assert_equal
'Hey'
,
@string
.
try
(
:sub
,
'llo'
)
{
|
match
|
'y'
}
end
def
test_nil_to_type
assert_nil
nil
.
try
(
:to_s
)
assert_nil
nil
.
try
(
:to_i
)
end
def
test_false_try
assert_equal
'false'
,
false
.
try
(
:to_s
)
end
end
activesupport/test/core_ext/time_with_zone_test.rb
浏览文件 @
deb597d6
...
...
@@ -105,6 +105,15 @@ def test_xmlschema
end
end
def
test_xmlschema_with_fractional_seconds
silence_warnings
do
# silence warnings raised by tzinfo gem
@twz
+=
0.123456
# advance the time by a fraction of a second
assert_equal
"1999-12-31T19:00:00.123-05:00"
,
@twz
.
xmlschema
(
3
)
assert_equal
"1999-12-31T19:00:00.123456-05:00"
,
@twz
.
xmlschema
(
6
)
assert_equal
"1999-12-31T19:00:00.123456-05:00"
,
@twz
.
xmlschema
(
12
)
end
end
def
test_to_yaml
silence_warnings
do
# silence warnings raised by tzinfo gem
assert_equal
"--- 1999-12-31 19:00:00 -05:00
\n
"
,
@twz
.
to_yaml
...
...
railties/configs/initializers/session_store.rb
浏览文件 @
deb597d6
...
...
@@ -5,7 +5,7 @@
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
ActionController
::
Base
.
session
=
{
:
session_key
=>
'_<%= app_name %>_session'
,
:
key
=>
'_<%= app_name %>_session'
,
:secret
=>
'<%= app_secret %>'
}
...
...
railties/doc/guides/source/security.txt
浏览文件 @
deb597d6
...
...
@@ -93,7 +93,7 @@ That means the security of this storage depends on this secret (and of the diges
....................................
config.action_controller.session = {
:
session_key
=> ‘_app_session’,
:
key
=> ‘_app_session’,
:secret => ‘0x0dkfj3927dkc7djdh36rkckdfzsg...’
}
....................................
...
...
railties/lib/initializer.rb
浏览文件 @
deb597d6
...
...
@@ -537,7 +537,7 @@ def initialize_i18n
end
def
initialize_metal
configuration
.
middleware
.
insert_before
(
:"ActionController::
VerbPiggybacking
"
,
Rails
::
Rack
::
Metal
)
configuration
.
middleware
.
insert_before
(
:"ActionController::
RewindableInput
"
,
Rails
::
Rack
::
Metal
)
end
# Initializes framework-specific settings for each of the loaded frameworks
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录