From d0afe72a22ee5e84183c79d3652307c1e09a70da Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Aug 2019 19:54:59 +0530 Subject: [PATCH 45/71] A better fix for python3.7 smtplib breakage --- src/calibre/utils/smtp.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/calibre/utils/smtp.py b/src/calibre/utils/smtp.py index 93fb3cf73c..4f0ce963bc 100644 --- a/src/calibre/utils/smtp.py +++ b/src/calibre/utils/smtp.py @@ -13,7 +13,7 @@ This module implements a simple commandline SMTP client that supports: import sys, traceback, os, socket, encodings.idna as idna from calibre import isbytestring from calibre.constants import ispy3, iswindows -from polyglot.builtins import unicode_type, as_unicode +from polyglot.builtins import unicode_type, as_unicode, native_string_type def decode_fqdn(fqdn): @@ -139,33 +139,37 @@ def sendmail_direct(from_, to, msg, timeout, localhost, verbose, raise IOError('Failed to send mail: '+repr(last_error)) +def get_smtp_class(use_ssl=False, debuglevel=0): + # We need this as in python 3.7 we have to pass the hostname + # in the constructor, because of https://bugs.python.org/issue36094 + # which means the constructor calls connect(), + # but there is no way to set debuglevel before connect() is called + import polyglot.smtplib as smtplib + cls = smtplib.SMTP_SSL if use_ssl else smtplib.SMTP + bases = (cls,) if ispy3 else (cls, object) + return type(native_string_type('SMTP'), bases, {native_string_type('debuglevel'): debuglevel}) + + def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=None, relay=None, username=None, password=None, encryption='TLS', port=-1, debug_output=None, verify_server_cert=False, cafile=None): if relay is None: for x in to: return sendmail_direct(from_, x, msg, timeout, localhost, verbose) - import polyglot.smtplib as smtplib - cls = smtplib.SMTP_SSL if encryption == 'SSL' else smtplib.SMTP timeout = None # Non-blocking sockets sometimes don't work port = int(port) - kwargs = dict(timeout=timeout, local_hostname=localhost or safe_localhost()) - if debug_output is not None: - kwargs['debug_to'] = debug_output - s = cls(**kwargs) - s.set_debuglevel(verbose) if port < 0: port = 25 if encryption != 'SSL' else 465 - s.connect(relay, port) + kwargs = dict(host=relay, port=port, timeout=timeout, local_hostname=localhost or safe_localhost()) + if debug_output is not None: + kwargs['debug_to'] = debug_output + cls = get_smtp_class(use_ssl=encryption == 'SSL', debuglevel=verbose) + s = cls(**kwargs) if encryption == 'TLS': context = None if verify_server_cert: import ssl context = ssl.create_default_context(cafile=cafile) - if not getattr(s, '_host', None) and ispy3: - # needed because as of python 3.7 starttls expects _host to be set, - # bu we cant set it via the constructor - s._host = relay s.starttls(context=context) s.ehlo() if username is not None and password is not None: