Klaus1: Wie kann ich ein Blob an PHP-Ajax übertragen?

Hallo,

Ich möchte ein erstelltes Blob per Ajax-Script an PHP übertragen.

		var file = document.getElementById("upload").files[0];
		var reader = new FileReader();
		reader.readAsDataURL(file);
		reader.onload = function(event) {
			var content = event.target.result;
			var blob = new Blob([content], {type: application/pdf});
			alert(blob instance of Blob);			// gibt true aus
			var myform = new FormData();
			myform.append("dragdropname", filename);
			myform.append("dragdropfile", blob);

			req2.onload = function(e) {
				result = req2.responseText;
				alert(result);
			}
			url = 'upload.php?';
			try{
				req2.open('post', url, true);
				req2.send(myform);
			}catch(err){
				alert("error");
			}

PHP:

	if ($_FILES["dragdropfile"]) {
		$name = $_POST["dragdropname"];
		$dateiname = $pfadphp."/files/".$name;
		$tmpname = $_FILES["dragdropfile"]['tmp_name'];
//	move_uploaded_file($tmpname, $dateiname);
		file_put_contents($dateiname, file_get_contents($tmpname));
	}

Im Javascript sieht noch alles ok aus, aber im PHP-Script wird nur "data:application/pdf;base64," in die Datei gespeichert.

Was mache ich falsch?

LG Klaus

  1. var file = document.getElementById("upload").files[0];
    var reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function(event) {
    		var content = event.target.result;
    		var blob = new Blob([content], {type: application/pdf});
    		alert(blob instance of Blob);			// gibt true aus
    		var myform = new FormData();
    		myform.append("dragdropname", filename);
    		myform.append("dragdropfile", blob);
    		XMLHttpRequest...
    

    Im Javascript sieht noch alles ok aus, aber im PHP-Script wird nur "data:application/pdf;base64," in die Datei gespeichert.

    Es funktioniert alles so, wie es soll. Du fütterst blob mit der Ausgabe von readAsDataURL() – und genau das bekommst du dann auch geliefert: eine data:-URL.

    Die Rohdaten bekommst du mit readAsArrayBuffer().

    Davon unabhängig könntest du eigentlich file auch direkt an myform.append() übergeben. Den Sinn des Umweges über FileReader und blob sehe ich in diesem Beispiel nicht.

    Weiterhin:

    PHP:

    $name = $_POST["dragdropname"];
    $dateiname = $pfadphp."/files/".$name;
    $tmpname = $_FILES["dragdropfile"]['tmp_name'];
    file_put_contents($dateiname, file_get_contents($tmpname));
    

    Ich gehe mal davon aus, dass das nur ein Test sein soll und du $name aus dem Formular nicht wirklich völlig ungeprüft als Dateinamen auf deinem Server benutzen willst.

    1. Hallo,

      sorry, für die späte Antwort, ich hatte gestern komplett Internet-Ausfall :(

      Es funktioniert alles so, wie es soll. Du fütterst blob mit der Ausgabe von readAsDataURL() – und genau das bekommst du dann auch geliefert: eine data:-URL.

      Die Rohdaten bekommst du mit readAsArrayBuffer().

      Das werde ich direkt mal ausprobieren, vielen Dank.

      Davon unabhängig könntest du eigentlich file auch direkt an myform.append() übergeben. Den Sinn des Umweges über FileReader und blob sehe ich in diesem Beispiel nicht.

      Den Umweg muss ich wohl gehen, da eine per Drag&Drop aus meinem Mail-Programm übertragenes Attachment im file-object die Größe 0 hat. Damit wurde vom PHP immer nur eine 0-Byte-Datei erstellt. In einem anderen Projekt verwende ich den FileReader bereits, um den Inhalt von Mails (auch per Drag&Drop) zu parsen, da hatte ich die Anhänge allerdings nicht gespeichert, sondern nur als Link angezeigt.

      Ich gehe mal davon aus, dass das nur ein Test sein soll und du $name aus dem Formular nicht wirklich völlig ungeprüft als Dateinamen auf deinem Server benutzen willst.

      Klar, ich überprüfe vorher auf Sonderzeichen und auch den Dateityp. Aber welche Risiken könnten dadurch noch entstehen? Außer, dass der Server die Datei zwar speichert, sie aber nicht mehr gelesen werden kann (z.b. wenn deutsche Umlaute im Linux als Fragezeichen gespeichert werden).

      LG Klaus

      1. Hallo Klaus1,

        Aber welche Risiken könnten dadurch noch entstehen?

        Mit einem regulären Request vom Browser vermutlich nicht so viel.

        Aber was tust Du, wenn Dir jemand per handgezaubertem Upload-Request ../test.php als Dateiname übermittelt? Keine Ahnung ob PHP dem einen Riegel vorschiebt. Wenn nicht, kann das ins Auge gehen.

        Es ist nicht so schlimm, wenn Du über Zugriffsrechte sicherstellst, dass dein PHP Runtime-User nur auf den files-Ordner schreiben kann. Aber ist das auch so?

        Vom User übermittelte Dateinamen sollte man als genauso vergiftet wie alle anderen User-Eingaben betrachten.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Aber was tust Du, wenn Dir jemand per handgezaubertem Upload-Request ../test.php als Dateiname übermittelt? Keine Ahnung ob PHP dem einen Riegel vorschiebt.

          Das weiß ich: PHP tut das selbst nicht. Zumindest die Dateiendung bleibt.

          Es ist nicht so schlimm, wenn Du über Zugriffsrechte sicherstellst, dass dein PHP Runtime-User nur auf den files-Ordner schreiben kann.

          Ich habe da Bauchschmerzen, denn da kommt der Feind laut Plan bis zur letzten Verteidigungslinie, die da heißt, dass in einem bestimmten Ordner Dateien mit der Endung "php" (und andere: wie cgi, pl, py, ...) hoffentlich nicht bei einem Aufruf vom Webserver wie ebensolche also z.B. von PHP verarbeitet werden. Ein Ausführen-Recht ist in vielen Fällen (eben: PHP) nicht nötig. (Bei cgi oder pl zumeist schon...)

          Denn wenn es so ist, hat man bei einem kleinem Fehler (e.g. unbedachte Konfigurationsänderung, unbedachte Änderung des Speicherziels) ganz schnell viele erfreute Mitbenutzer auf dem Server - wenn die „test.php“ z.B. eine „Webshell“ ist und der ursprüngliche Mailversender erraten kann, wo die dann liegt und also die URL kennt. Es dauert dann nicht lange, bis z.B. die .T..., gmail, gmx & C. keine weiteren Mails von diesem Host mehr haben wollen.

          Vorgeschlagene Lösung: Es gibt tempnam(). Das verträgt auch die Angabe eines Verzeichnisses. Die Datei also mit einem sicheren Name (ergo ohne Endung) speichern und sodann die Metadaten der Datei (darunter deren Name, ggf. auch welche Benutzer der Webseite Rechte daran haben) in einer weiteren Datenbank (z.B. sqlite3) ablegen.

          tempnam() löst auch das Problem, dass mehrere hoch geladene Dateien den selben Name haben.

          Man kann aber sogar noch einen Schritt weiter gehen: Man nehme die Datei und hashe diese (hierfür reicht dann md5() / md5_file() oder sha1 / sha1_file() , weil es hier nicht um Kryptographie geht). Man nehme das Ergebnis als Dateiname auf dem Filesystem und vermeide das doppelte Speichern, füge aber einen weiteren Eintrag in der Datenbank mit den Metadaten hinzu. Wenn der Benutzer die Dateien auch löschen können soll muss man dann freilich aufpassen, dass die Datei selbst erst gelöscht wird, wenn der letzte Eintrag mit den zugehörigen Metadaten gelöscht wurde.

          Weitere Idee: Wenn man schon dabei ist, kann man die hoch geladene Datei auch gleich mit gzip für den späteren Download (also HTTP(S)-Transport) vorbereiten und auf diesem Weg auch Speicher auf dem Server sparen :-)

          Optimierung: Es können ja womöglich sehr viele Dateien werden. Wenn man wie vorgeschlagen den Hash als "physischen" Dateiname benutzt kann man für die ersten 1 bis 4 Zeichen Ordner anlegen also z.b.

          • ./0/ .. ./f/ oder
          • ./0/0/ .. ./f/f/ oder
          • ./0/0/0/0/ .. ./f/f/f/f/

          (Beide hash-Verfahren geben, wiewohl als String, eigentlich eine recht große, hexadezimal formulierte Zahl aus, enhalten als zur Zeichen [0123456789abcdef]) Die Dateien dann in Abhängigkeit von den linken Stellen in diesen unterbringen:

          Inhalt 'Hallo': md5('Hallo') -> 'd1bf93299de1b68e6d382c893bf1215f', käme also z.B. nach ./d/1/d1bf93299de1b68e6d382c893bf1215f

          (Man kann das auch mit etwas wie <?=base64_encode(md5('Hallo',1)); machen, aber das liefert dann 0b+TKZ3hto5tOCyJO/EhXw==, dann hätte man kürzere Namen, dafür wesentlich mehr Ordner und muss das Problem klären, dass insbesondere '[+./]' darin vorkommen können…

          Das geht schnell und löst Performanceprobleme, die manche Dateisysteme haben, wenn sehr viele Dateien in einem Ordner sind - weil sich dann die Dateien (bei hexadezimaler Darstellung) je nach Zahl der unterschiedenen Stellen auf 16, 256, 4096, 65536 Ordner verteilen, welche jeweils nur 16 Ordner oder halt den entsprechenden Anteil der Dateien enthalten.

          1. Hallo Raketenwilli,

            Ich habe da Bauchschmerzen, denn da kommt der Feind laut Plan bis zur letzten Verteidigungslinie,

            Ja, da war ich unaufmerksam; ich bin implizit davon ausgegangen, dass der /files Ordner nicht in Reichweiter des Oberindianers ist, d.h. außerhalb des Web-Root Folders ist. Der Name $pfadphp hätte mich warnen müssen, dass das hier nicht so sein dürfte.

            Einen Hash als Dateiname kann man verwenden, aber es gibt dann noch die klitzekleine Chance, dass doch mal 2 Dateien den gleichen Hash haben. tempnam() ist definitiv sinnvoller. Mit der Frage, ob auf längere Sicht VIELE Dateien im Files-Ordner zum Performanceproblem werden, hatten wir uns ja schon mal befasst, meine ich, und der Status war: es muss eine gewaltige Menge sein, bevor die heutigen Filesysteme bei Direktzugriff auf einen Namen in die Knie gehen. Die Idee, die Dateien auf einen Baum aus Ordnern zu verteilen, stammt aus der schlechten alten FAT Zeit und ist nicht mehr nötig.

            Aber wenn man ohnehin eine DB braucht, in der der Realname drin steht und vielleicht auch der User, dem die Datei "gehört" oder weitere Infos, dann könnte man ja das gzip-Ergebnis auch gleich in ein Binary-Feld der Datenbank legen und sich auf einen Autoincrement-Key verlassen. Dann sind alle Daten beisammen, die beisammen sein sollten.

            Rolf

            --
            sumpsi - posui - obstruxi
            1. und der Status war: es muss eine gewaltige Menge sein, bevor die heutigen Filesysteme bei Direktzugriff auf einen Namen in die Knie gehen.

              Also, meine sorgfältig nach Künster-Disk-Position-Titel.ogg von meinen CDs gegrappte Musiksammlung hat gereicht um (allerdings auf einem Raspi und einer 2,5-Zoll-Magnetplatte an USB-2) für unerträglich negative Performance zu sorgen. Ja: Das sind ein paar Tausend.

              Nachdem ich diese (natürlich mit einem Skript) auf die Anfangsbuchstaben der Künstler/Künstler/CD/Titel.ogg verteilt hatte ging es…

              aber es gibt dann noch die klitzekleine Chance, dass doch mal 2 Dateien den gleichen Hash haben. tempnam() ist definitiv sinnvoller.

              Das dürfte eine Frage der Wichtung sein. MD5() liefert eine 32-stellige hexadezimale Zahl…

              1:16^32 1:340282366920938463463374607431768211456

              = 0.0000000000000000000000000000000000000029

              sha1() eine mit 40 Stellen. Man kann natürlich auch hash('sha512',$string,0) verwenden (128 Stellen, für "Hallo" kommt "2d0e4a42d260c2407281975322837e61c1362a3b18be38b71eb6f4dfd3e4f992d964c1bac6eec
              4532064f69779b6014b3770891cf60c74f976e57588a6c976c9" heraus) wodurch die Wahrscheinlichkeit auf

              1:16^128 1: 13407807929942597099574024998205846127479365820592393377723561443721
              76403007354697680187429816690342769003185818648605085375388281194656
              9946433649006084096

              = 0.0000000000000000000000000000000000000000000000000000000000000000000
              00000000000000000000000000000000000000000000000000000000000000000000
              000000000000000000075

              verringert wird. So lange die Dateien zufällig sind (also nicht mit aufwenigen Verfahren zwei Dateien mit gleichem Hash aber verschiedenem Inhalt erzeugt und verschickt werden) sollte man sein Glück im Lotto versuchen, wenn man zwei Dateien bekommt, für die das zutrifft, weil wenn man diese klitzekleine Chance knackt, dann ist der Lottogewinn auch ein sehr wahrscheinliches Ereignis…

              Man muss sich also zwischen einer sehr unwahrscheinlichen Ereignis und dem Speicherplatz entscheiden… Das ist eine Frage der Wichtung und natürlich der Umstände. Wenn ich erwarte, dass viele Nutzer die selbe Datei speichern, dann werde ich wohl eher das machen, was z.B. das ZFS-Dateisystem (bei Verfügbarkeit von reichlich RAM) auch mit Duplikaten anstellen kann

              1. Hallo,

                und der Status war: es muss eine gewaltige Menge sein, bevor die heutigen Filesysteme bei Direktzugriff auf einen Namen in die Knie gehen.

                mit der Betonung auf heutige. Ich erinnere mich noch an Windows NT4, bei dem der Explorer gern mal >10s gebraucht hat, bis er endlich das Listing eines Verzeichnisses mit über 1000 Dateien angezeigt hat. Dabei war NTFS einen Hauch effizienter als FAT[1], aber nicht wirklich viel.

                Okay, damals war auch die Rechenleistung nicht mit der heutiger PCs vergleichbar.

                Nachdem ich diese (natürlich mit einem Skript) auf die Anfangsbuchstaben der Künstler/Künstler/CD/Titel.ogg verteilt hatte ging es…

                Mir genügt die Unterteilung nach Interpret als oberste Hierarchieebene. Das sind in meiner Sammlung knapp 700 mit in Summe etwa 5000 Dateien (Volumen: ca. 22GB).

                Live long and pros healthy,
                 Martin

                --
                Hunde, die bellen, beißen nicht.
                Jedenfalls nicht gleichzeitig.

                1. FAT32, to be precise. ↩︎

                1. Hallo Martin,

                  unerträglich negative Performance.... Ja: Das sind ein paar Tausend.

                  Die Frage wäre: Wobei. Öffnen einer bestimmten Datei oder Suchen im Directory - oder gar in den Metadaten der MP3 Files - nach bestimmten Künstlern?

                  bei dem der Explorer gern mal >10s gebraucht hat, bis er endlich das Listing eines Verzeichnisses mit über 1000 Dateien angezeigt hat.

                  Das war dann aber ein vollständiger Abruf des Directory Index, incl. Dateiinformationen. Das ist was anderes als "öffne die Datei 879edf862af.dat".

                  Deswegen schrub ich ja: Die Metainfos gehören in eine DB und nicht ins Directory.

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Hallo,

                    unerträglich negative Performance.... Ja: Das sind ein paar Tausend.

                    Die Frage wäre: Wobei. Öffnen einer bestimmten Datei oder Suchen im Directory - oder gar in den Metadaten der MP3 Files - nach bestimmten Künstlern?

                    in den Metadaten der Dateien zu suchen (also Dateiinhalt) dauert natürlich ziemlich lange. Aber auch beim Öffnen einer konkreten Datei ist vorher eine Directory-Suche erforderlich. Wenn das Verzeichnis auf dem Datenträger reichlich fragmentiert ist, kann das auch einen Moment dauern.

                    Im übrigen habe ich mich zumindest zu Zeiten von Windows 5.x noch gefragt, warum z.B. Verzeichnisse oder manche Systemdateien von der Defragmentierung ausgenommen waren.

                    bei dem der Explorer gern mal >10s gebraucht hat, bis er endlich das Listing eines Verzeichnisses mit über 1000 Dateien angezeigt hat.

                    Das war dann aber ein vollständiger Abruf des Directory Index, incl. Dateiinformationen. Das ist was anderes als "öffne die Datei 879edf862af.dat".

                    Nur geringfügig. Anders als die gängigen Linux-Filesysteme (ext2,3,4) speichern FAT und NTFS ja alle Metadaten vom Dateinamen bis hin zu den Attributen in einem zusammenhängenden Block. Habe ich beim Directory Scan den Dateinamen gefunden, habe ich automatisch auch alle weiteren Metadaten.
                    Das Aufbauen eines kompletten Verzeichnislistings dauert daher nicht wirklich länger als das Öffnen einer Datei, die zufällig physikalisch weit hinten im Verzeichnis steht.

                    Deswegen schrub ich ja: Die Metainfos gehören in eine DB und nicht ins Directory.

                    Kommt drauf an, welche Metainformationen du meinst. Wenn du Künstler, Titel, Erscheinungsjahr, Spieldauer und so'n Zeug meinst: Ja, 100% einverstanden. Wenn du aber auf Dateigröße, Timestamp, Besitzer oder Zugriffsrechte abzielst: Die gehören unbedingt ins Directory.

                    Live long and pros healthy,
                     Martin

                    --
                    Hunde, die bellen, beißen nicht.
                    Jedenfalls nicht gleichzeitig.
                    1. Hallo Martin,

                      Das Aufbauen eines kompletten Verzeichnislistings dauert daher nicht wirklich länger als das Öffnen einer Datei, die zufällig physikalisch weit hinten im Verzeichnis steht.

                      Kannst Du diese These belegen?

                      Meine Gegenthese:

                      Die MFT ist eine Relationale DB, die als B-Tree organisiert ist. Steht hier, ein Dokument aus einer Zeit, als Microsoft noch richtige Dokumentationen erstellte und veröffentlichte.

                      Zitat aus dem Artikel:

                      The MFT is a relational database that consists of rows of file records and columns of file attributes. It contains at least one entry for every file on an NTFS volume, including the MFT itself.

                      Und

                      The benefit of using B-tree structures is evident when NTFS enumerates files in a large folder. The B-tree structure allows NTFS to group, or index, similar file names and then search only the group that contains the file, minimizing the number of disk accesses needed to find a particular file, especially for large folders. Because of the B-tree structure, NTFS outperforms FAT for large folders because FAT must scan all file names in a large folder before listing all of the files.

                      D.h. wenn ich weiß, dass die Datei "Martins Bericht.doc" heißt und im Ordner "C:\Users\Martin\Documents" steht, springt NTFS relativ zügig über den B-Tree zur Datei "Martins Bericht.doc".

                      Wenn ich "C:\Users\Martin\Documents" auflisten will, wird es komplizierter, denn dann muss ich über den Folder Index gehen. Solange ich nur Namen suche, ist das ein Index Scan und fix, aber wenn ich auch Datei-Infos brauche, damm denke ich, dass man dann für jede gefundene Datei in die File Table einsteigen und sie holen muss.

                      Rolf

                      --
                      sumpsi - posui - obstruxi
                      1. Hi,

                        Das Aufbauen eines kompletten Verzeichnislistings dauert daher nicht wirklich länger als das Öffnen einer Datei, die zufällig physikalisch weit hinten im Verzeichnis steht.

                        Kannst Du diese These belegen?

                        nicht wirklich; ich bin von der allgemeinen Microsoft-Philosophie ausgegangen, möglichst fest an bewährten Dingen festzuhalten und möglichst wenig zu verändern. Davon ausgehend habe ich angenommen, dass NTFS zwar zusätzliche Felder pro Verzeichniseintrag hat (z.B. wegen der Besitzer- und Rechteverwaltung), aber ansonsten beim FAT-Konzept bleibt, alle Datei-Metadaten in einem Record zusammenzuhalten.
                        Und FAT12/16/32 kenne ich sehr intim. 😉

                        Die MFT ist eine Relationale DB, die als B-Tree organisiert ist. Steht hier, ein Dokument aus einer Zeit, als Microsoft noch richtige Dokumentationen erstellte und veröffentlichte.

                        Das ist ja mal interessant!

                        D.h. wenn ich weiß, dass die Datei "Martins Bericht.doc" heißt und im Ordner "C:\Users\Martin\Documents" steht, springt NTFS relativ zügig über den B-Tree zur Datei "Martins Bericht.doc".

                        Wenn ich "C:\Users\Martin\Documents" auflisten will, wird es komplizierter, denn dann muss ich über den Folder Index gehen. Solange ich nur Namen suche, ist das ein Index Scan und fix, aber wenn ich auch Datei-Infos brauche, damm denke ich, dass man dann für jede gefundene Datei in die File Table einsteigen und sie holen muss.

                        Schon, aber wenn ich das richtig verstanden habe, dann ist doch ein Verzeichnislisting auch nichts weiter als ein Table Scan mit dem Elternverzeichnis als Suchkriterium, bei dem man für jede Row als Treffer die weiteren Columns ausliest. Klingt für mich auch nicht nach großem Aufwand.

                        Aber immerhin mehr Aufwand als bei FAT, wo man nur den Einstiegspunkt finden muss, wo die dem Verzeichnis entsprechende Clusterkette anfängt, und dann Cluster für Cluster, Record für Record stur alle Einträge sequentiell durchgeht, ohne noch weiter filtern zu müssen - es sei denn, man hat ein Suchmuster für die Dateinamen angegeben.

                        Dass die Anzeige des Verzeichnislistings im Explorer erst mit Verzögerung kommt, liegt aber auch daran, dass der immer nach irgendeinem Kriterium sortieren will, und deshalb erst alle gefundenen Einträge sammeln muss. Seit es Windows gibt, habe ich mir schon immer eine Explorer-Ansicht "unsortiert" gewünscht, also einfach in der Reihenfolge, in der die Einträge physikalisch auf dem Datenträger vorliegen.
                        Die mir bekannten Dateimanager unter Linux können das auch alle nicht.

                        Live long and pros healthy,
                         Martin

                        --
                        Hunde, die bellen, beißen nicht.
                        Jedenfalls nicht gleichzeitig.
                        1. Hallo Der,

                          Schon, aber wenn ich das richtig verstanden habe, dann ist doch ein Verzeichnislisting auch nichts weiter als ein Table Scan mit dem Elternverzeichnis als Suchkriterium,

                          Wenn ich den Microsoft-Text richtig verstanden habe, ist die MFT nach Dateien sortiert, unabhängig von Directories. Es bedeutet natürlich auch, dass die MFT eine Tabelle mit einem non-unique primary key ist.

                          Verschieben einer Datei bedeutet umhängen in den Directory Indices. Und mutmaßlich auch ein paar Dinge mehr - er muss ja schon wissen, ob ich "Martins Text.doc" in C:\Users\Martin oder C:\Users\Rolf meine.

                          Aber die Details habe ich nie ergründet.

                          Rolf

                          --
                          sumpsi - posui - obstruxi
                          1. Hallo Rolf,

                            Schon, aber wenn ich das richtig verstanden habe, dann ist doch ein Verzeichnislisting auch nichts weiter als ein Table Scan mit dem Elternverzeichnis als Suchkriterium,

                            Wenn ich den Microsoft-Text richtig verstanden habe, ist die MFT nach Dateien sortiert, unabhängig von Directories.

                            eben, so habe ich es auch verstanden. Aber dann muss der Index des Directory, in dem eine Datei liegt (und das ja selbst auch durch einen Eintrag in der MFT repräsentiert wird), als Property in den Dateieigenschaften vermerkt sein. Nach dem muss man die Tabelle beim Directory Scan filtern.

                            Verschieben einer Datei bedeutet umhängen in den Directory Indices.

                            Verschieben bedeutet nach meinem Verständnis einfach, die Directory ID eines ausgewählten Datei-Objekts auf einen anderen gültigen Wert zu ändern.

                            Live long and pros healthy,
                             Martin

                            --
                            Hunde, die bellen, beißen nicht.
                            Jedenfalls nicht gleichzeitig.
                            1. Hallo Martin,

                              Nach dem muss man die Tabelle beim Directory Scan filtern.

                              Ich habe das so verstanden, dass die Directories ihre eigenen B-Trees haben, als secondary Index, so dass man ein Verzeichnis mittels indexsequenziellem Zugriff lesen kann, bzw. mit reinem Index-Scan, solange man nur auf Namen filtert (bzw. nur Namen auflistet).

                              D.h. ein MFT full table Scan, um ein Dir aufzulisten, ist nicht erforderlich. Da die MFT zu Beginn 12.5% der Platte einnimmt und von da an noch wachsen kann, könnte das eeetwas lange brauchen.

                              Den Unterschied merkt man auf der Befehlszeile, bspw. in der Powershell. Der Ordner C:\Windows\WinSXS ist riesig (18000 Ordner). Wenn ich dort "dir" aufrufe (oder Get-ChildItem auf powershellisch), dauert es ca 800ms, bis der Inhalt eingefangen ist. Natürlich fange ich ihn in einer Variablen, das Ausgeben auf die Konsole würde die Messung verfälschen. Statt dessen kann ich auch cmd /c dir aufrufen, dann bekomme ich kein Array von FileSystemInfo Objekten, sondern ein String[] als Capture der Konsolenausgabe und es dauert 450ms. Damit verzichte ich aber auf den Vorteil von Powershell - eine objektorientierte Pipeline.

                              Ich kann aber auch - weil Powershell ja eine .net Shell ist - die GetFileSystemEntries Methode der System.IO.Directory Klasse nutzen. Die liefert nur die Namen, d.h. geht NUR über den Directory Index, und ist in 50ms fertig. Sechzehnmal schneller.

                              Im Normalfall fällt es natürlich kaum auf. Aber wir sprachen ja von pathologischen Fällen mit VIELEN Einträgen.

                              Rolf

                              --
                              sumpsi - posui - obstruxi
                  2. Die Frage wäre: Wobei.

                    Listen des Directorys insbesondere durch den Apache himself…

              2. Wenn man den hash eines Dateinhalts als Dateiname benutzt sollte man die PHP-Funktionen md5_file() bzw. sha1_file() benutzen.

                Will man den hash als Dateiname (oder nur ermitteln) und soll es ein SHA512-Hash sein, dann ist es definitiv unvorteilhaft, hash( 'sha512', file_get_contents( datei ); zu benutzen. Besser ist es, hier, die Rückgabe des Shell-Befehls openssl sha512 datei zu verwerten.

                Das Experiment:

                <?php
                
                function myFileHashSHA512( $file ) {
                
                	$openssl = '/usr/bin/openssl';
                
                	if ( is_executable( $openssl ) ) {
                
                		$cmd = $openssl . ' SHA512 < "' . str_replace( '"', '\"', $file ) . '"';
                		return trim( str_replace ( '(stdin)= ', '', `$cmd` ) );
                
                	} else {
                
                		trigger_error( "$openssl ist nicht installiert, hash_file('sha512') wurde benutzt", E_USER_NOTICE );
                		echo "PHP: hash_file( 'sha512', ${file} )" . PHP_EOL;
                		#return hash( 'sha512', file_get_contents( $file ) );
                		return hash_file( 'sha512', $file );
                
                	}
                }
                
                # 1GB große Datei:
                $file = "/home/fastix/vmware/Andere/TrueNAS12/TrueNAS12-s001.vmdk";
                
                echo myFileHashSHA512( $file ) , PHP_EOL;
                
                echo memory_get_peak_usage() , " Bytes benutzt." , PHP_EOL;
                
                

                Erstes Ergebnis - mit korrektem pfad zu openssl:

                time php test.php 
                fc5796bf3eb413e54bdf12c3feec6a8733d29487385dd36f5a5cf1600167836a8078d3d588ef6ecb209967e0f7126c763d294e4d32736537f84a97706473bdb1
                433560 Bytes benutzt.
                
                real  0m1,708s
                user  0m1,557s
                sys	  0m0,140s
                

                Zweites Ergebnis - falscher Pfad zu openssl, hash_file() wird benutzt:

                fastix@trainer:/tmp$ time php test.php 
                PHP Notice:  foo/usr/bin/openssl ist nicht installiert, hash_file('sha512') wurde benutzt in /tmp/test.php on line 14
                PHP: hash_file( 'sha512', /home/fastix/vmware/Andere/TrueNAS12/TrueNAS12-s001.vmdk )
                fc5796bf3eb413e54bdf12c3feec6a8733d29487385dd36f5a5cf1600167836a8078d3d588ef6ecb209967e0f7126c763d294e4d32736537f84a97706473bdb1
                432344 Bytes benutzt.
                
                real  0m3,241s
                user  0m3,065s
                sys   0m0,164s
                

                Der Hash ist (natürlich) der gleiche - aber die benötigte Zeit unterscheidet sich erheblich: Zumindest derzeit ist das Linux-Programm openssl deutlich schneller als das sehr aktuelle PHP 8.0.5 (cli) (built: May 3 2021 11:30:57) ( NTS ).

                Benutzt man die Funktionen

                • md5( file_get_contents( $file ) ),
                • sha1( file_get_contents( $file ) ),
                • hash('sha512', file_get_contents( $file ) )

                wird man anhand derAusgaben von memory_get_peak_usage() sehr genau sehen, was man falsch gemacht hat… Damit habe ich es auf bis zu 1109154016 (1.109.154.016 ~ 1 GB statt 430KB) gebracht.

                Wer will kann ja auch md5_file() vers. openssl MD5 bzw. openssl SHA1 vers. sha1_file() testen…

      2. Hallo,

        Davon unabhängig könntest du eigentlich file auch direkt an myform.append() übergeben. Den Sinn des Umweges über FileReader und blob sehe ich in diesem Beispiel nicht.

        Den Umweg muss ich wohl gehen, da eine per Drag&Drop aus meinem Mail-Programm übertragenes Attachment im file-object die Größe 0 hat. Damit wurde vom PHP immer nur eine 0-Byte-Datei erstellt.

        ach, die Geschichte war das.

        Ich gehe mal davon aus, dass das nur ein Test sein soll und du $name aus dem Formular nicht wirklich völlig ungeprüft als Dateinamen auf deinem Server benutzen willst.

        Klar, ich überprüfe vorher auf Sonderzeichen und auch den Dateityp.

        Definiere "Sonderzeichen". 😉
        Und wie ermittelst du den Dateityp? Nur anhand des Namens?

        Aber welche Risiken könnten dadurch noch entstehen? Außer, dass der Server die Datei zwar speichert, sie aber nicht mehr gelesen werden kann (z.b. wenn deutsche Umlaute im Linux als Fragezeichen gespeichert werden).

        Das Umlautproblem hast du nur, wenn irgendwo etwas mit der Zeichencodierung nicht stimmt. Aber vermutlich würdest du es nicht lustig finden, wenn jemand .htaccess als Dateinamen angibt. Dabei will der doch nur spielen.

        Live long and pros healthy,
         Martin

        --
        Hunde, die bellen, beißen nicht.
        Jedenfalls nicht gleichzeitig.