c 20012010 python software foundation barry warsaw contact emailsigpython org miscellaneous utilities all collapserfc2231value decodeparams decoderfc2231 encoderfc2231 formataddr formatdate formatdatetime getaddresses makemsgid mktimetz parseaddr parsedate parsedatetz parsedatetodatetime unquote import os import re import time import datetime import urllib parse from email parseaddr import quote from email parseaddr import addresslist as addresslist from email parseaddr import mktimetz from email parseaddr import parsedate parsedatetz parsedatetz commaspace emptystring uemptystring crlf rn tick specialsre re compiler escapesre re compiler def hassurrogatess this check is based on the fact that unless there are surrogates utf8 python s default encoding can encode any string this is the fastest way to check for surrogates see issue 11454 for timings how to deal with a string containing bytes before handing it to the application through the normal interface turn any escaped bytes into unicode unknown char if the escaped bytes happen to be utf8 they will instead get decoded even if they were invalid in the charset the source was supposed to be in this seems like it is not a bad thing a defect was still registered helpers the inverse of parseaddr this takes a 2tuple of the form realname emailaddress and returns the string value suitable for an rfc 2822 from to or cc header if the first element of pair is false then the second element is returned unmodified the optional charset is the character set that is used to encode realname in case realname is not ascii safe can be an instance of str or a charsetlike object which has a headerencode method default is utf8 the address must per rfc be ascii so raise a unicodeerror if it isn t lazy import to improve module import time return a list of realname email for each fieldvalue all commaspace joinstrv for v in fieldvalues a addresslistall return a addresslist def formattimetupleandzonetimetuple zone return s 02d s 04d 02d 02d 02d s mon tue wed thu fri sat sun timetuple6 timetuple2 jan feb mar apr may jun jul aug sep oct nov dec timetuple1 1 timetuple0 timetuple3 timetuple4 timetuple5 zone def formatdatetimevalnone localtimefalse usegmtfalse note we cannot use strftime because that honors the locale and rfc 2822 requires that day and month names be the english abbreviations if timeval is none timeval time time dt datetime datetime fromtimestamptimeval datetime timezone utc if localtime dt dt astimezone usegmt false elif not usegmt dt dt replacetzinfonone return formatdatetimedt usegmt def formatdatetimedt usegmtfalse now dt timetuple if usegmt if dt tzinfo is none or dt tzinfo datetime timezone utc raise valueerrorusegmt option requires a utc datetime zone gmt elif dt tzinfo is none zone 0000 else zone dt strftimez return formattimetupleandzonenow zone def makemsgididstringnone domainnone lazy imports to speedup module import time no other functions in email utils need these modules import random import socket timeval inttime time100 pid os getpid randint random getrandbits64 if idstring is none idstring else idstring idstring if domain is none domain socket getfqdn msgid d d dss timeval pid randint idstring domain return msgid def parsedatetodatetimedata parseddatetz parsedatetzdata if parseddatetz is none raise valueerror invalid date value or format s strdata dtuple tz parseddatetz if tz is none return datetime datetimedtuple 6 return datetime datetimedtuple 6 tzinfodatetime timezonedatetime timedeltasecondstz def parseaddraddr addrs addresslistaddr addresslist if not addrs return return addrs0 rfc822 unquote doesn t properly debackslashify in python pre2 3 def unquotestr rfc2231related functions parameter encoding and decoding decode string according to rfc 2231 parts s splittick 2 if lenparts 2 return none none s return parts def encoderfc2231s charsetnone languagenone s urllib parse quotes safe encodingcharset or ascii if charset is none and language is none return s if language is none language return s s s charset language s rfc2231continuation re compiler pnamew pnum09 re ascii def decodeparamsparams newparams params0 map parameter s name to a list of continuations the values are a 3tuple of the continuation number the string value and a flag specifying whether a particular segment is encoded rfc2231params for name value in params1 encoded name endswith value unquotevalue mo rfc2231continuation matchname if mo name num mo group name num if num is not none num intnum rfc2231params setdefaultname appendnum value encoded else newparams appendname s quotevalue if rfc2231params for name continuations in rfc2231params items value extended false sort by number continuations sort and now append all values in numerical order converting encodings for the encoded segments if any of the continuation names ends in a then the entire string after decoding segments and concatenating must have the charset and language specifiers at the beginning of the string for num s encoded in continuations if encoded decode as latin1 so the characters in s directly represent the percentencoded octet values collapserfc2231value treats this as an octet sequence s urllib parse unquotes encodinglatin1 extended true value appends value quoteemptystring joinvalue if extended charset language value decoderfc2231value newparams appendname charset language s value else newparams appendname s value return newparams def collapserfc2231valuevalue errors replace fallbackcharset usascii if not isinstancevalue tuple or lenvalue 3 return unquotevalue while value comes to us as a unicode string we need it to be a bytes object we do not want bytes normal utf8 decoder we want a straight interpretation of the string as character bytes charset language text value if charset is none issue 17369 if charsetlang is none decoderfc2231 couldn t parse the value so use the fallbackcharset charset fallbackcharset rawbytes bytestext rawunicodeescape try return strrawbytes charset errors except lookuperror charset is not a known codec return unquotetext datetime doesn t provide a localtime function yet so provide one code adapted from the patch in issue 9527 this may not be perfect but it is better than not having it def localtimedtnone isdstnone if isdst is not none import warnings warnings deprecated the isdst parameter to localtime message name is deprecated and slated for removal in python remove remove3 14 if dt is none dt datetime datetime now return dt astimezone c 2001 2010 python software foundation barry warsaw contact email sig python org miscellaneous utilities return true if s contains surrogate escaped binary data this check is based on the fact that unless there are surrogates utf8 python s default encoding can encode any string this is the fastest way to check for surrogates see issue 11454 for timings how to deal with a string containing bytes before handing it to the application through the normal interface turn any escaped bytes into unicode unknown char if the escaped bytes happen to be utf 8 they will instead get decoded even if they were invalid in the charset the source was supposed to be in this seems like it is not a bad thing a defect was still registered helpers the inverse of parseaddr this takes a 2 tuple of the form realname email_address and returns the string value suitable for an rfc 2822 from to or cc header if the first element of pair is false then the second element is returned unmodified the optional charset is the character set that is used to encode realname in case realname is not ascii safe can be an instance of str or a charset like object which has a header_encode method default is utf 8 the address must per rfc be ascii so raise a unicodeerror if it isn t lazy import to improve module import time return a list of realname email for each fieldvalue returns a date string as specified by rfc 2822 e g fri 09 nov 2001 01 08 47 0000 optional timeval if given is a floating point time value as accepted by gmtime and localtime otherwise the current time is used optional localtime is a flag that when true interprets timeval and returns a date relative to the local timezone instead of utc properly taking daylight savings time into account optional argument usegmt means that the timezone is written out as an ascii string not numeric one so gmt instead of 0000 this is needed for http and is only used when localtime false note we cannot use strftime because that honors the locale and rfc 2822 requires that day and month names be the english abbreviations turn a datetime into a date string as specified in rfc 2822 if usegmt is true dt must be an aware datetime with an offset of zero in this case gmt will be rendered instead of the normal 0000 required by rfc2822 this is to support http headers involving date stamps returns a string suitable for rfc 2822 compliant message id e g 142480216486 20800 16526388040877946887 nightshade la mastaler com optional idstring if given is a string used to strengthen the uniqueness of the message id optional domain if given provides the portion of the message id after the it defaults to the locally defined hostname lazy imports to speedup module import time no other functions in email utils need these modules parse addr into its constituent realname and email address parts return a tuple of realname and email address unless the parse fails in which case return a 2 tuple of rfc822 unquote doesn t properly de backslash ify in python pre 2 3 remove quotes from a string rfc2231 related functions parameter encoding and decoding decode string according to rfc 2231 encode string according to rfc 2231 if neither charset nor language is given then s is returned as is if charset is given but not language the string is encoded using the empty string for language decode parameters list according to rfc 2231 params is a sequence of 2 tuples containing param name string value map parameter s name to a list of continuations the values are a 3 tuple of the continuation number the string value and a flag specifying whether a particular segment is encoded sort by number and now append all values in numerical order converting encodings for the encoded segments if any of the continuation names ends in a then the entire string after decoding segments and concatenating must have the charset and language specifiers at the beginning of the string decode as latin 1 so the characters in s directly represent the percent encoded octet values collapse_rfc2231_value treats this as an octet sequence while value comes to us as a unicode string we need it to be a bytes object we do not want bytes normal utf 8 decoder we want a straight interpretation of the string as character bytes issue 17369 if charset lang is none decode_rfc2231 couldn t parse the value so use the fallback_charset charset is not a known codec datetime doesn t provide a localtime function yet so provide one code adapted from the patch in issue 9527 this may not be perfect but it is better than not having it return local time as an aware datetime object if called without arguments return current time otherwise dt argument should be a datetime instance and it is converted to the local time zone according to the system time zone database if dt is naive that is dt tzinfo is none it is assumed to be in local time the isdst parameter is ignored
__all__ = [
    'collapse_rfc2231_value',
    'decode_params',
    'decode_rfc2231',
    'encode_rfc2231',
    'formataddr',
    'formatdate',
    'format_datetime',
    'getaddresses',
    'make_msgid',
    'mktime_tz',
    'parseaddr',
    'parsedate',
    'parsedate_tz',
    'parsedate_to_datetime',
    'unquote',
    ]

import os
import re
import time
import datetime
import urllib.parse

from email._parseaddr import quote
from email._parseaddr import AddressList as _AddressList
from email._parseaddr import mktime_tz

from email._parseaddr import parsedate, parsedate_tz, _parsedate_tz

COMMASPACE = ', '
EMPTYSTRING = ''
UEMPTYSTRING = ''
CRLF = '\r\n'
TICK = "'"

specialsre = re.compile(r'[][\\()<>@,:;".]')
escapesre = re.compile(r'[\\"]')

def _has_surrogates(s):
    
    
    
    
    try:
        s.encode()
        return False
    except UnicodeEncodeError:
        return True



def _sanitize(string):
    
    
    
    
    original_bytes = string.encode('utf-8', 'surrogateescape')
    return original_bytes.decode('utf-8', 'replace')





def formataddr(pair, charset='utf-8'):
    
    name, address = pair
    
    address.encode('ascii')
    if name:
        try:
            name.encode('ascii')
        except UnicodeEncodeError:
            if isinstance(charset, str):
                
                from email.charset import Charset
                charset = Charset(charset)
            encoded_name = charset.header_encode(name)
            return "%s <%s>" % (encoded_name, address)
        else:
            quotes = ''
            if specialsre.search(name):
                quotes = '"'
            name = escapesre.sub(r'\\\g<0>', name)
            return '%s%s%s <%s>' % (quotes, name, quotes, address)
    return address



