Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Grzegorz 'baziorek' Bazior
Inne artykuły

[C++, POCO] Pobieranie poczty przy użyciu protokołu POP3 za pomocą biblioteki POCO

[artykuł] Artykuł opisuje w jaki sposób można odczytywać pocztę za pośrednictwem protokołu POP3 z poziomu języka C++.

O czym będzie ten artykuł?

Mimo istnienia programów pocztowych bywa, że programistycznie chcemy sprawdzić pocztę. Da się to również w C++, m.in. przy użyciu biblioteki POCO. W tym artykule opiszę jak to zrobić zaczynając od instalacji biblioteki, przez wyjaśnienie funkcji, do przykładów użycia.

Artykuł będzie się składał zasadniczo z następujących części:
  • instalacja biblioteki POCO na Linuxie i Windowsie
  • pobieranie poczty przy użyciu protokołu POP3 (protokół IMAP nie jest niestety dostarczony przez tę bibliotekę)

Instalacja

Zacznijmy od ściągnięcia paczki, najnowszą wersję można pobrać ze strony
Projektu Poco (proszę zwrócić uwagę na wersję systemu oraz wariant, do pobierania maili wystarczy edycja Basic).
Można też ściągnąć te biblioteki przy pomocy managera pakietów C/C++ Conan, szczegóły na ww. stronie.

Instalacja Windows

Jeśli chodzi o ten system to niestety szybko skonfigurować bibliotekę jesteśmy w stanie jedynie na Visual Studio. Natomiast jeśli chcielibyśmy skonfigurować pod innym kompilatorem, to mimo teoretycznie dostępnej konfiguracji Cmake'a dla MinGW nie jest łatwo to zrobić, te wszystkie tutoriale w internecie są bardzo stare, nawet mamy wyjaśnienie od jednego z członków Projektu Poco dlaczego ta konfiguracja może nie działać https://stackoverflow.com/a​/14680539.

Zacznijmy więc od pobrania Visual Studio (który to można za darmo pobrać i po zarejestrowaniu używać, chociaż zapewne tylko na komputerze domowym).
Możemy zbudować domyślnie dynamicznie lub w bardziej wyrafinowany sposób np. statycznie.

Domyślne budowanie dynamiczne

Po rozpakowaniu paczki należy wejść do rozpakowanego katalogu i edytować plik components żeby zawierał tylko te komponenty, które chcemy zbudować, w naszym przypadku musimy zostawić: Net i Foundation.

Teraz trzeba by się dowiedzieć jaką mamy wersje VS, jeśli ktoś wie to odpala skrypt (wystarczy przez dwuklik, chociaż wtedy nie zauważymy, gdy pojawi się błąd, dlatego lepiej przez konsolę to odpalić): build_vsXXX.cmd, gdzie XXX to wersja VS. Jeśli ktoś nie wie jaką ma wersję powinien to znaleźć wchodząc do C:\Program Files (x86) i tam powinien mieć katalog Microsoft Visual Studio XX.0, w razie posiadania wielu katalogów należy wejść i sprawdzić, który z nich zawiera taką treść, która potwierdza, że jest w pełni zainstalowany (o ile oczywiście w ogóle mamy zainstalowane VS).

Niedomyślne budowanie

Jeśli natomiast chcemy zmodyfikować ustawienia budowania np. zbudować statycznie wtedy już musimy wejść do rozpakowanego katalogu następnie do odkatalogu Net, potem otworzyć plik solucji VS dla odpowiedniej wersji: Net_vsXXX.sln, po otwarciu klikamy prawym przyciskiem na projekcie Net, następnie Properties->Configuration i tam wybieramy wersję static, kwestię MD i MT sugeruję rozstrzygnąć samodzielnie w oparciu np. o https://stackoverflow.com​/questions/757418​/should-i-compile-with-md-or-mt.

Zbudowane biblioteki

Zbudowane biblioteki statyczne będziemy mieli w katalogu (rozpakowane POCO)/lib, natomiast dynamiczne w katalogu (rozpakowane POCO)/bin

Instalacja Linux

Tutaj sprawa wygląda o wiele lepiej, gdyż możemy zainstalować sobie biblioteki z repozytoriów np.:

sudo apt-get install libpoco-dev # cała biblioteka
sudo apt-get install libpoconet9v5 #  tylko do obsługi maili

Ale jeśli chcemy mieć najnowszą, oficjalną wersję to najlepiej samodzielnie skompilować pobraną paczkę, w tym celu należy rozpakować:

tar -xvzf poco-X.X.X.tar.gz # należy ustawić na nazwę aktualnie pobranej paczki
następnie (budowa może chwilę zająć):

cd poco-X.X.X
./configure
make
sudo make install

jeśli chcemy się ograniczyć do tylko potrzebnych komponentów (czyli tylko do obsługi poczty), zbudować wyłącznie statycznie oraz w konkretnym miejscu to możemy skonfigurować w następujący sposób:

./configure --static --no-tests --no-sharedlibs --no-samples --prefix=/sciezka/gdzie/skompilowac/biblioteke/ --omit=Crypto,Zip,Data,Data/SQLite,Data/ODBC,Data/MySQL,MongoDB,PDF,CppParser,PageCompiler,Util
Chcąc budować na Linuxie będziemy musieli dodać odpowiednie flagi kompilacji: -lPocoNet -lPocoFoundation, może się okazać, że również i -lpthread, jeśli zbudowaliśmy we własnej lokalizacji, to musimy podać przed powyższymi ścieżki do nagłówek i bibliotek:
-isystem/sciezka/gdzie/skompilowac/biblioteke/include -L/sciezka/gdzie/skompilowac/biblioteke/lib

Pobieranie poczty

Prawie wszyscy dostawcy poczty internetowej dostarczają serwery POP3 do pobierania poczty, mimo iż ten protokół jest dość stary i ubogi w funkcjonalność. Ja akurat testowałem ten artykuł dla Poczty O2, dla której adres serwera poczty przychodzącej POP3 to po prostu: "poczta.o2.pl", dla Poczty Wp jest adres: "pop3.wp.pl", dla innych poczt powinno być łatwo znaleźć te serwery w internecie, chociaż czasami może być konieczne przez Web włączenie dostępu przez pop3.

Obiekt Poco::Net::POP3ClientSession

Służy on do obsługi poczty przez protokół POP3. Poniższe obiekty są w przestrzeni nazw
Poco::Net
.
Poniższe instrukcje są pobrane ze strony dokumentacji projektu
C/C++
using namespace Poco::Net;

POP3ClientSession::POP3ClientSession(
const std::string & host,
Poco::UInt16 port = POP3_PORT
); // tworzy obiekt POP3ClientSession

void POP3ClientSession::login(
const std::string & username,
const std::string & password
); // loguje do poczty przy pomocy nazwy użytkownika i hasła, w razie niepowodzenia zostanie wyrzucony wyjątek:  POP3Exception lub NetException

void POP3ClientSession::listMessages(
POP3ClientSession::MessageInfoVec & messages
); // służy do listowania wiadomości na skrzynce, ten typ to vector zawierający tylko id oraz rozmiar każdej wiadomości. Może rzucać wyjątki jw.

Pobieranie nagłówka wiadomości

Mając pobraną listę wiadomości w formie id - rozmiar, możemy pobrać już całą wiadomość, która może być dość duża, dlatego dla większych wiadomości czemu by wpierw nie pobrać nagłówka wiadomości:
C/C++
void POP3ClientSession::retrieveHeader(
int id,
MessageHeader & header
);
aby się dowiedzieć co on zawiera możemy dokonać iteracji po jego składowych:
C/C++
for( const auto & a: header )
     cout << a.first << ": " << a.second << endl;
// lub prościej:
virtual void MessageHeader::write(
std::ostream & ostr
) const;
pojawi się nam output na kształt (oczywiście pewne linie pominąłem):

Date = Tue, 27 Jun 2017 10:33:28 +0200
From = "Spamerzy" <no-response@spamerzy.pl>
Reply-To = no-reply@spamerzy.pl
To = twojAdresMailowy@o2.pl
Message-ID = <2130857396.201055761498552408317.JavaMail.root@ee121459e014>
Subject = Letnia promocja!
MIME-Version = 1.0
Content-Type = multipart/related; boundary="----=_Part_20127866_1548667696.1498552408316"

