Robo HTTP usando socket e código multiplataforma

Publicado por João Marcos Menezes 17/02/2009

[ Hits: 10.740 ]

Download rankvol.c




Neste programa irei mostrar como criar um Robo HTTP que se conecta em um servidor web (por socket), obtém o cookie e gerencia os dados, tudo isso em C com um código que compila no gcc tanto no Linux quanto no Windows.

Testei no linux e windows nas versões 3 e 4 do gcc. Não testei em outros sistemas operacionais, mas acredito que seja fácil portar, caso o código não seja 100% compativel.

No exemplo, conectamos com o VOL e exibimos o ranking. Isto poderá lhe ser muito util.

  



Esconder código-fonte

/**
 * rankvol.c
 *
 * Neste programa iremos conectar com um servidor web, 
 * obter o cookie e entao com este cookie obter novos 
 * dados do servidor.
 *
 * Iremos utilizar o VOL como exemplo.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#ifdef WIN32
    #include <windows.h>
    #include <winsock.h>
    #include <conio.h>
#else
    #include <netdb.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <termios.h>
#endif

#ifdef WIN32
    #define FILE_SEPARATOR ''
    #define MSG_WAITALL 0
    #define CHAR_ENTER 13
#else
    #define FILE_SEPARATOR '/'
    #define CHAR_ENTER 10
#endif

#ifndef TCP_NODELAY
    #define TCP_NODELAY 1
#endif

/* Aqui iremos declarar algumas variaveis globais */
static char servidor[] = "www.vivaolinux.com.br";
static char scriptLogin[] = "/testarLogin.php";
static char scriptHome[] = "/index.php";

/* Porta HTTP do servidor */
static int portaServidor = 80;

/* Iremos armazenar aqui o login e a senha */
char login[17];
char senha[101];

/* Vamos lidar com buffer de 1Kb (1024 bytes) e mais 
um byte para o armazenamento do null */
char buffer[1025]; 

/* Vamos utilizar buffer de 32Kb para o pacote de subida */
#define BUF_32KB 32768
#define BUF_32KB_WORK 32767
char bufEnvio[BUF_32KB];

/* Prototipos */
int abrirConexao(char * servidorConexao, int portaConexao);
char * obterHeaderHttp(char * servidorConexao, char * cookie);
void enviarDados(int socket, char * buffer);
char * receberDados(int socket);
char * obterPagina(char * script, char * dadosForm, char * cookie);
char * obterCookie();
int efetuarLogin(char * login, char * senha, char * cookie);
long obterRanking(char * cookie);

/* Esta funcao le um caractere. Veio de uma dica na net, 
mas fiz algumas modificacoes :P 
Para evitar o uso de ncurses esse e o jeito de usar algo tipo o getch 
No windows deveremos usar o famoso conio.he o proprio getch */
int lerCarac() {
    int chLido;
    #ifdef WIN32
        chLido = getch();
    #else
        struct termios oldt, newt;
        tcgetattr(STDIN_FILENO, &oldt);
        newt = oldt;
        newt.c_lflag &= ~( ICANON | ECHO );
        tcsetattr(STDIN_FILENO, TCSANOW, &newt);
        chLido = getchar();
        tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    #endif
    return chLido;
}

