wieZucker: Downloadscript bereitet Probleme

Hallo zusammen,
ich habe ein PHP-Script, das mir Dateien vom gleichen Server ausliefern soll. Ansich einfache Anforderung! Aber an den einfachsten Dinge scheitet man dann natürlich. *grr*

Und zwar habe ich folgende Probleme:
 * Im IE 6/7 lädt man die Datei runter, aber der Rechner kann damit nicht umgehen, weil die Dateien korrupt sind.
 * Lade ich die Datei mit dem selben Script via FF 3 runter, so kann ich sie problemlos anzeigen! Ausnahme ist eine Datei die mehr als 30MB benötigt. Diese wird zu 99% heruntergeladen und ist damit fehlerhaft. Zu groß? Wie kann ich es umgehen?
 * Gebe ich die Dateilänge nicht an, scheint es im IE zu funktionieren - hin und wieder. Dafür sieht man dann aber logischerweise nicht, wie lange es noch dauert.
 * In meinem geschriebenen Script (siehe unten) verwende ich fread(), welches mir den Inhalt liefert aber eben mit obigen Problemen. fgetc() hat zur Folge das der Download erst gar nicht startet (?), file_get_contents() liest genauso wie readfile() nichts aus, man erhält 0 Bytes.

Ich bin mit meinem Latein/PHP am Ende. Ich habe duzende Varianten versucht und bekomme das Script nicht einwandfrei ans laufen. Egal was ich mache, es hakt immer irgendwo. Ich benötige Hilfe.

Folgendes Script ist aktuell das Problem:

<?php  
    // Generelles  
    $filedir = './files/';  
  
    // Dateiort  
    $filename = strip_tags($_GET['file']);  
    $filename = str_replace('..', '', $filename);    // Entfernt alle relativen Subdir-Verweise  
    $filename = str_replace('./', '', $filename);    // Entfernt alle relativen dir-Verweise  
    $filename = str_replace('//', '', $filename);    // Entfernt alle doppelten Slashes  
    if($filename[0] == '/')  
        $filename = substr($filename, 1);    // Entfernt einfachen Slash an erster Position um doppelte Slashes im Pfad zu verhindern  
    $filepath = $filedir.$filename;  
  
    //prüfen ob Datei vorhanden  
    if($filename == '' || !file_exists($filepath) || !is_file($filepath))  
        die('Datei nicht verfügbar');  
  
    // Dateiinformationen laden  
    $filepath = realpath($filepath);  
    $fileinfo = pathinfo($filepath);  
    $fileinfo['size'] = filesize($filepath);  
  
    switch(strtolower($fileinfo['extension']))  
    {  
        // Archiv  
        case 'zip':  
            $fileinfo['mimetype'] = 'application/x-zip-compressed';  
            break;  
  
        // PDF  
        case 'pdf':  
            $fileinfo['mimetype'] = 'application/pdf';  
            break;  
  
        // Installer  
        case 'cab':  
        case 'exe':  
        case 'msi':  
        default:  
            $fileinfo['mimetype'] = 'application/octet-stream';  
            break;  
    }  
  
    // Datei öffnen  
    $fp = @fopen($filepath, 'rb');  
    if(!$fp)  
        die('FilePointer nicht gesetzt');  
  
    // Headerinformationen festlegen  
    header('Content-Type: '.$fileinfo['mimetype']);  
    header('Content-Disposition: attachment; filename="'.$filename.'"');  
    header('Content-Length: '.$fileinfo['size']);  
  
    // Ausgabe  
    @fseek($fp, 0, SEEK_SET);  
    while(!feof($fp))  
    {  
        //Reset für große Dateien  
        set_time_limit(0);  
        print @fread($fp, 1024*8);  
        flush();  
        ob_flush();  
    }  
  
    flush();  
    ob_end_clean();  
  
    if($fileinfo['size'] != ftell($fp))  
    {  
        // irgendwelche Fehler ...  
    }  
  
    @fclose($fp);  
    exit;  
?>

Meine Fragen lauten kurz und bündig:
 - Was ist falsch?
 - Wo ist der Fehler, weswegen man damit nicht mit allen Browsern, in allen Dateigrößen und -typen runterladen kann?
 - Wie kann ich das Problem beheben?

