/* Copyright 2016 Luke Shumaker */ #include #include #include /* for {get,free}addrinfo() */ #include /* for EXIT_FAILURE */ #include #include #include #include /* for open */ #include /* for open */ #include "util.h" #include "mpjpeg.h" struct httpfile { char name[256]; struct mpjpeg_stream *stream; }; size_t filec = 0; struct httpfile *filev = NULL; struct reader_thread_args { struct mpjpeg_stream *stream; int fd; const char *boundary; }; void *reader_thread(void *args_anon) { struct reader_thread_args *args = args_anon; mpjpeg_reader(args->stream, args->fd, args->boundary); error(EXIT_FAILURE, 0, "mpjpeg stream ended"); return NULL; } void start_mpjpeg_reader(struct mpjpeg_stream *s, int fd, const char *boundary) { struct reader_thread_args *args = xrealloc(NULL, sizeof(struct reader_thread_args)); args->stream = s; args->fd = fd; args->boundary = boundary; pthread_t thread; pthread_create(&thread, NULL, reader_thread, args); } void file_add(const char *filename) { struct mpjpeg_stream *stream = xrealloc(NULL, sizeof(struct mpjpeg_stream)); init_mpjpeg_stream(stream); int fd = open(filename, O_RDONLY); if (fd < 0) error(EXIT_FAILURE, errno, "opening file: %s", filename); filev = xrealloc(filev, (++filec)*sizeof(*filev)); const char *shortname = strrchr(filename, '/'); if (shortname == NULL) shortname = filename; strncpy(filev[filec-1].name, shortname, sizeof(filev[filec-1].name)); error(0, 0, "added file #%zd: %s", filec-1, filev[filec-1].name); filev[filec-1].stream = stream; start_mpjpeg_reader(stream, fd, "ffserver" /* FIXME */); } struct mpjpeg_stream *file_get(const char *filename) { for (size_t i = 0; i < filec; i++) { if (strncmp(filename, filev[i].name, sizeof(filev[i].name)) == 0) { return filev[i].stream; } } return NULL; } int tcp4_parse_listen(const char *port) { int r; struct addrinfo *addr = NULL; struct addrinfo hints = { 0 }; hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if ((r = getaddrinfo(NULL, port, &hints, &addr)) != 0) error(EXIT_FAILURE, r, _("Could not resolve TCP4 port %s"), port); int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); if (sock < 0) error(EXIT_FAILURE, errno, _("Could not create a TCP4 socket")); int yes = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) error(EXIT_FAILURE, errno, _("Could not bind to TCP4 port %s"), port); if (listen(sock, 5) < 0) error(EXIT_FAILURE, errno, _("Could not listen on TCP4 port %s"), port); freeaddrinfo(addr); return sock; } void connection_handler(int fd) { FILE *netstream = fdopen(fd, "r"); char *line_buf = NULL; size_t line_cap = 0; ssize_t line_len = 0; line_len = getline(&line_buf, &line_cap, netstream); /* expect line to be "GET /path.mjpg HTTP/1.1" */ if (strncmp(line_buf, "GET ", 4) != 0) { dprintf(fd, "HTTP/1.1 405 Method Not Allowed\r\n" "Allow: GET\r\n" "\r\n"); close(fd); return; } char *version = &line_buf[4]; char *path = strsep(&version, " "); if (strcmp(version, "HTTP/1.1\r\n") != 0 && strcmp(version, "HTTP/1.0\r\n")) { close(fd); return; } path = strdupa(path); /* run through the rest of the headers */ while (strcmp(line_buf, "\r\n") != 0 && line_len >= 0) line_len = getline(&line_buf, &line_cap, netstream); struct mpjpeg_stream *vidstream = file_get(path); if (vidstream == NULL) { error(0, 0, "404 %s", path); dprintf(fd, "HTTP/1.1 404 Not Found\r\n" "\r\n"); return; } const char *boundary = "boundary" /* FIXME */; error(0, 0, "200 %s", path); dprintf(fd, "HTTP/1.1 200 OK\r\n" "Content-Type: multipart/x-mixed-replace;boundary=%s\r\n" "\r\n", boundary); mpjpeg_writer(vidstream, fd, boundary); close(fd); } void *connection_thread(void *arg_anon) { int fd = (int)(intptr_t)arg_anon; error(0, 0, "Connection %d opened", fd); connection_handler(fd); error(0, 0, "Connection %d closed", fd); return NULL; } void usage() { printf("Usage: %s [-h] PORT [FILENAME...]\n", program_invocation_name); } int main(int argc, char *argv[]) { if (argc < 2) { dup2(2, 1); usage(); return EXIT_FAILURE; } if (strcmp(argv[1], "-h") == 0) { usage(); return EXIT_SUCCESS; } int sock = tcp4_parse_listen(argv[1]); for (int i = 2; i < argc; i++) file_add(argv[i]); signal(SIGPIPE, SIG_IGN); while (1) { int conn = accept(sock, NULL, NULL); if (conn < 0) continue; pthread_t thread; pthread_create(&thread, NULL, connection_thread, (void*)(intptr_t)conn); } return 0; }