Skip to content

Fix string interpolation and platform where 'ip' command (from iproute2) is unavailable.#4734

Open
f1rmb wants to merge 8 commits intoZoneMinder:masterfrom
f1rmb:fix_string_interpolation_deprecated_from_PHP_8_2_and_missing_ip_command_case
Open

Fix string interpolation and platform where 'ip' command (from iproute2) is unavailable.#4734
f1rmb wants to merge 8 commits intoZoneMinder:masterfrom
f1rmb:fix_string_interpolation_deprecated_from_PHP_8_2_and_missing_ip_command_case

Conversation

@f1rmb
Copy link
Copy Markdown
Contributor

@f1rmb f1rmb commented Apr 11, 2026

  • as from PHP 8.2, ${var} string interpolation is deprecated. the fix looks a bit hackish, but it should parse the correct syntax only.
  • on FreeBSD, the 'ip' command is not available. Also, the old code that parse the 'ifconfig' output looks a bit weird (probably really outdated). The new code works on Linux (6.19.11 here) and FreeBSD. Also, to get the default route interface, I've added a FreeBSD only code section. Later I will add a ZM_PATH_ROUTE in cmake / conf, for now the command is hardcoded, I will also add Linux support for default route detection when 'ip' is not available (unlikely). now get_network() works on FreeBSD too, interfaces array is no more empty.

…e2) is unavailable.

 - as from PHP 8.2, ${var} string interpolation is deprecated.
   the fix looks a bit hackish, but it should parse the correct syntax only.
 - on FreeBSD, the 'ip' command is not available. Also, the old code that parse
   the 'ifconfig' output looks a bit weird (probably really outdated). The new code
   works on Linux (6.19.11 here) and FreeBSD.
   Also, to get the default route interface, I've added a FreeBSD only code section.
   Later I will add a ZM_PATH_ROUTE in cmake / conf, for now the command is hardcoded, I
   will also add Linux support for default route detection when 'ip' is not available (unlikely).
   now get_network() works on FreeBSD too, interfaces array is no more empty.
Comment thread web/includes/functions.php Outdated
@IgorA100
Copy link
Copy Markdown
Contributor

@f1rmb
Please format the code (use spaces instead of tabs).

@f1rmb
Copy link
Copy Markdown
Contributor Author

f1rmb commented Apr 12, 2026

@f1rmb Please format the code (use spaces instead of tabs).

Okay. Done. I had to reindent the whole function, as the code is (and still) a mix of tab/space indentation.
get_network() is now space indented.

@IgorA100
Copy link
Copy Markdown
Contributor

Yes, there is such a problem (spaces/tabs).
We should try to replace all tabs with spaces, including in existing code.

@IgorA100
Copy link
Copy Markdown
Contributor

get_network() is now space indented.

Your formatting uses 4 spaces, but 99% of the code uses 2 spaces.
It's probably not critical (Isaac has the final say), but it's ugly.

@f1rmb
Copy link
Copy Markdown
Contributor Author

f1rmb commented Apr 12, 2026 via email

@connortechnology
Copy link
Copy Markdown
Member

Yes we use 2space formatting. In general we use the google style guidelines, with a few exceptions.

That being said, I don't feel a need to be pedantic about this. As long as the code you change is formatted correctly and doesn't undo existing good formatting I don't care.

All that being said, I think the entire issue is stupid and I wish the world would move on. Your editor should display whatever formatting you prefer and the stored code should have almost no unnecessary white space at all. But I still use vim so I'm part of the problem, and as I age I find that consistent styling, helps my brain parse the code. So here we are, 2spaces please.

@connortechnology
Copy link
Copy Markdown
Member

You may find it useful to setup git to auto-lint the code. We have an eslint ci setup to that checks js files, but you can set that up in git to do it before you even commit. Same for c++ code using cpplint etc. I find that very helpful.

Comment thread web/skins/classic/views/onvifprobe.php Outdated
@f1rmb
Copy link
Copy Markdown
Contributor Author

f1rmb commented Apr 12, 2026 via email

Comment thread web/skins/classic/views/onvifprobe.php Outdated
Please run the following command from a command line for more information:
$shell_command"
);
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these whitespace changes should not be in this PR.

@f1rmb f1rmb force-pushed the fix_string_interpolation_deprecated_from_PHP_8_2_and_missing_ip_command_case branch from 60def0c to 8f3c9f6 Compare April 14, 2026 13:12
@f1rmb f1rmb force-pushed the fix_string_interpolation_deprecated_from_PHP_8_2_and_missing_ip_command_case branch from 8f3c9f6 to d627ca2 Compare April 14, 2026 13:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates ONVIF probe UI string building for PHP 8.2 compatibility and reworks network interface detection to function on platforms where ip (iproute2) is unavailable (e.g., FreeBSD).

Changes:

  • Replace deprecated PHP string interpolation in onvifprobe.php with concatenation.
  • Make ONVIF probe interface selection resilient when no default interface is returned.
  • Rework get_networks() to support an ifconfig-based fallback and add a FreeBSD-only default-route detection.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
web/skins/classic/views/onvifprobe.php Avoids deprecated interpolation and avoids assuming get_networks() always returns a default key.
web/includes/functions.php Updates get_networks() to enumerate interfaces via ip when present, otherwise via ifconfig, with a FreeBSD default-route lookup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web/includes/functions.php Outdated
Comment thread web/includes/functions.php Outdated
Comment on lines +2286 to +2312
exec('ifconfig', $output, $status);

