Однажды мне дали задание получить список контактов пользователя для некоторых почтовых сервисов (среди которых gmail.com, yahoo.com, aos.com). Сначала я занялся изучением почтовых протоколов IMAP и POP3, но к сожалению эти протоколы не предусматривают возможности получения списка контактов. От использования API которые предоставляют некоторые из этих почтовых сервисов я отказался, так-как эти API не для всех языков предоставляются, а так-же пришлось бы тратить время на изучение каждого из них.
Решил я что использование сокетов — это единственный вариант, который позволит быстро получить список контактов и делать это примерно одинаково для большинства почтовых сервисов. Суть в том, чтобы эмулировать работу браузера. Я рассмотрю алгоритм получения списка контактов с gmail.com, но подобным образом это делается и в других почтовых сервисах.
Для начала понадобится http sniffer. Я воспользовался HTTP Analyzer и к сожалению Internet Explorer лучше всех подошёл (Chrome вообще не снифится, а Mozilla Firefox многое не снифит).
Контакты делятся на категории, поэтому мы будем доставать из категорий «Контакты» и «Другие контакты». Для получения списка контактов, надо отправить следующие запросы:
GET /mail/?auth=%0 HTTP/1.1
Host: mail.google.com
Cookie: SID=%1
GET /mail/c/u/0/data/contactstore?ac=false&ccnt=true&cr=true&ct=true&eu=1&ev=true&f=g4&gids=26ae&gp=false&hl=ru&id=personal&max=250&nge=true&out=js&pbd=true&sf=display&st=0&type=4 HTTP/1.1
Host: mail.google.com
Cookie: GX=%0
Поэтому потребуется достать значение параметра auth и куки SID и GX.
Приступим.
Входим в систему и сохраняем параметр sidt для accounts.youtube.com и кук SID:
Затем получаем значение sidt для accounts.google.com:
После чего получаем значение auth для mail.google.com:
И достаём наш искомый кук GX:
Теперь можно получить список контактов:
И список контактов из группы «Другие контакты»:
Ещё есть конструктор, который инициализирует логин и пароль при создании объекта:
И сам метод получения списка контактов:
Хедер выглядит следующим образом:
Использование класса:
И наконец *.pro файл:
Всё общение с сервером происходит по защищённому соединению. Код полностью рабочий. Если скопипастить поочерёдно каждый блок кода и поместить в соответствующие файлы — он будет компилироваться. Использовал компилятор g++, среда разработки Qt creator, версия 5.1
Решил я что использование сокетов — это единственный вариант, который позволит быстро получить список контактов и делать это примерно одинаково для большинства почтовых сервисов. Суть в том, чтобы эмулировать работу браузера. Я рассмотрю алгоритм получения списка контактов с gmail.com, но подобным образом это делается и в других почтовых сервисах.
Для начала понадобится http sniffer. Я воспользовался HTTP Analyzer и к сожалению Internet Explorer лучше всех подошёл (Chrome вообще не снифится, а Mozilla Firefox многое не снифит).
Контакты делятся на категории, поэтому мы будем доставать из категорий «Контакты» и «Другие контакты». Для получения списка контактов, надо отправить следующие запросы:
GET /mail/?auth=%0 HTTP/1.1
Host: mail.google.com
Cookie: SID=%1
GET /mail/c/u/0/data/contactstore?ac=false&ccnt=true&cr=true&ct=true&eu=1&ev=true&f=g4&gids=26ae&gp=false&hl=ru&id=personal&max=250&nge=true&out=js&pbd=true&sf=display&st=0&type=4 HTTP/1.1
Host: mail.google.com
Cookie: GX=%0
Поэтому потребуется достать значение параметра auth и куки SID и GX.
Приступим.
Входим в систему и сохраняем параметр sidt для accounts.youtube.com и кук SID:
QString GmailCom::postLogin()
{
int searchResult = -1;
QString buffer = "";
QString temp = "";
QString endString = "\r\n";
QString endHead = "\r\n\r\n";
QString startSidt = "sidt=";
QString startCookieSid = "SID=";
QString autoLogin = "X-Auto-Login:";
QSslSocket socket;
socket.connectToHostEncrypted("accounts.google.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 0";
socket.write(QString("POST /ServiceLoginAuth HTTP/1.1\r\n"\
"Content-Type: application/x-www-form-urlencoded\r\n"\
"Host: accounts.google.com\r\n"\
"Content-Length: 741\r\n"\
"Cookie: GALX=P08JFYX33nQ;\r\n"\
"\r\n"\
"continue=http%3A%2F%2Fmail.google.com%2Fmail%2F&service=mail&rm=false&dsh=-8197000947972966366<mpl=default&scc=1&GALX=P08JFYX33nQ&pstMsg=1&dnConn=&checkConnection=youtube%3A1281%3A0&checkedDomains=youtube&timeStmp=&secTok=&_utf8=%E2%98%83&bgresponse=%21A0KXP079cDPUuUQp6r4pV6akFgIAAAFIUgAAAF4qARD5AbnvQQIZUPsKgyJAteOUThq8meti42P_lSOgXFYCB7HooRpk6np4p5WpvswQrM2ejpn0MjFQxlTHTjWuoyZeWUIsb9xxFDpxCCClPHPv2S3AB416Sq7sUB2IVzG2muVwYSLZ7I_EE99s2k10uDsq9l8nq7IV4QAxMMBRs_SUH-JsiFDBhg0c2xMGGjXvBtoJSGKXuY1d9wTKRx3AQ7hZTyP3vEnLoSX-bj8DruGDteXN1QSMsbLUVd2QQpCSdT6LyDVcEnqo2vJmfxLOvpb898yVrVILjYXona137oYoGy4Lc45CH9c4iicTXIaV4viayj5JlyjgBKPVr7gcYdQVEpLlU6sUGgVuU-RZ64rNLg&Email=%0&Passwd=%1&signIn=%D0%92%D0%BE%D0%B9%D1%82%D0%B8&rmShown=1").arg(mLogin).arg(mPassword).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endHead) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(autoLogin);
if(searchResult != -1) return "Error 1";
searchResult = buffer.indexOf(startCookieSid);
if(searchResult == -1) return "Error 2";
buffer = buffer.mid(searchResult + startCookieSid.length());
mCookieSid = buffer.mid(0, buffer.indexOf(endString));
searchResult = buffer.indexOf(startSidt);
if(searchResult == -1) return "Error 3";
buffer = buffer.mid(searchResult + startSidt.length());
mAccountsYoutubeSidt = buffer.mid(0, buffer.indexOf(endString));
return "Ok";
}
Затем получаем значение sidt для accounts.google.com:
QString GmailCom::getAccountsGoogle()
{
int searchResult = -1;
QString buffer = "";
QString temp = "";
QString endString = "\r\n";
QString endHead = "\r\n\r\n";
QString startSidt = "sidt=";
QSslSocket socket;
socket.connectToHostEncrypted("accounts.youtube.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 4";
socket.write(QString("GET /accounts/SetSID?ssdc=1&sidt=%0 HTTP/1.1\r\n"\
"Host: accounts.youtube.com\r\n"\
"\r\n").arg(mAccountsYoutubeSidt).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endHead) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(startSidt);
if(searchResult == -1) return "Error 5";
buffer = buffer.mid(searchResult + startSidt.length());
mAccountsGoogleSidt = buffer.mid(0, buffer.indexOf(endString));
return "Ok";
}
После чего получаем значение auth для mail.google.com:
QString GmailCom::getAccountsMail()
{
int searchResult = -1;
QString buffer = "";
QString temp = "";
QString endString = "\r\n";
QString endHead = "\r\n\r\n";
QString startAuth = "auth=";
QSslSocket socket;
socket.connectToHostEncrypted("accounts.youtube.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 6";
socket.write(QString("GET /accounts/SetSID?ssdc=1&sidt=%0 HTTP/1.1\r\n"\
"Host: accounts.google.md\r\n"\
"\r\n").arg(mAccountsGoogleSidt).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endHead) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(startAuth);
if(searchResult == -1) return "Error 7";
buffer = buffer.mid(searchResult + startAuth.length());
mMailGoogleAuth = buffer.mid(0, buffer.indexOf(endString));
return "Ok";
}
И достаём наш искомый кук GX:
QString GmailCom::getMailAuth()
{
int searchResult = -1;
QString buffer = "";
QString temp = "";
QString endString = "\r\n";
QString endHead = "\r\n\r\n";
QString startGx = "GX=";
QSslSocket socket;
socket.connectToHostEncrypted("mail.google.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 8";
socket.write(QString("GET /mail/?auth=%0 HTTP/1.1\r\n"\
"Host: mail.google.com\r\n"\
"Cookie: SID=%1\r\n"\
"\r\n").arg(mMailGoogleAuth).arg(mCookieSid).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endHead) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(startGx);
if(searchResult == -1) return "Error 9";
buffer = buffer.mid(searchResult + startGx.length());
mGxCookie = buffer.mid(0, buffer.indexOf(endString));
return "Ok";
}
Теперь можно получить список контактов:
QString GmailCom::getMyContacts()
{
int searchResult = -1;
QStringList listContacts;
QString stringContacts;
QString buffer = "";
QString startContacts = "&&&START&&&";
QString endContacts = "&&&END&&&";
QString temp = "";
QString rn = "\r\n";
QSslSocket socket;
socket.connectToHostEncrypted("mail.google.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 10";
socket.write(QString("GET /mail/c/u/0/data/contactstore?ac=false&ccnt=true&cr=true&ct=true&eu=1&ev=true&f=g4&gids=6&gp=false&hl=ru&id=personal&max=250&nge=true&out=js&pbd=true&type=4 HTTP/1.1\r\n"\
"Host: mail.google.com\r\n"\
"Cookie: GX=%0\r\n"\
"\r\n").arg(mGxCookie).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endContacts) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(startContacts);
if(searchResult == -1) return "Error 11";
buffer = buffer.mid(searchResult + startContacts.length());
searchResult = buffer.indexOf(endContacts);
if(searchResult == -1) return "Error 11";
buffer = buffer.mid(0, searchResult);
while(1)
{
int rn_1 = buffer.indexOf(rn, 0);
int rn_2 = buffer.indexOf(rn, rn_1 + rn.length());
if(rn_1 == -1 || rn_2 == -1)
break;
else
buffer.remove(rn_1, rn_2 - rn_1 + rn.length());
}
listContacts = buffer.split("\\\"");
listContacts.removeDuplicates();
for(int i = 0; i < listContacts.size(); i++)
if(listContacts[i].indexOf("@") != -1)
stringContacts += listContacts[i] + " ";
return stringContacts;
}
И список контактов из группы «Другие контакты»:
QString GmailCom::getGroupContacts()
{
int searchResult = -1;
QStringList listContacts;
QString stringContacts;
QString buffer = "";
QString startContacts = "&&&START&&&";
QString endContacts = "&&&END&&&";
QString temp = "";
QString rn = "\r\n";
QSslSocket socket;
socket.connectToHostEncrypted("mail.google.com", 443);
if(!socket.waitForEncrypted(MAX_WAIT))
return "Error 12";
socket.write(QString("GET /mail/c/u/0/data/contactstore?ac=false&ccnt=true&cr=true&ct=true&eu=1&ev=true&f=g4&gids=26ae&gp=false&hl=ru&id=personal&max=250&nge=true&out=js&pbd=true&sf=display&st=0&type=4 HTTP/1.1\r\n"\
"Host: mail.google.com\r\n"\
"Cookie: GX=%0\r\n"\
"\r\n"
).arg(mGxCookie).toUtf8());
while(socket.waitForReadyRead(MAX_WAIT))
{
temp = socket.readAll();
buffer += temp;
if(temp.indexOf(endContacts) != -1) break;
}
socket.disconnectFromHost();
searchResult = buffer.indexOf(startContacts);
if(searchResult == -1) return "Error 13";
buffer = buffer.mid(searchResult + startContacts.length());
searchResult = buffer.indexOf(endContacts);
if(searchResult == -1) return "Error 13";
buffer = buffer.mid(0, searchResult);
while(1)
{
int rn_1 = buffer.indexOf(rn, 0);
int rn_2 = buffer.indexOf(rn, rn_1 + rn.length());
if(rn_1 == -1 || rn_2 == -1)
break;
else
buffer.remove(rn_1, rn_2 - rn_1 + rn.length());
}
listContacts = buffer.split("\\\"");
listContacts.removeDuplicates();
for(int i = 0; i < listContacts.size(); i++)
if(listContacts[i].indexOf("@") != -1)
stringContacts += listContacts[i] + " ";
return stringContacts;
}
Ещё есть конструктор, который инициализирует логин и пароль при создании объекта:
GmailCom::GmailCom(QString login, QString password):
mLogin(login), mPassword(password) {}
И сам метод получения списка контактов:
QString GmailCom::getContacts()
{
QString temp;
QString error;
if((error = postLogin()) != "Ok") return error;
if((error = getAccountsGoogle()) != "Ok") return error;
if((error = getAccountsMail()) != "Ok") return error;
if((error = getMailAuth()) != "Ok") return error;
QString stringContacts = "";
temp = getMyContacts();
if(temp.split(' ')[0] == "Error") return temp;
stringContacts += temp;
temp = getGroupContacts();
if(temp.split(' ')[0] == "Error") return temp;
stringContacts += temp;
stringContacts.remove(stringContacts.length() - 1, 1);
return stringContacts;
}
Хедер выглядит следующим образом:
#ifndef GMAILCOM_H
#define GMAILCOM_H
#include <QSslSocket>
#include <QNetworkProxy>
#include <QStringList>
#define MAX_WAIT 3000
class GmailCom
{
private:
QString mLogin;
QString mPassword;
QString mAccountsYoutubeSidt;
QString mAccountsGoogleSidt;
QString mMailGoogleAuth;
QString mCookieSid;
QString mGxCookie;
QString postLogin();
QString getAccountsGoogle();
QString getAccountsMail();
QString getMailAuth();
QString getMyContacts();
QString getGroupContacts();
public:
GmailCom(QString login, QString password);
QString getContacts();
};
#endif
Использование класса:
#include <QCoreApplication>
#include "GmailCom.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
GmailCom gmailCom("login", "password");
qDebug() << gmailCom.getContacts();
return a.exec();
}
И наконец *.pro файл:
#-------------------------------------------------
#
# Project created by QtCreator 2013-10-23T09:40:42
#
#-------------------------------------------------
QT += core
QT += network
QT -= gui
TARGET = Mailer
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
GmailCom.cpp
HEADERS += \
GmailCom.h
Всё общение с сервером происходит по защищённому соединению. Код полностью рабочий. Если скопипастить поочерёдно каждый блок кода и поместить в соответствующие файлы — он будет компилироваться. Использовал компилятор g++, среда разработки Qt creator, версия 5.1