Stranger Than Usual

Hey dogs, look at me: You are beautiful, you are special and you matter. As for cats: you don't need my validation and you know it.

John Oliver

Eintausend 🎉

Das hier ist der tausendste Post auf diesem Blog! Naja, je nachdem, wie man zählt. Ich habe alle Posts auf meinem alten Blog, die ich mal importiert habe mitgezählt. Bis auf den einen, den ich nicht importiert habe, weil er nur ein Verweis auf dieses Blog hier ist.

Jedenfalls sind es nur noch 24 Posts bis zu einer schönen, runden Zahl! Ich feiere das Jubiläum trotzdem jetzt schon, denn ich bin nur durch Zufall darauf gestoßen, dass wir kurz vor der 1000 sind und bis zur 1024 hätte ich das schon wieder vergessen. Jubiläen vergessen hat zwar eine gewisse Tradition hier, aber man muss ja nicht jede Tradition durchziehen.

Es gibt auch ein paar kleine Änderungen: innerhalb der letzten Monate habe ich sukzessive für alle alten Blogposts eine Zusammenfassung hinzugefügt. Alles Handarbeit, keine LLMs oder andere Generatoren. War eine Menge Arbeit, aber nebenbei konnte ich einige alte Posts noch taggen oder kaputte Links austauschen und ein paar Typos und schlechte Typografie korrigieren.

Diese Zusammenfassungen tauchten bisher nur in meta-Tags auf und waren im Blog nicht sichtbar. Das habe ich nun geändert. Auf den Tag-Seiten und auf den Kategorie-Seiten wird jetzt unter dem Titel auch jeweils die Zusammenfassung (und das Veröffentlichungsdatum) angezeigt. Ich bin noch nicht sicher, ob ich das Markup so lasse, aber ich probiere es erst einmal so. Vielleicht muss ich dort auch noch paging einbauen, die Seiten werden mittlerweile ziemlich groß.

Die Version meines Generators habe ich jetzt auf 1.0.0 hochgezogen. Das macht eigentlich keinen Unterschied, aber eine Blogsoftware, die seit fast fünf Jahren ihre Arbeit macht und nur noch kleinere Verbesserungen bekommt, hat eine stabile Versionsnummer verdient.

Rollenspielszenen: Die Stimmung ist zu positiv

Rollenspielszene. Wir sind in einer besetzten Stadt. Wir suchen eine befreundete (und berüchtigte) Ætherwissenschaftlerin, Dr. Sejöstroem. Wir haben ein verschwörerisches Treffen mit Olive, einer Journalistin, und Jaro, einem Angestellten der Besatzerarmee.

Die beiden erzählen uns, dass Dr. Sjöstroem in einem Sperrgebiet im Sperrgebiet verschollen ist, bei dem Versuch, die dort stattfindenden Entwicklungen gefährlicher Ætherwaffen (inklusive Tests an lebenden Menschen) offenzulegen. Wir entscheiden uns, in das Sperrgebiet einzudringen und sie zu retten. Allerdings brauchen wir dafür eine Ablenkung. Wir entscheiden uns, der örtlichen Journalisten dazu einzuspannen, für einen Aufruhr zu sorgen (im Austausch für eine Druckerpresse, denn deren eigene Pressen wurden von den Besatzern zerstört).

Wie spricht man so ein Thema an? Ganz einfach:

Ich dachte, die Stimmung in der Stadt ist ein bisschen zu positiv. Ich finde, das sollte geändert werden.

Verspätungs-Doomscrolling

Habe ich gesagt, dass mir die Ankündigung der 30 Minuten Verspätung nicht hilft, weil ich trotzdem zum gleichen Zeitpunkt losfahren muss, um rechtzeitig am Umsteigebahnhof zu sein?

Zwischendurch hatte sich die Verspätung auf 41 Minuten (warum geben die überhaupt minutengenaue Angaben, wenn die das sowieso nicht genau wissen?) erhöht. Also dachte ich mir, ich nehme einen Zug später.

