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;
}
|