Получение списка контактов с gmail.com без использования Google API

от автора

Однажды мне дали задание получить список контактов пользователя для некоторых почтовых сервисов (среди которых 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:

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:     explicit GmailCom(QString login, QString password);     explicit GmailCom(QString login, QString password, QString proxyHostName, int proxyPort);     explicit GmailCom(QString login, QString password, QString proxyHostName, int proxyPort, QString proxyUser, QString proxyPassword);      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

ссылка на оригинал статьи http://habrahabr.ru/post/203034/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *