initial import
[vuplus_webkit] / Tools / iExploder / iexploder-1.7.2 / src / iexploder.rb
1 # encoding: ASCII-8BIT
2
3 # iExploder - Generates bad HTML files to perform QA for web browsers.
4 #
5 # Copyright 2010 Thomas Stromberg - All Rights Reserved.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 #      http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18
19 require 'cgi'
20 require 'yaml'
21
22 require './scanner.rb'
23 require './version.rb'
24
25 # Used to speed up subtest generation
26 $TEST_CACHE = {}
27
28 # Media extensions to proper mime type map (not that we always listen'
29 $MIME_MAP = {
30   'bmp' => 'image/bmp',
31   'gif' => 'image/gif',
32   'jpg' => 'image/jpeg',
33   'png' => 'image/png',
34   'svg' => 'image/svg+xml',
35   'tiff' => 'image/tiff',
36   'xbm' => 'image/xbm',
37   'ico' => 'image/x-icon',
38   'jng' => 'image/x-jng',
39   'xpm' => 'image/x-portable-pixmap',
40   'ogg' => 'audio/ogg',
41   'snd' => 'audio/basic',
42   'wav' => 'audio/wav'
43 }
44
45 # These tags get src properties more often than others
46 $SRC_TAGS = ['img', 'audio', 'video', 'embed']
47
48 class IExploder
49   attr_accessor :test_num, :subtest_data, :lookup_mode, :random_mode, :cgi_url, :browser, :claimed_browser
50   attr_accessor :offset, :lines, :stop_num, :config
51
52   def initialize(config_path)
53     @config = YAML::load(File.open(config_path))
54     @stop_num = nil
55     @subtest_data = nil
56     @test_num = 0
57     @cgi_url = '/iexploder.cgi'
58     @browser = 'UNKNOWN'
59     @claimed_browser = nil
60     readTagFiles()
61     return nil
62   end
63
64   def setRandomSeed
65     if @test_num > 0
66       srand(@test_num)
67     else
68       srand
69     end
70   end
71
72
73   def readTagFiles
74     # These if statements are so that mod_ruby doesn't have to reload the files
75     # each time
76     data_path = @config['mangle_data_path']
77     @cssTags = readTagsDir("#{data_path}/css-properties")
78     @cssPseudoTags = readTagsDir("#{data_path}/css-pseudo")
79     @cssAtRules = readTagsDir("#{data_path}/css-atrules")
80     @htmlTags = readTagsDir("#{data_path}/html-tags")
81     @htmlAttr = readTagsDir("#{data_path}/html-attrs")
82     @htmlValues = readTagsDir("#{data_path}/html-values")
83     @cssValues = readTagsDir("#{data_path}/css-values")
84     @headerValues = readTagsDir("#{data_path}/headers")
85     @protocolValues = readTagsDir("#{data_path}/protocols")
86     @mimeTypes = readTagsDir("#{data_path}/mime-types")
87     @media = readMediaDir("#{data_path}/media")
88   end
89
90   def readTagsDir(directory)
91     values = []
92     Dir.foreach(directory) { |filename|
93       if File.file?(directory + "/" + filename)
94         values = values + readTagFile(directory + "/" + filename)
95       end
96     }
97     return values.uniq
98   end
99
100   def readMediaDir(directory)
101     data = {}
102     Dir.foreach(directory) { |filename|
103       if File.file?(directory + "/" + filename)
104        (base, extension) = filename.split('.')
105         mime_type = $MIME_MAP[extension]
106         data[mime_type] = File.read(directory + "/" + filename)
107       end
108     }
109     return data
110   end
111
112   def readTagFile(filename)
113     list = Array.new
114     File.new(filename).readlines.each { |line|
115       line.chop!
116
117       # Don't include comments.
118       if (line !~ /^# /) && (line.length > 0)
119         list << line
120       end
121     }
122     return list
123   end
124
125
126   def generateHtmlValue(tag)
127     choice = rand(100)
128     tag = tag.sub('EXCLUDED_', '')
129     if tag =~ /^on/ and choice < 90
130       return generateHtmlValue('') + "()"
131     elsif tag == 'src' or tag == 'data' or tag == 'profile' and choice < 90
132       return generateGarbageUrl(tag)
133     end
134
135     case choice
136       when 0..50 then
137         return @htmlValues[rand(@htmlValues.length)]
138       when 51..75
139         return generateGarbageNumber()
140       when 76..85
141         return generateGarbageValue()
142       when 86..90
143         return generateGarbageNumber() + ',' + generateGarbageNumber()
144       when 91..98
145         return generateGarbageUrl(tag)
146     else
147       return generateOverflow()
148     end
149   end
150
151   def generateMediaUrl(tag)
152     mime_type = @media.keys[rand(@media.keys.length)]
153     return generateTestUrl(@test_num, nil, nil, mime_type)
154   end
155
156   def generateGarbageUrl(tag)
157     choice = rand(100)
158     case choice
159       when 0..30
160       return generateMediaUrl(tag)
161       when 31..50
162       return @protocolValues[rand(@protocolValues.length)] + '%' + generateGarbageValue()
163       when 51..60
164       return @protocolValues[rand(@protocolValues.length)] + '//../' + generateGarbageValue()
165       when 60..75
166       return @protocolValues[rand(@protocolValues.length)] + '//' + generateGarbageValue()
167       when 75..85
168       return generateOverflow() + ":" + generateGarbageValue()
169       when 86..97
170       return generateGarbageValue() + ":" + generateOverflow()
171     else
172       return generateOverflow()
173     end
174   end
175
176   def generateCssValue(property)
177     size_types = ['', 'em', 'px', '%', 'pt', 'pc', 'ex', 'in', 'cm', 'mm']
178
179     choice = rand(100)
180     case choice
181       when 0..50 then
182       # return the most likely scenario
183       case property.sub('EXCLUDED_', '')
184         when /-image|content/
185           return 'url(' + generateGarbageUrl(property) + ')'
186         when /-width|-radius|-spacing|margin|padding|height/
187           return generateGarbageValue() + size_types[rand(size_types.length)]
188         when /-color/
189           return generateGarbageColor()
190         when /-delay|-duration/
191           return generateGarbageValue() + 'ms'
192       else
193         return @cssValues[rand(@cssValues.length)]
194       end
195       when 51..75 then return generateGarbageNumber()
196       when 76..85 then return 'url(' + generateGarbageUrl(property) + ')'
197       when 85..98 then return generateGarbageValue()
198     else
199       return generateOverflow()
200     end
201   end
202
203   def generateGarbageColor()
204     case rand(100)
205       when 0..50 then return '#' + generateGarbageValue()
206       when 51..70 then return 'rgb(' + generateGarbageNumber() + ',' + generateGarbageNumber() + ',' + generateGarbageNumber() + ')'
207       when 71..98 then return 'rgb(' + generateGarbageNumber() + '%,' + generateGarbageNumber() + '%,' + generateGarbageNumber() + '%)'
208     else
209       return generateOverflow()
210     end
211   end
212
213   def generateGarbageNumber()
214     choice = rand(100)
215     case choice
216       when 0 then return '0'
217       when 1..40 then return '9' * rand(100)
218       when 41..60 then return '999999.' + rand(999999999999999999999).to_s
219       when 61..80 then return '-' + ('9' * rand(100))
220       when 81..90 then return '-999999.' + rand(999999999999999999999).to_s
221       when 91..98 then return generateGarbageText()
222     else
223       return generateOverflow()
224     end
225   end
226
227   def generateGarbageValue()
228     case rand(100)
229       when 0..30 then return rand(255).chr * rand(@config['buffer_overflow_length'])
230       when 31..50 then return "%n" * 50
231       when 51..65 then return ("&#" + rand(999999).to_s + ";") * rand(@config['max_garbage_text_size'])
232       when 66..70 then
233       junk = []
234       0.upto(rand(20)+1) do
235         junk << "\\x" + rand(65535).to_s(16)
236       end
237       return junk.join('') * rand(@config['max_garbage_text_size'])
238       when 71..99 then
239       junk = []
240       chars = '%?!$#^0123456789ABCDEF%#./\&|;'
241       0.upto(rand(20)+1) do
242         junk << chars[rand(chars.length)].chr
243       end
244       return junk.join('') * rand(@config['max_garbage_text_size'])
245     end
246   end
247
248   def generateOverflow()
249     return rand(255).chr * (@config['buffer_overflow_length'] + rand(500))
250   end
251
252   def generateGarbageText
253     case rand(100)
254       when 0..70 then return 'X' * 129
255       when 71..75 then return "%n" * 15
256       when 76..85 then return ("&#" + rand(9999999999999).to_s + ";") * rand(@config['max_garbage_text_size'])
257       when 86..90 then return generateGarbageValue()
258       when 91..98 then return rand(255).chr * rand(@config['max_garbage_text_size'])
259     else
260       return generateOverflow()
261     end
262   end
263
264   def isPropertyInBlacklist(properties)
265     # Format: [img, src] or [img, style, property]
266     blacklist_entries = []
267     if @config.has_key?('exclude') and @config['exclude']
268       blacklist_entries << properties.join('.')
269       wildcard_property = properties.dup
270       wildcard_property[0] = '*'
271       blacklist_entries << wildcard_property.join('.')
272       blacklist_entries.each do |entry|
273         if @config['exclude'].has_key?(entry) and @browser =~ /#{@config['exclude'][entry]}/
274           return true
275         end
276       end
277     end
278     return false
279   end
280
281   def generateCssStyling(tag)
282     out = ' style="'
283     0.upto(rand(@config['properties_per_style_max'])) {
284       property = @cssTags[rand(@cssTags.length)]
285       if isPropertyInBlacklist([tag, 'style', property])
286         property = "EXCLUDED_#{property}"
287       end
288       out << property
289
290       # very small chance we let the tag run on.
291       if rand(65) > 1
292         out << ": "
293       end
294
295       values = []
296       0.upto(rand(@config['attributes_per_style_property_max'])) {
297         values << generateCssValue(property)
298       }
299       out << values.join(' ')
300       # we almost always put the ; there.
301       if rand(65) > 1
302         out << ";\n    "
303       end
304     }
305     out << "\""
306     return out
307   end
308
309   def mangleTag(tag, no_close_chance=false)
310     if not no_close_chance and rand(100) < 15
311       return "</" + tag + ">"
312     end
313     out = "<" + tag
314     if rand(100) > 1
315       out << ' '
316     else
317       out << generateOverflow()
318     end
319
320     attrNum = rand(@config['attributes_per_html_tag_max']) + 1
321     attrs = []
322     # The HTML head tag does not have many useful attributes, but is always included in tests.
323     if tag == 'head' and rand(100) < 75
324       case rand(3)
325         when 0 then attrs << 'lang'
326         when 1 then attrs << 'dir'
327         when 2 then attrs << 'profile'
328       end
329     end
330     # 75% of the time, these tags get a src attribute
331     if $SRC_TAGS.include?(tag) and rand(100) < 75
332       if @config.has_key?('exclude') and @config['exclude'] and @config['exclude'].has_key?("#{tag}.src")
333         attrs << 'EXCLUDED_src'
334       else
335         attrs << 'src'
336       end
337     end
338
339     while attrs.length < attrNum
340       attribute = @htmlAttr[rand(@htmlAttr.length)]
341       if isPropertyInBlacklist([tag, attribute])
342         attribute = "EXCLUDED_#{attribute}"
343       end
344       attrs << attribute
345     end
346
347     # Add a few HTML attributes
348     for attr in attrs
349       out << attr
350       if rand(100) > 1
351         out << '='
352       end
353       if (rand(100) >= 50)
354         quoted = 1
355         out << "\""
356       else
357         quoted = nil
358       end
359       out << generateHtmlValue(attr)
360       if quoted
361         if rand(100) >= 10
362           out << "\""
363         end
364       end
365       if rand(100) >= 1
366         out << "\n  "
367       end
368     end
369
370     if rand(100) >= 25
371       out << generateCssStyling(tag)
372     end
373     out << ">\n"
374     return out
375   end
376
377   def nextTestNum()
378     if @subtest_data
379       return @test_num
380     elsif @random_mode
381       return rand(99999999999)
382     else
383       return @test_num  + 1
384     end
385   end
386
387   def generateCssPattern()
388     # Generate a CSS selector pattern.
389     choice = rand(100)
390     pattern = ''
391     case choice
392       when 0..84 then pattern = @htmlTags[rand(@htmlTags.length)].dup
393       when 85..89 then pattern = "*"
394       when 90..94 then pattern = @cssAtRules[rand(@cssAtRules.length)].dup
395       when 95..100 then pattern = ''
396     end
397
398     if rand(100) < 25
399       pattern << " " + @htmlTags[rand(@htmlTags.length)]
400     end
401
402     if rand(100) < 25
403       pattern << " > " + @htmlTags[rand(@htmlTags.length)]
404     end
405
406     if rand(100) < 25
407       pattern << " + " + @htmlTags[rand(@htmlTags.length)]
408     end
409
410     if rand(100) < 10
411       pattern << "*"
412     end
413
414
415     if rand(100) < 25
416       pseudo = @cssPseudoTags[rand(@cssPseudoTags.length)].dup
417       # These tags typically have a parenthesis
418       if (pseudo =~ /^lang|^nth|^not/ and rand(100) < 75 and pseudo !~ /\(/) or rand(100) < 20
419         pseudo << '('
420       end
421
422       if pseudo =~ /\(/
423         if rand(100) < 75
424           pseudo << generateGarbageValue()
425         end
426         if rand(100) < 75
427           pseudo << ')'
428         end
429       end
430       pattern << ":" + pseudo
431     end
432
433     if rand(100) < 20
434       html_attr = @htmlAttr[rand(@htmlAttr.length)]
435       match = '[' + html_attr
436       choice = rand(100)
437       garbage = generateGarbageValue()
438       case choice
439         when 0..25 then match << ']'
440         when 26..50 then match << "=\"#{garbage}\"]"
441         when 51..75 then match << "=~\"#{garbage}\"]"
442         when 76..99 then match << "|=\"#{garbage}\"]"
443       end
444       pattern << match
445     end
446
447     if rand(100) < 20
448       if rand(100) < 50
449         pattern << '.' + generateGarbageValue()
450       else
451         pattern << '.*'
452       end
453     end
454
455     if rand(100) < 20
456       pattern << '#' + generateGarbageValue()
457     end
458
459     if rand(100) < 5
460       pattern << ' #' + generateGarbageValue()
461     end
462
463     return pattern
464   end
465
466   def buildStyleTag()
467     out = "\n"
468     0.upto(rand(@config['properties_per_style_max'])) {
469       out << generateCssPattern()
470       if rand(100) < 90
471         out << " {\n"
472       end
473
474       0.upto(rand(@config['properties_per_style_max'])) {
475         property = @cssTags[rand(@cssTags.length)].dup
476         if isPropertyInBlacklist(['style', 'style', property])
477           property = "  EXCLUDED_#{property}"
478         end
479         out << "  #{property}: "
480
481         values = []
482         0.upto(rand(@config['attributes_per_style_property_max'])) {
483           values << generateCssValue(property)
484         }
485         out << values.join(' ')
486         if rand(100) < 95
487           out << ";\n"
488         end
489       }
490       if rand(100) < 90
491         out << "\n}\n"
492       end
493
494     }
495     return out
496   end
497
498
499   # Build any malicious javascript here. Fairly naive at the moment.
500   def buildJavaScript
501     target = @htmlTags[rand(@htmlTags.length)]
502     css_property = @cssTags[rand(@cssTags.length)]
503     css_property2 = @cssTags[rand(@cssTags.length)]
504     html_attr = @htmlAttr[rand(@htmlAttr.length)]
505     css_value = generateCssValue(css_property)
506     html_value = generateHtmlValue(html_attr)
507     html_value2 = generateGarbageNumber()
508     mangled = mangleTag(@htmlTags[rand(@htmlTags.length)]);
509     mangled2 = mangleTag(@htmlTags[rand(@htmlTags.length)]);
510
511     js = []
512     js << "window.onload=function(){"
513     js << "  var ietarget = document.createElement('#{target}');"
514     js << "  ietarget.style.#{css_property} = '#{css_value}';"
515     js << "  ietarget.#{html_attr} = '#{html_value}';"
516     js << "  document.body.appendChild(ietarget);"
517     js << "  ietarget.style.#{css_property2} = #{html_value2};"
518
519     js << "  document.write('#{mangled}');"
520     js << "  document.write('#{mangled2}');"
521     js << "}"
522     return js.join("\n")
523   end
524
525   def buildMediaFile(mime_type)
526     if @media.has_key?(mime_type)
527       data = @media[mime_type].dup
528     else
529       puts "No media found for #{mime_type}"
530       data = generateGarbageText()
531     end
532
533     # corrupt it in a subtle way
534     choice = rand(100)
535     if choice > 50
536       garbage = generateGarbageValue()
537     else
538       garbage = rand(255).chr * rand(8)
539     end
540
541     if "1.9".respond_to?(:encoding)
542       garbage.force_encoding('ASCII-8BIT')
543       data.force_encoding('ASCII-8BIT')
544     end
545
546     garbage_start = rand(data.length)
547     garbage_end = garbage_start + garbage.length
548     data[garbage_start..garbage_end] = garbage
549     if rand(100) < 15
550       data << generateGarbageValue()
551     end
552     return data
553   end
554
555   # Parse the subtest data passed in as part of the URL
556   def parseSubTestData(subtest_data)
557     # Initialize with one line at 0
558     if not subtest_data or subtest_data.to_i == 0
559       return [@config['initial_subtest_width'], [0]]
560     end
561      (lines_at_time, offsets_string) = subtest_data.split('_')
562     offsets = offsets_string.split(',').map! {|x| x.to_i }
563     return [lines_at_time.to_i, offsets]
564   end
565
566   def generateTestUrl(test_num, subtest_width=nil, subtest_offsets=nil, mime_type=nil)
567     url = @cgi_url + '?'
568     if subtest_width
569       if subtest_offsets.length > @config['subtest_combinations_max']
570         url << "t=" << test_num.to_s << "&l=test_redirect&z=THE_END"
571       else
572         url << "t=" << test_num.to_s << "&s=" << subtest_width.to_s << "_" << subtest_offsets.join(',')
573       end
574     else
575       url << "t=" << test_num.to_s
576     end
577
578     if @random_mode
579       url << "&r=1"
580     elsif @stop_num
581       url << "&x=" << @stop_num.to_s
582     end
583
584     if mime_type
585       url << '&m=' + CGI::escape(mime_type)
586     end
587
588     url << "&b=" << CGI::escape(@browser)
589     return url
590   end
591
592   def buildBodyTags(tag_count)
593     tagList = ['body']
594     # subtract the <body> tag from tag_count.
595     1.upto(tag_count-1) { tagList << @htmlTags[rand(@htmlTags.length)] }
596
597     # Lean ourselves toward lots of img and src tests
598     for tag, percent in @config['favor_html_tags']
599       if rand(100) < percent.to_f
600         # Don't overwrite the body tag.
601         tagList[rand(tagList.length-1)+1] = tag
602       end
603     end
604
605     # Now we have our hitlist of tags,lets mangle them.
606     mangled_tags = []
607     tagList.each do |tag|
608       tag_data = mangleTag(tag)
609       if tag == 'script'
610         if rand(100) < 40
611           tag_data = "<script>"
612         end
613         tag_data << buildJavaScript() + "\n" + "</script>\n"
614       elsif tag == 'style'
615         if rand(100) < 40
616           tag_data = "<style>"
617         end
618         tag_data << buildStyleTag() + "\n" + "</style>\n"
619       elsif rand(100) <= 90
620         tag_data << generateGarbageText() << "\n"
621       else
622         tag_data << "\n"
623       end
624
625       if rand(100) <= 33
626         tag_data << "</#{tag}>\n"
627       end
628       mangled_tags << "\n<!-- START #{tag} -->\n" + tag_data + "\n<!-- END #{tag} -->\n"
629     end
630     return mangled_tags
631   end
632
633   def buildHeaderTags(tag_count)
634     valid_head_tags = ['title', 'base', 'link', 'meta']
635     header_tags = ['html', 'head']
636     1.upto(tag_count-1) { header_tags << valid_head_tags[rand(valid_head_tags.length)] }
637     header_tags << @htmlTags[rand(@htmlTags.length)]
638     mangled_tags = []
639     header_tags.each do |tag|
640       mangled_tags << mangleTag(tag, no_close_chance=true)
641     end
642     return mangled_tags
643   end
644
645   def buildSurvivedPage(page_type)
646     page = "<html><head>"
647     page << "<body>Bummer. You survived both redirects. Let me go sulk in the corner.</body>"
648     page << "</html>"
649     return page
650   end
651
652   def buildRedirect(test_num, subtest_data, lookup_mode, stop_num=nil)
653     # no more redirects.
654     if lookup_mode == '1' or stop_num == test_num
655       return ''
656     end
657
658     if subtest_data
659       width, offsets = parseSubTestData(@subtest_data)
660     else
661       width, offsets = nil
662     end
663
664     # We still need a redirect, but don't bother generating new data.
665     if lookup_mode
666       redirect_url = generateTestUrl(test_num, width, offsets)
667       if lookup_mode == 'test_redirect'
668         redirect_url << "&l=test_another_redirect"
669       elsif lookup_mode == 'test_another_redirect'
670         redirect_url << "&l=survived_redirect"
671       else
672         redirect_url << "&l=#{lookup_mode}"
673       end
674     else
675       # This is a normal redirect going on to the next page. If we have subtest, get the next one.
676       if subtest_data
677         width, offsets = combine_combo_creator(@config['html_tags_per_page'], width, offsets)[0..1]
678       end
679       redirect_url = generateTestUrl(nextTestNum(), width, offsets)
680     end
681
682     redirect_code = "\t<META HTTP-EQUIV=\"Refresh\" content=\"0;URL=#{redirect_url}\">\n"
683     # use both techniques, because you never know how you might be corrupting yourself.
684     redirect_code << "\t<script language=\"javascript\">setTimeout('window.location=\"#{redirect_url}\"', 1000);</script>\n"
685     return redirect_code
686   end
687
688   def buildPage()
689     if @lookup_mode == 'survived_redirect'
690       return self.buildSurvivedPage(@lookup_mode)
691     end
692     tag_count = @config['html_tags_per_page']
693
694     if $TEST_CACHE.has_key?(@test_num)
695      (header_tags, body_tags) = $TEST_CACHE[@test_num]
696     else
697       header_tags = buildHeaderTags(3)
698       body_tags = buildBodyTags(tag_count - header_tags.length)
699     end
700     required_tags = {
701       0 => 'html',
702       1 => 'head',
703       header_tags.length => 'body'
704     }
705
706     if @subtest_data and @subtest_data.length > 0
707       if not $TEST_CACHE.has_key?(@test_num)
708         $TEST_CACHE[@test_num] = [header_tags, body_tags]
709       end
710       (width, offsets) = parseSubTestData(@subtest_data)
711       lines = combine_combo_creator(tag_count, width, offsets)[2]
712       all_tags = header_tags + body_tags
713       body_start = header_tags.length
714       header_tags = []
715       body_tags = []
716       # <html> and <body> are required, regardless of their existence in the subtest data.
717       0.upto(tag_count) do |line_number|
718         tag_data = nil
719         if lines.include?(line_number)
720           tag_data = all_tags[line_number]
721         elsif required_tags.key?(line_number)
722           tag_data = "<" + required_tags[line_number] + ">"
723         end
724         if tag_data
725           if line_number < body_start
726             header_tags << tag_data
727           else
728             body_tags << tag_data
729           end
730         end
731       end
732       header_tags << "<!-- subtest mode: #{offsets.length} combinations, width: #{width} -->"
733     end
734
735     htmlText = header_tags[0..1].join("\n\t")
736     htmlText << buildRedirect(@test_num, @subtest_data, @lookup_mode, @stop_num)
737     htmlText << "<title>[#{@test_num}:#{@subtest_data}] iExploder #{$VERSION} - #{generateGarbageText()}</title>\n"
738     if @claimed_browser and @claimed_browser.length > 1
739       show_browser = @claimed_browser
740     else
741       show_browser = @browser
742     end
743     htmlText << "\n<!-- iExploder #{$VERSION} | test #{@test_num}:#{@subtest_data} at #{Time.now} -->\n"
744     htmlText << "<!-- browser: #{show_browser} -->\n"
745     htmlText << header_tags[2..-1].join("\n\t")
746     htmlText << "\n</head>\n\n"
747     htmlText << body_tags.join("\n")
748     htmlText << "</body>\n</html>"
749     return htmlText
750   end
751
752   def buildHeaders(mime_type)
753     use_headers = []
754     banned_headers = []
755     response = {'Content-Type' => mime_type}
756     0.upto(rand(@config['headers_per_page_max'])) do
757       try_header = @headerValues[rand(@headerValues.length)]
758       if ! banned_headers.include?(try_header.downcase)
759         use_headers << try_header
760       end
761     end
762     for header in use_headers.uniq
763       if rand(100) > 75
764         response[header] = generateGarbageNumber()
765       else
766         response[header] = generateGarbageUrl(header)
767       end
768     end
769     return response
770   end
771 end
772
773
774 # for testing
775 if $0 == __FILE__
776   ie = IExploder.new('config.yaml')
777   ie.test_num = ARGV[0].to_i || 1
778   ie.subtest_data = ARGV[1] || nil
779   mime_type = ARGV[2] || nil
780   ie.setRandomSeed()
781   if not mime_type
782     html_output = ie.buildPage()
783     puts html_output
784   else
785     headers = ie.buildHeaders(mime_type)
786     for (key, value) in headers
787       puts "#{key}: #{value}"
788     end
789     puts "Mime-Type: #{mime_type}"
790     puts ie.buildMediaFile(mime_type)
791   end
792 end