Intestazioni
dinamiche
Quello che segue è un estratto delle intestazioni
di un messaggio di prova inviato e ricevuto tramite
un provider Internet non particolare. All'interno di
questo si possono riconoscere molte chiavi: From,
Return-Path, Delivered-To, Received,
Message-ID, ecc. Ciascuna di queste ha un preciso
significato all'interno del mondo della posta elettronica
che, per una corretta interoperabilità tra diversi
server e client, deve essere uniforme.
From
spammer_99@null.it Sat Dec 20 16:57:57 2003
Return-Path: <spammer_99@null.it>
Delivered-To: max@0
Received: (qmail 23653 invoked by uid 511); 20 Dec 2003
12:05:44 -0000
Received: from spammer_99@null.it by mxavas1.aruba.it
by uid 503 with qmail-scanner-1.20rc4
(fsecure: 4.50/2111/fprot(2003-10-15)/avp(2003-10-16).
spamassassin: 2.60. Clear:RC:0:SA:0(1.2/5.0):.
Processed in 0.133694 secs); 20 Dec 2003 12:05:44 -0000
Received: from unknown (HELO vsmtp2.tin.it) (212.216.176.222)
by mxavas1.aruba.it with SMTP; 20 Dec 2003 12:05:43
-0000
Received: from Computer-di-Massimiliano-Bigatti.local
(82.48.89.160) by vsmtp2.tin.it (7.0.019)
id 3FE030400010B68D for max@bigatti.it; Sat, 20 Dec
2003 13:05:44 +0100
Message-ID: <15820815.1071922119368.JavaMail.max@Computer-di-Massimiliano-Bigatti.local>
Date: Sat, 20 Dec 2003 13:08:38 +0100 (CET)
From: spammer_99@null.it
To: max@bigatti.it
Subject: Messaggio di prova
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
La particolarità delle intestazioni di posta
è che queste sono espandibili con chiavi aggiuntive.
Ad esempio, il sistema contro la posta spazzatura Spam
Assassin aggiunge una serie di intestazioni al messaggio
al fine di indicare il server che ha eseguito la verifica,
la sua versione, le anomalie riscontrate sul messaggio
e così via. Spam Assassin è un sistema
antispam che analizza un messaggio al fine di trovare
elementi indicativi del fatto che è indesiderato,
come ad esempio la presenza di testo sospetto o la mancanza
di dati essenziali, come il nome del mittente. Un esempio
di queste intestazioni è il seguente:
X-Spam-Rating:
mxavas1.aruba.it 1.6.2 0/1000/N
X-Spam-Checker-Version: SpamAssassin 2.60 (1.212-2003-09-23-exp)
on
mxavas1.aruba.it
X-Spam-Level: *
X-Spam-Status: No, hits=1.2 required=5.0 tests=FROM_ENDS_IN_NUMS,NO_REAL_NAME
autolearn=no version=2.60
I metodi principali per operare con le intestazioni
sono definiti all'interno dell'interfaccia javax.mail.Part
e sono:
- addHeader(String,String).
Si aspetta il nome dell'header ed il valore ed aggiunge
quest'ultimo ai valori esistenti per il nome indicato;
- Enumeration
getAllHeaders(). Ritorna una enumerazione che
elenca tutti i valori delle intestazioni presenti
nella parte;
- String[]
getHeader(String). Ritorna un array contentente
i valori dell'intestazione passata come parametro;
- Enumeration
getMatchingHeaders(String[]). Ritorna una enumerazione
che elenca tutte i valori delle intestazioni relative
ai nomi passati come parametro, sotto forma di array
di stringhe;
- Enumeration
getNonMatchingHeaders(String[]). Come la precedente,
ma ritorna tutte le intestazioni le cui chiavi non
sono presenti nell'array passato come parametro;
- removeHeader(String).
Rimuove tutte le intestazioni associate al nome indicato.
- setHeader(String,String).
Imposta il valore di una intestazione, cancellando
il valore di tutte le intestazioni con il nome indicato
se presenti.
Tutti questi metodi sollevano una MessagingException
nel caso di generici problemi di accesso al messaggio;
i metodi dispositivi, come setHeader(), sollevano
anche una IllegalWriteException nel caso la parte
sia stata ottenuta da un Folder aperto in sola
lettura.
La sottointerfaccia MimePart, che definisce una
parte di un messaggio multiparte secondo lo standard RFC822
e quello MIME RFC2045 (la posta Internet), definisce anche
i seguenti metodi:
- addHeaderLine(String).
Aggiunge una linea di intestazione secondo lo standard
RFC822;
- Enumeration
getAllHeaderLines(). Ritorna una enumerazione
di tutte le linee di intestazione in formato grezzo
RFC822, composte cioè dalla forma "chiave:
valore";
- String
getHeader(String,String). Ritorna tutti i valori
dell'intestazione indicata, separati dal delimitatore
passato come secondo parametro;
- Enumeration
getMatchingHeaders(String[]). Ritorna una enumerazione
che elenca tutte i valori delle intestazioni relative
ai nomi passati come parametro, sotto forma di array
di stringhe grezze RFC822;
- Enumeration
getNonMatchingHeaders(String[]). Come la precedente,
ma ritorna tutte le intestazioni le cui chiavi non
sono presenti nell'array passato come parametro grezze
RFC822;
Sostanzialmente, accedendo alle intestazioni tramite i
metodi dell'interfaccia Part, si ottiene un accesso
astratto dal particolare protocollo, dove il concetto
di intestazione è limitato ad una chiave a cui
sono associati zero o più valori; nell'interfaccia
MimePart, questo concetto viene contestualizzato
al protocollo RFC822 che definisce gli standard di comunicazione
dei messaggi di posta testuali su Internet.
E' una questione di priorità
Un esempio di header spesso utilizzata è quella
relativa alla priorità: X-Priority. Quando
impostata ad 1 rappresenta un'alta priorità, 2
è media e 3 è bassa. Per default, quando
l'intestazione non è presente, il messaggio viene
considerato solitamente di media prorità. I client
Microsoft come Outlook aggiungono anche le intestazioni
Priority ed Importance, che riportano informazioni
descrittive.
Messaggi e Flag
Ciascun messaggio può disporre di un insieme di
flag che indicano lo stato del messaggio. I flag di sistema
sono rappresentati dalla classe interna Flags.Flag,
mentre quelli definiti dall'utente sono implementati come
semplici stringhe.
La classe Flag definisce le seguenti costanti:
- ANSWERED.
Il messaggio ha avuto una risposta;
- DELETED.
Il messaggio è stato cancellato;
- DRAFT.
Il messaggio è una bozza;
- FLAGGED.
Il messaggio è stato marcato;
- RECENT.
Il messaggio è recente;
- SEEN.
Il messaggio è stato letto;
- USER.
Flag speciale che indica che il Folder supporta
flag personalizzati.
Per operare con i flag di un messaggio (si noti che un
singolo oggetto Flags contiene tutti i flag impostati
di uno specifico messaggio) sono disponibili i seguenti
metodi della classe Message:
- Flags
getFlags(): ritorna i flag di questo messaggio;
- boolean
isSet(Flags.Flag): indica se un particolare flag
è impostato;
- setFlag(Flags.Flag,boolean):
imposta il valore di uno specifico flag di sistema;
- setFlags(Flags,boolean):
imposta i flag indicati al valore passato come parametro.
Questi metodi sollevano una generica MessagingException
in caso di problemi ad ottenere le informazioni richieste,
oppure un IllegalWriteException se il messaggio
proviene da un Folder aperto in sola lettura.
Per cancellare un messaggio si può dunque procedere
come segue:
message.setFlag(Flags.Flag.DELETED, true);
si noti che questa operazione di cancellazione non produce
alcun effetto immediato. Per rimuovere definitivamente
il messaggio dal Folder che lo contiene, è
necessario chiamare il metodo expunge():
Message[] deleted = folder.expunge();
Il metodo ritorna l'array dei messaggi eliminati, con
il loro numero univoco progressivo (ottenuto con getMessageNumber()),
mentre i messaggi rimasti nella cartella sono soggetti
a rinumerazione. Si noti che sul messaggio cancellato
definitivamente sono attivi i soli metodi isExpunged()
- che ritorna true - e getMessageNumber():
gli altri dovrebbero sollevare una MessageRemovedException.
In alternativa ad expunge() è possibile
scrivere:
folder.close(true);
in questo caso, alla chiusura della cartella segue un'operazione
di cancellazione definitiva. Se non si desidera rimuovere
permanentemente i messaggi marcati per la cancellazione,
si può richiamare lo stesso metodo con parametro
false:
folder.close(false);
Ricerca di messaggi
Quando una cassetta postale o una cartella cominciano
a contenere un numero considerevole di messaggi, diventa
necessaria una funzione di ricerca che consenta di individuare
con facilità gli elementi desiderati.
Le API Javamail supportano la ricerca dei messaggi tramite
due metodi della classe Folder:
- Message[]
search(SearchTerm). Ricerca i messaggi all'interno
di tutta la cartella;
- Message[]
search(SearchTerm, Message[]). Ricerca i messaggi
all'interno dell'elenco dei messaggi indicato; i messaggi
devono appartenere alla cartella su cui viene chiamato
il metodo.
Entrambi i metodi sollevano una MessagingException
in caso di problemi o una IllegalStateException
se la cartella non è aperta. I due metodi si
aspettano un oggetto SearchTerm, che definisce
le chiavi di ricerca da applicare; se questi sono troppo
complessi, viene sollevata una SearchException.
La classe SearchTerm funge da superclasse astratta
comune alle diverse tipologie di ricerca possibili (circa
una ventina), che sono divise per tipo di condizione
logica e per parte del messaggio da confrontare. I diversi
termini di ricerca possono essere infatti uniti con
operatori AND, OR e NOT (classi AndTerm, OrTerm
e NotTerm), mentre le parti del messaggio da
ricercare possono essere la data di invio, il contenuto
e le intestazioni (classi SentDateTerm, BodyTerm,
FromTerm, RecipientTerm, SubjectTerm
ed altre). L'elenco completo dei termini di ricerca
è presente in tabella 1 e riassunto in figura
1.
Tabella 1 - Classi Javamail per la ricerca dei
messaggi
AddressStringTerm: classe astratta che implementa un
confronto degli indirizzi di un messaggio a livello
di stringa
AddressTerm: implementa il confronto tra indirizzi
AndTerm: realizza un AND logico su termini di ricerca
singoli
BodyTerm: implementa una ricerca sul corpo del messaggio
ComparisonTerm: rappresenta un operatore di confronto
DateTerm: implementa confronti per date
FlagTerm: implementa un confronto di flag
FromStringTerm: implementa un confronto a livello di
stringa del mittente del messaggio
FromTerm: realizza un confronto del mittente del messaggio
HeaderTerm: implementa confronti per le intestazioni
dei messaggi
IntegerComparisonTerm: implementa confronti tra numeri
interi
MessageIDTerm: definisce un identificativo univoco secondo
specifiche RFC822
MessageNumberTerm: implementa un confronto per numero
di messaggio
NotTerm: rappresenta un NOT booleano
OrTerm: implementa un OR logico su due termini singoli
ReceivedDateTerm: confronto tra date di ricezione del
messaggio
RecipientStringTerm: implementa un confronto a livello
di stringa del destinatario
RecipientTerm: implementa un confronto del destinatario
SearchTerm: superclasse generica di ricerca. Il criterio
di ricerca è espresso come albero di termini
di ricerca
SentDateTerm: implementa un confronto per le date di
invio.
SizeTerm: realizza un confronto sulla dimensione del
messaggio.
StringTerm: classe generica per il confronto di stringhe.
SubjectTerm: implementa un confronto con l'oggetto del
messaggio.
Figura 1 - Gerarchia delle classi di ricerca. In
giallo sono evidenziate le classi che implementano operatori
booleani mentre in azzurro sono evidenziati le classi
finali che possono essere utilizzate per le ricerche.
Quelle in bianco sono invece classi astratte infrastrutturali
di supporto.
Esempi di ricerca
Ad esempio, per individuare i messaggi di dimensione
superiore ai 200K, eventualmente per richiedere all'utente
una conferma al loro scaricamento, si può scrivere:
SearchTerm
grandiMessaggi =
new SizeTerm( SizeTerm.GE, 200 * 1024 );
Message[] elenco = folder.search( grandiMessaggi );
Il costruttore di SizeTerm si aspetta come primo
parametro il termine di confronto (definite nella classe
ComparisionTerm), che può assumere i valori:
- EQ
- (equal) equivale a;
- GE
- (greater/equals than) maggiore o uguale a;
- GT
- (greater than) maggiore di;
- LE
- (lesser/equals than) minore o uguale a;
- LT
- (lesser than) minore di;
- NE
- (not equal) non equivalente a;
E' anche possibile individuare i messaggi in funzione
dello stato dei loro flag. Ad esempio, per individuare
i messaggi che devono essere ancora letti si può
scrivere:
SearchTerm nonLetti =
new FlagTerm( Flags.Flag.SEEN, false );
Message[] elenco = folder.search( nonLetti );
Tramite gli operatori booleani è anche possibile
concatenare più termini per eseguire ricerche
complesse. Ad esempio, per cercare tutte le newsletter
inviate da Mokabyte, con oggetto "Mokabyte Newsletter"
e con mittente la redazione si potrebbe scrivere:
SearchTerm
st = new AndTerm(new SubjectTerm("Mokabyte Newsletter"),
new
FromStringTerm("redazione@mokabyte.com"));
Message[] msgs = folder.search(st);
Un esempio completo
Il programma presentato nel listato 1 implementa una
semplice ricerca dei messaggi ricevuti nella giornata
odierna. La ricerca viene eseguita tramite la classe
ReceivedDateTerm; l'elenco dei messaggi ottenuti
viene stampato a video, presentando l'oggetto e la data
di ricezione.
Listato
1 - Ricerca.java
package com.mokabyte.mokabook2.javamail;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.search.*;
public class Ricerca {
public static void main(String args[]) {
try {
Properties
props = System.getProperties();
//props.put("mail.debug","true");
Session
session = Session.getDefaultInstance(props, null);
Store
store = session.getStore("pop3");
store.connect(args[0],
args[1], args[2]);
Folder
folder = store.getDefaultFolder();
if
(folder != null) {
folder
= folder.getFolder("INBOX");
if
(folder != null) {
folder.open(Folder.READ_ONLY);
System.out.println("Ricerca
messaggi di oggi (totale="
+
folder.getMessageCount() + ")" );
Calendar
oggi = Calendar.getInstance();
oggi.add(
Calendar.DATE, -1 );
SearchTerm
st = new SentDateTerm(SentDateTerm.GE,oggi.getTime());
//SearchTerm
st = new FromStringTerm( "giuliani" );
Message[]
elencoMessaggi = folder.search( st );
if(
elencoMessaggi != null && elencoMessaggi.length
!= 0 ) {
for
(int indice = 0; indice < elencoMessaggi.length;
indice++) {
Message
messaggio = elencoMessaggi[ indice ];
System.out.println(
" OGGETTO: " + messaggio.getSubject() +
"
DATA: " + messaggio.getSentDate());
}
}
else {
System.out.println(
"Nessun messaggio trovato" );
}
folder.close(false);
}
else {
System.out.println(
"Folder non trovato" );
}
}
else {
System.out.println(
"Folder di default non trovato" );
}
store.close();
}
catch
(Exception ex) {
ex.printStackTrace();
}
}
}
Conclusioni
Con questo articolo si conclude questa breve serie di
articoli su Javamail che, come abbiamo visto, è
molto completa e potente; il framework generale supporta
un generico sistema di posta, che può essere
esteso ed adattato anche a protocolli diversi da quelli
utilizzati su Internet. In aggiunta sono presenti tutti
gli elementi necessari a supportare i protocolli della
rete, tra cui POP3, IMAP ed SMTP. Sono interessanti
anche i prodotti di terze parti che stanno nascendo
su queste API, che SUN elenca alla pagina http://java.sun.com/products/javamail/Third_Party.html.
Bibliografia
RFC822 - http://www.w3.org/Protocols/rfc822/Overview.html
|