RAHHH
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"Tests for twistd.mail"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- test-case-name: twisted.mail.test.test_pop3client -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.protocol import Factory
|
||||
from twisted.protocols import basic
|
||||
|
||||
USER = "test"
|
||||
PASS = "twisted"
|
||||
|
||||
PORT = 1100
|
||||
|
||||
SSL_SUPPORT = True
|
||||
UIDL_SUPPORT = True
|
||||
INVALID_SERVER_RESPONSE = False
|
||||
INVALID_CAPABILITY_RESPONSE = False
|
||||
INVALID_LOGIN_RESPONSE = False
|
||||
DENY_CONNECTION = False
|
||||
DROP_CONNECTION = False
|
||||
BAD_TLS_RESPONSE = False
|
||||
TIMEOUT_RESPONSE = False
|
||||
TIMEOUT_DEFERRED = False
|
||||
SLOW_GREETING = False
|
||||
|
||||
"""Commands"""
|
||||
CONNECTION_MADE = b"+OK POP3 localhost v2003.83 server ready"
|
||||
|
||||
CAPABILITIES = [b"TOP", b"LOGIN-DELAY 180", b"USER", b"SASL LOGIN"]
|
||||
|
||||
CAPABILITIES_SSL = b"STLS"
|
||||
CAPABILITIES_UIDL = b"UIDL"
|
||||
|
||||
|
||||
INVALID_RESPONSE = b"-ERR Unknown request"
|
||||
VALID_RESPONSE = b"+OK Command Completed"
|
||||
AUTH_DECLINED = b"-ERR LOGIN failed"
|
||||
AUTH_ACCEPTED = b"+OK Mailbox open, 0 messages"
|
||||
TLS_ERROR = b"-ERR server side error start TLS handshake"
|
||||
LOGOUT_COMPLETE = b"+OK quit completed"
|
||||
NOT_LOGGED_IN = b"-ERR Unknown AUHORIZATION state command"
|
||||
STAT = b"+OK 0 0"
|
||||
UIDL = b"+OK Unique-ID listing follows\r\n."
|
||||
LIST = b"+OK Mailbox scan listing follows\r\n."
|
||||
CAP_START = b"+OK Capability list follows:"
|
||||
|
||||
|
||||
class POP3TestServer(basic.LineReceiver):
|
||||
def __init__(self, contextFactory=None):
|
||||
self.loggedIn = False
|
||||
self.caps = None
|
||||
self.tmpUser = None
|
||||
self.ctx = contextFactory
|
||||
|
||||
def sendSTATResp(self, req):
|
||||
self.sendLine(STAT)
|
||||
|
||||
def sendUIDLResp(self, req):
|
||||
self.sendLine(UIDL)
|
||||
|
||||
def sendLISTResp(self, req):
|
||||
self.sendLine(LIST)
|
||||
|
||||
def sendCapabilities(self):
|
||||
if self.caps is None:
|
||||
self.caps = [CAP_START]
|
||||
|
||||
if UIDL_SUPPORT:
|
||||
self.caps.append(CAPABILITIES_UIDL)
|
||||
|
||||
if SSL_SUPPORT:
|
||||
self.caps.append(CAPABILITIES_SSL)
|
||||
|
||||
for cap in CAPABILITIES:
|
||||
self.caps.append(cap)
|
||||
resp = b"\r\n".join(self.caps)
|
||||
resp += b"\r\n."
|
||||
|
||||
self.sendLine(resp)
|
||||
|
||||
def connectionMade(self):
|
||||
if DENY_CONNECTION:
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
if SLOW_GREETING:
|
||||
reactor.callLater(20, self.sendGreeting)
|
||||
|
||||
else:
|
||||
self.sendGreeting()
|
||||
|
||||
def sendGreeting(self):
|
||||
self.sendLine(CONNECTION_MADE)
|
||||
|
||||
def lineReceived(self, line):
|
||||
"""Error Conditions"""
|
||||
|
||||
uline = line.upper()
|
||||
find = lambda s: uline.find(s) != -1
|
||||
|
||||
if TIMEOUT_RESPONSE:
|
||||
# Do not respond to clients request
|
||||
return
|
||||
|
||||
if DROP_CONNECTION:
|
||||
self.disconnect()
|
||||
return
|
||||
|
||||
elif find(b"CAPA"):
|
||||
if INVALID_CAPABILITY_RESPONSE:
|
||||
self.sendLine(INVALID_RESPONSE)
|
||||
else:
|
||||
self.sendCapabilities()
|
||||
|
||||
elif find(b"STLS") and SSL_SUPPORT:
|
||||
self.startTLS()
|
||||
|
||||
elif find(b"USER"):
|
||||
if INVALID_LOGIN_RESPONSE:
|
||||
self.sendLine(INVALID_RESPONSE)
|
||||
return
|
||||
|
||||
resp = None
|
||||
try:
|
||||
self.tmpUser = line.split(" ")[1]
|
||||
resp = VALID_RESPONSE
|
||||
except BaseException:
|
||||
resp = AUTH_DECLINED
|
||||
|
||||
self.sendLine(resp)
|
||||
|
||||
elif find(b"PASS"):
|
||||
resp = None
|
||||
try:
|
||||
pwd = line.split(" ")[1]
|
||||
|
||||
if self.tmpUser is None or pwd is None:
|
||||
resp = AUTH_DECLINED
|
||||
elif self.tmpUser == USER and pwd == PASS:
|
||||
resp = AUTH_ACCEPTED
|
||||
self.loggedIn = True
|
||||
else:
|
||||
resp = AUTH_DECLINED
|
||||
except BaseException:
|
||||
resp = AUTH_DECLINED
|
||||
|
||||
self.sendLine(resp)
|
||||
|
||||
elif find(b"QUIT"):
|
||||
self.loggedIn = False
|
||||
self.sendLine(LOGOUT_COMPLETE)
|
||||
self.disconnect()
|
||||
|
||||
elif INVALID_SERVER_RESPONSE:
|
||||
self.sendLine(INVALID_RESPONSE)
|
||||
|
||||
elif not self.loggedIn:
|
||||
self.sendLine(NOT_LOGGED_IN)
|
||||
|
||||
elif find(b"NOOP"):
|
||||
self.sendLine(VALID_RESPONSE)
|
||||
|
||||
elif find(b"STAT"):
|
||||
if TIMEOUT_DEFERRED:
|
||||
return
|
||||
self.sendLine(STAT)
|
||||
|
||||
elif find(b"LIST"):
|
||||
if TIMEOUT_DEFERRED:
|
||||
return
|
||||
self.sendLine(LIST)
|
||||
|
||||
elif find(b"UIDL"):
|
||||
if TIMEOUT_DEFERRED:
|
||||
return
|
||||
elif not UIDL_SUPPORT:
|
||||
self.sendLine(INVALID_RESPONSE)
|
||||
return
|
||||
|
||||
self.sendLine(UIDL)
|
||||
|
||||
def startTLS(self):
|
||||
if SSL_SUPPORT and self.ctx is not None:
|
||||
self.sendLine(b"+OK Begin TLS negotiation now")
|
||||
self.transport.startTLS(self.ctx)
|
||||
else:
|
||||
self.sendLine(b"-ERR TLS not available")
|
||||
|
||||
def disconnect(self):
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
||||
usage = """popServer.py [arg] (default is Standard POP Server with no messages)
|
||||
no_ssl - Start with no SSL support
|
||||
no_uidl - Start with no UIDL support
|
||||
bad_resp - Send a non-RFC compliant response to the Client
|
||||
bad_cap_resp - send a non-RFC compliant response when the Client sends a 'CAPABILITY' request
|
||||
bad_login_resp - send a non-RFC compliant response when the Client sends a 'LOGIN' request
|
||||
deny - Deny the connection
|
||||
drop - Drop the connection after sending the greeting
|
||||
bad_tls - Send a bad response to a STARTTLS
|
||||
timeout - Do not return a response to a Client request
|
||||
to_deferred - Do not return a response on a 'Select' request. This
|
||||
will test Deferred callback handling
|
||||
slow - Wait 20 seconds after the connection is made to return a Server Greeting
|
||||
"""
|
||||
|
||||
|
||||
def printMessage(msg):
|
||||
print("Server Starting in %s mode" % msg)
|
||||
|
||||
|
||||
def processArg(arg):
|
||||
if arg.lower() == "no_ssl":
|
||||
global SSL_SUPPORT
|
||||
SSL_SUPPORT = False
|
||||
printMessage("NON-SSL")
|
||||
|
||||
elif arg.lower() == "no_uidl":
|
||||
global UIDL_SUPPORT
|
||||
UIDL_SUPPORT = False
|
||||
printMessage("NON-UIDL")
|
||||
|
||||
elif arg.lower() == "bad_resp":
|
||||
global INVALID_SERVER_RESPONSE
|
||||
INVALID_SERVER_RESPONSE = True
|
||||
printMessage("Invalid Server Response")
|
||||
|
||||
elif arg.lower() == "bad_cap_resp":
|
||||
global INVALID_CAPABILITY_RESPONSE
|
||||
INVALID_CAPABILITY_RESPONSE = True
|
||||
printMessage("Invalid Capability Response")
|
||||
|
||||
elif arg.lower() == "bad_login_resp":
|
||||
global INVALID_LOGIN_RESPONSE
|
||||
INVALID_LOGIN_RESPONSE = True
|
||||
printMessage("Invalid Capability Response")
|
||||
|
||||
elif arg.lower() == "deny":
|
||||
global DENY_CONNECTION
|
||||
DENY_CONNECTION = True
|
||||
printMessage("Deny Connection")
|
||||
|
||||
elif arg.lower() == "drop":
|
||||
global DROP_CONNECTION
|
||||
DROP_CONNECTION = True
|
||||
printMessage("Drop Connection")
|
||||
|
||||
elif arg.lower() == "bad_tls":
|
||||
global BAD_TLS_RESPONSE
|
||||
BAD_TLS_RESPONSE = True
|
||||
printMessage("Bad TLS Response")
|
||||
|
||||
elif arg.lower() == "timeout":
|
||||
global TIMEOUT_RESPONSE
|
||||
TIMEOUT_RESPONSE = True
|
||||
printMessage("Timeout Response")
|
||||
|
||||
elif arg.lower() == "to_deferred":
|
||||
global TIMEOUT_DEFERRED
|
||||
TIMEOUT_DEFERRED = True
|
||||
printMessage("Timeout Deferred Response")
|
||||
|
||||
elif arg.lower() == "slow":
|
||||
global SLOW_GREETING
|
||||
SLOW_GREETING = True
|
||||
printMessage("Slow Greeting")
|
||||
|
||||
elif arg.lower() == "--help":
|
||||
print(usage)
|
||||
sys.exit()
|
||||
|
||||
else:
|
||||
print(usage)
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
printMessage("POP3 with no messages")
|
||||
else:
|
||||
args = sys.argv[1:]
|
||||
|
||||
for arg in args:
|
||||
processArg(arg)
|
||||
|
||||
f = Factory()
|
||||
f.protocol = POP3TestServer
|
||||
reactor.listenTCP(PORT, f)
|
||||
reactor.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,86 @@
|
||||
Return-Path: <twisted-commits-admin@twistedmatrix.com>
|
||||
Delivered-To: exarkun@meson.dyndns.org
|
||||
Received: from localhost [127.0.0.1]
|
||||
by localhost with POP3 (fetchmail-6.2.1)
|
||||
for exarkun@localhost (single-drop); Thu, 20 Mar 2003 14:50:20 -0500 (EST)
|
||||
Received: from pyramid.twistedmatrix.com (adsl-64-123-27-105.dsl.austtx.swbell.net [64.123.27.105])
|
||||
by intarweb.us (Postfix) with ESMTP id 4A4A513EA4
|
||||
for <exarkun@meson.dyndns.org>; Thu, 20 Mar 2003 14:49:27 -0500 (EST)
|
||||
Received: from localhost ([127.0.0.1] helo=pyramid.twistedmatrix.com)
|
||||
by pyramid.twistedmatrix.com with esmtp (Exim 3.35 #1 (Debian))
|
||||
id 18w648-0007Vl-00; Thu, 20 Mar 2003 13:51:04 -0600
|
||||
Received: from acapnotic by pyramid.twistedmatrix.com with local (Exim 3.35 #1 (Debian))
|
||||
id 18w63j-0007VK-00
|
||||
for <twisted-commits@twistedmatrix.com>; Thu, 20 Mar 2003 13:50:39 -0600
|
||||
To: twisted-commits@twistedmatrix.com
|
||||
From: etrepum CVS <etrepum@twistedmatrix.com>
|
||||
Reply-To: twisted-python@twistedmatrix.com
|
||||
X-Mailer: CVSToys
|
||||
Message-Id: <E18w63j-0007VK-00@pyramid.twistedmatrix.com>
|
||||
Subject: [Twisted-commits] rebuild now works on python versions from 2.2.0 and up.
|
||||
Sender: twisted-commits-admin@twistedmatrix.com
|
||||
Errors-To: twisted-commits-admin@twistedmatrix.com
|
||||
X-BeenThere: twisted-commits@twistedmatrix.com
|
||||
X-Mailman-Version: 2.0.11
|
||||
Precedence: bulk
|
||||
List-Help: <mailto:twisted-commits-request@twistedmatrix.com?subject=help>
|
||||
List-Post: <mailto:twisted-commits@twistedmatrix.com>
|
||||
List-Subscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
|
||||
<mailto:twisted-commits-request@twistedmatrix.com?subject=subscribe>
|
||||
List-Id: <twisted-commits.twistedmatrix.com>
|
||||
List-Unsubscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
|
||||
<mailto:twisted-commits-request@twistedmatrix.com?subject=unsubscribe>
|
||||
List-Archive: <http://twistedmatrix.com/pipermail/twisted-commits/>
|
||||
Date: Thu, 20 Mar 2003 13:50:39 -0600
|
||||
|
||||
Modified files:
|
||||
Twisted/twisted/python/rebuild.py 1.19 1.20
|
||||
|
||||
Log message:
|
||||
rebuild now works on python versions from 2.2.0 and up.
|
||||
|
||||
|
||||
ViewCVS links:
|
||||
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/python/rebuild.py.diff?r1=text&tr1=1.19&r2=text&tr2=1.20&cvsroot=Twisted
|
||||
|
||||
Index: Twisted/twisted/python/rebuild.py
|
||||
diff -u Twisted/twisted/python/rebuild.py:1.19 Twisted/twisted/python/rebuild.py:1.20
|
||||
--- Twisted/twisted/python/rebuild.py:1.19 Fri Jan 17 13:50:49 2003
|
||||
+++ Twisted/twisted/python/rebuild.py Thu Mar 20 11:50:08 2003
|
||||
@@ -206,15 +206,27 @@
|
||||
clazz.__dict__.clear()
|
||||
clazz.__getattr__ = __getattr__
|
||||
clazz.__module__ = module.__name__
|
||||
+ if newclasses:
|
||||
+ import gc
|
||||
+ if (2, 2, 0) <= sys.version_info[:3] < (2, 2, 2):
|
||||
+ hasBrokenRebuild = 1
|
||||
+ gc_objects = gc.get_objects()
|
||||
+ else:
|
||||
+ hasBrokenRebuild = 0
|
||||
for nclass in newclasses:
|
||||
ga = getattr(module, nclass.__name__)
|
||||
if ga is nclass:
|
||||
log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
|
||||
else:
|
||||
- import gc
|
||||
- for r in gc.get_referrers(nclass):
|
||||
- if isinstance(r, nclass):
|
||||
+ if hasBrokenRebuild:
|
||||
+ for r in gc_objects:
|
||||
+ if not getattr(r, '__class__', None) is nclass:
|
||||
+ continue
|
||||
r.__class__ = ga
|
||||
+ else:
|
||||
+ for r in gc.get_referrers(nclass):
|
||||
+ if getattr(r, '__class__', None) is nclass:
|
||||
+ r.__class__ = ga
|
||||
if doLog:
|
||||
log.msg('')
|
||||
log.msg(' (fixing %s): ' % str(module.__name__))
|
||||
|
||||
|
||||
_______________________________________________
|
||||
Twisted-commits mailing list
|
||||
Twisted-commits@twistedmatrix.com
|
||||
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits
|
||||
@@ -0,0 +1,135 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
from __future__ import annotations
|
||||
|
||||
import email.message
|
||||
import email.parser
|
||||
from email.message import Message
|
||||
from io import BytesIO, StringIO
|
||||
from typing import IO, AnyStr, Callable
|
||||
|
||||
from twisted.mail import bounce
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class BounceTests(unittest.TestCase):
|
||||
"""
|
||||
Bounce message generation
|
||||
"""
|
||||
|
||||
def test_bounceMessageUnicode(self) -> None:
|
||||
"""
|
||||
L{twisted.mail.bounce.generateBounce} can accept L{unicode}.
|
||||
"""
|
||||
fromAddress, to, s = bounce.generateBounce(
|
||||
StringIO(
|
||||
"""\
|
||||
From: Moshe Zadka <moshez@example.com>
|
||||
To: nonexistent@example.org
|
||||
Subject: test
|
||||
|
||||
"""
|
||||
),
|
||||
"moshez@example.com",
|
||||
"nonexistent@example.org",
|
||||
)
|
||||
self.assertEqual(fromAddress, b"")
|
||||
self.assertEqual(to, b"moshez@example.com")
|
||||
emailParser = email.parser.Parser()
|
||||
mess = emailParser.parse(StringIO(s.decode("utf-8")))
|
||||
self.assertEqual(mess["To"], "moshez@example.com")
|
||||
self.assertEqual(mess["From"], "postmaster@example.org")
|
||||
self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
|
||||
|
||||
def test_bounceMessageBytes(self) -> None:
|
||||
"""
|
||||
L{twisted.mail.bounce.generateBounce} can accept L{bytes}.
|
||||
"""
|
||||
fromAddress, to, s = bounce.generateBounce(
|
||||
BytesIO(
|
||||
b"""\
|
||||
From: Moshe Zadka <moshez@example.com>
|
||||
To: nonexistent@example.org
|
||||
Subject: test
|
||||
|
||||
"""
|
||||
),
|
||||
b"moshez@example.com",
|
||||
b"nonexistent@example.org",
|
||||
)
|
||||
self.assertEqual(fromAddress, b"")
|
||||
self.assertEqual(to, b"moshez@example.com")
|
||||
emailParser = email.parser.Parser()
|
||||
mess = emailParser.parse(StringIO(s.decode("utf-8")))
|
||||
self.assertEqual(mess["To"], "moshez@example.com")
|
||||
self.assertEqual(mess["From"], "postmaster@example.org")
|
||||
self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
|
||||
|
||||
def test_bounceMessageCustomTranscript(self) -> None:
|
||||
"""
|
||||
Pass a custom transcript message to L{twisted.mail.bounce.generateBounce}.
|
||||
"""
|
||||
fromAddress, to, s = bounce.generateBounce(
|
||||
BytesIO(
|
||||
b"""\
|
||||
From: Moshe Zadka <moshez@example.com>
|
||||
To: nonexistent@example.org
|
||||
Subject: test
|
||||
|
||||
"""
|
||||
),
|
||||
b"moshez@example.com",
|
||||
b"nonexistent@example.org",
|
||||
"Custom transcript",
|
||||
)
|
||||
self.assertEqual(fromAddress, b"")
|
||||
self.assertEqual(to, b"moshez@example.com")
|
||||
emailParser = email.parser.Parser()
|
||||
mess = emailParser.parse(StringIO(s.decode("utf-8")))
|
||||
self.assertEqual(mess["To"], "moshez@example.com")
|
||||
self.assertEqual(mess["From"], "postmaster@example.org")
|
||||
self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
|
||||
self.assertTrue(mess.is_multipart())
|
||||
parts: list[Message] = mess.get_payload() # type:ignore[assignment]
|
||||
self.assertEqual(
|
||||
parts[0].get_payload(),
|
||||
"Custom transcript\n",
|
||||
)
|
||||
|
||||
def _bounceBigMessage(
|
||||
self, header: AnyStr, message: AnyStr, ioType: Callable[[AnyStr], IO[AnyStr]]
|
||||
) -> None:
|
||||
"""
|
||||
Pass a really big message to L{twisted.mail.bounce.generateBounce}.
|
||||
"""
|
||||
fromAddress, to, s = bounce.generateBounce(
|
||||
ioType(header + message), "moshez@example.com", "nonexistent@example.org"
|
||||
)
|
||||
emailParser = email.parser.Parser()
|
||||
mess = emailParser.parse(StringIO(s.decode("utf-8")))
|
||||
self.assertEqual(mess["To"], "moshez@example.com")
|
||||
self.assertEqual(mess["From"], "postmaster@example.org")
|
||||
self.assertEqual(mess["subject"], "Returned Mail: see transcript for details")
|
||||
self.assertTrue(mess.is_multipart())
|
||||
parts: list[Message] = mess.get_payload() # type:ignore[assignment]
|
||||
innerMessage: list[Message] = parts[1].get_payload() # type:ignore[assignment]
|
||||
if isinstance(message, bytes):
|
||||
messageText = message.decode("utf-8")
|
||||
else:
|
||||
messageText = message
|
||||
pl: str = innerMessage[0].get_payload() # type:ignore[assignment]
|
||||
self.assertEqual(pl + "\n", messageText)
|
||||
|
||||
def test_bounceBigMessage(self) -> None:
|
||||
"""
|
||||
L{twisted.mail.bounce.generateBounce} with big L{unicode} and
|
||||
L{bytes} messages.
|
||||
"""
|
||||
header = b"""\
|
||||
From: Moshe Zadka <moshez@example.com>
|
||||
To: nonexistent@example.org
|
||||
Subject: test
|
||||
|
||||
"""
|
||||
self._bounceBigMessage(header, b"Test test\n" * 10000, BytesIO)
|
||||
self._bounceBigMessage(header.decode("utf-8"), "More test\n" * 10000, StringIO)
|
||||
7974
backend/lib/python3.12/site-packages/twisted/mail/test/test_imap.py
Normal file
7974
backend/lib/python3.12/site-packages/twisted/mail/test/test_imap.py
Normal file
File diff suppressed because it is too large
Load Diff
2643
backend/lib/python3.12/site-packages/twisted/mail/test/test_mail.py
Normal file
2643
backend/lib/python3.12/site-packages/twisted/mail/test/test_mail.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,391 @@
|
||||
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.mail.scripts.mailmail}, the implementation of the
|
||||
command line program I{mailmail}.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from unittest import skipIf
|
||||
|
||||
from twisted.copyright import version
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.internet.testing import MemoryReactor
|
||||
from twisted.mail import smtp
|
||||
from twisted.mail.scripts import mailmail
|
||||
from twisted.mail.scripts.mailmail import parseOptions
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.python.runtime import platformType
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
|
||||
class OptionsTests(TestCase):
|
||||
"""
|
||||
Tests for L{parseOptions} which parses command line arguments and reads
|
||||
message text from stdin to produce an L{Options} instance which can be
|
||||
used to send a message.
|
||||
"""
|
||||
|
||||
memoryReactor = MemoryReactor()
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Override some things in mailmail, so that we capture C{stdout},
|
||||
and do not call L{reactor.stop}.
|
||||
"""
|
||||
self.out = StringIO()
|
||||
# Override the mailmail logger, so we capture stderr output
|
||||
from twisted.logger import Logger, textFileLogObserver
|
||||
|
||||
logObserver = textFileLogObserver(self.out)
|
||||
self.patch(mailmail, "_log", Logger(observer=logObserver))
|
||||
self.host = None
|
||||
self.options = None
|
||||
self.ident = None
|
||||
|
||||
# Override mailmail.sendmail, so we don't call reactor.stop()
|
||||
def sendmail(host, options, ident):
|
||||
self.host = host
|
||||
self.options = options
|
||||
self.ident = ident
|
||||
return smtp.sendmail(
|
||||
host,
|
||||
options.sender,
|
||||
options.to,
|
||||
options.body,
|
||||
reactor=self.memoryReactor,
|
||||
)
|
||||
|
||||
self.patch(mailmail, "sendmail", sendmail)
|
||||
|
||||
def test_unspecifiedRecipients(self):
|
||||
"""
|
||||
If no recipients are given in the argument list and there is no
|
||||
recipient header in the message text, L{parseOptions} raises
|
||||
L{SystemExit} with a string describing the problem.
|
||||
"""
|
||||
self.patch(sys, "stdin", StringIO("Subject: foo\n" "\n" "Hello, goodbye.\n"))
|
||||
exc = self.assertRaises(SystemExit, parseOptions, [])
|
||||
self.assertEqual(exc.args, ("No recipients specified.",))
|
||||
|
||||
def test_listQueueInformation(self):
|
||||
"""
|
||||
The I{-bp} option for listing queue information is unsupported and
|
||||
if it is passed to L{parseOptions}, L{SystemExit} is raised.
|
||||
"""
|
||||
exc = self.assertRaises(SystemExit, parseOptions, ["-bp"])
|
||||
self.assertEqual(exc.args, ("Unsupported option.",))
|
||||
|
||||
def test_stdioTransport(self):
|
||||
"""
|
||||
The I{-bs} option for using stdin and stdout as the SMTP transport
|
||||
is unsupported and if it is passed to L{parseOptions}, L{SystemExit}
|
||||
is raised.
|
||||
"""
|
||||
exc = self.assertRaises(SystemExit, parseOptions, ["-bs"])
|
||||
self.assertEqual(exc.args, ("Unsupported option.",))
|
||||
|
||||
def test_ignoreFullStop(self):
|
||||
"""
|
||||
The I{-i} and I{-oi} options for ignoring C{"."} by itself on a line
|
||||
are unsupported and if either is passed to L{parseOptions},
|
||||
L{SystemExit} is raised.
|
||||
"""
|
||||
exc = self.assertRaises(SystemExit, parseOptions, ["-i"])
|
||||
self.assertEqual(exc.args, ("Unsupported option.",))
|
||||
exc = self.assertRaises(SystemExit, parseOptions, ["-oi"])
|
||||
self.assertEqual(exc.args, ("Unsupported option.",))
|
||||
|
||||
def test_copyAliasedSender(self):
|
||||
"""
|
||||
The I{-om} option for copying the sender if they appear in an alias
|
||||
expansion is unsupported and if it is passed to L{parseOptions},
|
||||
L{SystemExit} is raised.
|
||||
"""
|
||||
exc = self.assertRaises(SystemExit, parseOptions, ["-om"])
|
||||
self.assertEqual(exc.args, ("Unsupported option.",))
|
||||
|
||||
def test_version(self):
|
||||
"""
|
||||
The I{--version} option displays the version and raises
|
||||
L{SystemExit} with L{None} as the exit code.
|
||||
"""
|
||||
out = StringIO()
|
||||
self.patch(sys, "stdout", out)
|
||||
systemExitCode = self.assertRaises(SystemExit, parseOptions, "--version")
|
||||
# SystemExit.code is None on success
|
||||
self.assertEqual(systemExitCode.code, None)
|
||||
data = out.getvalue()
|
||||
self.assertEqual(data, f"mailmail version: {version}\n")
|
||||
|
||||
def test_backgroundDelivery(self):
|
||||
"""
|
||||
The I{-odb} flag specifies background delivery.
|
||||
"""
|
||||
stdin = StringIO("\n")
|
||||
self.patch(sys, "stdin", stdin)
|
||||
o = parseOptions("-odb")
|
||||
self.assertTrue(o.background)
|
||||
|
||||
def test_foregroundDelivery(self):
|
||||
"""
|
||||
The I{-odf} flags specifies foreground delivery.
|
||||
"""
|
||||
stdin = StringIO("\n")
|
||||
self.patch(sys, "stdin", stdin)
|
||||
o = parseOptions("-odf")
|
||||
self.assertFalse(o.background)
|
||||
|
||||
def test_recipientsFromHeaders(self):
|
||||
"""
|
||||
The I{-t} flags specifies that recipients should be obtained
|
||||
from headers.
|
||||
"""
|
||||
stdin = StringIO(
|
||||
"To: Curly <invaliduser2@example.com>\n"
|
||||
"Cc: Larry <invaliduser1@example.com>\n"
|
||||
"Bcc: Moe <invaliduser3@example.com>\n"
|
||||
"\n"
|
||||
"Oh, a wise guy?\n"
|
||||
)
|
||||
self.patch(sys, "stdin", stdin)
|
||||
o = parseOptions("-t")
|
||||
self.assertEqual(len(o.to), 3)
|
||||
|
||||
def test_setFrom(self):
|
||||
"""
|
||||
When a message has no I{From:} header, a I{From:} value can be
|
||||
specified with the I{-F} flag.
|
||||
"""
|
||||
stdin = StringIO("To: invaliduser2@example.com\n" "Subject: A wise guy?\n\n")
|
||||
self.patch(sys, "stdin", stdin)
|
||||
o = parseOptions(["-F", "Larry <invaliduser1@example.com>", "-t"])
|
||||
self.assertEqual(o.sender, "Larry <invaliduser1@example.com>")
|
||||
|
||||
def test_overrideFromFlagByFromHeader(self):
|
||||
"""
|
||||
The I{-F} flag specifies the From: value. However, I{-F} flag is
|
||||
overriden by the value of From: in the e-mail header.
|
||||
"""
|
||||
stdin = StringIO(
|
||||
"To: Curly <invaliduser4@example.com>\n"
|
||||
"From: Shemp <invaliduser4@example.com>\n"
|
||||
)
|
||||
self.patch(sys, "stdin", stdin)
|
||||
o = parseOptions(["-F", "Groucho <invaliduser5@example.com>", "-t"])
|
||||
self.assertEqual(o.sender, "invaliduser4@example.com")
|
||||
|
||||
@skipIf(
|
||||
platformType == "win32",
|
||||
"mailmail.run() does not work on win32 due to lack of support for" " getuid()",
|
||||
)
|
||||
def test_runErrorsToStderr(self):
|
||||
"""
|
||||
Call L{mailmail.run}, and specify I{-oep} to print errors
|
||||
to stderr. The sender, to, and printErrors options should be
|
||||
set and there should be no failure.
|
||||
"""
|
||||
argv = ("test_mailmail.py", "invaliduser2@example.com", "-oep")
|
||||
stdin = StringIO("\n")
|
||||
self.patch(sys, "argv", argv)
|
||||
self.patch(sys, "stdin", stdin)
|
||||
mailmail.run()
|
||||
self.assertEqual(self.options.sender, mailmail.getlogin())
|
||||
self.assertEqual(self.options.to, ["invaliduser2@example.com"])
|
||||
# We should have printErrors set because we specified "-oep"
|
||||
self.assertTrue(self.options.printErrors)
|
||||
# We should not have any failures.
|
||||
self.assertIsNone(mailmail.failed)
|
||||
|
||||
@skipIf(
|
||||
platformType == "win32",
|
||||
"mailmail.run() does not work on win32 due to lack of support for" " getuid()",
|
||||
)
|
||||
def test_readInvalidConfig(self):
|
||||
"""
|
||||
Error messages for illegal UID value, illegal GID value, and illegal
|
||||
identity entry will be sent to stderr.
|
||||
"""
|
||||
stdin = StringIO("\n")
|
||||
self.patch(sys, "stdin", stdin)
|
||||
|
||||
filename = self.mktemp()
|
||||
myUid = os.getuid()
|
||||
myGid = os.getgid()
|
||||
|
||||
with open(filename, "w") as f:
|
||||
# Create a config file with some invalid values
|
||||
f.write(
|
||||
"[useraccess]\n"
|
||||
"allow=invaliduser2,invaliduser1\n"
|
||||
"deny=invaliduser3,invaliduser4,{}\n"
|
||||
"order=allow,deny\n"
|
||||
"[groupaccess]\n"
|
||||
"allow=invalidgid1,invalidgid2\n"
|
||||
"deny=invalidgid1,invalidgid2,{}\n"
|
||||
"order=deny,allow\n"
|
||||
"[identity]\n"
|
||||
"localhost=funny\n"
|
||||
"[addresses]\n"
|
||||
"smarthost=localhost\n"
|
||||
"default_domain=example.com\n".format(myUid, myGid)
|
||||
)
|
||||
|
||||
# The mailmail script looks in
|
||||
# the twisted.mail.scripts.GLOBAL_CFG variable
|
||||
# and then the twisted.mail.scripts.LOCAL_CFG
|
||||
# variable for the path to it's config file.
|
||||
#
|
||||
# Override twisted.mail.scripts.LOCAL_CFG with the file we just
|
||||
# created.
|
||||
self.patch(mailmail, "LOCAL_CFG", filename)
|
||||
|
||||
argv = ("test_mailmail.py", "invaliduser2@example.com", "-oep")
|
||||
self.patch(sys, "argv", argv)
|
||||
mailmail.run()
|
||||
self.assertRegex(
|
||||
self.out.getvalue(),
|
||||
"Illegal UID in \\[useraccess\\] section: " "invaliduser1",
|
||||
)
|
||||
self.assertRegex(
|
||||
self.out.getvalue(),
|
||||
"Illegal GID in \\[groupaccess\\] section: " "invalidgid1",
|
||||
)
|
||||
self.assertRegex(
|
||||
self.out.getvalue(), "Illegal entry in \\[identity\\] section: funny"
|
||||
)
|
||||
|
||||
def getConfigFromFile(self, config):
|
||||
"""
|
||||
Read a mailmail configuration file.
|
||||
|
||||
The mailmail script checks the twisted.mail.scripts.mailmail.GLOBAL_CFG
|
||||
variable and then the twisted.mail.scripts.mailmail.LOCAL_CFG
|
||||
variable for the path to its config file.
|
||||
|
||||
@param config: path to config file
|
||||
@type config: L{str}
|
||||
|
||||
@return: A parsed config.
|
||||
@rtype: L{twisted.mail.scripts.mailmail.Configuration}
|
||||
"""
|
||||
|
||||
from twisted.mail.scripts.mailmail import loadConfig
|
||||
|
||||
filename = self.mktemp()
|
||||
|
||||
with open(filename, "w") as f:
|
||||
f.write(config)
|
||||
|
||||
return loadConfig(filename)
|
||||
|
||||
def test_loadConfig(self):
|
||||
"""
|
||||
L{twisted.mail.scripts.mailmail.loadConfig}
|
||||
parses the config file for mailmail.
|
||||
"""
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[addresses]
|
||||
smarthost=localhost"""
|
||||
)
|
||||
self.assertEqual(config.smarthost, "localhost")
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[addresses]
|
||||
default_domain=example.com"""
|
||||
)
|
||||
self.assertEqual(config.domain, "example.com")
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[addresses]
|
||||
smarthost=localhost
|
||||
default_domain=example.com"""
|
||||
)
|
||||
self.assertEqual(config.smarthost, "localhost")
|
||||
self.assertEqual(config.domain, "example.com")
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[identity]
|
||||
host1=invalid
|
||||
host2=username:password"""
|
||||
)
|
||||
self.assertNotIn("host1", config.identities)
|
||||
self.assertEqual(config.identities["host2"], ["username", "password"])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[useraccess]
|
||||
allow=invalid1,35
|
||||
order=allow"""
|
||||
)
|
||||
self.assertEqual(config.allowUIDs, [35])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[useraccess]
|
||||
deny=35,36
|
||||
order=deny"""
|
||||
)
|
||||
self.assertEqual(config.denyUIDs, [35, 36])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[useraccess]
|
||||
allow=35,36
|
||||
deny=37,38
|
||||
order=deny"""
|
||||
)
|
||||
self.assertEqual(config.allowUIDs, [35, 36])
|
||||
self.assertEqual(config.denyUIDs, [37, 38])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[groupaccess]
|
||||
allow=gid1,41
|
||||
order=allow"""
|
||||
)
|
||||
self.assertEqual(config.allowGIDs, [41])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[groupaccess]
|
||||
deny=41
|
||||
order=deny"""
|
||||
)
|
||||
self.assertEqual(config.denyGIDs, [41])
|
||||
|
||||
config = self.getConfigFromFile(
|
||||
"""
|
||||
[groupaccess]
|
||||
allow=41,42
|
||||
deny=43,44
|
||||
order=allow,deny"""
|
||||
)
|
||||
self.assertEqual(config.allowGIDs, [41, 42])
|
||||
self.assertEqual(config.denyGIDs, [43, 44])
|
||||
|
||||
def test_senderror(self):
|
||||
"""
|
||||
L{twisted.mail.scripts.mailmail.senderror} sends mail back to the
|
||||
sender if an error occurs while sending mail to the recipient.
|
||||
"""
|
||||
|
||||
def sendmail(host, sender, recipient, body):
|
||||
self.assertRegex(sender, "postmaster@")
|
||||
self.assertEqual(recipient, ["testsender"])
|
||||
self.assertRegex(body.getvalue(), "ValueError")
|
||||
return Deferred()
|
||||
|
||||
self.patch(smtp, "sendmail", sendmail)
|
||||
opts = mailmail.Options()
|
||||
opts.sender = "testsender"
|
||||
fail = Failure(ValueError())
|
||||
mailmail.senderror(fail, opts)
|
||||
@@ -0,0 +1,183 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.mail.tap}.
|
||||
"""
|
||||
|
||||
from twisted.internet import defer, endpoints
|
||||
from twisted.mail import protocols
|
||||
from twisted.mail.tap import Options, makeService
|
||||
from twisted.python.usage import UsageError
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
|
||||
class OptionsTests(TestCase):
|
||||
"""
|
||||
Tests for the command line option parser used for I{twistd mail}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.aliasFilename = self.mktemp()
|
||||
with open(self.aliasFilename, "w") as aliasFile:
|
||||
aliasFile.write("someuser:\tdifferentuser\n")
|
||||
|
||||
def testAliasesWithoutDomain(self):
|
||||
"""
|
||||
Test that adding an aliases(5) file before adding a domain raises a
|
||||
UsageError.
|
||||
"""
|
||||
self.assertRaises(
|
||||
UsageError, Options().parseOptions, ["--aliases", self.aliasFilename]
|
||||
)
|
||||
|
||||
def testAliases(self):
|
||||
"""
|
||||
Test that adding an aliases(5) file to an IAliasableDomain at least
|
||||
doesn't raise an unhandled exception.
|
||||
"""
|
||||
Options().parseOptions(
|
||||
[
|
||||
"--maildirdbmdomain",
|
||||
"example.com=example.com",
|
||||
"--aliases",
|
||||
self.aliasFilename,
|
||||
]
|
||||
)
|
||||
|
||||
def _endpointTest(self, service):
|
||||
"""
|
||||
Use L{Options} to parse a single service configuration parameter and
|
||||
verify that an endpoint of the correct type is added to the list for
|
||||
that service.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(["--" + service, "tcp:1234"])
|
||||
self.assertEqual(len(options[service]), 1)
|
||||
self.assertIsInstance(options[service][0], endpoints.TCP4ServerEndpoint)
|
||||
|
||||
def test_endpointSMTP(self):
|
||||
"""
|
||||
When I{--smtp} is given a TCP endpoint description as an argument, a
|
||||
TCPServerEndpoint is added to the list of SMTP endpoints.
|
||||
"""
|
||||
self._endpointTest("smtp")
|
||||
|
||||
def test_endpointPOP3(self):
|
||||
"""
|
||||
When I{--pop3} is given a TCP endpoint description as an argument, a
|
||||
TCPServerEndpoint is added to the list of POP3 endpoints.
|
||||
"""
|
||||
self._endpointTest("pop3")
|
||||
|
||||
def test_protoDefaults(self):
|
||||
"""
|
||||
POP3 and SMTP each listen on a TCP4ServerEndpoint by default.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions([])
|
||||
|
||||
self.assertEqual(len(options["pop3"]), 1)
|
||||
self.assertIsInstance(options["pop3"][0], endpoints.TCP4ServerEndpoint)
|
||||
|
||||
self.assertEqual(len(options["smtp"]), 1)
|
||||
self.assertIsInstance(options["smtp"][0], endpoints.TCP4ServerEndpoint)
|
||||
|
||||
def test_protoDisable(self):
|
||||
"""
|
||||
The I{--no-pop3} and I{--no-smtp} options disable POP3 and SMTP
|
||||
respectively.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(["--no-pop3"])
|
||||
self.assertEqual(options._getEndpoints(None, "pop3"), [])
|
||||
self.assertNotEqual(options._getEndpoints(None, "smtp"), [])
|
||||
|
||||
options = Options()
|
||||
options.parseOptions(["--no-smtp"])
|
||||
self.assertNotEqual(options._getEndpoints(None, "pop3"), [])
|
||||
self.assertEqual(options._getEndpoints(None, "smtp"), [])
|
||||
|
||||
def test_allProtosDisabledError(self):
|
||||
"""
|
||||
If all protocols are disabled, L{UsageError} is raised.
|
||||
"""
|
||||
options = Options()
|
||||
self.assertRaises(
|
||||
UsageError, options.parseOptions, (["--no-pop3", "--no-smtp"])
|
||||
)
|
||||
|
||||
def test_esmtpWithoutHostname(self):
|
||||
"""
|
||||
If I{--esmtp} is given without I{--hostname}, L{Options.parseOptions}
|
||||
raises L{UsageError}.
|
||||
"""
|
||||
options = Options()
|
||||
exc = self.assertRaises(UsageError, options.parseOptions, ["--esmtp"])
|
||||
self.assertEqual("--esmtp requires --hostname", str(exc))
|
||||
|
||||
def test_auth(self):
|
||||
"""
|
||||
Tests that the --auth option registers a checker.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(["--auth", "memory:admin:admin:bob:password"])
|
||||
self.assertEqual(len(options["credCheckers"]), 1)
|
||||
checker = options["credCheckers"][0]
|
||||
interfaces = checker.credentialInterfaces
|
||||
registered_checkers = options.service.smtpPortal.checkers
|
||||
for iface in interfaces:
|
||||
self.assertEqual(checker, registered_checkers[iface])
|
||||
|
||||
|
||||
class SpyEndpoint:
|
||||
"""
|
||||
SpyEndpoint remembers what factory it is told to listen with.
|
||||
"""
|
||||
|
||||
listeningWith = None
|
||||
|
||||
def listen(self, factory):
|
||||
self.listeningWith = factory
|
||||
return defer.succeed(None)
|
||||
|
||||
|
||||
class MakeServiceTests(TestCase):
|
||||
"""
|
||||
Tests for L{twisted.mail.tap.makeService}
|
||||
"""
|
||||
|
||||
def _endpointServerTest(self, key, factoryClass):
|
||||
"""
|
||||
Configure a service with two endpoints for the protocol associated with
|
||||
C{key} and verify that when the service is started a factory of type
|
||||
C{factoryClass} is used to listen on each of them.
|
||||
"""
|
||||
cleartext = SpyEndpoint()
|
||||
secure = SpyEndpoint()
|
||||
config = Options()
|
||||
config[key] = [cleartext, secure]
|
||||
service = makeService(config)
|
||||
service.privilegedStartService()
|
||||
service.startService()
|
||||
self.addCleanup(service.stopService)
|
||||
self.assertIsInstance(cleartext.listeningWith, factoryClass)
|
||||
self.assertIsInstance(secure.listeningWith, factoryClass)
|
||||
|
||||
def test_pop3(self):
|
||||
"""
|
||||
If one or more endpoints is included in the configuration passed to
|
||||
L{makeService} for the C{"pop3"} key, a service for starting a POP3
|
||||
server is constructed for each of them and attached to the returned
|
||||
service.
|
||||
"""
|
||||
self._endpointServerTest("pop3", protocols.POP3Factory)
|
||||
|
||||
def test_smtp(self):
|
||||
"""
|
||||
If one or more endpoints is included in the configuration passed to
|
||||
L{makeService} for the C{"smtp"} key, a service for starting an SMTP
|
||||
server is constructed for each of them and attached to the returned
|
||||
service.
|
||||
"""
|
||||
self._endpointServerTest("smtp", protocols.SMTPFactory)
|
||||
1613
backend/lib/python3.12/site-packages/twisted/mail/test/test_pop3.py
Normal file
1613
backend/lib/python3.12/site-packages/twisted/mail/test/test_pop3.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,639 @@
|
||||
# -*- test-case-name: twisted.mail.test.test_pop3client -*-
|
||||
# Copyright (c) 2001-2004 Divmod Inc.
|
||||
# See LICENSE for details.
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
from typing import List
|
||||
from unittest import skipIf
|
||||
|
||||
from zope.interface import directlyProvides
|
||||
|
||||
import twisted.mail._pop3client
|
||||
from twisted.internet import defer, error, interfaces, protocol, reactor
|
||||
from twisted.internet.testing import StringTransport
|
||||
from twisted.mail.pop3 import (
|
||||
AdvancedPOP3Client as POP3Client,
|
||||
InsecureAuthenticationDisallowed,
|
||||
ServerErrorResponse,
|
||||
)
|
||||
from twisted.mail.test import pop3testserver
|
||||
from twisted.protocols import basic, loopback
|
||||
from twisted.python import log
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
try:
|
||||
from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
|
||||
except ImportError:
|
||||
ClientTLSContext = None # type: ignore[assignment,misc]
|
||||
ServerTLSContext = None # type: ignore[assignment,misc]
|
||||
|
||||
|
||||
class StringTransportWithConnectionLosing(StringTransport):
|
||||
def loseConnection(self):
|
||||
self.protocol.connectionLost(error.ConnectionDone())
|
||||
|
||||
|
||||
capCache = {
|
||||
b"TOP": None,
|
||||
b"LOGIN-DELAY": b"180",
|
||||
b"UIDL": None,
|
||||
b"STLS": None,
|
||||
b"USER": None,
|
||||
b"SASL": b"LOGIN",
|
||||
}
|
||||
|
||||
|
||||
def setUp(greet=True):
|
||||
p = POP3Client()
|
||||
|
||||
# Skip the CAPA login will issue if it doesn't already have a
|
||||
# capability cache
|
||||
p._capCache = capCache
|
||||
|
||||
t = StringTransportWithConnectionLosing()
|
||||
t.protocol = p
|
||||
p.makeConnection(t)
|
||||
|
||||
if greet:
|
||||
p.dataReceived(b"+OK Hello!\r\n")
|
||||
|
||||
return p, t
|
||||
|
||||
|
||||
def strip(f):
|
||||
return lambda result, f=f: f()
|
||||
|
||||
|
||||
class POP3ClientLoginTests(TestCase):
|
||||
def testNegativeGreeting(self):
|
||||
p, t = setUp(greet=False)
|
||||
p.allowInsecureLogin = True
|
||||
d = p.login(b"username", b"password")
|
||||
p.dataReceived(b"-ERR Offline for maintenance\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"Offline for maintenance")
|
||||
)
|
||||
|
||||
def testOkUser(self):
|
||||
p, t = setUp()
|
||||
d = p.user(b"username")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
p.dataReceived(b"+OK send password\r\n")
|
||||
return d.addCallback(self.assertEqual, b"send password")
|
||||
|
||||
def testBadUser(self):
|
||||
p, t = setUp()
|
||||
d = p.user(b"username")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
p.dataReceived(b"-ERR account suspended\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"account suspended")
|
||||
)
|
||||
|
||||
def testOkPass(self):
|
||||
p, t = setUp()
|
||||
d = p.password(b"password")
|
||||
self.assertEqual(t.value(), b"PASS password\r\n")
|
||||
p.dataReceived(b"+OK you're in!\r\n")
|
||||
return d.addCallback(self.assertEqual, b"you're in!")
|
||||
|
||||
def testBadPass(self):
|
||||
p, t = setUp()
|
||||
d = p.password(b"password")
|
||||
self.assertEqual(t.value(), b"PASS password\r\n")
|
||||
p.dataReceived(b"-ERR go away\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"go away")
|
||||
)
|
||||
|
||||
def testOkLogin(self):
|
||||
p, t = setUp()
|
||||
p.allowInsecureLogin = True
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
p.dataReceived(b"+OK go ahead\r\n")
|
||||
self.assertEqual(t.value(), b"USER username\r\nPASS password\r\n")
|
||||
p.dataReceived(b"+OK password accepted\r\n")
|
||||
return d.addCallback(self.assertEqual, b"password accepted")
|
||||
|
||||
def testBadPasswordLogin(self):
|
||||
p, t = setUp()
|
||||
p.allowInsecureLogin = True
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
p.dataReceived(b"+OK waiting on you\r\n")
|
||||
self.assertEqual(t.value(), b"USER username\r\nPASS password\r\n")
|
||||
p.dataReceived(b"-ERR bogus login\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"bogus login")
|
||||
)
|
||||
|
||||
def testBadUsernameLogin(self):
|
||||
p, t = setUp()
|
||||
p.allowInsecureLogin = True
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
p.dataReceived(b"-ERR bogus login\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"bogus login")
|
||||
)
|
||||
|
||||
def testServerGreeting(self):
|
||||
p, t = setUp(greet=False)
|
||||
p.dataReceived(b"+OK lalala this has no challenge\r\n")
|
||||
self.assertEqual(p.serverChallenge, None)
|
||||
|
||||
def testServerGreetingWithChallenge(self):
|
||||
p, t = setUp(greet=False)
|
||||
p.dataReceived(b"+OK <here is the challenge>\r\n")
|
||||
self.assertEqual(p.serverChallenge, b"<here is the challenge>")
|
||||
|
||||
def testAPOP(self):
|
||||
p, t = setUp(greet=False)
|
||||
p.dataReceived(b"+OK <challenge string goes here>\r\n")
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertEqual(
|
||||
t.value(), b"APOP username f34f1e464d0d7927607753129cabe39a\r\n"
|
||||
)
|
||||
p.dataReceived(b"+OK Welcome!\r\n")
|
||||
return d.addCallback(self.assertEqual, b"Welcome!")
|
||||
|
||||
def testInsecureLoginRaisesException(self):
|
||||
p, t = setUp(greet=False)
|
||||
p.dataReceived(b"+OK Howdy\r\n")
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertFalse(t.value())
|
||||
return self.assertFailure(d, InsecureAuthenticationDisallowed)
|
||||
|
||||
def testSSLTransportConsideredSecure(self):
|
||||
"""
|
||||
If a server doesn't offer APOP but the transport is secured using
|
||||
SSL or TLS, a plaintext login should be allowed, not rejected with
|
||||
an InsecureAuthenticationDisallowed exception.
|
||||
"""
|
||||
p, t = setUp(greet=False)
|
||||
directlyProvides(t, interfaces.ISSLTransport)
|
||||
p.dataReceived(b"+OK Howdy\r\n")
|
||||
d = p.login(b"username", b"password")
|
||||
self.assertEqual(t.value(), b"USER username\r\n")
|
||||
t.clear()
|
||||
p.dataReceived(b"+OK\r\n")
|
||||
self.assertEqual(t.value(), b"PASS password\r\n")
|
||||
p.dataReceived(b"+OK\r\n")
|
||||
return d
|
||||
|
||||
|
||||
class ListConsumer:
|
||||
def __init__(self):
|
||||
self.data = {}
|
||||
|
||||
def consume(self, result):
|
||||
(item, value) = result
|
||||
self.data.setdefault(item, []).append(value)
|
||||
|
||||
|
||||
class MessageConsumer:
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
|
||||
def consume(self, line):
|
||||
self.data.append(line)
|
||||
|
||||
|
||||
class POP3ClientListTests(TestCase):
|
||||
def testListSize(self):
|
||||
p, t = setUp()
|
||||
d = p.listSize()
|
||||
self.assertEqual(t.value(), b"LIST\r\n")
|
||||
p.dataReceived(b"+OK Here it comes\r\n")
|
||||
p.dataReceived(b"1 3\r\n2 2\r\n3 1\r\n.\r\n")
|
||||
return d.addCallback(self.assertEqual, [3, 2, 1])
|
||||
|
||||
def testListSizeWithConsumer(self):
|
||||
p, t = setUp()
|
||||
c = ListConsumer()
|
||||
f = c.consume
|
||||
d = p.listSize(f)
|
||||
self.assertEqual(t.value(), b"LIST\r\n")
|
||||
p.dataReceived(b"+OK Here it comes\r\n")
|
||||
p.dataReceived(b"1 3\r\n2 2\r\n3 1\r\n")
|
||||
self.assertEqual(c.data, {0: [3], 1: [2], 2: [1]})
|
||||
p.dataReceived(b"5 3\r\n6 2\r\n7 1\r\n")
|
||||
self.assertEqual(c.data, {0: [3], 1: [2], 2: [1], 4: [3], 5: [2], 6: [1]})
|
||||
p.dataReceived(b".\r\n")
|
||||
return d.addCallback(self.assertIdentical, f)
|
||||
|
||||
def testFailedListSize(self):
|
||||
p, t = setUp()
|
||||
d = p.listSize()
|
||||
self.assertEqual(t.value(), b"LIST\r\n")
|
||||
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"Fatal doom server exploded")
|
||||
)
|
||||
|
||||
def testListUID(self):
|
||||
p, t = setUp()
|
||||
d = p.listUID()
|
||||
self.assertEqual(t.value(), b"UIDL\r\n")
|
||||
p.dataReceived(b"+OK Here it comes\r\n")
|
||||
p.dataReceived(b"1 abc\r\n2 def\r\n3 ghi\r\n.\r\n")
|
||||
return d.addCallback(self.assertEqual, [b"abc", b"def", b"ghi"])
|
||||
|
||||
def testListUIDWithConsumer(self):
|
||||
p, t = setUp()
|
||||
c = ListConsumer()
|
||||
f = c.consume
|
||||
d = p.listUID(f)
|
||||
self.assertEqual(t.value(), b"UIDL\r\n")
|
||||
p.dataReceived(b"+OK Here it comes\r\n")
|
||||
p.dataReceived(b"1 xyz\r\n2 abc\r\n5 mno\r\n")
|
||||
self.assertEqual(c.data, {0: [b"xyz"], 1: [b"abc"], 4: [b"mno"]})
|
||||
p.dataReceived(b".\r\n")
|
||||
return d.addCallback(self.assertIdentical, f)
|
||||
|
||||
def testFailedListUID(self):
|
||||
p, t = setUp()
|
||||
d = p.listUID()
|
||||
self.assertEqual(t.value(), b"UIDL\r\n")
|
||||
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"Fatal doom server exploded")
|
||||
)
|
||||
|
||||
|
||||
class POP3ClientMessageTests(TestCase):
|
||||
def testRetrieve(self):
|
||||
p, t = setUp()
|
||||
d = p.retrieve(7)
|
||||
self.assertEqual(t.value(), b"RETR 8\r\n")
|
||||
p.dataReceived(b"+OK Message incoming\r\n")
|
||||
p.dataReceived(b"La la la here is message text\r\n")
|
||||
p.dataReceived(b"..Further message text tra la la\r\n")
|
||||
p.dataReceived(b".\r\n")
|
||||
return d.addCallback(
|
||||
self.assertEqual,
|
||||
[b"La la la here is message text", b".Further message text tra la la"],
|
||||
)
|
||||
|
||||
def testRetrieveWithConsumer(self):
|
||||
p, t = setUp()
|
||||
c = MessageConsumer()
|
||||
f = c.consume
|
||||
d = p.retrieve(7, f)
|
||||
self.assertEqual(t.value(), b"RETR 8\r\n")
|
||||
p.dataReceived(b"+OK Message incoming\r\n")
|
||||
p.dataReceived(b"La la la here is message text\r\n")
|
||||
p.dataReceived(b"..Further message text\r\n.\r\n")
|
||||
return d.addCallback(self._cbTestRetrieveWithConsumer, f, c)
|
||||
|
||||
def _cbTestRetrieveWithConsumer(self, result, f, c):
|
||||
self.assertIdentical(result, f)
|
||||
self.assertEqual(
|
||||
c.data, [b"La la la here is message text", b".Further message text"]
|
||||
)
|
||||
|
||||
def testPartialRetrieve(self):
|
||||
p, t = setUp()
|
||||
d = p.retrieve(7, lines=2)
|
||||
self.assertEqual(t.value(), b"TOP 8 2\r\n")
|
||||
p.dataReceived(b"+OK 2 lines on the way\r\n")
|
||||
p.dataReceived(b"Line the first! Woop\r\n")
|
||||
p.dataReceived(b"Line the last! Bye\r\n")
|
||||
p.dataReceived(b".\r\n")
|
||||
return d.addCallback(
|
||||
self.assertEqual, [b"Line the first! Woop", b"Line the last! Bye"]
|
||||
)
|
||||
|
||||
def testPartialRetrieveWithConsumer(self):
|
||||
p, t = setUp()
|
||||
c = MessageConsumer()
|
||||
f = c.consume
|
||||
d = p.retrieve(7, f, lines=2)
|
||||
self.assertEqual(t.value(), b"TOP 8 2\r\n")
|
||||
p.dataReceived(b"+OK 2 lines on the way\r\n")
|
||||
p.dataReceived(b"Line the first! Woop\r\n")
|
||||
p.dataReceived(b"Line the last! Bye\r\n")
|
||||
p.dataReceived(b".\r\n")
|
||||
return d.addCallback(self._cbTestPartialRetrieveWithConsumer, f, c)
|
||||
|
||||
def _cbTestPartialRetrieveWithConsumer(self, result, f, c):
|
||||
self.assertIdentical(result, f)
|
||||
self.assertEqual(c.data, [b"Line the first! Woop", b"Line the last! Bye"])
|
||||
|
||||
def testFailedRetrieve(self):
|
||||
p, t = setUp()
|
||||
d = p.retrieve(0)
|
||||
self.assertEqual(t.value(), b"RETR 1\r\n")
|
||||
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"Fatal doom server exploded")
|
||||
)
|
||||
|
||||
def test_concurrentRetrieves(self):
|
||||
"""
|
||||
Issue three retrieve calls immediately without waiting for any to
|
||||
succeed and make sure they all do succeed eventually.
|
||||
"""
|
||||
p, t = setUp()
|
||||
messages = [
|
||||
p.retrieve(i).addCallback(
|
||||
self.assertEqual,
|
||||
[b"First line of %d." % (i + 1,), b"Second line of %d." % (i + 1,)],
|
||||
)
|
||||
for i in range(3)
|
||||
]
|
||||
|
||||
for i in range(1, 4):
|
||||
self.assertEqual(t.value(), b"RETR %d\r\n" % (i,))
|
||||
t.clear()
|
||||
p.dataReceived(b"+OK 2 lines on the way\r\n")
|
||||
p.dataReceived(b"First line of %d.\r\n" % (i,))
|
||||
p.dataReceived(b"Second line of %d.\r\n" % (i,))
|
||||
self.assertEqual(t.value(), b"")
|
||||
p.dataReceived(b".\r\n")
|
||||
|
||||
return defer.DeferredList(messages, fireOnOneErrback=True)
|
||||
|
||||
|
||||
class POP3ClientMiscTests(TestCase):
|
||||
def testCapability(self):
|
||||
p, t = setUp()
|
||||
d = p.capabilities(useCache=0)
|
||||
self.assertEqual(t.value(), b"CAPA\r\n")
|
||||
p.dataReceived(b"+OK Capabilities on the way\r\n")
|
||||
p.dataReceived(b"X\r\nY\r\nZ\r\nA 1 2 3\r\nB 1 2\r\nC 1\r\n.\r\n")
|
||||
return d.addCallback(
|
||||
self.assertEqual,
|
||||
{
|
||||
b"X": None,
|
||||
b"Y": None,
|
||||
b"Z": None,
|
||||
b"A": [b"1", b"2", b"3"],
|
||||
b"B": [b"1", b"2"],
|
||||
b"C": [b"1"],
|
||||
},
|
||||
)
|
||||
|
||||
def testCapabilityError(self):
|
||||
p, t = setUp()
|
||||
d = p.capabilities(useCache=0)
|
||||
self.assertEqual(t.value(), b"CAPA\r\n")
|
||||
p.dataReceived(b"-ERR This server is lame!\r\n")
|
||||
return d.addCallback(self.assertEqual, {})
|
||||
|
||||
def testStat(self):
|
||||
p, t = setUp()
|
||||
d = p.stat()
|
||||
self.assertEqual(t.value(), b"STAT\r\n")
|
||||
p.dataReceived(b"+OK 1 1212\r\n")
|
||||
return d.addCallback(self.assertEqual, (1, 1212))
|
||||
|
||||
def testStatError(self):
|
||||
p, t = setUp()
|
||||
d = p.stat()
|
||||
self.assertEqual(t.value(), b"STAT\r\n")
|
||||
p.dataReceived(b"-ERR This server is lame!\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!")
|
||||
)
|
||||
|
||||
def testNoop(self):
|
||||
p, t = setUp()
|
||||
d = p.noop()
|
||||
self.assertEqual(t.value(), b"NOOP\r\n")
|
||||
p.dataReceived(b"+OK No-op to you too!\r\n")
|
||||
return d.addCallback(self.assertEqual, b"No-op to you too!")
|
||||
|
||||
def testNoopError(self):
|
||||
p, t = setUp()
|
||||
d = p.noop()
|
||||
self.assertEqual(t.value(), b"NOOP\r\n")
|
||||
p.dataReceived(b"-ERR This server is lame!\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!")
|
||||
)
|
||||
|
||||
def testRset(self):
|
||||
p, t = setUp()
|
||||
d = p.reset()
|
||||
self.assertEqual(t.value(), b"RSET\r\n")
|
||||
p.dataReceived(b"+OK Reset state\r\n")
|
||||
return d.addCallback(self.assertEqual, b"Reset state")
|
||||
|
||||
def testRsetError(self):
|
||||
p, t = setUp()
|
||||
d = p.reset()
|
||||
self.assertEqual(t.value(), b"RSET\r\n")
|
||||
p.dataReceived(b"-ERR This server is lame!\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!")
|
||||
)
|
||||
|
||||
def testDelete(self):
|
||||
p, t = setUp()
|
||||
d = p.delete(3)
|
||||
self.assertEqual(t.value(), b"DELE 4\r\n")
|
||||
p.dataReceived(b"+OK Hasta la vista\r\n")
|
||||
return d.addCallback(self.assertEqual, b"Hasta la vista")
|
||||
|
||||
def testDeleteError(self):
|
||||
p, t = setUp()
|
||||
d = p.delete(3)
|
||||
self.assertEqual(t.value(), b"DELE 4\r\n")
|
||||
p.dataReceived(b"-ERR Winner is not you.\r\n")
|
||||
return self.assertFailure(d, ServerErrorResponse).addCallback(
|
||||
lambda exc: self.assertEqual(exc.args[0], b"Winner is not you.")
|
||||
)
|
||||
|
||||
|
||||
class SimpleClient(POP3Client):
|
||||
def __init__(self, deferred, contextFactory=None):
|
||||
self.deferred = deferred
|
||||
self.allowInsecureLogin = True
|
||||
|
||||
def serverGreeting(self, challenge):
|
||||
self.deferred.callback(None)
|
||||
|
||||
|
||||
class POP3HelperMixin:
|
||||
serverCTX = None
|
||||
clientCTX = None
|
||||
|
||||
def setUp(self):
|
||||
d = defer.Deferred()
|
||||
self.server = pop3testserver.POP3TestServer(contextFactory=self.serverCTX)
|
||||
self.client = SimpleClient(d, contextFactory=self.clientCTX)
|
||||
self.client.timeout = 30
|
||||
self.connected = d
|
||||
|
||||
def tearDown(self):
|
||||
del self.server
|
||||
del self.client
|
||||
del self.connected
|
||||
|
||||
def _cbStopClient(self, ignore):
|
||||
self.client.transport.loseConnection()
|
||||
|
||||
def _ebGeneral(self, failure):
|
||||
self.client.transport.loseConnection()
|
||||
self.server.transport.loseConnection()
|
||||
return failure
|
||||
|
||||
def loopback(self):
|
||||
return loopback.loopbackTCP(self.server, self.client, noisy=False)
|
||||
|
||||
|
||||
class TLSServerFactory(protocol.ServerFactory):
|
||||
class protocol(basic.LineReceiver):
|
||||
context = None
|
||||
output: List[bytes] = []
|
||||
|
||||
def connectionMade(self):
|
||||
self.factory.input = []
|
||||
self.output = self.output[:]
|
||||
for line in self.output.pop(0):
|
||||
self.sendLine(line)
|
||||
|
||||
def lineReceived(self, line):
|
||||
self.factory.input.append(line)
|
||||
[self.sendLine(l) for l in self.output.pop(0)]
|
||||
if line == b"STLS":
|
||||
self.transport.startTLS(self.context)
|
||||
|
||||
|
||||
@skipIf(not ClientTLSContext, "OpenSSL not present")
|
||||
@skipIf(not interfaces.IReactorSSL(reactor, None), "OpenSSL not present")
|
||||
class POP3TLSTests(TestCase):
|
||||
"""
|
||||
Tests for POP3Client's support for TLS connections.
|
||||
"""
|
||||
|
||||
def test_startTLS(self):
|
||||
"""
|
||||
POP3Client.startTLS starts a TLS session over its existing TCP
|
||||
connection.
|
||||
"""
|
||||
sf = TLSServerFactory()
|
||||
sf.protocol.output = [
|
||||
[b"+OK"], # Server greeting
|
||||
[b"+OK", b"STLS", b"."], # CAPA response
|
||||
[b"+OK"], # STLS response
|
||||
[b"+OK", b"."], # Second CAPA response
|
||||
[b"+OK"], # QUIT response
|
||||
]
|
||||
sf.protocol.context = ServerTLSContext()
|
||||
port = reactor.listenTCP(0, sf, interface="127.0.0.1")
|
||||
self.addCleanup(port.stopListening)
|
||||
H = port.getHost().host
|
||||
P = port.getHost().port
|
||||
|
||||
connLostDeferred = defer.Deferred()
|
||||
cp = SimpleClient(defer.Deferred(), ClientTLSContext())
|
||||
|
||||
def connectionLost(reason):
|
||||
SimpleClient.connectionLost(cp, reason)
|
||||
connLostDeferred.callback(None)
|
||||
|
||||
cp.connectionLost = connectionLost
|
||||
cf = protocol.ClientFactory()
|
||||
cf.protocol = lambda: cp
|
||||
|
||||
conn = reactor.connectTCP(H, P, cf)
|
||||
|
||||
def cbConnected(ignored):
|
||||
log.msg("Connected to server; starting TLS")
|
||||
return cp.startTLS()
|
||||
|
||||
def cbStartedTLS(ignored):
|
||||
log.msg("Started TLS; disconnecting")
|
||||
return cp.quit()
|
||||
|
||||
def cbDisconnected(ign):
|
||||
log.msg("Disconnected; asserting correct input received")
|
||||
self.assertEqual(sf.input, [b"CAPA", b"STLS", b"CAPA", b"QUIT"])
|
||||
|
||||
def cleanup(result):
|
||||
log.msg(
|
||||
"Asserted correct input; disconnecting "
|
||||
"client and shutting down server"
|
||||
)
|
||||
conn.disconnect()
|
||||
return connLostDeferred
|
||||
|
||||
cp.deferred.addCallback(cbConnected)
|
||||
cp.deferred.addCallback(cbStartedTLS)
|
||||
cp.deferred.addCallback(cbDisconnected)
|
||||
cp.deferred.addBoth(cleanup)
|
||||
|
||||
return cp.deferred
|
||||
|
||||
|
||||
class POP3TimeoutTests(POP3HelperMixin, TestCase):
|
||||
def testTimeout(self):
|
||||
def login():
|
||||
d = self.client.login("test", "twisted")
|
||||
d.addCallback(loggedIn)
|
||||
d.addErrback(timedOut)
|
||||
return d
|
||||
|
||||
def loggedIn(result):
|
||||
self.fail("Successfully logged in!? Impossible!")
|
||||
|
||||
def timedOut(failure):
|
||||
failure.trap(error.TimeoutError)
|
||||
self._cbStopClient(None)
|
||||
|
||||
def quit():
|
||||
return self.client.quit()
|
||||
|
||||
self.client.timeout = 0.01
|
||||
|
||||
# Tell the server to not return a response to client. This
|
||||
# will trigger a timeout.
|
||||
pop3testserver.TIMEOUT_RESPONSE = True
|
||||
|
||||
methods = [login, quit]
|
||||
map(self.connected.addCallback, map(strip, methods))
|
||||
self.connected.addCallback(self._cbStopClient)
|
||||
self.connected.addErrback(self._ebGeneral)
|
||||
return self.loopback()
|
||||
|
||||
|
||||
class POP3ClientModuleStructureTests(TestCase):
|
||||
"""
|
||||
Miscellaneous tests more to do with module/package structure than
|
||||
anything to do with the POP3 client.
|
||||
"""
|
||||
|
||||
def test_all(self):
|
||||
"""
|
||||
twisted.mail._pop3client.__all__ should be empty because all classes
|
||||
should be imported through twisted.mail.pop3.
|
||||
"""
|
||||
self.assertEqual(twisted.mail._pop3client.__all__, [])
|
||||
|
||||
def test_import(self):
|
||||
"""
|
||||
Every public class in twisted.mail._pop3client should be available as
|
||||
a member of twisted.mail.pop3 with the exception of
|
||||
twisted.mail._pop3client.POP3Client which should be available as
|
||||
twisted.mail.pop3.AdvancedClient.
|
||||
"""
|
||||
publicClasses = [
|
||||
c[0]
|
||||
for c in inspect.getmembers(
|
||||
sys.modules["twisted.mail._pop3client"], inspect.isclass
|
||||
)
|
||||
if not c[0][0] == "_"
|
||||
]
|
||||
|
||||
for pc in publicClasses:
|
||||
if not pc == "POP3Client":
|
||||
self.assertTrue(
|
||||
hasattr(twisted.mail.pop3, pc),
|
||||
f"{pc} not in {twisted.mail.pop3}",
|
||||
)
|
||||
else:
|
||||
self.assertTrue(hasattr(twisted.mail.pop3, "AdvancedPOP3Client"))
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for the command-line mailer tool provided by Twisted Mail.
|
||||
"""
|
||||
|
||||
from twisted.scripts.test.test_scripts import ScriptTestsMixin
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
|
||||
class ScriptTests(TestCase, ScriptTestsMixin):
|
||||
"""
|
||||
Tests for all one of mail's scripts.
|
||||
"""
|
||||
|
||||
def test_mailmail(self) -> None:
|
||||
self.scriptTest("mail/mailmail")
|
||||
1908
backend/lib/python3.12/site-packages/twisted/mail/test/test_smtp.py
Normal file
1908
backend/lib/python3.12/site-packages/twisted/mail/test/test_smtp.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user