winton-fetcher 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +94 -0
- data/Rakefile +30 -0
- data/fetcher.gemspec +27 -0
- data/gemspec.rb +17 -0
- data/init.rb +0 -0
- data/lib/fetcher.rb +22 -0
- data/lib/fetcher/base.rb +63 -0
- data/lib/fetcher/imap.rb +79 -0
- data/lib/fetcher/pop.rb +50 -0
- data/lib/vendor/plain_imap.rb +21 -0
- data/lib/vendor/secure_pop.rb +125 -0
- data/test/fetcher_test.rb +74 -0
- metadata +69 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Dan Weinand and Slantwise Design, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
= Fetcher
|
2
|
+
|
3
|
+
Fetcher is a simple message fetcher perfect for using in a daemon or via cron.
|
4
|
+
|
5
|
+
It implements the following common pattern:
|
6
|
+
|
7
|
+
1. Connect to a server
|
8
|
+
2. Download available messages
|
9
|
+
3. Send each message to another object for further processing
|
10
|
+
4. Remove downloaded messages from the remote server
|
11
|
+
|
12
|
+
Install using:
|
13
|
+
|
14
|
+
sudo gem install winton-fetcher
|
15
|
+
|
16
|
+
== Notice
|
17
|
+
|
18
|
+
This is a fork of {fetcher}[http://github.com/look/fetcher] by {Luke Francl}[http://github.com/look].
|
19
|
+
|
20
|
+
Differences:
|
21
|
+
|
22
|
+
* Gemified
|
23
|
+
* Doesn't have generators
|
24
|
+
* Fixes a connection exception issue
|
25
|
+
|
26
|
+
== Usage
|
27
|
+
|
28
|
+
Create a new fetcher object like the following:
|
29
|
+
|
30
|
+
@fetcher = Fetcher.create({:type => :pop,
|
31
|
+
:receiver => IncomingMailHandler,
|
32
|
+
:server => 'mail.example.com',
|
33
|
+
:username => 'jim',
|
34
|
+
:password => 'test'})
|
35
|
+
|
36
|
+
The receiver object is expected to have a receive method that takes a message as its only argument
|
37
|
+
(e.g., the way <tt>ActionMailer::Base.recieve</tt> works; but you don't <em>have</em> to use ActionMailer.).
|
38
|
+
|
39
|
+
Call <tt>fetch</tt> to download messages and process them.
|
40
|
+
|
41
|
+
@fetcher.fetch
|
42
|
+
|
43
|
+
== Configuration
|
44
|
+
|
45
|
+
The following options can be passed to the <tt>Fetcher.create</tt> factory method:
|
46
|
+
|
47
|
+
[<tt>type</tt>] POP or IMAP
|
48
|
+
[<tt>server</tt>] The IP address or domain name of the server
|
49
|
+
[<tt>port</tt>] The port to connect to (defaults to the standard port for the type of server)
|
50
|
+
[<tt>ssl</tt>] Set to any value to use SSL encryption
|
51
|
+
[<tt>username</tt>] The username used to connect to the server
|
52
|
+
[<tt>password</tt>] The password used to connect to the server
|
53
|
+
[<tt>authentication</tt>] The authentication scheme to use (IMAP only). Supports LOGIN, CRAM-MD5, and PASSWORD (defaults to PLAIN)
|
54
|
+
[<tt>use_login</tt>] Set to any value to use the LOGIN command instead of AUTHENTICATE. Some servers, like GMail, do not support AUTHENTICATE (IMAP only).
|
55
|
+
[<tt>sleep_time</tt>] The number of seconds to sleep between fetches (defaults to 60 seconds; valid only for the generated daemon)
|
56
|
+
[<tt>processed_folder</tt>] The name of a folder to move mail to after it has been processed (IMAP only). <b>NOTE:</b> If not specified, mail is deleted.
|
57
|
+
[<tt>error_folder</tt>] The name a folder to move mail that causes an error during processing (IMAP only). Defaults to +bogus+.
|
58
|
+
|
59
|
+
== Running via cron
|
60
|
+
|
61
|
+
You can also run the Fetcher periodically via cron. It is important to ensure that only one
|
62
|
+
instance is running at one time, and for that the {Lockfile gem}[http://codeforpeople.com/lib/ruby/lockfile/] is recommended.
|
63
|
+
|
64
|
+
Here is an example script to be with <tt>script/runner</tt> via cron:
|
65
|
+
|
66
|
+
begin
|
67
|
+
Lockfile.new('cron_mail_fetcher.lock', :retries => 0) do
|
68
|
+
config = YAML.load_file("#{RAILS_ROOT}/config/mail.yml")
|
69
|
+
config = config[RAILS_ENV].to_options
|
70
|
+
|
71
|
+
fetcher = Fetcher.create({:receiver => MailReceiver}.merge(config))
|
72
|
+
fetcher.fetch
|
73
|
+
end
|
74
|
+
rescue Lockfile::MaxTriesLockError => e
|
75
|
+
puts "Another fetcher is already running. Exiting."
|
76
|
+
end
|
77
|
+
|
78
|
+
== Extending
|
79
|
+
|
80
|
+
You can subclass <tt>Fetcher::Base</tt> or one of the protocol-specific classed to override the standard behavior.
|
81
|
+
|
82
|
+
== Further documentation
|
83
|
+
|
84
|
+
<shameless-plug>
|
85
|
+
|
86
|
+
You can read more about how to use the Fetcher in the PeepCode book {Receiving Email with Ruby}[https://peepcode.com/products/mms2r-pdf].
|
87
|
+
|
88
|
+
</shameless-plug>
|
89
|
+
|
90
|
+
== Credit & Copyright
|
91
|
+
|
92
|
+
Created by Dan Weinand and Luke Francl. Development supported by {Slantwise Design}[http://slantwisedesign.com].
|
93
|
+
|
94
|
+
Licensed under the terms of the MIT License. Be excellent to each other.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'gemspec'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc "Generate gemspec"
|
10
|
+
task :gemspec do
|
11
|
+
File.open("#{Dir.pwd}/#{GEM_NAME}.gemspec", 'w') do |f|
|
12
|
+
f.write(GEM_SPEC.to_ruby)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Test the fetcher plugin.'
|
17
|
+
Rake::TestTask.new(:test) do |t|
|
18
|
+
t.libs << 'lib'
|
19
|
+
t.pattern = 'test/**/*_test.rb'
|
20
|
+
t.verbose = true
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Generate documentation for the fetcher plugin.'
|
24
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
25
|
+
rdoc.rdoc_dir = 'rdoc'
|
26
|
+
rdoc.title = 'Fetcher'
|
27
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
28
|
+
rdoc.rdoc_files.include('README')
|
29
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
30
|
+
end
|
data/fetcher.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{fetcher}
|
5
|
+
s.version = "0.1.1"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Luke Francl"]
|
9
|
+
s.date = %q{2009-06-27}
|
10
|
+
s.email = %q{look@recursion.org}
|
11
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
12
|
+
s.files = ["fetcher.gemspec", "gemspec.rb", "init.rb", "lib", "lib/fetcher", "lib/fetcher/base.rb", "lib/fetcher/imap.rb", "lib/fetcher/pop.rb", "lib/fetcher.rb", "lib/vendor", "lib/vendor/plain_imap.rb", "lib/vendor/secure_pop.rb", "MIT-LICENSE", "Rakefile", "README.rdoc", "test", "test/fetcher_test.rb"]
|
13
|
+
s.homepage = %q{http://github.com/winton/fetcher}
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
s.rubygems_version = %q{1.3.1}
|
16
|
+
s.summary = %q{download email from POP3 or IMAP and do stuff with it. gemified fork}
|
17
|
+
|
18
|
+
if s.respond_to? :specification_version then
|
19
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
20
|
+
s.specification_version = 2
|
21
|
+
|
22
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
23
|
+
else
|
24
|
+
end
|
25
|
+
else
|
26
|
+
end
|
27
|
+
end
|
data/gemspec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
GEM_NAME = 'fetcher'
|
2
|
+
GEM_FILES = FileList['**/*'] - FileList['coverage', 'coverage/**/*', 'pkg', 'pkg/**/*']
|
3
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
4
|
+
# == CONFIGURE ==
|
5
|
+
s.author = "Luke Francl"
|
6
|
+
s.email = "look@recursion.org"
|
7
|
+
s.homepage = "http://github.com/winton/#{GEM_NAME}"
|
8
|
+
s.summary = "download email from POP3 or IMAP and do stuff with it. gemified fork"
|
9
|
+
# == CONFIGURE ==
|
10
|
+
s.extra_rdoc_files = [ "README.rdoc" ]
|
11
|
+
s.files = GEM_FILES.to_a
|
12
|
+
s.has_rdoc = false
|
13
|
+
s.name = GEM_NAME
|
14
|
+
s.platform = Gem::Platform::RUBY
|
15
|
+
s.require_path = "lib"
|
16
|
+
s.version = "0.1.1"
|
17
|
+
end
|
data/init.rb
ADDED
File without changes
|
data/lib/fetcher.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Fetcher
|
2
|
+
# Use factory-style initialization or insantiate directly from a subclass
|
3
|
+
#
|
4
|
+
# Options:
|
5
|
+
# * <tt>:type</tt> - Name of class as a symbol to instantiate
|
6
|
+
#
|
7
|
+
# Other options are the same as Fetcher::Base.new
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Fetcher.create(:type => :pop) is equivalent to
|
12
|
+
# Fetcher::Pop.new()
|
13
|
+
def self.create(options={})
|
14
|
+
klass = options.delete(:type)
|
15
|
+
raise ArgumentError, 'Must supply a type' unless klass
|
16
|
+
module_eval "#{klass.to_s.capitalize}.new(options)"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'fetcher/base'
|
21
|
+
require 'fetcher/pop'
|
22
|
+
require 'fetcher/imap'
|
data/lib/fetcher/base.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module Fetcher
|
2
|
+
class Base
|
3
|
+
# Options:
|
4
|
+
# * <tt>:server</tt> - Server to connect to.
|
5
|
+
# * <tt>:username</tt> - Username to use when connecting to server.
|
6
|
+
# * <tt>:password</tt> - Password to use when connecting to server.
|
7
|
+
# * <tt>:receiver</tt> - Receiver object to pass messages to. Assumes the
|
8
|
+
# receiver object has a receive method that takes a message as it's argument
|
9
|
+
#
|
10
|
+
# Additional protocol-specific options implimented by sub-classes
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# Fetcher::Base.new(:server => 'mail.example.com',
|
14
|
+
# :username => 'pam',
|
15
|
+
# :password => 'test',
|
16
|
+
# :receiver => IncomingMailHandler)
|
17
|
+
def initialize(options={})
|
18
|
+
%w(server username password receiver).each do |opt|
|
19
|
+
raise ArgumentError, "#{opt} is required" unless options[opt.to_sym]
|
20
|
+
# convert receiver to a Class if it isn't already.
|
21
|
+
if opt == "receiver" && options[:receiver].is_a?(String)
|
22
|
+
options[:receiver] = Kernel.const_get(options[:receiver])
|
23
|
+
end
|
24
|
+
|
25
|
+
instance_eval("@#{opt} = options[:#{opt}]")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Run the fetching process
|
30
|
+
def fetch
|
31
|
+
establish_connection
|
32
|
+
get_messages
|
33
|
+
close_connection
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
# Stub. Should be overridden by subclass.
|
39
|
+
def establish_connection #:nodoc:
|
40
|
+
raise NotImplementedError, "This method should be overridden by subclass"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Stub. Should be overridden by subclass.
|
44
|
+
def get_messages #:nodoc:
|
45
|
+
raise NotImplementedError, "This method should be overridden by subclass"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Stub. Should be overridden by subclass.
|
49
|
+
def close_connection #:nodoc:
|
50
|
+
raise NotImplementedError, "This method should be overridden by subclass"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Send message to receiver object
|
54
|
+
def process_message(message)
|
55
|
+
@receiver.receive(message)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Stub. Should be overridden by subclass.
|
59
|
+
def handle_bogus_message(message) #:nodoc:
|
60
|
+
raise NotImplementedError, "This method should be overridden by subclass"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/fetcher/imap.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../vendor/plain_imap'
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
class Imap < Base
|
5
|
+
|
6
|
+
PORT = 143
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
# Additional Options:
|
11
|
+
# * <tt>:authentication</tt> - authentication type to use, defaults to PLAIN
|
12
|
+
# * <tt>:port</tt> - port to use (defaults to 143)
|
13
|
+
# * <tt>:ssl</tt> - use SSL to connect
|
14
|
+
# * <tt>:use_login</tt> - use LOGIN instead of AUTHENTICATE to connect (some IMAP servers, like GMail, do not support AUTHENTICATE)
|
15
|
+
# * <tt>:processed_folder</tt> - if set to the name of a mailbox, messages will be moved to that mailbox instead of deleted after processing. The mailbox will be created if it does not exist.
|
16
|
+
# * <tt>:error_folder:</tt> - the name of a mailbox where messages that cannot be processed (i.e., your receiver throws an exception) will be moved. Defaults to "bogus". The mailbox will be created if it does not exist.
|
17
|
+
def initialize(options={})
|
18
|
+
@authentication = options.delete(:authentication) || 'PLAIN'
|
19
|
+
@port = options.delete(:port) || PORT
|
20
|
+
@ssl = options.delete(:ssl)
|
21
|
+
@use_login = options.delete(:use_login)
|
22
|
+
@processed_folder = options.delete(:processed_folder)
|
23
|
+
@error_folder = options.delete(:error_folder) || 'bogus'
|
24
|
+
super(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Open connection and login to server
|
28
|
+
def establish_connection
|
29
|
+
@connection = Net::IMAP.new(@server, @port, @ssl)
|
30
|
+
if @use_login
|
31
|
+
@connection.login(@username, @password)
|
32
|
+
else
|
33
|
+
@connection.authenticate(@authentication, @username, @password)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Retrieve messages from server
|
38
|
+
def get_messages
|
39
|
+
@connection.select('INBOX')
|
40
|
+
@connection.uid_search(['ALL']).each do |uid|
|
41
|
+
msg = @connection.uid_fetch(uid,'RFC822').first.attr['RFC822']
|
42
|
+
begin
|
43
|
+
process_message(msg)
|
44
|
+
add_to_processed_folder(uid) if @processed_folder
|
45
|
+
rescue
|
46
|
+
handle_bogus_message(msg)
|
47
|
+
end
|
48
|
+
# Mark message as deleted
|
49
|
+
@connection.uid_store(uid, "+FLAGS", [:Seen, :Deleted])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Store the message for inspection if the receiver errors
|
54
|
+
def handle_bogus_message(message)
|
55
|
+
create_mailbox(@error_folder)
|
56
|
+
@connection.append(@error_folder, message)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Delete messages and log out
|
60
|
+
def close_connection
|
61
|
+
@connection.expunge
|
62
|
+
@connection.logout
|
63
|
+
@connection.disconnect
|
64
|
+
rescue
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_to_processed_folder(uid)
|
68
|
+
create_mailbox(@processed_folder)
|
69
|
+
@connection.uid_copy(uid, @processed_folder)
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_mailbox(mailbox)
|
73
|
+
unless @connection.list("", mailbox)
|
74
|
+
@connection.create(mailbox)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
data/lib/fetcher/pop.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../vendor/secure_pop'
|
2
|
+
|
3
|
+
module Fetcher
|
4
|
+
class Pop < Base
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Additional Options:
|
9
|
+
# * <tt>:ssl</tt> - whether or not to use ssl encryption
|
10
|
+
# * <tt>:port</tt> - port to use (defaults to 110)
|
11
|
+
def initialize(options={})
|
12
|
+
@ssl = options.delete(:ssl)
|
13
|
+
@port = options.delete(:port) || Net::POP3.default_port
|
14
|
+
super(options)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Open connection and login to server
|
18
|
+
def establish_connection
|
19
|
+
@connection = Net::POP3.new(@server, @port)
|
20
|
+
@connection.enable_ssl(OpenSSL::SSL::VERIFY_NONE) if @ssl
|
21
|
+
@connection.start(@username, @password)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retrieve messages from server
|
25
|
+
def get_messages
|
26
|
+
unless @connection.mails.empty?
|
27
|
+
@connection.each_mail do |msg|
|
28
|
+
begin
|
29
|
+
process_message(msg.pop)
|
30
|
+
rescue
|
31
|
+
handle_bogus_message(msg.pop)
|
32
|
+
end
|
33
|
+
# Delete message from server
|
34
|
+
msg.delete
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Store the message for inspection if the receiver errors
|
40
|
+
def handle_bogus_message(message)
|
41
|
+
# This needs a good solution
|
42
|
+
end
|
43
|
+
|
44
|
+
# Close connection to server
|
45
|
+
def close_connection
|
46
|
+
@connection.finish
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'net/imap'
|
2
|
+
# add plain as an authentication type...
|
3
|
+
# This is taken from:
|
4
|
+
# http://svn.ruby-lang.org/cgi-bin/viewvc.cgi/trunk/lib/net/imap.rb?revision=7657&view=markup&pathrev=10966
|
5
|
+
|
6
|
+
# Authenticator for the "PLAIN" authentication type. See
|
7
|
+
# #authenticate().
|
8
|
+
class PlainAuthenticator
|
9
|
+
def process(data)
|
10
|
+
return "\0#{@user}\0#{@password}"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def initialize(user, password)
|
16
|
+
@user = user
|
17
|
+
@password = password
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Net::IMAP.add_authenticator "PLAIN", PlainAuthenticator
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'net/pop'
|
3
|
+
require 'net/protocol'
|
4
|
+
require 'openssl/ssl'
|
5
|
+
|
6
|
+
# Backport of ruby 1.9's POP3 SSL support
|
7
|
+
class Net::POP3
|
8
|
+
@@usessl = nil
|
9
|
+
@@verify = nil
|
10
|
+
@@certs = nil
|
11
|
+
PORT = 110
|
12
|
+
SSL_PORT = 995
|
13
|
+
|
14
|
+
def self.default_port
|
15
|
+
PORT
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.default_ssl_port
|
19
|
+
SSL_PORT
|
20
|
+
end
|
21
|
+
|
22
|
+
# Enable SSL for all new instances.
|
23
|
+
# +verify+ is the type of verification to do on the Server Cert; Defaults
|
24
|
+
# to OpenSSL::SSL::VERIFY_PEER.
|
25
|
+
# +certs+ is a file or directory holding CA certs to use to verify the
|
26
|
+
# server cert; Defaults to nil.
|
27
|
+
def self.enable_ssl( verify = OpenSSL::SSL::VERIFY_PEER, certs = nil )
|
28
|
+
@@usessl = true
|
29
|
+
@@verify = verify
|
30
|
+
@@certs = certs
|
31
|
+
end
|
32
|
+
|
33
|
+
# Disable SSL for all new instances.
|
34
|
+
def self.disable_ssl
|
35
|
+
@@usessl = nil
|
36
|
+
@@verify = nil
|
37
|
+
@@certs = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a new POP3 object.
|
41
|
+
# +addr+ is the hostname or ip address of your POP3 server.
|
42
|
+
# The optional +port+ is the port to connect to.
|
43
|
+
# The optional +isapop+ specifies whether this connection is going
|
44
|
+
# to use APOP authentication; it defaults to +false+.
|
45
|
+
# This method does *not* open the TCP connection.
|
46
|
+
def initialize(addr, port = nil, isapop = false)
|
47
|
+
@address = addr
|
48
|
+
@usessl = @@usessl
|
49
|
+
if @usessl
|
50
|
+
@port = port || SSL_PORT
|
51
|
+
else
|
52
|
+
@port = port || PORT
|
53
|
+
end
|
54
|
+
@apop = isapop
|
55
|
+
|
56
|
+
@certs = @@certs
|
57
|
+
@verify = @@verify
|
58
|
+
|
59
|
+
@command = nil
|
60
|
+
@socket = nil
|
61
|
+
@started = false
|
62
|
+
@open_timeout = 30
|
63
|
+
@read_timeout = 60
|
64
|
+
@debug_output = nil
|
65
|
+
|
66
|
+
@mails = nil
|
67
|
+
@n_mails = nil
|
68
|
+
@n_bytes = nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# does this instance use SSL?
|
72
|
+
def usessl?
|
73
|
+
@usessl
|
74
|
+
end
|
75
|
+
|
76
|
+
# Enables SSL for this instance. Must be called before the connection is
|
77
|
+
# established to have any effect.
|
78
|
+
# +verify+ is the type of verification to do on the Server Cert; Defaults
|
79
|
+
# to OpenSSL::SSL::VERIFY_PEER.
|
80
|
+
# +certs+ is a file or directory holding CA certs to use to verify the
|
81
|
+
# server cert; Defaults to nil.
|
82
|
+
# +port+ is port to establish the SSL conection on; Defaults to 995.
|
83
|
+
def enable_ssl(verify = OpenSSL::SSL::VERIFY_PEER, certs = nil,
|
84
|
+
port = SSL_PORT)
|
85
|
+
@usessl = true
|
86
|
+
@verify = verify
|
87
|
+
@certs = certs
|
88
|
+
@port = port
|
89
|
+
end
|
90
|
+
|
91
|
+
def disable_ssl
|
92
|
+
@usessl = nil
|
93
|
+
@verify = nil
|
94
|
+
@certs = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_start( account, password )
|
98
|
+
s = timeout(@open_timeout) { TCPSocket.open(@address, @port) }
|
99
|
+
if @usessl
|
100
|
+
unless defined?(OpenSSL)
|
101
|
+
raise "SSL extension not installed"
|
102
|
+
end
|
103
|
+
sslctx = OpenSSL::SSL::SSLContext.new
|
104
|
+
sslctx.verify_mode = @verify
|
105
|
+
sslctx.ca_file = @certs if @certs && FileTest::file?(@certs)
|
106
|
+
sslctx.ca_path = @certs if @certs && FileTest::directory?(@certs)
|
107
|
+
s = OpenSSL::SSL::SSLSocket.new(s, sslctx)
|
108
|
+
s.sync_close = true
|
109
|
+
s.connect
|
110
|
+
end
|
111
|
+
|
112
|
+
@socket = Net::InternetMessageIO.new(s)
|
113
|
+
on_connect
|
114
|
+
@command = Net::POP3Command.new(@socket)
|
115
|
+
if apop?
|
116
|
+
@command.apop account, password
|
117
|
+
else
|
118
|
+
@command.auth account, password
|
119
|
+
end
|
120
|
+
@started = true
|
121
|
+
ensure
|
122
|
+
do_finish if not @started
|
123
|
+
end
|
124
|
+
private :do_start
|
125
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# require File.dirname(__FILE__) + '/../../../../config/boot'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
require 'fetcher'
|
6
|
+
|
7
|
+
class FetcherTest < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@receiver = mock()
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_should_set_configuration_instance_variables
|
14
|
+
create_fetcher
|
15
|
+
assert_equal 'test.host', @fetcher.instance_variable_get(:@server)
|
16
|
+
assert_equal 'name', @fetcher.instance_variable_get(:@username)
|
17
|
+
assert_equal 'password', @fetcher.instance_variable_get(:@password)
|
18
|
+
assert_equal @receiver, @fetcher.instance_variable_get(:@receiver)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_should_require_subclass
|
22
|
+
create_fetcher
|
23
|
+
assert_raise(NotImplementedError) { @fetcher.fetch }
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_require_server
|
27
|
+
assert_raise(ArgumentError) { create_fetcher(:server => nil) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_should_require_username
|
31
|
+
assert_raise(ArgumentError) { create_fetcher(:username => nil) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_should_require_password
|
35
|
+
assert_raise(ArgumentError) { create_fetcher(:password => nil) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_should_require_receiver
|
39
|
+
assert_raise(ArgumentError) { create_fetcher(:receiver => nil) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_fetcher(options={})
|
43
|
+
@fetcher = Fetcher::Base.new({:server => 'test.host', :username => 'name', :password => 'password', :receiver => @receiver}.merge(options))
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
class FactoryFetcherTest < Test::Unit::TestCase
|
49
|
+
|
50
|
+
def setup
|
51
|
+
@receiver = mock()
|
52
|
+
@pop_fetcher = Fetcher.create(:type => :pop, :server => 'test.host',
|
53
|
+
:username => 'name',
|
54
|
+
:password => 'password',
|
55
|
+
:receiver => @receiver)
|
56
|
+
|
57
|
+
@imap_fetcher = Fetcher.create(:type => :imap, :server => 'test.host',
|
58
|
+
:username => 'name',
|
59
|
+
:password => 'password',
|
60
|
+
:receiver => @receiver)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_should_be_sublcass
|
64
|
+
assert_equal Fetcher::Pop, @pop_fetcher.class
|
65
|
+
assert_equal Fetcher::Imap, @imap_fetcher.class
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_require_type
|
69
|
+
assert_raise(ArgumentError) { Fetcher.create({}) }
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Write tests for sub-classes
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: winton-fetcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luke Francl
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-27 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: look@recursion.org
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- fetcher.gemspec
|
26
|
+
- gemspec.rb
|
27
|
+
- init.rb
|
28
|
+
- lib
|
29
|
+
- lib/fetcher
|
30
|
+
- lib/fetcher/base.rb
|
31
|
+
- lib/fetcher/imap.rb
|
32
|
+
- lib/fetcher/pop.rb
|
33
|
+
- lib/fetcher.rb
|
34
|
+
- lib/vendor
|
35
|
+
- lib/vendor/plain_imap.rb
|
36
|
+
- lib/vendor/secure_pop.rb
|
37
|
+
- MIT-LICENSE
|
38
|
+
- Rakefile
|
39
|
+
- README.rdoc
|
40
|
+
- test
|
41
|
+
- test/fetcher_test.rb
|
42
|
+
has_rdoc: false
|
43
|
+
homepage: http://github.com/winton/fetcher
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.2.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: download email from POP3 or IMAP and do stuff with it. gemified fork
|
68
|
+
test_files: []
|
69
|
+
|