PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Der Download, der sich selbst löscht

with 12 comments

Manchmal kann es nützlich sein dass eine Datei nur einmal heruntergeladen werden kann und dann gelöscht wird, weil sie nicht mehr benötigt wird und nur Platz verschwendet. Bietet man die Datei direkt zum Download an (sprich eine direkte URL zur Datei), dann liefert der Webserver die Datei auch auch an den Browser, aber sie wird nicht gelöscht sobald der Download beendet wurde.

Ich möchte hier ein paar Beispiele sammeln, die dies möglich machen, auf möglichst einfache Art und Weise. Auf die Idee gekommen bin ich bei der Recherche zur Frage, ob ein PHP-Script sich selbst löschen kann, denn ich hatte mich schon immer gefragt ob das geht und wenn ja, warum das nicht gemacht wird bei WordPress-Installationen zum Beispiel, denn dort muss man nach der Installation immer manuell die install.php umbenennen oder löschen.

Lösung 1:

Hier ein kleines PHP Script, das eine Datei zum Download anbietet und nach erfolgtem Download die Datei und sich selbst löscht.

<?php
$filepath = '/data/Report.docx';

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $filepath);

header('Content-type: '.$mimeType);
header('Content-Disposition: attachment; filename="'.basename($filepath).'"');

readfile($filepath);

unlink($filepath);

unlink(__FILE__);

Lösung 1b:

Eine etwas generischere Lösung ist die folgende. Dann benötigt man nicht mehr pro Download-Datei ein PHP-Script, sondern kommt mit einem Script im ganzen Download-Ordner aus. Allerdings sollte man für ein sicherers Script noch prüfen ob in $_GET[‚filepath‘] auch nichts böses drinsteht!

<?php
$filepath = $_GET['filepath']; // Hier Vorsicht!

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $filepath);

header('Content-type: '.$mimeType);
header('Content-Disposition: attachment; filename="'.basename($filepath).'"');

readfile($filepath);

unlink($filepath);

Lösung 2:

Hier steckt der Inhalt der Datei direkt mit in der PHP Datei drin, und zwar als base64 String. Es ist natürlich etwas aufwändiger, dieses PHP-Script zu erstellen. Auch hat es den Nachteil dass dadurch der Speicherverbrauch des PHP-Prozesses sehr gr0ß werden kann wenn die Datei sehr groß wird:

<?php
$filename = 'bild.gif';
$mimetype = 'image/gif';
$filecontent = 'R0lGODlhLQAtABEAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBMgABACwAAAAALQAtAKH+/gAA/gAAAAAAAAAC/4yPiSRfKD6mrgpJIvggIkLxMXVFCD6mCMXH1BMh+JgoQvExU4TgY6qJUHxME4KPqSoUH1MIPqYuCMXHE4KPqXtC8TGE4GPqplD8E4JvIoKIQGTCN6H4JgQfSDbBRyDZBB+E4ocQfCD5QvCP5AvBB6H4QvCB5AvBP5IvBB+EYgvBN5IvBP9IvhB8k4gIEuEbyReCfyRfCL5JRAjBR5JN8BFINsFHiRDEIzLh44lIJhAlQvAxdTlJiRB8TF1OUkTwMXV5SSJ8TF1ekggfU5eXJMLH1OUlifBFCD6mqgjBF7kTIfiYqiIEX+ROhOBjqooQfAk5ExLhY6qKEGyJkDuSCLkfU1GE4EuE3JFE+JiKIgRfIuSOJMLHVBQh+BIRckcS4WOqiRB8iYgQgi9yd3d3DJEIXyi2EHyRu7u7QyTCF4ovBB9EEBGPSIQPQvFDCD6IICICkQgfhOKbEHwTEUQEIhO+CcU/IfhGsgk+kGyCb0LxMYTgH8nHI/gnFB9PCD4CyReCjyAUH1MIPqaqUHxMEyH4mGoiFB8zRQg+JopQfEw9EYKPKULxMXVFSCJ8EBGh+Ji6SvKF4mNiAQAh+QQFZAAAACwIABgAHQAOAKEA/gAAAAD+/v5+fn4CeVQIH1MxhBTCx1QMoSkEH1MthAYFwcdUCyFAQfAx1UII0ATBx9QYIXgSQfAxLUJoAMCIDIXwMWNEaIQtMhJBIfyLCBmSCB9kRIbE+CcjMiTCvxEZIU0yIiOkSYbgI4iMkCYZkRHSJAQfcUSGpNnYEHxMHBGaNAQfoQAAOw==';

header('Content-type: '.$mimetype);
header('Content-Disposition: attachment; filename="'.$filename.'"');

echo base64_decode($filecontent);

unlink(__FILE__);

Bei Lösung 1 und 1b dürfen die eigentlichen Dateien nicht im Document Root liegen, damit kein direkter Download möglich ist, sondern das PHP-Script genutzt werden muss.

Natürlich könnte man die Scripte noch aufbohren und mit einer kleinen Textdatei oder SQLite verbinden falls man nicht einen, sondern 3 Downloads ermöglichen möchte, dann benötigt man eine Speichermöglichkeit für einen Zähler. Auch wird nun kein partieller Download mehr ermöglicht, sodass Download-Manager mit Resume oder parallelen Requests auf eine Datei nicht funktionieren. Dies kann man jedoch mit ein wenig Arbeit nachbauen.

Habt ihr noch schöne Ideen wie man dieses (vielleicht etwas praxisferne) Problem möglichst einfach lösen könnte? Gibt es eventuell Betriebssysteme oder PHP-Umgebungen wo dieses „unlink(__FILE__);“ nicht funktioniert?

Written by Michael Kliewe

