1require 'net/pop'
2require 'test/unit'
3require 'digest/md5'
4
5class TestPOP < Test::Unit::TestCase
6  def setup
7    @users = {'user' => 'pass' }
8    @ok_user = 'user'
9    @stamp_base = "#{$$}.#{Time.now.to_i}@localhost"
10  end
11
12  def test_pop_auth_ok
13    pop_test(false) do |pop|
14      assert_instance_of Net::POP3, pop
15      assert_nothing_raised do
16        pop.start(@ok_user, @users[@ok_user])
17      end
18    end
19  end
20
21  def test_pop_auth_ng
22    pop_test(false) do |pop|
23      assert_instance_of Net::POP3, pop
24      assert_raise Net::POPAuthenticationError do
25        pop.start(@ok_user, 'bad password')
26      end
27    end
28  end
29
30  def test_apop_ok
31    pop_test(@stamp_base) do |pop|
32      assert_instance_of Net::APOP, pop
33      assert_nothing_raised do
34        pop.start(@ok_user, @users[@ok_user])
35      end
36    end
37  end
38
39  def test_apop_ng
40    pop_test(@stamp_base) do |pop|
41      assert_instance_of Net::APOP, pop
42      assert_raise Net::POPAuthenticationError do
43        pop.start(@ok_user, 'bad password')
44      end
45    end
46  end
47
48  def test_apop_invalid
49    pop_test("\x80"+@stamp_base) do |pop|
50      assert_instance_of Net::APOP, pop
51      assert_raise Net::POPAuthenticationError do
52        pop.start(@ok_user, @users[@ok_user])
53      end
54    end
55  end
56
57  def test_apop_invalid_at
58    pop_test(@stamp_base.sub('@', '.')) do |pop|
59      assert_instance_of Net::APOP, pop
60      e = assert_raise Net::POPAuthenticationError do
61        pop.start(@ok_user, @users[@ok_user])
62      end
63    end
64  end
65
66  def pop_test(apop=false)
67    host = 'localhost'
68    server = TCPServer.new(host, 0)
69    port = server.addr[1]
70    thread = Thread.start do
71      sock = server.accept
72      begin
73        pop_server_loop(sock, apop)
74      ensure
75        sock.close
76      end
77    end
78    begin
79      pop = Net::POP3::APOP(apop).new(host, port)
80      #pop.set_debug_output $stderr
81      yield pop
82    ensure
83      begin
84        pop.finish
85      rescue IOError
86        raise unless $!.message == "POP session not yet started"
87      end
88    end
89  ensure
90    server.close
91    thread.value
92  end
93
94  def pop_server_loop(sock, apop)
95    if apop
96      sock.print "+OK ready <#{apop}>\r\n"
97    else
98      sock.print "+OK ready\r\n"
99    end
100    user = nil
101    while line = sock.gets
102      case line
103      when /^USER (.+)\r\n/
104        user = $1
105        if @users.key?(user)
106          sock.print "+OK\r\n"
107        else
108          sock.print "-ERR unknown user\r\n"
109        end
110      when /^PASS (.+)\r\n/
111        if @users[user] == $1
112          sock.print "+OK\r\n"
113        else
114          sock.print "-ERR invalid password\r\n"
115        end
116      when /^APOP (.+) (.+)\r\n/
117        user = $1
118        if apop && Digest::MD5.hexdigest("<#{apop}>#{@users[user]}") == $2
119          sock.print "+OK\r\n"
120        else
121          sock.print "-ERR authentication failed\r\n"
122        end
123      when /^QUIT/
124        sock.print "+OK bye\r\n"
125        return
126      else
127        sock.print "-ERR command not recognized\r\n"
128        return
129      end
130    end
131  end
132end
133