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,245 @@
1
+ #
2
+ # hermeneutics/cli/imap/parser.rb -- Parsing IMAP responses
3
+ #
4
+
5
+ require "supplement"
6
+
7
+ require "hermeneutics/cli/imap/utf7imap"
8
+
9
+
10
+ module Hermeneutics
11
+
12
+ module Cli
13
+
14
+ module ImapTools
15
+
16
+ class StringReader
17
+ def initialize src
18
+ @src = src.lines
19
+ @src.each { |l| l.chomp! }
20
+ end
21
+ def readline
22
+ @src.shift
23
+ end
24
+ def eof? ; @src.empty? ; end
25
+ end
26
+
27
+
28
+ class Parser
29
+
30
+ RE = %r/\A\s*(?:
31
+ (\()|
32
+ (\))|
33
+ ("(?:[^\\"]|\\.)*")|
34
+ (\{\d+\}\z)|
35
+ ((?:\[[^\]]*\]|[^ \t)])+)|
36
+ )/x
37
+
38
+ class <<self
39
+
40
+ def run input
41
+ p = new
42
+ l = input.readline
43
+ loop do
44
+ if l.empty? then
45
+ break if p.closed?
46
+ l = input.readline
47
+ end
48
+ l.slice! RE
49
+ case
50
+ when $1 then
51
+ p.step_in
52
+ when $2 then
53
+ p.step_out
54
+ when $3 then
55
+ r = UTF7.decode $3
56
+ p.add r
57
+ when $4 then
58
+ n = $4[1,$4.length-2].to_i
59
+ r = ""
60
+ while n > 0 do
61
+ l = input.readline
62
+ l or raise "No more data after {#$4}"
63
+ m = l.length
64
+ if n <= m then
65
+ r << (l.slice! 0, n)
66
+ else
67
+ r << l << "\n"
68
+ l.clear
69
+ n -= 2
70
+ end
71
+ n -= m
72
+ end
73
+ p.add r
74
+ when $5 then
75
+ r = $5.nil_if "NIL"
76
+ r = UTF7.decode r if r
77
+ p.add r
78
+ else
79
+ raise "Error reading '#$''"
80
+ end
81
+ end
82
+ p
83
+ end
84
+
85
+ def compile input, compiler
86
+ p = run input
87
+ p.walk compiler
88
+ compiler.result
89
+ end
90
+
91
+ end
92
+
93
+ def initialize
94
+ @list = []
95
+ end
96
+
97
+ def step_in
98
+ if @sub then
99
+ @sub.step_in
100
+ else
101
+ @sub = self.class.new
102
+ end
103
+ end
104
+
105
+ def step_out
106
+ if @sub.closed? then
107
+ @list.push @sub
108
+ @sub = nil
109
+ else
110
+ @sub.step_out
111
+ end
112
+ end
113
+
114
+ def add token
115
+ if @sub then
116
+ @sub.add token
117
+ else
118
+ @list.push token
119
+ end
120
+ end
121
+
122
+ def closed?
123
+ not @sub
124
+ end
125
+
126
+ def walk compiler
127
+ closed? or raise "Object was not fully parsed. Rest: #@sub"
128
+ @list.each { |x|
129
+ case x
130
+ when self.class then compiler.step do x.walk compiler end
131
+ else compiler.add x
132
+ end
133
+ }
134
+ compiler.finish
135
+ end
136
+
137
+ end
138
+
139
+
140
+ class Data
141
+
142
+ class <<self
143
+ alias [] new
144
+ end
145
+
146
+ attr_reader :name, :params
147
+
148
+ def initialize name, *params
149
+ @name, @params = name, params
150
+ end
151
+
152
+ def stream_lines r, &block
153
+ r << " " << @name.to_s
154
+ add_to_stream r, @params, &block
155
+ yield [r]
156
+ end
157
+
158
+ private
159
+
160
+ # If you think this is too complicated, then complain to
161
+ # the designers of IMAP.
162
+ #
163
+ def add_to_stream r, ary, &block
164
+ ary.each { |a|
165
+ r << " " unless @opened
166
+ @opened = false
167
+ case a
168
+ when Array then
169
+ r << "("
170
+ @opened = true
171
+ add_to_stream r, a, &block
172
+ r << ")"
173
+ @opened = false
174
+ else
175
+ a = a.to_s
176
+ s = a.notempty? ? (a.split /\r?\n/, -1) : [""]
177
+ l = s.length - 1
178
+ if l > 0 then
179
+ m = 0
180
+ s.each { |e| m += e.length }
181
+ m += l*2
182
+ r << "{#{m}}"
183
+ _ = yield [r]
184
+ r.clear
185
+ yield s
186
+ else
187
+ r << (UTF7.encode a).to_s
188
+ end
189
+ end
190
+ }
191
+ end
192
+
193
+ public
194
+
195
+ class Compiler
196
+
197
+ def initialize
198
+ @list = []
199
+ end
200
+
201
+ def result
202
+ Data.new @name, *@list
203
+ end
204
+
205
+ def step
206
+ list_, @list = @list, []
207
+ sub_, @sub = @sub, true
208
+ yield
209
+ ensure
210
+ list_.push @list
211
+ @list, @sub = list_, sub_
212
+ end
213
+
214
+ def add x
215
+ if @sub then
216
+ @list.push x
217
+ else
218
+ if not @name then
219
+ is_name? x or raise "Not an item name: #{x}"
220
+ @name = x.to_sym
221
+ else
222
+ @list.push x
223
+ end
224
+ end
225
+ end
226
+
227
+ def finish
228
+ end
229
+
230
+ private
231
+
232
+ def is_name? x
233
+ x =~ /\A[A-Z]+\z/
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
240
+ end
241
+
242
+ end
243
+
244
+ end
245
+
@@ -0,0 +1,75 @@
1
+ #
2
+ # hermeneutics/cli/imap/utf7imap.rb -- IMAP's UTF-7
3
+ #
4
+
5
+ require "supplement"
6
+
7
+ module Hermeneutics
8
+
9
+ module Cli
10
+
11
+ module ImapTools
12
+
13
+ class UTF7
14
+
15
+ class <<self
16
+
17
+ def encode str
18
+ e = str.gsub /&|([^ -~]+)/ do
19
+ if $1 then
20
+ b64 = [($1.encode Encoding::UTF_16BE)].pack "m0"
21
+ b64.slice! %r/=+\z/
22
+ b64.tr! "/", ","
23
+ end
24
+ "&#{b64}-"
25
+ end
26
+ if e.empty? or e =~ %r/[ "]/ then
27
+ e = %Q["#{e.gsub /(["\\])/ do "\\#$1" end}"]
28
+ end
29
+ new e
30
+ end
31
+
32
+ def decode txt
33
+ (new txt).decode
34
+ end
35
+
36
+ end
37
+
38
+ def initialize txt
39
+ @txt = txt
40
+ end
41
+
42
+ def to_s ; @txt ; end
43
+
44
+ def decode
45
+ t = @txt
46
+ if t =~ /\A"(.*)"\z/ then
47
+ t = $1
48
+ t.gsub! /\\(.)/ do $1 end
49
+ end
50
+ t.gsub /&(.*?)-/ do
51
+ if $1.empty? then
52
+ "&"
53
+ else
54
+ r = $1
55
+ r.tr! ",", "/"
56
+ f = -r.length % 4
57
+ if f.nonzero? then
58
+ r << "=" * f
59
+ end
60
+ r, = r.unpack "m"
61
+ r.force_encoding Encoding::UTF_16BE
62
+ r.encode! Encoding.default_external
63
+ r
64
+ end
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+
@@ -0,0 +1,240 @@
1
+ #
2
+ # hermeneutics/cli/imap.rb -- IMAP client
3
+ #
4
+
5
+ require "hermeneutics/cli/protocol"
6
+ require "hermeneutics/cli/imap/commands"
7
+
8
+
9
+ module Hermeneutics
10
+
11
+ module Cli
12
+
13
+ class IMAP < Protocol
14
+
15
+ CRLF = true
16
+
17
+ PORT, PORT_SSL = 143, 993
18
+
19
+ class Error < StandardError ; end
20
+ class UnspecResponse < Error ; end
21
+ class ServerBye < Error ; end
22
+ class ServerError < Error ; end
23
+ class NotOk < 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 do |i|
30
+ yield i
31
+ i.stop_watch
32
+ end
33
+ end
34
+ end
35
+
36
+ TAG_PREFIX = "H"
37
+
38
+ def initialize *args
39
+ super
40
+ @tag = "H%04d" % 0
41
+ @info = [ get_response]
42
+ start_watch
43
+ end
44
+
45
+ attr_reader :info
46
+
47
+ def auths data = nil
48
+ a = []
49
+ (data||@info.first.data).params.each { |p|
50
+ p =~ /\AAUTH=/ and a.push $'.to_s
51
+ }
52
+ a
53
+ end
54
+
55
+ def command cmd, *args, &block
56
+ c = cmd.new *args
57
+ r = write_request c, &block
58
+ r.ok? or raise NotOk, r.text
59
+ c.responses
60
+ end
61
+
62
+
63
+ include ImapTools
64
+
65
+ alias readline! readline
66
+ private :readline!
67
+
68
+ def peekline
69
+ @peek ||= readline!
70
+ end
71
+
72
+ def readline
73
+ @peek or readline!
74
+ ensure
75
+ @peek = nil
76
+ end
77
+
78
+
79
+ def get_response_plain
80
+ Response.create @tag, self
81
+ end
82
+
83
+ def get_response
84
+ r = get_response_plain
85
+ r.bye? and raise ServerBye, "Server closed the connection"
86
+ r
87
+ end
88
+
89
+
90
+ def start_watch
91
+ @watch = Thread.new do
92
+ Thread.current.abort_on_exception = true
93
+ Thread.current.report_on_exception = false
94
+ while @socket.wait do
95
+ r = get_response
96
+ @info.push r
97
+ end
98
+ end
99
+ end
100
+
101
+ def stop_watch
102
+ @watch or return
103
+ @watch.kill if @watch.alive?
104
+ @watch.value
105
+ @watch = nil
106
+ end
107
+
108
+ def write_request cmd
109
+ stop_watch
110
+ @tag.succ!
111
+ r = nil
112
+ cmd.stream_lines "#@tag" do |a|
113
+ a.each { |l| writeline l.to_s }
114
+ r = get_response_plain
115
+ r.wait? or break
116
+ if block_given? then # does only make sense for the IDLE command
117
+ begin
118
+ start_watch
119
+ yield
120
+ ensure
121
+ stop_watch
122
+ end
123
+ end
124
+ r.text
125
+ end
126
+ until r.done? do
127
+ bye ||= r.bye?
128
+ cmd.add_response r
129
+ r = get_response_plain
130
+ r or raise ServerError, cmd.responses.last.to_s
131
+ end
132
+ bye or start_watch
133
+ r
134
+ end
135
+
136
+ end
137
+
138
+ module ImapTools
139
+
140
+ class Response
141
+ class <<self
142
+ private :new
143
+ def create tag, reader
144
+ p = reader.peekline
145
+ p and p.slice! /\A(\S+) +/ or return
146
+ case $1
147
+ when tag then ResponseFinish.create reader
148
+ when "+" then ResponseWait. create reader
149
+ when "*" then ResponseStatus.create reader or
150
+ ResponseData. create reader
151
+ else raise UnspecResponse, reader.readline
152
+ end
153
+ end
154
+ private
155
+ def compile_string str
156
+ r = StringReader.new str
157
+ compile_stream r
158
+ end
159
+ def compile_stream reader
160
+ c = Data::Compiler.new
161
+ Parser.compile reader, c
162
+ end
163
+ end
164
+ def done? ; false ; end
165
+ def wait? ; false ; end
166
+ def bye? ; false ; end
167
+ end
168
+
169
+ class ResponseWait < Response
170
+ class <<self
171
+ def create reader
172
+ new reader.readline
173
+ end
174
+ end
175
+ attr_reader :text
176
+ def initialize text ; @text = text ; end
177
+ def wait? ; true ; end
178
+ def to_s ; "#{text}" ; end
179
+ end
180
+
181
+ class ResponseData < Response
182
+ class <<self
183
+ def create reader
184
+ if reader.peekline.slice! /\A(\d+) +/ then
185
+ n = $1.to_i
186
+ end
187
+ data = compile_stream reader
188
+ new n, data
189
+ end
190
+ end
191
+ attr_reader :num, :data
192
+ def initialize num, data
193
+ @num, @data = num, data
194
+ end
195
+ def to_s ; "#{num} #{data}" ; end
196
+ end
197
+
198
+ class ResponseStatus < Response
199
+ class <<self
200
+ def create reader
201
+ l = compile_line reader
202
+ new *l if l
203
+ end
204
+ private
205
+ def compile_line reader
206
+ if reader.peekline.slice! /\A(OK|NO|BAD|BYE|PREAUTH) +/ then
207
+ status = $1.to_sym
208
+ if reader.peekline.slice! /\[(.*)\] +/ then
209
+ data = compile_string $1
210
+ end
211
+ [ status, data, reader.readline]
212
+ end
213
+ end
214
+ end
215
+ attr_reader :status, :data, :text
216
+ def initialize status, data, text
217
+ @status, @data, @text = status, data, text
218
+ end
219
+ def ok? ; @status == :OK ; end
220
+ def bye? ; @status == :BYE ; end
221
+ def to_s ; "#{status} #{data} #{text}" ; end
222
+ end
223
+
224
+ class ResponseFinish < ResponseStatus
225
+ class <<self
226
+ def create reader
227
+ l = compile_line reader
228
+ new *l if l
229
+ end
230
+ end
231
+ def done? ; true ; end
232
+ def to_s ; "finished" ; end
233
+ end
234
+
235
+ end
236
+
237
+ end
238
+
239
+ end
240
+
@@ -0,0 +1,11 @@
1
+ require "openssl"
2
+
3
+ module OpenSSL
4
+ module SSL
5
+ class SSLSocket
6
+ def wait timeout = :read ; @io.wait timeout ; end
7
+ def ready? ; @io.ready? ; end
8
+ end
9
+ end
10
+ end
11
+