iZsh-wwmd 0.2.19

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 (79) hide show
  1. data/History.txt +24 -0
  2. data/README.rdoc +87 -0
  3. data/Rakefile +34 -0
  4. data/examples/config_example.yaml +24 -0
  5. data/examples/wwmd_example.rb +73 -0
  6. data/lib/wwmd.rb +78 -0
  7. data/lib/wwmd/class_extensions.rb +2 -0
  8. data/lib/wwmd/class_extensions/extensions_base.rb +235 -0
  9. data/lib/wwmd/class_extensions/extensions_encoding.rb +79 -0
  10. data/lib/wwmd/class_extensions/extensions_external.rb +18 -0
  11. data/lib/wwmd/class_extensions/extensions_nilclass.rb +11 -0
  12. data/lib/wwmd/class_extensions/extensions_rbkb.rb +188 -0
  13. data/lib/wwmd/class_extensions/mixins_string_encoding.rb +40 -0
  14. data/lib/wwmd/guid.rb +155 -0
  15. data/lib/wwmd/page.rb +3 -0
  16. data/lib/wwmd/page/auth.rb +17 -0
  17. data/lib/wwmd/page/constants.rb +64 -0
  18. data/lib/wwmd/page/form.rb +99 -0
  19. data/lib/wwmd/page/form_array.rb +296 -0
  20. data/lib/wwmd/page/headers.rb +111 -0
  21. data/lib/wwmd/page/helpers.rb +30 -0
  22. data/lib/wwmd/page/html2text_hpricot.rb +76 -0
  23. data/lib/wwmd/page/html2text_nokogiri.rb +42 -0
  24. data/lib/wwmd/page/inputs.rb +47 -0
  25. data/lib/wwmd/page/irb_helpers.rb +114 -0
  26. data/lib/wwmd/page/page.rb +241 -0
  27. data/lib/wwmd/page/parsing_convenience.rb +94 -0
  28. data/lib/wwmd/page/reporting_helpers.rb +87 -0
  29. data/lib/wwmd/page/scrape.rb +198 -0
  30. data/lib/wwmd/page/spider.rb +127 -0
  31. data/lib/wwmd/urlparse.rb +104 -0
  32. data/lib/wwmd/viewstate.rb +17 -0
  33. data/lib/wwmd/viewstate/viewstate.rb +101 -0
  34. data/lib/wwmd/viewstate/viewstate_deserializer_methods.rb +217 -0
  35. data/lib/wwmd/viewstate/viewstate_from_xml.rb +128 -0
  36. data/lib/wwmd/viewstate/viewstate_types.rb +51 -0
  37. data/lib/wwmd/viewstate/viewstate_utils.rb +162 -0
  38. data/lib/wwmd/viewstate/viewstate_yaml.rb +25 -0
  39. data/lib/wwmd/viewstate/vs_stubs.rb +22 -0
  40. data/lib/wwmd/viewstate/vs_stubs/vs_array.rb +38 -0
  41. data/lib/wwmd/viewstate/vs_stubs/vs_binary_serialized.rb +30 -0
  42. data/lib/wwmd/viewstate/vs_stubs/vs_hashtable.rb +42 -0
  43. data/lib/wwmd/viewstate/vs_stubs/vs_hybrid_dict.rb +42 -0
  44. data/lib/wwmd/viewstate/vs_stubs/vs_indexed_string.rb +6 -0
  45. data/lib/wwmd/viewstate/vs_stubs/vs_indexed_string_ref.rb +24 -0
  46. data/lib/wwmd/viewstate/vs_stubs/vs_int_enum.rb +27 -0
  47. data/lib/wwmd/viewstate/vs_stubs/vs_list.rb +34 -0
  48. data/lib/wwmd/viewstate/vs_stubs/vs_pair.rb +29 -0
  49. data/lib/wwmd/viewstate/vs_stubs/vs_read_types.rb +11 -0
  50. data/lib/wwmd/viewstate/vs_stubs/vs_read_value.rb +35 -0
  51. data/lib/wwmd/viewstate/vs_stubs/vs_sparse_array.rb +58 -0
  52. data/lib/wwmd/viewstate/vs_stubs/vs_string.rb +33 -0
  53. data/lib/wwmd/viewstate/vs_stubs/vs_string_array.rb +39 -0
  54. data/lib/wwmd/viewstate/vs_stubs/vs_string_formatted.rb +32 -0
  55. data/lib/wwmd/viewstate/vs_stubs/vs_stub_helpers.rb +37 -0
  56. data/lib/wwmd/viewstate/vs_stubs/vs_triplet.rb +31 -0
  57. data/lib/wwmd/viewstate/vs_stubs/vs_type.rb +23 -0
  58. data/lib/wwmd/viewstate/vs_stubs/vs_unit.rb +30 -0
  59. data/lib/wwmd/viewstate/vs_stubs/vs_value.rb +35 -0
  60. data/lib/wwmd/wwmd_config.rb +52 -0
  61. data/lib/wwmd/wwmd_puts.rb +9 -0
  62. data/lib/wwmd/wwmd_utils.rb +28 -0
  63. data/spec/README +3 -0
  64. data/spec/form_array.spec +49 -0
  65. data/spec/spider_csrf_test.spec +28 -0
  66. data/spec/urlparse_test.spec +101 -0
  67. data/tasks/ann.rake +80 -0
  68. data/tasks/bones.rake +20 -0
  69. data/tasks/gem.rake +201 -0
  70. data/tasks/git.rake +40 -0
  71. data/tasks/notes.rake +27 -0
  72. data/tasks/post_load.rake +34 -0
  73. data/tasks/rdoc.rake +51 -0
  74. data/tasks/rubyforge.rake +55 -0
  75. data/tasks/setup.rb +292 -0
  76. data/tasks/spec.rake +54 -0
  77. data/tasks/test.rake +40 -0
  78. data/tasks/zentest.rake +36 -0
  79. metadata +174 -0
