diff --git a/README.md b/README.md
index 5467c3f..5ae5ccb 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@
| 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, auth settings |  No 3 |  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.
diff --git a/README.ru.md b/README.ru.md
index 103e0d2..f2f119d 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -16,7 +16,7 @@
| Стрим через UNIX domain socket |  Есть |  Нет |
| Дебаг-логи без перекомпиляции,
логгирование статистики производительности,
возможность получения параметров
трансляции по HTTP |  Есть |  Нет |
| Поддержка контролов веб-камер (фокус,
движение сервами) и всяких настроек,
типа яркости, через HTTP |  Нет |  Есть |
-| Возможность сервить файлы встроенным
HTTP-сервером, настройки авторизации |  Нет 3 |  Есть |
+| Возможность сервить файлы встроенным
HTTP-сервером |  Нет 3 |  Есть |
Сносочки:
* ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси.
diff --git a/src/base64.c b/src/base64.c
new file mode 100644
index 0000000..f9a299c
--- /dev/null
+++ b/src/base64.c
@@ -0,0 +1,71 @@
+/*****************************************************************************
+# #
+# 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 "tools.h"
+#include "base64.h"
+
+
+static const char ENCODING_TABLE[] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
+ 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
+ 'w', 'x', 'y', 'z', '0', '1', '2', '3',
+ '4', '5', '6', '7', '8', '9', '+', '/',
+};
+
+static const unsigned MOD_TABLE[] = {0, 2, 1};
+
+
+char *base64_encode(const unsigned char *str) {
+ size_t str_len = strlen((const char *)str);
+ size_t encoded_size = 4 * ((str_len + 2) / 3) + 1; // +1 for '\0'
+ char *encoded;
+
+ A_CALLOC(encoded, encoded_size);
+
+ for (unsigned str_index = 0, encoded_index = 0; str_index < str_len;) {
+ unsigned octet_a = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
+ unsigned octet_b = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
+ unsigned octet_c = (str_index < str_len ? (unsigned char)str[str_index++] : 0);
+
+ unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
+
+ encoded[encoded_index++] = ENCODING_TABLE[(triple >> 3 * 6) & 0x3F];
+ encoded[encoded_index++] = ENCODING_TABLE[(triple >> 2 * 6) & 0x3F];
+ encoded[encoded_index++] = ENCODING_TABLE[(triple >> 1 * 6) & 0x3F];
+ encoded[encoded_index++] = ENCODING_TABLE[(triple >> 0 * 6) & 0x3F];
+ }
+
+ for (unsigned index = 0; index < MOD_TABLE[str_len % 3]; index++) {
+ encoded[encoded_size - 2 - index] = '=';
+ }
+
+ encoded[encoded_size - 1] = '\0';
+ return encoded;
+}
diff --git a/src/base64.h b/src/base64.h
new file mode 100644
index 0000000..531e5df
--- /dev/null
+++ b/src/base64.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 . #
+# #
+*****************************************************************************/
+
+
+#include
+
+
+char *base64_encode(const unsigned char *str);
diff --git a/src/http.c b/src/http.c
index 9f391eb..ee7f403 100644
--- a/src/http.c
+++ b/src/http.c
@@ -50,6 +50,7 @@
#include "logging.h"
#include "encoder.h"
#include "stream.h"
+#include "base64.h"
#include "http.h"
#include "data/index_html.h"
@@ -58,6 +59,7 @@
static bool _http_get_param_true(struct evkeyvalq *params, const char *key);
static char *_http_get_param_uri(struct evkeyvalq *params, const char *key);
+static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server);
static void _http_callback_root(struct evhttp_request *request, void *arg);
static void _http_callback_state(struct evhttp_request *request, void *v_server);
@@ -99,7 +101,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) {
assert((run->http = evhttp_new(run->base)));
evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD);
- assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL));
+ assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void *)server));
assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server));
assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server));
assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server));
@@ -128,6 +130,10 @@ void http_server_destroy(struct http_server_t *server) {
client = next;
}
+ if (server->run->auth_token) {
+ free(server->run->auth_token);
+ }
+
free(server->run->exposed->picture.data);
free(server->run->exposed);
free(server->run);
@@ -135,16 +141,18 @@ void http_server_destroy(struct http_server_t *server) {
}
int http_server_listen(struct http_server_t *server) {
- struct timeval refresh_interval;
+ {
+ struct timeval refresh_interval;
- refresh_interval.tv_sec = 0;
- if (server->run->stream->dev->desired_fps > 0) {
- refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
- } else {
- refresh_interval.tv_usec = 16000; // ~60fps
+ refresh_interval.tv_sec = 0;
+ if (server->run->stream->dev->desired_fps > 0) {
+ refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2);
+ } else {
+ refresh_interval.tv_usec = 16000; // ~60fps
+ }
+ assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
+ assert(!event_add(server->run->refresh, &refresh_interval));
}
- assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server)));
- assert(!event_add(server->run->refresh, &refresh_interval));
server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank);
@@ -154,6 +162,23 @@ int http_server_listen(struct http_server_t *server) {
evhttp_set_timeout(server->run->http, server->timeout);
+ if (server->user != NULL && strlen(server->user) > 0) {
+ char *passwd = (server->passwd != NULL ? server->passwd : "");
+ char *raw_token;
+ char *encoded_token;
+
+ A_CALLOC(raw_token, strlen(server->user) + strlen(passwd) + 2);
+ sprintf(raw_token, "%s:%s", server->user, passwd);
+ encoded_token = base64_encode((unsigned char *)raw_token);
+ free(raw_token);
+
+ A_CALLOC(server->run->auth_token, strlen(encoded_token) + 16);
+ sprintf(server->run->auth_token, "Basic %s", encoded_token);
+ free(encoded_token);
+
+ LOG_INFO("Using HTTP basic auth");
+ }
+
if (server->unix_path) {
struct sockaddr_un unix_addr;
int unix_fd_flags;
@@ -240,17 +265,36 @@ static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) {
#define ADD_HEADER(_key, _value) \
assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value))
-#define PROCESS_HEAD_REQUEST { \
- if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
- evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
+static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server) {
+ if (server->run->auth_token) {
+ const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization");
+
+ if (token == NULL || strcmp(token, server->run->auth_token) != 0) {
+ ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\"");
+ evhttp_send_reply(request, 401, "Unauthorized", NULL);
+ return -1;
+ }
+ }
+
+ if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \
+ evhttp_send_reply(request, HTTP_OK, "OK", NULL); \
+ return -1;
+ }
+
+ return 0;
+}
+
+#define PREPROCESS_REQUEST { \
+ if (_http_preprocess_request(request, server) < 0) { \
return; \
} \
}
-static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) {
+static void _http_callback_root(struct evhttp_request *request, UNUSED void *v_server) {
+ struct http_server_t *server = (struct http_server_t *)v_server;
struct evbuffer *buf;
- PROCESS_HEAD_REQUEST;
+ PREPROCESS_REQUEST;
assert((buf = evbuffer_new()));
assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE));
@@ -265,7 +309,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server)
enum encoder_type_t encoder_run_type;
unsigned encoder_run_quality;
- PROCESS_HEAD_REQUEST;
+ PREPROCESS_REQUEST;
# define ENCODER(_next) server->run->stream->encoder->_next
@@ -319,7 +363,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv
struct evbuffer *buf;
char header_buf[64];
- PROCESS_HEAD_REQUEST;
+ PREPROCESS_REQUEST;
# define EXPOSED(_next) server->run->exposed->_next
@@ -384,7 +428,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
unsigned short client_port;
uuid_t uuid;
- PROCESS_HEAD_REQUEST;
+ PREPROCESS_REQUEST;
conn = evhttp_request_get_connection(request);
if (conn != NULL) {
@@ -431,7 +475,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server
}
}
-#undef PROCESS_HEAD_REQUEST
+#undef PREPROCESS_REQUEST
static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) {
# define BOUNDARY "boundarydonotcross"
diff --git a/src/http.h b/src/http.h
index 2ea5bad..c3d8a66 100644
--- a/src/http.h
+++ b/src/http.h
@@ -70,6 +70,7 @@ struct http_server_runtime_t {
struct event_base *base;
struct evhttp *http;
evutil_socket_t unix_fd;
+ char *auth_token;
struct event *refresh;
struct stream_t *stream;
struct exposed_t *exposed;
@@ -84,6 +85,8 @@ struct http_server_t {
char *unix_path;
bool unix_rm;
mode_t unix_mode;
+ char *user;
+ char *passwd;
unsigned drop_same_frames;
bool slowdown;
unsigned fake_width;
diff --git a/src/main.c b/src/main.c
index 3077a47..6cd7a20 100644
--- a/src/main.c
+++ b/src/main.c
@@ -79,11 +79,13 @@ static const struct option _LONG_OPTS[] = {
{"unix", required_argument, NULL, 'u'},
{"unix-rm", no_argument, NULL, 'r'},
{"unix-mode", required_argument, NULL, 'o'},
+ {"user", required_argument, NULL, 3000},
+ {"passwd", required_argument, NULL, 3001},
{"drop-same-frames", required_argument, NULL, 'e'},
{"slowdown", no_argument, NULL, 'l'},
- {"fake-width", required_argument, NULL, 3001},
- {"fake-height", required_argument, NULL, 3002},
- {"server-timeout", required_argument, NULL, 3003},
+ {"fake-width", required_argument, NULL, 3002},
+ {"fake-height", required_argument, NULL, 3003},
+ {"server-timeout", required_argument, NULL, 3004},
{"perf", no_argument, NULL, 5000},
{"verbose", no_argument, NULL, 5001},
@@ -159,6 +161,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s
printf(" -u|--unix ─────────── Bind to UNIX domain socket. Default: disabled\n\n");
printf(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled\n\n");
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(" -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");
@@ -274,11 +278,13 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e
case 'u': OPT_SET(server->unix_path, optarg);
case 'r': OPT_SET(server->unix_rm, true);
case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode");
+ case 3000: OPT_SET(server->user, optarg);
+ case 3001: OPT_SET(server->passwd, optarg);
case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30);
case 'l': OPT_SET(server->slowdown, true);
- case 3001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
- case 3002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
- case 3003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
+ case 3002: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920);
+ case 3003: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200);
+ case 3004: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60);
case 5000: OPT_SET(log_level, LOG_LEVEL_PERF);
case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);