home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / email / _parseaddr.py < prev    next >
Encoding:
Python Source  |  2006-08-25  |  14.4 KB  |  471 lines

  1. # Copyright (C) 2002-2006 Python Software Foundation
  2. # Contact: email-sig@python.org
  3.  
  4. """Email address parsing code.
  5.  
  6. Lifted directly from rfc822.py.  This should eventually be rewritten.
  7. """
  8.  
  9. import time
  10.  
  11. SPACE = ' '
  12. EMPTYSTRING = ''
  13. COMMASPACE = ', '
  14.  
  15. # Parse a date field
  16. _monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
  17.                'aug', 'sep', 'oct', 'nov', 'dec',
  18.                'january', 'february', 'march', 'april', 'may', 'june', 'july',
  19.                'august', 'september', 'october', 'november', 'december']
  20.  
  21. _daynames = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
  22.  
  23. # The timezone table does not include the military time zones defined
  24. # in RFC822, other than Z.  According to RFC1123, the description in
  25. # RFC822 gets the signs wrong, so we can't rely on any such time
  26. # zones.  RFC1123 recommends that numeric timezone indicators be used
  27. # instead of timezone names.
  28.  
  29. _timezones = {'UT':0, 'UTC':0, 'GMT':0, 'Z':0,
  30.               'AST': -400, 'ADT': -300,  # Atlantic (used in Canada)
  31.               'EST': -500, 'EDT': -400,  # Eastern
  32.               'CST': -600, 'CDT': -500,  # Central
  33.               'MST': -700, 'MDT': -600,  # Mountain
  34.               'PST': -800, 'PDT': -700   # Pacific
  35.               }
  36.  
  37.  
  38. def parsedate_tz(data):
  39.     """Convert a date string to a time tuple.
  40.  
  41.     Accounts for military timezones.
  42.     """
  43.     data = data.split()
  44.     # The FWS after the comma after the day-of-week is optional, so search and
  45.     # adjust for this.
  46.     if data[0].endswith(',') or data[0].lower() in _daynames:
  47.         # There's a dayname here. Skip it
  48.         del data[0]
  49.     else:
  50.         i = data[0].rfind(',')
  51.         if i >= 0:
  52.             data[0] = data[0][i+1:]
  53.     if len(data) == 3: # RFC 850 date, deprecated
  54.         stuff = data[0].split('-')
  55.         if len(stuff) == 3:
  56.             data = stuff + data[1:]
  57.     if len(data) == 4:
  58.         s = data[3]
  59.         i = s.find('+')
  60.         if i > 0:
  61.             data[3:] = [s[:i], s[i+1:]]
  62.         else:
  63.             data.append('') # Dummy tz
  64.     if len(data) < 5:
  65.         return None
  66.     data = data[:5]
  67.     [dd, mm, yy, tm, tz] = data
  68.     mm = mm.lower()
  69.     if mm not in _monthnames:
  70.         dd, mm = mm, dd.lower()
  71.         if mm not in _monthnames:
  72.             return None
  73.     mm = _monthnames.index(mm) + 1
  74.     if mm > 12:
  75.         mm -= 12
  76.     if dd[-1] == ',':
  77.         dd = dd[:-1]
  78.     i = yy.find(':')
  79.     if i > 0:
  80.         yy, tm = tm, yy
  81.     if yy[-1] == ',':
  82.         yy = yy[:-1]
  83.     if not yy[0].isdigit():
  84.         yy, tz = tz, yy
  85.     if tm[-1] == ',':
  86.         tm = tm[:-1]
  87.     tm = tm.split(':')
  88.     if len(tm) == 2:
  89.         [thh, tmm] = tm
  90.         tss = '0'
  91.     elif len(tm) == 3:
  92.         [thh, tmm, tss] = tm
  93.     else:
  94.         return None
  95.     try:
  96.         yy = int(yy)
  97.         dd = int(dd)
  98.         thh = int(thh)
  99.         tmm = int(tmm)
  100.         tss = int(tss)
  101.     except ValueError:
  102.         return None
  103.     tzoffset = None
  104.     tz = tz.upper()
  105.     if _timezones.has_key(tz):
  106.         tzoffset = _timezones[tz]
  107.     else:
  108.         try:
  109.             tzoffset = int(tz)
  110.         except ValueError:
  111.             pass
  112.     # Convert a timezone offset into seconds ; -0500 -> -18000
  113.     if tzoffset:
  114.         if tzoffset < 0:
  115.             tzsign = -1
  116.             tzoffset = -tzoffset
  117.         else:
  118.             tzsign = 1
  119.         tzoffset = tzsign * ( (tzoffset//100)*3600 + (tzoffset % 100)*60)
  120.     return yy, mm, dd, thh, tmm, tss, 0, 1, 0, tzoffset
  121.  
  122.  
  123. def parsedate(data):
  124.     """Convert a time string to a time tuple."""
  125.     t = parsedate_tz(data)
  126.     if isinstance(t, tuple):
  127.         return t[:9]
  128.     else:
  129.         return t
  130.  
  131.  
  132. def mktime_tz(data):
  133.     """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp."""
  134.     if data[9] is None:
  135.         # No zone info, so localtime is better assumption than GMT
  136.         return time.mktime(data[:8] + (-1,))
  137.     else:
  138.         t = time.mktime(data[:8] + (0,))
  139.         return t - data[9] - time.timezone
  140.  
  141.  
  142. def quote(str):
  143.     """Add quotes around a string."""
  144.     return str.replace('\\', '\\\\').replace('"', '\\"')
  145.  
  146.  
  147. class AddrlistClass:
  148.     """Address parser class by Ben Escoto.
  149.  
  150.     To understand what this class does, it helps to have a copy of RFC 2822 in
  151.     front of you.
  152.  
  153.     Note: this class interface is deprecated and may be removed in the future.
  154.     Use rfc822.AddressList instead.
  155.     """
  156.  
  157.     def __init__(self, field):
  158.         """Initialize a new instance.
  159.  
  160.         `field' is an unparsed address header field, containing
  161.         one or more addresses.
  162.         """
  163.         self.specials = '()<>@,:;.\"[]'
  164.         self.pos = 0
  165.         self.LWS = ' \t'
  166.         self.CR = '\r\n'
  167.         self.atomends = self.specials + self.LWS + self.CR
  168.         # Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
  169.         # is obsolete syntax.  RFC 2822 requires that we recognize obsolete
  170.         # syntax, so allow dots in phrases.
  171.         self.phraseends = self.atomends.replace('.', '')
  172.         self.field = field
  173.         self.commentlist = []
  174.  
  175.     def gotonext(self):
  176.         """Parse up to the start of the next address."""
  177.         while self.pos < len(self.field):
  178.             if self.field[self.pos] in self.LWS + '\n\r':
  179.                 self.pos += 1
  180.             elif self.field[self.pos] == '(':
  181.                 self.commentlist.append(self.getcomment())
  182.             else:
  183.                 break
  184.  
  185.     def getaddrlist(self):
  186.         """Parse all addresses.
  187.  
  188.         Returns a list containing all of the addresses.
  189.         """
  190.         result = []
  191.         while self.pos < len(self.field):
  192.             ad = self.getaddress()
  193.             if ad:
  194.                 result += ad
  195.             else:
  196.                 result.append(('', ''))
  197.         return result
  198.  
  199.     def getaddress(self):
  200.         """Parse the next address."""
  201.         self.commentlist = []
  202.         self.gotonext()
  203.  
  204.         oldpos = self.pos
  205.         oldcl = self.commentlist
  206.         plist = self.getphraselist()
  207.  
  208.         self.gotonext()
  209.         returnlist = []
  210.  
  211.         if self.pos >= len(self.field):
  212.             # Bad email address technically, no domain.
  213.             if plist:
  214.                 returnlist = [(SPACE.join(self.commentlist), plist[0])]
  215.  
  216.         elif self.field[self.pos] in '.@':
  217.             # email address is just an addrspec
  218.             # this isn't very efficient since we start over
  219.             self.pos = oldpos
  220.             self.commentlist = oldcl
  221.             addrspec = self.getaddrspec()
  222.             returnlist = [(SPACE.join(self.commentlist), addrspec)]
  223.  
  224.         elif self.field[self.pos] == ':':
  225.             # address is a group
  226.             returnlist = []
  227.  
  228.             fieldlen = len(self.field)
  229.             self.pos += 1
  230.             while self.pos < len(self.field):
  231.                 self.gotonext()
  232.                 if self.pos < fieldlen and self.field[self.pos] == ';':
  233.                     self.pos += 1
  234.                     break
  235.                 returnlist = returnlist + self.getaddress()
  236.  
  237.         elif self.field[self.pos] == '<':
  238.             # Address is a phrase then a route addr
  239.             routeaddr = self.getrouteaddr()
  240.  
  241.             if self.commentlist:
  242.                 returnlist = [(SPACE.join(plist) + ' (' +
  243.                                ' '.join(self.commentlist) + ')', routeaddr)]
  244.             else:
  245.                 returnlist = [(SPACE.join(plist), routeaddr)]
  246.  
  247.         else:
  248.             if plist:
  249.                 returnlist = [(SPACE.join(self.commentlist), plist[0])]
  250.             elif self.field[self.pos] in self.specials:
  251.                 self.pos += 1
  252.  
  253.         self.gotonext()
  254.         if self.pos < len(self.field) and self.field[self.pos] == ',':
  255.             self.pos += 1
  256.         return returnlist
  257.  
  258.     def getrouteaddr(self):
  259.         """Parse a route address (Return-path value).
  260.  
  261.         This method just skips all the route stuff and returns the addrspec.
  262.         """
  263.         if self.field[self.pos] != '<':
  264.             return
  265.  
  266.         expectroute = False
  267.         self.pos += 1
  268.         self.gotonext()
  269.         adlist = ''
  270.         while self.pos < len(self.field):
  271.             if expectroute:
  272.                 self.getdomain()
  273.                 expectroute = False
  274.             elif self.field[self.pos] == '>':
  275.                 self.pos += 1
  276.                 break
  277.             elif self.field[self.pos] == '@':
  278.                 self.pos += 1
  279.                 expectroute = True
  280.             elif self.field[self.pos] == ':':
  281.                 self.pos += 1
  282.             else:
  283.                 adlist = self.getaddrspec()
  284.                 self.pos += 1
  285.                 break
  286.             self.gotonext()
  287.  
  288.         return adlist
  289.  
  290.     def getaddrspec(self):
  291.         """Parse an RFC 2822 addr-spec."""
  292.         aslist = []
  293.  
  294.         self.gotonext()
  295.         while self.pos < len(self.field):
  296.             if self.field[self.pos] == '.':
  297.                 aslist.append('.')
  298.                 self.pos += 1
  299.             elif self.field[self.pos] == '"':
  300.                 aslist.append('"%s"' % self.getquote())
  301.             elif self.field[self.pos] in self.atomends:
  302.                 break
  303.             else:
  304.                 aslist.append(self.getatom())
  305.             self.gotonext()
  306.  
  307.         if self.pos >= len(self.field) or self.field[self.pos] != '@':
  308.             return EMPTYSTRING.join(aslist)
  309.  
  310.         aslist.append('@')
  311.         self.pos += 1
  312.         self.gotonext()
  313.         return EMPTYSTRING.join(aslist) + self.getdomain()
  314.  
  315.     def getdomain(self):
  316.         """Get the complete domain name from an address."""
  317.         sdlist = []
  318.         while self.pos < len(self.field):
  319.             if self.field[self.pos] in self.LWS:
  320.                 self.pos += 1
  321.             elif self.field[self.pos] == '(':
  322.                 self.commentlist.append(self.getcomment())
  323.             elif self.field[self.pos] == '[':
  324.                 sdlist.append(self.getdomainliteral())
  325.             elif self.field[self.pos] == '.':
  326.                 self.pos += 1
  327.                 sdlist.append('.')
  328.             elif self.field[self.pos] in self.atomends:
  329.                 break
  330.             else:
  331.                 sdlist.append(self.getatom())
  332.         return EMPTYSTRING.join(sdlist)
  333.  
  334.     def getdelimited(self, beginchar, endchars, allowcomments=True):
  335.         """Parse a header fragment delimited by special characters.
  336.  
  337.         `beginchar' is the start character for the fragment.
  338.         If self is not looking at an instance of `beginchar' then
  339.         getdelimited returns the empty string.
  340.  
  341.         `endchars' is a sequence of allowable end-delimiting characters.
  342.         Parsing stops when one of these is encountered.
  343.  
  344.         If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
  345.         within the parsed fragment.
  346.         """
  347.         if self.field[self.pos] != beginchar:
  348.             return ''
  349.  
  350.         slist = ['']
  351.         quote = False
  352.         self.pos += 1
  353.         while self.pos < len(self.field):
  354.             if quote:
  355.                 slist.append(self.field[self.pos])
  356.                 quote = False
  357.             elif self.field[self.pos] in endchars:
  358.                 self.pos += 1
  359.                 break
  360.             elif allowcomments and self.field[self.pos] == '(':
  361.                 slist.append(self.getcomment())
  362.             elif self.field[self.pos] == '\\':
  363.                 quote = True
  364.             else:
  365.                 slist.append(self.field[self.pos])
  366.             self.pos += 1
  367.  
  368.         return EMPTYSTRING.join(slist)
  369.  
  370.     def getquote(self):
  371.         """Get a quote-delimited fragment from self's field."""
  372.         return self.getdelimited('"', '"\r', False)
  373.  
  374.     def getcomment(self):
  375.         """Get a parenthesis-delimited fragment from self's field."""
  376.         return self.getdelimited('(', ')\r', True)
  377.  
  378.     def getdomainliteral(self):
  379.         """Parse an RFC 2822 domain-literal."""
  380.         return '[%s]' % self.getdelimited('[', ']\r', False)
  381.  
  382.     def getatom(self, atomends=None):
  383.         """Parse an RFC 2822 atom.
  384.  
  385.         Optional atomends specifies a different set of end token delimiters
  386.         (the default is to use self.atomends).  This is used e.g. in
  387.         getphraselist() since phrase endings must not include the `.' (which
  388.         is legal in phrases)."""
  389.         atomlist = ['']
  390.         if atomends is None:
  391.             atomends = self.atomends
  392.  
  393.         while self.pos < len(self.field):
  394.             if self.field[self.pos] in atomends:
  395.                 break
  396.             else:
  397.                 atomlist.append(self.field[self.pos])
  398.             self.pos += 1
  399.  
  400.         return EMPTYSTRING.join(atomlist)
  401.  
  402.     def getphraselist(self):
  403.         """Parse a sequence of RFC 2822 phrases.
  404.  
  405.         A phrase is a sequence of words, which are in turn either RFC 2822
  406.         atoms or quoted-strings.  Phrases are canonicalized by squeezing all
  407.         runs of continuous whitespace into one space.
  408.         """
  409.         plist = []
  410.  
  411.         while self.pos < len(self.field):
  412.             if self.field[self.pos] in self.LWS:
  413.                 self.pos += 1
  414.             elif self.field[self.pos] == '"':
  415.                 plist.append(self.getquote())
  416.             elif self.field[self.pos] == '(':
  417.                 self.commentlist.append(self.getcomment())
  418.             elif self.field[self.pos] in self.phraseends:
  419.                 break
  420.             else:
  421.                 plist.append(self.getatom(self.phraseends))
  422.  
  423.         return plist
  424.  
  425. class AddressList(AddrlistClass):
  426.     """An AddressList encapsulates a list of parsed RFC 2822 addresses."""
  427.     def __init__(self, field):
  428.         AddrlistClass.__init__(self, field)
  429.         if field:
  430.             self.addresslist = self.getaddrlist()
  431.         else:
  432.             self.addresslist = []
  433.  
  434.     def __len__(self):
  435.         return len(self.addresslist)
  436.  
  437.     def __add__(self, other):
  438.         # Set union
  439.         newaddr = AddressList(None)
  440.         newaddr.addresslist = self.addresslist[:]
  441.         for x in other.addresslist:
  442.             if not x in self.addresslist:
  443.                 newaddr.addresslist.append(x)
  444.         return newaddr
  445.  
  446.     def __iadd__(self, other):
  447.         # Set union, in-place
  448.         for x in other.addresslist:
  449.             if not x in self.addresslist:
  450.                 self.addresslist.append(x)
  451.         return self
  452.  
  453.     def __sub__(self, other):
  454.         # Set difference
  455.         newaddr = AddressList(None)
  456.         for x in self.addresslist:
  457.             if not x in other.addresslist:
  458.                 newaddr.addresslist.append(x)
  459.         return newaddr
  460.  
  461.     def __isub__(self, other):
  462.         # Set difference, in-place
  463.         for x in other.addresslist:
  464.             if x in self.addresslist:
  465.                 self.addresslist.remove(x)
  466.         return self
  467.  
  468.     def __getitem__(self, index):
  469.         # Make indexing, slices, and 'in' work
  470.         return self.addresslist[index]
  471.