@@ -0,0 +1,24 @@
1
+ == 0.2.17 / 2009-06-22
2
+
3
+ * lots happening between here and .9
4
+ * viewstate refactor complete
5
+ * clean up page/page.rb
6
+ * cleaning up page/headers.rb
7
+ * cleaning up page/scrape.rb
8
+ * FormArray refactor includes the form action (full URL)
9
+ * page.submit(page.get_form)
10
+ * still bugs in URLParse but hunting them down throw by throw
11
+ * remove broken NTLM (preserve auth header warnings)
12
+ * remove WWMDConfig in favor of WWMD module methods but preserve old class for back compat
13
+ * add some burp helpers
14
+ * Page#from_paste (take entire request into Page and turn off cookies)
15
+ * burp log parsing coming
16
+ * Curb includes http_put (with header munging bug so careful)
17
+ * internal monkey patch for Curb to do arbitrary verb tampering (not here yet)
18
+ * add String#pbcopy
19
+ * move lots of things around for clarity during refactor
20
+ * refactor progressing but still unstable (2.0.16 gem including viewstate is good to go)
21
+
22
+ == 0.2.9 / 2009-05-05
23
+
24
+ * bonesify
@@ -0,0 +1,87 @@
1
+ == PARDON OUR DUST
2
+
3
+ WWMD is currently in the throes of major cleanup and refactoring.
4
+
5
+ 0.2.17 should be stable.
6
+
7
+ The viewstate tools can be had by themselves by using:
8
+
9
+ require 'wwmd/viewstate'
10
+
11
+ We appreciate your patience.
12
+
13
+ <;'"}()[]>{ XSSFish says, "Swim wif me"
14
+
15
+ == DESCRIPTION:
16
+
17
+ WWMD was originally intended to provide a console helper tool for
18
+ conducting web application security assessments (which is something I
19
+ find myself doing alot of). I've spent alot of time and had alot of
20
+ success writing application specific fuzzers + scrapers to test with.
21
+ WWMD provides a base of useful code to help you work with web sites both
22
+ in IRB and by writing scripts that can be as generic or as application
23
+ specific as you choose.
24
+
25
+ There's alot of helpful stuff crammed in here and its usage has evolved
26
+ alot. It's not intended to replace, remove or be better than any of the
27
+ tools you currently use. In fact, WWMD works best *with* the tools you
28
+ currently use to get stuff done. You get convenience methods for
29
+ getting, scraping, spidering, decoding, decrypting and munging user
30
+ inputs, pages and web applications.
31
+
32
+ It doesn't try to be smart. That's up to you.
33
+
34
+ What's here is the basic framework for getting started. There's a raft
35
+ of cookbook scripts and examples that are coming soon so make sure you
36
+ check the wiki regularly.
37
+
38
+ == REQUIREMENTS:
39
+
40
+ * rubygems
41
+ * ruby-debug
42
+ * curb (taf2-curb located here on github)
43
+ * nokogiri >= 1.3.2
44
+ * hpricot (not used by default)
45
+ * htmlentities
46
+
47
+ == INSTALL
48
+
49
+ ==== gem installation
50
+
51
+ WWMD is available as a gem from github:
52
+
53
+ gem sources -a http://gems.github.com #(you only have to do this once)
54
+ gem install mtracy-wwmd
55
+
56
+ === manual installation
57
+
58
+ fetch the repository from github and add path/to/wwmd/lib to your RUBYLIB
59
+ environment variable
60
+
61
+ == LICENSE:
62
+
63
+ (The MIT License)
64
+
65
+ Copyright (c) 2008,2009 Michael Tracy <mtracy@matasano.com>
66
+
67
+ Permission is hereby granted, free of charge, to any person obtaining
68
+ a copy of this software and associated documentation files (the
69
+ 'Software'), to deal in the Software without restriction, including
70
+ without limitation the rights to use, copy, modify, merge, publish,
71
+ distribute, sublicense, and/or sell copies of the Software, and to
72
+ permit persons to whom the Software is furnished to do so, subject to
73
+ the following conditions:
74
+
75
+ The above copyright notice and this permission notice shall be
76
+ included in all copies or substantial portions of the Software.
77
+
78
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
79
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
80
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
81
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
82
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
83
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
84
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
+
86
+ ## Blah blah blah
87
+
@@ -0,0 +1,34 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ begin
6
+ require 'bones'
7
+ Bones.setup
8
+ rescue LoadError
9
+ begin
10
+ load 'tasks/setup.rb'
11
+ rescue LoadError
12
+ raise RuntimeError, '### please install the "bones" gem ###'
13
+ end
14
+ end
15
+
16
+ ensure_in_path 'lib'
17
+ require 'wwmd'
18
+
19
+ task :default => 'spec:run'
20
+
21
+ PROJ.name = 'wwmd'
22
+ PROJ.authors = 'Michael L. Tracy'
23
+ PROJ.email = 'mtracy@matasano.com'
24
+ PROJ.url = 'http://github.com/miketracy/wwmd/tree/master'
25
+ PROJ.version = WWMD::VERSION
26
+ #PROJ.rubyforge.name = 'wwmd'
27
+
28
+ PROJ.spec.opts << '--color'
29
+
30
+ depend_on 'ruby-debug'
31
+ depend_on 'curb'
32
+ depend_on 'nokogiri'
33
+
34
+ # EOF
@@ -0,0 +1,24 @@
1
+ #---
2
+ :base_url: "http://www.example.com"
3
+ :header_file: "./HEADERS.default" # argv
4
+ :username: "username" # argv
5
+ :password: "password" # argv
6
+
7
+ # opts for spider (only spider local urls)
8
+ :spider_local_only: true
9
+
10
+ # opts for curl object
11
+ # set max_redirects and follow_location (follows 302s)
12
+ :follow_location: true
13
+ :max_redirects: 20
14
+
15
+ # --use_proxy=host:port overrides both of these settings
16
+ # to use a proxy (I use burp and so should you)
17
+ :use_proxy: false
18
+ :proxy_url: "localhost:8080"
19
+
20
+ # cookies (where are we going to save our cookies?)
21
+ :enable_cookies: true
22
+ :cookiejar: "./__cookiejar"
23
+
24
+ #+++
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'wwmd'
4
+ include WWMD
5
+
6
+ $stop = lambda { Debugger.breakpoint; Debugger.catchpoint }
7
+
8
+ module WWMD
9
+ class Page
10
+ # here we add directly to Page.login instead of creating an outside
11
+ # helper class. Normally we create a mixin script for this.
12
+ def login
13
+ self.get(self.opts[:base_url]) ;# GET the main page redirects to /login
14
+ form = self.get_form ;# get the login form
15
+ if form.nil? then ;# did we actually get a form?
16
+ puts "WARN: No login form on base page"
17
+ return (self.logged_in = false)
18
+ end
19
+ form.set("name",self.opts[:username]) ;# set login form variables from config
20
+ form.set("password",self.opts[:password])
21
+ self.url = self.action ;# set the url to submit to to the form action
22
+ self.submit(form) ;# submit the form
23
+
24
+ # perform some check to make sure we aren't still on the login page
25
+ # (this naively checks to make sure we don't have any password fields on the current page
26
+ self.logged_in = (self.search("//input[@type='password']").size == 0)
27
+ end
28
+ end
29
+ end
30
+
31
+ # parse options and load configuration file
32
+ inopts = WWMDConfig.parse_opts(ARGV)
33
+ conf = ARGV[0] || "./config_example.yaml"
34
+ opts = WWMDConfig.load_config(conf)
35
+ inopts.each_pair { |k,v| opts[k] = v }
36
+ $opts = opts
37
+
38
+ # create our Page object and name it page
39
+ page = Page.new(opts)
40
+ page.scrape.warn = false ;# don't complain about not overwriting scrape
41
+
42
+ # move our spider object up here
43
+ spider = page.spider
44
+
45
+ # output current configuration
46
+ puts "current configuration:\n"
47
+ page.opts.each_pair { |k,v|
48
+ if k == :password then
49
+ puts "#{k} :: ********"
50
+ else
51
+ puts "#{k} :: #{v}"
52
+ end
53
+ }
54
+ puts "\n"
55
+
56
+ # use the Helper method to login to the application
57
+ if page.opts[:use_auth] then
58
+ page.login
59
+ if page.logged_in? then
60
+ puts "logged in as #{opts[:username]}"
61
+ else
62
+ puts "WARN: could not log in" if !page.logged_in?
63
+ end
64
+ else
65
+ page.get opts[:base_url]
66
+ end
67
+
68
+ # report our current location and let's drop to irb with
69
+ # our whole context complete
70
+ puts "current location: #{page.current}"
71
+ puts "enter \"irb\" to go to the console"
72
+
73
+ $stop.call
@@ -0,0 +1,78 @@
1
+ # third-party
2
+ require 'rubygems'
3
+ require 'ruby-debug'
4
+ require 'curb'
5
+ require 'yaml'
6
+ require 'fileutils'
7
+ require 'base64'
8
+ require 'optparse'
9
+ require 'digest'
10
+ require 'uri'
11
+ require 'htmlentities'
12
+ require 'nkf'
13
+ require 'rexml/document'
14
+
15
+ module WWMD
16
+
17
+ # :stopdoc:
18
+ VERSION = "0.2.19"
19
+ PARSER = :nokogiri # :nokogiri || :hpricot
20
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
21
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
22
+ # :startdoc:
23
+
24
+ # Returns the version string for the library.
25
+ #
26
+ def self.version
27
+ VERSION
28
+ end
29
+
30
+ # Returns the library path for the module. If any arguments are given,
31
+ # they will be joined to the end of the libray path using
32
+ # <tt>File.join</tt>.
33
+ #
34
+ def self.libpath( *args )
35
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
36
+ end
37
+
38
+ # Returns the lpath for the module. If any arguments are given,
39
+ # they will be joined to the end of the path using
40
+ # <tt>File.join</tt>.
41
+ #
42
+ def self.path( *args )
43
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
44
+ end
45
+
46
+ # Utility method used to require all files ending in .rb that lie in the
47
+ # directory below this file that has the same name as the filename passed
48
+ # in. Optionally, a specific _directory_ name can be passed in such that
49
+ # the _filename_ does not have to be equivalent to the directory.
50
+ #
51
+ def self.require_all_libs_relative_to( fname, dir = nil )
52
+ dir ||= ::File.basename(fname, '.*')
53
+ search_me = ::File.expand_path(
54
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
55
+
56
+ Dir.glob(search_me).sort.each do |rb|
57
+ next if rb =~ /html2text_/
58
+ require rb
59
+ end
60
+ end
61
+
62
+ end # module WWMD
63
+
64
+ WWMD.require_all_libs_relative_to(__FILE__)
65
+
66
+ # special case parser
67
+
68
+ if WWMD::PARSER == :nokogiri
69
+ require 'nokogiri'
70
+ WWMD::HDOC = Nokogiri::HTML
71
+ require 'wwmd/page/html2text_nokogiri'
72
+ else
73
+ require 'hpricot'
74
+ WWMD::HDOC = Hpricot
75
+ require 'wwmd/page/html2text_hpricot'
76
+ end
77
+
78
+ # EOF
@@ -0,0 +1,2 @@
1
+ Dir.glob(::File.join(::File.dirname(__FILE__),"class_extensions/","*.rb")).each { |rb| require rb }
2
+
@@ -0,0 +1,235 @@
1
+ require 'htmlentities'
2
+
3
+ =begin rdoc
4
+ let's re-open everything!
5
+ =end
6
+
7
+ require 'uri'
8
+
9
+ class Numeric
10
+ # return binary representation of <tt>length</tt> size padded with \x00
11
+ # length: length in bytes to return (padded with least signficant \x00
12
+ # reverse: reverse the byte order
13
+ def to_bin (len,rev = false)
14
+ str = ""
15
+ bignum = self
16
+ 1.upto(len) do |i|
17
+ str << (bignum & 0xFF).to_n8
18
+ bignum = bignum >> 8
19
+ end
20
+ return str.reverse if rev
21
+ return str
22
+ end
23
+
24
+ # integer to ip address
25
+ def int_to_ip
26
+ [24, 16, 8, 0].map { |b| (self >> b) & 255 }.join('.')
27
+ end
28
+
29
+ # integer to mac address [uses ':' as delimiter]
30
+ def int_to_mac
31
+ [40,32,24,16,8,0].map { |b| ((self >> b) & 255).to_s(16).rjust(2,"0") }.join(":")
32
+ end
33
+ end
34
+
35
+ class String
36
+
37
+ def strip_up
38
+ self.gsub(/[^\x20-\x7e,\n]/,"").gsub(/^\n/,"")
39
+ end
40
+
41
+ # ip address to int
42
+ def ip_to_int
43
+ self.split('.').inject(0) { |a,e| (a << 8) + e.to_i }
44
+ end
45
+
46
+ # mac address to int [uses ':' as delimiter]
47
+ def mac_to_int
48
+ self.split(':').inject(0) { |a,e| (a << 8) + e.to_i(16) }
49
+ end
50
+
51
+ # return true or false for <tt>string.match</tt>
52
+ def contains?(rexp)
53
+ return !self.match(rexp).nil?
54
+ end
55
+
56
+ # strip the string and return true if empty
57
+ def empty?
58
+ return self.strip == ''
59
+ end
60
+
61
+ # return everything in the string (url) before the first get param
62
+ ## "http://foo.bar.com/page.asp?somearg=foo&otherarg=bar".clip
63
+ ## => "http://foo.bar.com/page.asp"
64
+ def clip(pref="?")
65
+ if (v = self.index(pref))
66
+ return self[0..(v-1)]
67
+ end
68
+ return self
69
+ end
70
+
71
+ # return everything in the string (url) after the first get parameter
72
+ # without the leading '?'
73
+ #
74
+ # pass true as the second param to also get back the ?
75
+ ## "http://foo.bar.com/page.asp?somearg=foo&otherarg=bar".clop
76
+ ## => "somearg=foo&otherarg=bar"
77
+ def clop(pref="?",preftoo=false)
78
+ (preftoo == false) ? add = "" : add = pref
79
+ if (v = self.index(pref))
80
+ return add + self[(v+1)..-1]
81
+ end
82
+ return nil
83
+ end
84
+
85
+ def clopp; self.clop("?",true); end #:nodoc:
86
+
87
+ def clopa
88
+ return [self.clip,self.clop]
89
+ end
90
+
91
+ alias_method :clipa, :clopa
92
+
93
+ # File.dirname with a trailing slash
94
+ def dirname
95
+ return self if self.match(/\/$/)
96
+ File.dirname(self) + "/"
97
+ end
98
+
99
+ # File.basename
100
+ def basename(ext=nil)
101
+ if ext
102
+ File.basename(self,ext)
103
+ else
104
+ File.basename(self)
105
+ end
106
+ end
107
+
108
+ def extname
109
+ self.split('.').last
110
+ end
111
+
112
+ # write string to passed filename
113
+ # if filename is nil? will raise an error
114
+ def write(fname=nil)
115
+ raise "filename required" if fname.nil?
116
+ File.write(fname,self)
117
+ return fname
118
+ end
119
+
120
+ # parse passed GET param string into a form and return the FormArray object
121
+ def to_form
122
+ if self.split("\n").size > 1
123
+ return self.to_form_from_show
124
+ end
125
+ ret = FormArray.new
126
+ self.split("&").each do |x|
127
+ y = x.split("=",2)
128
+ ret.extend!(y[0].to_s,y[1].to_s)
129
+ end
130
+ return ret
131
+ end
132
+
133
+ def to_form_from_show
134
+ self.split("\n").map { |a|
135
+ key,val = a.split("=",2)
136
+ key = key.split(" ")[-1]
137
+ val = val.strip if val
138
+ ["#{key}=#{val}"]
139
+ }.join("&").to_form.squeeze_keys!
140
+ end
141
+
142
+ def mform
143
+ return self.gsub("\n","").to_form
144
+ end
145
+
146
+ def to_form_from_req
147
+ # self.split("\x0d\x0a\x0d\x0a")[1].to_form
148
+ self.split("\n\n")[1].to_form
149
+ end
150
+ alias_method :to_ffr, :to_form_from_req
151
+
152
+ # create filename from url changing "/" to "_"
153
+ def to_fn(ext=nil)
154
+ ret = self.clip.split("/")[3..-1].join("_")
155
+ ret += ".#{ext}" if not ext.nil?
156
+ return ret
157
+ end
158
+
159
+ # strip html tags from string
160
+ def strip_html
161
+ self.gsub(/<\/?[^>]*>/, "")
162
+ end
163
+
164
+ # range or int
165
+ def head(c=5)
166
+ if c.kind_of?(Range) then
167
+ range = c
168
+ else
169
+ range = (0..(c - 1))
170
+ end
171
+ self.split("\n")[range].join("\n")
172
+ end
173
+
174
+ # return a literal regexp object for this string
175
+ #
176
+ # escape regexp operators
177
+ def to_regexp
178
+ return Regexp.new(self.gsub(/([\[\]\{\}\(\)\*\$\?])/) { |x| '\\' + x })
179
+ end
180
+
181
+ # check if this string is a guid
182
+ def is_guid?
183
+ begin
184
+ Guid.from_s(self)
185
+ rescue => e
186
+ return false
187
+ end
188
+ return true
189
+ end
190
+
191
+ def md5
192
+ Digest::MD5.digest(self).hexify
193
+ end
194
+
195
+ def sha1
196
+ Digest::SHA1.digest(self).hexify
197
+ end
198
+
199
+ def sha256
200
+ Digest::SHA256.digest(self).hexify
201
+ end
202
+
203
+ def sha512
204
+ Digest::SHA512.digest(self).hexify
205
+ end
206
+
207
+ def pbcopy
208
+ IO.popen('pbcopy', 'r+') { |c| c.print self }
209
+ end
210
+ end
211
+
212
+ class Array
213
+ # grep each element of an array for the passed regular expression
214
+ # and return an Array of matches
215
+ # (only works one deep)
216
+ def each_grep(regex)
217
+ ret = []
218
+ self.each { |e| ret << e.grep(regex) }
219
+ return ret
220
+ end
221
+
222
+ # join the array with "\n" and write to a file
223
+ def to_file(filename)
224
+ File.write(filename,self.join("\n"))
225
+ end
226
+ end
227
+
228
+ class File
229
+ # write string to file
230
+ def self.write(filename,contents)
231
+ fout = File.open(filename,"w")
232
+ fout.print contents
233
+ fout.close
234
+ end
235
+ end