koseki-mocksmtpd 0.0.2

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/ChangeLog ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 / 2008-11-03
2
+
3
+ * initial release
4
+
data/README ADDED
@@ -0,0 +1,29 @@
1
+
2
+ = mocksmtpd
3
+
4
+
5
+ == Description
6
+
7
+
8
+ == Installation
9
+
10
+ === Archive Installation
11
+
12
+ rake install
13
+
14
+ === Gem Installation
15
+
16
+ gem sources -a http://gems.github.com
17
+ gem install koseki-mocksmtpd
18
+
19
+ == Features/Problems
20
+
21
+
22
+ == Synopsis
23
+
24
+
25
+ == Copyright
26
+
27
+ Author:: KOSEKI Kengo <koseki@gmail.com>
28
+ Copyright:: Copyright (c) 2008
29
+ License:: Ruby Licence
data/Rakefile ADDED
@@ -0,0 +1,144 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'rake/contrib/sshpublisher'
10
+ require 'fileutils'
11
+ require 'lib/mocksmtpd'
12
+ include FileUtils
13
+
14
+ NAME = "mocksmtpd"
15
+ AUTHOR = "KOSEKI Kengo"
16
+ EMAIL = "koseki@gmail.com"
17
+ DESCRIPTION = "Mock SMTP server for development/testing."
18
+ RUBYFORGE_PROJECT = "mocksmtpd"
19
+ HOMEPATH = "http://github.com/koseki/mocksmtpd/"
20
+ BIN_FILES = %w(mocksmtpd)
21
+
22
+ VERS = Mocksmtpd::VERSION
23
+ REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
+ RDOC_OPTS = [
26
+ '--title', "#{NAME} documentation",
27
+ "--charset", "utf-8",
28
+ "--opname", "index.html",
29
+ "--line-numbers",
30
+ "--main", "README",
31
+ "--inline-source",
32
+ ]
33
+
34
+ task :default => [:test]
35
+ task :package => [:clean]
36
+
37
+ Rake::TestTask.new("test") do |t|
38
+ t.libs << "test"
39
+ t.pattern = "test/**/*_test.rb"
40
+ t.verbose = true
41
+ end
42
+
43
+ spec = Gem::Specification.new do |s|
44
+ s.name = NAME
45
+ s.version = VERS
46
+ s.platform = Gem::Platform::RUBY
47
+ s.has_rdoc = true
48
+ s.extra_rdoc_files = ["README", "ChangeLog"]
49
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
50
+ s.summary = DESCRIPTION
51
+ s.description = DESCRIPTION
52
+ s.author = AUTHOR
53
+ s.email = EMAIL
54
+ s.homepage = HOMEPATH
55
+ s.executables = BIN_FILES
56
+ s.rubyforge_project = RUBYFORGE_PROJECT
57
+ s.bindir = "bin"
58
+ s.require_path = "lib"
59
+ #s.autorequire = ""
60
+ s.test_files = Dir["test/*_test.rb"]
61
+
62
+ #s.add_dependency('activesupport', '>=1.3.1')
63
+ #s.required_ruby_version = '>= 1.8.2'
64
+
65
+ s.files = %w(README ChangeLog Rakefile) +
66
+ Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
67
+ Dir.glob("ext/**/*.{h,c,rb}") +
68
+ Dir.glob("examples/**/*.rb") +
69
+ Dir.glob("tools/*.rb") +
70
+ Dir.glob("rails/*.rb")
71
+
72
+ s.extensions = FileList["ext/**/extconf.rb"].to_a
73
+ end
74
+
75
+ Rake::GemPackageTask.new(spec) do |p|
76
+ p.need_tar = true
77
+ p.gem_spec = spec
78
+ end
79
+
80
+ task :install do
81
+ name = "#{NAME}-#{VERS}.gem"
82
+ sh %{rake package}
83
+ sh %{sudo gem install pkg/#{name}}
84
+ end
85
+
86
+ task :uninstall => [:clean] do
87
+ sh %{sudo gem uninstall #{NAME}}
88
+ end
89
+
90
+
91
+ Rake::RDocTask.new do |rdoc|
92
+ rdoc.rdoc_dir = 'html'
93
+ rdoc.options += RDOC_OPTS
94
+ rdoc.template = "resh"
95
+ #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
96
+ if ENV['DOC_FILES']
97
+ rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
98
+ else
99
+ rdoc.rdoc_files.include('README', 'ChangeLog')
100
+ rdoc.rdoc_files.include('lib/**/*.rb')
101
+ rdoc.rdoc_files.include('ext/**/*.c')
102
+ end
103
+ end
104
+
105
+ desc "Publish to RubyForge"
106
+ task :rubyforge => [:rdoc, :package] do
107
+ require 'rubyforge'
108
+ Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, '').upload
109
+ end
110
+
111
+ desc 'Package and upload the release to rubyforge.'
112
+ task :release => [:clean, :package] do |t|
113
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
114
+ abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
115
+ pkg = "pkg/#{NAME}-#{VERS}"
116
+
117
+ require 'rubyforge'
118
+ rf = RubyForge.new.configure
119
+ puts "Logging in"
120
+ rf.login
121
+
122
+ c = rf.userconfig
123
+ # c["release_notes"] = description if description
124
+ # c["release_changes"] = changes if changes
125
+ c["preformatted"] = true
126
+
127
+ files = [
128
+ "#{pkg}.tgz",
129
+ "#{pkg}.gem"
130
+ ].compact
131
+
132
+ puts "Releasing #{NAME} v. #{VERS}"
133
+ rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
134
+ end
135
+
136
+ desc 'Show information about the gem.'
137
+ task :debug_gem do
138
+ puts spec.to_ruby
139
+ end
140
+
141
+ desc 'Update gem spec'
142
+ task :gemspec do
143
+ open("#{NAME}.gemspec", 'w').write spec.to_ruby
144
+ end
data/bin/mocksmtpd ADDED
@@ -0,0 +1,6 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "mocksmtpd"
5
+
6
+ Mocksmtpd.new(ARGV).run
data/lib/mocksmtpd.rb ADDED
@@ -0,0 +1,289 @@
1
+ $:.unshift File.dirname(__FILE__) # for test/development
2
+
3
+ require 'optparse'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require 'erb'
7
+ require 'nkf'
8
+ require 'smtpserver'
9
+
10
+ class Mocksmtpd
11
+ VERSION = '0.0.2'
12
+ TEMPLATE_DIR = Pathname.new(File.dirname(__FILE__)) + "../templates"
13
+
14
+ include ERB::Util
15
+
16
+ def initialize(argv)
17
+ @opt = OptionParser.new
18
+ @opt.banner = "Usage: #$0 [options] [start|stop|init PATH]"
19
+ @opt.on("-f FILE", "--config=FILE", "Specify mocksmtpd.conf") do |v|
20
+ @conf_file = v
21
+ end
22
+
23
+ @opt.on("--version", "Show version string `#{VERSION}'") do
24
+ puts VERSION
25
+ exit
26
+ end
27
+
28
+ @opt.parse!(argv)
29
+
30
+ if argv.empty?
31
+ @command = "console"
32
+ else
33
+ @command = argv.shift
34
+ commands = %w(start stop init)
35
+ unless commands.include? @command
36
+ opterror "No such command: #{@command}"
37
+ exit 1
38
+ end
39
+ end
40
+
41
+ if @command == "init"
42
+ @init_dir = argv.shift || "mocksmtpd"
43
+ if test(?e, @init_dir)
44
+ opterror("Init path already exists: #{@init_dir}")
45
+ exit 1
46
+ end
47
+ end
48
+ end
49
+
50
+ def opterror(msg)
51
+ puts("Error: #{msg}")
52
+ puts(@opt.help)
53
+ end
54
+
55
+ def load_conf
56
+ @conf_file = Pathname.new(@conf_file || "./mocksmtpd.conf")
57
+ unless @conf_file.exist? && @conf_file.readable?
58
+ opterror "Can't load config file: #{@conf_file}"
59
+ exit 1
60
+ end
61
+ @conf_file = @conf_file.realpath
62
+
63
+ @conf = {}
64
+ YAML.load_file(@conf_file).each do |k,v|
65
+ @conf[k.intern] = v
66
+ end
67
+
68
+ @inbox = resolve_conf_path(@conf[:InboxDir])
69
+ @logfile = resolve_conf_path(@conf[:LogFile])
70
+ @pidfile = resolve_conf_path(@conf[:PidFile])
71
+
72
+ @templates = load_templates
73
+ end
74
+
75
+ def resolve_conf_path(path)
76
+ result = nil
77
+ if path[0] == ?/
78
+ result = Pathname.new(path)
79
+ else
80
+ result = @conf_file.parent + path
81
+ end
82
+ return result.cleanpath
83
+ end
84
+
85
+ def run
86
+ send(@command)
87
+ end
88
+
89
+ def load_templates
90
+ result = {}
91
+ result[:mail] = template("html/mail")
92
+ result[:index] = template("html/index")
93
+ result[:index_entry] = template("html/index_entry")
94
+ return result
95
+ end
96
+
97
+ def template(name)
98
+ path = TEMPLATE_DIR + "#{name}.erb"
99
+ src = path.read
100
+ return ERB.new(src, nil, "%-")
101
+ end
102
+
103
+ def init
104
+ Dir.mkdir(@init_dir)
105
+ puts "Created: #{@init_dir}/"
106
+ path = Pathname.new(@init_dir)
107
+ Dir.mkdir(path + "inbox")
108
+ puts "Created: #{path + 'inbox'}/"
109
+ Dir.mkdir(path + "log")
110
+ puts "Created: #{path + 'log'}/"
111
+
112
+ open(path + "mocksmtpd.conf", "w") do |io|
113
+ io << template("mocksmtpd.conf").result(binding)
114
+ end
115
+ puts "Created: #{path + 'mocksmtpd.conf'}"
116
+ end
117
+
118
+ def stop
119
+ load_conf
120
+ unless @pidfile.exist?
121
+ puts "ERROR: pid file does not exist: #{@pidfile}"
122
+ exit 1
123
+ end
124
+ unless @pidfile.readable?
125
+ puts "ERROR: Can't read pid file: #{@pidfile}"
126
+ exit 1
127
+ end
128
+
129
+ pid = File.read(@pidfile)
130
+ print "Stopping #{pid}..."
131
+ system "kill -TERM #{pid}"
132
+ puts "done"
133
+ end
134
+
135
+ def start
136
+ load_conf
137
+ @logger = WEBrick::Log.new(@logfile.to_s, WEBrick::BasicLog::INFO)
138
+ @daemon = true
139
+ smtpd
140
+ end
141
+
142
+ def console
143
+ load_conf
144
+ @logger = WEBrick::Log.new
145
+ @daemon = false
146
+ smtpd
147
+ end
148
+
149
+ def create_pid_file
150
+ if @pidfile.exist?
151
+ pid = @pidfile.read
152
+ @logger.warn("pid file already exists: #{pid}")
153
+ exit 1
154
+ end
155
+ open(@pidfile, "w") do |io|
156
+ io << Process.pid
157
+ end
158
+ end
159
+
160
+ def delete_pid_file
161
+ File.delete(@pidfile)
162
+ end
163
+
164
+ def init_permission
165
+ File.umask(@conf[:Umask]) unless @conf[:Umask].nil?
166
+ stat = File::Stat.new(@conf_file)
167
+ uid = stat.uid
168
+ gid = stat.gid
169
+ begin
170
+ Process.egid = gid
171
+ Process.euid = uid
172
+ rescue
173
+ end
174
+ end
175
+
176
+ def smtpd
177
+ start_cb = Proc.new do
178
+ @logger.info("Inbox: #{@inbox}")
179
+ if @daemon
180
+ @logger.info("LogFile: #{@logfile}")
181
+ @logger.info("PidFile: #{@pidfile}")
182
+ end
183
+
184
+ begin
185
+ init_permission
186
+ create_pid_file if @daemon
187
+ rescue => e
188
+ @logger.error("Start: #{e}")
189
+ raise e
190
+ end
191
+ end
192
+
193
+ stop_cb = Proc.new do
194
+ begin
195
+ delete_pid_file if @daemon
196
+ rescue => e
197
+ @logger.error("Stop: #{e}")
198
+ raise e
199
+ end
200
+ end
201
+
202
+ data_cb = Proc.new do |src, sender, recipients|
203
+ recieve_mail(src, sender, recipients)
204
+ end
205
+
206
+ @conf[:ServerType] = @daemon ? WEBrick::Daemon : nil
207
+ @conf[:Logger] = @logger
208
+ @conf[:StartCallback] = start_cb
209
+ @conf[:StopCallback] = stop_cb
210
+ @conf[:DataHook] = data_cb
211
+
212
+ server = SMTPServer.new(@conf)
213
+
214
+ [:INT, :TERM].each do |signal|
215
+ Signal.trap(signal) { server.shutdown }
216
+ end
217
+
218
+ server.start
219
+ end
220
+
221
+ def recieve_mail(src, sender, recipients)
222
+ @logger.info "mail recieved from #{sender}"
223
+
224
+ mail = parse_mail(src, sender, recipients)
225
+
226
+ save_mail(mail)
227
+ save_index(mail)
228
+ end
229
+
230
+ def parse_mail(src, sender, recipients)
231
+ src = NKF.nkf("-wm", src)
232
+ subject = src.match(/^Subject:\s*(.+)/i).to_a[1].to_s.strip
233
+ date = src.match(/^Date:\s*(.+)/i).to_a[1].to_s.strip
234
+
235
+ src = ERB::Util.h(src)
236
+ src = src.gsub(%r{https?://[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+},'<a href="\0">\0</a>')
237
+ src = src.gsub(/(?:\r\n|\r|\n)/, "<br />\n")
238
+
239
+ if date.empty?
240
+ date = Time.now
241
+ else
242
+ date = Time.parse(date)
243
+ end
244
+
245
+ mail = {
246
+ :source => src,
247
+ :sender => sender,
248
+ :recipients => recipients,
249
+ :subject => subject,
250
+ :date => date,
251
+ }
252
+
253
+ format = "%Y%m%d%H%M%S"
254
+ fname = date.strftime(format) + ".html"
255
+ while @inbox.join(fname).exist?
256
+ date += 1
257
+ fname = date.strftime(format) + ".html"
258
+ end
259
+
260
+ mail[:file] = fname
261
+ mail[:path] = @inbox.join(fname)
262
+
263
+ return mail
264
+ end
265
+
266
+ def save_mail(mail)
267
+ open(mail[:path], "w") do |io|
268
+ io << @templates[:mail].result(binding)
269
+ end
270
+ end
271
+
272
+ def save_index(mail)
273
+ path = @inbox + "index.html"
274
+ unless File.exist?(path)
275
+ open(path, "w") do |io|
276
+ io << @templates[:index].result(binding)
277
+ end
278
+ end
279
+
280
+ htmlsrc = File.read(path)
281
+ add = @templates[:index_entry].result(binding)
282
+
283
+ htmlsrc.sub!(/<!-- ADD -->/, add)
284
+ open(path, "w") do |io|
285
+ io << htmlsrc
286
+ end
287
+ end
288
+
289
+ end
data/lib/smtpserver.rb ADDED
@@ -0,0 +1,257 @@
1
+ # http://tmtm.org/ja/ruby/smtpd/
2
+ # http://rubyist.g.hatena.ne.jp/muscovyduck/20070707/p1
3
+
4
+ require 'webrick'
5
+ require 'tempfile'
6
+
7
+ module GetsSafe
8
+ def gets_safe(rs = nil, timeout = @timeout, maxlength = @maxlength)
9
+ rs = $/ unless rs
10
+ f = self.kind_of?(IO) ? self : STDIN
11
+ @gets_safe_buf = '' unless @gets_safe_buf
12
+ until @gets_safe_buf.include? rs do
13
+ if maxlength and @gets_safe_buf.length > maxlength then
14
+ raise Errno::E2BIG, 'too long'
15
+ end
16
+ if IO.select([f], nil, nil, timeout) == nil then
17
+ raise Errno::ETIMEDOUT, 'timeout exceeded'
18
+ end
19
+ begin
20
+ @gets_safe_buf << f.sysread(4096)
21
+ rescue EOFError, Errno::ECONNRESET
22
+ return @gets_safe_buf.empty? ? nil : @gets_safe_buf.slice!(0..-1)
23
+ end
24
+ end
25
+ p = @gets_safe_buf.index rs
26
+ if maxlength and p > maxlength then
27
+ raise Errno::E2BIG, 'too long'
28
+ end
29
+ return @gets_safe_buf.slice!(0, p+rs.length)
30
+ end
31
+ attr_accessor :timeout, :maxlength
32
+ end
33
+
34
+ class SMTPD
35
+ class Error < StandardError; end
36
+
37
+ def initialize(sock, domain)
38
+ @sock = sock
39
+ @domain = domain
40
+ @error_interval = 5
41
+ class << @sock
42
+ include GetsSafe
43
+ end
44
+ @helo_hook = nil
45
+ @mail_hook = nil
46
+ @rcpt_hook = nil
47
+ @data_hook = nil
48
+ @data_each_line = nil
49
+ @rset_hook = nil
50
+ @noop_hook = nil
51
+ @quit_hook = nil
52
+ end
53
+ attr_writer :helo_hook, :mail_hook, :rcpt_hook, :data_hook,
54
+ :data_each_line, :rset_hook, :noop_hook, :quit_hook
55
+
56
+ def start
57
+ @helo_name = nil
58
+ @sender = nil
59
+ @recipients = []
60
+ catch(:close) do
61
+ puts_safe "220 #{@domain} service ready"
62
+ while comm = @sock.gets_safe do
63
+ catch :next_comm do
64
+ comm.sub!(/\r?\n/, '')
65
+ comm, arg = comm.split(/\s+/,2)
66
+ break if comm == nil
67
+ case comm.upcase
68
+ when 'EHLO' then comm_helo arg
69
+ when 'HELO' then comm_helo arg
70
+ when 'MAIL' then comm_mail arg
71
+ when 'RCPT' then comm_rcpt arg
72
+ when 'DATA' then comm_data arg
73
+ when 'RSET' then comm_rset arg
74
+ when 'NOOP' then comm_noop arg
75
+ when 'QUIT' then comm_quit arg
76
+ else
77
+ error '502 Error: command not implemented'
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ def line_length_limit=(n)
85
+ @sock.maxlength = n
86
+ end
87
+
88
+ def input_timeout=(n)
89
+ @sock.timeout = n
90
+ end
91
+
92
+ attr_reader :line_length_limit, :input_timeout
93
+ attr_accessor :error_interval
94
+ attr_accessor :use_file, :max_size
95
+
96
+ private
97
+ def comm_helo(arg)
98
+ if arg == nil or arg.split.size != 1 then
99
+ error '501 Syntax: HELO hostname'
100
+ end
101
+ @helo_hook.call(arg) if @helo_hook
102
+ @helo_name = arg
103
+ reply "250 #{@domain}"
104
+ end
105
+
106
+ def comm_mail(arg)
107
+ if @sender != nil then
108
+ error '503 Error: nested MAIL command'
109
+ end
110
+ if arg !~ /^FROM:/i then
111
+ error '501 Syntax: MAIL FROM: <address>'
112
+ end
113
+ sender = parse_addr $'
114
+ if sender == nil then
115
+ error '501 Syntax: MAIL FROM: <address>'
116
+ end
117
+ @mail_hook.call(sender) if @mail_hook
118
+ @sender = sender
119
+ reply '250 Ok'
120
+ end
121
+
122
+ def comm_rcpt(arg)
123
+ if @sender == nil then
124
+ error '503 Error: need MAIL command'
125
+ end
126
+ if arg !~ /^TO:/i then
127
+ error '501 Syntax: RCPT TO: <address>'
128
+ end
129
+ rcpt = parse_addr $'
130
+ if rcpt == nil then
131
+ error '501 Syntax: RCPT TO: <address>'
132
+ end
133
+ @rcpt_hook.call(rcpt) if @rcpt_hook
134
+ @recipients << rcpt
135
+ reply '250 Ok'
136
+ end
137
+
138
+ def comm_data(arg)
139
+ if @recipients.size == 0 then
140
+ error '503 Error: need RCPT command'
141
+ end
142
+ if arg != nil then
143
+ error '501 Syntax: DATA'
144
+ end
145
+ reply '354 End data with <CR><LF>.<CR><LF>'
146
+ if @data_hook
147
+ tmpf = @use_file ? Tempfile.new('smtpd') : ''
148
+ end
149
+ size = 0
150
+ loop do
151
+ l = @sock.gets_safe
152
+ if l == nil then
153
+ raise SMTPD::Error, 'unexpected EOF'
154
+ end
155
+ if l.chomp == '.' then break end
156
+ if l[0] == ?. then
157
+ l[0,1] = ''
158
+ end
159
+ size += l.size
160
+ if @max_size and @max_size < size then
161
+ error '552 Error: message too large'
162
+ end
163
+ @data_each_line.call(l) if @data_each_line
164
+ tmpf << l if @data_hook
165
+ end
166
+ if @data_hook then
167
+ if @use_file then
168
+ tmpf.pos = 0
169
+ @data_hook.call(tmpf, @sender, @recipients)
170
+ tmpf.close(true)
171
+ else
172
+ @data_hook.call(tmpf, @sender, @recipients)
173
+ end
174
+ end
175
+ reply '250 Ok'
176
+ @sender = nil
177
+ @recipients = []
178
+ end
179
+
180
+ def comm_rset(arg)
181
+ if arg != nil then
182
+ error '501 Syntax: RSET'
183
+ end
184
+ @rset_hook.call(@sender, @recipients) if @rset_hook
185
+ reply '250 Ok'
186
+ @sender = nil
187
+ @recipients = []
188
+ end
189
+
190
+ def comm_noop(arg)
191
+ if arg != nil then
192
+ error '501 Syntax: NOOP'
193
+ end
194
+ @noop_hook.call(@sender, @recipients) if @noop_hook
195
+ reply '250 Ok'
196
+ end
197
+
198
+ def comm_quit(arg)
199
+ if arg != nil then
200
+ error '501 Syntax: QUIT'
201
+ end
202
+ @quit_hook.call(@sender, @recipients) if @quit_hook
203
+ reply '221 Bye'
204
+ throw :close
205
+ end
206
+
207
+ def parse_addr(str)
208
+ str = str.strip
209
+ if str == '' then
210
+ return nil
211
+ end
212
+ if str =~ /^<(.*)>$/ then
213
+ return $1.gsub(/\s+/, '')
214
+ end
215
+ if str =~ /\s/ then
216
+ return nil
217
+ end
218
+ str
219
+ end
220
+
221
+ def reply(msg)
222
+ puts_safe msg
223
+ end
224
+
225
+ def error(msg)
226
+ sleep @error_interval if @error_interval
227
+ puts_safe msg
228
+ throw :next_comm
229
+ end
230
+
231
+ def puts_safe(str)
232
+ begin
233
+ @sock.puts str + "\r\n"
234
+ rescue
235
+ raise SMTPD::Error, "cannot send to client: '#{str.gsub(/\s+/," ")}': #{$!.to_s}"
236
+ end
237
+ end
238
+ end
239
+
240
+ SMTPDError = SMTPD::Error
241
+
242
+ class SMTPServer < WEBrick::GenericServer
243
+ def run(sock)
244
+ server = SMTPD.new(sock, @config[:ServerName])
245
+ server.input_timeout = @config[:RequestTimeout]
246
+ server.line_length_limit = @config[:LineLengthLimit]
247
+ server.helo_hook = @config[:HeloHook]
248
+ server.mail_hook = @config[:MailHook]
249
+ server.rcpt_hook = @config[:RcptHook]
250
+ server.data_hook = @config[:DataHook]
251
+ server.data_each_line = @config[:DataEachLine]
252
+ server.rset_hook = @config[:RsetHook]
253
+ server.noop_hook = @config[:NoopHook]
254
+ server.quit_hook = @config[:QuitHook]
255
+ server.start
256
+ end
257
+ end
@@ -0,0 +1,46 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <link rel="index" href="./index.html" />
7
+ <title>Inbox</title>
8
+ <style type="text/css">
9
+ body {
10
+ background:#eee;
11
+ }
12
+ table {
13
+ border: 1px #999 solid;
14
+ border-collapse: collapse;
15
+ }
16
+ th, td {
17
+ border: 1px #999 solid;
18
+ padding: 6px 12px;
19
+ }
20
+ th {
21
+ background: #ccc;
22
+ }
23
+ td {
24
+ background: white;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <h1>Inbox</h1>
30
+ <table>
31
+ <thead>
32
+ <tr>
33
+ <th>Date</th>
34
+ <th>Subject</th>
35
+ <th>From</th>
36
+ <th>To</th>
37
+ </tr>
38
+ </thead>
39
+
40
+ <tbody>
41
+ <!-- ADD -->
42
+
43
+ </tbody>
44
+ </table>
45
+ </body>
46
+ </html>
@@ -0,0 +1,8 @@
1
+ <!-- ADD -->
2
+
3
+ <tr>
4
+ <td><%= mail[:date].strftime("%Y-%m-%d %H:%M:%S") %></td>
5
+ <td><a href="<%=h mail[:file] %>"><%=h mail[:subject] %></a></td>
6
+ <td><%=h mail[:sender] %></td>
7
+ <td><%=h mail[:recipients].to_a.join(",") %></td>
8
+ </tr>
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <link rel="index" href="./index.html" />
7
+ <title><%=h mail[:subject] %> (<%= mail[:date].to_s %>)</title>
8
+ </head>
9
+ <body style="background:#eee">
10
+ <h1 id="subject"><%=h mail[:subject] %></h1>
11
+ <div><p id="date" style="font-size:0.8em;"><%= mail[:date].to_s %></div>
12
+ <div id="source" style="border: solid 1px #666; background:white; padding:2em;">
13
+ <p><%= mail[:source] %></p>
14
+ </div>
15
+ </body>
16
+ </html>
@@ -0,0 +1,10 @@
1
+ ServerName: mocksmtpd
2
+ Port: 25
3
+ RequestTimeout: 120
4
+ LineLengthLimit: 1024
5
+ Loglevel: INFO
6
+
7
+ LogFile: ./log/mocksmtpd.log
8
+ PidFile: ./log/mocksmtpd.pid
9
+ InboxDir: ./inbox
10
+ Umask: 2
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ require "test/unit"
4
+ class MocksmtpdTest < Test::Unit::TestCase
5
+ end
@@ -0,0 +1,3 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/mocksmtpd'
3
+
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: koseki-mocksmtpd
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - KOSEKI Kengo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-04 00:00:00 -08:00
13
+ default_executable: mocksmtpd
14
+ dependencies: []
15
+
16
+ description: Mock SMTP server for development/testing.
17
+ email: koseki@gmail.com
18
+ executables:
19
+ - mocksmtpd
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - ChangeLog
25
+ files:
26
+ - README
27
+ - ChangeLog
28
+ - Rakefile
29
+ - bin/mocksmtpd
30
+ - test/mocksmtpd_test.rb
31
+ - test/test_helper.rb
32
+ - lib/mocksmtpd-old.rb
33
+ - lib/mocksmtpd-old.rb~
34
+ - lib/mocksmtpd.rb
35
+ - lib/mocksmtpd.rb~
36
+ - lib/smtpserver.rb
37
+ - templates/html
38
+ - templates/html/index.erb
39
+ - templates/html/index.erb~
40
+ - templates/html/index_entry.erb
41
+ - templates/html/index_entry.erb~
42
+ - templates/html/mail.erb
43
+ - templates/html/mail.html.erb~
44
+ - templates/mocksmtpd.conf.erb
45
+ has_rdoc: true
46
+ homepage: http://github.com/koseki/mocksmtpd/
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --title
50
+ - mocksmtpd documentation
51
+ - --charset
52
+ - utf-8
53
+ - --opname
54
+ - index.html
55
+ - --line-numbers
56
+ - --main
57
+ - README
58
+ - --inline-source
59
+ - --exclude
60
+ - ^(examples|extras)/
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project: mocksmtpd
78
+ rubygems_version: 1.2.0
79
+ signing_key:
80
+ specification_version: 2
81
+ summary: Mock SMTP server for development/testing.
82
+ test_files:
83
+ - test/mocksmtpd_test.rb