Etwa vierzehn Minuten, bevor mein ursprünglicher Zug losgefahren ist, kriege ich die Meldung, dass der Anschlusszug nur noch 22 Minuten Verspätung hat. Ich muss also doch den ursprünglich geplanten Zug erwischen! Die Zeit ist knapp, also muss ich schnell aufbrechen um dann bei 27 °C im Schatten mit schwerem Gepäck durch die Stadt zu rennen.

Am Umsteigebahnhof angekommen, kriegte ich direkt eine Mail, dass der Zug doch wieder 33 Minuten Verspätung hat. Kurz darauf eine Durchsage am Bahnsteig, dass die Verspätung 35 Minuten beträgt.

In Zukunft werde ich diese Mails ignorieren. Sie helfen mir nicht, weil ich mich nicht auf sie verlassen kann. Kennt ihr das Konzept des „Doomscrollings“? Grob gesagt ist das, wenn man sich in social media durch die schlechten Nachrichten liest. Dank Endlosscrolling kommen immer mehr und mehr schlechte Nachrichten herein, was den Gemütszustand immer weiter verschlechtert. Man kann nicht aufhören, aber etwas tun kann man auch nicht.

Die Verspätungsmails der DB-Reisebegleitung sind das Doomscrolling des Bahnverkehrs.

Rollenspielszenen: Kinder manipulieren

Ich spiele regelmäßig Pen & Paper-Rollenspiele, und habe mir mal vorgenommen, öfter was dazu zu schreiben. Angefangen damit, (wenn möglich) nach jeder Runde eine markante Szene aufzuschreiben und zu posten. Hier kommt die erste Szene, aus einer World Tree-Runde, die ich leite:

Die Gruppe ist in einem Orren-Dorf und muss einen See überqueren. Das einzige Boot des Dorfes wurde aber von einer Bastlerin versenkt:

Orren: „Fizzel wollte ein Schnellboot daraus machen. Das hat auch so halb funktioniert“

Rassimel (PC): „Wie kann das halb funktioniert haben?“

Orren: „Die Hälfte, die funktioniert hat, liegt da drüben am Ufer. Die andere Hälfte ist am Steg versunken.

Die Zeit drängte, und zum Reparieren wird jede Hand gebraucht. Wie kriegt man einen Haufen fauler Orren dazu, mit anzupacken?

Die Lösung war ein Orrenkind. Schon am Dorfeingang musste die Gruppe das Kind abwimmeln, und verriet ihm, dass sie in „geheimer Mission“ unterwegs seien. Später entdeckten sie, wie das Kind ihnen hinterherspioniert. Sie nutzten die Gelegenheit und unterhielten sich lautstark über ihren „geheimen“ Auftrag:

„ES WÄRE WIRKLICH SCHLIMM, WENN WIR NICHT RECHTZEITIG ÜBER DEN SEE KÄMEN!“

„GENAU! WIR BRAUCHEN DRINGEND DIESEN HEILZAUBER, UM DIE MHEROBUMP ZU RETTEN!“

„WENN WIR DOCH NUR GENUG LEUTE HÄTTEN, DIE UNS BEIM REPARIEREN HELFEN!“

Kurze Zeit später rannte das Kind überall herum, scheuchte Leute auf und bettelte seine Mutter an, mit anzupacken. Voller Erfolg!

Moderne Bahnreisen

Wie schön doch die Digitalisierung ist! Dank E-Mail-Benachrichtigung weiß man schon eineinhalb Stunden vorher, dass der Zug Verspätung hat. Bringt aber auch nichts. Ich muss nämlich trotzdem zur selben Zeit los, darf dann aber am Umsteigebahnhof bei hochsommerlichen Mittagstemperaturen eine halbe Stunde extra warten.

Jetzt muss ich C# lernen

In der Erwartung, dass ich bald beruflich wieder Go benutzen werde, habe ich Anfang der Woche herumexperimentiert, wie gut Go einen davor schützt, falsches Encoding in string-Variablen zu haben.

Zwei Tage später hatte ich dann ein Gespräch mit zukünftigen Kollegen und musste erfahren: Es wird nicht Go. Der Kunde besteht auf C# und lässt sich durch nichts davon abbringen. 75% des Teams kann zwar kein C#, aber das sind alles intelligente Leute, die werden das lernen.

