# iExploder - Generates bad HTML files to perform QA for web browsers.
# Developed for the Mozilla Foundation.
#####################
#
# Copyright (c) 2006 Thomas Stromberg <thomas%stromberg.org>
#
# This software is provided 'as-is', without any express or implied warranty.
# In no event will the authors be held liable for any damages arising from the
# use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software in a
# product, an acknowledgment in the product documentation would be appreciated
# but is not required.
#
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
#
# 3. This notice may not be removed or altered from any source distribution.
$VERSION="1.3.2"
class IExploder
attr_accessor :test_num, :subtest_num, :lookup_mode, :random_mode, :url
attr_accessor :offset, :lines, :stop_num
def initialize(max_tags, max_attrs, max_props)
@htmlMaxTags = max_tags
@htmlMaxAttrs = max_attrs
@cssMaxProps = max_props
@mangledTagTotal = 0
@stop_num = 0
end
def setRandomSeed
if @test_num > 0
srand(@test_num)
else
srand
end
end
def readTagFiles
# These if statements are so that mod_ruby doesn't have to reload the files
# each time
if (! @cssTags)
@cssTags = readTagFile('cssproperties.in');
end
if (! @htmlTags)
@htmlTags = readTagFile('htmltags.in');
end
if (! @htmlAttr)
@htmlAttr = readTagFile('htmlattrs.in');
end
if (! @htmlValues)
@htmlValues = readTagFile('htmlvalues.in');
end
if (! @cssValues)
@cssValues = readTagFile('cssvalues.in');
end
end
def readTagFile(filename)
list = Array.new
File.new(filename).readlines.each { |line|
line.chop!
# Don't include comments.
if (line !~ /^# /) && (line.length > 0)
list << line
end
}
return list
end
# based on make_up_value, essentially.
def inventValue
value = rand(19);
case value
when 1..3 then return (@htmlValues[rand(@htmlValues.length)])
when 4..5 then return (@htmlValues[rand(@htmlValues.length)] + inventValue())
when 6 then return (@htmlValues[rand(@htmlValues.length)] + "//" + inventValue())
when 7 then return ''
# this may return negative argument?
when 8..10 then return rand(255).chr * (rand(256)+8)
when 11 then return rand(255).chr * (rand(2048)+8)
when 12 then return "#" + rand(999999).to_s
when 13 then return rand(999999).to_s + "%"
when 14..15 then return "&" + rand(999999).to_s + ";"
# filters
when 16 then
return inventValue() + "=" + inventValue()
# this my return undefined method + for nil:NilClass
when 17 then return inventValue() + "," + inventValue()
else
if rand(5) > 3
return "-" + rand(999999).to_s
else
return rand(999999).to_s
end
end
end
# based on make_up_value, essentially.
def inventCssValue(tag)
value = rand(23);
case value
when 1..10 then return @cssValues[rand(@cssValues.length)]
when 11 then return ''
when 12 then return rand(255).chr * (rand(8192)+8)
when 13
length = rand(1024) + 8
return (rand(255).chr * length) + " " + (rand(255).chr * length) + " " + (rand(255).chr * length)
when 14 then return (rand(255).chr * (rand(1024)+3)) + "px"
when 15 then return (rand(255).chr * (rand(1024)+3)) + "em"
when 16 then return "url(" + inventValue() + ")"
when 17..18 then return "#" + rand(999999999).to_s
when 19 then return "-" + rand(99999999).to_s
else return rand(99999999).to_s;
end
end
def mangleTag(tag)
@mangledTagTotal += 1
out = ''
# 20% chance of closing a tag instead of opening it. This
# still counts against @mangledTagTotal, however.
if rand(10) > 8
out = "</" + tag + ">"
return out
end
# we're opening it.
out = "<" + tag
# forgot the space between the tag and the attributes
if rand(15) > 1
out << ' '
end
attrNum = rand(@htmlMaxAttrs) + 1
1.upto(attrNum) {
attr = @htmlAttr[rand(@htmlAttr.length)]
out << attr
# 7.5% of the time we skip the = sign. Don't prefix it
# if the attribute ends with a ( however.
if rand(15) > 1
out << '='
end
# sometimes quote it, sometimes not. I doubt the importance
# of this test, but mangleme-1.2 added it, and adding more
# random-ness never hurt anything but time. I'll do it less often.
quote = rand(2)
if (quote > 1)
out << "\""
end
out << inventValue()
# end the quote when you are done
if (quote > 1)
out << "\" "
end
# 5% chance we skip the space at the end of the name
if rand(20) > 1
out << ' '
end
}
# CSS styles!
if rand(4) > 1
out << " style=\""
1.upto(rand(@cssMaxProps)+1) {
out << @cssTags[rand(@cssTags.length)]
# very small chance we let the tag run on.
if rand(50) > 1
out << ": "
end
out << inventCssValue(tag)
# we almost always put the ; there.
if rand(50) > 1
out << '; '
end
}
out << "\""
end
out << ">\n"
# support our local troops!
if (@subtest_num > 0) && filterSubTest()
if tag =~ /html|body|head/
return '<' + tag + '>'
else
return "<x-#@mangledTagTotal>\n"
end
else
return out
end
end
#end
def filterSubTest()
result = 1
if (@mangledTagTotal >= @offset) && (@mangledTagTotal < (@offset + @lines))
result = nil
end
return result
end
def nextTestNum()
if random_mode
n = rand(99999999)
else
if @test_num
n = @test_num + 1
else
n = 1
end
end
return n
end
# If we are at line 30 with 8 extra lines, there is no point to try line 31
# with 8 lines as well.. skip back to 1 and bump up the line count.
def nextSubTestNum()
if (@offset + @lines) > @htmlMaxTags
nextNum = ((@lines * 2 -1)) * @htmlMaxTags
else
nextNum = @subtest_num + 1
end
return nextNum
end
def buildPage
if (! @test_num) || (@test_num < 1)
@test_num = 1
end
next_num=nextTestNum()
@lines = @subtest_num.div(@htmlMaxTags) + 1
@offset = @subtest_num.modulo(@htmlMaxTags)
# building the HTML
bodyText = mangleTag('html')
bodyText << "\n<head>\n"
# Only do redirects if lookup=1 has not been specified.
if (! @lookup_mode) && (@lines <= @htmlMaxTags) && (@stop_num != @test_num)
newpage = @url + "?"
if @subtest_num > 0
newpage << "test=" << @test_num.to_s << "&subtest=" << nextSubTestNum().to_s
else
newpage << "test=" << next_num.to_s
end
if @random_mode
newpage << "&random=1"
end
if @stop_num > 0
newpage << "&stop=" << @stop_num.to_s
end
bodyText << "\t<META HTTP-EQUIV=\"Refresh\" content=\"0;URL=#{newpage}\">\n"
# use both techniques, because you never know how you might be corrupting yourself.
bodyText << "\t<script language=\"javascript\">setTimeout('window.location=\"#{newpage}\"', 1000);</script>\n"
end
bodyText << "\t" << mangleTag('meta')
bodyText << "\t" << mangleTag('meta')
bodyText << "\t" << mangleTag('link')
bodyText << "\t<title>[#@test_num] iExploder #{$VERSION} - #{inventValue()}</title>\n"
bodyText << "</head>\n\n"
# What tags will we be messing with ######################
tagList = [ 'body']
# we already have 5 tags?
1.upto(@htmlMaxTags - 5 ) { tagList << @htmlTags[rand(@htmlTags.length)] }
tagList.each { |tag|
bodyText << mangleTag(tag)
bodyText << inventValue() + "\n"
}
bodyText << "</body>\n</html>"
end
end
if $0 == __FILE__
max=ARGV[0].to_i
puts "testing #{max} tags"
test = IExploder.new(max, 5, 5)
test.readTagFiles()
test.test_num=1
test.subtest_num=1
counter=0
test.lines=0
while test.lines < max
test.lines = test.subtest_num.div(max) + 1
test.offset = test.subtest_num.modulo(max)
test.subtest_num=test.nextSubTestNum
counter = counter + 1
puts "[#{counter}] subtest #{test.subtest_num} is #{test.lines} lines with #{test.offset} offset"
end
puts "for #{max} tests, you will have #{counter} iterations until #{test.subtest_num}"
end