def getaddresses(fieldvalues):
    
    all = COMMASPACE.join(str(v) for v in fieldvalues)
    a = _AddressList(all)
    return a.addresslist


def _format_timetuple_and_zone(timetuple, zone):
    return '%s, %02d %s %04d %02d:%02d:%02d %s' % (
        ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][timetuple[6]],
        timetuple[2],
        ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
         'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][timetuple[1] - 1],
        timetuple[0], timetuple[3], timetuple[4], timetuple[5],
        zone)

def formatdate(timeval=None, localtime=False, usegmt=False):
    
    
    
    if timeval is None:
        timeval = time.time()
    dt = datetime.datetime.fromtimestamp(timeval, datetime.timezone.utc)

    if localtime:
        dt = dt.astimezone()
        usegmt = False
    elif not usegmt:
        dt = dt.replace(tzinfo=None)
    return format_datetime(dt, usegmt)

def format_datetime(dt, usegmt=False):
    
    now = dt.timetuple()
    if usegmt:
        if dt.tzinfo is None or dt.tzinfo != datetime.timezone.utc:
            raise ValueError("usegmt option requires a UTC datetime")
        zone = 'GMT'
    elif dt.tzinfo is None:
        zone = '-0000'
    else:
        zone = dt.strftime("%z")
    return _format_timetuple_and_zone(now, zone)