Ich habe noch nie C# benutzt. Aber ich habe meine Vorurteile. Meinen Vorurteilen zufolge hat C# (zusammen mit Mono, bzw. .net) die gleichen Probleme, mit denen ich mich bei Java jahrelang herumplagen musste und froh war, davon wegzukommen. Insbesondere, dass es zwanghaft Klassen verwendet, auch für statische Funktionen, und dass es die Art von Entwickler anzieht, die es für eine gute Idee halten, auf Teufel komm raus alle Design-Pattern, die die objektorientierte Programmierung zu bieten hat, in den Code zu quetschen, egal, ob es nun sinnvoll ist oder nicht.

Ich erwarte Dependency Injection, Inversion of Control, zu viele Interfaces, Dinge wie die AbstractSingletonProxyFactoryBean in Java Spring, ORMs, das ganze Programm. Alles Dinge, die sich jemand ausgedacht hat um Sachen einfacher und flexibler zu machen, die am Ende aber alles komplexer machen, Flexibilität schaffen, die man nicht braucht und es dafür an anderer Stelle unmöglich machen, eigentlich triviale Dinge überhaupt zu machen.

Ich war in meiner Anfangszeit als Programmierer ein großer Fan von objektorientierter Programmierung, habe aber mit der Zeit ihre Schwächen gelernt und picke mir jetzt nur noch die Teile davon heraus, die ich für gut befinde. Das geht in Go und rust sehr gut, in Java eher nicht. Wie es bei C# aussieht bleibt abzusehen.

Jedenfalls kann ich meine Vorurteile demnächst prüfen. Vielleicht werden sie ja widerlegt. Ich erwarte das aber nicht wirklich.

Zwei positive Seiten hat es. Zum Einen lerne ich mal wieder eine neue Programmiersprache. Nur schade, dass ich nie gezwungen werde, Sprachen wie Lisp oder Haskell zu lernen (und alleine bringe ich nicht genug Motivation auf, diese Sprachen zu lernen, weil ich mit rust so bequem geworden bin).

Zum Anderen kann ich die Experimente zum String-encoding, die ich neulich mit Go gemacht habe, in C# wiederholen. Ich erwarte nicht wirklich viel, denn erste Experimente haben schon gezeigt, dass es sehr einfach ist, einen string in c# mitten im (UTF-16-codierten) codepoint durchzuschneiden:

const string snek = "🐍";

string snekstats = string.Format("length: {0}", snek.Length);
Console.WriteLine(snekstats); // ergibt "2"

char foo = snek[0]; // char ist 16 bit
Console.WriteLine(string.Format("{0}", foo)); // ungültiges encoding

Neue PNG-Spezifikation

Vor ein paar Tagen kam die Meldung, dass es eine aktualisiert PNG-Spezifikation gibt. Darin enthalten: verschiedene Standardisierungen, oft von ohnehin schon so benutzten Features, wie z.B. Exif-Metadaten oder dem APNG-Format.

Letzteres habe ich hier auch schon verwendet, in dem Blogpost über eine Barber's Pole als Farbschichtschema für Fordite. Ich wollte eine Animation haben, aber eine GIF-Datei wollte ich nicht benutzen, also habe ich mir angeschaut, was es sonst so gibt. Es gibt wohl das MNG-Format, das von niemandem unterstützt wird und die Konkurrenz in form von APNG, die bis vor ein paar Tagen nicht standardisiert war.

Ich wollte dann direkt mal nachschauen, ob meine APNG-Dateien eigentlich standardkonform sind. Bei der Suche nach einem Tool bin ich dann aber abgelenkt worden und auf apngopt gestoßen. Wer dieses Blog verfolgt, weiß, dass ich nahezu zwanghaft alle Bilder optimal komprimieren möchte. Momentan benutze ich für die meisten Bilder in diesem Blog das WebP-Format (je nach Anwendung in verlustfreier oder verlustbehafteter Version). Verlustfrei komprimiert WebPs sind in den meisten relevanten Fällen kleiner als vergleichbare PNGs. PNGs sind aber immer noch ein Ding, nicht zuletzt wegen der Animationen.

