Pull to refresh

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

Reading time8 min
Views4.1K
Однажды мне дали задание получить список контактов пользователя для некоторых почтовых сервисов (среди которых 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&ltmpl=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
Tags:
Hubs:
Total votes 17: ↑9 and ↓8+1
Comments16

Articles