/* Entrada do programa */
main(int argc, char *argv[])
{
    /* Entrada de dados */
    printf("\nHTTP-Robot - Exemplo de Robo HTTP - Mostra ranking no VOL:\n\n");
    printf("Entre com o login do VOL: ");

    /* O fgets ja coloca um null na ultima posicao automaticamente */
    /* Assim tambem evitamos buffer overflow :P */
    fgets(login, 17, stdin);

    /* Remover o enter, aproveitamos e colocamos um final de string */
    if (login[strlen(login) - 1] == 10) {
        login[strlen(login)-1] = 0;
    } else {
        login[strlen(login)] = 0;
    }

    /* Agora vamos ler a senha */
    printf("Entre com a senha do VOL: ");
    char chLido = 0;
    while (chLido != CHAR_ENTER && strlen(senha) < 101) {
        chLido = lerCarac();
        if (chLido > 27) {
            /* Caracter lido e asterisco na tela */
            putchar(42);
            fflush(stdout);
            senha[strlen(senha)] = chLido;
        }
    }

    /* Forca finalizacao da string da senha */
    senha[strlen(senha)] = 0;
    printf("\n");

    /* Precisamos obter o cookie */
    char * cookie = obterCookie();
    printf("Cookie de sessao obtido: [%s]\n", cookie);

    /* Vamos enviar os dados do login */
    if (!efetuarLogin(login, senha, cookie)) {
        printf("Nao foi possivel efetuar o login.\n");
        printf("Verifique se o login e senha estao corretos.\n");
        exit(-1); 
    } else {
        printf("Login efetuado com sucesso.\n");
    }

    /* Agora vamos obter o ranking */
    long ranking = obterRanking(cookie);
    if (ranking == 0) {
        printf("Ranking nao localizado. \n");
        printf("Verifique se o layout da home do vol foi modificado.\n");
    } else {
        printf("Seu ranking no VOL e: %d\n", ranking);
    }

    /* E nao podemos esquecer de liberar da memoria */
    if (cookie != NULL) {
        free(cookie);
    }

    /* Se chegar aqui e pq tudo deu certo */
    printf("Programa finalizado com sucesso.\n");
    return 0;
}

/* Rotina para obter o cookie da sessao */
char * obterCookie() {

    /* 
        Primeiro precisamos do cookie 
        Vamos obte-lo chamando a index.php e pegando o Set-Cookie
        */
    printf("\nAcessando home e obtendo cookie de sessao ...\n");
    char * dadosHome = obterPagina(scriptHome, NULL, NULL);
    char * posCookieIni = strstr(dadosHome, "Set-Cookie: ");
    if (posCookieIni == NULL) {
        printf("Erro ao obter o cookie. Pode ser problema de conexao.\n");
        exit(-1);
    }
    char * posCookieFim = strstr(posCookieIni + 12, ";");
    if (posCookieFim == NULL) {
        printf("Erro ao obter o cookie. Pode ser problema de conexao.\n");
        exit(-1);
    }
    char * cookie = (char *) malloc(sizeof(char) * 2048);
    if (cookie == NULL) {
        printf("Falta de memoria.\n");
        exit(-1);
    }
    int tamanho = strlen(posCookieIni) - strlen(posCookieFim) - 12;
    strncpy(cookie, posCookieIni + 12, tamanho);
    cookie[tamanho] = 0;

    /* Nao precisamos mais dos dados da home */
    /* Automaticamente estaremos limpando posCookieIni e posCokieFim */
    if (dadosHome != NULL) {
        free(dadosHome);
    }
    return cookie;

}

