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

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

Récupérer tous les champs du document SPIP pour que les pipelines éventuels qui utilisent $Document->get_spip_document()
pour tester certaines particularités n'aient pas à refaire une requête SQL pour un champ qui manquerait.

+ Coquille dans la methode get_id_document() !

File size: 13.9 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        // toujours envoyer un content type, meme vide !
227        header("Content-Type: " . $Document->get_mime_type());
228
229        // document décrit dans la table spip_documents ?
230        if ($doc = $Document->get_spip_document()) {
231                // On passe les infos précises du document à afficher dans un pipeline
232                $doc = pipeline('accesrestreint_afficher_document', array(
233                        'args' => array('document' => $Document),
234                        'data' => $doc,
235                ));
236               
237                // pour les images ne pas passer en attachment
238                // sinon, lorsqu'on pointe directement sur leur adresse,
239                // le navigateur les downloade au lieu de les afficher
240                if ($doc['inclus'] == 'non') {
241                        $f = basename($chemin_fichier);
242                        // ce content-type est necessaire pour eviter des corruptions de zip dans ie6
243                        header('Content-Type: application/octet-stream');
244
245                        header("Content-Disposition: attachment; filename=\"$f\";");
246                        header("Content-Transfer-Encoding: binary");
247
248                        // fix for IE catching or PHP bug issue
249                        header("Pragma: public");
250                        header("Expires: 0"); // set expiration time
251                        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
252                } else {
253                        header("Expires: 3600"); // set expiration time
254                }
255        } else {
256                header("Expires: 3600"); // set expiration time
257        }
258
259
260        if ($size = filesize($chemin_fichier)) {
261                header("Content-Length: ". $size);
262        }
263
264        readfile($chemin_fichier);
265}
266
267
268/**
269 * Décrit un document qu'un utilisateur cherche à afficher.
270 *
271 * @note
272 *     La propriété 'status' est publique et servira à déterminer
273 *     quel type de status http transmettre.
274**/
275class Accesrestreint_document {
276
277        /**
278         * Identifiant du document demandé, si connu
279         *
280         * 0 si document inconnu.
281         *
282         * @var int
283        **/
284        private $id_document = 0;
285
286        /**
287         * Clé d'action auteur, si connue
288         *
289         * 0 si inconnue
290         *
291         * @var int|string
292        **/
293        private $cle_action = 0;
294
295        /**
296         * Chemin du fichier demandé, depuis IMG/
297         *
298         * @var string
299        **/
300        private $_fichier = "";
301
302        /**
303         * Status HTTP à utiliser pour ce document
304         *
305         * @var string|int
306        **/
307        public $status = "";
308
309        /**
310         * Mime type pour ce document
311         *
312         * @var string
313        **/
314        private $mime_type = "";
315
316
317        /**
318         * Constructeur
319         *
320         * @param int $_fichier
321         *     Chemin du fichier demandé, depuis IMG/
322         * @param int $id_document
323         *     Identifiant du document, si connu
324         * @param int|string $cle_action
325         *     Clé d'action auteur, si connue
326         * @return
327        **/
328        public function __construct($_fichier, $id_document = 0, $cle_action = 0) {
329                $this->_fichier = $_fichier;
330                $this->id_document = $id_document;
331                $this->cle_action  = $cle_action;
332        }
333
334        /**
335         * Récupérer le chemin du fichier (depuis IMG/)
336         * @return string
337        **/
338        public function get_chemin_fichier() {
339                return $this->_fichier;
340        }
341
342        /**
343         * Récupérer le numéro de document
344         * @return int
345        **/
346        public function get_id_document() {
347                return $this->id_document;
348        }
349
350        /**
351         * Récupérer la clé d'action
352         * @return int|string
353        **/
354        public function get_cle_action() {
355                return $this->cle_action;
356        }
357
358
359        /**
360         * Test si le document demandé vérifie simplement le fonctionnement correct du .htaccess dans IMG posé par Acces Restreint
361         *
362         * @see accesrestreint_gerer_htaccess()
363         *
364         * return bool
365         *     True si document de test
366         */
367        public function est_un_test() {
368                return  $this->id_document == 0
369                        AND $this->cle_action == 1
370                        AND $this->_fichier == "test/.test";
371        }
372
373
374        /**
375         * Test si le document demandé n'a pas un chemin d'accès sécurisé
376         *
377         * Par exemple avec ../ ou url absolue http://
378         *
379         * @return bool
380         *     True si le chemin est insécurisé
381        **/
382        public function est_insecurise() {
383                return (strpos($this->_fichier, '../') !== false)
384                        OR (preg_match(',^\w+://,', $this->_fichier));
385        }
386
387        /**
388         * Test si le fichier existe
389         *
390         * @return bool True si existe
391        **/
392        public function est_existant() {
393                $chemin_fichier = $this->get_chemin_complet_fichier();
394                return file_exists($chemin_fichier);
395        }
396
397        /**
398         * Test si le fichier est accessible en lecture
399         *
400         * @return bool True si lisible
401        **/
402        public function est_lisible() {
403                $chemin_fichier = $this->get_chemin_complet_fichier();
404                return is_readable($chemin_fichier);
405        }
406
407
408        /**
409         * Test si un document est dans un sous-répertoire à la racine de IMG
410         *
411         * @param string|array $repertoires
412         *     Nom d'un ou plusieurs répertoires
413         * @return bool
414         *     True si le document est contenu dans un de ces répertoires
415        **/
416        public function est_dans_repertoire($repertoires) {
417                if (!$repertoires) {
418                        return false;
419                }
420                if (is_string($repertoires)) {
421                        $repertoires = array($repertoires);
422                }
423                $repertoires = array_filter($repertoires);
424
425                return preg_match('%^(' . implode('|', $repertoires) . ')/%', $this->_fichier);
426        }
427
428
429        /**
430         * Test l'autorisation de voir ce document (spip)
431         *
432         * @return bool True si on peut voir le document
433        **/
434        public function est_autorise() {
435                $doc = $this->get_spip_document();
436                if (!$doc) {
437                        spip_log("acces interdit, document hors de spip_documents");
438                        return false;
439                }
440
441                // en controlant la cle passee en argument si elle est dispo
442                // (perf issue : toutes les urls ont en principe cette cle fournie dans la page au moment du calcul de la page)
443                if ($this->cle_action && !defined('ACCES_RESTREINT_FORCE_AUTORISE')) {
444                        include_spip('inc/securiser_action');
445                        if (!verifier_cle_action($doc['id_document'].','.$this->_fichier, $this->cle_action)) {
446                                spip_log("acces interdit $this->cle_action erronee");
447                                return false;
448                        }
449                        return true;
450                }
451               
452                // en verifiant le droit explicitement sinon, plus lent !
453                if (!function_exists("autoriser")) {
454                        include_spip("inc/autoriser");
455                }
456
457                if (!autoriser('voir', 'document', $doc['id_document'])) {
458                        spip_log("acces interdit, pas autorise a voir le document " . $doc['id_document']);
459                        return false;
460                }
461
462                return true;
463        }
464
465
466        /**
467         * Retourne la description du document dans la table spip_documents (et spip_types_documents)
468         *
469         * Seulement si ce fichier est un document dans SPIP
470         *
471         * @return array Description du document dans SPIP
472        **/
473        public function get_spip_document() {
474                static $doc = null;
475                if (is_null($doc)) {
476                        include_spip('inc/documents');
477                        $where = "documents.fichier = ".sql_quote(set_spip_doc($this->get_chemin_complet_fichier()))
478                                . ($this->id_document ? " AND documents.id_document=".intval($this->id_document): '');
479                        spip_log($where, 'dbg');
480
481                        $doc = sql_fetsel(
482                                "documents.*, types.mime_type, types.inclus",
483                                "spip_documents AS documents LEFT JOIN spip_types_documents AS types ON documents.extension=types.extension",
484                                $where
485                        );
486
487                        spip_log($doc, 'dbg');
488                        if (!$doc) {
489                                $doc = array();
490                        }
491                }
492                return $doc;
493        }
494
495        /**
496         * Calcule et retourne le chemin complet  du fichier (depuis la racine du site)
497         *
498         * @return string
499        **/
500        public function get_chemin_complet_fichier() {
501                static $fichier = null;
502                if (is_null($fichier)) {
503                        include_spip('inc/documents');
504                        $fichier = get_spip_doc($this->_fichier);
505                        spip_log($fichier, 'dbg');
506                }
507                return $fichier;
508        }
509
510        /**
511         * Calcule et retourne le hash Etag du fichier
512         *
513         * @return string
514        **/
515        public function get_ETag() {
516                static $ETag = null;
517                if (is_null($ETag)) {
518                        $ETag = md5($this->get_chemin_complet_fichier() . ': '. filemtime($this->get_chemin_complet_fichier()));
519                }
520                return $ETag;
521        }
522
523        /**
524         * Calcule et retourne un content type
525         *
526         * Cherche
527         * - un content type déjà indiqué,
528         * - sinon dans le document spip,
529         * - sinon rien
530         *
531         * @note
532         *     Tester l'extension du fichier si on n'en trouve pas ?
533         *
534         * @param bool $calculer
535         *     Calculer le mime type si absent à partir de spip_documents
536         * @return string
537        **/
538        public function get_mime_type($calculer = true) {
539                if ($this->mime_type) {
540                        return $this->mime_type;
541                }
542                if (!$calculer) {
543                        return "";
544                }
545                if ($doc = $this->get_spip_document()) {
546                        if ($doc['mime_type']) {
547                                return $doc['mime_type'];
548                        }
549                }
550                return "";
551        }
552
553        /**
554         * Définit un type mime pour ce document
555         *
556         * @param string $mime_type Mime type
557        **/
558        public function set_mime_type($mime_type) {
559                $this->mime_type = $mime_type;
560        }
561}
Note: See TracBrowser for help on using the repository browser.