ronin-support 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/ChangeLog.md +42 -1
- data/README.md +4 -1
- data/gemspec.yml +2 -1
- data/lib/ronin/extensions.rb +2 -0
- data/lib/ronin/extensions/enumerable.rb +54 -0
- data/lib/ronin/extensions/file.rb +70 -2
- data/lib/ronin/extensions/ip_addr.rb +45 -45
- data/lib/ronin/extensions/regexp.rb +45 -0
- data/lib/ronin/extensions/resolv.rb +80 -0
- data/lib/ronin/extensions/string.rb +35 -32
- data/lib/ronin/formatting/extensions/binary/integer.rb +12 -5
- data/lib/ronin/formatting/extensions/binary/string.rb +44 -16
- data/lib/ronin/formatting/extensions/html/integer.rb +51 -31
- data/lib/ronin/formatting/extensions/html/string.rb +50 -31
- data/lib/ronin/formatting/extensions/http/integer.rb +10 -2
- data/lib/ronin/formatting/extensions/sql.rb +20 -0
- data/lib/ronin/formatting/extensions/sql/string.rb +98 -0
- data/lib/ronin/formatting/extensions/text/array.rb +11 -9
- data/lib/ronin/formatting/extensions/text/string.rb +213 -29
- data/lib/ronin/formatting/sql.rb +20 -0
- data/lib/ronin/network/extensions/http.rb +1 -0
- data/lib/ronin/network/extensions/http/net.rb +2 -2
- data/lib/ronin/network/extensions/http/uri/http.rb +226 -0
- data/lib/ronin/network/extensions/imap/net.rb +1 -1
- data/lib/ronin/network/extensions/ssl/net.rb +7 -1
- data/lib/ronin/network/http/proxy.rb +20 -21
- data/lib/ronin/network/mixins.rb +27 -0
- data/lib/ronin/network/mixins/esmtp.rb +165 -0
- data/lib/ronin/network/mixins/http.rb +723 -0
- data/lib/ronin/network/mixins/imap.rb +151 -0
- data/lib/ronin/network/mixins/pop3.rb +141 -0
- data/lib/ronin/network/mixins/smtp.rb +159 -0
- data/lib/ronin/network/mixins/tcp.rb +331 -0
- data/lib/ronin/network/mixins/telnet.rb +199 -0
- data/lib/ronin/network/mixins/udp.rb +227 -0
- data/lib/ronin/network/ssl.rb +17 -11
- data/lib/ronin/path.rb +3 -3
- data/lib/ronin/spec/ui/output.rb +28 -0
- data/lib/ronin/support.rb +3 -0
- data/lib/ronin/support/version.rb +1 -1
- data/lib/ronin/ui/output.rb +21 -0
- data/lib/ronin/ui/output/helpers.rb +248 -0
- data/lib/ronin/ui/output/output.rb +146 -0
- data/lib/ronin/ui/output/terminal.rb +21 -0
- data/lib/ronin/ui/output/terminal/color.rb +118 -0
- data/lib/ronin/ui/output/terminal/raw.rb +103 -0
- data/lib/ronin/ui/shell.rb +219 -0
- data/ronin-support.gemspec +1 -1
- data/spec/extensions/enumerable_spec.rb +24 -0
- data/spec/extensions/file_spec.rb +39 -0
- data/spec/extensions/ip_addr_spec.rb +6 -0
- data/spec/extensions/resolv_spec.rb +18 -0
- data/spec/formatting/html/integer_spec.rb +2 -2
- data/spec/formatting/html/string_spec.rb +1 -1
- data/spec/formatting/sql/string_spec.rb +55 -0
- data/spec/formatting/text/string_spec.rb +110 -0
- data/spec/network/ssl_spec.rb +10 -4
- data/spec/ui/classes/test_shell.rb +22 -0
- data/spec/ui/output_spec.rb +32 -0
- data/spec/ui/shell_spec.rb +79 -0
- metadata +132 -90
data/.gitignore
ADDED
data/ChangeLog.md
CHANGED
@@ -1,4 +1,45 @@
|
|
1
|
-
### 0.
|
1
|
+
### 0.3.0 / 2011-10-16
|
2
|
+
|
3
|
+
* Require combinatorics ~> 0.4.
|
4
|
+
* Added {Enumerable#map_hash}.
|
5
|
+
* Added {String.generate}.
|
6
|
+
* Added {String#fuzz}.
|
7
|
+
* Added {File.each_line}.
|
8
|
+
* Added {File.each_row}.
|
9
|
+
* Added {Resolv.resolver}.
|
10
|
+
* Added {URI::HTTP#request}.
|
11
|
+
* Added {URI::HTTP#status}.
|
12
|
+
* Added {URI::HTTP#ok?}.
|
13
|
+
* Added {URI::HTTP#server}.
|
14
|
+
* Added {URI::HTTP#powered_by}.
|
15
|
+
* Added {URI::HTTP#copy}.
|
16
|
+
* Added {URI::HTTP#delete}.
|
17
|
+
* Added {URI::HTTP#get}.
|
18
|
+
* Added {URI::HTTP#get_headers}.
|
19
|
+
* Added {URI::HTTP#get_body}.
|
20
|
+
* Added {URI::HTTP#head}.
|
21
|
+
* Added {URI::HTTP#lock}.
|
22
|
+
* Added {URI::HTTP#mkcol}.
|
23
|
+
* Added {URI::HTTP#move}.
|
24
|
+
* Added {URI::HTTP#options}.
|
25
|
+
* Added {URI::HTTP#post}.
|
26
|
+
* Added {URI::HTTP#post_headers}.
|
27
|
+
* Added {URI::HTTP#post_body}.
|
28
|
+
* Added {URI::HTTP#prop_find}.
|
29
|
+
* Added {URI::HTTP#prop_match}.
|
30
|
+
* Added {URI::HTTP#trace}.
|
31
|
+
* Added {URI::HTTP#unlock}.
|
32
|
+
* Added {Regexp::MAC}.
|
33
|
+
* Added {Regexp::IPv6}, {Regexp::IPv4} and {Regexp::IP}.
|
34
|
+
* Added {Regexp::HOST_NAME}.
|
35
|
+
* Added {Regexp::USER_NAME}.
|
36
|
+
* Added {Regexp::EMAIL_ADDR}.
|
37
|
+
* Moved {Ronin::UI::Output}, {Ronin::UI::Shell} and {Ronin::Network::Mixins}
|
38
|
+
from ronin into ronin-support.
|
39
|
+
* Refactored {Ronin::UI::Shell} into a Class where commands are defined as
|
40
|
+
protected methods.
|
41
|
+
|
42
|
+
### 0.2.0 / 2011-07-04
|
2
43
|
|
3
44
|
* Require data_paths ~> 0.3.
|
4
45
|
* Added {Ronin::Mixin}.
|
data/README.md
CHANGED
@@ -25,6 +25,7 @@ or payloads over many common Source-Code-Management (SCM) systems.
|
|
25
25
|
* URIs
|
26
26
|
* HTML
|
27
27
|
* JavaScript
|
28
|
+
* SQL
|
28
29
|
* Generating random text.
|
29
30
|
* Networking:
|
30
31
|
* TCP
|
@@ -53,11 +54,13 @@ please see [Everyday Ronin](http://ronin-ruby.github.com/guides/everyday_ronin.h
|
|
53
54
|
* [hexdump](http://github.com/postmodern/hexdump#readme)
|
54
55
|
~> 0.1
|
55
56
|
* [combinatorics](http://github.com/postmodern/combinatorics#readme)
|
56
|
-
~> 0.
|
57
|
+
~> 0.4
|
57
58
|
* [uri-query_params](http://github.com/postmodern/uri-query_params#readme)
|
58
59
|
~> 0.5, >= 0.5.2
|
59
60
|
* [data_paths](http://github.com/postmodern/data_paths#readme)
|
60
61
|
~> 0.3
|
62
|
+
* [parameters](http://github.com/postmodern/parameters#readme)
|
63
|
+
~> 0.2, >= 0.2.3
|
61
64
|
|
62
65
|
## Install
|
63
66
|
|
data/gemspec.yml
CHANGED
@@ -15,9 +15,10 @@ required_ruby_version: ">= 1.8.7"
|
|
15
15
|
dependencies:
|
16
16
|
chars: ~> 0.2
|
17
17
|
hexdump: ~> 0.1
|
18
|
-
combinatorics: ~> 0.
|
18
|
+
combinatorics: ~> 0.4
|
19
19
|
uri-query_params: ~> 0.5, >= 0.5.2
|
20
20
|
data_paths: ~> 0.3
|
21
|
+
parameters: ~> 0.2, >= 0.2.3
|
21
22
|
|
22
23
|
development_dependencies:
|
23
24
|
bundler: ~> 1.0.10
|
data/lib/ronin/extensions.rb
CHANGED
@@ -19,8 +19,10 @@
|
|
19
19
|
|
20
20
|
require 'ronin/extensions/meta'
|
21
21
|
require 'ronin/extensions/string'
|
22
|
+
require 'ronin/extensions/regexp'
|
22
23
|
require 'ronin/extensions/file'
|
23
24
|
require 'ronin/extensions/ip_addr'
|
25
|
+
require 'ronin/extensions/resolv'
|
24
26
|
require 'ronin/extensions/kernel'
|
25
27
|
|
26
28
|
require 'hexdump/extensions'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2006-2011 Hal Brodigan (postmodern.mod3 at gmail.com)
|
3
|
+
#
|
4
|
+
# This file is part of Ronin Support.
|
5
|
+
#
|
6
|
+
# Ronin Support is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published
|
8
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Ronin Support is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
module Enumerable
|
21
|
+
|
22
|
+
#
|
23
|
+
# Maps the elements to a Hash.
|
24
|
+
#
|
25
|
+
# @yield [element]
|
26
|
+
# The given block will be passed each element.
|
27
|
+
# The return value from the block will be stored in the hash.
|
28
|
+
#
|
29
|
+
# @yieldparam [Object] element
|
30
|
+
# An element.
|
31
|
+
#
|
32
|
+
# @return [Hash{Object => Object}]
|
33
|
+
# The hash of elements and their mappings.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# host_names = %w[www.wired.com www.google.com]
|
37
|
+
# host_names.map_hash { |name| Resolv.getaddresses(name) }
|
38
|
+
# # {"www.wired.com"=>["184.84.183.17", "184.84.183.91"],
|
39
|
+
# # "www.google.com"=>["173.194.33.18", "173.194.33.17", "173.194.33.19", "173.194.33.20", "173.194.33.16"]}
|
40
|
+
#
|
41
|
+
# @since 0.3.0
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
#
|
45
|
+
def map_hash
|
46
|
+
new_hash = Hash.new do |hash,key|
|
47
|
+
hash[key] = yield(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
each { |element| new_hash[element] }
|
51
|
+
return new_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -19,6 +19,71 @@
|
|
19
19
|
|
20
20
|
class File
|
21
21
|
|
22
|
+
#
|
23
|
+
# Reads each line from the file.
|
24
|
+
#
|
25
|
+
# @param [String] path
|
26
|
+
# The path of the file.
|
27
|
+
#
|
28
|
+
# @yield [line]
|
29
|
+
# The given block will be passed each line.
|
30
|
+
#
|
31
|
+
# @yieldparam [String] line
|
32
|
+
# A line from the file, with the trailing newline characters removed.
|
33
|
+
#
|
34
|
+
# @return [Enumerator]
|
35
|
+
# If no block is given, an Enumerator will be returned.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# File.each_line('passwords.txt') do |line|
|
39
|
+
# # ...
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @since 0.3.0
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
#
|
46
|
+
def File.each_line(path)
|
47
|
+
return enum_for(:each_line,path) unless block_given?
|
48
|
+
|
49
|
+
File.open(path) do |file|
|
50
|
+
file.each_line { |line| yield line.chomp }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Reads each row from the file.
|
56
|
+
#
|
57
|
+
# @param [String] path
|
58
|
+
# The path of the file.
|
59
|
+
#
|
60
|
+
# @param [Regexp, String] separator
|
61
|
+
# The pattern to split the line by.
|
62
|
+
#
|
63
|
+
# @yield [row]
|
64
|
+
# The given block will be passed each row.
|
65
|
+
#
|
66
|
+
# @yieldparam [Array<String>] row
|
67
|
+
# A row from the file.
|
68
|
+
#
|
69
|
+
# @return [Enumerator]
|
70
|
+
# If no block is given, an Enumerator will be returned.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# File.each_row('db_dump.txt', '|') do |row|
|
74
|
+
# # ...
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# @since 0.3.0
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
#
|
81
|
+
def File.each_row(path,separator=/\s+/)
|
82
|
+
return enum_for(:each_row,path,separator) unless block_given?
|
83
|
+
|
84
|
+
File.each_line(path) { |line| yield line.split(separator) }
|
85
|
+
end
|
86
|
+
|
22
87
|
#
|
23
88
|
# Writes the given data to a specified path.
|
24
89
|
#
|
@@ -30,6 +95,9 @@ class File
|
|
30
95
|
#
|
31
96
|
# @return [nil]
|
32
97
|
#
|
98
|
+
# @example
|
99
|
+
# File.write('dump.txt',data)
|
100
|
+
#
|
33
101
|
# @api public
|
34
102
|
#
|
35
103
|
def File.write(path,data)
|
@@ -51,10 +119,10 @@ class File
|
|
51
119
|
path = path.to_s
|
52
120
|
|
53
121
|
# remove any \0 characters first
|
54
|
-
path.
|
122
|
+
path.tr!("\0",'')
|
55
123
|
|
56
124
|
# remove any home-dir expansions
|
57
|
-
path.gsub!(
|
125
|
+
path.gsub!('~',"\\~")
|
58
126
|
|
59
127
|
path = File.expand_path(File.join('/',path))
|
60
128
|
|
@@ -17,8 +17,10 @@
|
|
17
17
|
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
18
|
#
|
19
19
|
|
20
|
+
require 'ronin/extensions/resolv'
|
21
|
+
require 'ronin/extensions/regexp'
|
22
|
+
|
20
23
|
require 'ipaddr'
|
21
|
-
require 'resolv'
|
22
24
|
require 'strscan'
|
23
25
|
require 'combinatorics/list_comprehension'
|
24
26
|
|
@@ -26,14 +28,11 @@ class IPAddr
|
|
26
28
|
|
27
29
|
include Enumerable
|
28
30
|
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# A regular expression for matching IP Addresses.
|
36
|
-
REGEXP = /#{IPV4_REGEXP}|#{IPV6_REGEXP}/
|
31
|
+
# Socket families and IP address masks
|
32
|
+
MASKS = {
|
33
|
+
Socket::AF_INET => IN4MASK,
|
34
|
+
Socket::AF_INET6 => IN6MASK
|
35
|
+
}
|
37
36
|
|
38
37
|
#
|
39
38
|
# Extracts IP Addresses from text.
|
@@ -41,8 +40,8 @@ class IPAddr
|
|
41
40
|
# @param [String] text
|
42
41
|
# The text to scan for IP Addresses.
|
43
42
|
#
|
44
|
-
# @param [Symbol] version
|
45
|
-
# The version of IP Address to scan for (`:
|
43
|
+
# @param [Integer, Symbol] version
|
44
|
+
# The version of IP Address to scan for (`4`, `6`, `:v4` or `:v6`).
|
46
45
|
#
|
47
46
|
# @yield [ip]
|
48
47
|
# The given block will be passed each extracted IP Address.
|
@@ -53,29 +52,36 @@ class IPAddr
|
|
53
52
|
# @return [Array<String>]
|
54
53
|
# The IP Addresses found in the text.
|
55
54
|
#
|
55
|
+
# @example
|
56
|
+
# IPAddr.extract("Host: 127.0.0.1\n\rHost: 10.1.1.1\n\r")
|
57
|
+
# # => ["127.0.0.1", "10.1.1.1"]
|
58
|
+
#
|
59
|
+
# @example Extract only IPv4 addresses from a large amount of text.
|
60
|
+
# IPAddr.extract(text,:v4) do |ip|
|
61
|
+
# puts ip
|
62
|
+
# end
|
63
|
+
#
|
56
64
|
# @api public
|
57
65
|
#
|
58
66
|
def IPAddr.extract(text,version=nil,&block)
|
67
|
+
return enum_for(:extract,text,version).to_a unless block_given?
|
68
|
+
|
59
69
|
regexp = case version
|
60
|
-
when :ipv4
|
61
|
-
|
62
|
-
when :ipv6
|
63
|
-
|
70
|
+
when :ipv4, :v4, 4
|
71
|
+
Regexp::IPv4
|
72
|
+
when :ipv6, :v6, 6
|
73
|
+
Regexp::IPv6
|
64
74
|
else
|
65
|
-
|
75
|
+
Regexp::IP
|
66
76
|
end
|
67
77
|
|
68
|
-
|
78
|
+
scanner = StringScanner.new(text)
|
69
79
|
|
70
|
-
|
71
|
-
yield
|
72
|
-
return nil
|
73
|
-
else
|
74
|
-
ips = []
|
75
|
-
|
76
|
-
ips << parser.matched while parser.skip_until(regexp)
|
77
|
-
return ips
|
80
|
+
while scanner.skip_until(regexp)
|
81
|
+
yield scanner.matched
|
78
82
|
end
|
83
|
+
|
84
|
+
return nil
|
79
85
|
end
|
80
86
|
|
81
87
|
#
|
@@ -120,28 +126,27 @@ class IPAddr
|
|
120
126
|
return enum_for(:each,cidr_or_glob) unless block
|
121
127
|
|
122
128
|
if cidr_or_glob.include?('::')
|
123
|
-
|
129
|
+
separator = '::'
|
130
|
+
base = 16
|
131
|
+
|
132
|
+
prefix = if cidr_or_glob.start_with?('::')
|
124
133
|
'::'
|
125
134
|
else
|
126
135
|
''
|
127
136
|
end
|
128
137
|
|
129
|
-
separator = '::'
|
130
|
-
base = 16
|
131
|
-
|
132
138
|
format = lambda { |address|
|
133
139
|
prefix + address.map { |i| '%.2x' % i }.join('::')
|
134
140
|
}
|
135
141
|
else
|
136
142
|
separator = '.'
|
137
|
-
base
|
138
|
-
|
139
|
-
format = lambda { |address| address.join('.') }
|
143
|
+
base = 10
|
144
|
+
format = lambda { |address| address.join('.') }
|
140
145
|
end
|
141
146
|
|
142
147
|
# split the address
|
143
148
|
segments = cidr_or_glob.split(separator)
|
144
|
-
ranges
|
149
|
+
ranges = []
|
145
150
|
|
146
151
|
# map the components of the address to numeric ranges
|
147
152
|
segments.each do |segment|
|
@@ -159,23 +164,23 @@ class IPAddr
|
|
159
164
|
end
|
160
165
|
|
161
166
|
# cycle through the address ranges
|
162
|
-
ranges.comprehension
|
163
|
-
yield format[address]
|
164
|
-
end
|
165
|
-
|
167
|
+
ranges.comprehension { |address| yield format[address] }
|
166
168
|
return nil
|
167
169
|
end
|
168
170
|
|
169
171
|
#
|
170
172
|
# Resolves the host-names for the IP address.
|
171
173
|
#
|
174
|
+
# @param [String] nameserver
|
175
|
+
# The optional nameserver to query.
|
176
|
+
#
|
172
177
|
# @return [Array<String>]
|
173
178
|
# The host-names for the IP address.
|
174
179
|
#
|
175
180
|
# @api public
|
176
181
|
#
|
177
|
-
def lookup
|
178
|
-
Resolv.getnames(self.to_s)
|
182
|
+
def lookup(nameserver=nil)
|
183
|
+
Resolv.resolver(nameserver).getnames(self.to_s)
|
179
184
|
end
|
180
185
|
|
181
186
|
#
|
@@ -201,12 +206,7 @@ class IPAddr
|
|
201
206
|
def each
|
202
207
|
return enum_for(:each) unless block_given?
|
203
208
|
|
204
|
-
|
205
|
-
when Socket::AF_INET
|
206
|
-
family_mask = IN4MASK
|
207
|
-
when Socket::AF_INET6
|
208
|
-
family_mask = IN6MASK
|
209
|
-
end
|
209
|
+
family_mask = MASKS[@family]
|
210
210
|
|
211
211
|
(0..((~@mask_addr) & family_mask)).each do |i|
|
212
212
|
yield _to_string(@addr | i)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2006-2011 Hal Brodigan (postmodern.mod3 at gmail.com)
|
3
|
+
#
|
4
|
+
# This file is part of Ronin Support.
|
5
|
+
#
|
6
|
+
# Ronin Support is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Lesser General Public License as published
|
8
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Ronin Support is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with Ronin Support. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'ronin/extensions/resolv'
|
21
|
+
|
22
|
+
class Regexp
|
23
|
+
|
24
|
+
# Regular expression for finding MAC addresses in text
|
25
|
+
MAC = /[0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5}/
|
26
|
+
|
27
|
+
# A regular expression for matching IPv4 Addresses.
|
28
|
+
IPv4 = /[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}/
|
29
|
+
|
30
|
+
# A regular expression for matching IPv6 Addresses.
|
31
|
+
IPv6 = /:(:[0-9a-f]{1,4}){1,7}|([0-9a-f]{1,4}::?){1,7}[0-9a-f]{1,4}(:#{IPv4})?/
|
32
|
+
|
33
|
+
# A regular expression for matching IP Addresses.
|
34
|
+
IP = /#{IPv4}|#{IPv6}/
|
35
|
+
|
36
|
+
# Regular expression used to find host-names in text
|
37
|
+
HOST_NAME = /(?:[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9]+)*\.)+(?:#{union(Resolv::TLDS)})/
|
38
|
+
|
39
|
+
# Regular expression to match a word in the username of an email address
|
40
|
+
USER_NAME = /[A-Za-z](?:[A-Za-z0-9]+[\._-])*[A-Za-z0-9]+/
|
41
|
+
|
42
|
+
# Regular expression to find email addresses in text
|
43
|
+
EMAIL_ADDR = /#{USER_NAME}(?:\.#{USER_NAME})*\@#{HOST_NAME}/
|
44
|
+
|
45
|
+
end
|