Na podstawie nagłówka możemy ocenić czy dana wiadomość będzie dla nas przydatna i warto się nią dalej zajmować. Na nasze szczęście nie musimy parsować outputu samodzielnie i możemy użyć np.:
C/C++
string mailSubject = header.get( "subject" );
string mailSender = header.get( "from" );
string mailReceivingDate = header.get( "date" );
Wielkość liter nie ma znaczenia.

Pobieranie całej wiadomości

Jeśli z nagłówka wiemy, że chcemy pobrać całą wiadomość, lub jeśli rozmiar wiadomości był na tyle niski, że zdecydowaliśmy się pobrać wiadomość od razu to możemy to zrobić w następujący sposób:
C/C++
void POP3ClientSession::retrieveMessage(
int id,
MailMessage & message
);

Wyjmowanie pewnych informacji z wiadomości

mając już pobraną wiadomość możemy wyciągnąć z niej pewne informacje przy użyciu wbudowanych funkcji:
C/C++
...message.getDate().epochTime(); // epochTime() zwraca time_t dla wiadomości, czyli ilość sekund od 1.01.1970
...message.getSender();
...message.getSubject();
...message.getContent(); // zwraca treść wiadomości, niestety tylko jeśli wiadomość nie jest "multipart-part"
...message.getContentType(); // przy pomocy tej funkcji można sprawdzić typ wiadomości, czy wiadomość jest single-part
...message.isMultipart(); // jeśli zostanie zwrócone true to nie uda się nam tej wiadomości pobrać przez getContent()
...message.recipients(); // vector odbiorców, ale o tym później

Dodatkowe operacje na sesji Pop3

C/C++
void POP3ClientSession::deleteMessage(
int id
);

int POP3ClientSession::messageCount();

void POP3ClientSession::close();

bool POP3ClientSession::sendCommand(
const std::string & command,
std::string & response
);

bool POP3ClientSession::sendCommand(
const std::string & command,
const std::string & arg1,
const std::string & arg2,
std::string & response
);
Te dwie ostatnie umożliwiają wysłanie do serwera dodatkowej komendy i czekają na odpowiedź serwera.
Jak widać POP jest dość prostym protokołem, z małą ilością komend, za to jest łatwiejszy do zaimplementowania.
Standardy dla protokołu w różnych wersjach: https://en.wikipedia.org/wiki​/Post_Office_Protocol#Related_requests_for_comments_.28RFCs.29 w nich jest zawarta informacja jakie komendy są obsługiwane. Niestety ten protokół nie obsługuje folderów, dlatego możemy operować tylko na wiadomościach ze skrzynki odbiorczej. Podobnie ten protokół nie daje możliwości wykrycia, która z wiadomości jest już przeczytana.
Ciekawostką jest, że można przy użyciu narzędzia Telnet połączyć się z własną skrzynką i wykonać wszystkie operacje, które wymieniłem, chociaż odradzam, ze względu na brak szyfrowania.

Przykłady

Opisałem funkcje, czas teraz na praktykę.

Pobranie i wyświetlenie ostatniej wiadomości

C/C++
#include <iostream>
#include <ctime> // ctime()

#include <Poco/Net/POP3ClientSession.h>
#include <Poco/Net/MailMessage.h>

using namespace std;
using namespace Poco::Net;

void downloadLastMail( MailMessage & outputMessage, const char * serverAddress, const char * userName, const char * password )
{
    POP3ClientSession session( serverAddress );
    session.login( userName, password );
   
    Poco::Net::POP3ClientSession::MessageInfoVec messages;
    session.listMessages( messages );
   
    const Poco::Net::POP3ClientSession::MessageInfo & currentMessage = messages.back();
    session.retrieveMessage( currentMessage.id, outputMessage );
}

void printMailInfo( const Poco::Net::MailMessage & message )
{
    time_t receivedTime = message.getDate().epochTime();
    cout << "date:    " << ctime( & receivedTime ) << endl;
   
    cout << "subject: " << message.getSubject() << endl;
    cout << "sender:  " << message.getSender() << endl;
    cout << "content: " << message.getContent() << endl;
}


