/*
 * internet - acessa uma URL pública HTTP/HTTPS e imprime o conteúdo baixado.
 * Uso: internet <url> [--raw] [--max-bytes=N] [--output=arquivo]
 *
 * Segurança: bloqueia localhost e IPs privados/reservados antes do download.
 */

#define _GNU_SOURCE
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>

static void uso(void)
{
    fprintf(stderr, "Uso: internet <url> [--raw] [--max-bytes=N] [--output=arquivo]\n");
    fprintf(stderr, "Baixa uma pagina/endpoint publico HTTP/HTTPS. Por padrao remove tags HTML simples.\n");
}

static int ip_publico(const char *ip)
{
    unsigned char b[16];
    int fam = strchr(ip, ':') ? AF_INET6 : AF_INET;
    if (inet_pton(fam, ip, b) != 1) return 0;
    if (fam == AF_INET) {
        unsigned int x = ((unsigned int)b[0] << 24) | ((unsigned int)b[1] << 16) | ((unsigned int)b[2] << 8) | b[3];
        if ((x >> 24) == 10) return 0;              /* 10.0.0.0/8 */
        if ((x >> 20) == 0xAC1) return 0;           /* 172.16.0.0/12 */
        if ((x >> 16) == 0xC0A8) return 0;          /* 192.168.0.0/16 */
        if ((x >> 24) == 127) return 0;             /* loopback */
        if ((x >> 24) == 0) return 0;               /* this network */
        if ((x >> 16) == 0xA9FE) return 0;          /* link-local */
        if ((x >> 24) >= 224) return 0;             /* multicast/reserved */
    } else {
        static const unsigned char loop[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
        if (!memcmp(b, loop, 16)) return 0;
        if ((b[0] & 0xFE) == 0xFC) return 0;        /* unique local */
        if (b[0] == 0xFE && (b[1] & 0xC0) == 0x80) return 0; /* link-local */
        if (b[0] == 0xFF) return 0;                 /* multicast */
    }
    return 1;
}

static int extrair_host(const char *url, char *host, size_t tam)
{
    const char *p = strstr(url, "://");
    if (!p) return -1;
    p += 3;
    if (*p == '[') {
        p++;
        const char *q = strchr(p, ']');
        if (!q || (size_t)(q - p) >= tam) return -1;
        memcpy(host, p, (size_t)(q - p));
        host[q - p] = '\0';
        return host[0] ? 0 : -1;
    }
    size_t i = 0;
    while (p[i] && p[i] != '/' && p[i] != ':' && p[i] != '?' && p[i] != '#' && i + 1 < tam) {
        host[i] = p[i];
        i++;
    }
    host[i] = '\0';
    return i > 0 ? 0 : -1;
}

static int validar_url_publica(const char *url)
{
    if (strncmp(url, "http://", 7) != 0 && strncmp(url, "https://", 8) != 0) {
        fprintf(stderr, "internet: URL invalida: use http:// ou https://\n");
        return 0;
    }
    char host[512];
    if (extrair_host(url, host, sizeof(host)) != 0) {
        fprintf(stderr, "internet: URL invalida: host nao identificado\n");
        return 0;
    }
    if (!strcasecmp(host, "localhost") || !strcmp(host, "127.0.0.1") || !strcmp(host, "::1")) {
        fprintf(stderr, "internet: URL bloqueada: localhost nao permitido\n");
        return 0;
    }
    struct addrinfo hints;
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    struct addrinfo *res = NULL;
    if (getaddrinfo(host, NULL, &hints, &res) != 0) {
        fprintf(stderr, "internet: nao foi possivel resolver host: %s\n", host);
        return 0;
    }
    for (struct addrinfo *a = res; a; a = a->ai_next) {
        char ip[INET6_ADDRSTRLEN];
        void *addr = NULL;
        if (a->ai_family == AF_INET) addr = &((struct sockaddr_in *)a->ai_addr)->sin_addr;
        else if (a->ai_family == AF_INET6) addr = &((struct sockaddr_in6 *)a->ai_addr)->sin6_addr;
        else continue;
        if (inet_ntop(a->ai_family, addr, ip, sizeof(ip)) && !ip_publico(ip)) {
            freeaddrinfo(res);
            fprintf(stderr, "internet: URL bloqueada: IP privado/reservado (%s)\n", ip);
            return 0;
        }
    }
    freeaddrinfo(res);
    return 1;
}

static char *ler_arquivo_limitado(const char *path, long max_bytes, long *tam_out)
{
    FILE *fp = fopen(path, "rb");
    if (!fp) return NULL;
    fseek(fp, 0, SEEK_END);
    long tam = ftell(fp);
    if (tam < 0) { fclose(fp); return NULL; }
    if (max_bytes > 0 && tam > max_bytes) tam = max_bytes;
    fseek(fp, 0, SEEK_SET);
    char *buf = malloc((size_t)tam + 1);
    if (!buf) { fclose(fp); return NULL; }
    size_t n = fread(buf, 1, (size_t)tam, fp);
    fclose(fp);
    buf[n] = '\0';
    if (tam_out) *tam_out = (long)n;
    return buf;
}

static char *texto_sem_tags(const char *html)
{
    size_t cap = strlen(html) + 1, len = 0;
    char *out = malloc(cap);
    if (!out) return NULL;
    int tag = 0, sp = 1;
    for (const char *p = html; *p; p++) {
        if (*p == '<') { tag = 1; if (!sp) { out[len++] = ' '; sp = 1; } continue; }
        if (tag) { if (*p == '>') tag = 0; continue; }
        unsigned char c = (unsigned char)*p;
        if (isspace(c)) { if (!sp) { out[len++] = ' '; sp = 1; } }
        else { out[len++] = (char)c; sp = 0; }
    }
    while (len > 0 && isspace((unsigned char)out[len - 1])) len--;
    out[len] = '\0';
    return out;
}

static int gravar_arquivo(const char *path, const char *conteudo)
{
    FILE *fp = fopen(path, "wb");
    if (!fp) return -1;
    fputs(conteudo, fp);
    if (!*conteudo || conteudo[strlen(conteudo) - 1] != '\n') fputc('\n', fp);
    return fclose(fp) == 0 ? 0 : -1;
}

int main(int argc, char **argv)
{
    const char *url = NULL, *output = NULL;
    int raw = 0;
    long max_bytes = 1024 * 1024;
    for (int i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "--ajuda") || !strcmp(argv[i], "-h")) { uso(); return 0; }
        else if (!strcmp(argv[i], "--raw")) raw = 1;
        else if (strncmp(argv[i], "--max-bytes=", 12) == 0) max_bytes = atol(argv[i] + 12);
        else if (strncmp(argv[i], "--output=", 9) == 0) output = argv[i] + 9;
        else if (argv[i][0] != '-' && !url) url = argv[i];
        else { uso(); return 1; }
    }
    if (!url) { uso(); return 1; }
    if (!validar_url_publica(url)) return 1;

    char tmp[] = "/tmp/sisc_internet_XXXXXX";
    int fd = mkstemp(tmp);
    if (fd < 0) { perror("internet: mkstemp"); return 1; }
    close(fd);

    pid_t pid = fork();
    if (pid == 0) {
        freopen(tmp, "wb", stdout);
        char *args[] = {
            "curl", "-L", "--max-redirs", "5", "--max-time", "30", "--connect-timeout", "10",
            "-A", "sisc-internet/1.0", "--proto", "=http,https", "--proto-redir", "=http,https",
            "-sS", (char *)url, NULL
        };
        execvp("curl", args);
        _exit(127);
    }
    int st = 0;
    waitpid(pid, &st, 0);
    if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
        unlink(tmp);
        fprintf(stderr, "internet: falha ao baixar URL (curl=%d)\n", WIFEXITED(st) ? WEXITSTATUS(st) : 1);
        return 1;
    }

    long tam = 0;
    char *conteudo = ler_arquivo_limitado(tmp, max_bytes, &tam);
    unlink(tmp);
    if (!conteudo) { fprintf(stderr, "internet: nao foi possivel ler resposta\n"); return 1; }
    char *saida = raw ? strdup(conteudo) : texto_sem_tags(conteudo);
    free(conteudo);
    if (!saida) return 1;

    if (output) {
        if (strstr(output, "..") || output[0] == '/') {
            fprintf(stderr, "internet: output invalido; use caminho relativo sem ..\n");
            free(saida); return 1;
        }
        if (gravar_arquivo(output, saida) != 0) {
            fprintf(stderr, "internet: nao foi possivel gravar %s\n", output);
            free(saida); return 1;
        }
        printf("internet: salvo em %s (%ld bytes lidos)\n", output, tam);
    } else {
        fputs(saida, stdout);
        if (*saida && saida[strlen(saida) - 1] != '\n') fputc('\n', stdout);
    }
    free(saida);
    return 0;
}
