Date de publication du RFC : Août 2019
Auteur(s) du RFC : N. Jenkins (FastMail), C. Newman
(Oracle)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF jmap
Première rédaction de cet article le 14 août 2019
Ce nouveau RFC décrit un remplaçant pour le traditionnel protocole IMAP, remplaçant fondé sur le cadre JMAP (JSON Meta Application Protocol, RFC 8620).
Le protocole décrit dans ce RFC fournit les mêmes services qu'IMAP (RFC 9051) : accéder aux boîtes aux lettres de courrier, chercher dans ces boîtes, gérer les messages (détruire les inutiles, par exemple), etc. Par rapport à IMAP, outre l'utilisation du format JSON, l'accent est mis sur la synchronisation rapide, l'optimisation pour les clients mobiles, et sur la possibilité de notifications. JMAP est sans état (pas besoin de connexion permanente). Ce « JMAP pour le courrier » s'appuie sur JMAP, normalisé dans le RFC 8620. JMAP est un protocole générique, qui peut servir à synchroniser bien des choses entre un client et un serveur (par exemple un agenda, ou bien une liste de contacts). Par abus de langage, je vais souvent dire « JMAP » dans cet article alors que je devrais normalement préciser « JMAP pour le courrier », premier « utilisateur » du JMAP générique.
JMAP manipule différents types d'objets. Le plus important est
sans doute Email
(section 4 du RFC), qui
modélise un message. Il s'agit d'une représentation de haut
niveau, le client JMAP n'a pas à connaitre tous les détails de
l'IMF (Internet Message Format, RFC 5322), de
MIME (RFC 2045),
etc. Un objet de type Email
a une liste
d'en-têtes et un corps, et JMAP fournit des méthodes pour accéder
aux différentes parties du corps. Il y a même plusieurs
représentations d'un message, pour s'adapter aux différents
clients. Par exemple, un message MIME est normalement un
arbre, de profondeur quelconque, mais un
client JMAP peut décider de demander une représentation aplatie,
avec juste une liste d'attachements. (La plupart des MUA présentent à
l'utilisateur une vue aplatie de l'objet MIME.) Voilà pourquoi
l'objet Email
a plusieurs propriétés, le
client choisissant à laquelle il accède :
bodyStructure
: l'arbre MIME, c'est
la représentation la plus « authentique »,textBody
: une
liste des parties MIME à afficher quand
on préfère du texte,htmlBody
: une liste des parties MIME
à afficher quand on préfère de l'HTML,attachments
: la liste des « pièces
jointes » (rappelez-vous que le concept de « pièces jointes » a
été créé pour l'interface avec l'utilisateur ; il n'a pas de
sens en MIME, qui ne connait qu'un arbre avec des feuilles de
différents types).Les en-têtes doivent pouvoir être internationaux (RFC 6532).
Un message a évidemment des métadonnées, parmi lesquelles :
id
, un identifiant du message (ce
n'est pas le Message-ID:
, c'est attribué
par le serveur JMAP), contrairement à IMAP, l'identificateur
d'un message ne change pas, même quand le message change de
boîte, et il peut apparaitre dans plusieurs boîtes à la
foisblobIf
, un identifiant du message
représenté sous la forme d'une suite d'octets, à analyser par le client,
par opposition à l'objet de haut niveau identifié par
id
,size
, la taille du message,keywords
, des mots-clés, parmi
lesquels certains, commençant par un
dollar, ont une signification
spéciale.
En IMAP, les mots-clés spéciaux sont précédés d'une
barre inverse. En JMAP, c'est le
dollar. Parmi ces mots-clés,
$seen
indique que le message a été lu,
$answered
, qu'on y a répondu,
$junk
, que le serveur l'a classé comme
spam, etc. Ces mots-clés sont dans un
registre IANA.
Et quelles opérations sont possibles avec les objets de type
Email
? Ce sont les opérations génériques de
JMAP (RFC 8620, section 5). Ainsi,
on peut récupérer un message avec
Email/get
. Cette requête :
[[ "Email/get", { "ids": [ "f123u456", "f123u457" ], "properties": [ "threadId", "mailboxIds", "from", "subject", "receivedAt", "header:List-POST:asURLs", "htmlBody", "bodyValues" ], "bodyProperties": [ "partId", "blobId", "size", "type" ], "fetchHTMLBodyValues": true, "maxBodyValueBytes": 256 }, "#1" ]]
peut récupérer, par exemple, cette valeur :
[[ "Email/get", { "accountId": "abc", "state": "41234123231", "list": [ { "id": "f123u457", "threadId": "ef1314a", "mailboxIds": { "f123": true }, "from": [{ "name": "Joe Bloggs", "email": "joe@example.com" }], "subject": "Dinner on Thursday?", "receivedAt": "2013-10-13T14:12:00Z", "header:List-POST:asURLs": [ "mailto:partytime@lists.example.com" ], "htmlBody": [{ "partId": "1", "blobId": "B841623871", "size": 283331, "type": "text/html" }, { "partId": "2", "blobId": "B319437193", "size": 10343, "type": "text/plain" }], "bodyValues": { "1": { "isTruncated": true, "value": "<html><body><p>Hello ..." }, "2": { "isTruncated": false, "value": "-- Sent by your friendly mailing list ..." } } } ], "notFound": [ "f123u456" ] }, "#1" ]]
Notez que le client a demandé deux messages, mais qu'un seul, le
f123u457
, a été trouvé.
Tout aussi indispensable, Email/query
permet de demander au serveur une recherche, selon de nombreux
critères comme la date, les mots-clés, ou bien le contenu du corps
du message.
Email/set
permet de modifier un message,
ou d'en créer un (qu'on pourra ensuite envoyer avec
EmailSubmission
, décrit plus loin). Notez qu'il n'y a pas de
Email/delete
. Pour détruire un message, on
utilise Email/set
en changeant la propriété
indiquant la boîte aux lettres, pour mettre la boîte aux lettres
spéciale qui sert de poubelle (rôle = trash
).
Comme IMAP, JMAP pour le courrier a la notion de boîte aux lettres (section 2 du RFC). Une boîte (vous pouvez appeler ça un dossier ou un label si vous voulez) est un ensemble de messages. Tout message est dans au moins une boîte. Les attributs importants d'une boîte :
inbox
identifie
la boîte où le courrier arrive par défaut. (Les rôles figurent
dans un
registre IANA créé par le RFC 8457.)Ensuite, on utilise les méthodes JMAP pour accéder aux boîtes
(révisez donc le RFC 8620, qui décrit
le JMAP générique). Ainsi, pour accéder à une boîte,, on utilise
la méthode JMAP Mailbox/get
, qui utilise le
/get
JMAP (RFC 8620, section 5.1). Le paramètre
ids
peut être nul, cela indique alors qu'on
veut récupérer tous les messages (c'est ce qu'on fait dans
l'exemple ci-dessous).
De même, pour effectuer une recherche sur le serveur, JMAP
normalise la méthode /query
(RFC 8620, section 5.5) et JMAP pour le courrier peut
utiliser Mailbox/query
.
Par exemple, si on veut voir toutes les boîtes existantes, le client JMAP envoie le JSON :
[[ "Mailbox/get", { "accountId": "u33084183", "ids": null }, "0" ]]
et reçoit une réponse du genre (on n'affiche que les deux premières boîtes) :
[[ "Mailbox/get", { "accountId": "u33084183","state": "78540", "state": "78540", "list": [{ "id": "MB23cfa8094c0f41e6", "name": "Boîte par défaut", "role": "inbox", "totalEmails": 1607, "unreadEmails": 15, "myRights": { "mayAddItems": true, ...}, { "id": "MB674cc24095db49ce", "name": "Personnel", ...
Notez que state
est l'identificateur d'un
état de la boîte. Si on veut ensuite récupérer les changements, on
pourra utiliser Mailbox/changes
avec comme
paramètre "sinceState": "88540"
.
Dans JMAP, les messages peuvent être regroupés en fils de discussion (threads, section 3 du RFC). Tout message est membre d'un fil (parfois membre unique). Le RFC n'impose pas de méthode unique pour constituer les fils mais suggère :
In-Reply-To:
ou
References:
indiquant le
Message-Id:
d'un autre message).On peut ensuite accéder aux fils. Le client envoie :
[[ "Thread/get", { "accountId": "acme", "ids": ["f123u4", "f41u44"] }, "#1" ]]
Et récupère les fils f123u4
et
f41u44
:
[[ "Thread/get", { "accountId": "acme", "state": "f6a7e214", "list": [ { "id": "f123u4", "emailIds": [ "eaa623", "f782cbb"] }, { "id": "f41u44", "emailIds": [ "82cf7bb" ] } ...
Un client qui vient de se connecter à un serveur JMAP va
typiquement faire un Email/query
sans
conditions particulières, pour recevoir la liste des messages (ou
alors en se limitant aux N messages les plus récents),
puis récupérer les fils de discussion correspondants avec
Thread/get
, récupérer les messages
eux-mêmes. Pour diminuer la latence, JMAP permet au client
d'envoyer toutes ces requêtes en une seule fois (batching), en disant pour
chaque requête qu'elle doit utiliser le résultat de la précédente
(backreference, membre JSON resultOf
).
JMAP permet également d'envoyer des messages. Un client JMAP
n'a donc besoin que d'un seul protocole, contrairement au cas
courant aujourd'hui
où il faut IMAP et
SMTP,
configurés séparement, avec, trop souvent, l'un qui marche et l'autre pas. Cela simplifie
nettement les choses pour l'utilisateur. Cela se fait avec le type
EmailSubmission
(section 7 du RFC). Deux
importantes propriétés d'un objet de type
EmailSubmission
sont
mailFrom
, l'expéditeur, et
rcptTo
, les destinataires. Rappel important
sur le courrier électronique : il y a les adresses indiquées dans
le message (champs To:
,
Cc:
, etc, cf. RFC 5322), et les adresses indiquées dans l'enveloppe
(commandes SMTP comme MAIL
FROM
et RCPT TO
, cf. RFC 5321). Ces adresses ne sont pas forcément
identiques. Lorsqu'on apprend le fonctionnement du courrier
électronique, la distinction entre ces deux catégories d'adresses
est vraiment cruciale.
Un EmailSubmission/set
va créer l'objet
EmailSubmission
, et
envoyer le message. Ici, on envoie à
john@example.com
et
jane@example.com
un message (qui avait été créé
par Email/set
et qui avait l'identificateur
M7f6ed5bcfd7e2604d1753f6c
) :
[[ "EmailSubmission/set", { "accountId": "ue411d190", "create": { "k1490": { "identityId": "I64588216", "emailId": "M7f6ed5bcfd7e2604d1753f6c", "envelope": { "mailFrom": { "email": "john@example.com", "parameters": null }, "rcptTo": [{ "email": "jane@example.com", "parameters": null }, ... ] } } }, "onSuccessUpdateEmail": { "#k1490": { "mailboxIds/7cb4e8ee-df87-4757-b9c4-2ea1ca41b38e": null, "mailboxIds/73dbcb4b-bffc-48bd-8c2a-a2e91ca672f6": true, "keywords/$draft": null } } }, "0" ]]
Anecdote sur l'envoi de courrier : les premières versions de
« JMAP pour le courrier » utilisaient une boîte aux lettres
spéciale, nommée Outbox
, où on mettait les
messages à envoyer (comme dans ActivityPub).
JMAP a d'autres types d'objets amusants, comme
VacationResponse
(section 8), qui permet de
faire envoyer un message automatiquement lorsqu'on est absent
(l'auto-répondeur du serveur doit évidemment suivre le RFC 3834, pour éviter de faire des bêtises comme
de répondre à une liste de diffusion). On
crée un objet avec VacationResponse/set
et
hop, l'auto-répondeur est amorcé.
Et je n'ai pas parlé de tout, par exemple JMAP permet de pousser des changements depuis le serveur vers le client, si la boîte aux lettres est modifiée par un autre processus (RFC 8620, section 7).
JMAP a le concept de
capacités (capabilities),
que le serveur annonce au client, dans un objet JSON (rappel :
JSON nomme « objets » les
dictionnaires), et sous la forme d'un URI. JMAP pour le courrier
ajoute trois capacités au registre des
capacités JMAP,
urn:ietf:params:jmap:mail
pour dire qu'on
sait gérer le courrier,
urn:ietf:params:jmap:submission
, pour dire
qu'on sait en envoyer (cf. RFC 6409, sur ce
concept de soumission d'un message), et
urn:ietf:params:jmap:vacationresponse
pour
dire qu'on sait gérer un auto-répondeur.
Le courrier électronique pose plein de problèmes de sécurité
intéressants. La section 9 de notre RFC les détaille. Par exemple,
les messages en HTML sont particulièrement dangereux. (Il
est toujours amusant de voir des entreprises de sécurité
informatique envoyer leur newsletter en HTML,
malgré les risques associés, qui sont aujourd'hui bien connus.) Le
RFC rappelle donc aux clients JMAP (mais c'est valable pour tous
les MUA) que du
JavaScript dans le message peut changer son
contenu, qu'un message en HTML peut récupérer du contenu sur
l'Internet (via par exemple un <img
src=…
), ce qui trahit le
lecteur et fait fuiter des données privées, que
CSS,
quoique moins dangereux que JavaScript, permet également des trucs
assez limites, que les liens en HTML ne pointent pas toujours vers
ce qui semble (<a
href="http://evil.example/">cliquez ici pour aller sur le site
de votre banque https://good-bank.example</a>
),
etc. Pour faire face à tous ces dangers du courrier en HTML, le
RFC suggère de nettoyer le HTML avant de l'envoyer au
client. Attention, outre que c'est une modification du contenu, ce
qui est toujours délicat politiquement, le faire proprement est
difficile, et le RFC recommande fortement d'utiliser une
bibliothèque bien testée, de ne pas le faire soi-même à la main
(il y a trop de pièges). Par exemple, en
Python, on peut utiliser lxml, et son module
Cleaner
, ici en mode extrémiste qui retire
tout ce qui peut être dangereux :
from lxml.html.clean import Cleaner ... cleaner = Cleaner(scripts=True, javascript=True, embedded=True, meta=True, page_structure=True, links=True, remove_unknown_tags=True, style=True)
Mais il est probablement impossible de complètement sécuriser HTML dans le courrier. Le RFC explique à juste titre que HTML augmente beaucoup la surface d'attaque. Une organisation soucieuse de sécurité ne devrait pas traiter le HTML dans le courrier.
La soumission du courrier (cf. RFC 6409) pose également des problèmes de sécurité. Imaginez un client JMAP piraté et qui serve ensuite à envoyer du spam de manière massive, utilisant le compte de l'utilisateur ignorant de ce piratage. Les MTA qui acceptent du courrier ont des mécanismes de défense (maximum N messages par heure, avec au plus M destinataires par message…) mais ces mécanismes marchent d'autant mieux que le serveur a davantage d'information. Si la soumission via JMAP est mise en œuvre par un simple relais vers un serveur SMTP de soumission, certaines informations sur le client peuvent être perdues. De tels relais doivent donc veiller à transmettre au serveur SMTP toute l'information disponible, par exemple via le mécanisme XCLIENT.
JMAP a été développé essentiellement au sein de FastMail, qui le met en œuvre sur ses serveurs. Il existe une page « officielle » présentant le protocole, qui explique entre autres les avantages de JMAP par rapport à IMAP. Vous y trouverez également des conseils pour les auteurs de clients, très bien faits et qui donnent une bonne idée de comment le protocole marche. Ce site Web est un passage recommandé.
On y trouve également une liste de mises en œuvre de JMAP. Ainsi, le serveur IMAP bien connu Cyrus a déjà JMAP en expérimental. Le MUA K-9 Mail a, quant à lui, commencé le travail.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)