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