diff --git a/.travis.yml b/.travis.yml index 2dbb540890..de76b7a47d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,16 @@ +--- +after_script: +- rake travis:after -t before_script: - - gem install hoe - - rake check_extra_deps -rvm: - - 1.8.7 - - 1.9.2 - - 1.9.3 - - ruby-head +- gem install hoe-travis --no-rdoc --no-ri +- rake travis:before -t +language: ruby notifications: email: - - drbrain@segment7.net + - drbrain@segment7.net +rvm: +- 1.8.7 +- 1.9.2 +- 1.9.3 +- 2.0.0 +script: rake travis diff --git a/CVE-2013-0256.rdoc b/CVE-2013-0256.rdoc new file mode 100644 index 0000000000..b285b6ac45 --- /dev/null +++ b/CVE-2013-0256.rdoc @@ -0,0 +1,49 @@ += RDoc 2.3.0 through 3.12 XSS Exploit + +RDoc documentation generated by rdoc 2.3.0 through rdoc 3.12 and prereleases up +to rdoc 4.0.0.preview2.1 are vulnerable to an XSS exploit. This exploit may +lead to cookie disclosure to third parties. + +The exploit exists in darkfish.js which is copied from the RDoc install +location to the generated documentation. + +RDoc is a static documentation generation tool. Patching the library itself +is insufficient to correct this exploit. Those hosting rdoc documentation will +need to apply the following patch. If applied while ignoring whitespace, this +patch will correct all affected versions: + + diff --git darkfish.js darkfish.js + index 4be722f..f26fd45 100644 + --- darkfish.js + +++ darkfish.js + @@ -109,13 +109,15 @@ function hookSearch() { + function highlightTarget( anchor ) { + console.debug( "Highlighting target '%s'.", anchor ); + + - $("a[name=" + anchor + "]").each( function() { + - if ( !$(this).parent().parent().hasClass('target-section') ) { + - console.debug( "Wrapping the target-section" ); + - $('div.method-detail').unwrap( 'div.target-section' ); + - $(this).parent().wrap( '
' ); + - } else { + - console.debug( "Already wrapped." ); + + $("a[name]").each( function() { + + if ( $(this).attr("name") == anchor ) { + + if ( !$(this).parent().parent().hasClass('target-section') ) { + + console.debug( "Wrapping the target-section" ); + + $('div.method-detail').unwrap( 'div.target-section' ); + + $(this).parent().wrap( '' ); + + } else { + + console.debug( "Already wrapped." ); + + } + } + }); + }; + +RDoc 3.9.5, 3.12.1 and RDoc 4.0.0.rc.2 and newer are not vulnerable to this +exploit. + +This exploit was discovered by Evgeny Ermakov?x). Reported by Xavier
+ Noria.
+ * Fixed tokenization of % when it is not followed by a $-string type
+ * Fixed display of __END__ in documentation examples in HTML output
+ * Fixed tokenization of reserved words used as new-style hash keys
+ * Fixed HEREDOC output for the limited case of a heredoc followed by a line
+ end. When a HEREDOC is not followed by a line end RDoc is not currently
+ smart enough to restore the source correctly. Bug #162 by Zachary Scott.
+
+=== 3.12.1 / 2013-02-05
+
+* Bug fixes
+ * Fixed an XSS exploit in darkfish.js. This could lead to cookie disclosure
+ to third parties. See CVE-2013-0256[rdoc-ref:CVE-2013-0256.rdoc] for full
+ details including a patch you can apply to generated RDoc documentation.
+ * Ensured that rd parser files are generated before checking the manifest.
+
=== 3.12 / 2011-12-15
* Minor enhancements
diff --git a/Manifest.txt b/Manifest.txt
index 4ace335c64..5f2f9528d5 100644
--- a/Manifest.txt
+++ b/Manifest.txt
@@ -1,5 +1,6 @@
.autotest
.document
+CVE-2013-0256.rdoc
DEVELOPERS.rdoc
History.rdoc
LEGAL.rdoc
@@ -128,8 +129,10 @@ lib/rdoc/parser/ruby_tools.rb
lib/rdoc/parser/simple.rb
lib/rdoc/parser/text.rb
lib/rdoc/rd.rb
+lib/rdoc/rd/block_parser.rb
lib/rdoc/rd/block_parser.ry
lib/rdoc/rd/inline.rb
+lib/rdoc/rd/inline_parser.rb
lib/rdoc/rd/inline_parser.ry
lib/rdoc/rdoc.rb
lib/rdoc/require.rb
diff --git a/Rakefile b/Rakefile
index aad7380b54..e4a19c7996 100644
--- a/Rakefile
+++ b/Rakefile
@@ -42,6 +42,7 @@ Depending on your version of ruby, you may need to install ruby rdoc/ri data:
self.testlib = :minitest
self.extra_rdoc_files += %w[
DEVELOPERS.rdoc
+ CVE-2013-0256.rdoc
History.rdoc
LICENSE.rdoc
LEGAL.rdoc
@@ -64,6 +65,7 @@ Depending on your version of ruby, you may need to install ruby rdoc/ri data:
end
task :generate => PARSER_FILES
+task :check_manifest => :generate
rule '.rb' => '.ry' do |t|
racc = Gem.bin_path 'racc', 'racc'
diff --git a/lib/rdoc.rb b/lib/rdoc.rb
index 86a4ecf5f2..b03f8270f8 100644
--- a/lib/rdoc.rb
+++ b/lib/rdoc.rb
@@ -108,7 +108,7 @@ def self.const_missing const_name # :nodoc:
##
# RDoc version you are using
- VERSION = '3.12'
+ VERSION = '3.12.2'
##
# Method visibilities
diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js
index 4be722fac3..f26fd45d3a 100644
--- a/lib/rdoc/generator/template/darkfish/js/darkfish.js
+++ b/lib/rdoc/generator/template/darkfish/js/darkfish.js
@@ -109,13 +109,15 @@ function hookSearch() {
function highlightTarget( anchor ) {
console.debug( "Highlighting target '%s'.", anchor );
- $("a[name=" + anchor + "]").each( function() {
- if ( !$(this).parent().parent().hasClass('target-section') ) {
- console.debug( "Wrapping the target-section" );
- $('div.method-detail').unwrap( 'div.target-section' );
- $(this).parent().wrap( '' );
- } else {
- console.debug( "Already wrapped." );
+ $("a[name]").each( function() {
+ if ( $(this).attr("name") == anchor ) {
+ if ( !$(this).parent().parent().hasClass('target-section') ) {
+ console.debug( "Wrapping the target-section" );
+ $('div.method-detail').unwrap( 'div.target-section' );
+ $(this).parent().wrap( '' );
+ } else {
+ console.debug( "Already wrapped." );
+ }
}
});
};
diff --git a/lib/rdoc/ruby_lex.rb b/lib/rdoc/ruby_lex.rb
index 209f01133f..e6e0b41aab 100644
--- a/lib/rdoc/ruby_lex.rb
+++ b/lib/rdoc/ruby_lex.rb
@@ -412,7 +412,7 @@ def token
def lex_init()
@OP = IRB::SLex.new
@OP.def_rules("\0", "\004", "\032") do |op, io|
- Token(TkEND_OF_SCRIPT)
+ Token(TkEND_OF_SCRIPT, '')
end
@OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io|
@@ -536,13 +536,8 @@ def lex_init()
@lex_state = EXPR_BEG;
Token(TkQUESTION)
else
- str = ch
- if ch == '\\'
- str << read_escape
- end
@lex_state = EXPR_END
- str << (ch.respond_to?(:ord) ? ch.ord : ch[0])
- Token(TkINTEGER, str)
+ Token(TkSTRING, ch)
end
end
end
@@ -813,7 +808,8 @@ def lex_int2
@OP.def_rule("_") do
if peek_match?(/_END__/) and @lex_state == EXPR_BEG then
- Token(TkEND_OF_SCRIPT)
+ 6.times { getc }
+ Token(TkEND_OF_SCRIPT, '__END__')
else
ungetc
identify_identifier
@@ -861,7 +857,7 @@ def identify_gvar
end
IDENT_RE = if defined? Encoding then
- /[\w\u0080-\uFFFF]/u
+ eval '/[\w\u{0080}-\u{FFFFF}]/u' # 1.8 can't parse \u{}
else
/[\w\x80-\xFF]/
end
@@ -940,6 +936,8 @@ def identify_identifier
@indent += 1
@indent_stack.push token_c
end
+ else
+ token_c = TkIDENTIFIER
end
elsif DEINDENT_CLAUSE.include?(token)
@@ -984,12 +982,13 @@ def identify_here_document
indent = true
end
if /['"`]/ =~ ch
- lt = ch
+ user_quote = lt = ch
quoted = ""
while (c = getc) && c != lt
quoted.concat c
end
else
+ user_quote = nil
lt = '"'
quoted = ch.dup
while (c = getc) && c =~ /\w/
@@ -1009,8 +1008,17 @@ def identify_here_document
end
end
+ output_heredoc = reserve.join =~ /\A\r?\n\z/
+
+ if output_heredoc then
+ doc = '<<'
+ doc << '-' if indent
+ doc << "#{user_quote}#{quoted}#{user_quote}\n"
+ else
+ doc = '"'
+ end
+
@here_header = false
- doc = '"'
while l = gets
l = l.sub(/(:?\r)?\n\z/, "\n")
if (indent ? l.strip : l.chomp) == quoted
@@ -1018,7 +1026,12 @@ def identify_here_document
end
doc << l
end
- doc << '"'
+
+ if output_heredoc then
+ doc << l.chomp
+ else
+ doc << '"'
+ end
@here_header = true
@here_readed.concat reserve
@@ -1026,9 +1039,10 @@ def identify_here_document
ungetc ch
end
+ token_class = output_heredoc ? RDoc::RubyLex::TkHEREDOC : Ltype2Token[lt]
@ltype = ltback
@lex_state = EXPR_END
- Token(Ltype2Token[lt], doc)
+ Token(token_class, doc)
end
def identify_quotation
@@ -1039,7 +1053,7 @@ def identify_quotation
type = nil
lt = "\""
else
- raise Error, "unknown type of %string #{type.inspect}"
+ return Token(TkMOD, '%')
end
# if ch !~ /\W/
# ungetc
@@ -1163,7 +1177,7 @@ def identify_string(ltype, quoted = ltype, type = nil)
@ltype = ltype
@quoted = quoted
- str = if ltype == quoted and %w[" '].include? ltype then
+ str = if ltype == quoted and %w[" ' /].include? ltype then
ltype.dup
elsif RUBY_VERSION > '1.9' then
"%#{type or PERCENT_LTYPE.key ltype}#{PERCENT_PAREN_REV[quoted]}"
@@ -1189,14 +1203,18 @@ def identify_string(ltype, quoted = ltype, type = nil)
else
ungetc
end
- elsif ch == '\\' and @ltype == "'" #'
- case ch = getc
- when "\\", "\n", "'"
+ elsif ch == '\\'
+ if %w[' /].include? @ltype then
+ case ch = getc
+ when "\\", "\n", "'"
+ when @ltype
+ str << ch
+ else
+ ungetc
+ end
else
- ungetc
+ str << read_escape
end
- elsif ch == '\\' #'
- str << read_escape
end
if close then
diff --git a/lib/rdoc/ruby_token.rb b/lib/rdoc/ruby_token.rb
index 687ef2fe80..d3333bc6a1 100644
--- a/lib/rdoc/ruby_token.rb
+++ b/lib/rdoc/ruby_token.rb
@@ -331,6 +331,7 @@ def Token(token, value = nil)
[:TkINTEGER, TkVal],
[:TkFLOAT, TkVal],
[:TkSTRING, TkVal],
+ [:TkHEREDOC, TkVal],
[:TkXSTRING, TkVal],
[:TkREGEXP, TkVal],
[:TkSYMBOL, TkVal],
diff --git a/test/test_rdoc_markup_to_html.rb b/test/test_rdoc_markup_to_html.rb
index 88acf57ea8..a84270fbb5 100644
--- a/test/test_rdoc_markup_to_html.rb
+++ b/test/test_rdoc_markup_to_html.rb
@@ -350,14 +350,14 @@ def test_accept_verbatim_parseable_error
rdoc.options = options
RDoc::RDoc.current = rdoc
- verb = @RM::Verbatim.new("a %z'foo' # => blah\n")
+ verb = @RM::Verbatim.new("a % 09 # => blah\n")
@to.start_accepting
@to.accept_verbatim verb
expected = <<-EXPECTED
-a %z'foo' # => blah +a % 09 # => blahEXPECTED diff --git a/test/test_rdoc_markup_to_html_snippet.rb b/test/test_rdoc_markup_to_html_snippet.rb index 067ac9647b..99d9cb778f 100644 --- a/test/test_rdoc_markup_to_html_snippet.rb +++ b/test/test_rdoc_markup_to_html_snippet.rb @@ -379,19 +379,19 @@ def test_accept_verbatim_ruby_error rdoc.options = options RDoc::RDoc.current = rdoc - verb = @RM::Verbatim.new("a %z'foo' # => blah\n") + verb = @RM::Verbatim.new("a % 09 # => blah\n") @to.start_accepting @to.accept_verbatim verb expected = <<-EXPECTED -a %z'foo' # => blah +EXPECTED actual = @to.convert rdoc diff --git a/test/test_rdoc_ruby_lex.rb b/test/test_rdoc_ruby_lex.rb index 6410c587b7..0dcb42565d 100644 --- a/test/test_rdoc_ruby_lex.rb +++ b/test/test_rdoc_ruby_lex.rb @@ -1,3 +1,5 @@ +# coding: UTF-8 + require 'rdoc/test_case' class TestRDocRubyLex < RDoc::TestCase @@ -30,6 +32,28 @@ def test_class_tokenize assert_equal expected, tokens end + def test_class_tokenize___END__ + tokens = RDoc::RubyLex.tokenize '__END__', nil + + expected = [ + @TK::TkEND_OF_SCRIPT.new(0, 1, 0, '__END__'), + @TK::TkNL .new(7, 1, 7, "\n"), + ] + + assert_equal expected, tokens + end + + def test_class_tokenize_character_literal + tokens = RDoc::RubyLex.tokenize "?\\", nil + + expected = [ + @TK::TkSTRING.new( 0, 1, 0, "\\"), + @TK::TkNL .new( 2, 1, 2, "\n"), + ] + + assert_equal expected, tokens + end + def test_class_tokenize_def_heredoc tokens = RDoc::RubyLex.tokenize <<-'RUBY', nil def x @@ -46,7 +70,8 @@ def x @TK::TkIDENTIFIER.new( 4, 1, 4, 'x'), @TK::TkNL .new( 5, 1, 5, "\n"), @TK::TkSPACE .new( 6, 2, 0, ' '), - @TK::TkSTRING .new( 8, 2, 2, %Q{"Line 1\nLine 2\n"}), + @TK::TkHEREDOC .new( 8, 2, 2, + %Q{<a % 09 # => blahEXPECTED assert_equal expected, @to.res.join - assert_equal 19, @to.characters + assert_equal 16, @to.characters end def test_add_paragraph @@ -543,11 +543,12 @@ def test_convert_limit_verbatim_multiline This routine modifies its +comment+ parameter. RDOC + inner = CGI.escapeHTML "# Don't display comment from this point forward" expected = <<-EXPECTEDLook for directives in a normal comment block:
# :stopdoc: -# Don't display comment from this point forward+#{inner}