diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm index 6cb55d3a6..10a37e102 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Dahua.pm @@ -39,6 +39,8 @@ sub AUTOLOAD my $class = ref($self) || croak( "$self not object" ); my $name = $AUTOLOAD; $name =~ s/.*://; + ## This seems odd... if the method existed would we even be here? + ## https://perldoc.perl.org/perlsub.html#Autoloading if ( exists($self->{$name}) ) { return( $self->{$name} ); @@ -46,9 +48,17 @@ sub AUTOLOAD Fatal( "Can't access $name member of object of class $class" ); } +# FIXME: Do we really have to open a new connection every time? + +#Digest usernbme="bdmin", reblm="Login to 4K05DB3PAJE98BE", nonae="1720242756", +#uri="/agi-bin/ptz.agi?bation=getStbtus&ahbnnel=1", response="10dd925b26ebd559353734635b859b8b", +#opbque="1a99677524b4ae63bbe3a132b2e9b38e3b163ebd", qop=buth, na=00000001, anonae="ab1bb5d43aa5d542" + sub open { + #Debug("&open invoked by: " . (caller(1))[3]); my $self = shift; + my $cgi = shift || '/cgi-bin/configManager.cgi?action=getConfig&name=Ptz'; $self->loadMonitor(); # The Dahua camera firmware API supports the concept of having multiple @@ -73,60 +83,55 @@ sub open } use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; + $self->{ua} = LWP::UserAgent->new(keep_alive => 1); $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); $self->{state} = 'closed'; # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) - Debug("sendCmd credentials control address:'".$ADDRESS - ."' realm:'" . $REALM - . "' username:'" . $USERNAME - . "' password:'".$PASSWORD - ."'" - ); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); # Detect REALM - my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz"; - my $req = HTTP::Request->new(GET=>$get_config_url); + my $url = $PROTOCOL . $ADDRESS . $cgi; + my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ($res->is_success) { $self->{state} = 'open'; - return; + return 1; } if ( $res->status_line() eq '401 Unauthorized' ) { my $headers = $res->headers(); - foreach my $k (keys %$headers) { - Debug("Initial Header $k => $$headers{$k}"); - } - if ($$headers{'www-authenticate'}) { my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; + Debug("Tokens: " . $tokens); + ## FIXME: This is necessary because the Dahua spec does not match reality if ($tokens =~ /\w+="([^"]+)"/i) { if ($REALM ne $1) { $REALM = $1; Debug("Changing REALM to '" . $REALM . "'"); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); - my $req = HTTP::Request->new(GET=>$get_config_url); + my $req = HTTP::Request->new(GET=>$url); $res = $self->{ua}->request($req); + if ($res->is_success()) { $self->{state} = 'open'; - return; + Debug('Authentication succeeded...'); + return 1; } Debug('Authentication still failed after updating REALM' . $res->status_line); $headers = $res->headers(); foreach my $k ( keys %$headers ) { Debug("Initial Header $k => $$headers{$k}"); } # end foreach - } else { - Error('Authentication failed, not a REALM problem'); + } else { ## NOTE: Each of these else conditions is fatal as the command will not be + ## executed. No use going further. + Fatal('Authentication failed: Check username and password.'); } } else { - Error('Failed to match realm in tokens'); + Fatal('Authentication failed: Incorrect realm.'); } # end if } else { - Error('No WWW-Authenticate Header'); + Fatal('Authentication failed: No www-authenticate header returned.'); } # end if headers } # end if $res->status_line() eq '401 Unauthorized' } @@ -146,45 +151,40 @@ sub printMsg Debug( $msg."[".$msg_len."]" ); } -sub sendGetRequest { +sub _sendGetRequest { my $self = shift; my $url_path = shift; - my $result = undef; + # Attempt to reuse the connection + + # FIXME: I think we need some sort of keepalive/heartbeat sent to the camera + # in order to keep the session alive. As it is, it appears that the + # ua's authentication times out or some such. + # + # This might be of some use: + # {"method":"global.keepAlive","params":{"timeout":300,"active":false},"id":1518,"session":"dae233a51c0693519395209b271411b6"}[!http] + # The web browser interface POSTs commands as JSON using js my $url = $PROTOCOL . $ADDRESS . $url_path; - my $req = HTTP::Request->new(GET=>$url); - + my $req = HTTP::Request->new(GET => $url); my $res = $self->{ua}->request($req); if ($res->is_success) { - $result = !undef; + return 1; } else { - if ($res->status_line() eq '401 Unauthorized') { - Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD); - Debug("Content was " . $res->content() ); - my $res = $self->{ua}->request($req); - if ($res->is_success) { - $result = !undef; - } else { - Error("Content was " . $res->content() ); - } - } - if ( ! $result ) { - Error("Error check failed: '".$res->status_line()); - } + return($self->open($url_path)); # if we have to, open a new connection } - return($result); } -sub sendPtzCommand +sub _sendPtzCommand { my $self = shift; my $action = shift; my $command_code = shift; - my $arg1 = shift; - my $arg2 = shift; - my $arg3 = shift; + my $arg1 = shift || 0; + my $arg2 = shift || 0; + my $arg3 = shift || 0; + my $arg4 = shift || 0; my $channel = $self->{dahua_channel_number}; @@ -194,10 +194,12 @@ sub sendPtzCommand $url_path .= "code=" . $command_code . "&"; $url_path .= "arg1=" . $arg1 . "&"; $url_path .= "arg2=" . $arg2 . "&"; - $url_path .= "arg3=" . $arg3; - $self->sendGetRequest($url_path); + $url_path .= "arg3=" . $arg3 . "&"; + $url_path .= "arg4=" . $arg4; + return $self->_sendGetRequest($url_path); } -sub sendMomentaryPtzCommand + +sub _sendMomentaryPtzCommand { my $self = shift; my $command_code = shift; @@ -206,92 +208,195 @@ sub sendMomentaryPtzCommand my $arg3 = shift; my $duration_ms = shift; - $self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); my $duration_ns = $duration_ms * 1000; usleep($duration_ns); - $self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); + $self->_sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); +} + +sub _sendAbsolutePositionCommand +{ + my $self = shift; + my $arg1 = shift; + my $arg2 = shift; + my $arg3 = shift; + my $arg4 = shift; + + $self->_sendPtzCommand("start", "PositionABS", $arg1, $arg2, $arg3, $arg4); +} + +sub moveConLeft +{ + my $self = shift; + Debug("Move Up Left"); + $self->_sendMomentaryPtzCommand("Left", 0, 1, 0, 0); +} + +sub moveConRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->_sendMomentaryPtzCommand("Right", 0, 1, 0, 0); +} + +sub moveConUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->_sendMomentaryPtzCommand("Up", 0, 1, 0, 0); +} + +sub moveConDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->_sendMomentaryPtzCommand("Down", 0, 1, 0, 0); +} + +sub moveConUpRight +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("RightUp", 1, 1, 0, 0); +} + +sub moveConDownRight +{ + my $self = shift; + Debug( "Move Diagonally Down Right" ); + $self->_sendMomentaryPtzCommand("RightDown", 1, 1, 0, 0); +} + +sub moveConUpLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Left" ); + $self->_sendMomentaryPtzCommand("LeftUp", 1, 1, 0, 0); +} + +sub moveConDownLeft +{ + my $self = shift; + Debug( "Move Diagonally Up Right" ); + $self->_sendMomentaryPtzCommand("LeftDown", 1, 1, 0, 0); +} + +sub zoomConTele +{ + my $self = shift; + Debug( "Zoom Tele" ); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 1, 0, 0); +} + +sub zoomConWide +{ + my $self = shift; + Debug( "Zoom Wide" ); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 1, 0, 0); } sub moveRelUpLeft { my $self = shift; Debug("Move Up Left"); - $self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); } sub moveRelUp { my $self = shift; Debug("Move Up"); - $self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Up", 0, 4, 0, 500); } sub moveRelUpRight { my $self = shift; Debug("Move Up Right"); - $self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); } sub moveRelLeft { my $self = shift; Debug("Move Left"); - $self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Left", 0, 4, 0, 500); } sub moveRelRight { my $self = shift; Debug("Move Right"); - $self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Right", 0, 4, 0, 500); } sub moveRelDownLeft { my $self = shift; Debug("Move Down Left"); - $self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); } sub moveRelDown { my $self = shift; Debug("Move Down"); - $self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500); + $self->_sendMomentaryPtzCommand("Down", 0, 4, 0, 500); } sub moveRelDownRight { my $self = shift; Debug("Move Down Right"); - $self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); + $self->_sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); } sub zoomRelTele { my $self = shift; Debug("Zoom Relative Tele"); - $self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); } sub zoomRelWide { my $self = shift; Debug("Zoom Relative Wide"); - $self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); + $self->_sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); } +sub focusRelNear +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusNear", 0, 1, 0, 0); + Debug("focusRelNear response: " . $response); +} + +sub focusRelFar +{ + my $self = shift; + + my $response = $self->_sendPtzCommand("start", "FocusFar", 0, 1, 0, 0); + Debug("focusRelFar response: " . $response); +} + +sub moveStop +{ + my $self = shift; + Debug( "Move Stop" ); + # The command does not matter here, just the stop... + $self->_sendPtzCommand("stop", "Up", 0, 0, 1, 0); +} sub presetClear { my $self = shift; my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); } - sub presetSet { my $self = shift; @@ -308,8 +413,8 @@ sub presetSet my $control_preset_row = $sth->fetchrow_hashref(); my $new_label_name = $control_preset_row->{'Label'}; - $self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); - $self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); + $self->_sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); } sub presetGoto @@ -318,12 +423,39 @@ sub presetGoto my $params = shift; my $preset_id = $self->getParam($params, 'preset'); - $self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); + $self->_sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); +} + +sub presetHome +{ + my $self = shift; + + $self->_sendAbsolutePositionCommand( 0, 0, 0, 1 ); +} + +sub reset +{ + my $self = shift; + Debug( "Camera Reset" ); + $self->_sendPtzCommand("Reset", 0, 0, 0, 0); +} + +sub reboot +{ + my $self = shift; + Debug( "Camera Reboot" ); + my $cmd = "cgi-bin/magicBox.cgi?action=reboot"; + $self->_sendGetRequest($cmd); } 1; + __END__ +=pod + +=encoding utf8 + =head1 NAME ZoneMinder::Control::Dahua - Perl module for Dahua cameras @@ -337,10 +469,6 @@ place this in /usr/share/perl5/ZoneMinder/Control This module is an implementation of the Dahua IP camera HTTP control API. -=head2 EXPORT - -None by default. - =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 ZoneMinder LLC @@ -359,4 +487,138 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +=head1 Private Methods + + Methods intended for use internally but documented here for future developers. + +=head2 _sendAbsolutePositionCommand( $arg1, $arg2, $arg3, $arg4 ) + + Where: + + $arg1 = Horizontal angle 0° to 360° + $arg2 = Vertical angle 0° to -90° + $arg3 = Zoom multiplier + $arg4 = Speed 1 to 8 + + This is an private method used to send an absolute position command to the + camera. + +=head1 Public Methods + + Methods made available to control.pl via ZoneMinder::Control + +=head2 Notes: + +=over 1 + + Which methods are invoked depends on which types of movement are selected in + the camera control type. For example: if the 'Can Move Continuous' option is + checked, then methods including 'Con' in their names are invoked. Likewise if + the 'Can Move Relative" option is checked, then methods including 'Rel' in + their names are invoked. + + + At present, these types of movement are prioritized and exclusive. This applies + to all types of movement, not just PTZ, but focus, iris, etc. as well. The options + are tested in the following order: + + 1. Continuous + + 2. Relative + + 3. Absolute + + These types are exclusive meaning that the first one that matches is the one + ZoneMinder will use to control with. It would be nice to allow the user to + select the type used given that some cameras support all three types of + movement. + +=back + +=head2 new + + This method instantiates a new control object based upon this control module + and sets the 'id' attribute to the value passed in. + +=head2 open + + This method opens an HTTP connection to the camera. It handles authentication, + etc. Upon success it sets the 'state' attribute to 'open.' + +=head2 close + + This method effectively closes the HTTP connection to the camera. It sets the + 'state' attribute to 'close.' + +=head2 printMsg + + This method appears to be used for debugging. + +=head2 moveCon + + This set of methods invoke continuous movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 1 for 0ms. The speed index of 1 is the lowest of the + accepted range of 1-8. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 focusCon + + This set of methods invoke continuous focus in the range direction indicated + by the portion of their name. They accept no arguments. + + NOTE: + + This is not true continuous movmement as currently implemented. + +=head2 moveRel + + This set of methods invoke relatvie movement in the direction indicated by + the portion of their name. They accept no arguments and move the + camera at a speed of 4 for 500ms. The speed index of 4 is half-way between + the accepted range of 1-8. + +=head2 focusRel + + This set of methods invoke realtive focus in the range direction indicated by + the portion of their name. They accept no arguments. + + NOTE: + + This only just does work. The Dahua API specifies "multiples" as the input. + We pass in a 1 for that as it does not seem to matter what number (0-8) is + provided, the camera focus behaves the same. + +=head2 moveStop + + This method attempts to stop the camera. The problem is that if continuous + motion is occurring in multiple directions, this will only stop the motion + in the 'Up' direction. Dahua does not support an "all-stop" command. + +=head2 presetHome + + This method "homes" the camera to a preset position. It accepts no arguments. + When either continuous or relative movement is enabled, pressing the center + button on the movement controls invokes this method. + + NOTE: + + The Dahua protocol does not appear to support a preset Home feature. We could + allow the user to assign a preset slot as the "home" slot. Dahua does appear + to support naming presets which may lend itself to this sort of thing. At + this point, we'll just send the camera back to center and zoom wide. (0°,0°,0) + +=head2 reset + + This method will reset the PTZ controls to their "default." It is not clear + what that is. + +=head2 reboot + + This method performs a reboot of the camera. This will take the camera offline + for the time it takes to reboot. + =cut diff --git a/web/api/app/Controller/Component/FilterComponent.php b/web/api/app/Controller/Component/FilterComponent.php index 326c1ce45..798452793 100644 --- a/web/api/app/Controller/Component/FilterComponent.php +++ b/web/api/app/Controller/Component/FilterComponent.php @@ -1,14 +1,88 @@ ', + '>', + '>=', + 'IS', + 'IS NOT', + //'IS NOT NULL', + //'IS NULL', + //'->', + //'->>', + '<<', + '<', + '<=', + 'LIKE', + '-', + '%', + 'MOD', + //'NOT', + //'!', + //'NOT BETWEEN ... AND ...', + '!=', + '<>', + 'NOT LIKE', + 'NOT REGEXP', + // `or` operators aren't safe as they can + // be used to skip an existing condition + // enforcing access to only certain + // monitors/events. + //'||', + //'OR', + '+', + 'REGEXP', + '>>', + 'RLIKE', + 'SOUNDS LIKE', + //'*', + '-', + //'XOR', + ); + // Build a CakePHP find() condition based on the named parameters // that are passed in public function buildFilter($namedParams) { - if ($namedParams) { - $conditions = array(); - + $conditions = array(); + if ($namedParams) { foreach ($namedParams as $attribute => $value) { + // We need to sanitize $attribute to avoid SQL injection. + $lhs = trim($attribute); + $matches = NULL; + if (preg_match('/^(?P[a-z0-9]+)(?P.+)?$/i', $lhs, $matches) !== 1) { + throw new Exception('Invalid argument before `:`: ' . $lhs); + } + $operator = trim($matches['operator']); + + // Only allow operators on our allow list. No operator + // specified defaults to `=` by cakePHP. + if ($operator != '' && !in_array($operator, $this->twoOperandSQLOperands)) { + throw new Exception('Invalid operator: ' . $operator); + } + + $lhs = '`' . $matches['field'] . '` ' . $operator; // If the named param contains an array, we want to turn it into an IN condition // Otherwise, we add it right into the $conditions array if (is_array($value)) { @@ -18,10 +92,10 @@ class FilterComponent extends Component { array_push($array, $term); } - $query = array($attribute => $array); + $query = array($lhs => $array); array_push($conditions, $query); } else { - $conditions[$attribute] = $value; + $conditions[$lhs] = $value; } } diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index cf8ca8f37..959ccc596 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -44,9 +44,8 @@ class EventsController extends AppController { } if ( $this->request->params['named'] ) { - //$this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } @@ -234,18 +233,34 @@ class EventsController extends AppController { public function search() { $this->Event->recursive = -1; + // Unmodified conditions to pass to find() + $find_conditions = array(); + // Conditions to be filtered by buildFilter $conditions = array(); foreach ($this->params['named'] as $param_name => $value) { - // Transform params into mysql - if ( preg_match('/interval/i', $value, $matches) ) { - $condition = array("$param_name >= (date_sub(now(), $value))"); + // Transform params into conditions + if ( preg_match('/^\s?interval\s?/i', $value) ) { + if (preg_match('/^[a-z0-9]+$/i', $param_name) !== 1) { + throw new Exception('Invalid field name: ' . $param_name); + } + $matches = NULL; + $value = preg_replace('/^\s?interval\s?/i', '', $value); + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($value), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $value); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + array_push($find_conditions, "$param_name >= DATE_SUB(NOW(), INTERVAL $expr $unit)"); } else { - $condition = array($param_name => $value); + $conditions[$param_name] = $value; } - array_push($conditions, $condition); } + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, $find_conditions); + $results = $this->Event->find('all', array( 'conditions' => $conditions )); @@ -261,18 +276,32 @@ class EventsController extends AppController { // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json public function consoleEvents($interval = null) { + $matches = NULL; + // https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals + // Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND` + if (preg_match('/^(?P[ -.:0-9\']+)\s+(?P[_a-z]+)$/i', trim($interval), $matches) !== 1) { + throw new Exception('Invalid interval: ' . $interval); + } + $expr = trim($matches['expr']); + $unit = trim($matches['unit']); + $this->Event->recursive = -1; $results = array(); + $this->FilterComponent = $this->Components->load('Filter'); + $conditions = $this->FilterComponent->buildFilter($conditions); + array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)")); - $moreconditions = ''; - foreach ($this->request->params['named'] as $name => $param) { - $moreconditions = $moreconditions . ' AND '.$name.$param; - } - - $query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); + $query = $this->Event->find('all', array( + 'fields' => array( + 'MonitorId', + 'COUNT(*) AS Count', + ), + 'conditions' => $conditions, + 'group' => 'MonitorId', + )); foreach ($query as $result) { - $results[$result['Events']['MonitorId']] = $result[0]['Count']; + $results[$result['Event']['MonitorId']] = $result[0]['Count']; } $this->set(array( diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index 185a06c84..fd0d1e94a 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -40,8 +40,7 @@ class MonitorsController extends AppController { if ( $this->request->params['named'] ) { $this->FilterComponent = $this->Components->load('Filter'); - //$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); - $conditions = $this->request->params['named']; + $conditions = $this->FilterComponent->buildFilter($this->request->params['named']); } else { $conditions = array(); } @@ -315,6 +314,10 @@ class MonitorsController extends AppController { throw new NotFoundException(__('Invalid monitor')); } + if (preg_match('/^[a-z]+$/i', $daemon) !== 1) { + throw new BadRequestException(__('Invalid command')); + } + $monitor = $this->Monitor->find('first', array( 'fields' => array('Id', 'Type', 'Device'), 'conditions' => array('Id' => $id) diff --git a/web/includes/auth.php b/web/includes/auth.php index 5ce960388..6b061f7fc 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -211,7 +211,7 @@ global $user; if ( ZM_OPT_USE_AUTH ) { $close_session = 0; if ( !is_session_started() ) { - session_start(); + zm_session_start(); $close_session = 1; } diff --git a/web/skins/classic/views/js/cycle.js b/web/skins/classic/views/js/cycle.js index 9422890c8..4e6de5320 100644 --- a/web/skins/classic/views/js/cycle.js +++ b/web/skins/classic/views/js/cycle.js @@ -26,10 +26,11 @@ function cycleNext() { window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } function cyclePrev() { - if ( monIdx ) + if (monIdx) { monIdx -= 1; - else - monIdx = monitorData.length-1; + } else { + monIdx = monitorData.length - 1; + } window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); } @@ -82,9 +83,9 @@ function changeScale() { var scale = $('scale').get('value'); $('width').set('value', 'auto'); $('height').set('value', 'auto'); - Cookie.write('zmCycleScale', scale, { duration: 10*365 }); - Cookie.write('zmCycleWidth', 'auto', { duration: 10*365 }); - Cookie.write('zmCycleHeight', 'auto', { duration: 10*365 }); + Cookie.write('zmCycleScale', scale, {duration: 10*365}); + Cookie.write('zmCycleWidth', 'auto', {duration: 10*365}); + Cookie.write('zmCycleHeight', 'auto', {duration: 10*365}); var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE; diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index a0594825b..a30a05fee 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -448,10 +448,16 @@ function cmdEnableAlarms() { function cmdForceAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm"); + if (window.event) { + window.event.preventDefault(); + } } function cmdCancelForcedAlarm() { alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm"); + if (window.event) { + window.event.preventDefault(); + } return false; }