/* Para efetuar login no VOL */
int efetuarLogin(char * login, char * senha, char * cookie) {

    printf("Efetuando login no VOL ...\n");
    int resLogin = 0;

    /* 2Kb sao suficientes para os dados do form */
    char * dadosForm = (char *) malloc(sizeof(char) * 2048);
    if (dadosForm == NULL) {
        printf("Falta de memoria.");
        exit(-1);
    }

    if ((strlen(login) + strlen(senha) + 87) > BUF_32KB_WORK) {
        printf("Erro de estouro de buffer.\n");
        exit(-1);
    }
    sprintf(dadosForm, "referer=index.php&formLogin=%s&formSenha=%s", 
            login, senha);

    /* Botoes do form */
    strcat(dadosForm, "&imageField2.x=0&imageField2.y=0&Submit=Entrar\n");
    char * dadosRecebidos = obterPagina(scriptLogin, dadosForm, cookie);

    /* Vamos tentar identificar o login atraves de 2 informacoes:
        1-O vol retorna um HTTP/1.1 302 Found para redirecionar 
          para a home quando o login esta ok
        2-Ele retorna um Location: no header 
        3-Verificamos ainda o acesso negado (pagina login) */
    char * tmpBufHttp = strstr(dadosRecebidos, "302 Found");
    if (tmpBufHttp != NULL) {
        char * tmpBufLocation = strstr(dadosRecebidos, "Location:");
        if (tmpBufLocation != NULL) {
            /* Se ele encontrar as 2 strings vamos considerar que houve 
               autenticacao com sucesso */
            if ((strlen(tmpBufHttp) < strlen(dadosRecebidos)) 
                    && (strlen(tmpBufLocation) < strlen(dadosRecebidos))) {
                /* Mas antes vamos procurar por acesso restrito para verificar 
                   se nao mudaram a regra */
                char * tmpBufAcesso = strstr(dadosRecebidos, "acesso restrito");
                if (tmpBufAcesso == NULL) {
                    resLogin = 1;
                }
            }
        }
    }

    /* Vamos limpar da memoria */
    if (dadosForm != NULL) {
        free(dadosForm);
    }
    if (dadosRecebidos != NULL) {
        /* Ao limpar dadosRecebidos estaremos limpando tmpBufHttp 
           e tmpBufLocation */
        free(dadosRecebidos);
    }
    return resLogin;

}

/* Apos autenticado, obtem o ranking mostrado na home do VOL */
long obterRanking(char * cookie) {

    /* Vamos obter a home */
    printf("Obtendo ranking a partir da home ...\n");
    long ranking = 0;
    char * dadosHome = obterPagina(scriptHome, NULL, cookie);

    char * posRank = strstr(dadosHome, "Ranking: <b>");
    if (posRank != NULL) { 
        char * posFimRank = strstr(posRank, "</b>");
        if (posFimRank != NULL) {
            // Temos de tirar o ranking e o deg
            int tamRank = strlen(posRank) - strlen(posFimRank) - 17;
            if (tamRank > 0) {
                char * tmpRank = (char *) malloc(sizeof(char) * (tamRank + 1));
                if (tmpRank == NULL) {
                    printf("Falta de memoria.\n");
                    exit(-1);
                }
                strncpy(tmpRank, posRank + 12, tamRank);
                tmpRank[tamRank] = 0;

                /* Agora devemos converter para long 
                   e depois limpar da memoria */
                ranking = strtol(tmpRank, NULL, 10);
                if (tmpRank != NULL) {
                    free(tmpRank);
                }
            }
        }
    }

    /* Nao precisamos mais dos dados da home */
    /* Automaticamente estaremos limpando posRank e posFimRank */
    if (dadosHome != NULL) {
        free(dadosHome);
    }
    return ranking;
}

/* Funcao para conectar no host */
int abrirConexao(char * servidorConexao, int portaConexao) {
    #ifdef WIN32
        WSADATA wsaData;
        WORD wVersionRequested;
        int err;

        wVersionRequested = MAKEWORD(2, 0);
        err = WSAStartup(wVersionRequested, &wsaData);
        if (err != 0) {
            fprintf(stderr, "Nao achou WinSock DLL.\n");
            exit(-1);
        }
    #endif

    int meuSocket;
    struct sockaddr_in sockAddr;
    struct hostent *hEnt;

    /* Vamos obter os dados do host */
    hEnt = gethostbyname(servidorConexao);
    if (hEnt == NULL) {
        printf("Erro ao obter os dados do host.\n");
        exit(-1);
    }

    /* Agora vamos obter o socket */
    meuSocket = socket(AF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);
    if (meuSocket == -1) {
        printf("Erro ao obter socket tcp.\n");
        exit(-1);
    }

    /* Vamos definir o TCP_NODELAY (usado para comunicacoes de ida e 
       volta para melhorar a performance)  */
    int flagTcpNoDelay = 1;
    int resTcpNoDelay = setsockopt(meuSocket, IPPROTO_TCP, TCP_NODELAY,
        (char *) &flagTcpNoDelay, sizeof(int));
    if (resTcpNoDelay < 0) {
        printf("Erro ao setar tcp_nodelay.\n");
        exit(-1);
    }
    
    /* Vamos definir tambem o timeout do socket 
       (Voce pode precisar mudar em redes lentas) */
    struct timeval tv;
    int timeouts = 0;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    if (setsockopt(meuSocket, SOL_SOCKET, SO_RCVTIMEO, 
            (char *) &tv, sizeof tv)) {
        printf("Erro ao definir timeout.");
        exit(-1);
    }

    /* Entao conectamos */
    memcpy(&sockAddr.sin_addr, hEnt->h_addr, hEnt->h_length);
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = htons(portaConexao);
    if (connect(meuSocket, (struct sockaddr *) &sockAddr, 
            sizeof(sockAddr)) < 0) {
        printf("Erro ao conectar no servidor.\n");
        exit(-1);
    }

    /* E voltamos o socket conectado */
    return meuSocket;
}

