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

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

Ce que je croyais être une erreur sur l'ancien script, à savoir que parfois un 'Content Type' était envoyé sans valeur
(mime type non connu lorsque le document n'est pas dans spip_documents), n'en était pas vraiment une.

Firefox par exemple n'appréciait pas de recevoir le contenu d'un pdf si l'expression content type n'était pas présente (même vide).
Donc voilà, le content type est maintenant toujours envoyé.

Du coup, on peut aussi par exemple maintenant en forcer un avec $Document->set_mime_type('application/pdf') dans le pipeline.

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