false ]; private static $authsystem = null; // Attributs prives utiles (initialisation) private $id; // chemin extrait de l'URI private $raw_params; // paramètres reçus private $params; // paramètres donnés à la fonction private $options; // options private $http_method; // methode HTTP appelante // 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){ $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( is_null($uri) ) return $this->error->set(Err::MissingPath); /* (2) On vérifie la configuration ---------------------------------------------------------*/ /* (1) Dispatch if error */ if( Config::get()->error->get() != Err::Success ) return ($this->error = Config::get()->error); /* (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) Add slash at the beginning of URI */ if( !preg_match('@^\/@', $uri) ) $uri = "/$uri"; /* (3) Formattage @params en tableau */ $this->raw_params = (is_array($params)) ? $params : []; /* (4) 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) Si requête de documentation -> on arrête la vérification ---------------------------------------------------------*/ if( $this->id['doc_request'] ) return true; /* (6) Verification des permissions ---------------------------------------------------------*/ if( !$this->checkPermission() ) // Si on a pas les droits return false; // checkPermission() sets the error itself /* (7) Verification des parametres (si @type est defini) ---------------------------------------------------------*/ if( !$this->checkParams() ) // Verification de tous les types return false; // checkParams() sets the error itself /* (8) Récupèration des options ---------------------------------------------------------*/ $this->buildOptions(); /* (9) Construction de l'objet ---------------------------------------------------------*/ $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) Verification du format et de la coherence du chemin specifie * * @URI URI d'appel (commence par /) * * @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('@^\/[^\/]*(\/[^\/]*)*\/?$@', $uri) ) return $this->error->set(Err::WrongPathModule); /* (2) Add ending '/' if not there */ if( $uri[strlen($uri)-1] != '/' ) $uri = "$uri/"; /* (2) Verification de l'existence du chemin (conf) ---------------------------------------------------------*/ /* (1) Check if begins with each indexed @path */ $exists_size = 0; $path = null; foreach(Config::get()->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+1) == "$key/" || $key == '/' ){ $exists_size = $key == '/' ? 0 : $match_size; $path = $key; } } /* (2) If @path not found -> exit */ if( is_null($path) ) return $this->error->set(Err::UnknownModule); /* (3) Extract URI parameters ---------------------------------------------------------*/ /* (1) Extract URI string after @path */ $uri_end = substr($uri, $exists_size); /* (2) Special case: add / if root uri arguments */ if( strlen($uri_end) > 0 && $uri_end[0] != '/' ) $uri_end = "/$uri_end"; /* (3) If invalid format, return error */ if( !preg_match('@^((?:\/[^\/]*)*)\/?$@', $uri_end, $uri_match) ) return $this->error->set(Err::InvalidURI); /* (4) Add each URI parameter to the parameter store */ $uri_args = array_slice( explode('/', $uri_match[1]), 1); foreach($uri_args as $index=>$value) if( strlen($value) > 0 ) // do not store '//' empty values $this->raw_params["URL$index"] = $value; /* (4) Verification de l'existence de la methode (conf) ---------------------------------------------------------*/ /* (1) If it is a documentation request */ $doc_req = $this->http_method == 'OPTIONS'; /* (2) Check if HTTP method is in allowed methods */ if( !in_array($this->http_method, Config::$allowed_http_methods) && !$doc_req ) return $this->error->set(Err::UnknownHttpMethod, $this->http_method); /* (3) Check if HTTP method is defined for this @path */ if( !isset(Config::get()->index[$path][$this->http_method]) && !$doc_req ) return $this->error->set(Err::UnknownMethod, $this->http_method); /* (5) Enregistrement du chemin et renvoi de SUCCESS ---------------------------------------------------------*/ $this->id = [ 'path' => $path, 'method' => $this->http_method, 'doc_request' => $doc_req ]; return true; } /* (4) 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 = Config::get()->index[$this->id['path']][$this->id['method']]; // Si aucune permission n'est definie if( !isset($method['per']) || !is_array($method['per']) || count($method['per']) < 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['per'] ); /* (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; } /* (5) Verification du type des parametres envoyes * * @return correct Retourne si oui ou non les parametres ont le bon type * ---------------------------------------------------------*/ private function checkParams(){ /* (1) On verifie qu'il ne manque aucun parametre ---------------------------------------------------------*/ /* (1) Si @params n'est pas un tableau */ if( !is_array($this->raw_params) ) return $this->error->set(Err::MissingParam); /* (2) On récupère les données de la méthode */ $method = Config::get()->index[$this->id['path']][$this->id['method']]; /* (3) Si pas 'parameters' dans la config */ if( !isset($method['par']) || !is_array($method['par']) ) return $this->error->set(Err::ConfigError); /* (2) Si le type est defini, pour chaque param, on teste ---------------------------------------------------------*/ foreach($method['par'] as $name=>$config){ /* (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 @config n'est pas un tableau */ if( !is_array($config) ) return $this->error->set(Err::ConfigError); /* (3) So @config['typ] manquant ou incorrect */ if( !isset($config['typ']) || !is_string($config['typ']) ) return $this->error->set(Err::ConfigError); /* (2.2) Gestion des spécifications ---------------------------------------------------------*/ /* (1) On récupère le paramètre RENAME */ $rename = $name; if( isset($config['ren']) && is_string($config['ren']) && preg_match('@^\w+$@', $config['ren']) ) $rename = $config['ren']; /* (2) On récupère si le paramètre est optionnel ou pas */ $optional = isset($config['opt']) && $config['opt'] === true; /* (3) Gestion du paramètre DEFAULT */ $default = null; /* (3.1) Check if default NOT (NULL || FILE) -> matches TYPE */ if( isset($config['def']) ){ /* (3.1.1) Set default value from config */ $default = $config['def']; /* (3.1.2) If FILE and not null -> Check type */ if( $config['typ'] != 'FILE' || $default != null ) if( !Checker::run($config['typ'], $default) ) return $this->error->set(Err::WrongDefaultParam, $rename, $config['typ']); } /* (4) Si de type 'FILE' + fichier existe => on enregistre la ref. */ if( $config['typ'] == 'FILE' && isset($_FILES[$name]) ) $this->params[$rename] = &$_FILES[$name]; /* (4) Si param obligatoire et manquant -> erreur */ if( !isset($this->raw_params[$name]) && !$optional ) return $this->error->set(Err::MissingParam, $rename); /* (2.3) Gestion des valeurs ---------------------------------------------------------*/ /* (1) Si le paramètre est optionnel et manquant */ if( $optional && !isset($this->raw_params[$name]) ){ // On le crée le param optionnel avec la valeur @default $this->params[$rename] = $default; /* (2) Si le paramètre est renseigné (sauf FILE) */ }elseif( $config['typ'] != 'FILE' ){ // Si la verification est fausse, on retourne faux if( !Checker::run($config['typ'], $this->raw_params[$name]) ) return $this->error->set(Err::WrongParam, $rename, $config['typ']); // Sinon, on ajoute aux params qu'on enverra à l'appel else $this->params[$rename] = $this->raw_params[$name]; } } /* (3) Gestion du retour, si tout s'est bien passe ---------------------------------------------------------*/ return true; } /* (6) 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 = Config::get()->index[$this->id['path']][$this->id['method']]; /* (1) Si 'option' n'est pas défini (ou incorrect), on met les valeurs par défaut */ if( !isset($method['opt']) || !is_array($method['opt']) ) 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['opt']; /* (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; } /* (7) 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) Vérifications de niveau 0 ---------------------------------------------------------*/ /* (1) Si erreur -> on dispatch à la réponse */ if( $this->error->get() !== Err::Success ) return new Response($this->error); /* (2) S'il requête de documentation -> on génère la documentation */ if( $this->id['doc_request'] ) return Documentation::generate($this); /* (2) On essaie d'instancier le module ---------------------------------------------------------*/ $instance = ModuleFactory::getModule($this->id['path']); if( $instance instanceof Error ){ $this->error->set(Err::UncallableModule, $this->id['path']); return new Response($this->error); } /* (3) On verifie que la methode est amorcable ---------------------------------------------------------*/ if( !is_callable([$instance, $this->id['method']]) ){ $this->error->set(Err::UncallableMethod, $this->id['method']); return new Response($this->error); } /* (4) On amorce la methode ---------------------------------------------------------*/ /* (1) On lance la fonction */ $returned = call_user_func( [$instance, $this->id['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($returned); /* (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; } /* (8) Gestion d'un téléchargement HTTP * */ public function download($returned){ /* (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; } /* (9) Getter générique * * @index Index de l'attribut * ---------------------------------------------------------*/ public function get($index=null){ switch($index){ case 'id': return $this->id; break; case 'raw_params': return $this->raw_params; break; case 'params': return $this->params; break; case 'options': return $this->options; break; case 'http_method': return $this->http_method; break; } return null; } } ?>