source: spip-zone/_plugins_/acces_restreint/trunk/action/api_docrestreint.php @ 84566

Last change on this file since 84566 was 84566, checked in by marcimat@…, 7 years ago

Pouvoir obtenir des infos de l'objet Document (sans pouvoir les modifier).

File size: 12.8 KB
Line 
1<?php
2
3/***************************************************************************\
4 *  SPIP, Systeme de publication pour l'internet                           *
5 *                                                                         *
6 *  Copyright (c) 2001-2009                                                *
7 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8 *                                                                         *
9 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11\***************************************************************************/
12
13if (!defined("_ECRIRE_INC_VERSION")) return;
14
15include_spip('inc/headers');
16
17/**
18 * Acces aux documents joints securise
19 *
20 * verifie soit que le demandeur est authentifie
21 * soit que le document est publie, c'est-a-dire
22 * joint a au moins 1 article, breve ou rubrique publie
23 *
24 * URLs de la forme :
25 * docrestreint.api/id/cle/file
26 *
27 * @pipeline_appel accesrestreint_repertoires_toujours_autorises
28 * @pipeline_appel accesrestreint_pre_vue_document
29 *
30 * @param null $arg
31 */
32function action_api_docrestreint_dist($arg=null) {
33
34        // Obtenir l'argument '{id_document}/{cle_action}/{chemin_fichier.ext}'
35        if (is_null($arg)) {
36                $arg = _request('arg');
37        }
38        $arg = explode("/", $arg);
39
40        // Supprimer et vider les buffers qui posent des problemes de memory limit
41        accesrestreint_vider_buffers();
42
43        // manque des arguments : 404
44        if (count($arg) < 3) {
45                accesrestreint_afficher_erreur_document(404);
46                return;
47        }
48
49
50        // Séparer les 3 arguments
51        // file ($f) exige pour eviter le scan id_document par id_document
52        $id_document = intval(array_shift($arg));
53        $cle         = array_shift($arg);
54        $f           = implode("/", $arg);
55
56        // Objet reprenant toutes les infos connues
57        $Document = new Accesrestreint_document($f, $id_document, $cle);
58
59        // Simple test de fonctionnement ?
60        if ($Document->est_un_test()) {
61                echo "OK";
62                return;
63        }
64
65        // Insécurisé ( chemin ../ ou absolu ) : 403
66        if ($Document->est_insecurise()) {
67                $Document->status = 403;
68                accesrestreint_afficher_document_selon_status($Document);
69                return;
70        }
71
72        // inexistant ou non lisible : 404
73        if (!$Document->est_existant() OR !$Document->est_lisible()) {
74                $Document->status = 404;
75                accesrestreint_afficher_document_selon_status($Document);
76                return;
77        }
78
79        /**
80         * Liste les sous répertoires de IMG qui donnent accès aux fichiers
81         * systématiquement (sans autorisations particulières).
82        **/
83        $repertoires_autorises = pipeline('accesrestreint_repertoires_toujours_autorises', array('nl'));
84
85        if ($Document->est_dans_repertoire($repertoires_autorises)) {
86                $Document->status = 200;
87                accesrestreint_afficher_document_selon_status($Document);
88                return;
89        }
90
91        /**
92         * Propose à des plugins de modifier le Document
93         *
94         * Particulièrement pour donner le status http qui convient pour ce document.
95         *
96         * Si ce 'status' est renseigné par un plugin : on affiche le document (ou l'erreur) avec le statut trouvé
97         *
98         * Sinon, il sera calculé automatiquement ensuite :
99         *
100         * - si le document ne fait pas partie de spip_documents : 404
101         * - sinon, test d'autorisation de voir ce document (par clé d'action si indiquée, sinon par l'api autoriser)
102        **/
103        $Document = pipeline('accesrestreint_pre_vue_document', $Document);
104
105        if ($Document->status) {
106                accesrestreint_afficher_document_selon_status($Document);
107                return;
108        }
109
110        // Un document sans 'status' doit être analysé
111        // Est-ce un document SPIP ? Non = 404
112        $doc = $Document->get_spip_document();
113
114        if (!$doc) {
115                $Document->status = 404;
116                accesrestreint_afficher_document_selon_status($Document);
117                return;
118        }
119
120
121        // ETag pour gerer le status 304
122        $ETag = $Document->get_Etag();
123
124        if (isset($_SERVER['HTTP_IF_NONE_MATCH'])
125          AND $_SERVER['HTTP_IF_NONE_MATCH'] == $ETag) {
126                $Document->status = 304; // Not modified
127                accesrestreint_afficher_document_selon_status($Document);
128                exit;
129        }
130
131        header('ETag: ' . $ETag);
132
133        // Verifier les droits de lecture du document
134        if (!$Document->est_autorise()) {
135                $Document->status = 403;
136        }
137
138        // envoyer le document
139        accesrestreint_afficher_document_selon_status($Document);
140}
141
142/**
143 * Supprimer et vider les buffers qui posent des problemes de memory limit
144 *
145 * @link http://www.php.net/manual/en/function.readfile.php#81032
146 *
147 * @return void
148**/
149function accesrestreint_vider_buffers() {
150        @ini_set("zlib.output_compression","0"); // pour permettre l'affichage au fur et a mesure
151        @ini_set("output_buffering","off");
152        @ini_set('implicit_flush', 1);
153        @ob_implicit_flush(1);
154        $level = ob_get_level();
155        while ($level--){
156                @ob_end_clean();
157        }
158}
159
160/**
161 * Envoie le document au navigateur, en fonction de son status http
162 *
163 * @uses accesrestreint_afficher_erreur_document()
164 * @uses accesrestreint_afficher_document()
165 *
166 * @param Accesrestreint_document $Document
167 * @return void
168**/
169function accesrestreint_afficher_document_selon_status(Accesrestreint_document $Document) {
170        switch ($Document->status) {
171
172                case 304:
173                case 403:
174                case 404:
175                        accesrestreint_afficher_erreur_document($Document->status);
176                        break;
177
178                default:
179                        accesrestreint_afficher_document($Document);
180                        break;
181        }
182}
183
184
185/**
186 * Affiche une page indiquant un document introuvable ou interdit
187 *
188 * @param string $status
189 *     Numero d'erreur (403 ou 404)
190 * @return void
191**/
192function accesrestreint_afficher_erreur_document($status = 404) {
193
194        switch ($status)
195        {
196                case 304:
197                        // not modified : sortir de suite !
198                        http_status(304);
199                        exit;
200
201                case 403:
202                        include_spip('inc/minipres');
203                        echo minipres("","","",true);
204                        break;
205
206                case 404:
207                        http_status(404);
208                        include_spip('inc/minipres');
209                        echo minipres(_T('erreur') . ' 404', _T('medias:info_document_indisponible'), "", true);
210                        break;
211        }
212}
213
214/**
215 * Envoie le document au navigateur
216 *
217 * Soit en document attaché, soit en direct
218 *
219 * @param Accesrestreint_document $Document
220 * @return void
221**/
222function accesrestreint_afficher_document(Accesrestreint_document $Document) {
223
224        $chemin_fichier = $Document->get_chemin_complet_fichier();
225
226        // document décrit dans la table spip_documents ?
227        if ($doc = $Document->get_spip_document()) {
228                if ($doc['mime_type']) {
229                        header("Content-Type: " . $doc['mime_type']);
230                }
231
232                // pour les images ne pas passer en attachment
233                // sinon, lorsqu'on pointe directement sur leur adresse,
234                // le navigateur les downloade au lieu de les afficher
235                if ($doc['inclus'] == 'non') {
236                        $f = basename($chemin_fichier);
237                        // ce content-type est necessaire pour eviter des corruptions de zip dans ie6
238                        header('Content-Type: application/octet-stream');
239
240                        header("Content-Disposition: attachment; filename=\"$f\";");
241                        header("Content-Transfer-Encoding: binary");
242
243                        // fix for IE catching or PHP bug issue
244                        header("Pragma: public");
245                        header("Expires: 0"); // set expiration time
246                        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
247                } else {
248                        header("Expires: 3600"); // set expiration time
249                }
250        } else {
251                header("Expires: 3600"); // set expiration time
252        }
253
254
255        if ($size = filesize($chemin_fichier)) {
256                header("Content-Length: ". $size);
257        }
258
259        readfile($chemin_fichier);
260}
261
262
263/**
264 * Décrit un document qu'un utilisateur cherche à afficher.
265 *
266 * @note
267 *     La propriété 'status' est publique et servira à déterminer
268 *     quel type de status http transmettre.
269**/
270class Accesrestreint_document {
271
272        /**
273         * Identifiant du document demandé, si connu
274         *
275         * 0 si document inconnu.
276         *
277         * @var int
278        **/
279        private $id_document = 0;
280
281        /**
282         * Clé d'action auteur, si connue
283         *
284         * 0 si inconnue
285         *
286         * @var int|string
287        **/
288        private $cle_action = 0;
289
290        /**
291         * Chemin du fichier demandé, depuis IMG/
292         *
293         * @var string
294        **/
295        private $_fichier = "";
296
297        /**
298         * Status HTTP à utiliser pour ce document
299         *
300         * @var string|int
301        **/
302        public $status = "";
303
304
305        /**
306         * Constructeur
307         *
308         * @param int $_fichier
309         *     Chemin du fichier demandé, depuis IMG/
310         * @param int $id_document
311         *     Identifiant du document, si connu
312         * @param int|string $cle_action
313         *     Clé d'action auteur, si connue
314         * @return
315        **/
316        public function __construct($_fichier, $id_document = 0, $cle_action = 0) {
317                $this->_fichier = $_fichier;
318                $this->id_document = $id_document;
319                $this->cle_action  = $cle_action;
320        }
321
322        /**
323         * Récupérer le chemin du fichier (depuis IMG/)
324         * @return string
325        **/
326        public function get_chemin_fichier() {
327                return $this->_fichier;
328        }
329
330        /**
331         * Récupérer le numéro de document
332         * @return int
333        **/
334        public function get_id_document() {
335                return $this->document;
336        }
337
338        /**
339         * Récupérer la clé d'action
340         * @return int|string
341        **/
342        public function get_cle_action() {
343                return $this->cle_action;
344        }
345
346
347        /**
348         * Test si le document demandé vérifie simplement le fonctionnement correct du .htaccess dans IMG posé par Acces Restreint
349         *
350         * @see accesrestreint_gerer_htaccess()
351         *
352         * return bool
353         *     True si document de test
354         */
355        public function est_un_test() {
356                return  $this->id_document == 0
357                        AND $this->cle_action == 1
358                        AND $this->_fichier == "test/.test";
359        }
360
361
362        /**
363         * Test si le document demandé n'a pas un chemin d'accès sécurisé
364         *
365         * Par exemple avec ../ ou url absolue http://
366         *
367         * @return bool
368         *     True si le chemin est insécurisé
369        **/
370        public function est_insecurise() {
371                return (strpos($this->_fichier, '../') !== false)
372                        OR (preg_match(',^\w+://,', $this->_fichier));
373        }
374
375        /**
376         * Test si le fichier existe
377         *
378         * @return bool True si existe
379        **/
380        public function est_existant() {
381                $chemin_fichier = $this->get_chemin_complet_fichier();
382                return file_exists($chemin_fichier);
383        }
384
385        /**
386         * Test si le fichier est accessible en lecture
387         *
388         * @return bool True si lisible
389        **/
390        public function est_lisible() {
391                $chemin_fichier = $this->get_chemin_complet_fichier();
392                return is_readable($chemin_fichier);
393        }
394
395
396        /**
397         * Test si un document est dans un sous-répertoire à la racine de IMG
398         *
399         * @param string|array $repertoires
400         *     Nom d'un ou plusieurs répertoires
401         * @return bool
402         *     True si le document est contenu dans un de ces répertoires
403        **/
404        public function est_dans_repertoire($repertoires) {
405                if (!$repertoires) {
406                        return false;
407                }
408                if (is_string($repertoires)) {
409                        $repertoires = array($repertoires);
410                }
411                $repertoires = array_filter($repertoires);
412
413                return preg_match('%^(' . implode('|', $repertoires) . ')/%', $this->_fichier);
414        }
415
416
417        /**
418         * Test l'autorisation de voir ce document (spip)
419         *
420         * @return bool True si on peut voir le document
421        **/
422        public function est_autorise() {
423                $doc = $this->get_spip_document();
424                if (!$doc) {
425                        spip_log("acces interdit, document hors de spip_documents");
426                        return false;
427                }
428
429                // en controlant la cle passee en argument si elle est dispo
430                // (perf issue : toutes les urls ont en principe cette cle fournie dans la page au moment du calcul de la page)
431                if ($this->cle_action){
432                        include_spip('inc/securiser_action');
433                        if (!verifier_cle_action($doc['id_document'].','.$this->_fichier, $this->cle_action)) {
434                                spip_log("acces interdit $this->cle_action erronee");
435                                return false;
436                        }
437                        return true;
438                }
439               
440                // en verifiant le droit explicitement sinon, plus lent !
441                if (!function_exists("autoriser")) {
442                        include_spip("inc/autoriser");
443                }
444
445                if (!autoriser('voir', 'document', $doc['id_document'])) {
446                        spip_log("acces interdit, pas autorise a voir le document " . $doc['id_document']);
447                        return false;
448                }
449
450                return true;
451        }
452
453
454        /**
455         * Retourne la description du document dans la table spip_documents (et spip_types_documents)
456         *
457         * Seulement si ce fichier est un document dans SPIP
458         *
459         * @return array Description du document dans SPIP
460        **/
461        public function get_spip_document() {
462                static $doc = null;
463                if (is_null($doc)) {
464                        include_spip('inc/documents');
465                        $where = "documents.fichier = ".sql_quote(set_spip_doc($this->get_chemin_complet_fichier()))
466                                . ($this->id_document ? " AND documents.id_document=".intval($this->id_document): '');
467                        spip_log($where, 'dbg');
468
469                        $doc = sql_fetsel(
470                                "documents.id_document, documents.titre, documents.fichier, types.mime_type, types.inclus, documents.extension",
471                                "spip_documents AS documents LEFT JOIN spip_types_documents AS types ON documents.extension=types.extension",
472                                $where
473                        );
474
475                        spip_log($doc, 'dbg');
476                        if (!$doc) {
477                                $doc = array();
478                        }
479                }
480                return $doc;
481        }
482
483        /**
484         * Calcule et retourne le chemin complet  du fichier (depuis la racine du site)
485         *
486         * @return string
487        **/
488        public function get_chemin_complet_fichier() {
489                static $fichier = null;
490                if (is_null($fichier)) {
491                        include_spip('inc/documents');
492                        $fichier = get_spip_doc($this->_fichier);
493                        spip_log($fichier, 'dbg');
494                }
495                return $fichier;
496        }
497
498        /**
499         * Calcule et retourne le hash Etag du fichier
500         *
501         * @return string
502        **/
503        public function get_ETag() {
504                static $ETag = null;
505                if (is_null($ETag)) {
506                        $ETag = md5($this->get_chemin_complet_fichier() . ': '. filemtime($this->get_chemin_complet_fichier()));
507                }
508                return $ETag;
509        }
510
511}
Note: See TracBrowser for help on using the repository browser.