带语法高亮输出的HTTP头捕获

之前写了xterm256的wrapper,然后又看到了HTTPie,感觉Solarized的配色挺好看的,于是就有了这个带语法高亮输出的HTTP头捕获程序。

整个程序比较简单,用到了libpcap抓包,然后正则匹配、高亮输出,效果如下。

screenshot-http

代码如下,最新的版本在我的Github上,http-header-capture

#include <arpa/inet.h>
#include <ctype.h>
#include <iomanip>
#include <iostream>
#include <map>
#include <netinet/if_ether.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/ip.h>
#include <pcap.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <regex>
#include <vector>
#include "xterm256.hpp"

using namespace std;

/**
 *  @brief  Regular expression for extracting HTTP Request
 */
regex http_request("([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\\d+\\.\\d+)");

/**
 *  @brief  Regular expression for extracting HTTP Response
 */
regex http_response("(HTTP)(/)(\\d+\\.\\d+)( +)(\\d{3})( +)(.+)");

/**
 *  @brief  Regular expression for extracting HTTP Header Field
 */
regex http_header_field("(.*?)( *)(:)( *)(.+)");

/**
 *  @brief  xterm256 instance
 */
xterm256 xterm;

/**
 *  @brief  Highlight definitions
 */
xterm256::color function_c(0xa6, 0xe2, 0x2e);
xterm256::color text(0xf8, 0xf8, 0xf2);
xterm256::color ns(0xf8, 0xf8, 0xf2);
xterm256::color keyword(0x66, 0xd9, 0xef);
xterm256::color op(0xf9, 0x26, 0x72);
xterm256::color number(0xae, 0x81, 0xff);
xterm256::color reason(0xa6, 0xe2, 0x2e);
xterm256::color attribute(0xa6, 0xe2, 0x2e);
xterm256::color value(0xe6, 0xdb, 0x74);

/**
 *  @brief  Startswith
 *
 *  @param str    string
 *  @param prefix prefix
 *
 *  @return Return true if the given string starts with the prefix
 */
bool startswith(const char * str, const char * prefix) {
    bool result = false;
    size_t prefix_len = strlen(prefix);
    size_t str_len = strlen(str);

    if (str_len >= prefix_len) {
        result = true;
        for (size_t i = 0; i < prefix_len; i++) {
            if (str[i] != prefix[i]) {
                result = false;
                break;
            }
        }
    }

    return result;
}

/**
 *  @brief  First index of target string in the given source string
 *
 *  @param src    source string
 *  @param target target string
 *
 *  @return Returns a positive number to indicate the first index the target string occurs
 */
