aiwilliams-mlist 0.0.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/README +63 -0
- data/Rakefile +27 -0
- data/VERSION.yml +4 -0
- data/lib/mlist.rb +13 -0
- data/lib/mlist/email_server.rb +3 -0
- data/lib/mlist/email_server/base.rb +22 -0
- data/lib/mlist/email_server/email.rb +47 -0
- data/lib/mlist/email_server/fake.rb +16 -0
- data/lib/mlist/list.rb +49 -0
- data/lib/mlist/mail_list.rb +77 -0
- data/lib/mlist/manager/database.rb +34 -0
- data/lib/mlist/message.rb +107 -0
- data/lib/mlist/server.rb +36 -0
- data/lib/mlist/thread.rb +6 -0
- data/lib/mlist/util.rb +9 -0
- data/lib/mlist/util/header_sanitizer.rb +63 -0
- data/lib/mlist/util/quoting.rb +70 -0
- metadata +73 -0
data/README
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= mlist
|
2
|
+
|
3
|
+
http://aiwilliams.github.com/mlist
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
An insane attempt to build a mail list server library that can be used
|
8
|
+
other applications very easily. The first target is Ruby applications
|
9
|
+
that can load MList models for direct integration. It will later have
|
10
|
+
a RESTful API so that non-Ruby applications can easily integrate. That
|
11
|
+
may depend heavily on community involvement...
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
There is a LOT to do: segmenting, spam filtering, HTML conversion, i18n,
|
16
|
+
backscatter - only the Mailman developers know what else. I have enough
|
17
|
+
experience to know that rewrites are NEVER as easy as they seem. I begin
|
18
|
+
this with fear and trepidation. Alas, I go boldly forward.
|
19
|
+
|
20
|
+
== SYNOPSIS:
|
21
|
+
|
22
|
+
Let's say you want your web application to have a mailing list feature.
|
23
|
+
Let's also say you care about the UI, and you don't want to learn all
|
24
|
+
about creating the correct HTML structures for a mailing list. You want to
|
25
|
+
have lots of power for searching the mail, and you have your own strategy
|
26
|
+
for managing the lists. You love Ruby.
|
27
|
+
|
28
|
+
== REQUIREMENTS:
|
29
|
+
|
30
|
+
You'll need some gems.
|
31
|
+
|
32
|
+
* activesupport
|
33
|
+
* activerecord
|
34
|
+
* tmail
|
35
|
+
|
36
|
+
== INSTALL:
|
37
|
+
|
38
|
+
* FIX (sudo gem install, anything else)
|
39
|
+
|
40
|
+
== LICENSE:
|
41
|
+
|
42
|
+
(The MIT License)
|
43
|
+
|
44
|
+
Copyright (c) 2008 Adam Williams (aiwilliams)
|
45
|
+
|
46
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
47
|
+
a copy of this software and associated documentation files (the
|
48
|
+
'Software'), to deal in the Software without restriction, including
|
49
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
50
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
51
|
+
permit persons to whom the Software is furnished to do so, subject to
|
52
|
+
the following conditions:
|
53
|
+
|
54
|
+
The above copyright notice and this permission notice shall be
|
55
|
+
included in all copies or substantial portions of the Software.
|
56
|
+
|
57
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
58
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
59
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
60
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
61
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
62
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
63
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run all specs"
|
9
|
+
Spec::Rake::SpecTask.new do |t|
|
10
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
11
|
+
t.spec_opts = ['--options', 'spec/spec.opts']
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |s|
|
17
|
+
s.name = 'mlist'
|
18
|
+
s.summary = 'A Ruby mailing list library designed to be integrated into other applications.'
|
19
|
+
s.email = 'adam@thewilliams.ws'
|
20
|
+
s.files = FileList["[A-Z]*", "{lib,rails}/**/*"].exclude("tmp,**/tmp")
|
21
|
+
s.homepage = "http://github.com/aiwilliams/mlist"
|
22
|
+
s.description = s.summary
|
23
|
+
s.authors = ['Adam Williams']
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
27
|
+
end
|
data/VERSION.yml
ADDED
data/lib/mlist.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module MList
|
2
|
+
module EmailServer
|
3
|
+
class Base
|
4
|
+
def initialize
|
5
|
+
@receivers = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def deliver(email)
|
9
|
+
raise 'Implement actual delivery mechanism in subclasses'
|
10
|
+
end
|
11
|
+
|
12
|
+
def receive(tmail)
|
13
|
+
email = EmailServer::Email.new(tmail)
|
14
|
+
@receivers.each { |r| r.receive(email) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def receiver(rx)
|
18
|
+
@receivers << rx
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MList
|
2
|
+
module EmailServer
|
3
|
+
|
4
|
+
# The interface to an incoming email.
|
5
|
+
#
|
6
|
+
# My primary goal is to decouple the MList::EmailServer from the
|
7
|
+
# MList::Server, this class acting as the bridge.
|
8
|
+
#
|
9
|
+
class Email
|
10
|
+
|
11
|
+
# TODO Provide the email_server to the instances
|
12
|
+
def initialize(tmail)
|
13
|
+
@tmail = tmail
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_address
|
17
|
+
@tmail.from.first
|
18
|
+
end
|
19
|
+
|
20
|
+
# Answers the usable destination addresses of the email.
|
21
|
+
#
|
22
|
+
# TODO: Provide intelligence to this that allows it to ignore addresses
|
23
|
+
# that are not for the domain of the email_server.
|
24
|
+
#
|
25
|
+
def list_addresses
|
26
|
+
bounce? ? @tmail.header_string('to').match(/\Amlist-(.*)\Z/)[1] : @tmail.to
|
27
|
+
end
|
28
|
+
|
29
|
+
# Answers true if this email is a bounce.
|
30
|
+
#
|
31
|
+
# TODO Delegate to the email_server's bounce detector.
|
32
|
+
#
|
33
|
+
def bounce?
|
34
|
+
@tmail.header_string('to') =~ /mlist-/
|
35
|
+
end
|
36
|
+
|
37
|
+
# Answers unique copies of the underlying TMail::Mail instance,
|
38
|
+
# providing assurance that the MList::Server and it's sub-systems don't
|
39
|
+
# stomp all over each other by getting a reference to a single
|
40
|
+
# TMail::Mail instance.
|
41
|
+
#
|
42
|
+
def tmail
|
43
|
+
TMail::Mail.parse(@tmail.to_s)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/mlist/list.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module MList
|
2
|
+
|
3
|
+
# Represents the interface of the lists that a list manager must answer.
|
4
|
+
# This is distinct from the MList::MailList to allow for greater flexibility
|
5
|
+
# in processing email coming to a list - that is, whatever you include this
|
6
|
+
# into may re-define behavior appropriately.
|
7
|
+
#
|
8
|
+
module List
|
9
|
+
def bounce(email)
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
def host
|
14
|
+
address.match(/@(.*)\Z/)[1]
|
15
|
+
end
|
16
|
+
|
17
|
+
def list_headers
|
18
|
+
{
|
19
|
+
'list-id' => list_id,
|
20
|
+
'list-archive' => (archive_url rescue nil),
|
21
|
+
'list-subscribe' => (subscribe_url rescue nil),
|
22
|
+
'list-unsubscribe' => (unsubscribe_url rescue nil),
|
23
|
+
'list-owner' => (owner_url rescue nil),
|
24
|
+
'list-help' => (help_url rescue nil),
|
25
|
+
'list-post' => post_url
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def list_id
|
30
|
+
"#{label} <#{address}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def name
|
34
|
+
address.match(/\A(.*?)@/)[1]
|
35
|
+
end
|
36
|
+
|
37
|
+
def post_url
|
38
|
+
address
|
39
|
+
end
|
40
|
+
|
41
|
+
def recipients(message)
|
42
|
+
subscriptions.collect(&:address) - [message.from_address]
|
43
|
+
end
|
44
|
+
|
45
|
+
def subscriber?(address)
|
46
|
+
!subscriptions.detect {|s| s.address == address}.nil?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module MList
|
2
|
+
class MailList < ActiveRecord::Base
|
3
|
+
def self.find_or_create_by_list(list)
|
4
|
+
mail_list = find_or_create_by_identifier(list.list_id)
|
5
|
+
mail_list.manager_list = list
|
6
|
+
mail_list
|
7
|
+
end
|
8
|
+
|
9
|
+
has_many :messages, :dependent => :delete_all
|
10
|
+
has_many :threads, :dependent => :delete_all
|
11
|
+
|
12
|
+
attr_accessor :manager_list
|
13
|
+
delegate :address, :recipients, :subscriptions,
|
14
|
+
:to => :manager_list
|
15
|
+
|
16
|
+
def post(email_server, message)
|
17
|
+
return unless process?(message)
|
18
|
+
prepare_delivery(message)
|
19
|
+
deliver(message, email_server)
|
20
|
+
end
|
21
|
+
|
22
|
+
def been_there?(message)
|
23
|
+
message.header_string('x-beenthere') == address
|
24
|
+
end
|
25
|
+
|
26
|
+
# http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
|
27
|
+
def bounce_headers
|
28
|
+
{'sender' => "mlist-#{address}",
|
29
|
+
'errors-to' => "mlist-#{address}"}
|
30
|
+
end
|
31
|
+
|
32
|
+
def deliver(message, email_server)
|
33
|
+
transaction do
|
34
|
+
email_server.deliver(message.tmail)
|
35
|
+
thread = find_thread(message)
|
36
|
+
thread.messages << message
|
37
|
+
thread.save!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_thread(message)
|
42
|
+
if message.reply?
|
43
|
+
threads.find(:first,
|
44
|
+
:joins => :messages,
|
45
|
+
:readonly => false,
|
46
|
+
:conditions => ['messages.identifier = ?', message.parent_identifier]
|
47
|
+
)
|
48
|
+
else
|
49
|
+
threads.build
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# http://www.jamesshuggins.com/h/web1/list-email-headers.htm
|
54
|
+
def list_headers
|
55
|
+
headers = manager_list.list_headers
|
56
|
+
headers['x-beenthere'] = address
|
57
|
+
headers.update(bounce_headers)
|
58
|
+
headers.delete_if {|k,v| v.nil?}
|
59
|
+
end
|
60
|
+
|
61
|
+
def prepare_delivery(message)
|
62
|
+
prepare_list_headers(message)
|
63
|
+
message.to = address
|
64
|
+
message.bcc = recipients(message)
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepare_list_headers(message)
|
68
|
+
list_headers.each do |k,v|
|
69
|
+
message.write_header(k,v)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process?(message)
|
74
|
+
!been_there?(message) && !recipients(message).blank?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MList
|
2
|
+
module Manager
|
3
|
+
|
4
|
+
class Database
|
5
|
+
def create_list(address, attributes = {})
|
6
|
+
attributes = {
|
7
|
+
:address => address,
|
8
|
+
:label => address.match(/\A(.*?)@/)[1]
|
9
|
+
}.merge(attributes)
|
10
|
+
List.create!(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def lists(email)
|
14
|
+
lists = List.find_all_by_address(email.list_addresses)
|
15
|
+
email.list_addresses.map { |a| lists.detect {|l| l.address == a} }
|
16
|
+
end
|
17
|
+
|
18
|
+
class List < ActiveRecord::Base
|
19
|
+
include ::MList::List
|
20
|
+
|
21
|
+
has_many :subscriptions, :dependent => :delete_all
|
22
|
+
|
23
|
+
def subscribe(address)
|
24
|
+
subscriptions.find_or_create_by_address(address)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Subscription < ActiveRecord::Base
|
29
|
+
belongs_to :list
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module MList
|
2
|
+
|
3
|
+
# The persisted version of an email that is processed by MList::MailLists.
|
4
|
+
#
|
5
|
+
# The tmail object referenced by these are unique, though they may reference
|
6
|
+
# the 'same' originating email.
|
7
|
+
#
|
8
|
+
class Message < ActiveRecord::Base
|
9
|
+
belongs_to :mail_list
|
10
|
+
|
11
|
+
attr_writer :header_sanitizers
|
12
|
+
before_save :serialize_tmail
|
13
|
+
|
14
|
+
def charset
|
15
|
+
'utf-8'
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete_header(name)
|
19
|
+
tmail[name] = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_address
|
23
|
+
tmail.from.first
|
24
|
+
end
|
25
|
+
|
26
|
+
def parent_identifier
|
27
|
+
if in_reply_to = header_string('in-reply-to')
|
28
|
+
identifier = in_reply_to
|
29
|
+
elsif references = read_header('references')
|
30
|
+
identifier = references.ids.first
|
31
|
+
else
|
32
|
+
parent_message = mail_list.messages.find(:first,
|
33
|
+
:conditions => ['messages.subject = ?', remove_regard(subject)],
|
34
|
+
:order => 'created_at asc'
|
35
|
+
)
|
36
|
+
identifier = parent_message.identifier if parent_message
|
37
|
+
end
|
38
|
+
remove_brackets(identifier) if identifier
|
39
|
+
end
|
40
|
+
|
41
|
+
def read_header(name)
|
42
|
+
tmail[name]
|
43
|
+
end
|
44
|
+
|
45
|
+
def reply?
|
46
|
+
!parent_identifier.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_header(name, value)
|
50
|
+
tmail[name] = sanitize_header(name, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def tmail=(tmail)
|
54
|
+
write_attribute(:identifier, remove_brackets(tmail.header_string('message-id')))
|
55
|
+
write_attribute(:subject, tmail.subject)
|
56
|
+
@tmail = tmail
|
57
|
+
end
|
58
|
+
|
59
|
+
def tmail
|
60
|
+
@tmail ||= TMail::Mail.parse(email_text)
|
61
|
+
end
|
62
|
+
|
63
|
+
def to=(recipients)
|
64
|
+
tmail.to = sanitize_header('to', recipients)
|
65
|
+
end
|
66
|
+
|
67
|
+
def bcc=(recipients)
|
68
|
+
tmail.bcc = sanitize_header('bcc', recipients)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Provide delegation to *most* of the underlying TMail::Mail methods,
|
72
|
+
# excluding those overridden by this class and the [] and []= methods. We
|
73
|
+
# must maintain the ActiveRecord interface over that of the TMail::Mail
|
74
|
+
# interface.
|
75
|
+
#
|
76
|
+
def method_missing(symbol, *args, &block) # :nodoc:
|
77
|
+
if @tmail && @tmail.respond_to?(symbol) && !(symbol == :[] || symbol == :[]=)
|
78
|
+
@tmail.__send__(symbol, *args, &block)
|
79
|
+
else
|
80
|
+
super
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def sanitize_header(name, *values)
|
85
|
+
header_sanitizer(name).call(charset, *values)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def header_sanitizer(name)
|
90
|
+
@header_sanitizers ||= Util.default_header_sanitizers
|
91
|
+
@header_sanitizers[name]
|
92
|
+
end
|
93
|
+
|
94
|
+
def remove_brackets(string)
|
95
|
+
string =~ /\A<(.*?)>\Z/ ? $1 : string
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_regard(string)
|
99
|
+
stripped = string.strip
|
100
|
+
stripped =~ /\Are:\s+(.*?)\Z/i ? $1 : stripped
|
101
|
+
end
|
102
|
+
|
103
|
+
def serialize_tmail
|
104
|
+
write_attribute(:email_text, @tmail.to_s)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/lib/mlist/server.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module MList
|
2
|
+
class Server
|
3
|
+
attr_reader :list_manager, :email_server
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@list_manager = config[:list_manager]
|
7
|
+
@email_server = config[:email_server]
|
8
|
+
@email_server.receiver(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def receive(email)
|
12
|
+
lists = list_manager.lists(email)
|
13
|
+
if email.bounce?
|
14
|
+
process_bounce(lists.first, email)
|
15
|
+
else
|
16
|
+
process_post(lists, email)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def process_bounce(list, email)
|
22
|
+
list.bounce(email)
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_post(lists, email)
|
26
|
+
lists.each do |list|
|
27
|
+
if list.subscriber?(email.from_address)
|
28
|
+
mail_list = MailList.find_or_create_by_list(list)
|
29
|
+
mail_list.post(email_server, MList::Message.new(:mail_list => mail_list, :tmail => email.tmail))
|
30
|
+
else
|
31
|
+
list.non_subscriber_posted(email)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/mlist/thread.rb
ADDED
data/lib/mlist/util.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
module MList
|
2
|
+
module Util
|
3
|
+
|
4
|
+
class QuotingSanitizer
|
5
|
+
include Quoting
|
6
|
+
|
7
|
+
def initialize(method, bracket_urls)
|
8
|
+
@method, @bracket_urls = method, bracket_urls
|
9
|
+
end
|
10
|
+
|
11
|
+
def bracket_urls(values)
|
12
|
+
values.map do |value|
|
13
|
+
if value.include?('<') && value.include?('>')
|
14
|
+
value
|
15
|
+
else
|
16
|
+
"<#{value}>"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(charset, *values)
|
22
|
+
values = bracket_urls(values.flatten) if @bracket_urls
|
23
|
+
send(@method, charset, *values)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class HeaderSanitizerHash
|
28
|
+
def initialize
|
29
|
+
@hash = Hash.new
|
30
|
+
initialize_default_sanitizers
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize_default_sanitizers
|
34
|
+
self['to'] = quoter(:quote_any_address_if_necessary)
|
35
|
+
self['cc'] = quoter(:quote_any_address_if_necessary)
|
36
|
+
self['bcc'] = quoter(:quote_any_address_if_necessary)
|
37
|
+
self['from'] = quoter(:quote_any_address_if_necessary)
|
38
|
+
self['reply-to'] = quoter(:quote_any_address_if_necessary)
|
39
|
+
self['subject'] = quoter(:quote_any_if_necessary)
|
40
|
+
|
41
|
+
self['List-Help'] = quoter(:quote_address_if_necessary)
|
42
|
+
self['List-Subscribe'] = quoter(:quote_address_if_necessary)
|
43
|
+
self['List-Unsubscribe'] = quoter(:quote_address_if_necessary)
|
44
|
+
self['List-Post'] = quoter(:quote_address_if_necessary)
|
45
|
+
self['List-Owner'] = quoter(:quote_address_if_necessary)
|
46
|
+
self['List-Archive'] = quoter(:quote_address_if_necessary)
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](key)
|
50
|
+
@hash[key.downcase] ||= lambda { |charset, value| value }
|
51
|
+
end
|
52
|
+
|
53
|
+
def []=(key, value)
|
54
|
+
@hash[key.downcase] = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def quoter(method, bracket_urls = true)
|
58
|
+
QuotingSanitizer.new(method, bracket_urls)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module MList
|
2
|
+
module Util
|
3
|
+
|
4
|
+
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
5
|
+
#
|
6
|
+
# Taken from ActionMailer. Modified to make charset first argument in all
|
7
|
+
# signatures, allowing for a consistent pattern of invocation.
|
8
|
+
#
|
9
|
+
module Quoting #:nodoc:
|
10
|
+
# Convert the given text into quoted printable format, with an instruction
|
11
|
+
# that the text be eventually interpreted in the given charset.
|
12
|
+
def quoted_printable(charset, text)
|
13
|
+
text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
|
14
|
+
gsub( / /, "_" )
|
15
|
+
"=?#{charset}?Q?#{text}?="
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convert the given character to quoted printable format, taking into
|
19
|
+
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
20
|
+
def quoted_printable_encode(character)
|
21
|
+
result = ""
|
22
|
+
character.each_byte { |b| result << "=%02x" % b }
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
# A quick-and-dirty regexp for determining whether a string contains any
|
27
|
+
# characters that need escaping.
|
28
|
+
if !defined?(CHARS_NEEDING_QUOTING)
|
29
|
+
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
|
30
|
+
end
|
31
|
+
|
32
|
+
# Quote the given text if it contains any "illegal" characters
|
33
|
+
def quote_if_necessary(charset, text)
|
34
|
+
text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)
|
35
|
+
|
36
|
+
(text =~ CHARS_NEEDING_QUOTING) ?
|
37
|
+
quoted_printable(charset, text) :
|
38
|
+
text
|
39
|
+
end
|
40
|
+
|
41
|
+
# Quote any of the given strings if they contain any "illegal" characters
|
42
|
+
def quote_any_if_necessary(charset, *args)
|
43
|
+
args.map { |v| quote_if_necessary(charset, v) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Quote the given address if it needs to be. The address may be a
|
47
|
+
# regular email address, or it can be a phrase followed by an address in
|
48
|
+
# brackets. The phrase is the only part that will be quoted, and only if
|
49
|
+
# it needs to be. This allows extended characters to be used in the
|
50
|
+
# "to", "from", "cc", "bcc" and "reply-to" headers.
|
51
|
+
def quote_address_if_necessary(charset, address)
|
52
|
+
if Array === address
|
53
|
+
address.map { |a| quote_address_if_necessary(charset, a) }
|
54
|
+
elsif address =~ /^(\S.*)\s+(<.*>)$/
|
55
|
+
address = $2
|
56
|
+
phrase = quote_if_necessary(charset, $1.gsub(/^['"](.*)['"]$/, '\1'))
|
57
|
+
"\"#{phrase}\" #{address}"
|
58
|
+
else
|
59
|
+
address
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Quote any of the given addresses, if they need to be.
|
64
|
+
def quote_any_address_if_necessary(charset, *args)
|
65
|
+
args.map { |v| quote_address_if_necessary(charset, v) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aiwilliams-mlist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-01-26 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A Ruby mailing list library designed to be integrated into other applications.
|
17
|
+
email: adam@thewilliams.ws
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- README
|
27
|
+
- VERSION.yml
|
28
|
+
- lib/mlist
|
29
|
+
- lib/mlist/email_server
|
30
|
+
- lib/mlist/email_server/base.rb
|
31
|
+
- lib/mlist/email_server/email.rb
|
32
|
+
- lib/mlist/email_server/fake.rb
|
33
|
+
- lib/mlist/email_server.rb
|
34
|
+
- lib/mlist/list.rb
|
35
|
+
- lib/mlist/mail_list.rb
|
36
|
+
- lib/mlist/manager
|
37
|
+
- lib/mlist/manager/database.rb
|
38
|
+
- lib/mlist/message.rb
|
39
|
+
- lib/mlist/server.rb
|
40
|
+
- lib/mlist/thread.rb
|
41
|
+
- lib/mlist/util
|
42
|
+
- lib/mlist/util/header_sanitizer.rb
|
43
|
+
- lib/mlist/util/quoting.rb
|
44
|
+
- lib/mlist/util.rb
|
45
|
+
- lib/mlist.rb
|
46
|
+
has_rdoc: false
|
47
|
+
homepage: http://github.com/aiwilliams/mlist
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.2.0
|
69
|
+
signing_key:
|
70
|
+
specification_version: 2
|
71
|
+
summary: A Ruby mailing list library designed to be integrated into other applications.
|
72
|
+
test_files: []
|
73
|
+
|