miketracy-wwmd 0.2.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +3 -0
  2. data/README +62 -0
  3. data/README.txt +62 -0
  4. data/Rakefile +34 -0
  5. data/examples/config_example.yaml +24 -0
  6. data/examples/wwmd_example.rb +73 -0
  7. data/lib/wwmd.rb +78 -0
  8. data/lib/wwmd/encoding.rb +40 -0
  9. data/lib/wwmd/form.rb +110 -0
  10. data/lib/wwmd/form_array.rb +273 -0
  11. data/lib/wwmd/guid.rb +155 -0
  12. data/lib/wwmd/hpricot_html2text.rb +76 -0
  13. data/lib/wwmd/mixins.rb +318 -0
  14. data/lib/wwmd/mixins_extends.rb +188 -0
  15. data/lib/wwmd/mixins_external.rb +18 -0
  16. data/lib/wwmd/nokogiri_html2text.rb +41 -0
  17. data/lib/wwmd/page.rb +414 -0
  18. data/lib/wwmd/page/auth.rb +183 -0
  19. data/lib/wwmd/page/config.rb +44 -0
  20. data/lib/wwmd/page/constants.rb +60 -0
  21. data/lib/wwmd/page/headers.rb +107 -0
  22. data/lib/wwmd/page/inputs.rb +47 -0
  23. data/lib/wwmd/page/irb_helpers.rb +90 -0
  24. data/lib/wwmd/page/scrape.rb +202 -0
  25. data/lib/wwmd/page/spider.rb +127 -0
  26. data/lib/wwmd/page/urlparse.rb +79 -0
  27. data/lib/wwmd/page/utils.rb +30 -0
  28. data/lib/wwmd/viewstate.rb +118 -0
  29. data/lib/wwmd/viewstate/viewstate_class_helpers.rb +35 -0
  30. data/lib/wwmd/viewstate/viewstate_deserializer_methods.rb +213 -0
  31. data/lib/wwmd/viewstate/viewstate_from_xml.rb +126 -0
  32. data/lib/wwmd/viewstate/viewstate_types.rb +51 -0
  33. data/lib/wwmd/viewstate/viewstate_utils.rb +157 -0
  34. data/lib/wwmd/viewstate/viewstate_yaml.rb +25 -0
  35. data/lib/wwmd/viewstate/vs_array.rb +36 -0
  36. data/lib/wwmd/viewstate/vs_binary_serialized.rb +28 -0
  37. data/lib/wwmd/viewstate/vs_hashtable.rb +40 -0
  38. data/lib/wwmd/viewstate/vs_hybrid_dict.rb +40 -0
  39. data/lib/wwmd/viewstate/vs_indexed_string.rb +6 -0
  40. data/lib/wwmd/viewstate/vs_indexed_string_ref.rb +22 -0
  41. data/lib/wwmd/viewstate/vs_int_enum.rb +25 -0
  42. data/lib/wwmd/viewstate/vs_list.rb +32 -0
  43. data/lib/wwmd/viewstate/vs_pair.rb +27 -0
  44. data/lib/wwmd/viewstate/vs_read_types.rb +11 -0
  45. data/lib/wwmd/viewstate/vs_read_value.rb +33 -0
  46. data/lib/wwmd/viewstate/vs_sparse_array.rb +56 -0
  47. data/lib/wwmd/viewstate/vs_string.rb +29 -0
  48. data/lib/wwmd/viewstate/vs_string_array.rb +37 -0
  49. data/lib/wwmd/viewstate/vs_string_formatted.rb +30 -0
  50. data/lib/wwmd/viewstate/vs_triplet.rb +29 -0
  51. data/lib/wwmd/viewstate/vs_type.rb +21 -0
  52. data/lib/wwmd/viewstate/vs_unit.rb +28 -0
  53. data/lib/wwmd/viewstate/vs_value.rb +33 -0
  54. data/spec/README +3 -0
  55. data/spec/form_array.spec +49 -0
  56. data/spec/spider_csrf_test.spec +28 -0
  57. data/spec/urlparse_test.spec +89 -0
  58. data/tasks/ann.rake +80 -0
  59. data/tasks/bones.rake +20 -0
  60. data/tasks/gem.rake +201 -0
  61. data/tasks/git.rake +40 -0
  62. data/tasks/notes.rake +27 -0
  63. data/tasks/post_load.rake +34 -0
  64. data/tasks/rdoc.rake +51 -0
  65. data/tasks/rubyforge.rake +55 -0
  66. data/tasks/setup.rb +292 -0
  67. data/tasks/spec.rake +54 -0
  68. data/tasks/test.rake +40 -0
  69. data/tasks/zentest.rake +36 -0
  70. metadata +164 -0