int main()
{
    Poco::Net::MailMessage message;
    downloadLastMail( message, "poczta.o2.pl", "mail@o2.pl", "haslo" );
    printMailInfo( message );
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/include 1pobranieOstatniej.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/lib -lPocoNet -lPocoFoundation -lpthread

W ramach wyjaśnienia mogę dodać, że MailMessage jest niekopiowalny. Ponadto niestety funkcja MailMessage::getContent() działa tylko dla wiadomości jednoczęściowych, dla wieloczęściowych (multi-part) zostanie zwrócony pusty string.

przykładowy output:

date:    Wed Jun 28 00:36:06 2017

subject: A teraz polskie znaki
sender:  "Ja" <ja@o2.pl>
content: ąść

Pobieranie paru ostatnich wiadomości z poczty

C/C++
#include <iostream>
#include <ctime>
#include <vector>

#include <Poco/Net/POP3ClientSession.h>
#include <Poco/Net/MailMessage.h>

using namespace std;
using namespace Poco::Net;

struct Mail
{
    time_t receivedTime;
    std::string sender, subject, content;
    int sizeInBytes;
    int mailId;
};

ostream & operator <<( ostream & o, const Mail & m )
{
    o << "mail ID: " << m.mailId << '\n';
    o << "date:    " << ctime( & m.receivedTime );
    o << "subject: " << m.subject << '\n';
    o << "sender:  " << m.sender << '\n';
    o << "size[b]: " << m.sizeInBytes << '\n';
    if( !m.content.empty() )
         o << "content: " << m.content << '\n'; // to zadziala tylko dla jednoczesciowej tresci (bez zalacznikow)
   
    return o;
}

Mail getMailInfo( POP3ClientSession & session, int messageId )
{
    Poco::Net::MailMessage message;
    session.retrieveMessage( messageId, message );
   
    Mail mail;
    mail.receivedTime = message.getDate().epochTime();
    mail.subject = message.getSubject();
    mail.sender = message.getSender();
    mail.content = message.getContent();
    return mail;
}

std::vector < Mail > downloadLastMails( const char * serverAddress, const char * userName, const char * password, unsigned numberOfMails = 3 )
{
    POP3ClientSession session( serverAddress );
    session.login( userName, password );
   
    Poco::Net::POP3ClientSession::MessageInfoVec messages;
    session.listMessages( messages );
   
    std::vector < Mail > downloadedMails;
    unsigned counter = 0;
    for( auto m = messages.rbegin(); m != messages.rend(); ++m )
    {
        if( ++counter > numberOfMails )
             break;
       
        Mail && currentMail = getMailInfo( session, m->id );
        currentMail.sizeInBytes = m->size;
        currentMail.mailId = m->id;
        downloadedMails.emplace_back( currentMail );
    }
   
    return downloadedMails;
}


int main()
{
    const std::vector < Mail >& mails = downloadLastMails( "poczta.o2.pl", "mail@o2.pl", "haslo" );
   
    for( const auto & m: mails )
         cout << m << endl;
   
}

// g++ -isystem/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/include 2listowanie.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/lib -lPocoNet -lPocoFoundation -lpthread
output:

mail ID: 619
date:    Wed Jun 28 00:36:06 2017
subject: A teraz polskie znaki
sender:  "Ja" <ja@o2.pl>
size[b]: 884
content: ąść

mail ID: 618
date:    Tue Jun 27 22:05:48 2017
subject: Testuje tresc
sender: "Ja" <ja@o2.pl>
size[b]: 1778
content: Oto jest tresc tej wiadomosci.

Mam nadzieje, ze sie spodoba.

pozdr

mail ID: 617
date:    Tue Jun 27 19:33:59 2017
subject: Plik z polskimi znakami
sender:  "Ja" <ja@o2.pl>
size[b]: 3296
content: plik w załączniku
Teraz już każdy może sobie odpowiednio wyświetlić te informacje, które go interesują, np. filtracja po dacie, po nadawcy itp. Przy większej ilości pobieranych dużych wiadomości proponuję pobierać wpierw nagłówki.

Pobieranie wiadomości z załącznikami

Czyli tutaj mamy przykład wiadomości wieloczęściowej (czyli takiej, której treści nie da się pobrać przez getContent()).
C/C++
string getContentOfMultipartMessage( const Poco::Net::MailMessage & message )
{
    string messageContent;
   
    const Poco::Net::MailMessage::PartVec & parts = message.parts();
    for( const Poco::Net::MailMessage::Part & p: parts )
    {
        if( Poco::Net::MailMessage::CONTENT_INLINE == p.disposition )
        {
            string buffer;
            istream & is = p.pSource->stream();
            while( getline( is, buffer ) )
                 messageContent += buffer;
           
        }
    }
    return messageContent;
}

string getContentOfMessage( const Poco::Net::MailMessage & message )
{
    if( message.isMultipart() )
         return getContentOfMultipartMessage( message );
    else
         return message.getContent();
   
}
Jak widać z kodu wpierw pobieramy wszystkie części z wiadomości, następnie sprawdzamy typ każdej z części i jeśli dana część jest Inline w wiadomości to zostaje zwracana przez powyższą funkcję.

Pobieranie załączników z wiadomości

C/C++
string getTextWithoutQuotes( string originalText )
{
    if( '"' == originalText.front() )
         originalText = originalText.substr( 1 );
   
    if( '"' == originalText.back() )
         originalText.pop_back();
   
    return originalText;
}

void saveAttachmentsToPath( const string & path, const Poco::Net::MailMessage & message )
{
    const Poco::Net::MailMessage::PartVec & parts = message.parts();
    for( const Poco::Net::MailMessage::Part & p: parts )
    {
        if( Poco::Net::MailMessage::CONTENT_ATTACHMENT == p.disposition )
        {
            const string & outputFileName = path + '/' + getTextWithoutQuotes( p.pSource->filename() );
            ofstream attachmentFile( outputFileName, ios::out | ios::binary | ios::trunc );
           
            string buffer;
            istream & is = p.pSource->stream();
            while( getline( is, buffer ) )
                 attachmentFile << buffer << '\n';
           
        }
    }
}
W ramach wyjaśnienia -może się zdarzyć, że zwracane nazwy są w cudzysłowie, dlatego chcę go obciąć.

Pobieranie listy odbiorców

Tutaj wg dokumentacji powinno działać coś następującego:
C/C++
const char * toHumanReadable( Poco::Net::MailRecipient::RecipientType recipientType )
{
    switch( recipientType )
    {
    case Poco::Net::MailRecipient::PRIMARY_RECIPIENT:
        return "PRIMARY_RECIPIENT";
    case Poco::Net::MailRecipient::CC_RECIPIENT:
        return "CC";
    case Poco::Net::MailRecipient::BCC_RECIPIENT:
        return "BCC";
    }
    return "?";
}

const Poco::Net::MailMessage::Recipients & recipients = message.recipients();
for( const Poco::Net::MailRecipient & r: recipients )
{
    cout << "Reciper address: " << r.getAddress() << '\n';
    cout << "Reciper name:    " << r.getRealName() << '\n';
    cout << "Reciper type:    " << toHumanReadable( r.getType() ) << '\n';
}
Niestety dla pobieranej wiadomości nie jest to w aktualnej wersji obsługiwane, zapewne będzie, gdyż jest zgłoszony ticket. W międzyczasie musi nam wystarczyć to:
C/C++
if( message.has( "to" ) )
     cout << "To: " << message.get( "to" ) << '\n';

if( message.has( "cc" ) )
     cout << "Cc: " << message.get( "cc" ) << '\n';

co niestety da nam output do samodzielnego rozparsowania:

To: "Grzegorz Bazior (o2)" <ja@o2.pl>, ja@wp.pl, "Grzegorz Bazior (UJ)" <ja@uj.edu.pl>

Problem z ogonkami w temacie

W ramach naszej kultury posiadamy w alfabecie pewne dodatkowe litery, które niestety nie mieszczą się w zakresie ASCII, stąd przy pobieraniu tematu wiadomości o treści np.:

Wielu odbiorców
Nasz temat będzie miał formę:

=?UTF-8?Q?Wielu_odbiorc=c3=b3w?=
Więc jeśli chcemy obsługiwać ogonki to musimy sobie sami to zdekodować, na szczęście są w ramach Projektu POCO są dostarczone pewne klasy do dekodowania, a gotowy przykład jest na stacku, chociaż pozostaje mieć nadzieję, że taka funkcjonalność zostanie dodana w ramach ticketa.


Na szczęście ten problem nie występuje w przypadku treści wiadomości -tam treść jest automatycznie dekodowana (szczegóły w kodzie funkcji MailMessage::readPart w pliku MailMessage.cpp).

Kompletny przykład użycia biblioteki POCO do pobierania poczty

Ten przykład nie jest do końca pełny, gdyż pełny przykład to napisanie klienta pocztowego wraz z wieloma wtyczkami. W tym przykładzie pobieram wszystkie załączniki wysłane od kogoś z domeny uj.edu.pl:
C/C++
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>

#include <Poco/Net/POP3ClientSession.h>
#include <Poco/Net/MessageHeader.h>
#include <Poco/Net/MailMessage.h>
#include <Poco/Net/NetException.h>

using namespace std;


string getTextWithoutQuotes( string originalText )
{
    if( '"' == originalText.front() )
         originalText = originalText.substr( 1 );
   
    if( '"' == originalText.back() )
         originalText.pop_back();
   
    return originalText;
}

void saveAttachmentsToPath( const string & path, const Poco::Net::MailMessage & message )
{
    const Poco::Net::MailMessage::PartVec & parts = message.parts();
    for( const Poco::Net::MailMessage::Part & p: parts )
    {
        if( Poco::Net::MailMessage::CONTENT_ATTACHMENT == p.disposition )
        {
            const string & outputFileName = path + '/' + getTextWithoutQuotes( p.pSource->filename() );
            ofstream attachmentFile( outputFileName, ios::out | ios::binary | ios::trunc );
           
            string buffer;
            istream & is = p.pSource->stream();
            while( getline( is, buffer ) )
                 attachmentFile << buffer << '\n';
           
        }
    }
}

int main()
{
    Poco::Net::POP3ClientSession session( "pop3.wp.pl" );
   
    try
    {
        session.login( "ja@wp.pl", "moje hasło" );
       
        const unsigned totalNumberOfMessages = session.messageCount();
        cout << "Number of messages: " << totalNumberOfMessages << endl;
       
        Poco::Net::POP3ClientSession::MessageInfoVec messages;
        session.listMessages( messages );
       
       
        unsigned counterTotal = 0, counterUJ = 0;
        for( Poco::Net::POP3ClientSession::MessageInfo & mi: messages )
        {
            cout << "Message: " << ++counterTotal << '/' << totalNumberOfMessages << '\n';
           
            try
            {
                Poco::Net::MessageHeader header;
                session.retrieveHeader( mi.id, header );
               
                const string sender = header.get( "from" );
               
                if( sender.find( "uj.edu.pl" ) != std::string::npos )
                {
                    Poco::Net::MailMessage message;
                    session.retrieveMessage( mi.id, message );
                   
                    cout << ++counterUJ << ")\t" << sender << "\t" << message.getSubject();
                    if( message.isMultipart() )
                         cout << " has attachments " << message.parts().size() - 1;
                   
                    cout << '\n';
                   
                    saveAttachmentsToPath( "/home/grzegorz/Pulpit/tmp", message );
                }
            }
            catch(...)
            {
                cerr << "Blad pobierania wiadomosci id = " << counterTotal << endl;
            }
        }
       
    }
    catch( const Poco::Net::POP3Exception & e )
    {
        cerr << "Błąd z obsługą POP3: " << e.what() << endl;
        exit( 1 );
    }
    catch( const Poco::Net::NetException & e )
    {
        cerr << "Błąd po stronie obsługi sieci: " << e.what() << endl;
        exit( 2 );
    }
}

// g++ --std=c++11 -isystem/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/include 3pelny.cc -L/home/grzegorz/Pulpit/poco-1.7.8p3/zbudowany/lib -lPocoNet -lPocoFoundation -lpthread
Parę słów komentarza - bywało, że przy dużej ilości wiadomości w trakcie pobierania wiadomości pojawiały się błędy, stąd ten dodatkowy blok try-catch.
output (obcięty i ze zmodyfikowanymi danymi):

Message: 552/1411
...
Message: 1336/1411
1)    Mariusz <mariusz@uj.edu.pl>      Kwestia zajec
Message: 1337/1411
2)    Andrzej <andrzej@uj.edu.pl>       Fwd: Kurs. has attachments 1
Message: 1341/1411
Message: 1342/1411
3)    <odyseusz@uj.edu.pl>    Kurs has attachments 1
Message: 1343/1411
...
Message: 1411/1411

Bibliografia

https://pocoproject.org/docs​/Poco.Net.html oraz podstrony.