hermeneutics 1.10 → 1.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,257 @@
1
+ #
2
+ # hermeneutics/cli/pop3.rb -- POP3 client
3
+ #
4
+
5
+ require "hermeneutics/cli/protocol"
6
+
7
+ module Hermeneutics
8
+
9
+ module Cli
10
+
11
+ class POP3 < Protocol
12
+
13
+ CRLF = true
14
+
15
+ PORT, PORT_SSL = 110, 995
16
+
17
+ class Error < StandardError ; end
18
+ class UnspecError < Error ; end
19
+ class AuthFail < Error ; end
20
+ class Check < Error ; end
21
+ class Unused < Error ; end
22
+
23
+ class <<self
24
+ private :new
25
+ def open host, port = nil, timeout: nil, ssl: false
26
+ port ||= ssl ? PORT_SSL : PORT
27
+ super host, port, timeout: timeout, ssl: ssl
28
+ end
29
+ end
30
+
31
+ attr_reader :last_response
32
+
33
+ def initialize *args
34
+ super
35
+ @stamp = get_response.slice /<[!-~]+@[!-~]+>/
36
+ end
37
+
38
+ def authenticate name, pwd
39
+ if @stamp then
40
+ apop name, pwd
41
+ else
42
+ user name
43
+ pass pwd
44
+ end
45
+ end
46
+
47
+ def user name
48
+ writeline "USER #{name}"
49
+ get_response
50
+ end
51
+
52
+ def pass pwd
53
+ writeline "PASS #{pwd}"
54
+ get_response_auth
55
+ end
56
+
57
+ def apop name, pwd
58
+ require "digest/md5"
59
+ hash = Digest::MD5.hexdigest "#@stamp#{pwd}"
60
+ writeline "APOP #{name} #{hash}"
61
+ get_response_auth
62
+ end
63
+
64
+ def capa
65
+ if block_given? then
66
+ writeline "CAPA"
67
+ get_response do |_|
68
+ get_data { |l|
69
+ c, *rest = l.split
70
+ yield c, rest
71
+ }
72
+ end
73
+ else
74
+ r = Hash.new do |h,k| h[k] = [] end
75
+ capa do |c,v|
76
+ if v.notempty? then
77
+ r[c].concat v
78
+ else
79
+ r[c] = true
80
+ end
81
+ end
82
+ r
83
+ end
84
+ end
85
+
86
+ def stat
87
+ if block_given? then
88
+ writeline "STAT"
89
+ n, r = split_num_len get_response
90
+ yield n, r
91
+ else
92
+ stat do |*a| a end
93
+ end
94
+ end
95
+
96
+ def list n = nil
97
+ n = n.to_i.nonzero?
98
+ if block_given? then
99
+ cmd = "LIST"
100
+ cmd << " #{n}" if n
101
+ writeline cmd
102
+ if n then
103
+ n_, len = split_num_len get_response
104
+ n == n_ or raise Check, "Wrong LIST response: #{n} <-> #{n_}"
105
+ yield n, len
106
+ else
107
+ get_response do |_|
108
+ get_data do |l|
109
+ n_, len = split_num_len l
110
+ yield n_, len
111
+ end
112
+ end
113
+ end
114
+ else
115
+ if n then
116
+ list n do |*a| a end
117
+ else
118
+ h = {}
119
+ list n do |n_,len|
120
+ h[ n_] = len
121
+ end
122
+ h
123
+ end
124
+ end
125
+ end
126
+
127
+ def uidl n = nil
128
+ n = n.to_i.nonzero?
129
+ if block_given? then
130
+ cmd = "UIDL"
131
+ cmd << " #{n}" if n
132
+ writeline cmd
133
+ if n then
134
+ n_, id = split_num get_response
135
+ n == n_ or raise Check, "Wrong UIDL response: #{n} <-> #{n_}"
136
+ yield n, id
137
+ else
138
+ get_response do |_|
139
+ get_data do |l|
140
+ n_, id = split_num l
141
+ yield n_, id
142
+ end
143
+ end
144
+ end
145
+ else
146
+ if n then
147
+ uidl n do |*a| a end
148
+ else
149
+ h = {}
150
+ uidl n do |n_,id|
151
+ h[ n_] = id
152
+ end
153
+ h
154
+ end
155
+ end
156
+ end
157
+
158
+ def retr n, &block
159
+ writeline "RETR #{n}"
160
+ get_response do |_|
161
+ get_data_str &block
162
+ end
163
+ end
164
+
165
+ def top n, x, &block
166
+ writeline "TOP #{n} #{x}"
167
+ get_response do |_|
168
+ get_data_str &block
169
+ end
170
+ end
171
+
172
+
173
+ def dele n
174
+ writeline "DELE #{n}"
175
+ get_response
176
+ end
177
+
178
+ def rset
179
+ writeline "RSET"
180
+ get_response
181
+ end
182
+
183
+ def noop
184
+ writeline "NOOP"
185
+ get_response
186
+ end
187
+
188
+ def quit
189
+ writeline "QUIT"
190
+ get_response
191
+ end
192
+
193
+ private
194
+
195
+ def get_response
196
+ r = readline
197
+ a = case r
198
+ when /^\+OK */ then @last_response = $'.notempty?
199
+ when /^\-ERR */ then raise Error, $'
200
+ else raise UnspecError, r
201
+ end
202
+ if block_given? then
203
+ yield a
204
+ else
205
+ a
206
+ end
207
+ ensure
208
+ unless done? then
209
+ r = readline
210
+ r and raise Unused, "Unexpected data: #{r.inspect}"
211
+ end
212
+ end
213
+
214
+ def get_response_auth
215
+ begin
216
+ get_response
217
+ rescue Error
218
+ err = $!.message
219
+ end
220
+ raise AuthFail, err if err
221
+ end
222
+
223
+ def get_data
224
+ loop do
225
+ l = readline
226
+ break if l == "."
227
+ l.slice /\A\./
228
+ yield l
229
+ end
230
+ end
231
+
232
+ def get_data_str
233
+ if block_given? then
234
+ get_data { |l| yield l }
235
+ else
236
+ r = ""
237
+ get_data { |l| r << l << "\n" }
238
+ r
239
+ end
240
+ end
241
+
242
+ def split_num str
243
+ n, r = str.split nil, 2
244
+ [ n.to_i, r]
245
+ end
246
+
247
+ def split_num_len str
248
+ n, r = split_num str
249
+ [ n, r.to_i]
250
+ end
251
+
252
+ end
253
+
254
+ end
255
+
256
+ end
257
+
@@ -0,0 +1,141 @@
1
+ #
2
+ # hermeneutics/cli/protocol.rb -- Basic communication
3
+ #
4
+
5
+ require "supplement"
6
+ require "socket"
7
+
8
+
9
+ if RUBY_VERSION < "3" then
10
+ class TCPSocket
11
+ class <<self
12
+ alias open_orig open
13
+ def open host, port, connect_timeout: nil, &block
14
+ open_orig host, port, &block
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ module Hermeneutics
21
+
22
+ module Cli
23
+
24
+ class Protocol
25
+
26
+ class <<self
27
+ private :new
28
+ def open host, port, timeout: nil, ssl: false
29
+ open_socket host, port, timeout, ssl do |s|
30
+ i = new s, timeout
31
+ yield i
32
+ end
33
+ end
34
+ private
35
+ def open_socket host, port, timeout, ssl
36
+ TCPSocket.open host, port, connect_timeout: timeout do |s|
37
+ if ssl then
38
+ require "hermeneutics/cli/openssl"
39
+ if Hash === ssl then
40
+ if ssl[ :ca_file] || ssl[ :ca_path] then
41
+ ssl[ :verify_mode] ||= OpenSSL::SSL::VERIFY_PEER
42
+ end
43
+ else
44
+ vfm = case ssl
45
+ when true then OpenSSL::SSL::VERIFY_NONE
46
+ when Integer then ssl
47
+ when :none then OpenSSL::SSL::VERIFY_NONE
48
+ when :peer then OpenSSL::SSL::VERIFY_PEER
49
+ end
50
+ ssl = { verify_mode: vfm}
51
+ end
52
+ ctx = OpenSSL::SSL::SSLContext.new
53
+ ctx.set_params ssl
54
+ s = OpenSSL::SSL::SSLSocket.new s, ctx
55
+ s.connect
56
+ end
57
+ yield s
58
+ end
59
+ end
60
+ end
61
+
62
+ CRLF = false
63
+
64
+ attr_writer :timeout
65
+
66
+ def initialize socket, timeout
67
+ @socket, @timeout = socket, timeout
68
+ end
69
+
70
+ def trace!
71
+ @trace = true
72
+ end
73
+
74
+ def writeline l
75
+ l.chomp!
76
+ @trace and $stderr.puts "C: #{l}"
77
+ @socket.write l
78
+ @socket.write self.class::CRLF ? "\r\n" : "\n"
79
+ end
80
+
81
+ def readline
82
+ @socket.wait @timeout||0
83
+ r = @socket.readline
84
+ r.chomp!
85
+ @trace and $stderr.puts "S: #{r}"
86
+ r
87
+ rescue EOFError
88
+ end
89
+
90
+ def write data
91
+ @trace and $stderr.puts "C- #{data.inspect}"
92
+ @socket.write data
93
+ end
94
+
95
+ def read bytes
96
+ @socket.wait @timeout||0
97
+ r = @socket.read bytes
98
+ @trace and $stderr.puts "S- #{r.inspect}"
99
+ r
100
+ rescue EOFError
101
+ end
102
+
103
+ def done?
104
+ not @socket.ready?
105
+ end
106
+
107
+ end
108
+
109
+
110
+ module CramMD5
111
+
112
+ class <<self
113
+ def included cls
114
+ require "digest/md5"
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def crammd5_answer a
121
+ "#@user #{hmac_md5 a, @passwd}"
122
+ end
123
+
124
+ MASKS = [ 0x36, 0x5c, ]
125
+ IMASK, OMASK = *MASKS
126
+
127
+ def hmac_md5 text, key
128
+ key = Digest::MD5.digest key if key.length > 64
129
+ nulls = [ 0]*64
130
+ k_ip, k_op = *MASKS.map { |m|
131
+ (nulls.zip key.bytes).map { |n,k| ((k||n) ^ m).chr }.join
132
+ }
133
+ Digest::MD5.hexdigest k_op + (Digest::MD5.digest k_ip + text)
134
+ end
135
+
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+
@@ -0,0 +1,218 @@
1
+ #
2
+ # hermeneutics/cli/smtp.rb -- SMTP client
3
+ #
4
+
5
+ require "hermeneutics/cli/protocol"
6
+
7
+ module Hermeneutics
8
+
9
+ module Cli
10
+
11
+ class SMTP < Protocol
12
+
13
+ CRLF = true
14
+
15
+ PORT, PORT_SSL = 25, 465
16
+
17
+ class Error < StandardError ; end
18
+ class UnspecError < Error ; end
19
+ class ServerNotReady < Error ; end
20
+ class NotOk < Error ; end
21
+ class NotReadyForData < Error ; end
22
+ class Unused < Error ; end
23
+ class Uncaught < Error ; end
24
+
25
+ class <<self
26
+ private :new
27
+ def open host, port = nil, timeout: nil, ssl: false
28
+ port ||= ssl ? PORT_SSL : PORT
29
+ super host, port, timeout: timeout, ssl: ssl
30
+ end
31
+ end
32
+
33
+ attr_reader :domain, :greet
34
+ attr_reader :advertised
35
+ attr_reader :last_response
36
+
37
+ def initialize *args
38
+ super
39
+ get_response.ok? or raise ServerNotReady, @last_response.msg
40
+ end
41
+
42
+ def size
43
+ @advertised && @advertised[ :SIZE]
44
+ end
45
+
46
+ def auth
47
+ @advertised && @advertised[ :AUTH]
48
+ end
49
+
50
+ def has_auth? meth
51
+ a = auth
52
+ a and a.include? meth
53
+ end
54
+
55
+
56
+ def helo host = nil
57
+ cmd_hello "HELO", host
58
+ end
59
+
60
+ def ehlo host = nil
61
+ @advertised = {}
62
+ cmd_hello "EHLO", host do |code,msg|
63
+ unless @domain then
64
+ @domain, @greet = msg.split nil, 2
65
+ next
66
+ end
67
+ keyword, param = msg.split nil, 2
68
+ keyword.upcase!
69
+ keyword = keyword.to_sym
70
+ case keyword
71
+ when :SIZE then param = Integer param
72
+ when :AUTH then param = param.split.map { |p| p.upcase! ; p.to_sym }
73
+ end
74
+ @advertised[ keyword] = param || true
75
+ end
76
+ end
77
+
78
+ def mail_from from
79
+ cmd "MAIL", "FROM:<#{from}>"
80
+ end
81
+
82
+ def rcpt_to to
83
+ cmd "RCPT", "TO:<#{to}>"
84
+ end
85
+
86
+ def data reader
87
+ write_cmd "DATA"
88
+ get_response.waiting? or raise NotReadyForData, @last_response.msg
89
+ reader.each_line { |l|
90
+ l =~ /\A\./ and l = ".#{l}"
91
+ writeline l
92
+ }
93
+ writeline "."
94
+ get_response_ok
95
+ end
96
+
97
+ def bdat data
98
+ data.each { |d|
99
+ write_cmd "BDAT", d.bytesize
100
+ write d
101
+ get_response_ok
102
+ }
103
+ write_cmd "BDAT", 0, "LAST"
104
+ get_response_ok do |code,msg|
105
+ yield msg if block_given?
106
+ end
107
+ end
108
+
109
+ def rset
110
+ cmd "RSET"
111
+ end
112
+
113
+ def help str = nil, &block
114
+ cmd "HELP", str, &block
115
+ end
116
+
117
+ def noop str = nil
118
+ cmd "NOOP"
119
+ end
120
+
121
+ def quit
122
+ cmd "QUIT"
123
+ end
124
+
125
+
126
+ def plain user, password
127
+ write_cmd "AUTH", "PLAIN"
128
+ get_response.waiting? or raise NotReadyForData, @last_response.msg
129
+ l = ["\0#{user}\0#{password}"].pack "m0"
130
+ writeline l
131
+ get_response_ok
132
+ end
133
+
134
+
135
+ def login user, password
136
+ write_cmd "AUTH", "LOGIN"
137
+ get_response.waiting? or raise NotReadyForData, @last_response.msg
138
+ writeline [user].pack "m0"
139
+ get_response.waiting? or raise NotReadyForData, @last_response.msg
140
+ writeline [password].pack "m0"
141
+ get_response_ok
142
+ end
143
+
144
+
145
+ private
146
+
147
+ def cmd_hello name, host, &block
148
+ host ||= Socket.gethostname
149
+ write_cmd name, host
150
+ get_response_ok &block
151
+ unless @domain then
152
+ @domain, @greet = @last_response.msg.split nil, 2
153
+ end
154
+ end
155
+
156
+ def cmd name, *args, &block
157
+ write_cmd name, *args
158
+ get_response_ok &block
159
+ end
160
+
161
+ def write_cmd name, *args
162
+ l = [ name, *args].join " "
163
+ writeline l
164
+ end
165
+
166
+ class Response
167
+
168
+ attr_reader :code, :msg
169
+
170
+ def initialize code, msg
171
+ @code, @msg = code, msg
172
+ end
173
+
174
+ def kat ; code / 100 ; end
175
+
176
+ def to_s ; "%03d %s" % [ @code, @msg] ; end
177
+
178
+ def prelim? ; kat == 1 ; end
179
+ def ok? ; kat == 2 ; end
180
+ def waiting? ; kat == 3 ; end
181
+ def error? ; kat == 4 ; end
182
+ def fatal? ; kat == 5 ; end
183
+
184
+ end
185
+
186
+ def get_response_ok &block
187
+ get_response &block
188
+ @last_response.ok? or raise NotOk, @last_response.msg
189
+ true
190
+ end
191
+
192
+ def get_response
193
+ loop do
194
+ r = readline
195
+ if r =~ /\A(\d\d\d) / then
196
+ @last_response = Response.new $1.to_i, $'
197
+ break
198
+ elsif r =~ /\A(\d\d\d)-/ then
199
+ block_given? or raise Uncaught, r
200
+ yield $1.to_i, $'
201
+ else
202
+ raise UnspecError, r
203
+ end
204
+ end
205
+ @last_response
206
+ ensure
207
+ unless done? then
208
+ r = readline
209
+ r and raise Unused, "Unexpected data: #{r.inspect}"
210
+ end
211
+ end
212
+
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+
@@ -8,7 +8,7 @@
8
8
 
