false ]; private static $authsystem = null; // liste des methodes HTTP autorisées private static $allowed_http_methods = [ "GET", "POST", "PUT", "DELETE" ]; // Attributs prives utiles (initialisation) private $path; // chemin de base (uri) private $params; // paramètres (POST+GET) private $schema; // schema configuration private $options; // options private $http_method; // methode HTTP appelante // Contiendra la reponse a la requete public $answer; // Contiendra l'etat de la requete public $error; /* (0) Constructeur d'une requete de module * * @uri URI relative de l'appel * @param Tableau associatif contenant les parametres utiles au traitement * @forced_method Méthode demandée (optionnel) * * @return instance Instance crée * ---------------------------------------------------------*/ public function __construct($uri=null, $params=null, $forced_method=null){ return $this->buildRequestObject($uri, $params, $forced_method); } /* (1) Constructeur d'une requete de module (delegation) * * @uri URI relative de l'appel * @param Tableau associatif contenant les parametres utiles au traitement * @forced_method Méthode demandée (optionnel) * * @return status Retourne si oui ou non tout s'est bien passe * ---------------------------------------------------------*/ private function buildRequestObject($uri=null, $params=null, $forced_method=null){ /* (1) Initialisation ---------------------------------------------------------*/ /* (1) Erreur par défaut */ $this->error = new Error(Err::Success); /* (2) Si pas parametre manquant, on quitte */ if( $uri == null ) return $this->error->set(Err::MissingPath); /* (2) On met a jour la configuration ---------------------------------------------------------*/ /* (1) Build from configuration */ $this->buildConfig(); /* (2) Dispatch if error */ if( $this->error->get() != Err::Success ) return; /* (3) Verification des types des parametres ---------------------------------------------------------*/ /* (1) Si path est une */ if( !is_string($uri) ) // Si le type est incorrect return $this->error->set(Err::WrongPathModule); /* (2) Formattage @params en tableau */ $params = (is_array($params)) ? $params : []; /* (3) On définit en constante la méthode HTTP */ if( !isset($_SERVER['REQUEST_METHOD']) && !is_string($forced_method) ) return $this->error->set(Err::UnknownHttpMethod); $this->http_method = is_string($forced_method) ? strtoupper($forced_method) : strtoupper($_SERVER['REQUEST_METHOD']); /* (4) Verification du chemin (existence module+methode) ---------------------------------------------------------*/ if( !$this->checkURI($uri) ) // Verification de la coherence du chemin + attribution return false; // checkURI() sets the error itself /* (5) Verification des permissions ---------------------------------------------------------*/ if( !$this->checkPermission() ) // Si on a pas les droits return false; // checkPermission() sets the error itself /* (6) Verification des parametres (si @type est defini) ---------------------------------------------------------*/ if( !$this->checkParams($params) ) // Verification de tous les types return false; // checkParams() sets the error itself /* (7) Récupèration des options ---------------------------------------------------------*/ $this->buildOptions(); /* (8) Construction de l'objet (add http method to params) ---------------------------------------------------------*/ $this->params = $params; $this->params['HTTP_METHOD'] = $this->http_method; $this->error->set(Err::Success); return true; // On retourne que tout s'est bien passe } /* (2) Definit le systeme d'authentification * * @instance Instance de type AuthSystem * * @return success Whether the AuthSystem is valid or not * ---------------------------------------------------------*/ public static function setAuthSystem($instance=null){ /* (1) Check instance type */ if( !($instance instanceof AuthSystem) ) return false; /* (2) Store instance */ self::$authsystem = $instance; return true; } /* (3) Construction du schéma à partir de la configuration * ---------------------------------------------------------*/ private function buildConfig(){ /* (1) Access file content ---------------------------------------------------------*/ /* (1) Vérification existence fichier config */ if( !file_exists(self::config_path()) ) return $this->error->set(Err::UnreachableResource); /* (2) Lecture fichier config */ $conf = @file_get_contents(self::config_path()); /* (3) Si erreur lecture */ if( $conf === false ) return $this->error->set(Err::UnreachableResource); /* (4) Parsage json */ $this->schema['raw'] = json_decode( $conf, true ); /* (5) Gestion de l'erreur de parsage */ if( $this->schema['raw'] == null ) return $this->error->set(Err::ParsingFailed, 'json'); /* (2) Construction des outils d'accès ---------------------------------------------------------*/ /* (1) Initialisation */ $this->schema['index'] = []; /* (2) Pour chaque chemin */ foreach($this->schema['raw'] as $path=>$methods){ /* (2.1) Pour chaque méthode */ foreach($methods as $method=>$data){ /* (2.1.1) Suppression si pas dans les méthodes autorisées */ if( !in_array($method, self::$allowed_http_methods) ){ unset($this->schema[$path][$method]); continue; } /* (2.1.2) Création de l'index pour le chemin si n'existe pas déja */ if( !isset($this->schema['index'][$path]) ) $this->schema['index'][$path] = []; /* (2.1.3) Ajout de la méthode à l'index */ $this->schema['index'][$path][] = $method; } } } /* (4) Verification du format et de la coherence du chemin specifie * * @path String correspondant au chemin de delegation ("module/methode") * * @return validity Retourne si oui ou non l'objet est correct * ---------------------------------------------------------*/ private function checkURI($uri){ /* (1) Verification format general ---------------------------------------------------------*/ /* (1) If wrong format -> exit */ if( !preg_match('@^\w+(\/\w+)*\/?$@', $uri, $matches) ) return $this->error->set(Err::WrongPathModule); /* (2) Verification de l'existence du chemin (conf) ---------------------------------------------------------*/ /* (1) Check if begins with each indexed @path */ $exists_size = 0; $path = null; foreach($this->schema['index'] as $key=>$void){ $match_size = strlen($key); /* (1.1) Look for the longer match */ if( $match_size > $exists_size && substr($uri, 0, $match_size) == $key ){ $exists_size = $match_size; $path = $key; } } /* (2) If @path not found -> exit */ if( is_null($path) ) return $this->error->set(Err::UnknownModule); /* (3) Verification de l'existence de la methode (conf) ---------------------------------------------------------*/ /* (1) Check if HTTP method is in allowed methods */ if( !in_array($this->http_method, self::$allowed_http_methods) ) return $this->error->set(Err::UnknownHttpMethod, $this->http_method); /* (2) Check if HTTP method is defined for this @path */ if( !in_array($this->http_method, $this->schema['index'][$path]) ) return $this->error->set(Err::UnknownMethod, $this->http_method); /* (4) Enregistrement du chemin et renvoi de SUCCESS ---------------------------------------------------------*/ $this->path = [ 'path'=> $path, 'method'=> $this->http_method ]; return true; } /* (5) Retourne si on a la permission d'executer cette methode * * @return permission Retourne si on a les droits ou pas pour executer cette methode * ---------------------------------------------------------*/ private function checkPermission(){ /* (1) On recupere les informations utiles ---------------------------------------------------------*/ // On recupere le nom de la methode $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; // Si aucune permission n'est definie if( !isset($method['permissions']) || !is_array($method['permissions']) || count($method['permissions']) < 1 ) return true; /* (2) Vérification des permissions et de l'authentification ---------------------------------------------------------*/ // if no AuthSystem set up, use the default one if( !is_object(self::$authsystem) || !self::$authsystem instanceof AuthSystem ){ // try to load default AuthSystem if( !file_exists(__BUILD__.'/api/core/AuthSystemDefault.php') ) return $this->error->set(Err::UnreachableResource); // load default AuthSystem class $classname = '\\api\\core\\AuthSystemDefault'; self::$authsystem = new $classname(); } // Check permission using user-implemented AuthSystem $granted = self::$authsystem::permission( $method['permissions'] ); /* (1) On retourne FAUX si aucun droit n'a ete trouve */ if( $granted->get() !== Err::Success ){ $this->error = $granted; return false; } /* On retourne VRAI si la permission est ok */ return true; } /* (6) Verification du type des parametres envoyes * * @params Tableau associatif contenant les parametres * @params peut se voir rajouter les paramètres optionnels s'ils ne sont pas renseignés (initialisés à NULL) * * @return correct Retourne si oui ou non les parametres ont le bon type * ---------------------------------------------------------*/ private function checkParams(&$params){ /* (1) On verifie qu'il ne manque aucun parametre ---------------------------------------------------------*/ /* (1) Si @params n'est pas un tableau */ if( !is_array($params) ) return $this->error->set(Err::MissingParam); /* (2) On récupère les données de la méthode */ $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; /* (3) Si pas 'parameters' dans la config */ if( !isset($method['parameters']) || !is_array($method['parameters']) ) return $this->error->set(Err::ConfigError); /* (2) Si le type est defini, pour chaque param, on teste ---------------------------------------------------------*/ foreach($method['parameters'] as $name=>$paramsdata){ /* (2.1) Vérification des données ---------------------------------------------------------*/ /* (1) Si @name n'est pas une string */ if( !is_string($name) ) return $this->error->set(Err::ConfigError); /* (2) Si @paramsdata n'est pas un tableau */ if( !is_array($paramsdata) ) return $this->error->set(Err::ConfigError); /* (3) So @paramsdata['type] manquant ou incorrect */ if( !isset($paramsdata['type']) || !is_string($paramsdata['type']) ) return $this->error->set(Err::ConfigError); /* (2.2) Gestion des spécifications ---------------------------------------------------------*/ /* (1) On récupère si le paramètre est optionnel ou pas */ $optional = isset($paramsdata['optional']) && $paramsdata['optional'] === true; /* (2) Si de type 'FILE' + fichier existe => on enregistre la ref. */ if( $paramsdata['type'] == 'FILE' && isset($_FILES[$name]) ) $params[$name] = &$_FILES[$name]; /* (3) Si param obligatoire et manquant -> erreur */ if( !isset($params[$name]) && !$optional ) return $this->error->set(Err::MissingParam, $name); /* (2.3) Gestion des valeurs ---------------------------------------------------------*/ /* (1) Si le paramètre est optionnel et manquant */ if( $optional && !isset($params[$name]) ){ // On le crée le param optionnel avec la valeur NULL $params[$name] = null; /* (2) Si le paramètre est renseigné (sauf FILE) */ }elseif( $paramsdata['type'] != 'FILE'){ // Si la verification est fausse, on retourne faux if( !Checker::run($paramsdata['type'], $params[$name]) ) return $this->error->set(Err::WrongParam, $name, $paramsdata['type']); } } /* (3) Gestion du retour, si tout s'est bien passe ---------------------------------------------------------*/ return true; } /* (7) Ajout des options a partir de la configuration * * @return correct Retourne FAUS en cas d'erreur * ---------------------------------------------------------*/ private function buildOptions(){ /* (1) On récupère les options de la méthode en cours ---------------------------------------------------------*/ $method = $this->schema['raw'][$this->path['path']][$this->path['method']]; /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ if( !isset($method['options']) || !is_array($method['options']) ) return true; /* (2) Par défaut on définit les options par défaut */ $this->options = self::$default_options; /* (3) On récupère les options données */ $options = $method['options']; /* (2) Gestion des différentes options ---------------------------------------------------------*/ foreach($options as $option=>$value){ /* (1) On ne prend en compte l'option que si elle est dans les options par défaut */ if( !isset(self::$default_options[$option]) ) continue; /* (2) Le type de la valeur doit être le même que celui de la valeur par défaut */ if( gettype($value) != gettype(self::$default_options[$option]) ) continue; /* (3) Si tout est bon, on définit la valeur */ $this->options[$option] = $value; } return true; } /* (8) Execute le traitement associe et remplie la reponse * * @return answer Retourne une reponse de type si tout s'est bien passe * ---------------------------------------------------------*/ public function dispatch(){ /* (1) On verifie qu'aucune erreur n'a ete signalee ---------------------------------------------------------*/ if( $this->error->get() !== Err::Success ) // si il y a une erreur return new Response($this->error); // on la passe a la reponse /* (2) On essaie d'instancier le module ---------------------------------------------------------*/ $instance = ModuleFactory::getModule($this->path['path']); if( $instance instanceof Error ){ $this->error->set(Err::UncallableModule, $this->path['path']); return new Response($this->error); } /* (3) On verifie que la methode est amorcable ---------------------------------------------------------*/ if( !is_callable([$instance, $this->path['method']]) ){ $this->error->set(Err::UncallableMethod, $this->path['method']); return new Response($this->error); } /* (4) On amorce la methode ---------------------------------------------------------*/ /* (1) On lance la fonction */ $returned = call_user_func( [$instance, $this->path['method']], $this->params ); /* (2) On appelle le destructeur (si défini) */ $instance = null; /* (5) Gestion de la reponse ---------------------------------------------------------*/ /* (1) S'il s'agit d'un téléchargement -> on dispatch */ if( $this->options['download'] === true ) return $this->download(); /* (2) On construit la réponse avec l'erreur */ $response = new Response($this->error); /* (3) On ajoute les données */ $response->appendAll($returned); /* (4) On retourne la réponse */ return $response; } /* EXECUTE LE TRAITEMENT ASSOCIE ET RENVOIE UN FICHIER AVEC LE HEADER ET LE BODY SPECIFIE * */ public function download(){ /* (1) Vérification des erreurs et paramètres ---------------------------------------------------------*/ /* (1) Si retourne 'error' et n'est pas SUCCESS -> error */ if( isset($returned['error']) && $returned['error'] instanceof Error && $returned['error']->get() != Err::Success ){ $this->error = $returned['error']; return new Response($this->error); } /* (2) Vérification du contenu, si pas défini */ if( !isset($returned['body']) ){ $this->error->set(Err::MissingBody); return new Response($this->error); } /* (3) Si @headers n'est pas défini on met par défaut */ if( !isset($returned['headers']) || !is_array($returned['headers']) ){ $this->error->set(Err::MissingHeaders); return new Response($this->error); } /* (4) Détermine si téléchargement AJAX/DIRECT */ $from_ajax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'; /* (2) Gestion du téléchargement direct (HTTP) ---------------------------------------------------------*/ if( !$from_ajax ){ /* (1) On définit les headers */ foreach($returned['headers'] as $header=>$value) header($header.': '.$value); /* (2) On affiche le contenu */ echo $returned['body']; return true; } /* (3) Gestion du téléchargement différé (AJAX) ---------------------------------------------------------*/ /* (1) On génère les noms de fichiers utiles */ $target_fname = '/tmp/download_'.uniqid().'.php'; // cible $buffer_fname = __ROOT__.'/tmp/content_'.uniqid().'.php'; // buffer /* (2) On écrit le BODY dans un fichier buffer */ $buffer_file = fopen($buffer_fname, 'w'); fwrite($buffer_file, $returned['body']); fclose($buffer_file); /* (3) On crée le fichier cible */ $target_fnameroot = __PUBLIC__.$target_fname; $taret_file = fopen($target_fnameroot, 'w'); fwrite($taret_file, '$value) fwrite($taret_file, "header(\"$header: $value\");".PHP_EOL); /* (5) Script qui écrira le contenu du buffer */ chmod($buffer_fname, 0775); fwrite($taret_file, "readfile('$buffer_fname');".PHP_EOL); /* (6) Script qui supprimera les fichiers: buffer+target */ fwrite($taret_file, "unlink('$buffer_fname');".PHP_EOL); fwrite($taret_file, "unlink(__FILE__);".PHP_EOL); fwrite($taret_file, '?>'.PHP_EOL); /* (7) On ferme le fichier cible */ fclose($taret_file); chmod($target_fnameroot, 0775); /* (8) On envoie la réponse contenant le lien du fichier cible */ $response = new Response($this->error); $response->append('link', $target_fname); return $response; } } ?>