Source code for distro_tracker.core.utils.verp

# Copyright 2013 The Distro Tracker Developers
# See the COPYRIGHT file at the top-level directory of this distribution and
# at https://deb.li/DTAuthors
#
# This file is part of Distro Tracker. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution and at https://deb.li/DTLicense. No part of Distro Tracker,
# including this file, may be copied, modified, propagated, or distributed
# except according to the terms contained in the LICENSE file.

"""
Module for encoding and decoding Variable Envelope Return Path addresses.

It is implemented following the recommendations laid out in
`VERP <https://cr.yp.to/proto/verp.txt>`_ and
`<https://www.courier-mta.org/draft-varshavchik-verp-smtpext.txt>`_


>>> from distro_tracker.core.utils import verp

>>> str(verp.encode('itny-out@domain.com', 'node42!ann@old.example.com'))
'itny-out-node42+21ann=old.example.com@domain.com'

>>> map(str, decode('itny-out-node42+21ann=old.example.com@domain.com'))
['itny-out@domain.com', 'node42!ann@old.example.com']
"""

__all__ = ('encode', 'decode')

_RETURN_ADDRESS_TEMPLATE = (
    '{slocal}{separator}{encoderlocal}={encoderdomain}@{sdomain}')

_CHARACTERS = ('@', ':', '%', '!', '-', '[', ']', '+')
_ENCODE_MAPPINGS = {
    char: '+{val:0X}'.format(val=ord(char))
    for char in _CHARACTERS
}


[docs]def encode(sender_address, recipient_address, separator='-'): """ Encodes ``sender_address``, ``recipient_address`` to a VERP compliant address to be used as the envelope-from (return-path) address. :param sender_address: The email address of the sender :type sender_address: string :param recipient_address: The email address of the recipient :type recipient_address: string :param separator: The separator to be used between the sender's local part and the encoded recipient's local part in the resulting VERP address. :rtype: string >>> str(encode('itny-out@domain.com', 'node42!ann@old.example.com')) 'itny-out-node42+21ann=old.example.com@domain.com' >>> str(encode('itny-out@domain.com', 'tom@old.example.com')) 'itny-out-tom=old.example.com@domain.com' >>> str(encode('itny-out@domain.com', 'dave+priority@new.example.com')) 'itny-out-dave+2Bpriority=new.example.com@domain.com' >>> str(encode('bounce@dom.com', 'user+!%-:@[]+@other.com')) 'bounce-user+2B+21+25+2D+3A+40+5B+5D+2B=other.com@dom.com' """ # Split the addresses in two parts based on the last occurrence of '@' slocal, sdomain = sender_address.rsplit('@', 1) rlocal, rdomain = recipient_address.rsplit('@', 1) # Encode recipient parts by replacing relevant characters encoderlocal, encoderdomain = map(_encode_chars, (rlocal, rdomain)) # Putting it all together return _RETURN_ADDRESS_TEMPLATE.format(slocal=slocal, separator=separator, encoderlocal=encoderlocal, encoderdomain=encoderdomain, sdomain=sdomain)
[docs]def decode(verp_address, separator='-'): """ Decodes the given VERP encoded from address and returns the original sender address and recipient address, returning them as a tuple. :param verp_address: The return path address :type sender_address: string :param separator: The separator to be expected between the sender's local part and the encoded recipient's local part in the given ``verp_address`` >>> from_email, to_email = 'bounce@domain.com', 'user@other.com' >>> decode(encode(from_email, to_email)) == (from_email, to_email) True >>> map(str, decode('itny-out-dave+2Bpriority=new.example.com@domain.com')) ['itny-out@domain.com', 'dave+priority@new.example.com'] >>> map(str, decode('itny-out-node42+21ann=old.example.com@domain.com')) ['itny-out@domain.com', 'node42!ann@old.example.com'] >>> map(str, decode('bounce-addr+2B40=dom.com@asdf.com')) ['bounce@asdf.com', 'addr+40@dom.com'] >>> s = 'bounce-user+2B+21+25+2D+3A+40+5B+5D+2B=other.com@dom.com' >>> str(decode(s)[1]) 'user+!%-:@[]+@other.com' """ left_part, sdomain = verp_address.rsplit('@', 1) left_part, encodedrdomain = left_part.rsplit('=', 1) slocal, encodedrlocal = left_part.rsplit(separator, 1) rlocal, rdomain = map(_decode_chars, (encodedrlocal, encodedrdomain)) return (slocal + '@' + sdomain, rlocal + '@' + rdomain)
def _encode_chars(address): """ Helper function to replace the special characters in the recipient's address. """ return ''.join(_ENCODE_MAPPINGS.get(char, char) for char in address) def _decode_chars(address): """ Helper function to replace the encoded special characters with their regular character representation. """ for char in _CHARACTERS: address = address.replace(_ENCODE_MAPPINGS[char], char) address = address.replace(_ENCODE_MAPPINGS[char].lower(), char) return address if __name__ == '__main__': import doctest doctest.testmod()