9
9
  Hermeneutics::Color handles 24-bit colors.
10
10
 
11
- Hermeneutics::Colour is an alias for <code>Hermeneutics::Color</code>.
11
+ Hermeneutics::Colour is an alias for +Hermeneutics::Color+.
12
12
 
13
13
  =end
14
14
 
@@ -41,7 +41,7 @@ module Hermeneutics
41
41
  # new( r, g, b) -> clr
42
42
  #
43
43
  # Create a color with red, green and blue values. They are in range
44
- # <code>0..255</code>.
44
+ # +0..255+.
45
45
  #
46
46
  def initialize r, *gb
47
47
  if gb.any? then
@@ -79,8 +79,8 @@ module Hermeneutics
79
79
  # :call-seq:
80
80
  # gray( num) -> clr
81
81
  #
82
- # Create a gray color (r=b=g). <code>num</code> is in range
83
- # <code>0..255</code>.
82
+ # Create a gray color (r=b=g). +num+ is in range
83
+ # +0..255+.
84
84
  #
85
85
  def gray i
86
86
  new i, i, i
@@ -96,7 +96,7 @@ module Hermeneutics
96
96
  #
97
97
  def to_s ; "#" + tuple.map { |x| "%02x" % x }.join ; end
98
98
 
99
- def inspect ; "#<#{cls}:#{'0x%08x' % (object_id << 1)} #{to_s}>" ; end
99
+ def inspect ; "#<#{self.class}:#{'0x%08x' % (object_id << 1)} #{to_s}>" ; end
100
100
 
