<?php
// WebJaxe, Observatoire de Paris, licence GPL
// encodage : UTF-8

// lecture d'un fichier du répertoire contribXML ou du répertoire sites avec vérification du droit d'accès
// utilisation: lecture_fichier.php?fichier=chemin/vers/le/fichier (sur le serveur)

session_cache_limiter('private'); // pour éviter no-cache qui est utilisé par défaut avec les sessions

session_start();

if (!isset($_SESSION['langue']) || !$_SESSION['langue'])
    $_SESSION['langue'] = "fr";
$fichierlangue = "../../langues/".$_SESSION['langue'].".php";
require_once($fichierlangue);
require_once("../include/utilisateurs.php");
require_once("../include/contributions.php");
require_once("../include/projets.php");
require_once("../include/outils.php");
require_once("../include/base_de_donnees.php");

init_erreurs_texte();

$_SESSION['section'] = 'utilisateur';

if (!isset($_SESSION['id_utilisateur'])) {
// l'erreur peut être causée sur Firefox qui ne transmet pas les cookies de session à l'applet
//   quand "Accepter les cookies tiers" est désactivé (bugzilla #441166)
    header('HTTP/1.0 403 Forbidden');
    header("Pragma: no-cache");
    header("Cache-Control: no-cache");
    header("Expires: 0");
    header("content-type:text/plain; charset=utf-8");
    echo PAS_DE_SESSION;
    exit;
}

if (!isset($_GET['fichier']))
    erreur(PARAMETRES_MANQUANT);

$id_utilisateur = $_SESSION['id_utilisateur'];
$chemin = $_GET['fichier'];

// vérif que le chemin commence bien par le chemin vers le dossier de WebJaxe
$urlb = $_SERVER['PHP_SELF'];
$path_parts = pathinfo($urlb);
$dossier_racine = $path_parts['dirname'];
$tdr = explode('/', $dossier_racine);
array_splice($tdr, count($tdr) - 2);
$dossier_webjaxe = implode('/', $tdr);
$pos = strpos($chemin, $dossier_webjaxe);
if ($pos === false || $pos != 0) {
    header('HTTP/1.0 403 Forbidden');
    header("content-type:text/plain; charset=utf-8");
    echo PAS_AUTORISE.' - '.$chemin;
    exit;
}

// vérif que le fichier demandé est bien dans le répertoire contribXML ou le répertoire sites
$chemin_relatif = substr($chemin, strlen($dossier_webjaxe));
$real_chemin = realpath('../..'.$chemin_relatif);
if ($real_chemin == false) {
    header('HTTP/1.0 404 Not Found');
    header("content-type:text/plain; charset=utf-8");
    echo '404 Not Found';
    exit;
}
$real_contribXML = realpath("../../contribXML");
$real_sites = realpath("../../sites");
$pos = strpos($real_chemin, $real_contribXML);
$dans_contribXML = !($pos === false || $pos != 0);
$pos = strpos($real_chemin, $real_sites);
$dans_sites = !($pos === false || $pos != 0);
if (!$dans_contribXML && !$dans_sites) {
    header('HTTP/1.0 403 Forbidden');
    header("content-type:text/plain; charset=utf-8");
    echo PAS_AUTORISE.' - '.$chemin;
    exit;
}

// obtention du nom de la contribution
if ($dans_contribXML)
    $real_avant_nom_contrib = $real_contribXML.'/';
else
    $real_avant_nom_contrib = $real_sites.'/';
$contrib = substr($real_chemin, strlen($real_avant_nom_contrib));
$pos = strpos($contrib, '/');
if ($pos === false) {
    header('HTTP/1.0 403 Forbidden');
    header("content-type:text/plain; charset=utf-8");
    echo PAS_AUTORISE.' - '.$chemin;
    exit;
}
$contrib = substr($contrib, 0, $pos);

// vérif que l'utilisateur a le droit de lecture de la contribution
verifier_l_utilisateur($id_utilisateur, false, $contrib);

// envoi du contenu du fichier
if (!file_exists($real_chemin)) {
    header('HTTP/1.0 404 Not Found');
    header("content-type:text/plain; charset=utf-8");
    echo '404 Not Found';
    exit;
} else {
    $mime = getMimeType($real_chemin);
    header('Content-Type: '.$mime);
    if (isset($_SERVER['HTTP_RANGE'])) {
        session_write_close();
        rangeDownload($real_chemin);
    } else {
        header('Content-Length: '.filesize($real_chemin));
        header('Last-Modified: '.date('r', filemtime($real_chemin)));
        if ($mime == 'text/html')
            $expires = 5 * 60;
        else if ($mime == 'text/css' || $mime == 'application/java-archive' || $mime == 'application/x-shockwave-flash' ||
                $mime == 'application/octet-stream' || $mime == 'application/xml')
            $expires = 3 * 60 * 60;
        else
            $expires = 15 * 60;
        //header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');
        // avec private, Expires pourrait être mal interprété par les anciens caches (le défaut est 1981 donc expiré immédiatement)
        header('Cache-Control: private, max-age='.$expires.', pre-check='.$expires.', must-revalidate');
        session_write_close();
        readfile($real_chemin);
    }
}


