beatovich: linefeed Überraschung

hallo

Ich übertrage den Inhalt einer textarea (Firefox, Windows) an perl. (ich mache das über ein formData Objekt.)

Mit perl lese ich über CGI aus

$cgi->param("data")

Resultat: aus einem \n wird letztendlich ein CRCRLF

CRLF erschiene mir logisch. Was aber könnte dies erzeugen?

  1. hallo

    Mit perl lese ich über CGI aus

    $cgi->param("data")

    Resultat: aus einem \n wird letztendlich ein CRCRLF

    CRLF erschiene mir logisch. Was aber könnte dies erzeugen?

    Also das Übel ist in Perl

    			open(my $fh, ">", $thisfile ) or do {
    				$report .= "File konnte nicht geespeichert werden \n";
    				print response($report);
    				exit 0;
    			};
    			print $fh $cgi->param("data");
    			close $fh;
    
    

    $cgi->param("data") enthält kein \r\r\n Nach print existiert \r\r\n im Dokument.

    Warum?

    1. Moin @@beatovich,

      wo (unter welchem Betriebssystem) läuft denn dein Perlprogramm?

      Viele Grüße
      Robert

      1. hallo

        Moin @@beatovich,

        wo (unter welchem Betriebssystem) läuft denn dein Perlprogramm?

        Windows, localhost.

        Eigentlich sollte print keine Daten ändern. Notepad ändert Daten beim öffnen auch nicht.

        Das ist übrigens das erste Mal, das mit das mit Perl und dem CGI Modul untergekommen ist, deshalb zuerst mein Verdacht auf der Client-Seite. Die ist aber sauber.

        ein

        		print $fh join("\n", split(/\r\n/,$cgi->param("data") ) );
        

        bereinigt den Fehler. Datenmanipulation (auch wenn Content-type=text) möchte ich aber wenn möglich vermeiden.

        1. Moin @@beatovich,

          wo (unter welchem Betriebssystem) läuft denn dein Perlprogramm?

          Windows, localhost.

          wie ich vermutet hatte.

          Eigentlich sollte print keine Daten ändern.

          Das tut es aber, wie wir sehen.

          Notepad ändert Daten beim öffnen auch nicht.

          Das ist eine Frage der Einstellung (wenn man mehr als Notepad verwendet).

          ein

          print $fh join("\n", split(/\r\n/,$cgi->param("data") ) );
          

          bereinigt den Fehler.

          Das geht auch billiger mit chomp.

          Datenmanipulation (auch wenn Content-type=text) möchte ich aber wenn möglich vermeiden.

          Dann musst du Perl (oder die darunter liegende C-Bibliothek) davon überzeugen 😉

          Viele Grüße
          Robert

    2. print $fh $cgi->param("data");

      Bedenke den Listenkontext von print und daß param eben eine Liste liefert. Um das auszuschalten

      print $fh scalar $cgi->param("data");
      # oder
      print $fh $cgi->param("data").'';
      

      MfG

      1. hallo

        print $fh $cgi->param("data");

        Bedenke den Listenkontext von print und daß param eben eine Liste liefert. Um das auszuschalten

        print $fh scalar $cgi->param("data");
        # oder
        print $fh $cgi->param("data").'';
        

        Listenkontext ist nicht die Ursache, habe ich soeben getestet.

        1. hallo

          Listenkontext ist nicht die Ursache, habe ich soeben getestet.

          Ok, sehe ich auch so. Nächstes Thema: Windows und Textdateien. Machs mal so:

          use IO::File;
          $fh = IO::File->new;
          $fh->open($file, O_BINARY|O_RDWR|O_TRUNC|O_CREAT);
          

          MfG

  2. hi,

    das kommt vom Browser. Der sendet grundsätzlich 0x0D 0x0A als Zeilenumbruch was CRLF entspricht.

    MfG

    1. hallo

      hi,

      das kommt vom Browser. Der sendet grundsätzlich 0x0D 0x0A als Zeilenumbruch was CRLF entspricht.

      Das ist ja OK, schliesslich ist das die Norm für HTTP

      Das Problem ist aber dass Perl daraus bei print ein CRCRLF macht.

      1. hallo

        Das Problem ist aber dass Perl daraus bei print ein CRCRLF macht.

        kann ich nicht nachvollziehen. Selbst wenn die Ausgabe der header nicht HTTP konform ist, z,b

        #!/usr/bin/perl
        
        use strict;
        use warnings;
        use CGI;
        
        my $cgi = CGI->new;
        
        print "\r\n\r\n\r\r\n\n\r\n\r\n";
        my @hexi = map{sprintf "0x%02X",$_} unpack("C*", $cgi->param('text'));
        print "@hexi";
        
        # 0x0D 0x0A in Response
        # bei einem Zeilenumbruch in textarea
        

        der Server parst das ja und korrigiert es. Zeig mal Dein <form>,

        MfG

        1. hallo

          der Server parst das ja und korrigiert es. Zeig mal Dein <form>,

          	else if( status==200 || status==304  || status==404 ){ 
          		var formData = new FormData();
          		formData.append('edit_pass', edit_pass.value );
          		if(status==404){ formData.append('action', 'create' ); }
          		else if( status==200 || status==304 ){ formData.append('action', 'update' ); }
          		formData.append('url', href );
          		formData.append('data', edit_area.value ); // <---------------
          		xhr = new XMLHttpRequest();
          		xhr.open("POST","../pl/kissedit.pl");
          		xhr.addEventListener('load', function(event) {
          			console.warn(xhr.statusText, xhr.responseText);
          			edit_f.setAttribute('srcdoc', xhr.responseText);
          		});
          		console.log(formData);
          		xhr.send(formData);
          	}
          

          Es wird der Inhalt einer normalen Textarea ausgelesen

        2. Hallo pl,

          hast Du das unter Windows nachzuvollziehen versucht? (Siehe oben, beat verwendet das).

          Rolf

          --
          sumpsi - posui - clusi
          1. hi @Rolf B

            Windows hat ein Problem mit Textdateien und open(). Die Lösung ist ein sysopen() call mit dem Flag O_BINARY was IO::File importiert.

            Genau das konnte ich nachvollziehen.

            MfG

            1. hallo

              hi @Rolf B

              Windows hat ein Problem mit Textdateien und open(). Die Lösung ist ein sysopen() call mit dem Flag O_BINARY was IO::File importiert.

              Genau das konnte ich nachvollziehen.

              Lese gerade https://perldoc.perl.org/functions/binmode.html

              Da wäre möglich

              open(my $fh, ">:raw", $file) ...

              Das soll das Filehandle in den binmode schalten.

              1. open(my $fh, ">:raw", $file) ...
                Das soll das Filehandle in den binmode schalten.
                

                Das ist an dieser Stelle zu spät. Machs mit IO::File (sysopen) und setze das Flag O_BINMODE.

                Andernfalls:

                open FH, "> d:/tmp/f.txt";
                print FH "\r\n";
                # 0D 0D 0A
                

                MfG

                1. hallo

                  open(my $fh, ">:raw", $file) ...
                  Das soll das Filehandle in den binmode schalten.
                  

                  Das ist an dieser Stelle zu spät. Machs mit IO::File (sysopen) und setze das Flag O_BINMODE.

                  Nein, das macht genau das was ich will.

                  :raw gibts auch für altes Perl. Definitiv meine Methode ab jetzt.

                  1. hallo

                    :raw gibts auch für altes Perl. Definitiv meine Methode ab jetzt.

                    Meine Methode ist IO::File mit den entsprechenden Flags die als Konstanten importiert werden. Da siehste auch am Code klar was Sache ist.

                    MfG

            2. Hallo pl,

              von Perl und seinen spezifischen Problemen habe ich ja keine Ahnung. Aber bei dem, was hier diskutiert wird, habe ich doch die Stirn, sie zu runzeln.[1]

              Windows hat ein Problem mit Textdateien und open(). Die Lösung ist ein sysopen() call mit dem Flag O_BINARY was IO::File importiert.

              Windows? Oder Perl für Windows? Oder doch ein Layer-8 Problem? Wie mir ein Mönch verriet, wird Perl-intern das Zeilenende durch \n repräsentiert, und der :crlf Layer übersetzt unter Windows deshalb jedes \n beim Schreiben in \r\n. Aber wenn man dann einen String hat, der nicht den Perl-Konventionen entspricht (wie es bei beat der Fall ist, seine Zeilen sind \r\n terminiert), dann gibt's \r\r\n Gemüse. Weil der :crlf Layer von korrekten Perl-Zeilenumbrüchen ausgeht.

              Der Fehler wäre daher in $cgi->param oder im Umgang damit zu suchen. Eine Zeile wird in Perl durch \n terminiert, d.h. beim Auslesen müsste sichergestellt werden, dass korrekte Zeilenterminatoren gesetzt werden. Wenn Perl dafür keine Automatik bereitstellt, muss man es an dieser Stelle von Hand tun. Es wurdert mich aber schon, dass Perl es nicht selbst tut. Bei der Ausgabe machen sie sich diese Mühe, und bei der Eingabe nicht? Merkwürdig. Das bringt mich zu der Überlegung, ob es Alternativen zu $cgi->param geben könnte, die hier geeigneter sind. Aber wie mir ein anderer Mönch predigte - das ist nicht so. Ich weiß nur nicht, ob ich ihm vertrauen kann, denn er sagte auch dass die Zeilenterminatoren vom Betriebssystem des Browsers abhängen - was laut Spec NICHT der Fall ist. Vielleicht liefert ein Alt-Mac noch \r als Zeilenterminator, d.h. die Logik müsste sein:

              1. Ersetze \r*\n durch \n (standardisiert Windows+Unix)
              2. Ersetzte \r durch \n (falls es ein Alt-Mac war)

              Den vom Browser erhaltenen String einfach binär in eine Datei zu schreiben, ist jedenfalls FALSCH. Textdateien sind plattformabhängig. Perl möchte ein plattformneutrales Textdatei-API anbieten - was sinnvoll ist. Diese Schicht wird durch binäres Schreiben umgangen. Will man das Script mal eben so auf einen Unix-basierenden Server übertragen, dann hat man auf einmal unerwünschte Umbrüche im Text.

              Rolf

              --
              sumpsi - posui - clusi

              1. Heinz Erhardt ↩︎

              1. hallo

                Den vom Browser erhaltenen String einfach binär in eine Datei zu schreiben, ist jedenfalls FALSCH.

                Also der String kommt aus dem HTTP Kontext und da haben wir immer \r\n zu erwarten.

                Es spricht eigentlich nichts dagegen solche Daten auch etsprechend zu speichern.

                Das konnte mit einer einfachen Angabe gelöst werden

                open(my $fh, ">:raw", $data)...

                Solange man weiss, was (aus welchem Kontext) man letztlich speichert, muss man die Betriebsspezifischen Zeilenden nicht mal respektieren.

                Textdateien sind plattformabhängig. Perl möchte ein plattformneutrales Textdatei-API anbieten - was sinnvoll ist.

                Plattform-neutral ist wohl nicht der korrekte Ausdruck dafür. Dazu müsste Perl ja seine eigene Plattform darstellen.

                Diese Schicht wird durch binäres Schreiben umgangen. Will man das Script mal eben so auf einen Unix-basierenden Server übertragen, dann hat man auf einmal unerwünschte Umbrüche im Text.

                Sobald man einen über HTTP gespeicherten Inhalt über andere Methode als HTTP ausliefert, dann ja. Anderseits heisst ja übertragen Dass eine Textdatei erst mal in die Plattform HTTP übersetzt wird.

                Was mich aber wundert, ist eben dieses Verhalten, dass \r\n beim unmodifizierten Schreiben zu einem \r\r\n wird. Mit dem CGI Modul hat das nichts zu tun. Ich kann mir das nur so erklären, dass der dafür verantortliche Prozess ein simples s{\n}{\r\n}g vollführt, ohne weiter zu fragen. Das halte ich nicht für korrekt.

                1. Hallo beatovich,

                  Solange man weiss, was (aus welchem Kontext) man letztlich speichert, muss man die Betriebsspezifischen Zeilenden nicht mal respektieren.

                  Ja okay, wenn Du nichts weiter willst, als aus HTML stammende Daten wieder an HTML zurückzugeben, dann ist das so. Dann kannst Du das als Blob betrachten und binär speichern.

                  Ich dachte, du wolltest den Text für andere Anwendungen als Textdatei bereitstellen. Oder vielleicht in einer Perl-Verarbeitung in Zeilen zerlegen (da würden die \r dann auch stören).

                  Perl möchte ein plattformneutrales Textdatei-API anbieten Dazu müsste Perl ja seine eigene Plattform darstellen

                  So war's nicht gemeint. Perl läuft auf verschiedenen Plattformen, und ich habe das so verstanden, dass der :crlf Layer diesen Umstand vor dem unter Perl laufenden Script verbergen möchte, so dass Du das Script unmodifiziert unter Unix oder Windows laufen lassen kannst.

                  Was mich aber wundert, ist eben dieses Verhalten, dass \r\n beim unmodifizierten Schreiben zu einem \r\r\n wird.

                  Das hatte ich doch aus der Mönchspredigt herausgedeutet: Der :crlf Layer weiß nichts von deinen binären Absichten und will darum mithelfen. Diese Hilfe besteht unter Windows in der \n -> \r\n Übersetzung. Ein Linux-Perl würde das nicht tun. Eigentlich müsste ein Windows-Perl, wenn Du die Datei mit dem gleichen Layer einliest, das unnötige \r auch wieder entfernen. DU hast die Datei aber mit einem anderen Programm gelesen (Hexedit?, Notepad?) und hast ein betriebssystemkonformes Textdateiformat erwartet. Das bekommst Du aber nur, wenn die data chain korrekt geknüpft ist.

                  Wenn Du :raw verwendest, würden Dich auf einem Unix-System die unsinnigen \r in der Datei stören. Oder die komischen \n auf einem Mac-OS Server (hm, keine Ahnung ob es sowas je gab :D).

                  Rolf

                  --
                  sumpsi - posui - clusi
              2. hi @Rolf B

                von Perl und seinen spezifischen Problemen habe ich ja keine Ahnung. Aber bei dem, was hier diskutiert wird, habe ich doch die Stirn, sie zu runzeln.[^1]

                Windows hat ein Problem mit Textdateien und open(). Die Lösung ist ein sysopen() call mit dem Flag O_BINARY was IO::File importiert.

                Windows? Oder Perl für Windows?

                Es ist ein Windows Problem. Und es wurde hier vor ungefähr 10 Jahren mal durchgenommen.

                Wie mir ein Mönch verriet, wird Perl-intern das Zeilenende durch \n repräsentiert, und der :crlf Layer übersetzt unter Windows deshalb jedes \n beim Schreiben in \r\n. Aber wenn man dann einen String hat, der nicht den Perl-Konventionen entspricht (wie es bei beat der Fall ist, seine Zeilen sind \r\n terminiert), dann gibt's \r\r\n Gemüse. Weil der :crlf Layer von korrekten Perl-Zeilenumbrüchen ausgeht.

                Nun, das mag sein. Aber es löst das Problem nicht, weil mit open() das Kind bereits in den Brunnen gefallen ist. So lautet die Löung eben sysopen() mit dem Flag O_BINARY.

                Der Fehler wäre daher in $cgi->param oder im Umgang damit zu suchen.

                Nein. Mit CGI:: param() hat das Ganze nun wirklich gar nichts zu tun.

                Eine Zeile wird in Perl durch \n terminiert,

                Auch falsch. Es gibt eine Voreinstellung für chomp() und die ist mit $/ = "\n" als Default festgelegt (INPUT_RECORD_SEPARATOR).

                Mit Perl kann man seinen Code vorzüglich plattformunabhängig machen: Indem man IO::File nutzt womit die benötigten Flags gleich als Konstanten importiert werden.

                Man muss sie nur nutzen,

                O_BINARY vergessen:

                my $fh = IO::File->new;
                $fh->open("d:/tmp/f.txt", O_RDWR|O_TRUNC|O_CREAT) or die $!;;
                $fh->print("\n");
                print $fh->tell(); # 2
                $fh->close;
                

                Aus jedem "\n" entsteht "\r\n"

                MfG

                1. Hallo pl,

                  Es ist ein Windows Problem. Und es wurde hier vor ungefähr 10 Jahren mal durchgenommen.

                  Da war ich hier noch nicht aktiv :) Aber das \r kommt vom :crlf Layer hinein. Der ist hier im Spiel, denn das von beat anfangs gezeigte open(my $fh, ">", $thisfile ) verwendet per Default auf X-Betriebssystemen den :raw Layer und unter Windows den :crlf Layer. DAS ist Fakt.

                  Eine Zeile wird in Perl durch \n terminiert, Auch falsch. Es gibt eine Voreinstellung für chomp()

                  Wie gesagt - ich habe von Perl keine Ahnung und habe das möglicherweise zu universell formuliert. Aber chomp und IO auf Textdateien - das sind wohl zwei verschiedene Dinge. Ja gut, nicht GANZ verschieden, chomp benutzt man da zum Absägen der Zeilenendezeichen, aber es sind zwei verschiedene Aktivitäten.

                  Der Punkt ist doch: der :crlf Layer geht davon aus, dass eine Zeile durch \n terminiert ist. Bei dem von HTML gelieferten String ist das nicht der Fall. Wenn ich ein Script haben will, das an dieser Stelle keine Plattformabhängigkeit hat, dann muss ich wohl in Perl die \r entfernen und dann von Perl den Default-Layer auswählen lassen. Andernfalls habe ich den falschen Dateiinhalt, wenn ich mich vom Windows-Spielplatz zu den professionellen Junx bewege. Gelle?

                  Alles natürlich nur unter der Voraussetzung, dass die Datei für die Webanwendung mehr ist als ein Blob.

                  Rolf

                  --
                  sumpsi - posui - clusi
                  1. hallo

                    Wenn ich ein Script haben will, das an dieser Stelle keine Plattformabhängigkeit hat, dann muss ich wohl in Perl die \r entfernen und dann von Perl den Default-Layer auswählen lassen.

                    Technisch betrachtet mag das zu gewünschten Resultat passen

                    also auf Windows

                    Network \r\n -> via \n in Variable zu \r\n in Datei

                    auf Unix

                    Network \r\n -> via \n in Variable zu \n in Datei

                    Damit haben wir aber nur gerade zwei verbreitete Plattformen befriedigt. Wirklich unabhängig ist das nicht.

                    1. Hallo beatovich,

                      das ist zwar mittlerweile eine akademische Diskussion, aber du hast wohl recht.

                      Das logische Linefeed in Perl ist \n, das findet man an diversen Stellen. Ein I/O Layer hätte also die Aufgabe, ein \n in den plattformspezifischen Line-Terminator zu übersetzen.

                      Was ich nicht gewusst habe, ist, dass Perl diese Aufgabe NUR im DOS/Windows Bereich dem I/O Layer überlässt (also diesem :crlf Dingsbums). Andere Perl-Portierungen deuten \n um. \n ist \x0a unter Unix und DOS sowie dessen Abkömmlingen (Windows+OS/2), aber es ist wird von Perl für MacOS als \x0d aufgefasst. Auf EBCDIC-Plattformen ist es \x15 oder \x25. Quelle

                      D.h. auf Plattformen mit \n != \x0a funktioniert der vorgeschlagene Replace nicht. Korrekt wäre wohl /(\x0d?\x0a)\|\x0d/ -> '\n', damit sollte man besser zurecht kommen. Immer vorausgesetzt, dass der Browser einen Datenstrom liefert, der auf einer ASCII-Codepage bzw. Unicode basiert. Liefert er EBCDIC, hast Du eh andere Probleme, und der Browser erst recht.

                      Rolf

                      --
                      sumpsi - posui - clusi
                      1. hallo

                        das ist zwar mittlerweile eine akademische Diskussion, aber du hast wohl recht.

                        Nicht ganz so akademisch. Ich habe nömlich meine Methode nochmals überdacht. Tatsächlich ist es am Besten, Zeilen einfach mit einem logischen \n zu joinen, und den Rest dann dem Plattformabhängigen Perl zu überlassen.

                        habe ich jetzt auch so gemacht 😉

                        1. hallo

                          Ich habe nömlich meine Methode nochmals überdacht. Tatsächlich ist es am Besten, Zeilen einfach mit einem logischen \n zu joinen, und den Rest dann dem Plattformabhängigen Perl zu überlassen.

                          Perl an sich ist nicht plattformunabhängig. Dafür musst Du schon selbst was tun. Zum Beispiel dem I/O Layer mitteilen, daß er keine OS-abhängigen Voreinstellungen benutzt. Was von Perl v.5.6.1 bis zur aktuellen Subversion über das Flag O_BINMODE von IO::File als Konstante importiert, denkbar einfach zu machen ist.

                          Und noch etwas: Ich bin seit über 20 Jahren in heterogenen OS Umgebungen unterwegs mit Perl. Zum Einstellen diesbezüglicher Unabhängigkeiten gehört noch ein bischen mehr, unter anderem daß sämtlichst beteiligte Editoren einen einheitlichen Zeilenumbruch z.B. \n verfeueren und daß Dateitransfers (auch Textdateien!) per FTP//SCP grundsätzlich im Binmode erfolgen.

                          MfG

                      2. Hallo @@Rolf B,

                        \n ist \x0a unter Unix und DOS sowie dessen Abkömmlingen (Windows+OS/2), aber es ist wird von Perl für MacOS als \x0d aufgefasst. Auf EBCDIC-Plattformen ist es \x15 oder \x25. Quelle

                        Mit dem ganz dicken Hinweis, das hier Mac OS X kein Mac OS ist, sondern ein Unix!

                        Viele Grüße
                        Robert

                        1. Hallo Robert,

                          sie mussten dafür zwar erstmal eine Rhapsodie in Brummbär- und Katzenmusik aufführen, aber ab Leopard hat man es ihnen dann zertifiziert, ja :)

                          Danke für den Hinweis, der Leopard ja mittlerweile schon 11 Jahre alt (und der Brummbär stammt von 2000).

                          Rolf

                          --
                          sumpsi - posui - clusi
                  2. hi @Rolf B

                    Es ist ein Windows Problem. Und es wurde hier vor ungefähr 10 Jahren mal durchgenommen.

                    Da war ich hier noch nicht aktiv

                    Es hängt sehr wahrscheinlich auch mit der Perlversion zusammen, damals lief bei mir noch ein 5.6.1 und da kam mit :raw jede Hilfe zu spät. Das CR Problem konnte ich jedoch auch unabhängig von Perl mit einer anderen Anwendung nachvollziehen.

                    Das war damals übrigens auch für mich der Anlass auf IO::File umzusteigen und O_BINMODE generell zu setzen, womit all diese plattformabhängigen Dinge ein für allemal Geschichte sind. So bin ich seither nie wieder mit diesem Problem konfrontiert worden.

                    Und wer heute, im Zeitalter von OOP und Modern Perl unter Verzicht auf aktuelle und zweckmäßige Libraries wie IO::File immer noch mit open() operiert, darf sich dann halt nicht wundern wenn ihm uralte Probleme auf die Füße fallen. Was zweifelsfrei auch eine Frage des Programmierstils ist.

                    MfG