| 1 | #!/usr/bin/python |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # |
| 4 | # Objectifs : |
| 5 | # 05. si le destinataire est noreply@auf.org, on le remplace par l'émetteur |
| 6 | # (ANNULÉ car ça ne devrait plus arriver) |
| 7 | # (REPRIS pour le cas où le destinataire est impression-coda@auf.org) |
| 8 | # 10. envoyer une copie à l'émetteur, sauf s'il est déjà le destinataire |
| 9 | # (ANNULÉ car envoi déjà vers les bons destinataires) |
| 10 | # 20. ajouter une en-tête X-Coda pour faciliter les filtrages |
| 11 | # 30. préfixer le sujet par '[CODA] ' |
| 12 | # 40. remplacer l'organisation par AuF (forme longue, au lieu de CODA) |
| 13 | # 50. renommer l'attachement en utilisant le sujet |
| 14 | # Content-Type: application/pdf; name=attachment.pdf |
| 15 | # Content-Disposition: inline; filename=attachment.pdf |
| 16 | # |
| 17 | import sys |
| 18 | import email |
| 19 | import email.charset |
| 20 | from email.header import decode_header |
| 21 | import smtplib |
| 22 | import re |
| 23 | |
| 24 | # préfixe pour le sujet |
| 25 | SUBJECT_PREFIX = "[CODA] " |
| 26 | # contenu pour l'en-tête X-Coda qui sera ajoutée |
| 27 | X_CODA_VALUE = "Filtre AuF pour Coda v2" |
| 28 | # contenu pour l'en-tête Organisation |
| 29 | ORGANIZATION = "Agence universitaire de la Francophonie" |
| 30 | # masque de repérage du type et numéro de document |
| 31 | DOC_PATTERN = r'^(.*:)?\s*(\S+)\s*/\s*(\d+)\s*$' |
| 32 | # format du nouveau nom de document (à partir du type et numéro) |
| 33 | DOC_FORMAT = 'CODA-%s-%08d.pdf' |
| 34 | # réécriture des liens pour coda.auf |
| 35 | BODY_PATTERN_1 = r'\n(http://coda.auf/)(.*)\n' |
| 36 | BODY_FORMAT_1 = r'\n\nAccès depuis une implantation AUF :\n \1\2\n\nAccès depuis Internet :\n https://coda.auf.org/\2\n\n' |
| 37 | # réécriture des liens pour form.coda.auf |
| 38 | BODY_PATTERN_2 = r'\n(http://form.coda.auf/)(.*)\n' |
| 39 | BODY_FORMAT_2 = r'\n\nAccès depuis une implantation AUF :\n \1\2\n\nAccès depuis Internet :\n https://form-coda.auf.org/\2\n\n' |
| 40 | |
| 41 | # codes de sortie venant de <sysexits.h> |
| 42 | EX_TEMPFAIL = 75 |
| 43 | EX_UNAVAILABLE = 69 |
| 44 | |
| 45 | # on récupère l'expéditeur et le(s) destinataire(s) en paramètres |
| 46 | #email_from = 'jca.test@auf.org' |
| 47 | email_from = sys.argv[1] |
| 48 | email_to_list = sys.argv[2:] |
| 49 | |
| 50 | # on tente de récupérer le message arrivant sur l'entrée standard (pipe) |
| 51 | try: |
| 52 | msg = email.message_from_file(sys.stdin) |
| 53 | # en cas d'échec on demande à Postfix de considérer que c'est temporaire |
| 54 | except Exception, err: |
| 55 | print "Error: %s" % err |
| 56 | sys.exit(EX_TEMPFAIL) |
| 57 | |
| 58 | #-------------------------------------------------------------------------- |
| 59 | # TRAITEMENTS |
| 60 | #-------------------------------------------------------------------------- |
| 61 | |
| 62 | # par défaut les textes seront codés en Quoted-Printable |
| 63 | qp_charset = email.charset.Charset('utf-8') |
| 64 | qp_charset.header_encoding = email.charset.QP |
| 65 | qp_charset.body_encoding = email.charset.QP |
| 66 | |
| 67 | # 05. si le destinataire est noreply@auf.org, on le remplace par l'émetteur |
| 68 | # (ANNULÉ car ça ne devrait plus arriver) |
| 69 | # (REPRIS pour le cas où le destinataire est impression-coda@auf.org) |
| 70 | if 'impression-coda@auf.org' in email_to_list: |
| 71 | old_to = 'impression-coda@auf.org' |
| 72 | email_to_list.remove(old_to) |
| 73 | email_to_list.append(email_from) |
| 74 | msg.replace_header('To', email_from) |
| 75 | msg.add_header('X-Coda-Was-To', old_to) |
| 76 | |
| 77 | # 10. on ajoute l'émetteur aux destinataires, sauf s'il est déjà le destinataire |
| 78 | # (ANNULÉ car envoi déjà vers les bons destinataires) |
| 79 | #if email_from not in email_to_list and not email_from.endswith('@ca.auf.org'): |
| 80 | # email_to_list.append(email_from) |
| 81 | # msg.add_header('Cc', email_from) |
| 82 | |
| 83 | # 20. on ajoute une en-tête pour aider aux filtrages |
| 84 | msg.add_header('X-Coda', X_CODA_VALUE) |
| 85 | |
| 86 | # 30. on préfixe le sujet actuel par '[CODA] ' |
| 87 | subject = msg['Subject'] |
| 88 | try: |
| 89 | parts = decode_header(subject) |
| 90 | subject = u' '.join([unicode(s, e or 'ascii') for s,e in parts]) |
| 91 | except: |
| 92 | pass |
| 93 | msg.replace_header('Subject', SUBJECT_PREFIX + subject) |
| 94 | |
| 95 | # 40. on remplace l'organisation par l'AuF |
| 96 | del msg['ORGANISATON'] # non, ce n'est pas une faute de frappe… |
| 97 | msg.add_header('Organization', ORGANIZATION) |
| 98 | |
| 99 | # 50. on renomme l'attachement |
| 100 | |
| 101 | # construction du nouveau nom de fichier |
| 102 | # on tente de repérer le type et numéro de document |
| 103 | try: |
| 104 | m = re.match(DOC_PATTERN, subject) |
| 105 | prefix, com_type, com_num = m.groups() |
| 106 | new_filename = DOC_FORMAT % (com_type, int(com_num)) |
| 107 | # en cas d'échec on ignore le problème |
| 108 | except Exception, err: |
| 109 | print "Error: %s" % err |
| 110 | new_filename = 'document-coda.pdf' |
| 111 | |
| 112 | # parcours des différentes parties du courriel |
| 113 | for part in msg.walk(): |
| 114 | # traitement du corps de message pour y remplacer : |
| 115 | # http://coda.auf/coda/… |
| 116 | # par : |
| 117 | # Accès depuis une implantation AUF : http://coda.auf/coda/… |
| 118 | # Accès depuis Internet : https://coda.auf.org/coda/… |
| 119 | if part.get_content_type() == 'text/plain': |
| 120 | text = part.get_payload(decode=True) |
| 121 | new_text = re.sub(BODY_PATTERN_1, BODY_FORMAT_1, text) |
| 122 | new_text = re.sub(BODY_PATTERN_2, BODY_FORMAT_2, new_text) |
| 123 | if new_text != text: |
| 124 | del part['Content-Transfer-Encoding'] |
| 125 | part.set_payload(new_text, qp_charset) |
| 126 | continue |
| 127 | # traitement uniquement si c'est un attachement de PDF |
| 128 | if part.get_content_type() != 'application/pdf': |
| 129 | continue |
| 130 | # adaptation du nom de fichier dans le Content-Type |
| 131 | try: |
| 132 | raw_param = part.get_param('name') |
| 133 | param = email.utils.collapse_rfc2231_value(raw_param) |
| 134 | if param == 'attachment.pdf': |
| 135 | part.set_param('name', new_filename) |
| 136 | except: |
| 137 | pass |
| 138 | # adaptation du nom de fichier dans le Content-Disposition |
| 139 | try: |
| 140 | raw_param = part.get_param('filename', header='Content-Disposition') |
| 141 | param = email.utils.collapse_rfc2231_value(raw_param) |
| 142 | if param == 'attachment.pdf': |
| 143 | part.set_param('filename', new_filename, header='Content-Disposition') |
| 144 | except: |
| 145 | pass |
| 146 | |
| 147 | #-------------------------------------------------------------------------- |
| 148 | |
| 149 | # on se connecte à Postfix via le port de retour de filtrage |
| 150 | client = smtplib.SMTP('localhost', 10026) |
| 151 | #client.set_debuglevel(1) |
| 152 | |
| 153 | # on tente d'envoyer le courriel modifié |
| 154 | try: |
| 155 | client.sendmail(email_from, email_to_list, msg.as_string()) |
| 156 | # en cas d'échec on demande à Postfix de considérer que c'est temporaire |
| 157 | except Exception, err: |
| 158 | print "Error: %s" % err |
| 159 | sys.exit(EX_TEMPFAIL) |
| 160 | |
| 161 | # on termine proprement |
| 162 | client.quit() |
| 163 | sys.exit(0) |