2010-02-08 9 views
6

Ich benutze erzwungene Download zum Download hauptsächlich Reißverschlüsse und mp3s vor Ort habe ich (http://pr1pad.kissyour.net) - um Downloads in Google Analytics zu verfolgen, in der Datenbank und zu verstecken echten Download-Pfad:Idiot-proof, browserübergreifend erzwingen download in PHP

Es ist diese:

extending CI model 

... - bunch of code 

function _fullread ($sd, $len) { 
$ret = ''; 
$read = 0; 
while ($read < $len && ($buf = fread($sd, $len - $read))) { 
    $read += strlen($buf); 
    $ret .= $buf; 
} 
return $ret; 
} 

function download(){  
    /* DOWNLOAD ITSELF */ 

    ini_set('memory_limit', '160M'); 
    apache_setenv('no-gzip', '1'); 
    ob_end_flush(); 

    header("Pragma: public"); 
    header("Expires: 0"); 
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 
    header("Cache-Control: public",FALSE); 
    header("Content-Description: File Transfer"); 
    header("Content-type: application/octet-stream"); 
    if (isset($_SERVER['HTTP_USER_AGENT']) && 
     (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false)) 
     header('Content-Type: application/force-download'); //IE HEADER 
    header("Accept-Ranges: bytes"); 
    header("Content-Disposition: attachment; filename=\"" . basename("dir-with- files/".$filename) . "\";"); 
    header("Content-Transfer-Encoding: binary"); 
    header("Content-Length: " . filesize("dir-with-files/".$filename)); 

    // Send file for download 
    if ($stream = fopen("dir-with-files/$filename", 'rb')){ 
    while(!feof($stream) && connection_status() == 0){ 
     //reset time limit for big files 
     set_time_limit(0); 
     print($this->_fullread($stream,1024*16)); 
     flush(); 
    } 
    fclose($stream); 
    } 
} 

Es ist auf LAMP mit CI 1.7.2 - Es ist meine eigene Methode zusammengestellt aus verschiedenen how-Tos alle über das Internet, weil während der Entwicklung, die Lösung dieser Probleme kam: - Serverlimit. ini_set nicht geholfen haben, so habe ich _fullread statt normalen fread gepuffert, die insted von @readonly verwendet wurde - ob_end_flush(), weil, ist nur in CI1.7.2 tat und ich brauchte Puffer

Jetzt Es ... reinigen funktioniert nicht. Es tat, dann hörte es auf, die erwartete Größe/Download-Zeit zu zeigen - ich versuchte es aufzuräumen und während ich den Code aufräumte, passierte etwas, ich weiß nicht was und in irgendeine vorherige Version - es hat nicht funktioniert (keine Änderung in Einstellungen überhaupt) - bearbeiten: nicht arbeiten = gibt alles ins Browserfenster aus.

Also ich sagte, schrauben Sie es, ich werde hier suchen.

Also, ich sehe im Grunde für Skript oder eine Funktion, die ich zu meinem Output-Modell setzen und tun:

  • Anrufkraft Download (in Chrome Downloads zu starten, in IE, FF, Safari geöffnet die modal öffnen/speichern/abbrechen)
  • anzeigen Größe der Datei und die geschätzte dl Zeit (das ist bis zu Browser, ich weiß, aber zuerst müssen, Browser Dateigröße
  • WORK (getestet & bestätigt wissen!) in IE6,7 , 8, FF3, Opera, Chrome & und Safari auf PC + Mac (Linux ... ich don ' t Pflege wirklich) - das ist für Kopfteil
  • auf dem Server, ich habe Grenze auch so etwas wie 56MB Speicher, die ich nicht hinzufügen können, so dass auch wichtig

Danke im Voraus.

bearbeiten: Jetzt fühle ich mich mehr denn je/vor geschraubt, da ich Download mit .htaccess zu zwingen versucht - während es funktionierte, es einige kleinere/Haupt (Pick Ihre) Probleme

es
  • hatte zeigte vollständigen Pfad (für mich minor)
  • es wartet, bis ganzer Download es herunterzuladen (zeigen als „Verbinden“) und dann zeigen, gerade fertig ist - und Downloads in einer Sekunde (Dur für mich)

Jetzt, obwohl ich .htac gelöscht habe cess, es wartet immer noch, bis der Download abgeschlossen ist (genau so, als würde es zuerst in den Cache geladen werden) und es wird einfach connected angezeigt und öffnet/speichert den Dialog.

+0

verwandt: http://stackoverflow.com/questions/1465573/forcing-to-download-a-file-using-php –

Antwort

6

Also, habe ich diesen Code (It-Version fortsetzbar http-Download im Internet gefunden geändert hat)

function _output_file($file, $path) 
{ 
    $size = filesize($path.$file); 

    @ob_end_clean(); //turn off output buffering to decrease cpu usage 

    // required for IE, otherwise Content-Disposition may be ignored 
    if(ini_get('zlib.output_compression')) 
    ini_set('zlib.output_compression', 'Off'); 

    header('Content-Type: application/force-download'); 
    header('Content-Disposition: attachment; filename="'.basename($file).'"'); 
    header("Content-Transfer-Encoding: binary"); 
    header('Accept-Ranges: bytes'); 

    /* The three lines below basically make the 
    download non-cacheable */ 
    header("Cache-control: no-cache, pre-check=0, post-check=0"); 
    header("Cache-control: private"); 
    header('Pragma: private'); 
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 

    // multipart-download and download resuming support 
    if(isset($_SERVER['HTTP_RANGE'])) 
    { 
     list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2); 
     list($range) = explode(",",$range,2); 
     list($range, $range_end) = explode("-", $range); 
     $range=intval($range); 
     if(!$range_end) { 
      $range_end=$size-1; 
     } else { 
      $range_end=intval($range_end); 
     } 

     $new_length = $range_end-$range+1; 
     header("HTTP/1.1 206 Partial Content"); 
     header("Content-Length: $new_length"); 
     header("Content-Range: bytes $range-$range_end/$size"); 
    } else { 
     $new_length=$size; 
     header("Content-Length: ".$size); 
    } 

    /* output the file itself */ 
    $chunksize = 1*(1024*1024); //you may want to change this 
    $bytes_send = 0; 
    if ($file = fopen($path.$file, 'rb')) 
    { 
     if(isset($_SERVER['HTTP_RANGE'])) 
     fseek($file, $range); 

     while 
      (!feof($file) && 
      (!connection_aborted()) && 
      ($bytes_send<$new_length)) 
     { 
      $buffer = fread($file, $chunksize); 
      print($buffer); //echo($buffer); // is also possible 
      flush(); 
      $bytes_send += strlen($buffer); 
     } 
    fclose($file); 
    } else die('Error - can not open file.'); 

die(); 
} 

und dann in Modell:

function download_file($filename){ 
    /* 
     DOWNLOAD 
    */ 
    $path = "datadirwithmyfiles/"; //directory 

    //track analytics 

    include('includes/Galvanize.php'); //great plugin 
    $GA = new Galvanize('UA-XXXXXXX-7'); 
    $GA->trackPageView(); 

    $this->_output_file($filename, $path); 

} 

Es ist, als in allen mentiond Browser erwartet funktioniert auf Win/MAC - bisher keine Probleme damit.

+0

Könnten Sie mir vielleicht helfen zu verstehen, wie ich das bitte zur Arbeit bringen kann? Ich habe ein Array von Links ausgegeben - für JPEGs auf meiner Wordpress-Site, die ich zum Download zwingen möchte. Muss ich dieses Skript in eine download.php-Datei schreiben und die Download-Links wie folgt ausgeben ...? http://mysite.com/scripts/download.php?http://mysite.com/wp-content/uploads/2012/04/Qatar-FRI-Pic-7-188x118.jpg Alle Ratschläge oder Links zu das ursprüngliche Tutorial würde sehr geschätzt werden. – Joshc

+0

Hey @Joshc - so ziemlich das; Sie legen beide Funktionen in Ihre Download-Datei und rufen Sie download.php? Something.jpg auf. Ich ziehe es vor, den vollständigen Pfad zum Benutzer nicht anzuzeigen (als Teil der Vorbeugung böswilliger Absichten - ein anderer Teil säubert Ihre Eingaben ordnungsgemäß, z. B. Pfade, die außerhalb des Ordners "datadirwithmyfiles" liegen. –

+0

Lädt eine HTML-Datei anstelle der Datei selbst herunter. –

0

print($this->_fullread($stream,1024*16));

I _fullread nehmen ist innerhalb einer Klasse? Wenn der Code wie oben aussieht, würde $this-> nicht funktionieren.

Gibt es den Dateiinhalt auf dem Bildschirm aus, wenn Sie alle Kopfzeilen auskommentiert haben?

+0

Hallo, ja, es ist in der Klasse - es ist tatsächlich CI-Modell erweitert. Und nein, es gibt alle Header-Dateien zum Browser aus. –

+0

Ich habe auch Code bearbeitet, damit Sie wissen :) –

0

Nur ein Schuss in die Dunkelheit ... jeder Header, den ich in meinem 'force download' Code sende (der nicht so gut getestet ist wie Ihr), ist der gleiche wie Ihr, außer ich rufe: header ("Cache -Control: privat ", falsch);

anstelle von: Header ("Cache-Control: public", FALSE);

Ich weiß nicht, ob das hilft oder nicht.

0

Wenn Sie diese Art von "Echo it out mit PHP" -Methode machen, dann können Sie Ihren Benutzern keine verbleibende Zeit oder eine erwartete Größe anzeigen. Warum? Denn wenn der Browser versucht, den Download in der Mitte fortzusetzen, haben Sie keine Möglichkeit, diesen Fall in PHP zu behandeln.

Wenn Sie einen normalen Datei-Download haben, kann Apache wiederaufgenommene Downloads über HTTP unterstützen, aber im Falle eines Downloads hat Apache keine Möglichkeit herauszufinden, wo in Ihrem Skript Dinge ausgeführt wurden, wenn ein Client danach fragt der nächste Brocken.

Im Wesentlichen, wenn ein Browser einen Download pausiert, beendet er die Verbindung zum Webserver vollständig. Wenn Sie den Download fortsetzen, wird die Verbindung erneut geöffnet, und die Anforderung enthält ein Flag mit dem Hinweis "Start from byte number X". Aber für den Webserver, der oben auf Ihr PHP schaut, woher kommt Byte X?

Während es theoretisch möglich ist, dass der Server im Falle eines unterbrochenen Downloads erkennt, wo das Skript fortgesetzt werden kann, versucht Apache nicht herauszufinden, wo es fortgesetzt werden soll. Daher gibt der Header, der an den Browser gesendet wird, an, dass der Server resume nicht unterstützt, wodurch die erwarteten Größen für Dateigröße und Zeitlimit in den meisten gängigen Browsern deaktiviert werden.

EDIT: Es scheint, dass Sie in der Lage sein, mit diesem Fall umzugehen, aber es wird eine Menge Code auf Ihrer Seite nehmen. Siehe http://www.php.net/manual/en/function.fread.php#84115.

+0

es zeigte geschätzte Zeit, da ein Teil der Header Dateigröße ist - dieser Teil, jedoch nicht funktioniert. Ich stimme dem zu, dass das Herunterladen von http-resumable für dieses ein bisschen übertrieben sein kann :) –

1

Eines finde ich merkwürdig: Sie rufen am Anfang der Funktion ob_end_flush() an. Dadurch wird zwar der Ausgabepuffer bereinigt, aber es wird auch zuerst alles an den Client ausgegeben (ich gehe davon aus, dass Content-Header von CodeIgniter eingefügt werden). Ändern Sie den Anruf zu , löscht den Puffer und verwirft es. Dies gibt Ihnen einen sauberen Start für die Erstellung Ihrer eigenen Header.

Noch ein Tipp:

Statt die Datei als Stream zu lesen und es auf blockweise vorbei, können Sie diese Funktion einen Versuch geben:

// ... 
if (file_exists("dir-with-files/$filename")) { 
    readfile($file); 
} 

Diese Pflege alles von fast nimmt.

+0

Ich stimme zu, benutze readfile es wird dich nicht zu Gedächtnisqualitäten führen. Außerdem empfehle ich dirname (__ FILE __). '/ ../../thatpathwithfiles /' jedes Mal, wenn Sie einen Pfad zusammenstellen. – useless

+0

Es ist ** bewiesen **, dass Readfile (wie ich geschrieben habe) führte mich zu Speicherproblemen bereits - ich bin mir nicht sicher, aber es könnte versuchen, genügend Speicher für Datei zuweisen - welcher Server nicht unterstützt. –

+0

+1 für 'ob_end_clean()' –

2

Okay, das ist eine alte Frage und Adam hat bereits seine eigene Antwort akzeptiert, also hat er vermutlich das für sich selbst arbeiten lassen, aber er hat nicht erklärt warum es funktioniert hat. Eine Sache, die mir auffiel, war in der Frage verwendet er die Header:

header("Pragma: public"); 
header("Cache-Control: public",FALSE); 

Während in der Lösung, die er verwendet:

header("Cache-control: private"); 
header('Pragma: private'); 

er nicht erklären, warum er diese verändert, aber ich vermute, es bezieht sich auf die Verwendung von SSL. Ich löste vor kurzem ein ähnliches Problem in der Software, der beim Download über HTTP und HTTPS aktivieren muss, mit dem folgenden den richtigen Header hinzuzufügen:

if(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) { 
    header("Cache-control: private"); 
    header('Pragma: private'); 
} else { 
    header('Pragma: public'); 
} 

Hoffentlich wird jemand die Informationen in dieser Antwort eine sinnvolle Ergänzung zu dem oben finden .

0

Anstatt zu versuchen, Ihren Download-Pfad aus der Welt zu verstecken machen Sie es von außen zugänglich und greifen Sie nur auf die Dateien mit dem obigen Skript. Dazu legen Sie eine htaccess-Datei (eine Textdatei mit dem Namen '.htaccess' vergessen Sie nicht führenden Punkt) in das Verzeichnis. Inhalt der .htaccess wäre dies:

order deny,allow 
deny from all 
allow from localhost 

nun versuchen, den Weg für den Zugriff von * Welt machen erstellen die Web-Server ein 401 verboten.

Sicherheit durch Dunkelheit ist nicht das, was Sie wollen.

Verwandte Themen