asReq[$sKey] = $this->validateValue($sValidation, $oValue); } public function handle($sProcessPage, array $argv = array()): string { //Start buffering so warnings/notices can be collected ob_start(); //Parse variables $asReq = ToolBox::getRequest($argv); $this->asReq = array(); $sAction = $asReq['a'] ?? ''; $this->setReqVal('t', $asReq['t'] ?? ''); $this->setReqVal('name', $asReq['name'] ?? ''); $this->setReqVal('content', $asReq['content'] ?? ''); $this->setReqVal('id_project', $asReq['id_project'] ?? 0, 'positiveInt'); $this->setReqVal('id', $asReq['id'] ?? 0); $this->setReqVal('id_entity', $asReq['id'] ?? 0, 'positiveInt'); $this->setReqVal('field', $asReq['field'] ?? ''); $this->setReqVal('value', $asReq['value'] ?? ''); $this->setReqVal('type', $asReq['type'] ?? ''); $this->setReqVal('email', $asReq['email'] ?? ''); $this->setReqVal('latitude', $asReq['latitude'] ?? ''); $this->setReqVal('longitude', $asReq['longitude'] ?? ''); $this->setReqVal('timestamp', $asReq['timestamp'] ?? 0, 'positiveInt'); $this->setReqVal('csrf_token', $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ($_POST['csrf_token'] ?? '')); //Create Spot Instance $this->oSpot = new Spot($sProcessPage, $this->asReq['t']); $this->oSpot->setProjectId($this->asReq['id_project']); //Validate CSRF & dispatch if(!$this->validateMutationRequest($sAction)) $sResult = Spot::getJsonResult(false, Spot::UNAUTHORIZED); elseif($sAction == '') $sResult = $this->oSpot->getAppMainPage($this->getCsrfToken()); else $sResult = $this->dispatch($sAction); //Clean errors $sDebug = ob_get_clean(); if($sDebug != '') $this->oSpot->addUncaughtError($sDebug); return $sResult; } private function validateMutationRequest(string $sAction): bool { return PHP_SAPI === 'cli' || !in_array($sAction, self::MUTATING_ACTIONS, true) || ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST' && $this->checkCsrfToken($this->asReq['csrf_token']) ; } private function getCsrfToken(): string { if($this->sCsrfToken === '') $this->initCsrfToken(); return $this->sCsrfToken; } private function setCsrfToken(): void { if(empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); $this->sCsrfToken = $_SESSION['csrf_token']; } private function initCsrfToken(): void { if(PHP_SAPI === 'cli') return; $bCloseSession = false; if(session_status() !== PHP_SESSION_ACTIVE) { $bSecure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https'); session_set_cookie_params(array('httponly' => true, 'secure' => $bSecure, 'samesite' => 'Lax')); session_start(); $bCloseSession = true; } $this->setCsrfToken(); if($bCloseSession) session_write_close(); } private function checkCsrfToken(string $sClientToken): bool { $sServerToken = $this->getCsrfToken(); return PHP_SAPI === 'cli' || ($sServerToken !== '' && is_string($sClientToken) && hash_equals($sServerToken, $sClientToken)); } private function dispatch(string $sAction): string { return match($sAction) { 'markers' => $this->oSpot->getMarkers(), 'last_update' => $this->oSpot->getLastUpdate(), 'geojson' => $this->oSpot->getProjectGeoJson(), 'next_feed' => $this->oSpot->getNextFeed($this->asReq['id']), 'new_feed' => $this->oSpot->getNewFeed($this->asReq['id']), 'add_post' => $this->oSpot->addPost($this->asReq['name'], $this->asReq['content']), 'subscribe' => $this->oSpot->subscribe($this->asReq['email'], $this->asReq['name']), 'unsubscribe' => $this->oSpot->unsubscribe(), 'unsubscribe_email' => $this->oSpot->unsubscribeFromEmail($this->asReq['id_entity']), 'update_project' => $this->oSpot->updateProject(), default => $this->dispatchAdmin($sAction) }; } private function dispatchAdmin(string $sAction): string { if(!$this->oSpot->checkUserClearance(User::CLEARANCE_ADMIN)) { return Spot::getJsonResult(false, Spot::NOT_FOUND); } return match($sAction) { 'upload' => $this->oSpot->upload(), 'add_comment' => $this->oSpot->addComment($this->asReq['id_entity'], $this->asReq['content']), 'add_position' => $this->oSpot->addPosition($this->asReq['latitude'], $this->asReq['longitude'], $this->asReq['timestamp']), 'admin_get' => $this->oSpot->getAdminSettings(), 'admin_set' => $this->oSpot->setAdminSettings($this->asReq['type'], $this->asReq['id_entity'], $this->asReq['field'], $this->asReq['value']), 'admin_create' => $this->oSpot->createAdminSettings($this->asReq['type']), 'admin_delete' => $this->oSpot->deleteAdminSettings($this->asReq['type'], $this->asReq['id_entity']), 'sql' => $this->oSpot->getDbBuildScript(), 'build_geojson' => $this->oSpot->buildGeoJSON($this->asReq['name']), default => Spot::getJsonResult(false, Spot::NOT_FOUND) }; } private static function validateValue(string $sValidation, $oValue=0) { return match($sValidation) { '' => $oValue, 'positiveInt' => filter_var($oValue, FILTER_VALIDATE_INT, array('options' => array('default' => 0, 'min_range' => 0))) }; } }