/* Esta funcao obtem um header http */
char * obterHeaderHttp(char * servidorConexao, char * cookie) {

    /* Vamos usar um buffer grande (2kb nao tem perigo de estourar) */
    char * headerHttp = (char *) malloc(sizeof(char) * 2048);
    if (headerHttp == NULL) {
        printf("Falta de memoria.\n");
        exit(-1);
    }
    strcpy(headerHttp, "Host: ");
    strcat(headerHttp, servidorConexao);
    strcat(headerHttp, "\n");
    strcat(headerHttp, "User-Agent: Mozilla/5.0 ");
    strcat(headerHttp, "X11; U; Linux i686; en-US; rv:1.8.1.14)\n");
    strcat(headerHttp, "Accept: text/xml,text/html;q=0.9,text/plain;");
    strcat(headerHttp, "q=0.8,image/png,*/*;q=0.5\n");
    strcat(headerHttp, "Accept-Language: en-us,en;q=0.5\n"); 
    strcat(headerHttp, "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n");
    strcat(headerHttp, "Keep-Alive: 300\n");
    if (cookie != NULL) {
        strcat(headerHttp, "Cookie: ");
        strcat(headerHttp, cookie);
        strcat(headerHttp, ";\n");
    }
    strcat(headerHttp, "Connection: keep-alive\n");
    headerHttp[strlen(headerHttp)] = 0;
    return headerHttp;
}

/* Funcao para enviar dados via socket */
void enviarDados(int socket, char * buffer) {
    int envio = send(socket, buffer, strlen(buffer), 0);
    if (envio < 1) {
        printf("Erro no envio dos dados: [%d].\n", envio);
        exit(-1);
    }
}

/* Funcao para receber dados via socket. 
Vamos trabalhar com alocacao dinamica.
Esta funcao retorna um ponteiro para os dados recebidos
*/
char * receberDados(int socket) {
    char * dados = (char *) malloc(sizeof(char) * 1025);
    if (dados == NULL) {
        printf("Falta de memoria.\n");
        exit(-1);
    }
    int contBuf = 0;
    while ((contBuf = recv(socket, buffer, 1024, 0)) > 0) {
        /* Para mostrar que esta recebendo alguma coisa */
        putchar(42);
        fflush(stdout);

        /* Adiciona ao buffer se possivel */
        buffer[contBuf] = 0;
                
        if (dados == NULL) {
            strcpy(dados, buffer);
        } else {
            /* Realocamos mais 1kb */
            dados = realloc(dados, strlen(dados) + 1025);
            if (dados == NULL) {
                printf("Erro na realocacao dinamica.\n");
                exit(-1);
            }
            strcat(dados, buffer);
        }     

        /* Limpar buffer */
        int n = 0;        
        for(n = 0; n <= 1024; n++) {
            buffer[n] = 0;
        }
    }
    dados[strlen(dados)] = 0;
    return dados;
}

