imap_processor 1.0

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.
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ v��v����>�G]'M�����;K�Q��
2
+ ���qf�����씬�� �z�h�/�r�983u~(�J ���k�~u���x�3�ʩ�Ҳs�T�G3~�/_�0��#_���I뿓�2ŧ�����6���m���
data/.autotest ADDED
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
data/History.txt ADDED
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-05-12
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ .autotest
2
+ History.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ bin/imap_keywords
7
+ lib/imap_processor.rb
8
+ lib/imap_processor/keywords.rb
9
+ lib/imap_sasl_plain.rb
data/README.txt ADDED
@@ -0,0 +1,55 @@
1
+ = imap_processor
2
+
3
+ * http://seattlerb.rubyforge.org/imap_processor
4
+
5
+ == DESCRIPTION:
6
+
7
+ IMAPProcessor is a client for processing messages on an IMAP server. It
8
+ provides some basic mechanisms for connecting to an IMAP server, determining
9
+ capabilities and handling messages.
10
+
11
+ IMAPProcessor ships with the imap_keywords executable which can query an IMAP
12
+ server for keywords set on messages in mailboxes.
13
+
14
+ == FEATURES/PROBLEMS:
15
+
16
+ * Connection toolkit
17
+ * Executable toolkit
18
+ * Only known to work with SASL/PLAIN authentication
19
+
20
+ == SYNOPSIS:
21
+
22
+ See IMAPProcessor and IMAPProcessor::Keywords for details
23
+
24
+ == REQUIREMENTS:
25
+
26
+ * IMAP server
27
+
28
+ == INSTALL:
29
+
30
+ * gem install imap_processor
31
+
32
+ == LICENSE:
33
+
34
+ (The MIT License)
35
+
36
+ Copyright (c) 2009 Eric Hodel
37
+
38
+ Permission is hereby granted, free of charge, to any person obtaining
39
+ a copy of this software and associated documentation files (the
40
+ 'Software'), to deal in the Software without restriction, including
41
+ without limitation the rights to use, copy, modify, merge, publish,
42
+ distribute, sublicense, and/or sell copies of the Software, and to
43
+ permit persons to whom the Software is furnished to do so, subject to
44
+ the following conditions:
45
+
46
+ The above copyright notice and this permission notice shall be
47
+ included in all copies or substantial portions of the Software.
48
+
49
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
50
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
51
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
52
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
53
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
54
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
55
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ $:.unshift 'lib'
6
+ require 'imap_processor'
7
+
8
+ Hoe.new 'imap_processor', IMAPProcessor::VERSION do |ip|
9
+ ip.rubyforge_name = 'seattlerb'
10
+ ip.developer('Eric Hodel', 'drbrain@segment7.net')
11
+ end
12
+
13
+ # vim: syntax=Ruby
data/bin/imap_keywords ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'imap_processor/keywords'
4
+
5
+ IMAPProcessor::Keywords.run ARGV
6
+
@@ -0,0 +1,442 @@
1
+ require 'rubygems'
2
+ require 'optparse'
3
+ require 'net/imap'
4
+ require 'imap_sasl_plain'
5
+
6
+ ##
7
+ # IMAPProcessor is a client for processing messages on an IMAP server.
8
+ #
9
+ # Subclasses need to provide:
10
+ #
11
+ # * A process_args class method that adds any extra options to the default
12
+ # IMAPProcessor options.
13
+ # * An initialize method that connects to an IMAP server and sets the @imap
14
+ # instance variable
15
+ # * A run method that uses the IMAP connection to process messages.
16
+
17
+ class IMAPProcessor
18
+
19
+ ##
20
+ # The version of IMAPProcessor you are using
21
+
22
+ VERSION = '1.0'
23
+
24
+ ##
25
+ # A Connection Struct that has +imap+ and +capability+ accessors
26
+
27
+ class Connection < Struct.new :imap, :capability
28
+
29
+ ##
30
+ # Does this connection support the IDLE extension?
31
+
32
+ def idle?
33
+ capability.include? 'IDLE'
34
+ end
35
+
36
+ ##
37
+ # Does this connection support the UIDPLUS extension?
38
+
39
+ def uidplus?
40
+ capability.include? 'UIDPLUS'
41
+ end
42
+
43
+ end
44
+
45
+ ##
46
+ # Net::IMAP connection, set this via #initialize
47
+
48
+ attr_reader :imap
49
+
50
+ ##
51
+ # Options Hash from process_args
52
+
53
+ attr_reader :options
54
+
55
+ @@options = {}
56
+ @@extra_options = []
57
+
58
+ ##
59
+ # Adds a --move option to the option parser which stores the destination
60
+ # mailbox in the MoveTo option. Call this from a subclass' process_args
61
+ # method.
62
+
63
+ def self.add_move
64
+ @@options[:MoveTo] = nil
65
+
66
+ @@extra_options << proc do |opts, options|
67
+ opts.on( "--move=MAILBOX",
68
+ "Mailbox to move message to",
69
+ "Default: #{options[:MoveTo].inspect}",
70
+ "Options file name: MoveTo") do |mailbox|
71
+ options[:MoveTo] = mailbox
72
+ end
73
+ end
74
+ end
75
+
76
+ ##
77
+ # Handles processing of +args+ loading defaults from a file in ~ based on
78
+ # +processor_file+. Extra option defaults can be specified by
79
+ # +required_options+. Yields an option parser instance to add new
80
+ # OptionParser options to:
81
+ #
82
+ # class MyProcessor < IMAPProcessor
83
+ # def self.process_args(args)
84
+ # required_options = {
85
+ # :MoveTo => [nil, "MoveTo not set"],
86
+ # }
87
+ #
88
+ # super __FILE__, args, required_options do |opts, options|
89
+ # opts.banner << "Explain my_processor's executable"
90
+ #
91
+ # opts.on( "--move=MAILBOX",
92
+ # "Mailbox to move message to",
93
+ # "Default: #{options[:MoveTo].inspect}",
94
+ # "Options file name: MoveTo") do |mailbox|
95
+ # options[:MoveTo] = mailbox
96
+ # end
97
+ # end
98
+ # end
99
+
100
+ def self.process_args(processor_file, args,
101
+ required_options = {}) # :yield: OptionParser
102
+ opts_file_name = File.basename processor_file, '.rb'
103
+ opts_file = File.expand_path "~/.#{opts_file_name}"
104
+ options = @@options.dup
105
+
106
+ if required_options then
107
+ required_options.each do |option, (default, message)|
108
+ raise ArgumentError,
109
+ "required_options message is missing for #{option}" if
110
+ default.nil? and message.nil?
111
+ end
112
+ end
113
+
114
+ if File.exist? opts_file then
115
+ unless File.stat(opts_file).mode & 077 == 0 then
116
+ $stderr.puts "WARNING! #{opts_file} is group/other readable or writable!"
117
+ $stderr.puts "WARNING! I'm not doing a thing until you fix it!"
118
+ exit 1
119
+ end
120
+
121
+ options.merge! YAML.load_file(opts_file)
122
+ end
123
+
124
+ options[:SSL] ||= true
125
+ options[:Username] ||= ENV['USER']
126
+ options[:Root] ||= nil
127
+ options[:Verbose] ||= false
128
+ options[:Debug] ||= false
129
+
130
+ required_options.each do |k,(v,m)|
131
+ options[k] ||= v
132
+ end
133
+
134
+ opts = OptionParser.new do |opts|
135
+ opts.program_name = File.basename $0
136
+ opts.banner = "Usage: #{opts.program_name} [options]\n\n"
137
+
138
+ opts.separator ''
139
+ opts.separator 'Connection options:'
140
+
141
+ opts.on("-H", "--host HOST",
142
+ "IMAP server host",
143
+ "Default: #{options[:Host].inspect}",
144
+ "Options file name: Host") do |host|
145
+ options[:Host] = host
146
+ end
147
+
148
+ opts.on("-P", "--port PORT",
149
+ "IMAP server port",
150
+ "Default: The correct port SSL/non-SSL mode",
151
+ "Options file name: Port") do |port|
152
+ options[:Port] = port
153
+ end
154
+
155
+ opts.on("-s", "--[no-]ssl",
156
+ "Use SSL for IMAP connection",
157
+ "Default: #{options[:SSL].inspect}",
158
+ "Options file name: SSL") do |ssl|
159
+ options[:SSL] = ssl
160
+ end
161
+
162
+ opts.on( "--[no-]debug",
163
+ "Display Net::IMAP debugging info",
164
+ "Default: #{options[:Debug].inspect}",
165
+ "Options file name: Debug") do |debug|
166
+ options[:Debug] = debug
167
+ end
168
+
169
+ opts.separator ''
170
+ opts.separator 'Login options:'
171
+
172
+ opts.on("-u", "--username USERNAME",
173
+ "IMAP username",
174
+ "Default: #{options[:Username].inspect}",
175
+ "Options file name: Username") do |username|
176
+ options[:Username] = username
177
+ end
178
+
179
+ opts.on("-p", "--password PASSWORD",
180
+ "IMAP password",
181
+ "Default: Read from ~/.imap_cleanse",
182
+ "Options file name: Password") do |password|
183
+ options[:Password] = password
184
+ end
185
+
186
+ authenticators = Net::IMAP.send :class_variable_get, :@@authenticators
187
+ auth_types = authenticators.keys.sort.join ', '
188
+ opts.on("-a", "--auth AUTH", auth_types,
189
+ "IMAP authentication type override",
190
+ "Authentication type will be auto-",
191
+ "discovered",
192
+ "Default: #{options[:Auth].inspect}",
193
+ "Options file name: Auth") do |auth|
194
+ options[:Auth] = auth
195
+ end
196
+
197
+ opts.separator ''
198
+ opts.separator "IMAP options:"
199
+
200
+ opts.on("-r", "--root ROOT",
201
+ "Root of mailbox hierarchy",
202
+ "Default: #{options[:Root].inspect}",
203
+ "Options file name: Root") do |root|
204
+ options[:Root] = root
205
+ end
206
+
207
+ opts.on("-b", "--boxes BOXES", Array,
208
+ "Comma-separated list of mailbox names",
209
+ "to search",
210
+ "Default: #{options[:Boxes].inspect}",
211
+ "Options file name: Boxes") do |boxes|
212
+ options[:Boxes] = boxes
213
+ end
214
+
215
+ opts.on("-v", "--[no-]verbose",
216
+ "Be verbose",
217
+ "Default: #{options[:Verbose].inspect}",
218
+ "Options file name: Verbose") do |verbose|
219
+ options[:Verbose] = verbose
220
+ end
221
+
222
+ opts.on("-q", "--quiet",
223
+ "Be quiet") do
224
+ options[:Verbose] = false
225
+ end
226
+
227
+ if block_given? then
228
+ opts.separator ''
229
+ opts.separator "#{self} options:"
230
+
231
+ yield opts, options if block_given?
232
+ end
233
+
234
+ @@extra_options.each do |block|
235
+ block.call opts, options
236
+ end
237
+
238
+ opts.separator ''
239
+
240
+ opts.banner << <<-EOF
241
+
242
+ Options may also be set in the options file ~/.#{opts_file_name}
243
+
244
+ Example ~/.#{opts_file_name}:
245
+ \tHost=mail.example.com
246
+ \tPassword=my password
247
+
248
+ EOF
249
+ end
250
+
251
+ opts.parse! args
252
+
253
+ options[:Port] ||= options[:SSL] ? 993 : 143
254
+
255
+ if options[:Host].nil? or
256
+ options[:Password].nil? or
257
+ options[:Boxes].nil? or
258
+ required_options.any? { |k,(v,m)| options[k].nil? } then
259
+ $stderr.puts opts
260
+ $stderr.puts
261
+ $stderr.puts "Host name not set" if options[:Host].nil?
262
+ $stderr.puts "Password not set" if options[:Password].nil?
263
+ $stderr.puts "Boxes not set" if options[:Boxes].nil?
264
+ required_options.each do |option_name, (option_value, missing_message)|
265
+ $stderr.puts missing_message if options[option_name].nil?
266
+ end
267
+ exit 1
268
+ end
269
+
270
+ return options
271
+ end
272
+
273
+ ##
274
+ # Sets up an IMAP processor's options then calls its \#run method.
275
+
276
+ def self.run(args = ARGV, &block)
277
+ options = process_args args
278
+ client = new(options, &block)
279
+ client.run
280
+ rescue SystemExit
281
+ raise
282
+ rescue Exception => e
283
+ $stderr.puts "Failed to finish with exception: #{e.class}:#{e.message}"
284
+ $stderr.puts "\t#{e.backtrace.join "\n\t"}"
285
+
286
+ exit 1
287
+ ensure
288
+ client.imap.logout if client
289
+ end
290
+
291
+ ##
292
+ # Handles the basic settings from +options+ including verbosity, mailboxes
293
+ # to process, and Net::IMAP::debug
294
+
295
+ def initialize(options)
296
+ @options = options
297
+ @verbose = true
298
+ @boxes = options[:Boxes]
299
+ Net::IMAP.debug = options[:Debug]
300
+ end
301
+
302
+ ##
303
+ # Connects to IMAP server +host+ at +port+ using ssl if +ssl+ is true then
304
+ # logs in as +username+ with +password+. IMAPProcessor is only known to
305
+ # work with PLAIN auth on SSL sockets.
306
+ #
307
+ # Returns a Connection object.
308
+
309
+ def connect(host, port, ssl, username, password, auth = nil)
310
+ imap = Net::IMAP.new host, port, ssl
311
+ log "Connected to imap://#{host}:#{port}/"
312
+
313
+ capability = imap.capability
314
+
315
+ log "Capabilities: #{capability.join ', '}"
316
+
317
+ auth_caps = capability.select { |c| c =~ /^AUTH/ }
318
+
319
+ if auth.nil? then
320
+ raise "Couldn't find a supported auth type" if auth_caps.empty?
321
+ auth = auth_caps.first.sub(/AUTH=/, '')
322
+ end
323
+
324
+ auth = auth.upcase
325
+ log "Trying #{auth} authentication"
326
+ imap.authenticate auth, username, password
327
+ log "Logged in as #{username}"
328
+
329
+ Connection.new imap, capability
330
+ end
331
+
332
+ ##
333
+ # Yields each uid and message as a TMail::Message for +uids+ of MIME type
334
+ # +type+.
335
+ #
336
+ # If there's an exception raised during handling a message the subject,
337
+ # message-id and inspected body are logged.
338
+ #
339
+ # Returns the uids of successfully handled messages.
340
+
341
+ def each_message(uids, type) # :yields: TMail::Mail
342
+ parts = mime_parts uids, type
343
+
344
+ uids = []
345
+
346
+ each_part parts, true do |uid, message|
347
+ mail = TMail::Mail.parse message
348
+
349
+ begin
350
+ yield uid, mail
351
+
352
+ uids << uid
353
+ rescue => e
354
+ log e.message
355
+ puts "\t#{e.backtrace.join "\n\t"}" unless $DEBUG # backtrace at bottom
356
+ log "Subject: #{mail.subject}"
357
+ log "Message-Id: #{mail.message_id}"
358
+ p mail.body if verbose?
359
+
360
+ raise if $DEBUG
361
+ end
362
+ end
363
+
364
+ uids
365
+ end
366
+
367
+ ##
368
+ # Yields each message part from +parts+. If +header+ is true, a complete
369
+ # message is yielded, appropriately joined for use with TMail::Mail.
370
+
371
+ def each_part(parts, header = false) # :yields: uid, message
372
+ parts.each do |uid, section|
373
+ sequence = ["BODY[#{section}]"]
374
+ sequence.unshift "BODY[#{section}.MIME]" unless section == 'TEXT'
375
+ sequence.unshift 'BODY[HEADER]' if header
376
+
377
+ body = @imap.fetch(uid, sequence).first
378
+
379
+ sequence = sequence.map { |item| body.attr[item] }
380
+
381
+ unless section == 'TEXT' and header then
382
+ sequence[0].sub!(/\r\n\z/, '')
383
+ end
384
+
385
+ yield uid, sequence.join
386
+ end
387
+ end
388
+
389
+ ##
390
+ # Logs +message+ to $stderr if verbose
391
+
392
+ def log(message)
393
+ return unless @verbose
394
+ $stderr.puts "# #{message}"
395
+ end
396
+
397
+ ##
398
+ # Retrieves the BODY data item name for the +mime_type+ part from messages
399
+ # +uids+. Returns an array of uid/part pairs. If no matching part with
400
+ # +mime_type+ is found the uid is omitted.
401
+ #
402
+ # Returns an Array of uid, section pairs.
403
+ #
404
+ # Use a subsequent Net::IMAP#fetch to retrieve the selected part.
405
+
406
+ def mime_parts(uids, mime_type)
407
+ media_type, subtype = mime_type.upcase.split('/', 2)
408
+
409
+ structures = @imap.fetch uids, 'BODYSTRUCTURE'
410
+
411
+ structures.zip(uids).map do |body, uid|
412
+ section = nil
413
+ structure = body.attr['BODYSTRUCTURE']
414
+
415
+ case structure
416
+ when Net::IMAP::BodyTypeMultipart then
417
+ parts = structure.parts
418
+
419
+ section = parts.each_with_index do |part, index|
420
+ break index if part.media_type == media_type and
421
+ part.subtype == subtype
422
+ end
423
+
424
+ next unless Integer === section
425
+ when Net::IMAP::BodyTypeText, Net::IMAP::BodyTypeBasic then
426
+ section = 'TEXT' if structure.media_type == media_type and
427
+ structure.subtype == subtype
428
+ end
429
+
430
+ [uid, section]
431
+ end.compact
432
+ end
433
+
434
+ ##
435
+ # Did the user set --verbose?
436
+
437
+ def verbose?
438
+ @verbose
439
+ end
440
+
441
+ end
442
+
@@ -0,0 +1,158 @@
1
+ require 'imap_processor'
2
+
3
+ ##
4
+ # Lists keywords present on a server
5
+
6
+ class IMAPProcessor::Keywords < IMAPProcessor
7
+
8
+ def self.process_args(args)
9
+ required_options = {
10
+ :List => true
11
+ }
12
+
13
+ super __FILE__, args, required_options do |opts, options|
14
+ opts.banner << <<-EOF
15
+ imap_keywords lists keywords on an IMAP server and allows you to delete
16
+ previously set keywords.
17
+ EOF
18
+
19
+ # opts.on( "--add",
20
+ # "Add keyword(s) to all messages") do |add|
21
+ # options[:Add] = add
22
+ # end
23
+
24
+ opts.on( "--delete",
25
+ "Delete keyword(s) from all messages") do |delete|
26
+ options[:Delete] = delete
27
+ end
28
+
29
+ opts.on( "--keywords=KEYWORDS", Array,
30
+ "Select messages with keyword(s),",
31
+ "which will be ANDed") do |keywords|
32
+ options[:Keywords] = keywords
33
+ end
34
+
35
+ opts.on( "--[no-]list",
36
+ "Don't display messages") do |list|
37
+ options[:List] = list
38
+ end
39
+
40
+ opts.on( "--not",
41
+ "Select messages without --keywords") do
42
+ options[:Not] = true
43
+ end
44
+ end
45
+ end
46
+
47
+ def initialize(options)
48
+ super
49
+
50
+ @add = options[:Add]
51
+ @delete = options[:Delete]
52
+ @keywords = options[:Keywords]
53
+ @not = options[:Not] ? 'NOT' : nil
54
+ @list = options[:List]
55
+
56
+ if @add and @delete then
57
+ raise OptionParser::InvalidOption, "--add and --delete are exclusive"
58
+ elsif @keywords.nil? and (@add or @delete) then
59
+ raise OptionParser::InvalidOption,
60
+ "--add and --delete require --keywords"
61
+ end
62
+
63
+ connection = connect options[:Host], options[:Port], options[:SSL],
64
+ options[:Username], options[:Password], options[:Auth]
65
+
66
+ @imap = connection.imap
67
+ end
68
+
69
+ ##
70
+ # Turns +flags+ into a format usable by the IMAP server
71
+
72
+ def flags_to_literal(flags)
73
+ flags.flatten.map do |flag|
74
+ case flag
75
+ when Symbol then "\\#{flag}"
76
+ else flag
77
+ end
78
+ end
79
+ end
80
+
81
+ ##
82
+ # Makes a SEARCH argument set from +keywords+
83
+
84
+ def make_search(keywords)
85
+ if keywords then
86
+ keywords.map do |kw|
87
+ case kw
88
+ when '\Answered', '\Deleted', '\Draft', '\Flagged',
89
+ '\Recent', '\Seen' then
90
+ [@not, kw[1..-1].upcase]
91
+ else
92
+ [@not, 'KEYWORD', kw]
93
+ end
94
+ end.flatten.compact
95
+ else
96
+ %w[ALL]
97
+ end
98
+ end
99
+
100
+ def run
101
+ @boxes.each do |mailbox|
102
+ response = @imap.select mailbox
103
+ log "Selected mailbox #{mailbox}"
104
+ puts "Previously set flags:"
105
+ puts flags_to_literal(@imap.responses['FLAGS']).join(' ')
106
+ puts
107
+
108
+ puts "Permanent flags:"
109
+ puts flags_to_literal(@imap.responses['PERMANENTFLAGS']).join(' ')
110
+ puts
111
+
112
+ search = make_search @keywords
113
+
114
+ log "SEARCH #{search.join ' '}"
115
+ uids = @imap.search search
116
+
117
+ if uids.empty? then
118
+ puts "No messages"
119
+ next
120
+ else
121
+ puts "#{uids.length} messages in #{mailbox}#{@list ? ':' : ''}"
122
+ end
123
+
124
+ @imap.store uids, '-FLAGS.SILENT', @keywords if @delete
125
+
126
+ show_messages uids
127
+ end
128
+ end
129
+
130
+ ##
131
+ # Displays messages in +uids+ and their keywords
132
+
133
+ def show_messages(uids)
134
+ return unless @list
135
+
136
+ responses = @imap.fetch uids, [
137
+ 'BODY.PEEK[HEADER]',
138
+ 'FLAGS'
139
+ ]
140
+
141
+ responses.each do |res|
142
+ header = res.attr['BODY[HEADER]']
143
+
144
+ header =~ /^Subject: (.*)/i
145
+ puts "Subject: #{$1}"
146
+
147
+ header =~ /^Message-Id: (.*)/i
148
+ puts "Message-Id: #{$1}"
149
+
150
+ flags = res.attr['FLAGS'].map { |flag| flag.inspect }.join ', '
151
+
152
+ puts "Flags: #{flags}"
153
+ puts
154
+ end
155
+ end
156
+
157
+ end
158
+
@@ -0,0 +1,65 @@
1
+ require 'time'
2
+ require 'net/imap'
3
+
4
+ class Time
5
+
6
+ ##
7
+ # Formats this Time as an IMAP-style date.
8
+
9
+ def imapdate
10
+ strftime '%d-%b-%Y'
11
+ end
12
+
13
+ ##
14
+ # Formats this Time as an IMAP-style datetime.
15
+ #
16
+ # RFC 2060 doesn't specify the format of its times. Unfortunately it is
17
+ # almost but not quite RFC 822 compliant.
18
+ #--
19
+ # Go Mr. Leatherpants!
20
+
21
+ def imapdatetime
22
+ strftime '%d-%b-%Y %H:%M %Z'
23
+ end
24
+
25
+ end
26
+
27
+ ##
28
+ # RFC 2595 PLAIN Authenticator for Net::IMAP. Only for use with SSL (but not
29
+ # enforced).
30
+
31
+ class Net::IMAP::PlainAuthenticator
32
+
33
+ ##
34
+ # From RFC 2595 Section 6. PLAIN SASL Authentication
35
+ #
36
+ # The mechanism consists of a single message from the client to the
37
+ # server. The client sends the authorization identity (identity to
38
+ # login as), followed by a US-ASCII NUL character, followed by the
39
+ # authentication identity (identity whose password will be used),
40
+ # followed by a US-ASCII NUL character, followed by the clear-text
41
+ # password. The client may leave the authorization identity empty to
42
+ # indicate that it is the same as the authentication identity.
43
+
44
+ def process(data)
45
+ return [@user, @user, @password].join("\0")
46
+ end
47
+
48
+ private
49
+
50
+ ##
51
+ # Creates a new PlainAuthenticator that will authenticate with +user+ and
52
+ # +password+.
53
+
54
+ def initialize(user, password)
55
+ @user = user
56
+ @password = password
57
+ end
58
+
59
+ end
60
+
61
+ if defined? OpenSSL then
62
+ Net::IMAP.add_authenticator 'PLAIN', Net::IMAP::PlainAuthenticator
63
+ end
64
+
65
+
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: imap_processor
3
+ version: !ruby/object:Gem::Version
4
+ version: "1.0"
5
+ platform: ruby
6
+ authors:
7
+ - Eric Hodel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDNjCCAh6gAwIBAgIBADANBgkqhkiG9w0BAQUFADBBMRAwDgYDVQQDDAdkcmJy
14
+ YWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZFgNu
15
+ ZXQwHhcNMDcxMjIxMDIwNDE0WhcNMDgxMjIwMDIwNDE0WjBBMRAwDgYDVQQDDAdk
16
+ cmJyYWluMRgwFgYKCZImiZPyLGQBGRYIc2VnbWVudDcxEzARBgoJkiaJk/IsZAEZ
17
+ FgNuZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbbgLrGLGIDE76
18
+ LV/cvxdEzCuYuS3oG9PrSZnuDweySUfdp/so0cDq+j8bqy6OzZSw07gdjwFMSd6J
19
+ U5ddZCVywn5nnAQ+Ui7jMW54CYt5/H6f2US6U0hQOjJR6cpfiymgxGdfyTiVcvTm
20
+ Gj/okWrQl0NjYOYBpDi+9PPmaH2RmLJu0dB/NylsDnW5j6yN1BEI8MfJRR+HRKZY
21
+ mUtgzBwF1V4KIZQ8EuL6I/nHVu07i6IkrpAgxpXUfdJQJi0oZAqXurAV3yTxkFwd
22
+ g62YrrW26mDe+pZBzR6bpLE+PmXCzz7UxUq3AE0gPHbiMXie3EFE0oxnsU3lIduh
23
+ sCANiQ8BAgMBAAGjOTA3MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
24
+ BBS5k4Z75VSpdM0AclG2UvzFA/VW5DANBgkqhkiG9w0BAQUFAAOCAQEAHagT4lfX
25
+ kP/hDaiwGct7XPuVGbrOsKRVD59FF5kETBxEc9UQ1clKWngf8JoVuEoKD774dW19
26
+ bU0GOVWO+J6FMmT/Cp7nuFJ79egMf/gy4gfUfQMuvfcr6DvZUPIs9P/TlK59iMYF
27
+ DIOQ3DxdF3rMzztNUCizN4taVscEsjCcgW6WkUJnGdqlu3OHWpQxZBJkBTjPCoc6
28
+ UW6on70SFPmAy/5Cq0OJNGEWBfgD9q7rrs/X8GGwUWqXb85RXnUVi/P8Up75E0ag
29
+ 14jEc90kN+C7oI/AGCBN0j6JnEtYIEJZibjjDJTSMWlUKKkj30kq7hlUC2CepJ4v
30
+ x52qPcexcYZR7w==
31
+ -----END CERTIFICATE-----
32
+
33
+ date: 2009-05-12 00:00:00 -07:00
34
+ default_executable:
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: hoe
38
+ type: :development
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 1.12.1
45
+ version:
46
+ description: |-
47
+ IMAPProcessor is a client for processing messages on an IMAP server. It
48
+ provides some basic mechanisms for connecting to an IMAP server, determining
49
+ capabilities and handling messages.
50
+
51
+ IMAPProcessor ships with the imap_keywords executable which can query an IMAP
52
+ server for keywords set on messages in mailboxes.
53
+ email:
54
+ - drbrain@segment7.net
55
+ executables:
56
+ - imap_keywords
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - History.txt
61
+ - Manifest.txt
62
+ - README.txt
63
+ files:
64
+ - .autotest
65
+ - History.txt
66
+ - Manifest.txt
67
+ - README.txt
68
+ - Rakefile
69
+ - bin/imap_keywords
70
+ - lib/imap_processor.rb
71
+ - lib/imap_processor/keywords.rb
72
+ - lib/imap_sasl_plain.rb
73
+ has_rdoc: true
74
+ homepage: http://seattlerb.rubyforge.org/imap_processor
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options:
79
+ - --main
80
+ - README.txt
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: "0"
88
+ version:
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ requirements: []
96
+
97
+ rubyforge_project: seattlerb
98
+ rubygems_version: 1.3.3
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: IMAPProcessor is a client for processing messages on an IMAP server
102
+ test_files: []
103
+
metadata.gz.sig ADDED
@@ -0,0 +1 @@
1
+ <���X�:�+A���@(TM}��v�<Qj���������ԭ\�ֈ�,�}�<�U�@���~bR��z�&2��C�.MF�aZ1��=|f�R�[�b�O�����yO��mv�KU�νwFfP�+>$�Jf&�R��w������J6Cj��D)B�Y�Z��