+++ /dev/null
-# Copyright (C) 2001-2010 Python Software Foundation\r
-# Author: Barry Warsaw\r
-# Contact: email-sig@python.org\r
-\r
-"""Miscellaneous utilities."""\r
-\r
-__all__ = [\r
- 'collapse_rfc2231_value',\r
- 'decode_params',\r
- 'decode_rfc2231',\r
- 'encode_rfc2231',\r
- 'formataddr',\r
- 'formatdate',\r
- 'getaddresses',\r
- 'make_msgid',\r
- 'mktime_tz',\r
- 'parseaddr',\r
- 'parsedate',\r
- 'parsedate_tz',\r
- 'unquote',\r
- ]\r
-\r
-import os\r
-import re\r
-import time\r
-import base64\r
-import random\r
-import socket\r
-import urllib\r
-import warnings\r
-\r
-from email._parseaddr import quote\r
-from email._parseaddr import AddressList as _AddressList\r
-from email._parseaddr import mktime_tz\r
-\r
-# We need wormarounds for bugs in these methods in older Pythons (see below)\r
-from email._parseaddr import parsedate as _parsedate\r
-from email._parseaddr import parsedate_tz as _parsedate_tz\r
-\r
-from quopri import decodestring as _qdecode\r
-\r
-# Intrapackage imports\r
-from email.encoders import _bencode, _qencode\r
-\r
-COMMASPACE = ', '\r
-EMPTYSTRING = ''\r
-UEMPTYSTRING = u''\r
-CRLF = '\r\n'\r
-TICK = "'"\r
-\r
-specialsre = re.compile(r'[][\\()<>@,:;".]')\r
-escapesre = re.compile(r'[][\\()"]')\r
-\r
-\r
-\f\r
-# Helpers\r
-\r
-def _identity(s):\r
- return s\r
-\r
-\r
-def _bdecode(s):\r
- """Decodes a base64 string.\r
-\r
- This function is equivalent to base64.decodestring and it's retained only\r
- for backward compatibility. It used to remove the last \n of the decoded\r
- string, if it had any (see issue 7143).\r
- """\r
- if not s:\r
- return s\r
- return base64.decodestring(s)\r
-\r
-\r
-\f\r
-def fix_eols(s):\r
- """Replace all line-ending characters with \r\n."""\r
- # Fix newlines with no preceding carriage return\r
- s = re.sub(r'(?<!\r)\n', CRLF, s)\r
- # Fix carriage returns with no following newline\r
- s = re.sub(r'\r(?!\n)', CRLF, s)\r
- return s\r
-\r
-\r
-\f\r
-def formataddr(pair):\r
- """The inverse of parseaddr(), this takes a 2-tuple of the form\r
- (realname, email_address) and returns the string value suitable\r
- for an RFC 2822 From, To or Cc header.\r
-\r
- If the first element of pair is false, then the second element is\r
- returned unmodified.\r
- """\r
- name, address = pair\r
- if name:\r
- quotes = ''\r
- if specialsre.search(name):\r
- quotes = '"'\r
- name = escapesre.sub(r'\\\g<0>', name)\r
- return '%s%s%s <%s>' % (quotes, name, quotes, address)\r
- return address\r
-\r
-\r
-\f\r
-def getaddresses(fieldvalues):\r
- """Return a list of (REALNAME, EMAIL) for each fieldvalue."""\r
- all = COMMASPACE.join(fieldvalues)\r
- a = _AddressList(all)\r
- return a.addresslist\r
-\r
-\r
-\f\r
-ecre = re.compile(r'''\r
- =\? # literal =?\r
- (?P<charset>[^?]*?) # non-greedy up to the next ? is the charset\r
- \? # literal ?\r
- (?P<encoding>[qb]) # either a "q" or a "b", case insensitive\r
- \? # literal ?\r
- (?P<atom>.*?) # non-greedy up to the next ?= is the atom\r
- \?= # literal ?=\r
- ''', re.VERBOSE | re.IGNORECASE)\r
-\r
-\r
-\f\r
-def formatdate(timeval=None, localtime=False, usegmt=False):\r
- """Returns a date string as specified by RFC 2822, e.g.:\r
-\r
- Fri, 09 Nov 2001 01:08:47 -0000\r
-\r
- Optional timeval if given is a floating point time value as accepted by\r
- gmtime() and localtime(), otherwise the current time is used.\r
-\r
- Optional localtime is a flag that when True, interprets timeval, and\r
- returns a date relative to the local timezone instead of UTC, properly\r
- taking daylight savings time into account.\r
-\r
- Optional argument usegmt means that the timezone is written out as\r
- an ascii string, not numeric one (so "GMT" instead of "+0000"). This\r
- is needed for HTTP, and is only used when localtime==False.\r
- """\r
- # Note: we cannot use strftime() because that honors the locale and RFC\r
- # 2822 requires that day and month names be the English abbreviations.\r
- if timeval is None:\r
- timeval = time.time()\r
- if localtime:\r
- now = time.localtime(timeval)\r
- # Calculate timezone offset, based on whether the local zone has\r
- # daylight savings time, and whether DST is in effect.\r
- if time.daylight and now[-1]:\r
- offset = time.altzone\r
- else:\r
- offset = time.timezone\r
- hours, minutes = divmod(abs(offset), 3600)\r
- # Remember offset is in seconds west of UTC, but the timezone is in\r
- # minutes east of UTC, so the signs differ.\r
- if offset > 0:\r
- sign = '-'\r
- else:\r
- sign = '+'\r
- zone = '%s%02d%02d' % (sign, hours, minutes // 60)\r
- else:\r
- now = time.gmtime(timeval)\r
- # Timezone offset is always -0000\r
- if usegmt:\r
- zone = 'GMT'\r
- else:\r
- zone = '-0000'\r
- return '%s, %02d %s %04d %02d:%02d:%02d %s' % (\r
- ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][now[6]],\r
- now[2],\r
- ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\r
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now[1] - 1],\r
- now[0], now[3], now[4], now[5],\r
- zone)\r
-\r
-\r
-\f\r
-def make_msgid(idstring=None):\r
- """Returns a string suitable for RFC 2822 compliant Message-ID, e.g:\r
-\r
- <20020201195627.33539.96671@nightshade.la.mastaler.com>\r
-\r
- Optional idstring if given is a string used to strengthen the\r
- uniqueness of the message id.\r
- """\r
- timeval = time.time()\r
- utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))\r
- pid = os.getpid()\r
- randint = random.randrange(100000)\r
- if idstring is None:\r
- idstring = ''\r
- else:\r
- idstring = '.' + idstring\r
- idhost = socket.getfqdn()\r
- msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)\r
- return msgid\r
-\r
-\r
-\f\r
-# These functions are in the standalone mimelib version only because they've\r
-# subsequently been fixed in the latest Python versions. We use this to worm\r
-# around broken older Pythons.\r
-def parsedate(data):\r
- if not data:\r
- return None\r
- return _parsedate(data)\r
-\r
-\r
-def parsedate_tz(data):\r
- if not data:\r
- return None\r
- return _parsedate_tz(data)\r
-\r
-\r
-def parseaddr(addr):\r
- addrs = _AddressList(addr).addresslist\r
- if not addrs:\r
- return '', ''\r
- return addrs[0]\r
-\r
-\r
-# rfc822.unquote() doesn't properly de-backslash-ify in Python pre-2.3.\r
-def unquote(str):\r
- """Remove quotes from a string."""\r
- if len(str) > 1:\r
- if str.startswith('"') and str.endswith('"'):\r
- return str[1:-1].replace('\\\\', '\\').replace('\\"', '"')\r
- if str.startswith('<') and str.endswith('>'):\r
- return str[1:-1]\r
- return str\r
-\r
-\r
-\f\r
-# RFC2231-related functions - parameter encoding and decoding\r
-def decode_rfc2231(s):\r
- """Decode string according to RFC 2231"""\r
- parts = s.split(TICK, 2)\r
- if len(parts) <= 2:\r
- return None, None, s\r
- return parts\r
-\r
-\r
-def encode_rfc2231(s, charset=None, language=None):\r
- """Encode string according to RFC 2231.\r
-\r
- If neither charset nor language is given, then s is returned as-is. If\r
- charset is given but not language, the string is encoded using the empty\r
- string for language.\r
- """\r
- import urllib\r
- s = urllib.quote(s, safe='')\r
- if charset is None and language is None:\r
- return s\r
- if language is None:\r
- language = ''\r
- return "%s'%s'%s" % (charset, language, s)\r
-\r
-\r
-rfc2231_continuation = re.compile(r'^(?P<name>\w+)\*((?P<num>[0-9]+)\*?)?$')\r
-\r
-def decode_params(params):\r
- """Decode parameters list according to RFC 2231.\r
-\r
- params is a sequence of 2-tuples containing (param name, string value).\r
- """\r
- # Copy params so we don't mess with the original\r
- params = params[:]\r
- new_params = []\r
- # Map parameter's name to a list of continuations. The values are a\r
- # 3-tuple of the continuation number, the string value, and a flag\r
- # specifying whether a particular segment is %-encoded.\r
- rfc2231_params = {}\r
- name, value = params.pop(0)\r
- new_params.append((name, value))\r
- while params:\r
- name, value = params.pop(0)\r
- if name.endswith('*'):\r
- encoded = True\r
- else:\r
- encoded = False\r
- value = unquote(value)\r
- mo = rfc2231_continuation.match(name)\r
- if mo:\r
- name, num = mo.group('name', 'num')\r
- if num is not None:\r
- num = int(num)\r
- rfc2231_params.setdefault(name, []).append((num, value, encoded))\r
- else:\r
- new_params.append((name, '"%s"' % quote(value)))\r
- if rfc2231_params:\r
- for name, continuations in rfc2231_params.items():\r
- value = []\r
- extended = False\r
- # Sort by number\r
- continuations.sort()\r
- # And now append all values in numerical order, converting\r
- # %-encodings for the encoded segments. If any of the\r
- # continuation names ends in a *, then the entire string, after\r
- # decoding segments and concatenating, must have the charset and\r
- # language specifiers at the beginning of the string.\r
- for num, s, encoded in continuations:\r
- if encoded:\r
- s = urllib.unquote(s)\r
- extended = True\r
- value.append(s)\r
- value = quote(EMPTYSTRING.join(value))\r
- if extended:\r
- charset, language, value = decode_rfc2231(value)\r
- new_params.append((name, (charset, language, '"%s"' % value)))\r
- else:\r
- new_params.append((name, '"%s"' % value))\r
- return new_params\r
-\r
-def collapse_rfc2231_value(value, errors='replace',\r
- fallback_charset='us-ascii'):\r
- if isinstance(value, tuple):\r
- rawval = unquote(value[2])\r
- charset = value[0] or 'us-ascii'\r
- try:\r
- return unicode(rawval, charset, errors)\r
- except LookupError:\r
- # XXX charset is unknown to Python.\r
- return unicode(rawval, fallback_charset, errors)\r
- else:\r
- return unquote(value)\r