'No error has occurred (probably emtpy data passed)', JSON_ERROR_DEPTH => 'The maximum stack depth has been exceeded', JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', JSON_ERROR_SYNTAX => 'Syntax error' ); /** @var int The status code */ private $status = 200; /** @var int Data encapsulation format */ private $encapsulation = 1; /** @var mixed Any data to be returned to the caller */ private $data = ''; /** @var string A password passed to us by the caller */ private $password = null; public function execute($json) { // Check if we're activated $enabled = AEPlatform::get_platform_configuration_option('frontend_enable', 0); if(!$enabled) { $this->data = 'Access denied'; $this->status = self::STATUS_NOT_AVAILABLE; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } // Try to JSON-decode the request's input first $request = @json_decode($json, false); if(is_null($request)) { // Could not decode JSON $this->data = 'JSON decoding error: '.$this->json_errors[json_last_error()]; $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } // Decode the request body // Request format: {encapsulation, body{ [key], [challenge], method, [data] }} or {[challenge], method, [data]} if( isset($request->encapsulation) && isset($request->body) ) { if(!class_exists('AkeebaHelperEncrypt') && !($request->encapsulation == self::ENCAPSULATION_RAW)) { // Encrypted request found, but there is no encryption class available! $this->data = 'This server does not support encrypted requests'; $this->status = self::STATUS_NOT_AVAILABLE; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } // Fully specified request switch( $request->encapsulation ) { case self::ENCAPSULATION_AESCBC128: if(!isset($body)) { $request->body = base64_decode($request->body); $body = AkeebaHelperEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 128); } break; case self::ENCAPSULATION_AESCBC256: if(!isset($body)) { $request->body = base64_decode($request->body); $body = AkeebaHelperEncrypt::AESDecryptCBC($request->body, $this->serverKey(), 256); } break; case self::ENCAPSULATION_AESCTR128: if(!isset($body)) { $body = AkeebaHelperEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 128); } break; case self::ENCAPSULATION_AESCTR256: if(!isset($body)) { $body = AkeebaHelperEncrypt::AESDecryptCtr($request->body, $this->serverKey(), 256); } break; case self::ENCAPSULATION_RAW: $body = $request->body; break; } if(!empty($request->body)) { $body = rtrim( $body, chr(0) ); $request->body = json_decode($body); if(is_null($request->body)) { // Decryption failed. The user is an imposter! Go away, hacker! $this->data = 'Authentication failed'; $this->status = self::STATUS_NOT_AUTH; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } } } elseif( isset($request->body) ) { // Partially specified request, assume RAW encapsulation $request->encapsulation = self::ENCAPSULATION_RAW; $request->body = json_decode($request->body); } else { // Legacy request $legacyRequest = clone $request; $request = (object) array( 'encapsulation' => self::ENCAPSULATION_RAW, 'body' => null ); $request->body = json_decode($legacyRequest); unset($legacyRequest); } // Authenticate the user. Do note that if an encrypted request was made, we can safely assume that // the user is authenticated (he already knows the server key!) if($request->encapsulation == self::ENCAPSULATION_RAW) { $authenticated = false; if(isset($request->body->challenge)) { list($challenge,$check) = explode(':', $request->body->challenge); $crosscheck = strtolower(md5($challenge.$this->serverKey())); $authenticated = ($crosscheck == $check); } if(!$authenticated) { // If the challenge was missing or it was wrong, don't let him go any further $this->data = 'Invalid login credentials'; $this->status = self::STATUS_NOT_AUTH; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } } // Replicate the encapsulation preferences of the client for our own output $this->encapsulation = $request->encapsulation; // Store the client-specified key, or use the server key if none specified and the request // came encrypted. $this->password = isset($request->body->key) ? $request->body->key : null; if(is_null($request->body->key) && ($request->encapsulation != self::ENCAPSULATION_RAW) ) { $this->password = $this->serverKey(); } // Does the specified method exist? $method_exists = false; $method_name = ''; if(isset($request->body->method)) { $method_name = ucfirst($request->body->method); $method_exists = method_exists($this, '_api'.$method_name ); } if(!$method_exists) { // The requested method doesn't exist. Oops! $this->data = "Invalid method $method_name"; $this->status = self::STATUS_INVALID_METHOD; $this->encapsulation = self::ENCAPSULATION_RAW; return $this->getResponse(); } // Run the method $params = array(); if(isset($request->body->data)) $params = (array)$request->body->data; $this->data = call_user_func( array($this, '_api'.$method_name) , $params); return $this->getResponse(); } /** * Packages the response to a JSON-encoded object, optionally encrypting the * data part with a caller-supplied password. * @return string The JSON-encoded response */ private function getResponse() { // Initialize the response $response = array( 'encapsulation' => $this->encapsulation, 'body' => array( 'status' => $this->status, 'data' => null ) ); $data = json_encode($this->data); if(empty($this->password)) $this->encapsulation = self::ENCAPSULATION_RAW; switch($this->encapsulation) { case self::ENCAPSULATION_RAW: break; case self::ENCAPSULATION_AESCTR128: $data = AkeebaHelperEncrypt::AESEncryptCtr($data, $this->password, 128); break; case self::ENCAPSULATION_AESCTR256: $data = AkeebaHelperEncrypt::AESEncryptCtr($data, $this->password, 256); break; case self::ENCAPSULATION_AESCBC128: $data = base64_encode(AkeebaHelperEncrypt::AESEncryptCBC($data, $this->password, 128)); break; case self::ENCAPSULATION_AESCBC256: $data = base64_encode(AkeebaHelperEncrypt::AESEncryptCBC($data, $this->password, 256)); break; } $response['body']['data'] = $data; return '###' . json_encode($response) . '###'; } private function serverKey() { static $key = null; if(is_null($key)) { $key = AEPlatform::get_platform_configuration_option('frontend_secret_word', ''); } return $key; } private function _apiGetVersion() { return (object)array( 'api' => AKEEBA_JSON_API_VERSION, 'component' => AKEEBA_VERSION, 'date' => AKEEBA_DATE ); } private function _apiGetProfiles() { require_once JPATH_SITE.DS.'administrator'.DS.'components'.DS.'com_akeeba'.DS.'models'.DS.'profiles.php'; $model = new AkeebaModelProfiles(); $profiles = $model->getProfilesList(true); $ret = array(); if(count($profiles)) { foreach($profiles as $profile) { $temp = new stdClass(); $temp->id = $profile->id; $temp->name = $profile->description; $ret[] = $temp; } } return $ret; } private function _apiStartBackup($config) { $defConfig = array( 'profile' => 1, 'description' => '', 'comment' => '' ); $config = array_merge($defConfig, $config); extract($config); // Set the profile $profile = JRequest::getInt('profile',1); if(!is_numeric($profile)) $profile = 1; $session =& JFactory::getSession(); $session->set('profile', $profile, 'akeeba'); AEPlatform::load_configuration($profile); // Use the default description if none specified if(empty($description)) { jimport('joomla.utilities.date'); $user =& JFactory::getUser(); $userTZ = $user->getParam('timezone',0); $dateNow = new JDate(); $dateNow->setOffset($userTZ); if( AKEEBA_JVERSION == '16' ) { $description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->format(JText::_('DATE_FORMAT_LC2'), true); } else { $description = JText::_('BACKUP_DEFAULT_DESCRIPTION').' '.$dateNow->toFormat(JText::_('DATE_FORMAT_LC2')); } } // Start the backup AECoreKettenrad::reset(); $memory_filename = AEUtilTempvars::get_storage_filename(AKEEBA_BACKUP_ORIGIN); @unlink($memory_filename); $kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN); $options = array( 'description' => $description, 'comment' => $comment ); $kettenrad->setup($options); $array = $kettenrad->tick(); AECoreKettenrad::save(AKEEBA_BACKUP_ORIGIN); $array = $kettenrad->getStatusArray(); if($array['Error'] != '') { // A backup error had occured. Why are we here?! $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'A backup error had occured: '.$array['Error']; } else { $array = $kettenrad->tick(); if($array['Error'] != '') { // A backup error had occured. Why are we here?! $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'A backup error had occured: '.$array['Error']; } else { $statistics =& AEFactory::getStatistics(); $array['BackupID'] = $statistics->getId(); return $array; } } } private function _apiStepBackup($config) { $defConfig = array( ); $config = array_merge($defConfig, $config); extract($config); $kettenrad =& AECoreKettenrad::load(AKEEBA_BACKUP_ORIGIN); $array = $kettenrad->getStatusArray(); if($array['Error'] != '') { // A backup error had occured. Why are we here?! $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'A backup error had occured: '.$array['Error']; } elseif($array['HasRun'] == 1) { $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'The backup is already finalized!'; } else { $array = $kettenrad->tick(); AECoreKettenrad::save(); if($array['Error'] != '') { // A backup error had occured. Why are we here?! $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'A backup error had occured: '.$array['Error']; } else { return $array; } } } private function _apiListBackups($config) { $defConfig = array( 'from' => 0, 'limit' => 50 ); $config = array_merge($defConfig, $config); extract($config); require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php'; $model = new AkeebaModelStatistics(); return $model->getStatisticsListWithMeta(true); } private function _apiGetBackupInfo($config) { $defConfig = array( 'backup_id' => '0' ); $config = array_merge($defConfig, $config); extract($config); // Get the basic statistics $record = AEPlatform::get_statistics($backup_id); // Get a list of filenames $backup_stats = AEPlatform::get_statistics($backup_id); // Backup record doesn't exist if(empty($backup_stats)) { $this->status = self::STATUS_NOT_FOUND; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Invalid backup record identifier'; } $filenames = AEUtilStatistics::get_all_filenames($stat); if(empty($filenames)) { // Archives are not stored on the server or no files produced $record['filenames'] = array(); } else { $filedata = array(); $i = 0; // Get file sizes per part foreach($filenames as $file) { $i++; $size = @filesize($file); $size = is_numeric($size) ? $size : 0; $filedata[] = array( 'part' => $i, 'name' => basename($file), 'size' => $size ); } // Add the file info to $record['filenames'] $record['filenames'] = $filedata; } return $record; } private function _apiDownload($config) { $defConfig = array( 'backup_id' => 0, 'part_id' => 1, 'segment' => 1 ); $config = array_merge($defConfig, $config); extract($config); $backup_stats = AEPlatform::get_statistics($backup_id); if(empty($backup_stats)) { // Backup record doesn't exist $this->status = self::STATUS_NOT_FOUND; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Invalid backup record identifier'; } $files = AEUtilStatistics::get_all_filenames($backup_stats); if( (count($files) < $part_id) || ($part_id <= 0) ) { // Invalid part $this->status = self::STATUS_NOT_FOUND; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Invalid backup part'; } $file = $files[$segment-1]; $fp = fopen($file, 'rb'); if($fp === false) { // Could not read file $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Error reading backup archive'; } $seekPos = 1048576 * ($segment - 1); if($seekPos>0) if(!fseek($fp, $seekPos)) { // Could not seek to position $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Error reading specified segment'; } $buffer = fread($fp, 1048756); if($buffer === false) { // Could not read $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return 'Error reading specified segment'; } fclose($fp); switch($this->encapsulation) { case self::ENCAPSULATION_RAW: return base64_encode($buffer); break; case self::ENCAPSULATION_AESCTR128: $this->encapsulation = self::ENCAPSULATION_AESCBC128; return $buffer; break; case self::ENCAPSULATION_AESCTR256: $this->encapsulation = self::ENCAPSULATION_AESCBC256; return $buffer; break; default: // On encrypted comms the encryption will take care of transport encoding return $buffer; break; } } private function _apiDelete($config) { $defConfig = array( 'backup_id' => 0 ); $config = array_merge($defConfig, $config); extract($config); require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php'; $model = new AkeebaModelStatistics(); $result = $model->delete((int)$backup_id); if(!$result) { $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return $model->getError(); } else { return true; } } private function _apiDeleteFiles($config) { $defConfig = array( 'backup_id' => 0 ); $config = array_merge($defConfig, $config); extract($config); require_once JPATH_COMPONENT_ADMINISTRATOR.DS.'models'.DS.'statistics.php'; $model = new AkeebaModelStatistics(); $result = $model->deleteFile((int)$backup_id); if(!$result) { $this->status = self::STATUS_ERROR; $this->encapsulation = self::ENCAPSULATION_RAW; return $model->getError(); } else { return true; } } private function _apiGetLog($config) { $defConfig = array( 'tag' => 'remote' ); $config = array_merge($defConfig, $config); extract($config); $filename = AEUtilLogger::logName($tag); $buffer = file_get_contents($filename); switch($this->encapsulation) { case self::ENCAPSULATION_RAW: return base64_encode($buffer); break; case self::ENCAPSULATION_AESCTR128: $this->encapsulation = self::ENCAPSULATION_AESCBC128; return $buffer; break; case self::ENCAPSULATION_AESCTR256: $this->encapsulation = self::ENCAPSULATION_AESCBC256; return $buffer; break; default: // On encrypted comms the encryption will take care of transport encoding return $buffer; break; } } }