Ich hoffe, dass hier bei selfhtml Leute sind, die sich mehr dieser Sache annehmen. Bei bekannten anderen Foren war ich bislang wenig erfolgreich bzw. es wurde nicht mal ansatzweise darauf eingegangen.

Gruß

  1. Hi!

    Ich habe duzende Varianten versucht und

    Das war der Fehler - Du mußt siezende Varianten nehmen ;)

    *SCNR*

    off:PP

    --
    "You know that place between sleep and awake, the place where you can still remember dreaming?" (Tinkerbell)
    1. Ich habe duzende Varianten versucht und
      Das war der Fehler - Du mußt siezende Varianten nehmen ;)

      *SCNR*

      Ärgerlich das man Beiträge hier nicht editieren kann, aber offensichtlich hilft es Heiterkeit zu verbreiten ;) *g*

  2. Hallöchen,

    Probier mal
    a) noch paar zusätzliche Header
    b) andere Möglichkeit der Ausgabe

    Hab mal bissl geändert in deinem Script:

    <?php
    /* möglicherweise unnötig

    // Datei öffnen
        $fp = @fopen($filepath, 'rb');
        if(!$fp)
            die('FilePointer nicht gesetzt');

    */

    // Headerinformationen festlegen

    header("Pragma: public");
    //   header("Expires: 0");  // bin nicht ganz sicher, ob / wofür der notwendig ist
       header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
       header("Cache-Control: public", FALSE);
       header("Content-Description: File Transfer");

    header('Content-Type: '.$fileinfo['mimetype']);
        header('Content-Disposition: attachment; filename="'.$filename.'"');
        header('Content-Length: '.$fileinfo['size']);

    // Ausgabe

    $ausgabe = readfile($filename);

    if (!$ausgabe) {
               // vorher: if($fileinfo['size'] != ftell($fp))

    // irgendwelche Fehler ...
        }
    ?>

      
    Ob es funktioniert, musst du selbst herausfinden...  
      
    MfG  
    vaudi
    
    1. Hallo vaudi,

      Probier mal
      a) noch paar zusätzliche Header
      b) andere Möglichkeit der Ausgabe

      Ich komme der Sache näher! Ich habe beides a) und b) in meinem Script umgesetzt und nun macht das Script seine Arbeit! Es liest die Dateien und liefert sie dem Browser korrekt aus. Fraglich ist alleine jetzt noch, wie es bei dem 30 MB Download aussieht. Da hakt es noch etwas, aber ich bin guter Dinge. Ich frage mich nur warum die drei von dir genannten header mir nie so wirklich aufgefallen sind bei meinem Recherchen... Entweder blind oder nicht gut genug gesucht -.- In jedem Fall hätte ich es selber schaffen können.

      Dennoch vielen Dank für den Tip!

      Gruß

  3. Liebe(r) wieZucker,

    switch(strtolower($fileinfo['extension']))

    wozu bemühst Du Dich um den MIME-Type? Ich nehme für alles "application/octet-stream", damit der Download-Dialog des Browsers auch tatsächlich erscheint...

    // Datei öffnen
        $fp = @fopen($filepath, 'rb');

    Das mache ich auch so...

    // Headerinformationen festlegen
        header('Content-Type: '.$fileinfo['mimetype']);
        header('Content-Disposition: attachment; filename="'.$filename.'"');
        header('Content-Length: '.$fileinfo['size']);

    Sieht auch wie bei mir aus...

    // Ausgabe
        @fseek($fp, 0, SEEK_SET);

    Wozu das? Du hast doch gerade eine Datei zum Lesen geöffnet und den Dateizeiger deshalb schon am Anfang stehen!

    //Reset für große Dateien
            set_time_limit(0);

    Ist das wichtig? Hattest Du schon einen Timeout wegen eines zu langen Downloads?

    print @fread($fp, 1024*8);
            flush();

    Habe ich auch...

    ob_flush();

    Was soll der output buffer? Benutzt du ihn wirklich?

    flush();
        ob_end_clean();

    Wozu rufst Du diese zwei flushs erneut auf? Es gibt nach der Schleife nix mehr herunterzuspülen!

    if($fileinfo['size'] != ftell($fp))
        {
            // irgendwelche Fehler ...
        }

    Wieso kommt diese Prüfung hier? Wäre die nicht vor der Ausgabe der Datei an sich wesentlich gewesen?

    exit;

    Warum musst Du das Script hier zwangsbeenden?

    Eine generelle Frage habe ich noch: Ist bei Deinem Server mod_gzip geladen, das die Ausgabe an den Browser komprimiert? Das könnte nämlich zu "falschen" Dateigrößenangaben führen!

    Außerdem wäre es sehr sinnvoll, wenn Du in Deinem Script eine kleine Textdatei zum Debuggen öffnest, in die Du hineinschreibst, in welchen Verzweigungen Dein Script sich bewegt. Dazu kannst Du auch kleine Ausgaben bezüglich des $fileinfo-Arrays machen. Anschließend kannst Du diese Debug-Ausgaben mit deinem access-log vergleichen.

    Liebe Grüße,

    Felix Riesterer.

    --
    ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
    1. Hallo Felix!

      switch(strtolower($fileinfo['extension']))
      wozu bemühst Du Dich um den MIME-Type? Ich nehme für alles "application/octet-stream", damit der Download-Dialog des Browsers auch tatsächlich erscheint...

      Mag sein, dass man es weg lassen kann. Ich habe aber schlichtweg nur Schwierigkeiten gehabt, weswegen ich es lieber komplett mache.

      // Ausgabe
          @fseek($fp, 0, SEEK_SET);
      Wozu das? Du hast doch gerade eine Datei zum Lesen geöffnet und den Dateizeiger deshalb schon am Anfang stehen!

      Ich weiß, der Zeiger steht am Anfang wenn man eine Datei öffnet. Aber da das Auslesen fehlschlägt habe ich die "Sicher-ist-Sicher" Variante gewählt. Alle Eventualitäten aus dem Weg zu räumen.

      //Reset für große Dateien
              set_time_limit(0);
      Ist das wichtig? Hattest Du schon einen Timeout wegen eines zu langen Downloads?

      Auch hier gilt, erstmal alle Fehlerquellen minimieren.

      if($fileinfo['size'] != ftell($fp))
          {
              // irgendwelche Fehler ...
          }
      Wieso kommt diese Prüfung hier? Wäre die nicht vor der Ausgabe der Datei an sich wesentlich gewesen?

      Das war nur zum Debuggen notwendig. Ich habe versucht herauszufinden, warum die geladenen Dateigrößen geringer waren als ausgelesen. Und dort kam fast immer entweder gleich oder 0 Bytes gelesen raus.

      exit;
      Warum musst Du das Script hier zwangsbeenden?

      »»

      flush();
          ob_end_clean();
      Wozu rufst Du diese zwei flushs erneut auf? Es gibt nach der Schleife nix mehr herunterzuspülen!

      »»

      ob_flush();
      Was soll der output buffer? Benutzt du ihn wirklich?

      Altlasten! :D Da sie in sofern nicht störend waren und nur das Script lauffähiger (im Sinne von Fehlerquellenminimierung) machten, habe ich sie stehen gelassen. Das exit habe ich allerdings schlichtweg vergessen.

      Eine generelle Frage habe ich noch: Ist bei Deinem Server mod_gzip geladen, das die Ausgabe an den Browser komprimiert? Das könnte nämlich zu "falschen" Dateigrößenangaben führen!

      Ist vorhanden! Aber filesize hat bislang immer die korrekten Werte ermittelt. Deswegen habe ich das erstmal nicht weiter beachtet. Grober Fehler?

      Außerdem wäre es sehr sinnvoll, wenn Du in Deinem Script eine kleine Textdatei zum Debuggen öffnest, in die Du hineinschreibst, in welchen Verzweigungen Dein Script sich bewegt. Dazu kannst Du auch kleine Ausgaben bezüglich des $fileinfo-Arrays machen. Anschließend kannst Du diese Debug-Ausgaben mit deinem access-log vergleichen.

      Ähnlich habe ich das ja auch gemacht - siehe if($fileinfo['size'] != ftell($fp))

      Gruß