popthis 0.3
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.markdown +31 -0
- data/Rakefile +51 -0
- data/bin/popthis +35 -0
- data/lib/pop_this.rb +218 -0
- metadata +60 -0
data/README.markdown
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Can't POP(3) This.
|
2
|
+
|
3
|
+
<img src="http://cwninja.github.com/popthis/hammertime.gif" alt="Hammer Time"/>
|
4
|
+
|
5
|
+
So you downloaded [inaction_mailer](http://github.com/cwninja/inaction_mailer), and you had a folder full of e-mails generated while writing your app?
|
6
|
+
|
7
|
+
Now you want to preview your e-mails?!? You people are never happy!
|
8
|
+
|
9
|
+
Well, I'm a little tipsy, so I'm going to be nice, and I'm going to let you get at your mails with POP3!
|
10
|
+
|
11
|
+
I hope you like it. I do!
|
12
|
+
|
13
|
+
## Usage:
|
14
|
+
|
15
|
+
Start the server:
|
16
|
+
|
17
|
+
popthis tmp/mails/
|
18
|
+
|
19
|
+
Now, configure your mail client as follows:
|
20
|
+
|
21
|
+
* Server: **localhost**
|
22
|
+
* Protocol: **POP3**
|
23
|
+
* Port: **2220**
|
24
|
+
* Username: anything
|
25
|
+
* Password: anything
|
26
|
+
|
27
|
+
You should now be able to see the contents of your tmp/mails folder in your mail client!
|
28
|
+
|
29
|
+
## Origins:
|
30
|
+
|
31
|
+
I stole most of the code from [here](http://snippets.dzone.com/posts/show/5906)... so credit where credit's due.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :test
|
8
|
+
|
9
|
+
desc 'Test the plugin.'
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
spec = Gem::Specification.new do |s|
|
17
|
+
s.name = "popthis"
|
18
|
+
s.version = "0.3"
|
19
|
+
s.summary = "Run a pop server serving up the current dir."
|
20
|
+
s.description = "Run a pop server serving up the current dir."
|
21
|
+
|
22
|
+
s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'test/**/*.rb', 'rails/*']
|
23
|
+
s.require_path = 'lib'
|
24
|
+
s.test_files = Dir[*['test/**/*_test.rb']]
|
25
|
+
s.bindir = 'bin'
|
26
|
+
s.executables = FileList["bin/*"].map { |f| File.basename(f) }
|
27
|
+
|
28
|
+
s.has_rdoc = true
|
29
|
+
s.extra_rdoc_files = ["README.markdown"]
|
30
|
+
s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.textile"]
|
31
|
+
|
32
|
+
s.authors = ["Tom Lea"]
|
33
|
+
s.email = %q{commit@tomlea.co.uk}
|
34
|
+
|
35
|
+
s.platform = Gem::Platform::RUBY
|
36
|
+
end
|
37
|
+
|
38
|
+
Rake::GemPackageTask.new spec do |pkg|
|
39
|
+
pkg.need_tar = true
|
40
|
+
pkg.need_zip = true
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Clean files generated by rake tasks"
|
44
|
+
task :clobber => [:clobber_package]
|
45
|
+
|
46
|
+
desc "Generate a gemspec file"
|
47
|
+
task :gemspec do
|
48
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
49
|
+
f.write spec.to_ruby
|
50
|
+
end
|
51
|
+
end
|
data/bin/popthis
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require File.join(File.dirname(__FILE__), *%w[.. lib pop_this])
|
4
|
+
|
5
|
+
options = {
|
6
|
+
:host => "localhost",
|
7
|
+
:port => 2220
|
8
|
+
}
|
9
|
+
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: popthis [options] [path]"
|
12
|
+
|
13
|
+
opts.on("-p", "--port PORT", "Listen on PORT (default 2220)") do |v|
|
14
|
+
options[:port] = v.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("-H", "--host HOST", "Listen on HOST (default localhost)") do |v|
|
18
|
+
options[:host] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end.parse!
|
26
|
+
|
27
|
+
options[:path] = File.expand_path( ARGV.shift || Dir.pwd )
|
28
|
+
|
29
|
+
p options
|
30
|
+
server = PopThis::POP3Server.new(options)
|
31
|
+
|
32
|
+
trap("INT"){ server.stop }
|
33
|
+
|
34
|
+
server.start
|
35
|
+
server.join
|
data/lib/pop_this.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'gserver'
|
2
|
+
require 'digest/md5'
|
3
|
+
module PopThis
|
4
|
+
class POP3Server < GServer
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@hostname = options[:host]
|
8
|
+
super(options[:port])
|
9
|
+
@dir = options[:path]
|
10
|
+
end
|
11
|
+
|
12
|
+
class Email
|
13
|
+
attr_reader :filename
|
14
|
+
attr_accessor :deleted
|
15
|
+
|
16
|
+
def self.all(dir)
|
17
|
+
Dir.glob("#{ dir }/*").reject{|fn| fn =~ /^\./ || File.directory?(fn) }.map{|fn| Email.new(fn) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.delete(email)
|
21
|
+
File.delete(email.filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(filename)
|
25
|
+
@filename = filename
|
26
|
+
@deleted = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def email
|
30
|
+
@email ||= File.read(@filename)
|
31
|
+
end
|
32
|
+
|
33
|
+
def deleted?
|
34
|
+
deleted
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def serve(io)
|
39
|
+
@state = 'auth'
|
40
|
+
@failed = 0
|
41
|
+
io.print("+OK POP3 server ready\r\n")
|
42
|
+
loop do
|
43
|
+
if IO.select([io], nil, nil, 0.1)
|
44
|
+
begin
|
45
|
+
data = io.readpartial(4096)
|
46
|
+
puts ">> #{data.chomp}"
|
47
|
+
ok, op = process_line(data)
|
48
|
+
puts "<< #{op.chomp}"
|
49
|
+
io.print op
|
50
|
+
break unless ok
|
51
|
+
rescue Exception
|
52
|
+
end
|
53
|
+
end
|
54
|
+
break if io.closed?
|
55
|
+
end
|
56
|
+
io.close unless io.closed?
|
57
|
+
end
|
58
|
+
|
59
|
+
def emails
|
60
|
+
@emails = Email.all(@dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
def stat
|
64
|
+
msgs = bytes = 0
|
65
|
+
@emails.each do |e|
|
66
|
+
next if e.deleted?
|
67
|
+
msgs += 1
|
68
|
+
bytes += e.email.length
|
69
|
+
end
|
70
|
+
return msgs, bytes
|
71
|
+
end
|
72
|
+
|
73
|
+
def list(msgid = nil)
|
74
|
+
msgid = msgid.to_i if msgid
|
75
|
+
if msgid
|
76
|
+
return false if msgid > @emails.length or @emails[msgid-1].deleted?
|
77
|
+
return [ [msgid, @emails[msgid].email.length] ]
|
78
|
+
else
|
79
|
+
msgs = []
|
80
|
+
@emails.each_with_index do |e,i|
|
81
|
+
msgs << [ i + 1, e.email.length ]
|
82
|
+
end
|
83
|
+
msgs
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def retr(msgid)
|
88
|
+
msgid = msgid.to_i
|
89
|
+
return false if msgid > @emails.length or @emails[msgid-1].deleted?
|
90
|
+
@emails[msgid-1].email
|
91
|
+
end
|
92
|
+
|
93
|
+
def dele(msgid)
|
94
|
+
msgid = msgid.to_i
|
95
|
+
return false if msgid > @emails.length
|
96
|
+
@emails[msgid-1].deleted = true
|
97
|
+
end
|
98
|
+
|
99
|
+
def rset
|
100
|
+
@emails.each do |e|
|
101
|
+
e.deleted = false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def quit
|
106
|
+
@emails.each do |e|
|
107
|
+
if e.deleted?
|
108
|
+
Email.delete(e)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def process_line(line)
|
114
|
+
line.chomp!
|
115
|
+
case @state
|
116
|
+
when 'auth'
|
117
|
+
case line
|
118
|
+
when /^QUIT$/
|
119
|
+
return false, "+OK popthis POP3 server signing off\r\n"
|
120
|
+
when /^USER (.+)$/
|
121
|
+
return true, "+OK #{$1} is most welcome here\r\n"
|
122
|
+
when /^PASS (.+)$/
|
123
|
+
@state = 'trans'
|
124
|
+
emails
|
125
|
+
msgs, bytes = stat
|
126
|
+
return true, "+OK #{msgs} messages (#{bytes} octets)\r\n"
|
127
|
+
end
|
128
|
+
when 'trans'
|
129
|
+
case line
|
130
|
+
when /^NOOP$/
|
131
|
+
return true, "+OK\r\n"
|
132
|
+
when /^STAT$/
|
133
|
+
msgs, bytes = stat
|
134
|
+
return true, "+OK #{msgs} #{bytes}\r\n"
|
135
|
+
when /^LIST$/
|
136
|
+
msgs, bytes = stat
|
137
|
+
msg = "+OK #{msgs} messages (#{bytes} octets)\r\n"
|
138
|
+
list.each do |num, bytes|
|
139
|
+
msg += "#{num} #{bytes}\r\n"
|
140
|
+
end
|
141
|
+
msg += ".\r\n"
|
142
|
+
return true, msg
|
143
|
+
when /^LIST (\d+)$/
|
144
|
+
msgs, bytes = stat
|
145
|
+
num, bytes = list($1)
|
146
|
+
if num
|
147
|
+
return true, "+OK #{num} #{bytes}\r\n"
|
148
|
+
else
|
149
|
+
return true, "-ERR no such message, only #{msgs} messages in maildrop\r\n"
|
150
|
+
end
|
151
|
+
when /^RETR (\d+)$/
|
152
|
+
msg = retr($1)
|
153
|
+
if msg
|
154
|
+
msg = "+OK #{msg.length} octets\r\n" + msg
|
155
|
+
msg += "\r\n.\r\n"
|
156
|
+
else
|
157
|
+
msg = "-ERR no such message\r\n"
|
158
|
+
end
|
159
|
+
return true, msg
|
160
|
+
when /^DELE (\d+)$/
|
161
|
+
if dele($1)
|
162
|
+
return true, "+OK message #{$1} deleted\r\n"
|
163
|
+
else
|
164
|
+
return true, "-ERR message #{$1} already deleted\r\n"
|
165
|
+
end
|
166
|
+
when /^RSET$/
|
167
|
+
rset
|
168
|
+
msgs, bytes = stat
|
169
|
+
return true, "+OK maildrop has #{msgs} messages (#{bytes} octets)\r\n"
|
170
|
+
when /^QUIT$/
|
171
|
+
@state = 'update'
|
172
|
+
quit
|
173
|
+
msgs, bytes = stat
|
174
|
+
if msgs > 0
|
175
|
+
return false, "+OK popthis server signing off (#{msgs} messages left)\r\n"
|
176
|
+
else
|
177
|
+
return false, "+OK popthis server signing off (folder empty)\r\n"
|
178
|
+
end
|
179
|
+
when /^TOP (\d+) (\d+)$/
|
180
|
+
lines = $2
|
181
|
+
msg = retr($1)
|
182
|
+
unless msg
|
183
|
+
return true, "-ERR no such message\r\n"
|
184
|
+
end
|
185
|
+
cnt = nil
|
186
|
+
final = ""
|
187
|
+
msg.split(/\n/).each do |l|
|
188
|
+
final += l+"\n"
|
189
|
+
if cnt
|
190
|
+
cnt += 1
|
191
|
+
break if cnt > lines
|
192
|
+
end
|
193
|
+
if l !~ /\w/
|
194
|
+
cnt = 0
|
195
|
+
end
|
196
|
+
end
|
197
|
+
return true, "+OK\r\n"+final+".\r\n"
|
198
|
+
when /^UIDL$/
|
199
|
+
msgid = 0
|
200
|
+
msg = ''
|
201
|
+
@emails.each do |e|
|
202
|
+
msgid += 1
|
203
|
+
next if e.deleted?
|
204
|
+
msg += "#{msgid} #{Digest::MD5.hexdigest(e.email)}\r\n"
|
205
|
+
end
|
206
|
+
return true, "+OK\r\n#{msg}.\r\n";
|
207
|
+
end
|
208
|
+
when 'update'
|
209
|
+
case line
|
210
|
+
when /^QUIT$/
|
211
|
+
return true, "+OK popthis server signing off\r\n"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
return true, "-ERR unknown command\r\n"
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: popthis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.3"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Lea
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-23 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Run a pop server serving up the current dir.
|
17
|
+
email: commit@tomlea.co.uk
|
18
|
+
executables:
|
19
|
+
- popthis
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.markdown
|
24
|
+
files:
|
25
|
+
- Rakefile
|
26
|
+
- README.markdown
|
27
|
+
- lib/pop_this.rb
|
28
|
+
has_rdoc: true
|
29
|
+
homepage:
|
30
|
+
licenses: []
|
31
|
+
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options:
|
34
|
+
- --line-numbers
|
35
|
+
- --inline-source
|
36
|
+
- --main
|
37
|
+
- README.textile
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: "0"
|
45
|
+
version:
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
requirements: []
|
53
|
+
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.3.5
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: Run a pop server serving up the current dir.
|
59
|
+
test_files: []
|
60
|
+
|