Also musste ich das tool natürlich ausprobieren. Und siehe da: es kriegt meine APNG auch ein bisschen kleiner. Die eine Datei schrumpfte von 13546 byte zu 12206 byte, also um mehr als ein kilobyte oder um fast zehn Prozent. Die andere Datei schrumpfte von 46743 auf 43840 byte also um knapp drei kilobyte oder gut 6%.

Was mich an diesen ganzen Bildoptimierungsprogrammen irritiert ist, wie unterschiedlich das Benutzerinterface ist. Beispiele

  • optipng, jpegoptim und svgo ersetzen per default die Originaldatei mit der kleineren Version (nur, wenn die wirklich echt kleiner ist), lassen sich aber dazu überreden, eine andere Datei als Zieldatei zu verwenden.
  • zopflipng braucht explizit eine Ausgabedatei. Ist das dieselbe wie die Originaldatei, wird nachgefragt. Die Nachfrage kann man mit einem Flag unterdrücken
  • apngopt legt neben der Originaldatei eine zweite Datei an, die so heißt wie das Original, nur das (vor der Dateiendung) noch In _opt angefügt wurde. Es lässt sich nicht dazu bringen, es anders zu tun.

Immerhin kann ich bei apngopt zwischen verschiedenen Kompressionsbackends wählen, unter Anderem auch zopfli.

Eine kleine Anekdote noch: PNG, geschaffen für das Internet, wurde in den Neunzigerjahren als Alternative für GIF entwickelt, weil GIF von Patenten belastet war (die 2006 ausgelaufen sind). Herausgekommen ist ein Format, das Bilder kleiner und in besserer Qualität als GIF ausliefert.

Nur Microsoft hatte es lange nicht hingekriegt, PNGs vollständig zu unterstützen. Im berüchtigten Internet Explorer 6 zum Beispiel wurden keine transparenten PNGs unterstützt. Anstatt das zu beheben empfahl Microsoft, eine proprietäre Erweiterung im HTML-Code zu verwenden. Es gab auch Javascript von Drittparteien, die das Problem gelöst haben. Und wie das nun einmal so ist, hat man einen solchen Workaround einmal eingebaut, bleibt er lange. In einem Projekt, in dem ich gearbeitet habe, war bis 2018 noch Code, der Javascript enthielt, um dem zu diesem Zeitpunkt schon zum Teufel gejagten Internet Explorer 6 auf die Sprünge zu helfen.

Keilschriftuhr

Eigentlich wollte ich ja gestern nichts über die Bahn schreiben, sondern über etwas Schönes. Ich habe mich ja vor Kurzem über das GRSSK-Phänomen und den Missbrauch von nicht-lateinischen Schriften ausgelassen.

Im Gegensatz dazu kann man Keilschrift aber auch korrekt einsetzen. Da hat zum Beispiel jemand eine Website gebaut, die die Uhrzeit in Keilschrift anzeigt. Bonuspunkte für den Pfadnamen sumertime.

Diese Uhr benutzt die echten Babylonischen Zahlendarstellungen. Die gehen von 1 bis 59 (und ein Leerzeichen für die 0) und sind damit perfekt geeignet, um Minuten und Sekunden darzustellen. Tatsächlich lässt sich die 60 Minuten/Stunde, 60 Sekunden/Minute schlussendlich auf das Sexagesimalsystem der Sumerer und Babylonier zurückführen, auch wenn die eigentlich Aufteilung laut Wikipedia zum ersten Mal vor etwa tausend Jahren verwendet wurde.

Auch die Doppelpunkte, um Stunden, Minuten und Sekunden zu trennen sind wahrscheinlich ein Anachronismus. Trotzdem eine schöne Uhr.

Bahncard-Tortur

Ich sollte mal eine Statistik aufstellen, welche Tags in diesem Blog am häufigsten zusammen vorkommen. Ich würde wetten, es sind „rant“ und „deutsche bahn“.

Was ist es diesmal? Mal wieder der Onlineauftritt, in Kombination mit ihrem halbgaren Digitalzwang. Ich wollte nur eine Bahncard kaufen, weil ich demnächst häufiger mal nach Hamburg fahren muss. Mein Ärger fing schon beim Login an.

Passwortrichtlinien

Die DB hat ihre Passwortrichtlinien geändert. An sich kein Problem. Bisher wurden definitiv zu kurze Passworte erlaubt. Aber anstatt nach dem Stand der Wissenschaft zu gehen, sehe ich das hier:

For your own security, please choose a password that you have never used before. At least 12 characters, contains uppercase letters, contains lowercase letters, contains numbers, contains special characters, not your e-mail address

7 Regeln, nur drei davon sind gut: Das Passwort muss neu sein, mindestens 12 Zeichen lang, nicht die E-Mail-Adresse.

Ich habe mich dazu schon öfters ausgelassen, deswegen spare ich mir das jetzt. Siehe Umzug nach Nijmegen, Sparkasse pushTAN und das Passwort und Furchtbare Passwortrichtlinien bei der Bundesagentur für Arbeit. Manchmal frage ich mich, wer im falschen Paralleluniversum lebt. Bin ich es, und bilde mir nur ein, dass so komplizierte Passwortregeln als kontraproduktiv erwiesen wurden? Oder die Erfinder solcher Vorgaben, die die letzten zehn Jahre oder mehr verschlafen haben?

Dann war ich endlich eingelogged. Ich habe mir die Bahncard herausgesucht, „Lastschrift“ als Zahlart ausgewählt und wollte die Bestellung abschließen. Ich muss nur noch einen kleinen Schritt durchführen, sagt die Bahn-Website.

Identifizierung durch Verimi

Ich muss nur noch meine Identität bestätigen. Wo kämen wir denn hin, wenn ich für jemand anderen eine Bahncard kaufen könnte? Zur Identitätsbestimmung gibt es mehrere Möglichkeiten.

Als Erstes die E-Perso-App, oder wie die heißt. Ich habe zwar gerade einen neuen Perso bekommen, habe das aber noch nicht eingerichtet, außerdem fehlt mir der Chipkartenleser. Also fällt das erst einmal weg.

Als Zweites kann ich Verimi (oder einem anderen Drittanbieter) meine Logindaten für mein Online-Banking geben, dann können die sicher sein, dass ich ich bin. Meine Logindaten. Für Online-Banking.

Kermit der Frosch am Telefon: JA, DIE HABEN ALLE LACK GESOFFEN. NEIN, ICH WEIß NICHT WIE VIEL

Das ganze hat bei mir Flashbacks ausgelöst. Damit musste ich Silvester 2023 auch schon kämpfen, als ich mir ein Deutschlandticket besorgen wollte.

Die dritte Option war, meinen Perso zu Fotografieren und denen gleich auch noch ein Selfie zu schicken. Auch nicht meine Lieblingsvariante, insbesondere nach dieser Geschichte neulich, bei der eine Hotelkette Ausweisdaten gesammelt und nicht gesichert hat. Aber ich brauche diese Bahncard. Nach einigem Herumprobieren (Verimi war pingelig, was die fotografische Qualität meines Ausweises anging) war ich dann identifiziert.

Aber wenigstens kann ich dann endlich mit Lastschrift zahlen, oder?

Die Zahlart

„For the better, right?“ meme. Vier Panels. Panel 1: Anakin: „Du musst dich ausweisen.“ Panel 2: Padme (fröhlich): „Dann kann ich per Lastschrift zahlen, richtig?“. Panel 3: Beat Panel, Anakin schweigt. Panel 4: Padme (zweifelnd): „Richtig?“

Hahaha. Nein.

Die Zahlung konnte nicht durchgeführt werden. Die Zahlung konnte mit dem gewählten Zahlungsmittel nicht durchgeführt werden. Bitte wählen Sie ein anderes Zahlungsmittel.

Ok, dann also Paypal. Ich lese immer wieder von Leuten die schreiben, man solle Paypal nicht benutzen, weil Peter Thiel ein Arschloch ist. Ist er, aber irgendwie muss ich ja meine Bahntickets bezahlen.

Immerhin habe ich jetzt das Bahnticket. Das wird natürlich automatisch verlängert, man kann es, ähnlich wie das Deutschlandticket, nur im Abo kaufen. Zwei Mal bin ich schon darauf hereingefallen, dass ich vergessen habe, die Bahncard zu kündigen, dann habe ich eine Rechnung bekommen, die in den ganzen Ihr-Zug-kommt-zu-spät-oh-nein-doch-nicht-oh-warte-doch-ach-ne-er-fällt-komplett-aus-Spam-Emails untergegangen ist, und statt einer Mahnung haben sie mir dann direkt ein Inkassounternehmen auf den Hals gehetzt, dass eine saftige Strafgebühr verlangt hat. Vielleicht lassen sie mich deswegen nicht per Lastschrift zahlen. Ihr wisst, schon, dann würde das ja automatisch abgebucht und sie bekämen die Strafgebühr nicht.

Dass ich verdammte vier Wochen vor Ablauf kündigen muss, halte ich auch für eine Frechheit. Immerhin werde ich darauf hingewiesen. Und als weiteren Beweis, dass die Bahn unfähig ist, eine Website zu bauen, haben sie ihr HTML auch einmal zu viel escaped und zeigen statt einem Link den rohen HTML-Code an:

Eine Infobox, die die Kündigungsbedingungen der Bahncard erläutert. Darin ist HTML-Code, der wohl ein Link zur Seite mit der Widerrufsbelehrung werden sollte.

Gut, wann schicken die mir meine Bahncard zu?

Digitalzwang

Das war eine Fangfrage. Natürlich schicken die mir keine Bahncard mehr zu. Physische Bahncards haben ausgedient, das hatte ich schon vorher mitgekriegt. Digitalcourage hatte das immer mal wieder angesprochen.

Ich brauche also die DB-Navigator-App. Eine App, die wegen Datenschutz- und Sicherheitsproblemen immer mal wieder in den Schlagzeilen war. Ist aber auch irrelevant, ich kann die App nicht installieren, weil ich den Playstore nicht auf meinem Phone habe. Immerhin kann man mittlerweile ein Ersatz-PDF herunterladen, dass man dann ausdrucken oder auf einem Gerät vorzeigen kann. Aber wo finde ich das?

Es gibt eine Website mit einer laaangen Anleitung, wie man die DB-Navigator-App benutzt. Darunter ein Video, wie man die PDF-Bahncard herunterladen kann. Darunter noch eine „barrierefreie“ Option. Diese Option ist eine PDF-Datei mit vielen Bildern. 9 Seiten lang. 8 Seiten davon eine Anleitung, warum man doch lieber die App verwenden sollte. Auf Seite neun dann fünf kurze Punkte, wo man die Bahncard-PDF-Datei findet (sie ist gut versteckt). Fünf Punkte auf einer Strichpunktliste. This PDF could have been a website.

Naja, jetzt habe ich die Bahncard wenigstens. Und habe schon wieder zwei Stunden verbracht, diesen Blogpost zu schreiben. Ich hatte heute noch etwas anderes vor.

String encoding in Go

(Usually I write stuff in German, but this could be interesting to some people I know who do not speak German, so English it is.)

As frequent readers (do I have those?) know, text encoding is a small hobby / pet peeve of mine. A lot of things would be easier if we just used UTF-8 everywhere. But a consistent text encoding is only useful if it is used correctly and its validity is enforced. Most languages do a bad job at enforcing valid encoding. For example:

  • C does not have strings, C has null-terminated char-arrays. Encoding optional
  • C++ has strings that do not enforce encoding, newer versions have some questionable unicode strings
  • Java encodes strings in UTF-16 and does not check for unpaired surrogates
  • Javascript works similar to Java in that regard

Python and rust enforce valid unicode. Except that python allows to encode surrogate code points (but at least it does fail when it tries to encode that string as UTF-8). However, I'm not here to talk about any of these languages. I'm here to talk about Go, since I'm probably going to use it professionally again next week.

Strings in Go are just gloriefied immutable byte arrays. They have no inherent guarantee to contain a valid string of any encoding. Since Go is such a young language, this is a shame. The worst part: Developers don't expect it. They take a modern language, read strings from external sources, modify them, write them to external sinks and are oblivious about the fact that they are just one step away from splitting a code point in the middle.

I talked about the strengths and weaknesses of Go in another blogpost. Here, I'm going to ask the question: How bad is the situation? What can go wrong, where is easy to make mistakes and where is it hard to do so? Let's start with the basics.

Thing I already knew

Source files in Go are strictly UTF-8. So in theory, string literals are also strictly UTF-8. Unless they are not because you can use escape sequences to do stuff like this:

var s = "foo\xffbar"

There, an invalid byte in the middle of the string. Go has the unicode/utf8 package in its standard library, so we can at least check if the string is valid:

utf8.ValidString(s) // evaluates to false for the string above

You can also just create a string from a byte slice without validation. I have seen many developers doing exactly this, without even thinking about any problems:

c := []byte{0x66, 0x6f, 0x6f, 0xff}
s := string(c)

It should come to no surprise that you can just split a string in the middle of a code point:

s := "🐍"
s1 := s[:2] // s1 now contains an incomplete code point

Just as with casting a byte array to a string, this is something I have seen in the wild several times. Barely anyone thinks about it.

This situation is bad. It is far too easy to get invalidly encoded strings. And Golang is used primarily for web servers. Those get untrusted input all the time. So maybe at least the interfaces that are commonly used are protected? I have never tried this before, so let's do it!

Unicode validation at the border of golang web services

I have written a small test web server for a few test cases. I want to test three interfaces (or two, depending on how you count):

  • URL-parameters for GET requests
  • HTML form data from a POST request
  • JSON data (in this case from POST request, but it does not really matter)

The first two can count more or less as one, because the interface in Golang is the same: A http.Request-object has a field Form (this field needs to be populated by a call to ParseForm(), but that detail does not really matter here). Form is basically a map from parameter name to parameter value(s). So this is what I did:

if err := req.ParseForm(); err != nil {
    response.WriteHeader(http.StatusBadRequest)
    fmt.Fprintln(response, "bad form data")
    return
}

query, ok := req.Form["q"]
// [I left out some validation code here]
if utf8.ValidString(query[0]) {
    log.Printf("query string '%s' is valid utf-8", query[0])
} else {
    log.Println("query string is not valid utf-8")
}
response.WriteHeader(http.StatusOK)
fmt.Fprintln(response, "looks good")

So basically: I try to parse the form data and return an error if any problem occurs. Then I get the string from the form map and log whether it is valid UTF-8. Then I return with a success status code. Ideally, ParseForm() should fail for invalid UTF-8. Let's see what happens:

$ curl "http://localhost:8090/form?q=foo%ff"
looks good

Needless to say, there is a query string is not valid utf-8 message in the server log. And that was the most simple case. POST-body form data has basically the same result, no matter whether I use %ff to escape the byte or use a raw 0xff-byte in the body.

So that's form data. What about JSON? After all, we all write bloated single-page javascript apps with a server that is basically a JSON-API. Does Go's JSON parsing do it better? As a matter of fact, it does! Assume I have this struct

type Input struct {
	Query string `json:"q"`
}

and then handle it like this:

body, err := io.ReadAll(req.Body)
// [omitted error handling]

var input Input
err = json.Unmarshal(body, &input)
// [omitted error handling]

if utf8.ValidString(input.Query) {
    if strings.ContainsRune(input.Query, '�') {
        log.Printf("query string '%s' is valid utf-8 but contains a replacement character", input.Query)
    } else {
        log.Printf("query string '%s' is valid utf-8", input.Query)
    }
} else {
    log.Println("query string is not valid utf-8")
}
response.WriteHeader(http.StatusOK)
fmt.Fprintln(response, "looks good")

No matter what I threw at it, it was always handles gracefully. An escaped surrogate code point? Replaced with the replacement character. An unescaped surrogate code point? Replaced with three replacement characters. An unescaped 0xff-byte? Replaced with the replacement character. An overlong encoding of "}? Replaced with eight replacement characters.

So while I'm a bit unhappy that it does this silently and I have not found an option to let the parsing fail instead of replacing unexpected bytes, this is at least valid behaviour and leads to valid encoding.

Conclusions

Always check strings from untrusted sources for valid encoding. I cannot stress this enough: Most standard library functions will ignore encoding! You have to check manually. json.Unmarshal may have your back, but unless you know for certain that this is the case, always check (in addition to other security measures you should take with untrusted input).

Also: do not split strings in arbitrary places, do not cast byte arrays to strings without checking for valid encoding and be very careful with escape sequences in string literals.