@@ -0,0 +1,183 @@
1
+ =begin rdoc
2
+ This is where we do all the undocumented auth stuff. NTLM is here and
3
+ hooked in.
4
+
5
+ WWMDNTLM is an incredibly naive NTLM implementation (used to get
6
+ around NTLM for one project ahwile back
7
+ =end
8
+
9
+ module WWMD
10
+ class Page
11
+
12
+ #:stopdoc:
13
+
14
+ #:section: Authentication helpers
15
+
16
+ # check if this request requires NTLM
17
+ def ntlm?
18
+ return false if self.code != 401
19
+ count = 0
20
+ self.header_data.each do |i|
21
+ if i[0] =~ /www-authenticate/i
22
+ count += 1 if (i[1] == "Negotiate" || i[1] == "NTLM")
23
+ end
24
+ end
25
+ return (count > 0)
26
+ end
27
+
28
+ # does this request have an authenticate header?
29
+ def auth?
30
+ return false if self.code != 401
31
+ count = 0
32
+ self.header_data.each do |i|
33
+ if i[0] =~ /www-authenticate/i
34
+ count += 1
35
+ end
36
+ end
37
+ return (count > 0)
38
+ end
39
+
40
+ # not sure why this is here
41
+ def ntlm_perform(exp=nil)#:nodoc:
42
+ self.perform
43
+ return (self.code == exp)
44
+ end
45
+
46
+ # perform a get usig NTLM
47
+ def ntlm_get(url=nil?,debug=false)
48
+ self.clear_header('Authorization')
49
+ nobj = WWMDNTLM.new(self.opts)
50
+ self.url = @urlparse.parse(self.opts[:base_url],url) if not url.nil?
51
+ self.perform
52
+ return "This request does not appear to require NTLM" if not self.ntlm?
53
+ self.headers['Authorization'] = nobj.type_1_msg
54
+ self.perform
55
+ type2 = self.header_data.get_value('WWW-Authenticate')
56
+ nonce = nobj.get_nonce(type2)
57
+ type3 = nobj.type_3_msg(nonce)
58
+ self.headers['Authorization'] = type3
59
+ self.perform
60
+ self.clear_header('Authorization')
61
+ return self.code
62
+ end
63
+
64
+ #:startdoc:
65
+
66
+ end
67
+
68
+ class WWMDNTLM#:nodoc:
69
+ attr_accessor :hostname
70
+ attr_accessor :domain
71
+ attr_accessor :username
72
+ attr_accessor :password
73
+ attr_accessor :opts
74
+ attr_accessor :negotiate_flags
75
+ attr_accessor :debug
76
+
77
+ def initialize(opts,debug=false)
78
+ @opts = opts
79
+ @hostname = self.opts[:hostname]
80
+ @domain = self.opts[:domain]
81
+ @username = self.opts[:username]
82
+ @password = self.opts[:password]
83
+ @hostname = "LOCALHOST" if self.hostname.nil?
84
+ @negotiate_flags = 0x00002201.to_l32
85
+ @debug = debug
86
+ end
87
+
88
+ def type_1_msg
89
+ # do not add domain for now here as it doesn't seem to be needed
90
+ # it does need to be set for type3 messages
91
+ host_len = self.hostname.size
92
+ host_off = 0x20
93
+ # if self.domain.nil?
94
+ if true
95
+ dom_off = 0
96
+ dom_len = 0
97
+ else
98
+ dom_off = (host_off + hostname.size)
99
+ dom_len = self.domain.size
100
+ end
101
+ msg = ""
102
+ msg << "NTLMSSP\x00" # signature[8]
103
+ msg << 0x01.to_l32 # type[4]
104
+ msg << self.negotiate_flags # NegotiateFlags[4]
105
+ msg << dom_len.to_l16 # domain string length[2]
106
+ msg << dom_len.to_l16 # domain string length[2]
107
+ msg << dom_off.to_l32 # domain string offset[4]
108
+ msg << host_len.to_l16 # host string length[2]
109
+ msg << host_len.to_l16 # host string length[2]
110
+ msg << host_off.to_l32 # host string offset[4]
111
+ # msg << self.domain if not self.domain.nil? # domain[var]
112
+ msg << self.hostname # host name[var]
113
+ return "NTLM " + msg.b64e
114
+ end
115
+
116
+ def get_nonce(t2msg)
117
+ # Signature[8]
118
+ # MessageType[4]
119
+ # TargetNameFields[8]
120
+ # NegotiateFlags[4]
121
+ # ServerChallenge[8]
122
+ # Reserved[8] ! 0x00
123
+ # TargetInfoFields[8]
124
+ # Version[8]
125
+ # Payload[var]
126
+ msg = t2msg.split[1].b64d
127
+ return msg[24..31]
128
+ end
129
+
130
+ def type_3_msg(nonce)
131
+ hlen = 0x40
132
+ poff = hlen
133
+ domain = self.domain.to_utf16
134
+ username = self.username.to_utf16
135
+ hostname = self.hostname.to_utf16
136
+ lmresp = NTLM.lm_response(self.opts[:password],nonce,:lmhash)
137
+ ntresp = NTLM.lm_response(self.opts[:password],nonce,:nthash)
138
+ msg = ""
139
+ msg << "NTLMSSP\x00" # Signature[8]
140
+ msg << 0x03.to_l32 # MessageType[4]
141
+ # LmChallengeResonseFields[8]
142
+ msg << lmresp.size.to_l16 # LmChallengeResponseLen[2]
143
+ msg << lmresp.size.to_l16 # LmChallengeResponseMaxLen[2]
144
+ msg << poff.to_l32 # LmChallengeResponseBufferOffset[4]
145
+ poff += lmresp.size
146
+ # msg << 0x40.to_l32 # LmChallengeResponseBufferOffset[4]
147
+ # NtChallengeResponseFields[8]
148
+ msg << ntresp.size.to_l16 # NtChallengeResponseLen[2]
149
+ msg << ntresp.size.to_l16 # NtChallengeResponseMaxLen[2]
150
+ # msg << 0x58.to_l32 # NtChallengeResponseBufferOffset[4]
151
+ msg << poff.to_l32 # NtChallengeResponseBufferOffset[4]
152
+ poff += ntresp.size
153
+ # DomainNameFields[8]
154
+ msg << domain.size.to_l16 # DomainNameLen[2]
155
+ msg << domain.size.to_l16 # DomainNameMaxLen[2]
156
+ msg << poff.to_l32 # DomainNameBufferOffset[4]
157
+ poff += domain.size
158
+ # UserNameFields[8]
159
+ msg << username.size.to_l16 # UserNameLen[2]
160
+ msg << username.size.to_l16 # UserNameMaxLen[2]
161
+ msg << poff.to_l32 # UserNameBufferOffset[4]
162
+ poff += username.size
163
+ # WorkstationFields[8]
164
+ msg << hostname.size.to_l16 # WorkstationLen[2]
165
+ msg << hostname.size.to_l16 # WorkstationMaxLen[2]
166
+ msg << poff.to_l32 # WorkstationBufferOffset[4]
167
+ poff += hostname.size
168
+ # EncryptedRandomSessionKeyFields[8]
169
+ msg << 0x00.to_l16 # EncryptedRandomSessionKeyLen[2]
170
+ msg << 0x00.to_l16 # EncryptedRandomSessionKeyMaxLen[2]
171
+ msg << 0x00.to_l32 # EncryptedRandomSessionKeyBufferOffset[4]
172
+ msg << self.negotiate_flags # NegotiateFlags[4]
173
+ # Version[8] (optional do not add)
174
+ # Payload[var]
175
+ msg << lmresp # LmChallenge[var]
176
+ msg << ntresp # NtChallenge[var]
177
+ msg << domain # DomainName[var]
178
+ msg << username # UserName[var]
179
+ msg << hostname # Workstation[var]
180
+ return "NTLM " + msg.b64e
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,44 @@
1
+ module WWMD
2
+ class WWMDConfig
3
+
4
+ def self.load_config(file)
5
+ begin
6
+ config = YAML.load_file(file)
7
+ rescue => e
8
+ putw "config file not found #{file}"
9
+ putw e.inspect
10
+ exit
11
+ end
12
+ return config
13
+ end
14
+
15
+ def self.parse_opts(args)
16
+ inopts = Hash.new
17
+ opts = OptionParser.new do |opts|
18
+ # set defaults
19
+ opts.on("-p", "--password PASSWORD", "Password") { |v| inopts[:password] = v }
20
+ opts.on("-u", "--username USERNAME", "Username") { |v| inopts[:username] = v }
21
+ opts.on("--header_file HEADER_FILE","Header file") { |v| inopts[:header_file] = v }
22
+ opts.on("--base_url BASE_URL","Base url") { |v| inopts[:base_url] = v }
23
+ opts.on("--use_proxy PROXY_URL", "Use proxy at url") do |v|
24
+ ENV['HTTP_PROXY'] = "http://" + v.to_s
25
+ inopts[:use_proxy] = true
26
+ inopts[:proxy_url] = v
27
+ end
28
+ opts.on("--no_proxy","do not use proxy") do |v|
29
+ inopts[:use_proxy] = false
30
+ inopts[:proxy_url] = nil
31
+ end
32
+ opts.on("--use_auth","login before getting url") { |v| inopts[:use_auth] = true }
33
+ opts.on("--no_auth","no login before getting url") { |v| inopts[:use_auth] = false }
34
+ opts.on("--debug","debugging really doesn't work") { |v| inopts[:debug] = true }
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ putx opts
37
+ exit
38
+ end
39
+ end
40
+ opts.parse!(args)
41
+ return inopts
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ module WWMD
2
+ XSSFISH = "<;'\"}()[]>{"
3
+
4
+ DEFAULTS = {
5
+ :base_url => "",
6
+ :use_auth => true,
7
+ :enable_cookies => true,
8
+ :cookiejar => "./__cookiejar",
9
+ :follow_location => true,
10
+ :max_redirects => 20,
11
+ :use_proxy => false,
12
+ :debug => false,
13
+ :scrape_warn => true,
14
+ :parse => true,
15
+ :timeout => 20,
16
+ }
17
+
18
+ ESCAPE = {
19
+ :url => /[^a-zA-Z0-9\-_%]/,
20
+ :nalnum => /[^a-zA-Z0-9]/,
21
+ :xss => /[^a-zA-Z0-9=?&()']/,
22
+ :ltgt => /[<>]/,
23
+ :all => /.*/,
24
+ :b64 => /[=+\/]/,
25
+ :none => :none,
26
+ :default => :default,
27
+ }
28
+
29
+ UA = {
30
+ :mozilla => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.16) Gecko/20080702 Firefox/2.0.0.16",
31
+ :moz3 => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.0.1) Gecko/2008070206 Firefox/3.0.1",
32
+ :ie6 => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)",
33
+ :ie7 => "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)",
34
+ :ie8 => "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)",
35
+ :opera => "Opera/9.20 (Windows NT 6.0; U; en)",
36
+ :safari => "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_4_11; en) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.22"
37
+ }
38
+
39
+ DEFAULT_HEADERS = {
40
+ "User-Agent" => UA[:moz3],
41
+ "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
42
+ "Accept-Language" => "en-US,en;q=0.8,en-au;q=0.6,en-us;q=0.4,en;q=0.2",
43
+ "Accept-Encoding" => "gzip,deflate",
44
+ "Accept-Charset" => "SO-8859-1,utf-8;q=0.7,*;q=0.7",
45
+ "Keep-Alive" => "300",
46
+ "Connection" => "keep-alive",
47
+ }
48
+
49
+ HEADERS = {
50
+ :default => nil,
51
+ :utf7 => {
52
+ "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-7",
53
+ "Content-Transfer-Encoding" => "7bit",
54
+ },
55
+ :ajax => {
56
+ "X-Requested-With" => "XMLHttpRequest",
57
+ "X-Prototype-Version" => "1.5.0",
58
+ },
59
+ }
60
+ end
@@ -0,0 +1,107 @@
1
+ module WWMD
2
+ class Page
3
+
4
+ #:section: Header helper methods
5
+
6
+ # clear header at <key>
7
+ def clear_header(key)
8
+ self.headers.delete_if { |k,v| k.upcase == key.upcase }
9
+ return nil
10
+ end
11
+
12
+ alias_method :delete_header, :clear_header#:nodoc:
13
+
14
+ # clear all headers
15
+ def clear_headers
16
+ self.headers.delete_if { |k,v| true }
17
+ "headers cleared"
18
+ end
19
+
20
+ # set headers from passed argument
21
+ # Nil: set headers from WWMD::DEFAULT_HEADERS
22
+ # Symbol: entry in WWMD::HEADERS to set from
23
+ # Hash: hash to set headers from
24
+ # String: filename (NOT IMPLEMENTED)
25
+ #
26
+ # if clear == true then headers will be cleared before setting
27
+ def set_headers(arg=nil,clear=false)
28
+ self.clear_headers if clear
29
+ if arg.nil?
30
+ begin
31
+ self.clear_headers
32
+ WWMD::DEFAULT_HEADERS.each { |k,v| self.headers[k] = v }
33
+ return "headers set from default"
34
+ rescue => e
35
+ puts e
36
+ return "error setting headers"
37
+ end
38
+ elsif arg.class == Symbol
39
+ self.set_headers(WWMD::HEADERS[arg])
40
+ return "headers set from #{arg}"
41
+ elsif arg.class == Hash
42
+ arg.each { |k,v| self.headers[k] = v }
43
+ return "headers set from hash"
44
+ end
45
+ "error setting headers"
46
+ end
47
+
48
+ # set headers back to default headers
49
+ def default_headers(arg=nil)
50
+ self.set_headers
51
+ end
52
+
53
+ alias_method :set_default, :default_headers
54
+
55
+ # set headers from text
56
+ def headers_from_array(arr)
57
+ self.clear_headers
58
+ arr.each do |line|
59
+ h = line.split(":",2)
60
+ next if h[1].class != String
61
+ self.headers[h[0]] = h[1].strip
62
+ end
63
+ end
64
+
65
+ # set headers from file
66
+ def headers_from_file(fn)
67
+ self.clear_headers
68
+ File.read(fn).each do |line|
69
+ h = line.split(":",2)
70
+ next if h[1].class != String
71
+ self.headers[h[0]] = h[1].strip
72
+ end
73
+ return nil
74
+ end
75
+
76
+ # set headers to utf7 encoding post
77
+ def set_utf7_headers
78
+ self.headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-7"
79
+ "headers set to utf7"
80
+ end
81
+
82
+ # set headers to ajax
83
+ def set_ajax_headers
84
+ self.headers["X-Requested-With"] = "XMLHttpRequest"
85
+ self.headers["X-Prototype-Version"] = "1.5.0"
86
+ return "headers set to ajax"
87
+ end
88
+
89
+ # set headers to SOAP request headers
90
+ def set_soap_headers
91
+ self.headers['Content-Type'] = "text/xml;charset=utf-8"
92
+ self.headers['SOAPAction'] = "\"\""
93
+ return "headers set to soap"
94
+ end
95
+
96
+ # get the current Cookie header
97
+ def get_cookie
98
+ self.headers["Cookie"]
99
+ end
100
+
101
+ # set the Cookie header
102
+ def set_cookie(cookie=nil)
103
+ self.headers["Cookie"] = cookie
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,47 @@
1
+ module WWMD
2
+ class Inputs
3
+ attr_accessor :elems
4
+
5
+ @cobj = '' # wwmd object
6
+ @elems = '' # array of elems parse out by self.new()
7
+
8
+ def initialize(*args)
9
+ @cobj = args.shift
10
+ end
11
+
12
+ def show
13
+ puts @elems
14
+ end
15
+
16
+ # call me from Page.set_data
17
+ def set
18
+ @elems = [@cobj.search("//input").map,@cobj.search("//select").map].flatten
19
+ end
20
+
21
+ def get(attr=nil)
22
+ @elems.map { |x| x[attr] }.reject { |y| y.nil? }
23
+ end
24
+
25
+ #
26
+ # return: FormArray containing all page inputs
27
+ def form
28
+ ret = {}
29
+ @elems.map do |x|
30
+ name = x['name']
31
+ id = x['id']
32
+ next if (name.nil? && id.nil?)
33
+ value = x['value']
34
+ type = x['type']
35
+ ret[name] = value
36
+ ret[id] = value if ((id || name) != name)
37
+ end
38
+ return FormArray.new(ret)
39
+ end
40
+
41
+ #
42
+ # return: FormArray containing get params
43
+ def params
44
+ return FormArray.new(@cobj.cur.clop.to_form)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,90 @@
1
+ =begin rdoc
2
+ this file contains methods to help operations in irb (display methods etc.).
3
+ =end
4
+ module WWMD
5
+ class Page
6
+
7
+ #:section: IRB helper methods
8
+
9
+ def head(i=1)
10
+ if i.kind_of?(Range)
11
+ puts self.body_data.split("\n")[i].join("\n")
12
+ return nil
13
+ end
14
+ puts self.body_data.head(i)
15
+ end
16
+
17
+ # IRB: text report what has been parsed from this page
18
+ def report(short=nil)
19
+ putx "-------------------------------------------------"
20
+ self.summary
21
+ putx "---- links found [#{self.has_links?.to_s} | #{self.links.size}]"
22
+ self.links.each_index { |i| putx "#{i.to_s} :: #{@links[i]}" } if short.nil?
23
+ putx "---- javascript found [#{self.has_jlinks?.to_s} | #{self.jlinks.size}]"
24
+ self.jlinks.each { |url| putx url } if short.nil?
25
+ putx "---- forms found [#{self.has_form?.to_s} | #{self.forms.size}]"
26
+ putx "---- comments found [#{self.has_comments?.to_s}]"
27
+ return nil
28
+ end
29
+
30
+ alias_method :show, :report#:nodoc:
31
+
32
+ # IRB: display summary of what has been parsed from this page
33
+ def summary
34
+ status = self.page_status
35
+ putx "XXXX[#{self.report_flags}] | #{self.response_code.to_s} | #{status} | #{self.url} | #{self.size}"
36
+ return nil
37
+ end
38
+
39
+ # IRB: display current headers
40
+ def request_headers
41
+ self.headers.each_pair { |k,v| putx "#{k}: #{v}" }
42
+ return nil
43
+ end
44
+
45
+ alias_method :show_headers, :request_headers#:nodoc:
46
+ alias_method :req_headers, :request_headers#:nodoc:
47
+
48
+ # IRB: display response headers
49
+ def response_headers
50
+ self.header_data.each { |x| putx "#{x[0]} :: #{x[1]}" }
51
+ return nil
52
+ end
53
+
54
+ alias_method :resp_headers, :response_headers#:nodoc:
55
+
56
+ # display self.body_data
57
+ def dump_body
58
+ putx self.body_data
59
+ end
60
+
61
+ alias_method :dump, :dump_body#:nodoc:
62
+
63
+ # IRB: puts the page filtered through html2text
64
+ def to_text; putx self.html2text; end
65
+ def text; self.html2text; end
66
+
67
+ # IRB: display a human readable report of all forms contained in page.body_data
68
+ def all_forms
69
+ self.forms.each_index { |x| putx "[#{x.to_s}]-------"; self.forms[x].report }
70
+ nil
71
+ end
72
+
73
+ def onclicks
74
+ self.search("//*[@onclick]").each { |x| puts x[:onclick] }
75
+ nil
76
+ end
77
+
78
+ # hexdump self.body_data
79
+ def hexdump
80
+ puts self.body_data.hexdump
81
+ end
82
+
83
+ # this only works on a mac so get a mac
84
+ def open #:nodoc:
85
+ fn = "wwmdtmp_#{Guid.new}.html"
86
+ self.write(fn)
87
+ %x[open #{fn}]
88
+ end
89
+ end
90
+ end