Download
146 lines 4.5 KiBCModified 2020-09-18 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
/*
 * http-msg.c - A minimal C program to output an HTTP message.
 * Usage:
 *   http-msg <file.html
 *   http-msg <myfile "Content-Type: $(file -bi myfile)"
 *   maim -u | http-msg "Content-Type: image/png"
 */

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>

#include "utils.h"

// ~16Kb
#define CHUNK (1<<12)

const char *usage = "Usage: http-msg headers...\n";

static const struct {
    const char *extension;
    const char *header;
} mime_types[] = {
    {"css",  "text/css; charset=utf-8"},
    {"html", "text/html; charset=utf-8"},
    {"xml",  "text/xml; charset=utf-8"},
    {"js",   "text/javascript; charset=utf-8"},
    {"ico",  "image/x-icon; charset=binary"},
    {"png",  "image/png; charset=binary"},
    {"jpg",  "image/jpeg; charset=binary"},
    {"jpeg", "image/jpeg; charset=binary"},
    {"gif",  "image/gif; charset=binary"},
    {"txt",  "text/plain; charset=utf-8"},
    {"md",   "text/plain; charset=utf-8"},
    {"svg",  "image/svg+xml; charset=binary"},
    {"bmp",  "image/bmp; charset=binary"},
    {"pdf",  "application/pdf; charset=binary"},
    {"gz",   "application/gzip; charset=binary"},
    {"tar",  "application/tar; charset=binary"},
    {"zip",  "application/zip; charset=binary"},
    {"mp3",  "audio/mpeg; charset=binary"},
    {"webm", "video/webm; charset=binary"},
    {"wav",  "audio/wave; charset=binary"},
    {"ogg",  "video/ogg; charset=binary"},
    {"apng", "image/apng; charset=binary"},
    {"cur",  "image/x-icon; charset=binary"},
    {"tiff", "image/tiff; charset=binary"},
    {"tif",  "image/tiff; charset=binary"},
    {"webp", "image/webp; charset=binary"},
    {NULL, NULL},
};

static void mime_type(const char *filename)
{
    char *dot = strrchr(filename, '.');
    if (dot) {
        for (int i = 0; mime_types[i].extension; i++) {
            if (strcasecmp(dot+1, mime_types[i].extension) == 0) {
                xwrite(STDOUT_FILENO, mime_types[i].header);
                return;
            }
        }
    }
    xwrite(STDOUT_FILENO, "text/plain; charset=utf-8");
}

int main(int argc, char *argv[])
{
    char **args = &argv[1];
    if (args[0] && (strcmp(args[0], "-h") == 0 || strcmp(args[0], "--help") == 0)) {
        xwrite(STDERR_FILENO, usage);
        _exit(argc >= 2);
    }

    // Try to get filename/filesize of piped-in file. This won't work in all
    // cases (e.g. `cat file.html | http-msg`) but it will work in the common
    // case of `http-msg <file.html`
    char filename[PATH_MAX] = {0};
    off_t fsize = -1;
    if (readlink("/proc/self/fd/0", filename, sizeof(filename)) != -1) {
        fsize = lseek(STDIN_FILENO, 0, SEEK_END);
        if (fsize != -1)
            lseek(STDIN_FILENO, 0, SEEK_SET);
    }

    // Header:
    if (*args && startswith(*args, "HTTP/")) {
        char *header = *(args++);
        xwrite(STDOUT_FILENO, header);
        xwrite(STDOUT_FILENO, "\r\n");
    } else {
        xwrite(STDOUT_FILENO, "HTTP/1.1 200 OK\r\n");
    }

    int has_content_type = 0;
    int has_content_length = 0;
    int has_transfer_encoding = 0;

    for (; *args; args++) {
        xwrite(STDOUT_FILENO, *args);
        xwrite(STDOUT_FILENO, "\r\n");
        if (startswith(*args, "Content-Type:"))
            ++has_content_type;
        else if (startswith(*args, "Content-Length:"))
            ++has_content_length;
        else if (startswith(*args, "Transfer-Encoding:")
                 && strcmp(*args, "Transfer-Encoding: identity") != 0)
            ++has_transfer_encoding;
    }
    if (!has_content_type) {
        xwrite(STDOUT_FILENO, "Content-Type: ");
        mime_type(filename);
        xwrite(STDOUT_FILENO, "\r\n");
    }

    char buf[CHUNK];
    if (fsize >= 0 && !has_content_length) {
        printf("Content-Length: %ld\r\n\r\n", fsize);
        fflush(stdout);
        for (;;) {
            ssize_t just_read = read(STDIN_FILENO, buf, CHUNK);
            if (just_read <= 0)
                break;
            write(1, buf, (size_t)just_read);
        }
    } else {
        if (!has_transfer_encoding)
            xwrite(STDOUT_FILENO, "Transfer-Encoding: chunked\r\n\r\n");
        for (;;) {
            ssize_t just_read = read(STDIN_FILENO, buf, CHUNK);
            if (just_read <= 0)
                break;
            printf("%ld\r\n", just_read);
            fflush(stdout);
            write(1, buf, (size_t)just_read);
            xwrite(STDOUT_FILENO, "\r\n");
        }
        xwrite(STDOUT_FILENO, "\r\n");
    }

    return 0;
}