101
101
  class <<self
102
102
 
@@ -224,8 +224,8 @@ module Hermeneutics
224
224
  # :call-seq:
225
225
  # edit_hsv() { |h,s,v| ... } -> clr
226
226
  #
227
- # Convert it to an HSV triple, yield that to the block and build a
228
- # new <code>Color</code> from the blocks result.
227
+ # Convert it to an HSV triple, yield that to the block and build a new
228
+ # +Color+ from the blocks result.
229
229
  #
230
230
  def edit_hsv
231
231
  hsv = yield *to_hsv
@@ -241,19 +241,26 @@ module Hermeneutics
241
241
 
242
242
  end
243
243
 
244
- # Alias for class <code>Hermeneutics::Color</code> in British English.
244
+ # Alias for class +Hermeneutics::Color+ in British English.
245
245
  Colour = Color
246
246
 
247
247
  end
248
248
 
249
249
 
250
- class Float
250
+ class Integer
251
251
  def to_gray
252
252
  Hermeneutics::Color.gray self
253
253
  end
254
254
  alias to_grey to_gray
255
255
  end
256
256
 
257
+ class Float
258
+ def to_gray
259
+ (0xff * self).to_i.to_gray
260
+ end
261
+ alias to_grey to_gray
262
+ end
263
+
257
264
  class String
258
265
  def to_gray
259
266
  (Integer self).to_gray
@@ -115,6 +115,9 @@ module Hermeneutics
115
115
  end
116
116
  end
117
117
 
118
+ def empty? ; @hash.empty? ; end
119
+ def notempty? ; self if @hash.notempty? ; end
120
+
118
121
  # :call-seq:
119
122
  # []( key) -> str or nil
120
123
  #
@@ -123,6 +126,8 @@ module Hermeneutics
123
126
  def [] key ; @hash[ key.to_sym] ; end
124
127
  alias field []
125
128
 
129
+ private
130
+
126
131
  def method_missing sym, *args
127
132
  if sym =~ /[^a-z_]/ or args.any? then
128
133
  super
@@ -131,6 +136,8 @@ module Hermeneutics
131
136
  end
132
137
  end
133
138
 
139
+ public
140
+
134
141
  # :call-seq:
135
142
  # keys() -> ary
136
143
  #
@@ -252,6 +259,9 @@ module Hermeneutics
252
259
  super hash
253
260
  end
254
261
 
262
+ def empty? ; @caption.empty? and super ; end
263
+ def notempty? ; self if @caption.notempty? or super ; end
264
+
255
265
  def =~ re
256
266
  @caption =~ re
257
267
  end