#!/usr/bin/python

"""

AIM: Implements the PRA (Purported Responsible Address) algorithm
described in Internet-Draft draft-ietf-marid-par-00.txt.

USE: from the command line "python PRA.py < message" or from another Python
program, see the __main__ as an example.

REQUIRES: Python >= 2.2

LICENCE: trivial work, not patentable, not licenceable.

WARNING: Microsoft claims a patent on the PRA algorithm. The author
did not requested a licence.

AUTHOR: Stephane Bortzmeyer <bortzmeyer@nic.fr>

BUGS: I did not find any :-) But bug reports are welcome.

VERSION: $Id$

"""

import email
import sys
import string

def get_first_of(header_name, msg):
    """ Get the first NON-EMPTY header of this name """
    headers = msg.items()
    rank = 1
    for header in headers:
        if string.lower(header_name) == string.lower(header[0]) and \
           header[1]:
            return (rank, header[1])
        rank = rank + 1
    return (0, None)

def get_first_after(target_rank, header_name, msg):
    """ Get the first NON-EMPTY header of this name which starts after
    target_rank """
    headers = msg.items()
    rank = 1
    for header in headers:
        if rank < target_rank:
            rank = rank + 1
            continue
        if string.lower(header_name) == string.lower(header[0]) and \
           header[1]:
            return (rank, header[1])
        rank = rank + 1
    return (0, None)

def address_in(value):
    """ Extracts the email address from the value """
    if not value:
        raise Exception("Empty value, no address found")
    (name, address) = email.Utils.parseaddr(value)
    if not address:
        raise Exception("Not a valid email address %s" % value)
    # TODO: test that we have a syntax user@fqdn.something?
    return address

def check_only_one(header_name, msg):
    """ Check that there is only one header by this name """
    headers = msg.items()
    seen = False
    for header in headers:
        if (string.lower(header_name) == string.lower(header[0])) and \
           header[1]:
           if seen:
               raise Exception("Multiple %s headers" % header_name)
           seen = True

def get_pra(msg):
    """ Principal routine: gets the PRA """
    headers = msg.items()
    # The isolated numbers refer to steps in the specification (see section 2)
    # 1
    (rank_sender, resent_sender) = get_first_of("Resent-Sender", msg)
    if resent_sender is not None:
        (rank_from, resent_from) = get_first_of("Resent-From", msg)
        if resent_from is not None:
            (rank_received, received) = get_first_after(rank_from, "Received", msg)
            (rank_path, return_path) = get_first_after(rank_from,
                                                       "Return-Path", msg)
            if not ((received is not None and rank_received < rank_sender and 
                 rank_received > rank_from) or \
                (return_path is not None and rank_path < rank_sender and
                 rank_path > rank_from)):
                return address_in(resent_sender)
    # 2
    (rank_from, resent_from) = get_first_of("Resent-From", msg)
    if resent_from is not None:
        return address_in(resent_from)
    # 3
    check_only_one("Sender", msg)
    (rank_sender, sender) = get_first_of("Sender", msg)
    if sender is not None:
        return address_in(sender)
    # 4
    check_only_one("From", msg)
    (rank_sender, from_v) = get_first_of("From", msg)
    if from_v is not None:
        return address_in(from_v)
   
if __name__ == '__main__':
    msg = email.message_from_file(sys.stdin)
    print get_pra(msg)


        
    
