miketracy-wwmd 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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