coda : interception et ré-écriture des URL
[progfou.git] / coda / coda-filter.py
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)