mercurial/mail.py
changeset 26587 56b2bcea2529
parent 26098 ce26928cbe41
child 26673 ab1af5e7d734
equal deleted inserted replaced
26586:d51c658d3f04 26587:56b2bcea2529
    16 import time
    16 import time
    17 
    17 
    18 from .i18n import _
    18 from .i18n import _
    19 from . import (
    19 from . import (
    20     encoding,
    20     encoding,
       
    21     error,
    21     sslutil,
    22     sslutil,
    22     util,
    23     util,
    23 )
    24 )
    24 
    25 
    25 _oldheaderinit = email.Header.Header.__init__
    26 _oldheaderinit = email.Header.Header.__init__
    91                                             **self._sslkwargs)
    92                                             **self._sslkwargs)
    92             self.file = smtplib.SSLFakeFile(new_socket)
    93             self.file = smtplib.SSLFakeFile(new_socket)
    93             return new_socket
    94             return new_socket
    94 else:
    95 else:
    95     def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
    96     def SMTPS(sslkwargs, keyfile=None, certfile=None, **kwargs):
    96         raise util.Abort(_('SMTPS requires Python 2.6 or later'))
    97         raise error.Abort(_('SMTPS requires Python 2.6 or later'))
    97 
    98 
    98 def _smtp(ui):
    99 def _smtp(ui):
    99     '''build an smtp connection and return a function to send mail'''
   100     '''build an smtp connection and return a function to send mail'''
   100     local_hostname = ui.config('smtp', 'local_hostname')
   101     local_hostname = ui.config('smtp', 'local_hostname')
   101     tls = ui.config('smtp', 'tls', 'none')
   102     tls = ui.config('smtp', 'tls', 'none')
   102     # backward compatible: when tls = true, we use starttls.
   103     # backward compatible: when tls = true, we use starttls.
   103     starttls = tls == 'starttls' or util.parsebool(tls)
   104     starttls = tls == 'starttls' or util.parsebool(tls)
   104     smtps = tls == 'smtps'
   105     smtps = tls == 'smtps'
   105     if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
   106     if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
   106         raise util.Abort(_("can't use TLS: Python SSL support not installed"))
   107         raise error.Abort(_("can't use TLS: Python SSL support not installed"))
   107     mailhost = ui.config('smtp', 'host')
   108     mailhost = ui.config('smtp', 'host')
   108     if not mailhost:
   109     if not mailhost:
   109         raise util.Abort(_('smtp.host not configured - cannot send mail'))
   110         raise error.Abort(_('smtp.host not configured - cannot send mail'))
   110     verifycert = ui.config('smtp', 'verifycert', 'strict')
   111     verifycert = ui.config('smtp', 'verifycert', 'strict')
   111     if verifycert not in ['strict', 'loose']:
   112     if verifycert not in ['strict', 'loose']:
   112         if util.parsebool(verifycert) is not False:
   113         if util.parsebool(verifycert) is not False:
   113             raise util.Abort(_('invalid smtp.verifycert configuration: %s')
   114             raise error.Abort(_('invalid smtp.verifycert configuration: %s')
   114                              % (verifycert))
   115                              % (verifycert))
   115         verifycert = False
   116         verifycert = False
   116     if (starttls or smtps) and verifycert:
   117     if (starttls or smtps) and verifycert:
   117         sslkwargs = sslutil.sslkwargs(ui, mailhost)
   118         sslkwargs = sslutil.sslkwargs(ui, mailhost)
   118     else:
   119     else:
   149         ui.note(_('(authenticating to mail server as %s)\n') %
   150         ui.note(_('(authenticating to mail server as %s)\n') %
   150                   (username))
   151                   (username))
   151         try:
   152         try:
   152             s.login(username, password)
   153             s.login(username, password)
   153         except smtplib.SMTPException as inst:
   154         except smtplib.SMTPException as inst:
   154             raise util.Abort(inst)
   155             raise error.Abort(inst)
   155 
   156 
   156     def send(sender, recipients, msg):
   157     def send(sender, recipients, msg):
   157         try:
   158         try:
   158             return s.sendmail(sender, recipients, msg)
   159             return s.sendmail(sender, recipients, msg)
   159         except smtplib.SMTPRecipientsRefused as inst:
   160         except smtplib.SMTPRecipientsRefused as inst:
   160             recipients = [r[1] for r in inst.recipients.values()]
   161             recipients = [r[1] for r in inst.recipients.values()]
   161             raise util.Abort('\n' + '\n'.join(recipients))
   162             raise error.Abort('\n' + '\n'.join(recipients))
   162         except smtplib.SMTPException as inst:
   163         except smtplib.SMTPException as inst:
   163             raise util.Abort(inst)
   164             raise error.Abort(inst)
   164 
   165 
   165     return send
   166     return send
   166 
   167 
   167 def _sendmail(ui, sender, recipients, msg):
   168 def _sendmail(ui, sender, recipients, msg):
   168     '''send mail using sendmail.'''
   169     '''send mail using sendmail.'''
   172     ui.note(_('sending mail: %s\n') % cmdline)
   173     ui.note(_('sending mail: %s\n') % cmdline)
   173     fp = util.popen(cmdline, 'w')
   174     fp = util.popen(cmdline, 'w')
   174     fp.write(msg)
   175     fp.write(msg)
   175     ret = fp.close()
   176     ret = fp.close()
   176     if ret:
   177     if ret:
   177         raise util.Abort('%s %s' % (
   178         raise error.Abort('%s %s' % (
   178             os.path.basename(program.split(None, 1)[0]),
   179             os.path.basename(program.split(None, 1)[0]),
   179             util.explainexit(ret)[0]))
   180             util.explainexit(ret)[0]))
   180 
   181 
   181 def _mbox(mbox, sender, recipients, msg):
   182 def _mbox(mbox, sender, recipients, msg):
   182     '''write mails to mbox'''
   183     '''write mails to mbox'''
   206 def validateconfig(ui):
   207 def validateconfig(ui):
   207     '''determine if we have enough config data to try sending email.'''
   208     '''determine if we have enough config data to try sending email.'''
   208     method = ui.config('email', 'method', 'smtp')
   209     method = ui.config('email', 'method', 'smtp')
   209     if method == 'smtp':
   210     if method == 'smtp':
   210         if not ui.config('smtp', 'host'):
   211         if not ui.config('smtp', 'host'):
   211             raise util.Abort(_('smtp specified as email transport, '
   212             raise error.Abort(_('smtp specified as email transport, '
   212                                'but no smtp host configured'))
   213                                'but no smtp host configured'))
   213     else:
   214     else:
   214         if not util.findexe(method):
   215         if not util.findexe(method):
   215             raise util.Abort(_('%r specified as email transport, '
   216             raise error.Abort(_('%r specified as email transport, '
   216                                'but not in PATH') % method)
   217                                'but not in PATH') % method)
   217 
   218 
   218 def mimetextpatch(s, subtype='plain', display=False):
   219 def mimetextpatch(s, subtype='plain', display=False):
   219     '''Return MIME message suitable for a patch.
   220     '''Return MIME message suitable for a patch.
   220     Charset will be detected as utf-8 or (possibly fake) us-ascii.
   221     Charset will be detected as utf-8 or (possibly fake) us-ascii.
   300         acc, dom = addr.split('@')
   301         acc, dom = addr.split('@')
   301         acc = acc.encode('ascii')
   302         acc = acc.encode('ascii')
   302         dom = dom.decode(encoding.encoding).encode('idna')
   303         dom = dom.decode(encoding.encoding).encode('idna')
   303         addr = '%s@%s' % (acc, dom)
   304         addr = '%s@%s' % (acc, dom)
   304     except UnicodeDecodeError:
   305     except UnicodeDecodeError:
   305         raise util.Abort(_('invalid email address: %s') % addr)
   306         raise error.Abort(_('invalid email address: %s') % addr)
   306     except ValueError:
   307     except ValueError:
   307         try:
   308         try:
   308             # too strict?
   309             # too strict?
   309             addr = addr.encode('ascii')
   310             addr = addr.encode('ascii')
   310         except UnicodeDecodeError:
   311         except UnicodeDecodeError:
   311             raise util.Abort(_('invalid local address: %s') % addr)
   312             raise error.Abort(_('invalid local address: %s') % addr)
   312     return email.Utils.formataddr((name, addr))
   313     return email.Utils.formataddr((name, addr))
   313 
   314 
   314 def addressencode(ui, address, charsets=None, display=False):
   315 def addressencode(ui, address, charsets=None, display=False):
   315     '''Turns address into RFC-2047 compliant header.'''
   316     '''Turns address into RFC-2047 compliant header.'''
   316     if display or not address:
   317     if display or not address: