Ankündigung

Einklappen
Keine Ankündigung bisher.

mb_substr mit invaliden UTF-8 strings

Einklappen

Neue Werbung 2019

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • mb_substr mit invaliden UTF-8 strings

    Ich benutze mb_substr() um strings für Diagnosezwecke in einzelne Zeichen zu zerlegen.
    Diese Strings können bei meiner Anwendung vom Grundsatz beliebige Byte-Sequenzen sein.
    Das Verhalten von mb_substr() für solche Byte-Sequenzen ist nach meiner Kenntnis nicht dokumentiert.
    Wenn doch, bitte hier die Links dazu posten.

    Vorab: Ist hier zwar unter Fortgeschritten, kann jedoch nicht schaden um Missverständnisse zu vermeiden.
    Was verstehe ich unter invaliden UTF-8 Strings?

    Valide UTF-8 Strings ist das womit wir gewöhnlich arbeiten:
    PHP-Code:
    $str "aäö€"
    Um diesen String auf UTF8 zu validieren gibt es eine einfache Möglichkeit:
    PHP-Code:
    $isValidUTF8 = (bool)preg_match('//u',$str); 
    Invalide UTF-8 strings entstehen wenn valide Strings falsch verarbeitet werden.
    Als einfaches Beispiel wenn ich so versuche das letzte Zeichen zu entfernen:

    PHP-Code:
    $str substr("aäö€",0,-1);  //Das ist falsch!
    $isValidUTF8 = (bool)preg_match('//u',$str);
    var_dump($isValidUTF8$str);
    //bool(false) string(7) "aäö��" 
    Solche invaliden UTF-8-Strings bereiten dann oft auch Profis massive Probleme an Stellen wo sie nicht vermutet werden.

    Mir geht es speziell um das folgende Verhalten von mb_substr() womit auch Bytesequenzen geliefert werden die kein UTF-8 Zeichen sind.
    Beispiel:

    PHP-Code:
    $invalidUTF8Str substr("aäö€",0,-1); 
    $subStr mb_substr($invalidUTF8Str,3,1);
    var_dump($subStr);  //string(2) "��" 
    Bin dankbar für alle Erfahrungen und Hinweise zu diesen Verhalten von mb_substr.
    PHP-Klassen auf github


  • #2
    Also ich habe ja eigentlich keine Ahnung davon, aber wenn ich mir die erste Tabelle hier ansehe:
    https://en.wikipedia.org/wiki/UTF-8
    1 7 U+0000 U+007F 0xxxxxxx
    2 11 U+0080 U+07FF 110xxxxx 10xxxxxx
    3 16 U+0800 U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
    4 21 U+10000 U+10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    Würde ich halt behaupten dass er aufgrund der ersten Bits erkennt, wo ein neuer Character beginnt und aus wievielen Bytes der Character besteht und nachfolgende Bytes beginnen immer mit 10xxxxx.
    € besteht aus 3 Bytes (?), mb_substr() kann aber nur eine Sequenz aus 2 Bytes ausmachen, wo 3 erwartet werden und liefert eben zumindest diese beiden Bytes zurück. Also alles vom Initiator-Byte bis zum abrupten Ende.

    Gut, habe dafür natürlich keine Quelle, aber das scheint mir die einzig logische Vorgehensweise, wenn ich das selbst programmieren müsste.

    Edit: Vielleicht sollte ich auch die Vermutungen lassen und jemand kann übersetzen was hier wirklich passiert: https://github.com/php/php-src/blob/...filter.c#L1123

    Kommentar


    • #3
      Komplizierter wird es wenn invalide UTF-8 Fragmente innerhalb einer Zeichenkette liegen. mb_substr versucht dann vermutlich ausgehend vom 1.Byte genau so viele Bytes zu greifen wie es entsprechend der UTF-8 Codierungsvorschrift erwartet. Auch nur eine Vermutung, denn durch den Quelltext steige ich nicht durch. Doch der folgende kleine Test bestärkt diese These. Dort wird vom 3 Byte Zeichen "€" nur das erste Byte genommen wird und dann der string "abc" angehangen. Da nach dem 1. Byte noch 2 weitere Byte erwartet werden wird "ab" noch den ersten Zeichen zugeordnet und "c" als 2. Zeichen erkannt.
      PHP-Code:
      $str substr("€",0,1)."abc";
      for(
      $i=0; ($subStr mb_substr($str,$i,1,"UTF-8")) !== "";$i++){
        
      var_dump($subStr);
      }
      //string(3) "�ab" string(1) "c" 
      Zum Hintergrund:
      Bin dabei eine Funktion/Methode zu schreiben, die UTF-8 Zeichen (Strings) in die unter PHP 7 zulässigen String-Notation mit Unicode-Kodierung konvertiert, also "€" in " \u{20ac}".
      Mit dieser Notation können UTF-8 Zeichen mit gleichen oder ähnlichen Erscheinungsbild eindeutig identifiziert werden ohne irgendwelche Ausgaben als Hex-Code analysieren zu müssen.
      PHP-Klassen auf github

      Kommentar


      • #4
        Kein Code der Welt kann fehlende Informationen erraten.. kaputt ist kaputt.

        Best practice um einen UTF-8 String in seine Zeichen zu zerlegen ist IMHO das:
        PHP-Code:
        preg_split('//u'$str0PREG_SPLIT_NO_EMPTY); 
        Über 90% aller Gewaltverbrechen passieren innerhalb von 24 Stunden nach dem Konsum von Brot.

        Kommentar


        • #5
          Zitat von lstegelitz Beitrag anzeigen
          Kein Code der Welt kann fehlende Informationen erraten.. kaputt ist kaputt.
          Richtig. Ist aber nicht das Thema hier.

          Zitat von lstegelitz Beitrag anzeigen
          Best practice um einen UTF-8 String in seine Zeichen zu zerlegen ist IMHO das:

          preg_split('//u', $str, 0, PREG_SPLIT_NO_EMPTY);
          Mag sein. Funktioniert aber "nur" mit validen UTF-8 Strings.

          Was möchte ich erreichen?
          1. valide UTF-8 Strings mittels PHP 7 Unicode-Notation so darstellen, das diese Zeichen einfach und eindeutig identifiziert und reproduziert werden können.

          Beispiel: Um Herauszufinden um welches Emoji es sich handelt bedarf schon etwas Übung. Die Routine indentifiziert das eindeutig als U+1f601.
          Der konvertierte String besteht nur noch aus ASCII-Zeichen, kann überall dargestellt werden und in double quotes " gepackt zu 100% als PHP String reproduziert werden.

          Edit: Beim Versuch hier einUTF8-mb4 Emoji anstelle von "\xf0\x9f\x98\x81" reinzustellen hat der tolle Forumeditor hier mir den 2.Teil des Beitrages gelöscht!

          PHP-Code:
          $string "Hallo \xf0\x9f\x98\x81";
          echo 
          debug::strToUnicode($string);
          //Hallo\u{20}\u{1f601} 
          2. invalide UTF-8 Strings sollen möglichst in valide und kaputte Teile gesplittet werden sowie gut lesbar und reproduzierbar dargestellt werden.
          Ich denke das ist mir weitgehend gelungen.

          PHP-Code:
          $invalid substr("€",0,1);
          $string "äö".$invalid."äö";
          echo 
          debug::strToUnicode($string);
          //\u{e4}\u{f6}\xe2\u{e4}\u{f6} 
          Die UTF-8 Zeichen ä und ö werden als \u{e4} und \u{f6} ausgegeben, das Byte-Fragment (vom €) als \xe2 und die folgenden Zeichen wieder als Unicode.
          Diese Zeichenkette reproduziert $string zu 100%.

          Probe
          PHP-Code:
          $strFromOutput "\u{e4}\u{f6}\xe2\u{e4}\u{f6}";
          var_dump($strFromOutput === $string);
          //bool(true) 
          PHP-Klassen auf github

          Kommentar

          Lädt...
          X