Oktober 8th, 2010 at 10:46 am

12 Responses to 'Der Download, der sich selbst löscht'

Subscribe to comments with RSS or TrackBack to 'Der Download, der sich selbst löscht'.

  1. warum sollte sich das Skript löschen? es sollte nur blockieren bzw. die Datei sollte gelöscht werden. wobei ich nicht so der Freund von löschen bin =)

    Tim Glabisch

    8 Okt 10 at 11:35

  2. Ich vermute, dass bei WP sich die install.php nicht löscht weil einerseits nicht davon ausgegangen werden kann, dass der Benutzer den Installprozess wieder durchlaufen will (wg. Fehlverhalten o.ä.) und andererseits muss der Webserver auch die Rechte dazu haben.

    Norbert

    8 Okt 10 at 12:01

  3. Man kann das noch mit mod_rewrite koppeln, dann kannst Du auch oben den direkten Dateinamen angeben (angenommen man verfrachtet alle Downloads in den Ordner /downloads/) und dann kann man http://www.meinedomain.de/downloads/infofile.pdf aufrufen…

    stietze

    8 Okt 10 at 13:43

  4. Kein Installation Script wird sich selbst löschen, da es immer vorkommen kann, das es erneut aufgerufen werden muss.

    Theoretisch sollte das Script sich aber nicht selbst „locken“, das heißt solang du nicht den Interpreter selbst löschen willst dürfte es kein Problem geben.

    Aber irgendwie frage ich mich schon die ganze Zeit: WARUM willst du das Script löschen. Die Datei kann ich ja verstehen, wenn du eine Art Sync-Filespace bauen möchtest, wo du hier hoch-lädst und die Datei dort wieder runter-lädst. Oder ein Autosync wo ein Server regelmäßig eine LogDatei auf einem anderen Server abruft, die danach gelöscht werden soll. Aber ein Script für ein einmaligen Download ist doch schon fast Programmierung der Programmierung wegen und nicht für ein Zweck. Oder sehe ich das Falsch?

    Von daher macht Lösung 1b am meisten Sinn. Auch wenn ich keine Website kenne, die so etwas benutzt.

    Havyrl

    8 Okt 10 at 13:44

  5. Lösung 1b sieht für mich schon am schönsten aus. Doch sollte man sich vielleicht nicht nur auf php beschränken.
    Mit einer entsprechenden RewriteRule in der Apache-Config beispielsweise ließen sich alle Download-Requests auf das PHP-Skript weiterleiten ohne, dass der Benutzer etwas davon mitbekommt. Den ursprünglichen URI kann man der „internen Weiterleitung“ ja anhängen und im PHP Skript weiterverabreiten (Nachteil: setzt Apache voraus).

    Dominik

    8 Okt 10 at 14:16

  6. Ein Problem sind abgebrochene Downloads. Wenn die Datei weg ist hat der User das Nachsehen oder der Prozess zum generieren der Datei muss nochmal angeschoben werden. Das kostet unnötig Ressourcen.

    Warum nicht einfach der Datei eine TTL (time-to-live) geben? So kann ja ein Cron drüber huschen und die Dateien 60 Minuten oder so nach ihrem Erstellungsdatum löschen. Statt einem Cron kann man auch einer beliebigen Datei die eh aufgerufen wird die Aufgabe übergeben. Dateien löschen geht ja schnell.

    Sven

    8 Okt 10 at 14:22

  7. Ich würde einen ähnlichen Ansatz wie 2 verwenden. Allerdings würde ich einfach den Inhalt an das Ende der Datei anhängen, und vorher mit einem exit das Script verlassen.

    Dann kann im Script selber nach dem exit gesucht werden, und der Inhalt danach ausgegeben werden. Damit muss nicht der ganze Dateiinhalt in den Speicher geladen werden, sondern kann in einzelnen Blöcken gelesen werden. Das dürfte dann insbesondere bei großen Dateien Vorteile bringen.

    Sebastian

    8 Okt 10 at 17:38

  8. Man kann auch den Compiler beenden, somit muss man nicht erst in Base64 kodieren.

    <?php

    echo file_get_contents(__FILE__, false, null, __COMPILER_HALT_OFFSET__ );

    __halt_compiler();
    CONTENT CONTENT CONTENT

    phpgangsta

    8 Okt 10 at 22:59

  9. Eine andere Möglichkeit Dateien zu löschen wäre mittels FTP-Befehle. Unlink funktioniert nicht immer wenn der FTP-User ein anderer als der Webserver-User ist.

    Ralf

    9 Okt 10 at 23:21

  10. Ob ein Download erfolgreich war oder abgebrochen wurde (vom User / Timeout), lässt sich per PHP auch rausfinden:

    http://www.php.net/manual/en/features.connection-handling.php

    Ein anderes Problem im Bezug zu Dateien ist oft, wie man verhindert, dass jemand eine Datei mit PHP-Code hochlädt und dann per Browser ausführt.

    Und dann geht vom Worst-Case aus: kein .htaccess, kein ausserhalb-document-root, alle Dateiendungen werden mit PHP geparst, …

    Interessant wäre es, nach dem Upload dann PHP-Code davorzuhängen, ähnlich wie phpgangsta das vorschlägt…

    Balu

    14 Okt 10 at 12:49

  11. @Michael Kliewe Lösung #1 were echt super aber daher ich neu in der materie bin were echt super wenn du mir behlflich sein könntest. Danke

    stohny

    4 Apr 12 at 21:07

  12. @stohny Wobei kann ich dir denn behilflich sein?

    Michael Kliewe

    4 Apr 12 at 23:30

Leave a Reply

You can add images to your comment by clicking here.