koseki-mocksmtpd 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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