migrate mailr 2.2.2 to 2.3.2
This commit is contained in:
parent
56039cc595
commit
ae789b1d84
23 changed files with 5 additions and 48 deletions
505
lib/webmail/imapmailbox.rb
Normal file
505
lib/webmail/imapmailbox.rb
Normal file
|
@ -0,0 +1,505 @@
|
|||
# Copyright (c) 2005, Benjamin Stiglitz
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# Modifications (c) 2005 by littlegreen
|
||||
#
|
||||
require 'net/imap'
|
||||
|
||||
Net::IMAP.debug = true if CDF::CONFIG[:debug_imap]
|
||||
|
||||
class Net::IMAP
|
||||
class PlainAuthenticator
|
||||
def process(data)
|
||||
return "\0#{@user}\0#{@password}"
|
||||
end
|
||||
|
||||
private
|
||||
def initialize(user, password)
|
||||
@user = user
|
||||
@password = password
|
||||
end
|
||||
end
|
||||
add_authenticator('PLAIN', PlainAuthenticator)
|
||||
|
||||
class Address
|
||||
def to_s
|
||||
if(name)
|
||||
"#{name} #{mailbox}@#{host}"
|
||||
else
|
||||
"#{mailbox}@#{host}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AuthenticationError < RuntimeError
|
||||
end
|
||||
|
||||
class IMAPMailbox
|
||||
attr_reader :connected
|
||||
attr_accessor :selected_mailbox
|
||||
cattr_accessor :logger
|
||||
|
||||
def initialize
|
||||
@selected_mailbox = ''
|
||||
@folders = {}
|
||||
@connected = false
|
||||
end
|
||||
|
||||
def connect(username, password)
|
||||
unless @connected
|
||||
use_ssl = CDF::CONFIG[:imap_use_ssl] ? true : false
|
||||
port = CDF::CONFIG[:imap_port] || (use_ssl ? 993 : 143)
|
||||
begin
|
||||
@imap = Net::IMAP.new(CDF::CONFIG[:imap_server], port, use_ssl)
|
||||
rescue Net::IMAP::ByeResponseError => bye
|
||||
# make a timeout and retry
|
||||
begin
|
||||
System.sleep(CDF::CONFIG[:imap_bye_timeout_retry_seconds])
|
||||
@imap = Net::IMAP.new(CDF::CONFIG[:imap_server], port, use_ssl)
|
||||
rescue Error => ex
|
||||
logger.error "Error on authentication!"
|
||||
logger.error bye.backtrace.join("\n")
|
||||
raise AuthenticationError.new
|
||||
end
|
||||
rescue Net::IMAP::NoResponseError => noresp
|
||||
logger.error "Error on authentication!"
|
||||
logger.error noresp.backtrace.join("\n")
|
||||
raise AuthenticationError.new
|
||||
rescue Net::IMAP::BadResponseError => bad
|
||||
logger.error "Error on authentication!"
|
||||
logger.error bad.backtrace.join("\n")
|
||||
raise AuthenticationError.new
|
||||
rescue Net::IMAP::ResponseError => resp
|
||||
logger.error "Error on authentication!"
|
||||
logger.error resp.backtrace.join("\n")
|
||||
raise AuthenticationError.new
|
||||
end
|
||||
@username = username
|
||||
begin
|
||||
logger.error "IMAP authentication - #{CDF::CONFIG[:imap_auth]}."
|
||||
if CDF::CONFIG[:imap_auth] == 'NOAUTH'
|
||||
@imap.login(username, password)
|
||||
else
|
||||
@imap.authenticate(CDF::CONFIG[:imap_auth], username, password)
|
||||
end
|
||||
@connected = true
|
||||
rescue Exception => ex
|
||||
logger.error "Error on authentication!"
|
||||
logger.error ex.backtrace.join("\n")
|
||||
raise AuthenticationError.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def imap
|
||||
@imap
|
||||
end
|
||||
|
||||
# Function chnage password works only if root has run imap_backend
|
||||
# and users courier-authlib utility authtest - from courier-imap version 4.0.1
|
||||
def change_password(username, password, new_password)
|
||||
ret = ""
|
||||
cin, cout, cerr = Open3.popen3("/usr/sbin/authtest #{username} #{password} #{new_password}")
|
||||
ret << cerr.gets
|
||||
if ret.include?("Password change succeeded.")
|
||||
return true
|
||||
else
|
||||
logger.error "[!] Error on change password! - #{ret}"
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def disconnect
|
||||
if @connected
|
||||
@imap.logout
|
||||
#@imap.disconnect
|
||||
@imap = nil
|
||||
@connected = false
|
||||
end
|
||||
end
|
||||
|
||||
def [](mailboxname)
|
||||
@last_folder = IMAPFolderList.new(self, @username)[mailboxname]
|
||||
end
|
||||
|
||||
def folders
|
||||
# reference just to stop GC
|
||||
@folder_list ||= IMAPFolderList.new(self, @username)
|
||||
@folder_list
|
||||
end
|
||||
|
||||
def reload
|
||||
@folder_list.reload if @folder_list
|
||||
end
|
||||
|
||||
def create_folder(name)
|
||||
# begin
|
||||
@imap.create(Net::IMAP.encode_utf7(name))
|
||||
reload
|
||||
# rescue Exception=>e
|
||||
# end
|
||||
end
|
||||
|
||||
def delete_folder(name)
|
||||
begin
|
||||
@imap.delete(folders[name].utf7_name)
|
||||
reload
|
||||
rescue Exception=>e
|
||||
logger.error("Exception on delete #{name} folder #{e}")
|
||||
end
|
||||
end
|
||||
|
||||
def message_sent(message)
|
||||
# ensure we have sent folder
|
||||
begin
|
||||
@imap.create(CDF::CONFIG[:mail_sent])
|
||||
rescue Exception=>e
|
||||
end
|
||||
begin
|
||||
@imap.append(CDF::CONFIG[:mail_sent], message)
|
||||
folders[CDF::CONFIG[:mail_sent]].cached = false if folders[CDF::CONFIG[:mail_sent]]
|
||||
rescue Exception=>e
|
||||
logger.error("Error on append - #{e}")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def message_bulk(message)
|
||||
# ensure we have sent folder
|
||||
begin
|
||||
@imap.create(CDF::CONFIG[:mail_bulk_sent])
|
||||
rescue Exception=>e
|
||||
end
|
||||
begin
|
||||
@imap.append(CDF::CONFIG[:mail_bulk_sent], message)
|
||||
folders[CDF::CONFIG[:mail_sent]].cached = false if folders[CDF::CONFIG[:mail_bulk_sent]]
|
||||
rescue Exception=>e
|
||||
logger.error("Error on bulk - #{e}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class IMAPFolderList
|
||||
include Enumerable
|
||||
cattr_accessor :logger
|
||||
|
||||
def initialize(mailbox, username)
|
||||
@mailbox = mailbox
|
||||
@folders = Hash.new
|
||||
@username = username
|
||||
end
|
||||
|
||||
def each
|
||||
refresh if @folders.empty?
|
||||
#@folders.each_value { |folder| yield folder }
|
||||
# We want to allow sorted access; for now only (FIXME)
|
||||
|
||||
@folders.sort.each { |pair| yield pair.last }
|
||||
end
|
||||
|
||||
def reload
|
||||
refresh
|
||||
end
|
||||
|
||||
def [](name)
|
||||
refresh if @folders.empty?
|
||||
@folders[name]
|
||||
end
|
||||
|
||||
private
|
||||
def refresh
|
||||
@folders = {}
|
||||
result = @mailbox.imap.list('', '*')
|
||||
if result
|
||||
result.each do |info|
|
||||
folder = IMAPFolder.new(@mailbox, info.name, @username, info.attr, info.delim)
|
||||
@folders[folder.name] = folder
|
||||
end
|
||||
else
|
||||
# if there are no folders subscribe to INBOX - this is on first use
|
||||
@mailbox.imap.subscribe(CDF::CONFIG[:mail_inbox])
|
||||
# try again to list them - we should find INBOX
|
||||
@mailbox.imap.list('', '*').each do |info|
|
||||
@folders[info.name] = IMAPFolder.new(@mailbox, info.name, @username, info.attr, info.delim)
|
||||
end
|
||||
end
|
||||
@folders
|
||||
end
|
||||
end
|
||||
|
||||
class IMAPFolder
|
||||
attr_reader :mailbox
|
||||
attr_reader :name
|
||||
attr_reader :utf7_name
|
||||
attr_reader :username
|
||||
attr_reader :delim
|
||||
attr_reader :attribs
|
||||
|
||||
attr_writer :cached
|
||||
attr_writer :mcached
|
||||
|
||||
cattr_accessor :logger
|
||||
|
||||
@@fetch_attr = ['ENVELOPE','BODYSTRUCTURE', 'FLAGS', 'UID', 'RFC822.SIZE']
|
||||
|
||||
def initialize(mailbox, utf7_name, username, attribs, delim)
|
||||
@mailbox = mailbox
|
||||
@utf7_name = utf7_name
|
||||
@name = Net::IMAP.decode_utf7 utf7_name
|
||||
@username = username
|
||||
@messages = Array.new
|
||||
@delim = delim
|
||||
@attribs = attribs
|
||||
@cached = false
|
||||
@mcached = false
|
||||
end
|
||||
|
||||
def activate
|
||||
if(@mailbox.selected_mailbox != @name)
|
||||
@mailbox.selected_mailbox = @name
|
||||
@mailbox.imap.select(@utf7_name)
|
||||
load_total_unseen if !@cached
|
||||
end
|
||||
end
|
||||
|
||||
# Just delete message without interaction with Trash folder
|
||||
def delete(message)
|
||||
activate
|
||||
uid = (message.kind_of?(Integer) ? message : message.uid)
|
||||
@mailbox.imap.uid_store(uid, "+FLAGS", :Deleted)
|
||||
@mailbox.imap.expunge
|
||||
# Sync with trash cannot be made - new uid generated - so just delete message from current folder
|
||||
ImapMessage.delete_all(["username = ? and folder_name = ? and uid = ?", @username, @name, uid])
|
||||
@cached = false
|
||||
end
|
||||
|
||||
# Deleted messages - move to trash folder
|
||||
def delete_multiple(uids)
|
||||
# ensure we have trash folder
|
||||
begin
|
||||
@mailbox.imap.create(CDF::CONFIG[:mail_trash])
|
||||
rescue
|
||||
end
|
||||
move_multiple(uids, CDF::CONFIG[:mail_trash])
|
||||
end
|
||||
|
||||
def copy(message, dst_folder)
|
||||
uid = (message.kind_of?(Integer) ? message : message.uid)
|
||||
activate
|
||||
@mailbox.imap.uid_copy(uid, dst_folder)
|
||||
@mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder]
|
||||
end
|
||||
|
||||
def copy_multiple(message_uids, dst_folder)
|
||||
activate
|
||||
@mailbox.imap.uid_copy(message_uids, dst_folder)
|
||||
@mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder]
|
||||
end
|
||||
|
||||
def move(message, dst_folder)
|
||||
uid = (message.kind_of?(Integer) ? message : message.uid)
|
||||
activate
|
||||
@mailbox.imap.uid_copy(uid, dst_folder)
|
||||
@mailbox.imap.uid_store(uid, "+FLAGS", :Deleted)
|
||||
@mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.imap.expunge
|
||||
ImapMessage.delete_all(["username = ? and folder_name = ? and uid = ? ", @username, @name, uid])
|
||||
@cached = false
|
||||
@mcached = false
|
||||
end
|
||||
|
||||
def move_multiple(message_uids, dst_folder)
|
||||
activate
|
||||
@mailbox.imap.uid_copy(message_uids, @mailbox.folders[dst_folder].utf7_name)
|
||||
@mailbox.imap.uid_store(message_uids, "+FLAGS", :Deleted)
|
||||
@mailbox.folders[dst_folder].cached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.folders[dst_folder].mcached = false if @mailbox.folders[dst_folder]
|
||||
@mailbox.imap.expunge
|
||||
ImapMessage.delete_all(["username = ? and folder_name = ? and uid in ( ? )", @username, @name, message_uids])
|
||||
@cached = false
|
||||
@mcached = false
|
||||
end
|
||||
|
||||
def mark_read(message_uid)
|
||||
activate
|
||||
cached = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, message_uid])
|
||||
if cached.unread
|
||||
cached.unread = false
|
||||
cached.save
|
||||
@mailbox.imap.select(@name)
|
||||
@mailbox.imap.uid_store(message_uid, "+FLAGS", :Seen)
|
||||
@unseen_messages = @unseen_messages - 1
|
||||
end
|
||||
end
|
||||
|
||||
def mark_unread(message_uid)
|
||||
activate
|
||||
cached = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, message_uid])
|
||||
if !cached.unread
|
||||
cached.unread = true
|
||||
cached.save
|
||||
@mailbox.imap.select(@name)
|
||||
@mailbox.imap.uid_store(message_uid, "-FLAGS", :Seen)
|
||||
@unseen_messages = @unseen_messages + 1
|
||||
end
|
||||
end
|
||||
|
||||
def expunge
|
||||
activate
|
||||
@mailbox.imap.expunge
|
||||
end
|
||||
|
||||
def synchronize_cache
|
||||
startSync = Time.now
|
||||
activate
|
||||
startUidFetch = Time.now
|
||||
server_messages = @mailbox.imap.uid_fetch(1..-1, ['UID', 'FLAGS'])
|
||||
|
||||
startDbFetch = Time.now
|
||||
cached_messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name])
|
||||
|
||||
cached_unread_uids = Array.new
|
||||
cached_read_uids = Array.new
|
||||
uids_to_be_deleted = Array.new
|
||||
|
||||
cached_messages.each { |msg|
|
||||
cached_unread_uids << msg.uid if msg.unread
|
||||
cached_read_uids << msg.uid unless msg.unread
|
||||
uids_to_be_deleted << msg.uid
|
||||
}
|
||||
|
||||
uids_to_be_fetched = Array.new
|
||||
server_msg_uids = Array.new
|
||||
|
||||
uids_unread = Array.new
|
||||
uids_read = Array.new
|
||||
|
||||
server_messages.each { |server_msg|
|
||||
uid, flags = server_msg.attr['UID'], server_msg.attr['FLAGS']
|
||||
server_msg_uids << uid
|
||||
unless uids_to_be_deleted.include?(uid)
|
||||
uids_to_be_fetched << uid
|
||||
else
|
||||
if flags.member?(:Seen) && cached_unread_uids.include?(uid)
|
||||
uids_read << uid
|
||||
elsif !flags.member?(:Seen) && cached_read_uids.include?(uid)
|
||||
uids_unread << uid
|
||||
end
|
||||
end
|
||||
uids_to_be_deleted.delete(uid)
|
||||
} unless server_messages.nil?
|
||||
|
||||
ImapMessage.delete_all(["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_to_be_deleted]) unless uids_to_be_deleted.empty?
|
||||
ImapMessage.update_all('unread = 0', ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_read]) unless uids_read.empty?
|
||||
ImapMessage.update_all('unread = 1', ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids_unread]) unless uids_unread.empty?
|
||||
|
||||
|
||||
# fetch and store not cached messages
|
||||
unless uids_to_be_fetched.empty?
|
||||
imapres = @mailbox.imap.uid_fetch(uids_to_be_fetched, @@fetch_attr)
|
||||
imapres.each { |cache|
|
||||
envelope = cache.attr['ENVELOPE'];
|
||||
message = ImapMessage.create( :folder_name => @name,
|
||||
:username => @username,
|
||||
:msg_id => envelope.message_id,
|
||||
:uid => cache.attr['UID'],
|
||||
:from_addr => envelope.from,
|
||||
:to_addr => envelope.to,
|
||||
:subject => envelope.subject,
|
||||
:content_type => cache.attr['BODYSTRUCTURE'].multipart? ? 'multipart' : 'text',
|
||||
:date => envelope.date,
|
||||
:unread => !(cache.attr['FLAGS'].member? :Seen),
|
||||
:size => cache.attr['RFC822.SIZE'])
|
||||
}
|
||||
end
|
||||
@mcached = true
|
||||
logger.debug("Synchonization done for folder #{@name} in #{Time.now - startSync} ms.")
|
||||
end
|
||||
|
||||
def messages(offset = 0, limit = 10, sort = 'date desc')
|
||||
# Synchronize first retrieval time
|
||||
synchronize_cache unless @mcached
|
||||
|
||||
if limit == -1
|
||||
@messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name], :order => sort)
|
||||
else
|
||||
@messages = ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ?", @username, @name], :order => sort, :limit => limit, :offset => offset )
|
||||
end
|
||||
end
|
||||
|
||||
def messages_search(query = ["ALL"], sort = 'date desc')
|
||||
activate
|
||||
uids = @mailbox.imap.uid_search(query)
|
||||
if uids.size > 1
|
||||
ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ? and uid in ( ? )", @username, @name, uids], :order => sort )
|
||||
elsif uids.size == 1
|
||||
ImapMessage.find(:all, :conditions => ["username = ? and folder_name = ? and uid = ? ", @username, @name, uids.first], :order => sort )
|
||||
else
|
||||
return Array.new
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def message(uid)
|
||||
activate
|
||||
message = ImapMessage.find(:first, :conditions => ["username = ? and folder_name = ? and uid = ?", @username, @name, uid])
|
||||
message.set_folder(self)
|
||||
message
|
||||
end
|
||||
|
||||
def unseen
|
||||
activate
|
||||
load_total_unseen if !@cached
|
||||
@unseen_messages
|
||||
end
|
||||
|
||||
def total
|
||||
activate
|
||||
load_total_unseen if !@cached
|
||||
@total_messages
|
||||
end
|
||||
|
||||
def load_total_unseen
|
||||
stat = @mailbox.imap.status(@utf7_name, ["MESSAGES", "UNSEEN"])
|
||||
@total_messages, @unseen_messages = stat["MESSAGES"], stat['UNSEEN']
|
||||
@cached = true
|
||||
end
|
||||
|
||||
def update_status
|
||||
@status ||= @mailbox.imap.status(@utf7_name, ["MESSAGES"])
|
||||
end
|
||||
|
||||
def subscribe
|
||||
@mailbox.imap.subscribe(@utf7_name)
|
||||
end
|
||||
|
||||
def trash?
|
||||
self.name == CDF::CONFIG[:mail_trash]
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue