luci-base: implement http header plugins

This implements the injection of custom http headers via
the new plugin architecture.

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
This commit is contained in:
Paul Donald
2026-03-15 19:18:33 +01:00
parent 617f364333
commit ba0051729a
2 changed files with 86 additions and 0 deletions

View File

@@ -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(' ');

View File

@@ -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;
}
};