mercurial/mail.py
changeset 7114 30e49d54c537
parent 6548 962eb403165b
child 7191 d14212218582
equal deleted inserted replaced
7113:f7fc5f5ecd62 7114:30e49d54c537
     5 # This software may be used and distributed according to the terms
     5 # This software may be used and distributed according to the terms
     6 # of the GNU General Public License, incorporated herein by reference.
     6 # of the GNU General Public License, incorporated herein by reference.
     7 
     7 
     8 from i18n import _
     8 from i18n import _
     9 import os, smtplib, socket
     9 import os, smtplib, socket
       
    10 import email.Header, email.MIMEText, email.Utils
    10 import util
    11 import util
    11 
    12 
    12 def _smtp(ui):
    13 def _smtp(ui):
    13     '''build an smtp connection and return a function to send mail'''
    14     '''build an smtp connection and return a function to send mail'''
    14     local_hostname = ui.config('smtp', 'local_hostname')
    15     local_hostname = ui.config('smtp', 'local_hostname')
    82                                'but no smtp host configured'))
    83                                'but no smtp host configured'))
    83     else:
    84     else:
    84         if not util.find_exe(method):
    85         if not util.find_exe(method):
    85             raise util.Abort(_('%r specified as email transport, '
    86             raise util.Abort(_('%r specified as email transport, '
    86                                'but not in PATH') % method)
    87                                'but not in PATH') % method)
       
    88 
       
    89 def _charsets(ui):
       
    90     '''Obtains charsets to send mail parts not containing patches.'''
       
    91     charsets = [cs.lower() for cs in ui.configlist('email', 'charsets')]
       
    92     fallbacks = [util._fallbackencoding.lower(),
       
    93                  util._encoding.lower(), 'utf-8']
       
    94     for cs in fallbacks: # util.unique does not keep order
       
    95         if cs not in charsets:
       
    96             charsets.append(cs)
       
    97     return [cs for cs in charsets if not cs.endswith('ascii')]
       
    98 
       
    99 def _encode(ui, s, charsets):
       
   100     '''Returns (converted) string, charset tuple.
       
   101     Finds out best charset by cycling through sendcharsets in descending
       
   102     order. Tries both _encoding and _fallbackencoding for input. Only as
       
   103     last resort send as is in fake ascii.
       
   104     Caveat: Do not use for mail parts containing patches!'''
       
   105     try:
       
   106         s.decode('ascii')
       
   107     except UnicodeDecodeError:
       
   108         sendcharsets = charsets or _charsets(ui)
       
   109         for ics in (util._encoding, util._fallbackencoding):
       
   110             try:
       
   111                 u = s.decode(ics)
       
   112             except UnicodeDecodeError:
       
   113                 continue
       
   114             for ocs in sendcharsets:
       
   115                 try:
       
   116                     return u.encode(ocs), ocs
       
   117                 except UnicodeEncodeError:
       
   118                     pass
       
   119                 except LookupError:
       
   120                     ui.warn(_('ignoring invalid sendcharset: %s\n') % cs)
       
   121     # if ascii, or all conversion attempts fail, send (broken) ascii
       
   122     return s, 'us-ascii'
       
   123 
       
   124 def headencode(ui, s, charsets=None, display=False):
       
   125     '''Returns RFC-2047 compliant header from given string.'''
       
   126     if not display:
       
   127         # split into words?
       
   128         s, cs = _encode(ui, s, charsets)
       
   129         return str(email.Header.Header(s, cs))
       
   130     return s
       
   131 
       
   132 def addressencode(ui, address, charsets=None, display=False):
       
   133     '''Turns address into RFC-2047 compliant header.'''
       
   134     if display or not address:
       
   135         return address or ''
       
   136     name, addr = email.Utils.parseaddr(address)
       
   137     name = headencode(ui, name, charsets)
       
   138     try:
       
   139         acc, dom = addr.split('@')
       
   140         acc = acc.encode('ascii')
       
   141         dom = dom.encode('idna')
       
   142         addr = '%s@%s' % (acc, dom)
       
   143     except UnicodeDecodeError:
       
   144         raise util.Abort(_('invalid email address: %s') % addr)
       
   145     except ValueError:
       
   146         try:
       
   147             # too strict?
       
   148             addr = addr.encode('ascii')
       
   149         except UnicodeDecodeError:
       
   150             raise util.Abort(_('invalid local address: %s') % addr)
       
   151     return email.Utils.formataddr((name, addr))
       
   152 
       
   153 def mimeencode(ui, s, charsets=None, display=False):
       
   154     '''creates mime text object, encodes it if needed, and sets
       
   155     charset and transfer-encoding accordingly.'''
       
   156     cs = 'us-ascii'
       
   157     if not display:
       
   158         s, cs = _encode(ui, s, charsets)
       
   159     return email.MIMEText.MIMEText(s, 'plain', cs)