long long strpos(const char * src, const char * target) {
    size_t src_length = strlen(src);
    size_t target_length = strlen(target);
    int next[1024];
    size_t i = 0, j = 0;
    auto nexts = [&](){
        int j = 0, k = -1;
        next[0] = k;
        while (j < target_length - 1) {
            if (k == -1 || target[j] == target[k]) {
                j++;
                k++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
    };
    nexts();
    while (i < src_length) {
        if (j == -1 || src[i] == target[j]) {
            j++;
            i++;
        } else {
            j = next[j];
        }
        if (j == target_length) {
            return i - target_length;
        }
    }
    return -1;
}

/**
 *  @brief  Split the given string into lines
 *
 *  @param str string with "\r\n"
 *
 *  @return lines
 */
vector<string> splitlines(char * str) {
    vector<string> slines;

    for (int i = 0; i < strlen(str); i++) {
        int start = 0;
        char * string = str + i;

        for (; start < strlen(string); start++) {
            if (string[start] == '\r' && string[start + 1] == '\n') {
                break;
            }
        }
        string[start] = '\0';
        slines.emplace_back(string);
        string[start] = '\r';
        if (string[start + 2] == '\r' && string[start + 3] == '\n') {
            break;
        }
        i += start + 1;
    }

    return slines;
}

/**
 *  @brief  Parse the network packet we captured
 *
 *  @param packet Captured packet
 */
void parse_packet(const u_char * packet) {
    // Extract ether header from packet
    struct ether_header * ether = (struct ether_header *)packet;

    // IP Protocol number is 8
    int ether_protocol = ether->ether_type;
    if (ether_protocol == 8) {
        // Extract IP header from packet
        struct ip * ip = (struct ip *)(packet + sizeof(struct ether_header));

        // Calculate size of IP header
        int ip_header_len = ip->ip_hl * 4;

        // TCP Protocol number is 6
        int ip_protocol = ip->ip_p;
        if (ip_protocol == 6) {
            // Extract TCP header from packet
            struct tcphdr * tcp = (struct tcphdr *)(packet + sizeof(struct ether_header) + ip_header_len);

            // Calculate size of TCP header
            int tcp_header_len = tcp->th_off * 4;

            // TCP Port is 80
            int source_port = ntohs(tcp->th_sport);
            int dest_port = ntohs(tcp->th_dport);

            if (source_port == 80 or dest_port == 80) {
                // Sum header size of ether, IP and TCP
                int header_size = sizeof(struct ether_header) + ip_header_len + tcp_header_len;

                // Extract data from packet
                char * data = (char *)(packet + header_size);
                if (startswith(data, "HTTP")   or
                    startswith(data, "GET")    or
                    startswith(data, "POST")   or
                    startswith(data, "DELETE") or
                    startswith(data, "HEAD")   or
                    startswith(data, "PUT")    or
                    startswith(data, "TRACE"or
                    startswith(data, "CONNECT")) {

                    long long http_header_size = strpos(data, "\r\n\r\n");
                    if (http_header_size > 0) {
                        cout << "Source: " << inet_ntoa(ip->ip_src) << ":" << source_port << '\n';
                        cout << "Destination: " << inet_ntoa(ip->ip_dst) << ":" << dest_port << '\n';

                        data[http_header_size] = '\0';

                        vector<string> header_data = splitlines(data);
                        bool flag = true;

                        for (int i = 0; i < header_data.size(); i++) {
                            smatch matches;
                            string & line = header_data[i];
                            if (flag && regex_match(line, matches, http_request)) {
                                flag = false;
                                xterm   << function_c   << matches[1].str()
                                        << text         << matches[2].str()
                                        << ns           << matches[3].str()
                                        << text         << matches[4].str()
                                        << keyword      << matches[5].str()
                                        << op           << matches[6].str()
                                        << number       << matches[7].str()
                                        << '\n';
                            } else if (flag && regex_match(line, matches, http_response)) {
                                flag = false;
                                xterm   << keyword      << matches[1].str()
                                        << op           << matches[2].str()
                                        << number       << matches[3].str()
                                        << text         << matches[4].str()
                                        << number       << matches[5].str()
                                        << text         << matches[6].str()
                                        << reason       << matches[7].str()
                                        << '\n';
                            } else if (regex_match(line, matches, http_header_field)) {
                                xterm   << attribute    << matches[1].str()
                                        << text         << matches[2].str()
                                        << op           << matches[3].str()
                                        << text         << matches[4].str()
                                        << value        << matches[5].str()
                                        << '\n';
                            } else {
                                xterm << line << '\n';
                            }
                            xterm << "\e[0m";
                            std::cout<<std::flush;
                        }
                    }
                }
            }
        }
    }
}

int main(int argc, const char * argv[]) {
    pcap_if_t * devices;
    pcap_if_t * iterator;
    char errbuf[PCAP_ERRBUF_SIZE];

    if (pcap_findalldevs(&devices, errbuf) == -1) {
        perror("pcap_findalldevs");
        exit(-1);
    }

    printf("Avaiable devices:\n");
    for (iterator = devices; iterator != NULL; iterator = iterator->next) {
        cout << iterator->name << '\n';
    }

    string device;
    cout << "Choose a device to sniff: ";
    cin >> device;

    bool found = false;
    for (iterator = devices; iterator != NULL; iterator = iterator->next) {
        if (strcmp(iterator->name, device.c_str()) == 0) {
            found = true;
            break;
        }
    }

    if (!found) {
        cout << "No such device!\n";
        exit(-1);
    }

    pcap_t * pcap_fd = pcap_open_live(device.c_str(), 65536, 1, 0, errbuf);
    if (!pcap_fd) {
        cout << errbuf << '\n';
        exit(-1);
    }

    struct pcap_pkthdr header;
    const u_char * packet;
    while (1) {
        packet = pcap_next(pcap_fd, &header);
        const u_char * copy = (const u_char *)malloc(header.caplen + 1);
        memset((void *)copy, 0, header.caplen + 1);
        memcpy((void *)copy, packet, header.caplen);
        parse_packet(copy);
        free((void *)copy);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

19 + twelve =