Fix string interpolation and platform where 'ip' command (from iproute2) is unavailable.#4734
Conversation
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.
|
@f1rmb |
…tion (space indent now).
Okay. Done. I had to reindent the whole function, as the code is (and still) a mix of tab/space indentation. |
|
Yes, there is such a problem (spaces/tabs). |
Your formatting uses 4 spaces, but 99% of the code uses 2 spaces. |
|
Well, tell me, I could reformat de whole code. But later, I don't have much time today.
…---
73 de Daniel, F1RMB
-------- Original Message --------
On Sunday, 04/12/26 at 12:39 IgorA100 ***@***.***> wrote:
IgorA100 left a comment [(ZoneMinder/zoneminder#4734)](#4734 (comment))
> 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.
—
Reply to this email directly, [view it on GitHub](#4734 (comment)), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/ACP5DKB2VJGHF3P7C3V7YN34VNW5VAVCNFSM6AAAAACXVG6HJCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DEMZRGMYTSMRWGI).
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
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. |
|
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. |
|
Old school here too, I use Emacs ;-)
…---
73 de Daniel, F1RMB
-------- Original Message --------
On Sunday, 04/12/26 at 14:29 Isaac Connor ***@***.***> wrote:
connortechnology left a comment [(ZoneMinder/zoneminder#4734)](#4734 (comment))
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.
—
Reply to this email directly, [view it on GitHub](#4734 (comment)), or [unsubscribe](https://github.com/notifications/unsubscribe-auth/ACP5DKFDL2BIG5VIMOOM4C34VODZRAVCNFSM6AAAAACXVG6HJCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DEMZRGUYDINZZGY).
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
| Please run the following command from a command line for more information: | ||
| $shell_command" | ||
| ); | ||
| ); |
There was a problem hiding this comment.
All these whitespace changes should not be in this PR.
60def0c to
8f3c9f6
Compare
8f3c9f6 to
d627ca2
Compare
There was a problem hiding this comment.
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.phpwith concatenation. - Make ONVIF probe interface selection resilient when no default interface is returned.
- Rework
get_networks()to support anifconfig-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.
| 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]; | ||
| } |
There was a problem hiding this comment.
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).
| 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]; |
There was a problem hiding this comment.
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.
| $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)) |
There was a problem hiding this comment.
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.
| (strpos($matches[3], "LOOPBACK") == false) && (strpos($matches[3], "RUNNING") != false)) | |
| (strpos($matches[3], "LOOPBACK") === false) && (strpos($matches[3], "RUNNING") !== false)) |
| 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]; |
There was a problem hiding this comment.
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).
| 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; | |
| } |
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