/* Obtem os dados de uma pagina html */
char * obterPagina(char * script, char * dadosForm, char * cookie) {

    int meuSocket;
    printf("Conectando: (%s) ... \n", servidor);
    meuSocket = abrirConexao(servidor, portaServidor);
    if (meuSocket == -1) {
        printf("Erro ao conectar no servidor.\n");
        exit(-1);
    }

    /* Vamos obter o header */
    char * headerHttp = obterHeaderHttp(servidor, cookie);

    /* Vamos gerar o post de envio */
    if (dadosForm == NULL) {
        sprintf(bufEnvio, "GET %s HTTP/1.1\n", script);
    } else {
        sprintf(bufEnvio, "POST %s HTTP/1.1\n", script);
    }
    if ((strlen(bufEnvio) + strlen(headerHttp)) > BUF_32KB_WORK) {
        printf("Erro de estouro de buffer.\n");
        exit(-1);
    }
    strcat(bufEnvio, headerHttp);
    
    /* Vamos limpar da memoria */    
    if (headerHttp != NULL) {
        free(headerHttp);
    }    

    /* Temos de adicionar algumas informacoes para mandar o form */
    if (dadosForm != NULL) {
        if ((strlen(bufEnvio) + 64) > BUF_32KB_WORK) {
            printf("Erro de estouro de buffer.\n");
            exit(-1);
        }
        strcat(bufEnvio, "Content-Type: application/x-www-form-urlencoded\n");

        strcat(bufEnvio, "Content-Length: ");
        char * tmpBuf = (char *) malloc(sizeof(char) * 1024);
        if (tmpBuf == NULL) {
            printf("Falta de memoria.\n");
            exit(-1);
        }
        sprintf(tmpBuf, "%d", strlen(dadosForm));
        if ((strlen(bufEnvio) + strlen(tmpBuf) + 2) > BUF_32KB_WORK) {
            printf("Erro de estouro de buffer.\n");
            exit(-1);
        }
        strcat(bufEnvio, tmpBuf);
        strcat(bufEnvio, "\n");
        if (tmpBuf != NULL) {
            free(tmpBuf);
        }
    }
    strcat(bufEnvio, "\n");

    /* Armazena os dados do form */ 
    if (dadosForm != NULL) {
        if ((strlen(bufEnvio) + strlen(dadosForm)) > BUF_32KB_WORK) {
            printf("Erro de estouro de buffer.\n");
            exit(-1);
        }
        strcat(bufEnvio, dadosForm);
    }

    /* Envia os dados */
    enviarDados(meuSocket, bufEnvio);
    printf("Dados foram enviados, recebendo dados ...\n");
    char * dadosRecebidos = receberDados(meuSocket);   
    #ifdef WIN32
        closesocket(meuSocket);
        WSACleanup();
    #else
        close(meuSocket);
    #endif
    printf("\n");

    return dadosRecebidos;
}


Scripts recomendados

Simulação de controle de fluxo usando sockets

Funções básicas para conexão OpenSSL em C

Cliente em C via UDP

Socket em C/C++ - SERVER

Exemplo de sockets: um client e um server bem simples


  

Comentários
[1] Comentário enviado por niodio em 17/12/2010 - 02:02h

Joao,
Estou começando a estudar socket e achei intereçante este motor e da pra mim pegar bastante informação sobre socket. Eu compilei este codigo no meu Fedora 14 e deu tudo certo. Mas tenho uma duvida e se vc poder me ajudar eu agradeço, como que eu faço para meu socket faça uma conecção a um site sem precisar de senha como este e baixar o código html em .txt, isso seria um esperimento que estou tentando para um futuro Web Crawler.rsrsrs bem rustico msm. agradeço desde ja.

[2] Comentário enviado por stremer em 17/12/2010 - 16:22h

Ola,

Para isto basta utilizar o método obterPagina sem os dados do form que será utilizado o get, não necessitando cookie e autenticação caso a página possa ser chamada sozinha.
Estuda o código e tenta montar um do zero mais enxugado.

JM


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner
Linux banner
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts