Robo HTTP usando socket e código multiplataforma

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

[ Hits: 12.196 ]

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

Programa em C para monitorar IPs e portas ativas

Simulação de controle de fluxo usando sockets

Socket em C/C++ - CLIENT

Simples servidor http com concorrência feito em C

Tratando o log do snmptrapd


  

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

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts