Horst: Hochgeladene Dateien im ASCII-Format in einer DB speichern

Hallo Mitstreiter,

siehe Thema. Max_Size ist 0.7 MB/File. Die Dateien werden im ASCII-Zeichensatz (Base64_encode, _decode) in ein Textfeld geschrieben. Damit bin ich unabhängig vom verwendeteten Zeichensatz, den die RDMS spricht (in meinem Fall mySQL).

Ein Kollege indes warnte mich davor, Dateien in der DB selbst zu speichern. Begründen konnte er das jedoch nicht.

Hier mal die Vorteile, die ich sehe:

  • konsistente Datenhaltung ohne Medienbrüche zwischen Filesystem und DB,
  • in der DB stehen fakultative Angaben zur Datei, wie z.B. eine Beschreibung,
  • Weitere Felder eines Records sind: Content-type, size,
  • da die Dateien Base64 codiert und der Content-Type gespeichert sind, wird das automatisierte Erstellen von Mails vereinfacht,
  • ebenso ist es für die Anwendung einfach, den für den Content-Type richtigen Header zusammenzubauen und die Datei zum Browser zu schicken,
  • Suchmöglichkeit in der DB.

Hier mache ich mal einen Punkt und bitte Euch, ein paar Nachteile dieser Diät zusammenzutragen.