function getMimeType($file_path) {
    $mtype = '';
    $mime_types = array(
        'carte' => 'text/xml',
        'class' => 'application/java',
        'css' => 'text/css',
        'eot' => 'application/vnd.ms-fontobject',
        'fits' => 'image/fits',
        'htm' => 'text/html',
        'html' => 'text/html',
        'jar' => 'application/java-archive',
        'jpeg' => 'image/jpeg',
        'jpg' => 'image/jpeg',
        'js' => 'application/javascript',
        'gif' => 'image/gif',
        'gz' => 'application/x-gzip',
        'mng' => 'video/x-mng',
        'mp4' => 'video/mp4',
        'mpeg' => 'video/mpeg',
        'mpg' => 'video/mpeg',
        'ogv' => 'video/ogg',
        'pdf' => 'application/pdf',
        'png' => 'image/png',
        'properties' => 'text/plain',
        'swf' => 'application/x-shockwave-flash',
        'ttf' => 'application/octet-stream',
        'txt' => 'text/plain',
        'webm' => 'video/webm',
        'xhtml' => 'application/xhtml+xml',
        'xml' => 'application/xml',
        'xsd' => 'application/xml',
        'zip' => 'application/zip'
    );

    $ext = strtolower(substr($file_path, strrpos($file_path, '.') + 1));
    if (array_key_exists($ext, $mime_types)) {
        $mtype = $mime_types[$ext];
    } else if (function_exists('mime_content_type')) {
         $mtype = mime_content_type($file_path);
    } else if (function_exists('finfo_file')) {
        $finfo = finfo_open(FILEINFO_MIME);
        $mtype = finfo_file($finfo, $file_path);
        finfo_close($finfo);  
    }
    if ($mtype == '') {
        $mtype = 'application/octet-stream';
    }
    return $mtype;
}

// from http://mobiforge.com/developing/story/content-delivery-mobile-devices
// thanks to Thomas Thomassen
function rangeDownload($file) {

	$fp = @fopen($file, 'rb');
 
	$size   = filesize($file); // File size
	$length = $size;           // Content length
	$start  = 0;               // Start byte
	$end    = $size - 1;       // End byte
	// Now that we've gotten so far without errors we send the accept range header
	/* At the moment we only support single ranges.
	 * Multiple ranges requires some more work to ensure it works correctly
	 * and comply with the spesifications: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
	 *
	 * Multirange support annouces itself with:
	 * header('Accept-Ranges: bytes');
	 *
	 * Multirange content must be sent with multipart/byteranges mediatype,
	 * (mediatype = mimetype)
	 * as well as a boundry header to indicate the various chunks of data.
	 */
	header("Accept-Ranges: 0-$length");
	// header('Accept-Ranges: bytes');
	// multipart/byteranges
	// http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.2
	if (isset($_SERVER['HTTP_RANGE'])) {
 
		$c_start = $start;
		$c_end   = $end;
		// Extract the range string
		list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
		// Make sure the client hasn't sent us a multibyte range
		if (strpos($range, ',') !== false) {
 
			// (?) Shoud this be issued here, or should the first
			// range be used? Or should the header be ignored and
			// we output the whole content?
			header('HTTP/1.1 416 Requested Range Not Satisfiable');
			header("Content-Range: bytes $start-$end/$size");
			// (?) Echo some info to the client?
			exit;
		}
		// If the range starts with an '-' we start from the beginning
		// If not, we forward the file pointer
		// And make sure to get the end byte if spesified
		if ($range{0} == '-') {
 
			// The n-number of the last bytes is requested
			$c_start = $size - substr($range, 1);
		}
		else {
 
			$range  = explode('-', $range);
			$c_start = $range[0];
			$c_end   = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
		}
		/* Check the range and make sure it's treated according to the specs.
		 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
		 */
		// End bytes can not be larger than $end.
		$c_end = ($c_end > $end) ? $end : $c_end;
		// Validate the requested range and return an error if it's not correct.
		if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
 
			header('HTTP/1.1 416 Requested Range Not Satisfiable');
			header("Content-Range: bytes $start-$end/$size");
			// (?) Echo some info to the client?
			exit;
		}
		$start  = $c_start;
		$end    = $c_end;
		$length = $end - $start + 1; // Calculate new content length
		fseek($fp, $start);
		header('HTTP/1.1 206 Partial Content');
	}
	// Notify the client the byte range we'll be outputting
	header("Content-Range: bytes $start-$end/$size");
	header("Content-Length: $length");
 
	// Start buffered download
	$buffer = 1024 * 8;
	while(!feof($fp) && ($p = ftell($fp)) <= $end) {
 
		if ($p + $buffer > $end) {
 
			// In case we're only outputtin a chunk, make sure we don't
			// read past the length
			$buffer = $end - $p + 1;
		}
		set_time_limit(0); // Reset time limit for big files
		echo fread($fp, $buffer);
		flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
	}
 
	fclose($fp);
}

?>
