diff --git a/modules/luci-base/ucode/http.uc b/modules/luci-base/ucode/http.uc index e7f64ae6e9..350bcc5eef 100644 --- a/modules/luci-base/ucode/http.uc +++ b/modules/luci-base/ucode/http.uc @@ -13,6 +13,12 @@ import { stdin, stdout, mkstemp } from 'fs'; +import { + openlog, syslog, closelog, LOG_NOTICE, LOG_LOCAL0 +} from 'log'; + +import { run_plugins } from 'luciplugins'; + // luci.http module scope export let HTTP_MAX_CONTENT = 1024*100; // 100 kB maximum content size @@ -504,6 +510,37 @@ const Class = { if (!this.headers?.['x-content-type-options']) this.header('X-Content-Type-Options', 'nosniff'); + /* http header plugins */ + let log_class = 'http.uc'; + openlog(log_class); + for (let plugin_id, p_output in run_plugins('/luci/plugins/http/headers', 'http_headers_enabled')) { + + /* header plugins shall return e.g.: ['X-Header', 'foo'] */ + if (type(p_output) !== 'array' || length(p_output) !== 2) + continue; + + if (type(p_output[0]) !== 'string' || type(p_output[1]) !== 'string') + continue; + + if (!match(p_output[0], /^[A-Za-z0-9-]+$/)) { + syslog(LOG_NOTICE|LOG_LOCAL0, + sprintf("Invalid header name from plugin %s output: %s", plugin_id, p_output[0])); + continue; + } + + /* header plugin values shall not contain line-feeds */ + if (match(p_output[1], /[\r\n]/)) { + syslog(LOG_NOTICE|LOG_LOCAL0, + sprintf("\\r and/or \\n in plugin %s output", plugin_id)); + continue; + } + + if(!this.headers?.[p_output[0]]) + this.header(p_output[0], p_output[1]); + + } + closelog(); + this.output('Status: '); this.output(this.status_code); this.output(' '); diff --git a/modules/luci-base/ucode/luciplugins.uc b/modules/luci-base/ucode/luciplugins.uc new file mode 100644 index 0000000000..364c839b04 --- /dev/null +++ b/modules/luci-base/ucode/luciplugins.uc @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 + +import { + lsdir +} from 'fs'; + +import { + syslog, LOG_NOTICE, LOG_LOCAL0 +} from 'log'; + +import { cursor } from 'uci'; + + +/* generic plugin handler */ +export function run_plugins(plugin_class_path, plugin_class_enable) { + let uci = cursor(); + const require_path = replace(plugin_class_path, '/', '.'); + + if (uci.get('luci_plugins', 'global', 'enabled') == 1 && + uci.get('luci_plugins', 'global', plugin_class_enable) == 1) { + const PLUGINS_PATH = '/usr/share/ucode' + plugin_class_path; + const results = {}; + + for (let fn in lsdir(PLUGINS_PATH)) { + const plugin_id = replace(fn, /.uc$/, ''); + /* plugins shall have a <32_char_UUID_no_hyphens>.uc filename */ + if (!match(plugin_id, /^[a-f0-9]+$/) || length(plugin_id) !== 32) { + syslog(LOG_NOTICE|LOG_LOCAL0, + sprintf("Invalid plugin name: %s", plugin_id)); + continue; + } + + if (uci.get('luci_plugins', plugin_id, 'enabled')) { + const mod = require(require_path + `.${plugin_id}`); + if (type(mod) === 'function') { + try { + results[plugin_id] = mod(plugin_id); + } catch (e) { + syslog(LOG_NOTICE|LOG_LOCAL0, + sprintf("Could not execute plugin %s: %s", + join('/', [PLUGINS_PATH, plugin_id]), e)); + }; + } + } + } + + return results; + } +};