Viele Grüße,
Horst Haselhuhn

  1. Moin Moin!

    Hallo Mitstreiter,

    siehe Thema. Max_Size ist 0.7 MB/File.

    Wer garantiert diese Maximalgröße?

    Die Dateien werden im ASCII-Zeichensatz (Base64_encode, _decode) in ein Textfeld geschrieben. Damit bin ich unabhängig vom verwendeteten Zeichensatz, den die RDMS spricht (in meinem Fall mySQL).

    Und Du hast 33% Overhead.

    Ein Kollege indes warnte mich davor, Dateien in der DB selbst zu speichern. Begründen konnte er das jedoch nicht.

    Spricht nicht gerade für Unmengen an Kompetenz und Motivation.

    Hier mal die Vorteile, die ich sehe:

    • konsistente Datenhaltung ohne Medienbrüche zwischen Filesystem und DB,

    Richtig.

    • in der DB stehen fakultative Angaben zur Datei, wie z.B. eine Beschreibung,

    Das ist unabhängig davon, wo die Datei liegt.

    • Weitere Felder eines Records sind: Content-type, size,

    Ditto.

    Übrigens: Woher kommt der Content-Type? Glaubst Du dem hochladenden HTTP-Client?

    • da die Dateien Base64 codiert und der Content-Type gespeichert sind, wird das automatisierte Erstellen von Mails vereinfacht,

    Aber das Ausliefern der uncodierten Datei wird erschwert, weil Du einen Durchlauf vom Base64-Decoder brauchst.

    • ebenso ist es für die Anwendung einfach, den für den Content-Type richtigen Header zusammenzubauen und die Datei zum Browser zu schicken,

    Auch das hat mit der Lage der Datei nichts zu tun.

    • Suchmöglichkeit in der DB.

    Nur über die Metadaten. Im Base64 wirst Du nicht sinnvoll suchen können, ohne in der DB die Daten zu decodieren.

    Hier mache ich mal einen Punkt und bitte Euch, ein paar Nachteile dieser Diät zusammenzutragen.

    Dein Dateisystem bleibt schlank, aber die DB wird richtig fett.

    Übrigens hat MySQL auch Datentypen, um mit Binärdaten zu arbeiten, ohne die 33% Overhead von Base64. Im Gegensatz zu anderen RDBMS lassen die sich ohne viel Theater mit SELECT, INSERT und UPDATE behandeln.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
    1. Moin Moin!

      Danke Alexanter!

      siehe Thema. Max_Size ist 0.7 MB/File.

      Wer garantiert diese Maximalgröße?

      Mein Script.

      Die Dateien werden im ASCII-Zeichensatz (Base64_encode, _decode) in ein Textfeld geschrieben. Damit bin ich unabhängig vom verwendeteten Zeichensatz, den die RDMS spricht (in meinem Fall mySQL).

      Und Du hast 33% Overhead.

      Das kann ich nicht nachvollziehen. Die ASCII-Datei ist exact genauso groß wie die binary-Datei. Wo genau liegt denn der Overhead? Im RDBMS Text vs. Blob?

      Viele Grüße,
      Hotte

      Stück Script:

      • Content-Type liefert der User-Agent,
      • Dateigröße ist nach base64_encode genauso groß.
        
      sub upload{  
       my $fqd_filename = $q->param('file') or cgiError("Keine Datei","Backbutton...");  
       fileparse_set_fstype("MSDOS");  
       my ($ori_filename, $local_path) = fileparse $fqd_filename;  
       my $mime_type = $q->uploadInfo($fqd_filename)->{'Content-Type'};  
       my ($str,$buffer,$sum,$bytesread);  
       while ( $bytesread = read($fqd_filename,$buffer,1024)) {  
         $str .= $buffer;  
         $sum += $bytesread;  
        
         cgiError("Datei zu dick") if $sum > $maxsize;  
       }  
       $sum or cgiError("0 Bytes, Abbruch","Backbutton...");  
       $str = encode_base64($str);  
        
       # nun die Tabelle beschreiben  
       my $description = ($q->param('description')) ? $q->param('description') : 'NA';  
        
       $description = $dbh->quote($description);  
        
       $dbh->do("INSERT INTO filebase VALUES('', '$ori_filename', $description, '$sum', '$mime_type', '$str')") or cgiError($dbh->errstr);  
        
       redir($ENV{SCRIPT_NAME});  
      }  
      
      
      1. Hallo,

        Danke Alexanter!

        interessande Variation. ;-)

        Und Du hast 33% Overhead.
        Das kann ich nicht nachvollziehen. Die ASCII-Datei ist exact genauso groß wie die binary-Datei.

        Das kann ich nun wieder nicht glauben. Bei der base64-Codierung werden nämlich je 3 Bytes in 4 Zeichen umgewandelt, die ihrerseits in den meisten Systemen mit 8bit je Zeichen codiert werden (benutzt werden allerdings nur Zeichencodes aus dem Bereich 21h..7Ah, der sogar mit 7bit darstellbar ist). Du erhältst also 32bit, die du übertragen oder speichern musst, aus 24bit Nutzdaten.

        • Dateigröße ist nach base64_encode genauso groß.

        Genauso groß wie ...?
        Wir die übertragenen POST-Daten? Das mag sein, weil der POST-Datenblock eventuell auch schon base64-codiert ist, und bei der Übergabe an dein Script transparent decodiert wird.

        So long,
         Martin

        --
        Wenn der Computer wirklich alles kann,
        dann kann er mich mal kreuzweise.
        1. Moin Moin!

          Genauso groß wie ...?
          Wir die übertragenen POST-Daten? Das mag sein, weil der POST-Datenblock eventuell auch schon base64-codiert ist, und bei der Übergabe an dein Script transparent decodiert wird.

          Nö, HTTP ist nicht SMTP. Datei-Uploads sind in aller Regel binär.

          Alexander

          --
          Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
      2. Moin Moin!

        Die Dateien werden im ASCII-Zeichensatz (Base64_encode, _decode) in ein Textfeld geschrieben. Damit bin ich unabhängig vom verwendeteten Zeichensatz, den die RDMS spricht (in meinem Fall mySQL).

        Und Du hast 33% Overhead.

        Das kann ich nicht nachvollziehen. Die ASCII-Datei ist exact genauso groß wie die binary-Datei.

        Dann ist Dein Base64-Encoder eine Attrappe. Base64 erzeugt per Definition aus drei Eingangsbytes vier Ausgangsbytes, das sind 33% Overhead.

        • Content-Type liefert der User-Agent,

        Und dem glaubst und vertraust Du? Was hältst Du davon, dass Dir mein User-Agent ein nettes Binary mit C-T=text/plain liefert? Anschließend lädt es ein Internet Explorer herunter, schaltet dank Microsofts "Wir wissen alles besser"-Mentalität auf Heuristik um und meint, das Binary sei ein Executable, dass jetzt unbedingt ausgeführt werden muß.

        • Dateigröße ist nach base64_encode genauso groß.

        sub upload{
        my $fqd_filename = $q->param('file') or cgiError("Keine Datei","Backbutton...");
        fileparse_set_fstype("MSDOS");
        my ($ori_filename, $local_path) = fileparse $fqd_filename;
        my $mime_type = $q->uploadInfo($fqd_filename)->{'Content-Type'};
        my ($str,$buffer,$sum,$bytesread);
        while ( $bytesread = read($fqd_filename,$buffer,1024)) {
           $str .= $buffer;
           $sum += $bytesread;

        cgiError("Datei zu dick") if $sum > $maxsize;
        }
        $sum or cgiError("0 Bytes, Abbruch","Backbutton...");
        $str = encode_base64($str);

          
        An dieser Stelle muß length($str) etwa 1.3\*$sum sein, sonst ist encode\_base64() kaputt.  
          
        
        > ~~~perl
          
        
        >  $description = $dbh->quote($description);  
        >   
        >  $dbh->do("INSERT INTO filebase VALUES('', '$ori_filename', $description, '$sum', '$mime_type', '$str')") or cgiError($dbh->errstr);  
        > 
        
        

        NEIN, NEIN, NEIN!

        Du baust hier ein riesiges Sicherheitsloch. Vergiß, dass es so etwas wie $dbh->quote() überhaupt gibt. Benutze Platzhalter. Die sind sicherer und schneller.

          
        $dbh->do(  
          'insert into filebase values(?,?,?,?,?,?)',  
          undef, # DBI-Altlast  
          '',$ori_filename,$description,$sum,$mime_type,$str  
        ) or cgiError($dbh->errstr);  
        
        

        Angriffsvektoren für SQL Injection in dem bißchen Code, das Du gepostet hast: $ori_filename und $mime_type. Beide kommen direkt ungefiltert vom Client.

        Den Taint-Modus, der die gröbsten Sicherheitsprobleme unterbindet, hast Du vermutlich nicht angeschaltet. Und strict und warnings vermutlich auch nicht.

        (Die Intranet-Ausrede zählt nicht: 75 bis 90 Prozent aller Angriffe kommen von innen.)

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
        1. Moin Moin!

          Dann ist Dein Base64-Encoder eine Attrappe. Base64 erzeugt per Definition aus drei Eingangsbytes vier Ausgangsbytes, das sind 33% Overhead.

          Tschuldigung und vielen Dank Alexander. Hab nocheinmal richtig nachgerechnet: es sind sogar 35% Overhead. Aber alleine das ist kein Thema, also kein Nachteil an sich.

          Für die Anderen Infos bezüglich Security danke ich Dir ganz herzlich. Ist aber auch kein Thema in meiner Anwendung. Das Upload Script ist nicht öffentlich, ich bin der einzige Benutzer. Btw., ich benutze aucgh nicht den IE, der mir einen picassomäßgen Content-Type vorgaugelt.

          Fazit und zurück zum Thema: Es spricht nichts dagegen Dateien in einer DB zu speichern.

          Viele Grüße,
          Hotte

          1. Hi,

            Fazit und zurück zum Thema: Es spricht nichts dagegen Dateien in einer DB zu speichern.

            Doch, eine Menge.

            Du scheinst fuer deinen Spezialfall zu der Ansicht gekommen sein, dass wenig dagegen spraeche bzw. die Vorteile ueberwiegen wuerden (wenn auch wenig nachvollziehbar).

            Aber bitte verallgemeinere das nicht auf diese Weise in einem solchen "Fazit".
            Jemand, der diese Aussage spaeter im Archiv liest, koennte sie unbesehen glauben, und sich dadurch bestaetigt sehen, den gleichen Bloedsinn zu veranstalten.

            MfG ChrisB

            1. Hi,

              Fazit und zurück zum Thema: Es spricht nichts dagegen Dateien in einer DB zu speichern.

              Doch, eine Menge.

              Ja bitte, was denn? Sei mal sogut und schreibs hier hin:

              Solch eine Zusammenfassung würde uns allen gut tun.

              @Alexander: strict und warnings verwende ich selbstverständlich. Warum Platzhalter eine SQL-Injection verhindern sollen?: Ist das auch so eine Annahme von Dir, wie Deine Annahme, dass ich strict nicht verwende?

              Viele Grüße,
              Horst

              1. Moin Moin!

                @Alexander: strict und warnings verwende ich selbstverständlich. Warum Platzhalter eine SQL-Injection verhindern sollen?: Ist das auch so eine Annahme von Dir,

                Nein, eine definierte API:

                RDBMS, deren Native-API Platzhalter vorsieht, werden von DBDs exakt so angesteuert, sprich: SQL-Statement und Parameter werden getrennt voneinander übermittelt, quoting ist überhaupt nicht notwendig. Das RDBMS kann von sich aus das Statement cachen und sich bei einem erneuten Aufruf das Parsen des SQL-Textes komplett sparen. DBI kann das Statement cachen, wenn prepare_cached() direkt oder indirekt benutzt wird.

                RDBMS, deren Native-API keine Platzhalter vorsieht (was mittlerweile selten ist), benötigen etwas Unterstützung vom DBD. Dieser ersetzt die Platzhalter im SQL-Statement durch für das RDBMS passend gequotete Parameter. Das RDBMS kann natürlich nicht mehr sinnvoll cachen, wohl aber der DBD, insbesondere wenn prepare_cached() benutzt wird. In der Regel geschieht das Quoten und Einsetzen der Parameter in schnellem XS-Code.

                In beiden Fällen werden die Parameter so zur Datenbank gebracht, dass SQL Injection unmöglich ist -- im ersten Fall werden die Parameter nicht als Teil des SQL-Statements angesehen, im zweiten Fall kümmert sich der RBDMS-spezifische Quoting-Algorithmus um *ALLE* problematischen Zeichen in den Parametern.

                Es gibt nur zwei Möglichkeiten, sich eine SQL Injection mit DBI "einzufangen": Fehlerhafte DBDs oder fehlerhafte Scripts. Ersteres ist recht unwahrscheinlich, da große Teile des DBD-Codes generiert werden und/oder von einer Basisklasse erben. Letzteres passiert, wenn uralter Code kopiert wird oder der Coder die alte PHP-Denkweise nicht loslassen kann.

                Weitere Diskussion: http://www.perlmonks.org/index.pl?node_id=7548&lastnode_id=864, http://www.perl.com/pub/a/1999/10/DBI.html

                Alexander

                --
                Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
              2. Hi,

                Fazit und zurück zum Thema: Es spricht nichts dagegen Dateien in einer DB zu speichern.

                Doch, eine Menge.

                Ja bitte, was denn?

                Argumente dagegen wurden im Thread bereits genannt.

                MfG ChrisB