def make_msgid(idstring=None, domain=None):
    
    
    
    import random
    import socket

    timeval = int(time.time()*100)
    pid = os.getpid()
    randint = random.getrandbits(64)
    if idstring is None:
        idstring = ''
    else:
        idstring = '.' + idstring
    if domain is None:
        domain = socket.getfqdn()
    msgid = '<%d.%d.%d%s@%s>' % (timeval, pid, randint, idstring, domain)
    return msgid


def parsedate_to_datetime(data):
    parsed_date_tz = _parsedate_tz(data)
    if parsed_date_tz is None:
        raise ValueError('Invalid date value or format "%s"' % str(data))
    *dtuple, tz = parsed_date_tz
    if tz is None:
        return datetime.datetime(*dtuple[:6])
    return datetime.datetime(*dtuple[:6],
            tzinfo=datetime.timezone(datetime.timedelta(seconds=tz)))


def parseaddr(addr):
    
    addrs = _AddressList(addr).addresslist
    if not addrs:
        return '', ''
    return addrs[0]



def unquote(str):
    
    if len(str) > 1:
        if str.startswith('"') and str.endswith('"'):
            return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')
        if str.startswith('<') and str.endswith('>'):
            return str[1:-1]
    return str




def decode_rfc2231(s):
    
    parts = s.split(TICK, 2)
    if len(parts) <= 2:
        return None, None, s
    return parts


def encode_rfc2231(s, charset=None, language=None):
    
    s = urllib.parse.quote(s, safe='', encoding=charset or 'ascii')
    if charset is None and language is None:
        return s
    if language is None:
        language = ''
    return "%s'%s'%s" % (charset, language, s)


rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$',
    re.ASCII)

def decode_params(params):
    
    new_params = [params[0]]
    
    
    
    rfc2231_params = {}
    for name, value in params[1:]:
        encoded = name.endswith('*')
        value = unquote(value)
        mo = rfc2231_continuation.match(name)
        if mo:
            name, num = mo.group('name', 'num')
            if num is not None:
                num = int(num)
            rfc2231_params.setdefault(name, []).append((num, value, encoded))
        else:
            new_params.append((name, '"%s"' % quote(value)))
    if rfc2231_params:
        for name, continuations in rfc2231_params.items():
            value = []
            extended = False
            
            continuations.sort()
            
            
            
            
            
            for num, s, encoded in continuations:
                if encoded:
                    
                    
                    
                    s = urllib.parse.unquote(s, encoding="latin-1")
                    extended = True
                value.append(s)
            value = quote(EMPTYSTRING.join(value))
            if extended:
                charset, language, value = decode_rfc2231(value)
                new_params.append((name, (charset, language, '"%s"' % value)))
            else:
                new_params.append((name, '"%s"' % value))
    return new_params

def collapse_rfc2231_value(value, errors='replace',
                           fallback_charset='us-ascii'):
    if not isinstance(value, tuple) or len(value) != 3:
        return unquote(value)
    
    
    
    charset, language, text = value
    if charset is None:
        
        
        charset = fallback_charset
    rawbytes = bytes(text, 'raw-unicode-escape')
    try:
        return str(rawbytes, charset, errors)
    except LookupError:
        
        return unquote(text)








def localtime(dt=None, isdst=None):
    
    if isdst is not None:
        import warnings
        warnings._deprecated(
            "The 'isdst' parameter to 'localtime'",
            message='{name} is deprecated and slated for removal in Python {remove}',
            remove=(3, 14),
            )
    if dt is None:
        dt = datetime.datetime.now()
    return dt.astimezone()