Lissy: Download Attribut

Ein Link auf ein JPEG soll nicht auf das Bild direkt führen, sondern es soll direkt der Download-Dialog gezeigt werden.

Da Bild und Link im Drupal CMS mit einer View bereit gestellt wird (ich also keinen direkten Zugriff auf das HTML habe), habe ich mit JQuery das Download-Attribut hinzugefügt.

Der Code sieht nun so aus:

  
<a title="meinbild" href="http://www.meinedomain.de/meinbild.jpg" download="meinneuerbildname.jpg">Linktext</a>

Nur leider ändert sich nichts, es wird immer noch auf das Bild verlinkt.

Ich teste mit IE 11 und mit Firefox 25.

Sollte das in diesen Browsern funktionieren?

Worauf muß man noch achten?

  1. Vielleicht stimmt der Pfad bei download nicht?

    Gruß
    Geschenke ! Geschenke ! Geschenke ! - los mach hin du doofer Adventskalender!
    T-Rex

    1. Om nah hoo pez nyeetz, T-Rex!

      Vielleicht stimmt der Pfad bei download nicht?

      Der Wert des download-Attributs muss keine Pfadangabe sein. [HTML 4.8.1]

      Ich kann aber bestätigen, dass auch in meinem FF das Download-Attribut nicht nicht wie im webdesignerdepot beschrieben unterstützt wird.

      Das caniuse-Beispiel hingegen funktioniert, die bestätigen auch den Support durch FF und Chr. Allerdings bekommt der Link noch ein data-downloadurl="text/plain:MyFile.txt:…" Da steht der entsprechende mimi-type mit drin.

      Matthias

      --
      Der Unterschied zwischen Java und JavaScript ist größer als der zwischen Dress und Dressing.

      1. Hallo

        entsprechende mimi-type mit drin.

        Mimi?

        Gruß
        Kalk

  2. Der Code sieht nun so aus:

    <a title="meinbild" href="http://www.meinedomain.de/meinbild.jpg" download="meinneuerbildname.jpg">Linktext</a>

    
    >   
    > Nur leider ändert sich nichts, es wird immer noch auf das Bild verlinkt.  
      
    Variante 1:  
    ===========  
      
    Wenn der Server ein Apache ist \_und\_ so eingerichtet ist, dass die .htaccess beachtet wird ([allow ovrride](http://httpd.apache.org/docs/2.2/mod/core.html#allowoverride)) \_und\_ wenn Du die Möglichkeit hast, die Namen der Bilder zu beeinflussen, dann könntest Du mit mit einer Datei .htaccess dafür sorgen, dass anhand des namens unterschieden wird:  
      
      
    ~~~apache
    <FilesMatch ".*/download.*\.jpg$" >  
    ForceType application/octet-stream  
    </FilesMatch>
    

    Alle Dateien, die mit download beginnen werden mit einem (angeblichen) Dateityp "application/octet-stream" angeboten, der Browser wird diese zum Download anbieten. Es gänge z.B. auch mit 'file/safe-me' - weil der Browser alle unbekannten Dateitypen zum Download anbietet.

    Variante 2:

    Wenn der Server ein Apache ist _und_ so eingerichtet ist, dass die .htaccess beachtet wird (allow ovrride) _und_ wenn Du die Möglichkeit hast, den Ordner zu beeinflussen, in dem die auszuliefernden Bilder liegen, dann könntest Du ebenfalls mit mit einer Datei .htaccess dafür sorgen, dass anhand des Ordners unterschieden wird:

    Datei .htaccess im Wurzelordner oder als Teilstück direkt in der Serverkonfiguration, normalerweise: /etc/apache2/sites-enabled/[IRGEND_EIN_NAME].conf

    <Location /downloads>  
    ForceType unknown/unknown  
    </Location>  
    
    

    Datei .htaccess  im Ordner /downloads/:
    ForceType download/safe-me

    Variante 3:

    Mit PHP:
    Ein skript sorgt dafür, dass die Datei mit einem passenden Dateityp ausgeliefert wird. Das muss aber auch dafür sorgen, dass durch Manipulation nicht etwa z.B. die Datei /etc/passwd oder etc/groups oder eien sonstige nicht vorgesehene Datei ausgeliefert wird. Dazu bestimmt man den Ordner und sorgt dafür, dass die übermittelten Dateinamen nicht etwa '..' enthalten oder PHP-Skripte sind.

    Als Adresse gib etwas wie: 'download.php?filename=the_fine_pic.jpg' an.

    Die download.php

    <?php  
      
    # Anpassen:  
    define('DOWNLOAD_DIR', '/srv/www/downloads/');  
    define('MIME_TYP', 'bin/unknown');  
      
    # Mindestausstattung! Eventuell weitere Prüfungen einbauen,  
    /*  
    Verboten werden Zugriffe auf Dateien, die  
    - im Name etwas wie '../' enthalten - Zugriffe auf Ordner oberhalb von DOWNLOAD_DIR - wahlfreier Zugriff im Dateisystem  
    - mit '/' beginnen  - wahlfreier Zugriff im Dateisystem  
    - mit einem Punkt beginnen (versteckte Dateien wie z.B. .htaccess)  
    - mit der Endung php  
    - mit der Endung pl  
    - die im Name etwas wie ':/' enthalten (Zugriff auf entfernte Dateisysteme!)  
    - groß/klein ist irrelevant, es wird nach beiden gesucht.  
    */  
    $arForbiddenStrings=array(  
      '\.\.\/',  
      '^\/',  
      '^\.',  
      '\.php$',  
      '\.php',  
      '\.pl$',  
      ':\/'  
    );  
      
    # Programm:  
      
    if (! isset($_GET['filename'])) {  
         header('HTTP/1.1 404 NOT FOUND');  
         header('Content-Type:text/html');  
         print '<h1>Es fehlen Informationen.</h1>';  
         exit;  
    }  
      
      
    foreach ($arForbiddenStrings as $s) {  
       if ( preg_match('/'.$s.'/i', $_GET['filename']) ) {  
          header('HTTP/1.1 403 FORBIDDEN');  
          header('Content-Type:text/html');  
          print '<h1>Zugriff verboten!</h1>';  
          exit;  
       }  
    }  
      
      
    $filename=DOWNLOAD_DIR . str_replace('..', $_GET['filename']);  
    if (is_file($filename) && is_readable($filename)) {  
         header('Content-Type: ' . MIME_TYP);  
         passthru($filename);  
         exit;  
    } else {  
         header('HTTP/1.1 404 NOT FOUND');  
         header('Content-Type:text/html');  
         print '<h1>Nicht gefunden!</h1>';  
         exit;  
    }  
    
    

    Das das ?> fehlt ist hier nicht nur völlig in Ordnung, sondern sogar Absicht.
    Es ist kein Problem, sondern oft wünschenswert, wenn der Download-Ordner außerhalb des Dokument-Root liegt. Allerdings muss man sich dann im klaren sein, dass alle (nicht versteckten, also nicht mit einem Punkt beginnenden) Dateien, die mit Leserechten für jeden oder den Webserver versehen sind, dann auch ausgeliefert werden. Das könnte verwirren.

    Jörg Reinholz

    1. Einen unbekannten MIME-Type zu schicken, find ich ziemlich schmutzig.

      IMO wäre es besser Content-Disposition entsprechend zu setzen.
      »An opportunity to raise a "File Download" dialogue box for a known MIME type with binary format or suggest a filename for dynamic content. Quotes are necessary with special characters.«

      Für Variante 1 bzw 2 bräuchte man da wohl mod_headers.

      Variante 3:

      <?php

      Anpassen:

      define('DOWNLOAD_DIR', '/srv/www/downloads/');
      define('MIME_TYP', 'bin/unknown');

      s.o.  
        
      
      > ~~~php
      
       if (! isset($_GET['filename'])) {  
      
      >      header('HTTP/1.1 404 NOT FOUND');  
      >      header('Content-Type:text/html');  
      >      print '<h1>Es fehlen Informationen.</h1>';  
      >      exit;  
      > }
      
      

      400 Bad Request ist wohl zutreffender.

      $filename=DOWNLOAD_DIR . str_replace('..', $_GET['filename']);

      if (is_file($filename) && is_readable($filename)) {
           header('Content-Type: ' . MIME_TYP);
           passthru($filename);
           exit;
      } else {
           header('HTTP/1.1 404 NOT FOUND');
           header('Content-Type:text/html');
           print '<h1>Nicht gefunden!</h1>';
           exit;
      }

      Hier würde ich wohl auch noch mal unterscheiden, ob die Datei existiert (404 Not Found), oder ob der Server sie nicht lesen kann, aus welchm Grund auch immer (500 Internal Server Error)  
      (Verzeichnisse würde ich persönlich mit 400 Bad Request beantworten, es sei dann man will Archive des kompletten Verzeichnisses verschicken)  
        
      MfG  
      bubble
      
      -- 
      If "god" had intended us to drink beer, he would have given us stomachs. - David Daye
      
      1. Einen unbekannten MIME-Type zu schicken, find ich ziemlich schmutzig.

        Wer vertraut denn im Jahr 2013 noch auf einen im header gesendeten Dateityp?

        IMO wäre es besser Content-Disposition entsprechend zu setzen.
        »An opportunity to raise a "File Download" dialogue box for a known MIME type with binary format or suggest a filename for dynamic content. Quotes are necessary with special characters.«

        Ja. Aber warum zeigst Du es nicht gleich?

        if (is_file($filename) && is_readable($filename)) {
             header('Content-Type: ' . MIME_TYP);
             header("Content-Description: File Transfer");
             header("Content-Length: " . filesize($filename));
             header('Content-Disposition: attachment; filename="' . str_replace(dirname($filename), '', $filename) . '"');
             passthru($filename);
             exit;
        } else ...

        Für Variante 1 bzw 2 bräuchte man da wohl mod_headers.

        Nein. Die ForceType-Direktive gehört  zum core, funktioniert im Apache ganz ohne Module. FilesMatch ebenso.

        400 Bad Request ist wohl zutreffender.

        Das kann man diskutieren, es ist aber ebenso zutreffend wie "nicht gefunden". Bad Request Method käme mit gleicher Berechtigung noch in Frage, ebenso wie 204 (No Content), 406 (Not Acceptable), 510 (Not Extended) käme sogar am nächsten.

        Hier würde ich wohl auch noch mal unterscheiden, ob die Datei existiert (404 Not Found), oder ob der Server sie nicht lesen kann, aus welchm Grund auch immer (500 Internal Server Error)

        Ich würde nicht zu viel verraten wollen. "Issnich" reicht meiner Meinung nach völlig aus, weil weitere Informationen ausschließlich Angreifern zu Gute kommen. Ich würde letztendlich sogar soweit gehen und bei einer evidenten(!) Häufung von Zugriffsversuchen auf Sachen WIE BEISPIELSWEISE '/etc/passwd', '/etc/shadow', '../*' sogar den Client aussperren. Das wäre aber wieder ein komplexe Sache, denn hier muss mit einigem Feingefühl vorgegangen werden, weil auch jemand bewusst solche Links bauen kann wodurch dann z.B. Suchmaschinen gesperrt werden würden.

        (Verzeichnisse würde ich persönlich mit 400 Bad Request beantworten, es sei dann man will Archive des kompletten Verzeichnisses verschicken)

        Archive kompletter Verzeichnisse zu versenden geht zwar auch dynamisch, davon würde ich aus Performancegründen aber absehen und eine Lösung vorziehen, bei der das Archiv im aufwendigsten Fall halbautomatisch erstellt und aktualisiert wird. Das geht sehr weit über die hier gestellte Frage hinaus und würde darauf hinauslaufen, dass ich
        etwas wie ein ls -alr anwende,
        die Rückgabe durch md5 jage, und dann mit dem Inhalt der gespeicherten Datei dir.md5 vergleiche

        • Ist es gleich, dann
             -- sende ich die bestehende dir.tar.gz mit dem Content-Type "application/x-gzip"
        • sonst
             -- lege ich die dir.md5 und das Archiv neu an und jage das Archiv durchs Äther, dass auch Kupfer oder Glas sein kann.

        Jörg Reinholz

        1. Einen unbekannten MIME-Type zu schicken, find ich ziemlich schmutzig.
          Wer vertraut denn im Jahr 2013 noch auf einen im header gesendeten Dateityp?

          Ich vertraue zumindest auf header die ich selbst gesetzt hab :D

          IMO wäre es besser Content-Disposition entsprechend zu setzen.
          Ja. Aber warum zeigst Du es nicht gleich?

          Daran hab ich einfach in dem Moment nicht gedacht.

          Für Variante 1 bzw 2 bräuchte man da wohl mod_headers.
          Nein. Die ForceType-Direktive gehört  zum core, funktioniert im Apache ganz ohne Module. FilesMatch ebenso.

          Das war auch Content-Disposition bezogen. Wenn man den header setzten will brauch man mod_headers

          400 Bad Request ist wohl zutreffender.
          Das kann man diskutieren, es ist aber ebenso zutreffend wie "nicht gefunden". Bad Request Method käme mit gleicher Berechtigung noch in Frage, ebenso wie 204 (No Content), 406 (Not Acceptable), 510 (Not Extended) käme sogar am nächsten.

          204 passt mMn. nicht, da die Anfrage ja nicht erfolgreich war, 406 versteh ich so, dass es nur zu verwenden ist, wenn Accept-*-Bedingungen nicht erfüllt werden können. 510: Im verlinkten RFC trägts den Namen »An HTTP Extension Framework«, also um HTTP zu erweitern. Hier wird doch aber HTTP einfach nur "verwendet".

          Hier würde ich wohl auch noch mal unterscheiden, ob die Datei existiert (404 Not Found), oder ob der Server sie nicht lesen kann, aus welchm Grund auch immer (500 Internal Server Error)
          Ich würde nicht zu viel verraten wollen. "Issnich" reicht meiner Meinung nach völlig aus, weil weitere Informationen ausschließlich Angreifern zu Gute kommen.

          Ich dachte daran, falls die Datei vorhanden ist und normalerweise lesbar ist (und eine erlaubte Datei repräsentiert), aber z.B. wegen eines temporären Locks oder der gleichen mit 500 zu antworten, und natürlich keine weiteren Informationen angeben.
          Ich denke es macht auch für einen Anwender einen Unterschied, ob er ein 404 oder 500 vor gesetzt bekommt.

          (Verzeichnisse würde ich persönlich mit 400 Bad Request beantworten, es sei dann man will Archive des kompletten Verzeichnisses verschicken)

          Archive kompletter Verzeichnisse zu versenden geht zwar auch dynamisch, davon würde ich aus Performancegründen aber absehen und eine Lösung vorziehen, bei der das Archiv im aufwendigsten Fall halbautomatisch erstellt und aktualisiert wird. Das geht sehr weit über die hier gestellte Frage hinaus und würde darauf hinauslaufen, dass ich
          etwas wie ein ls -alr anwende,
          die Rückgabe durch md5 jage, und dann mit dem Inhalt der gespeicherten Datei dir.md5 vergleiche

          • Ist es gleich, dann
               -- sende ich die bestehende dir.tar.gz mit dem Content-Type "application/x-gzip"
          • sonst
               -- lege ich die dir.md5 und das Archiv neu an und jage das Archiv durchs Äther, dass auch Kupfer oder Glas sein kann.

          Das mit den Archiven war mehr oder weniger nur eine Randbemerkung, dass man die nicht die ganze Zeit on-the-fly generieren sollte, war mir schon bewusst. (Nebenbei, kann es sein, dass du ls -alR meintest? Oder gibt es einen bestimmten Grund, warum die Liste umgedreht sein soll?)

          MfG
          bubble

          --
          If "god" had intended us to drink beer, he would have given us stomachs. - David Daye
          1. (Nebenbei, kann es sein, dass du ls -alR meintest? Oder gibt es einen bestimmten Grund, warum die Liste umgedreht sein soll?)

            Ja, wenn schon ls, dann ls -alR, weil ich ja rekursiv listen will.

            Jörg Reinholz

          2. Hier würde ich wohl auch noch mal unterscheiden, ob die Datei existiert (404 Not Found), oder ob der Server sie nicht lesen kann, aus welchm Grund auch immer (500 Internal Server Error)
            Ich würde nicht zu viel verraten wollen. "Issnich" reicht meiner Meinung nach völlig aus, weil weitere Informationen ausschließlich Angreifern zu Gute kommen.
            Ich dachte daran, falls die Datei vorhanden ist und normalerweise lesbar ist (und eine erlaubte Datei repräsentiert), aber z.B. wegen eines temporären Locks oder der gleichen mit 500 zu antworten, und natürlich keine weiteren Informationen angeben.
            Ich denke es macht auch für einen Anwender einen Unterschied, ob er ein 404 oder 500 vor gesetzt bekommt.

            Warum sollte ich dem Anwender vormachen, es es sei ein Fehler aufgetreten? Der soll sie nicht abholen können - das ist das gleiche wie wenn sie nicht da ist. Mit einem 500er sorge ich nur dafür, dass Suchmaschinen  die Datei im Cache behalten. Das werde ich aber ganz gewiss auch nicht wollen, wenn ich dem Zugriff aufs Original verbiete.

            Jörg Reinholz