if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:$html_output");
} else {
array_walk($output, function($value, $key) use (&$interfaces) {
$retval = preg_match("/^([A-Za-z0-9]*):\s+flags=([0-9]*)<([A-Z,]*)>.*/ims", $value, $matches);
ZM\Debug(print_r($matches, true));
if (($retval == 1) && (strlen($matches[3]) > 0) &&
(strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false))
{
$interfaces[$matches[1]] = $matches[1];
}
});

if (strcmp($osname, 'freebsd') == 0) {
// Get default route iface
exec("route get default | grep interface | awk '{print $2}'", $defaultIface, $status);

if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
if (isset($defaultIface) && isset($defaultIface[0])) {
$interfaces['default'] = $defaultIface[0];
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ifconfig fallback runs exec(ZM_PATH_IFCONFIG, ...) and then immediately runs exec('ifconfig', ...), overwriting $output. If ZM_PATH_IFCONFIG succeeds but ifconfig isn’t in the web server PATH (common for /sbin tools), this will incorrectly fail. Use ZM_PATH_IFCONFIG for the actual call (and remove the redundant first/second exec).

Suggested change
exec('ifconfig', $output, $status);
if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:$html_output");
} else {
array_walk($output, function($value, $key) use (&$interfaces) {
$retval = preg_match("/^([A-Za-z0-9]*):\s+flags=([0-9]*)<([A-Z,]*)>.*/ims", $value, $matches);
ZM\Debug(print_r($matches, true));
if (($retval == 1) && (strlen($matches[3]) > 0) &&
(strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false))
{
$interfaces[$matches[1]] = $matches[1];
}
});
if (strcmp($osname, 'freebsd') == 0) {
// Get default route iface
exec("route get default | grep interface | awk '{print $2}'", $defaultIface, $status);
if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
if (isset($defaultIface) && isset($defaultIface[0])) {
$interfaces['default'] = $defaultIface[0];
}
array_walk($output, function($value, $key) use (&$interfaces) {
$retval = preg_match("/^([A-Za-z0-9]*):\s+flags=([0-9]*)<([A-Z,]*)>.*/ims", $value, $matches);
ZM\Debug(print_r($matches, true));
if (($retval == 1) && (strlen($matches[3]) > 0) &&
(strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false))
{
$interfaces[$matches[1]] = $matches[1];
}
});
if (strcmp($osname, 'freebsd') == 0) {
// Get default route iface
exec("route get default | grep interface | awk '{print $2}'", $defaultIface, $status);
if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
if (isset($defaultIface) && isset($defaultIface[0])) {
$interfaces['default'] = $defaultIface[0];

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are actually security issues around just running things and expecting PATH to find them. Sometimes the ZM admin is NOT the system admin. So system admin sets ZM_PATH_IFCONFIG and ZM admin just has to live with it.

Comment thread web/includes/functions.php Outdated
$retval = preg_match("/^([A-Za-z0-9]*):\s+flags=([0-9]*)<([A-Z,]*)>.*/ims", $value, $matches);
ZM\Debug(print_r($matches, true));
if (($retval == 1) && (strlen($matches[3]) > 0) &&
(strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false))
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strpos checks against the flags string use loose comparisons (== false / != false), which mis-handle matches at position 0 (e.g., strpos(...) returning 0). Use strict comparisons (=== false and !== false) so LOOPBACK/RUNNING detection is correct in all cases.

Suggested change
(strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false))
(strpos($matches[3], "LOOPBACK") === false) && (strpos($matches[3], "RUNNING") !== false))

Copilot uses AI. Check for mistakes.
Comment thread web/includes/functions.php Outdated
Comment on lines +2304 to +2311
exec("route get default | grep interface | awk '{print $2}'", $defaultIface, $status);

if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
if (isset($defaultIface) && isset($defaultIface[0])) {
$interfaces['default'] = $defaultIface[0];
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the FreeBSD default-route block, the error log builds $html_output from $output (the ifconfig output), not from $defaultIface (the route get default output), so failures will report the wrong command output. Also, this uses a shell pipeline (route | grep | awk) and unqualified command names, which is brittle if the web server PATH doesn’t include these tools; prefer executing the route command directly and parsing in PHP (or using configured full paths).

Suggested change
exec("route get default | grep interface | awk '{print $2}'", $defaultIface, $status);
if ($status) {
$html_output = implode("\n", $output);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
if (isset($defaultIface) && isset($defaultIface[0])) {
$interfaces['default'] = $defaultIface[0];
$defaultIface = array();
$routeCommand = 'route';
if (is_executable('/sbin/route')) {
$routeCommand = '/sbin/route';
} else if (is_executable('/usr/sbin/route')) {
$routeCommand = '/usr/sbin/route';
}
exec($routeCommand.' get default', $defaultIface, $status);
if ($status) {
$html_output = implode("\n", $defaultIface);
ZM\Error("Unable to get default ip route, status is '$status'. Output was:$html_output");
} else {
foreach ($defaultIface as $routeLine) {
if (preg_match('/^\s*interface:\s*(\S+)/', $routeLine, $matches)) {
$interfaces['default'] = $matches[1];
break;
}

Copilot uses AI. Check for mistakes.
f1rmb and others added 4 commits April 14, 2026 16:47
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ted_from_PHP_8_2_and_missing_ip_command_case' into fix_string_interpolation_deprecated_from_PHP_8_2_and_missing_ip_command_case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants