diff --git a/conf.c b/conf.c index e9a58f9f7..4f38a17e1 100644 --- a/conf.c +++ b/conf.c @@ -85,6 +85,7 @@ struct config conf_template = { .stream_localhost = 1, .stream_limit = 0, .stream_auth_method = 0, + .stream_cors_header = NULL, .stream_authentication = NULL, .stream_preview_scale = 25, .stream_preview_newline = 0, @@ -1271,6 +1272,16 @@ config_param config_params[] = { WEBUI_LEVEL_RESTRICTED }, { + "stream_cors_header", + "# Set the cross-origin resource sharing (CORS) header\n" + "# Default: not defined (Disabled)", + 0, + CONF_OFFSET(stream_cors_header), + copy_uri, + print_string, + WEBUI_LEVEL_RESTRICTED + }, + { "stream_authentication", "# Authentication for the stream. Syntax username:password\n" "# Default: not defined (Disabled)", @@ -2263,6 +2274,7 @@ void conf_output_parms(struct context **cnt) if (!strncmp(name, "netcam_url", 10) || !strncmp(name, "netcam_userpass", 15) || !strncmp(name, "netcam_highres", 14) || + !strncmp(name, "stream_cors_header", 18) || !strncmp(name, "stream_authentication", 21) || !strncmp(name, "webcontrol_authentication", 25) || !strncmp(name, "database_user", 13) || @@ -2300,7 +2312,8 @@ void malloc_strings(struct context *cnt) unsigned int i = 0; char **val; while (config_params[i].param_name != NULL) { - if (config_params[i].copy == copy_string) { /* if member is a string */ + if (config_params[i].copy == copy_string || + config_params[i].copy == copy_uri) { /* if member is a string */ /* val is made to point to a pointer to the current string. */ val = (char **)((char *)cnt+config_params[i].conf_value); @@ -2505,6 +2518,31 @@ struct context **copy_vid_ctrl(struct context **cnt, const char *config_val, int return cnt; } +struct context **copy_uri(struct context **cnt, const char *str, int val) { + + // Here's a complicated regex I found here: https://stackoverflow.com/questions/38608116/how-to-check-a-specified-string-is-a-valid-url-or-not-using-c-code + // Use it for validating URIs. + const char *regex_str = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"; + + regex_t regex; + if (regcomp(®ex, regex_str, REG_EXTENDED) != 0) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, "Error compiling regex in copy_uri"); + return cnt; + } + + // A single asterisk is also valid, so check for that. + if (strcmp(str, "*") != 0 && regexec(®ex, str, 0, NULL, 0) == REG_NOMATCH) { + MOTION_LOG(ERR, TYPE_STREAM, NO_ERRNO, "Invalid origin for cors_header in copy_uri"); + regfree(®ex); + return cnt; + } + + regfree(®ex); + cnt = copy_string(cnt, str, val); + return cnt; + +} + /** * mystrcpy * Is used to assign string type fields (e.g. config options) @@ -2586,6 +2624,8 @@ const char *config_type(config_param *configparam) return "int"; if (configparam->copy == copy_bool) return "bool"; + if (configparam->copy == copy_uri) + return "uri"; return "unknown"; } diff --git a/conf.h b/conf.h index 1eddda579..5b39b9b6a 100644 --- a/conf.h +++ b/conf.h @@ -74,6 +74,7 @@ struct config { int stream_localhost; int stream_limit; int stream_auth_method; + const char *stream_cors_header; const char *stream_authentication; int stream_preview_scale; int stream_preview_newline; @@ -183,6 +184,7 @@ extern dep_config_param dep_config_params[]; struct context **conf_load(struct context **); struct context **copy_string(struct context **, const char *, int); +struct context **copy_uri(struct context **, const char *, int); struct context **conf_cmdparse(struct context **, const char *, const char *); void conf_output_parms(struct context **cnt); const char *config_type(config_param *); diff --git a/motion.1 b/motion.1 index 953fbef91..05c917edf 100644 --- a/motion.1 +++ b/motion.1 @@ -1512,6 +1512,22 @@ The authentication method to use for viewing the stream. .RE .RE +.TP +.B stream_cors_header +.RS +.nf +Values: User specified string +Default: Not defined +Description: +.fi +.RS +The Access-Control-Allow-Origin header value to be sent with the stream. +If unspecified, no Access-Control-Allow-Origin header is sent. +The header allows browsers to access the stream via cross-origin resource sharing (CORS). +For example, * allows access from browser client code served from any domain. +.RE +.RE + .TP .B stream_authentication .RS diff --git a/motion.c b/motion.c index d7cb6abaa..5f6b0efb1 100644 --- a/motion.c +++ b/motion.c @@ -278,7 +278,8 @@ static void context_destroy(struct context *cnt) /* Free memory allocated for config parameters */ for (j = 0; config_params[j].param_name != NULL; j++) { - if (config_params[j].copy == copy_string) { + if (config_params[j].copy == copy_string || + config_params[j].copy == copy_uri) { void **val; val = (void *)((char *)cnt+(int)config_params[j].conf_value); if (*val) { @@ -1195,7 +1196,7 @@ static int motion_init(struct context *cnt) /* Initialize stream server if stream port is specified to not 0 */ if (cnt->conf.stream_port) { if (stream_init (&(cnt->stream), cnt->conf.stream_port, cnt->conf.stream_localhost, - cnt->conf.ipv6_enabled) == -1) { + cnt->conf.ipv6_enabled, cnt->conf.stream_cors_header) == -1) { MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-stream server in port %d", cnt->conf.stream_port); cnt->conf.stream_port = 0; @@ -1212,7 +1213,7 @@ static int motion_init(struct context *cnt) if ((cnt->conf.width / 2) % 8 == 0 && (cnt->conf.height / 2) % 8 == 0 && cnt->imgs.type == VIDEO_PALETTE_YUV420P){ if (stream_init (&(cnt->substream), cnt->conf.substream_port, cnt->conf.stream_localhost, - cnt->conf.ipv6_enabled) == -1) { + cnt->conf.ipv6_enabled, cnt->conf.stream_cors_header) == -1) { MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-substream server in port %d", cnt->conf.substream_port); cnt->conf.substream_port = 0; diff --git a/motion_config.html b/motion_config.html index 824aeb9ac..0075944d3 100644 --- a/motion_config.html +++ b/motion_config.html @@ -953,6 +953,12 @@

Configuration Options-Listed Alph stream_auth_method stream_auth_method + +
+ + + stream_cors_header +
stream_authentication @@ -1692,6 +1698,7 @@

Configuration Options-Listed by T webcontrol_parms + stream_cors_header @@ -4605,6 +4612,20 @@

stream_authentication

The syntax is username:password

+

stream_cors_header

+

+ +

+ The Access-Control-Allow-Origin header value to be sent with the stream. + If unspecified, no Access-Control-Allow-Origin header is sent. + The header allows browsers to access the stream via cross-origin resource sharing (CORS). + For example, * allows access from browser client code served from any origin. +

+

stream_preview_scale