diff --git a/README.md b/README.md
index 5ae5ccb..6dfdc85 100644
--- a/README.md
+++ b/README.md
@@ -16,15 +16,12 @@
| Streaming via UNIX domain socket |  Yes |  No |
| Debug logs without recompiling,
performance statistics log,
access to HTTP broadcast parameters |  Yes |  No |
| Access to webcam controls (focus, servos)
and settings such as brightness via HTTP |  No |  Yes |
-| Option to serve files
with a built-in HTTP server |  No 3 |  Yes |
Footnotes:
* ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code.
* ```2``` This feature allows to cut down outgoing traffic several-fold when broadcasting HDMI, but it increases CPU usage a little bit. The idea is that HDMI is a fully digital interface and each captured frame can be identical to the previous one byte-wise. There's no need to broadcast the same image over the net several times a second. With the `--drop-same-frames=20` option enabled, µStreamer will drop all the matching frames (with a limit of 20 in a row). Each new frame is matched with the previous one first by length, then using ```memcmp()```.
- * ```3``` ...and there'll never be. µStreamer is designed UNIX-way, so if you need a small website with your broadcast, install NGINX.
-
-----
# TL;DR
If you're going to live-stream from your backyard webcam and need to control it, use mjpg-streamer. If you need a high-quality image with high FPS - µStreamer for the win.
diff --git a/README.ru.md b/README.ru.md
index f2f119d..99c9c6f 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -16,15 +16,12 @@
| Стрим через UNIX domain socket |  Есть |  Нет |
| Дебаг-логи без перекомпиляции,
логгирование статистики производительности,
возможность получения параметров
трансляции по HTTP |  Есть |  Нет |
| Поддержка контролов веб-камер (фокус,
движение сервами) и всяких настроек,
типа яркости, через HTTP |  Нет |  Есть |
-| Возможность сервить файлы встроенным
HTTP-сервером |  Нет 3 |  Есть |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
* ```2``` Это фича позволяет в несколько раз снизить объем исходящего трафика при трансляции HDMI, однако немного увеличивает загрузку процессора. Суть в том, что HDMI - полностью цифровой интерфейс, и новый захваченный фрейм может быть идентичен предыдущему в точности до байта. В этом случае нет нужды передавать одну и ту же картинку по сети несколько раз в секунду. При использовании опции `--drop-same-frames=20`, µStreamer будет дропать все одинаковые фреймы, но не более 20 подряд. Новый фрейм сравнивается с предыдущим сначала по длине, а затем помощью ```memcmp()```.
- * ```3``` ... и не будет. µStreamer придерживается концепции UNIX-way, так что если вам нужно нарисовать маленький сайтик со встроенной трансляцией - просто поставьте NGINX.
-
-----
# TL;DR
Если вам нужно вещать стрим с уличной камеры и управлять ее параметрами - возьмите mjpg-streamer. Если же вам нужно очень качественное изображение с высоким FPS - µStreamer ваш бро.
diff --git a/src/http/mime.c b/src/http/mime.c
new file mode 100644
index 0000000..aaa9dd0
--- /dev/null
+++ b/src/http/mime.c
@@ -0,0 +1,73 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#include
+
+#include
+
+#include "../tools.h"
+
+#include "mime.h"
+
+
+static const struct {
+ const char *ext;
+ const char *mime;
+} _MIME_TYPES[] = {
+ {"html", "text/html"},
+ {"htm", "text/html"},
+ {"css", "text/css"},
+ {"js", "text/javascript"},
+ {"txt", "text/plain"},
+ {"jpg", "image/jpeg"},
+ {"jpeg", "image/jpeg"},
+ {"png", "image/png"},
+ {"gif", "image/gif"},
+ {"ico", "image/x-icon"},
+ {"bmp", "image/bmp"},
+ {"svg", "image/svg+xml"},
+ {"swf", "application/x-shockwave-flash"},
+ {"cab", "application/x-shockwave-flash"},
+ {"jar", "application/java-archive"},
+ {"json", "application/json"},
+};
+
+
+const char *guess_mime_type(const char *path) {
+ char *dot;
+ char *ext;
+
+ dot = strchr(path, '.');
+ if (dot == NULL || strchr(dot, '/') != NULL) {
+ goto misc;
+ }
+
+ ext = dot + 1;
+ for (unsigned index = 0; index < ARRAY_LEN(_MIME_TYPES); ++index) {
+ if (!evutil_ascii_strcasecmp(ext, _MIME_TYPES[index].ext)) {
+ return _MIME_TYPES[index].mime;
+ }
+ }
+
+ misc:
+ return "application/misc";
+}
diff --git a/src/http/mime.h b/src/http/mime.h
new file mode 100644
index 0000000..c06c9b4
--- /dev/null
+++ b/src/http/mime.h
@@ -0,0 +1,26 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+const char *guess_mime_type(const char *str);
diff --git a/src/http/path.c b/src/http/path.c
new file mode 100644
index 0000000..257ddaf
--- /dev/null
+++ b/src/http/path.c
@@ -0,0 +1,183 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#include
+#include
+#include
+#include
+
+#include "../tools.h"
+
+#include "path.h"
+
+
+char *simplify_request_path(const char *str) {
+ // Based on Lighttpd sources:
+ // - https://github.com/lighttpd/lighttpd1.4/blob/b31e7840d5403bc640579135b7004793b9ccd6c0/src/buffer.c#L840
+ // - https://github.com/lighttpd/lighttpd1.4/blob/77c01f981725512653c01cde5ca74c11633dfec4/src/t/test_buffer.c
+
+ char ch; // Current character
+ char pre1; // The one before
+ char pre2; // The one before that
+ char *simplified;
+ char *start;
+ char *out;
+ char *slash;
+
+ A_CALLOC(simplified, strlen(str) + 1);
+
+ if (str[0] == '\0') {
+ simplified[0] = '\0';
+ return simplified;
+ }
+
+ start = simplified;
+ out = simplified;
+ slash = simplified;
+
+ // Skip leading spaces
+ for (; *str == ' '; ++str);
+
+ if (*str == '.') {
+ if (str[1] == '/' || str[1] == '\0') {
+ ++str;
+ } else if (str[1] == '.' && (str[2] == '/' || str[2] == '\0')) {
+ str += 2;
+ }
+ }
+
+ pre1 = '\0';
+ ch = *(str++);
+
+ while (ch != '\0') {
+ pre2 = pre1;
+ pre1 = ch;
+
+ // Possibly: out == str - need to read first
+ ch = *str;
+ *out = pre1;
+
+ out++;
+ str++;
+ // (out <= str) still true; also now (slash < out)
+
+ if (ch == '/' || ch == '\0') {
+ size_t toklen = out - slash;
+
+ if (toklen == 3 && pre2 == '.' && pre1 == '.' && *slash == '/') {
+ // "/../" or ("/.." at end of string)
+ out = slash;
+ // If there is something before "/..", there is at least one
+ // component, which needs to be removed
+ if (out > start) {
+ --out;
+ for (; out > start && *out != '/'; --out);
+ }
+
+ // Don't kill trailing '/' at end of path
+ if (ch == '\0') {
+ ++out;
+ }
+ // slash < out before, so out_new <= slash + 1 <= out_before <= str
+ } else if (toklen == 1 || (pre2 == '/' && pre1 == '.')) {
+ // "//" or "/./" or (("/" or "/.") at end of string)
+ out = slash;
+ // Don't kill trailing '/' at end of path
+ if (ch == '\0') {
+ ++out;
+ }
+ // Slash < out before, so out_new <= slash + 1 <= out_before <= str
+ }
+
+ slash = out;
+ }
+ }
+
+ *out = '\0';
+ return simplified;
+}
+
+#if 0
+
+int test_simplify_request_path(const char *sample, const char *expected) {
+ char *result = simplify_request_path(sample);
+ int retval = -!!strcmp(result, expected);
+
+ printf("Testing '%s' -> '%s' ... ", sample, expected);
+ if (retval == 0) {
+ printf("ok\n");
+ } else {
+ printf("FAILED; got '%s'\n", result);
+ }
+ free(result);
+ return retval;
+}
+
+int main(void) {
+ int retval = 0;
+
+# define TEST_SIMPLIFY_REQUEST_PATH(_sample, _expected) { \
+ retval += test_simplify_request_path(_sample, _expected); \
+ }
+
+ TEST_SIMPLIFY_REQUEST_PATH("", "");
+ TEST_SIMPLIFY_REQUEST_PATH(" ", "");
+ TEST_SIMPLIFY_REQUEST_PATH("/", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("//", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("abc", "abc");
+ TEST_SIMPLIFY_REQUEST_PATH("abc//", "abc/");
+ TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz", "abc/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("abc/.//xyz", "abc/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz", "/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz", "/abc/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("/abc//./xyz", "/abc/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz", "/xyz");
+ TEST_SIMPLIFY_REQUEST_PATH("abc/../xyz/.", "/xyz/");
+ TEST_SIMPLIFY_REQUEST_PATH("/abc/../xyz/.", "/xyz/");
+ TEST_SIMPLIFY_REQUEST_PATH("abc/./xyz/..", "abc/");
+ TEST_SIMPLIFY_REQUEST_PATH("/abc/./xyz/..", "/abc/");
+ TEST_SIMPLIFY_REQUEST_PATH(".", "");
+ TEST_SIMPLIFY_REQUEST_PATH("..", "");
+ TEST_SIMPLIFY_REQUEST_PATH("...", "...");
+ TEST_SIMPLIFY_REQUEST_PATH("....", "....");
+ TEST_SIMPLIFY_REQUEST_PATH(".../", ".../");
+ TEST_SIMPLIFY_REQUEST_PATH("./xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH(".//xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("/./xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH(".././xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("/.././xyz/..", "/");
+ TEST_SIMPLIFY_REQUEST_PATH("../../../etc/passwd", "/etc/passwd");
+ TEST_SIMPLIFY_REQUEST_PATH("/../../../etc/passwd", "/etc/passwd");
+ TEST_SIMPLIFY_REQUEST_PATH(" ../../../etc/passwd", "/etc/passwd");
+ TEST_SIMPLIFY_REQUEST_PATH(" /../../../etc/passwd", "/etc/passwd");
+ TEST_SIMPLIFY_REQUEST_PATH(" /foo/bar/../../../etc/passwd", "/etc/passwd");
+
+# undef TEST_SIMPLIFY_REQUEST_PATH
+
+ if (retval < 0) {
+ printf("===== TEST FAILED =====\n");
+ }
+ return retval;
+}
+
+#endif
diff --git a/src/http/path.h b/src/http/path.h
new file mode 100644
index 0000000..29dc4c2
--- /dev/null
+++ b/src/http/path.h
@@ -0,0 +1,26 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+char *simplify_request_path(const char *str);
diff --git a/src/http/server.c b/src/http/server.c
index 1d12e72..d3cbea2 100644
--- a/src/http/server.c
+++ b/src/http/server.c
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include
#include
@@ -52,6 +53,8 @@
#include "../stream.h"
#include "base64.h"
+#include "mime.h"
+#include "static.h"
#include "server.h"
#include "data/index_html.h"
@@ -325,6 +328,9 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
struct evhttp_uri *uri = NULL;
char *uri_path;
char *decoded_path = NULL;
+ char *static_path = NULL;
+ int fd = -1;
+ struct stat st;
PREPROCESS_REQUEST;
@@ -340,7 +346,26 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
}
assert((buf = evbuffer_new()));
- assert(evbuffer_add_printf(buf, "|%s|", decoded_path)); // TODO
+
+ if ((static_path = find_static_file_path(server->static_path, decoded_path)) == NULL) {
+ goto not_found;
+ }
+
+ if ((fd = open(static_path, O_RDONLY)) < 0) {
+ LOG_PERROR("HTTP: Can't open found static file %s", static_path);
+ goto not_found;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ LOG_PERROR("HTTP: Can't stat() found static file %s", static_path);
+ goto not_found;
+ }
+
+ if (evbuffer_add_file(buf, fd, 0, st.st_size) < 0) {
+ LOG_ERROR("HTTP: Can't serve static file %s", static_path);
+ goto not_found;
+ }
+ ADD_HEADER("Content-Type", guess_mime_type(static_path));
evhttp_send_reply(request, HTTP_OK, "OK", buf);
goto cleanup;
@@ -348,7 +373,17 @@ static void _http_callback_static(struct evhttp_request *request, void *v_server
evhttp_send_error(request, HTTP_BADREQUEST, NULL);
goto cleanup;
+ not_found:
+ evhttp_send_error(request, HTTP_NOTFOUND, NULL);
+ goto cleanup;
+
cleanup:
+ if (fd >= 0) {
+ close(fd);
+ }
+ if (static_path) {
+ free(static_path);
+ }
if (buf) {
evbuffer_free(buf);
}
diff --git a/src/http/static.c b/src/http/static.c
new file mode 100644
index 0000000..4da6007
--- /dev/null
+++ b/src/http/static.c
@@ -0,0 +1,88 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include "../tools.h"
+#include "../logging.h"
+
+#include "path.h"
+#include "static.h"
+
+
+char *find_static_file_path(const char *root_path, const char *request_path) {
+ char *simplified_path;
+ char *path = NULL;
+ struct stat st;
+
+ simplified_path = simplify_request_path(request_path);
+ if (simplified_path[0] == '\0') {
+ goto error;
+ }
+
+ A_CALLOC(path, strlen(root_path) + strlen(simplified_path) + 32);
+ sprintf(path, "%s/%s", root_path, simplified_path);
+
+# define LOAD_STAT { \
+ if (lstat(path, &st) < 0) { \
+ /* LOG_PERROR("Can't stat() file %s", path); */ \
+ goto error; \
+ } \
+ }
+
+ LOAD_STAT;
+ if (S_ISDIR(st.st_mode)) {
+ strcat(path, "/index.html");
+ LOAD_STAT;
+ }
+
+# undef LOAD_STAT
+
+ if (!S_ISREG(st.st_mode)) {
+ // LOG_ERROR("Not a regulary file: %s", path);
+ goto error;
+ }
+
+ if (access(path, R_OK) < 0) {
+ // LOG_PERROR("Can't access() R_OK file %s", path);
+ goto error;
+ }
+
+ goto ok;
+
+ error:
+ if (path) {
+ free(path);
+ }
+ path = NULL;
+
+ ok:
+ free(simplified_path);
+
+ return path;
+}
diff --git a/src/http/static.h b/src/http/static.h
new file mode 100644
index 0000000..e493f36
--- /dev/null
+++ b/src/http/static.h
@@ -0,0 +1,26 @@
+/*****************************************************************************
+# #
+# uStreamer - Lightweight and fast MJPG-HTTP streamer. #
+# #
+# Copyright (C) 2018 Maxim Devaev #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see . #
+# #
+*****************************************************************************/
+
+
+#pragma once
+
+
+char *find_static_file_path(const char *root_path, const char *request_path);
diff --git a/src/main.c b/src/main.c
index 97823b0..6ef453e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -164,8 +164,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" -o|--unix-mode ────── Set UNIX socket file permissions (like 777). Default: disabled\n\n");
printf(" --user ────────────── HTTP basic auth user. Default: disabled\n\n");
printf(" --passwd ───────────── HTTP basic auth passwd. Default: empty\n\n");
- printf(" --static ───────────── Serve static files instead of embedded root index page.\n");
- printf(" Default: disabled.\n\n");
+ printf(" --static ───────────── Path to dir with static files instead of embedded root index page.\n");
+ printf(" Symlinks are not supported for security reasons. Default: disabled.\n\n");
printf(" -e|--drop-same-frames ── Don't send same frames to clients, but no more than specified number.\n");
printf(" It can significantly reduce the outgoing traffic, but will increase\n");
printf(" the CPU loading. Don't use this option with analog signal sources\n");