Shownotes & Links
- Die Folge über Input Validation / Output Encoding
- LANGSEC: Language-theoretic Security
- Regex für die Validierung von E-Mail Adressen
- Pandoc
- Lücke bei der Zertifikatsverifikation in OpenSSL
- Blogpost: Parse, don’t validate
- Buch: Secure by design
Feedback
Falls ihr Fragen oder Anregungen habt, schreibt uns gerne eine E-Mail an [email protected]
.
Transkript
Lisa: Hallo und herzlich willkommen beim INNOQ Security Podcast. Heute sind Christoph und Lars da. Hallo ihr beiden.
Christoph: Hallo, Lisa.
Lars: Hallo.
Lisa: Warum sind wir heute zu dritt? Und worum soll es heute gehen? Wir haben einmal eine Folge aufgenommen, die hieß Input Validation, Output Encoding. Das haben damals Christoph und ich gemacht und Lars hat sich gedacht, dass er dazu noch was sagen möchte oder etwas ergänzen möchte. Darum geht es heute in dieser Folge. Wir nennen es mal ‘Parsen statt Validieren’. Bevor wir aber darauf eingehen. Christoph, magst du einmal kurz beschreiben, worüber wir in dieser Input Validation und Output Encoding Folge gesprochen haben?
Christoph: Gerne. In der Folge ging es darum, dass man bei einer Webanwendung typischerweise auch Daten von außen annimmt, in Formularen oder auch, wenn man Dateien hochlädt oder ähnliches. Und denen kann man generell erst mal nicht trauen, weil man kann nicht davon ausgehen, dass alle User gutwillig sind, sondern manche vielleicht auch ganz böse Absichten haben. Und damit muss man irgendwie umgehen. Es gibt da verschiedene Möglichkeiten. Und das, was wir empfohlen haben, ist Input Validierung, wenn die Daten reinkommen. Was reinkommt, wird erst mal validiert. Passt das so, wie wir uns das vorstellen? Da gibt es verschiedene Arten. Größe validieren, Syntax validieren, Semantik validieren und so weiter. Und das reicht dann aber nicht, weil wenn wir diese Daten weitergeben, kann das sein, dass andere Systeme die anders verarbeiten müssen. Deshalb müssen wir denen bei dem Output an das nächste System vielleicht ein passendes Encoding mitgeben. Praktisches Beispiel ist die SQL Injection, die jemand versucht. Er versucht in Formularfelder, die dann vielleicht irgendwo mal in der Datenbank landen, einen SQL Befehl einzuschleusen und hat dann zum Beispiel da solche einfachen Anführungszeichen drin und gibt dann ein SQL Kommando ein. Und damit das jetzt nicht so in dem SQL Interpreter, der dieses Anführungszeichen interpretiert. Jetzt ist irgendwie der String geschlossen und jetzt führe ich das weitere Kommando dahinter aus. Dann muss man das richtig enkodieren. Zum Beispiel Backslash davor oder bei SQL Datenbanken ist es meistens, dass man das Anführungszeichen wiederholt, also zwei Anführungszeichen hintereinander. Und darum muss man sich auch kümmern, weil dieses Encoding ist ganz unterschiedlich, in welchem Kontext man das ausgibt. Ein SQL Interpreter, ein anderes Encoding als HTML, zum Beispiel, wenn ich es wieder auf der Webseite rausnehme oder im PDF brauche ich auch ein anderes Encoding. Und deshalb sollte man das immer so machen, dass man den Import erst validiert und dann, je nachdem wo ich es ausgebe, dann auch ein Encoding macht. Weil ich kann nicht alles ablehnen. So ein Anführungszeichen könnte ich mal gebrauchen, vielleicht in bestimmten Fällen und kann dann ich nicht einfach sagen: Ich lehne das erstmal alles ab. Und das ist sozusagen zusammengefasst die best practice, die man machen kann. Es gibt noch andere Möglichkeiten. Sanitization. Davon haben wir abgeraten. Das heißt einfach dieses Zeichen dann löschen. Aber da kann man dann vielleicht auch die Bedeutung verändern und das wollen wir nicht. Darum ging es und der Lars bringt uns jetzt, glaube ich, noch was Besseres mit. Ich bin gespannt.
Lars: Genau, diese Folge ist eigentlich ein Leserbrief von mir.
Christoph: Ein Audiokommentar vielleicht.
Lars: Ja, genau. Ich habe mir nämlich die Folge angehört und habe gedacht: Ja, das klingt alles sehr vernünftig. Da habe ich auch an sich gar nichts dagegen einzuwenden. Ich bin jetzt nicht hier, der sagt: Input Validation sollte man auf gar keinen Fall machen. Ein bisschen als Clickbait könnte ich jetzt sagen: Input Validation ist schlecht, Input Parsing ist gut. Und ich glaube, da werden wir heute mal darüber sprechen, was ich damit eigentlich meine.
Lisa: Erzähl mal, was du damit eigentlich meinst. Dankeschön an Christoph für die Zusammenfassung. Wir verlinken die alte Folge natürlich in den Shownotes. Aber Lars, warum bist du hier? Was meinst du mit Input Parsing? Was ist das?
Lars: Ja, ich glaube, so eine Sicherheitslücke, die allen wahrscheinlich noch im Hinterkopf ist, ist das Log4Shell. Das ist ein riesiges Thema gewesen. Da ging es darum, dass ich ein Log Framework habe, wo ich ein Pattern angeben kann, wie bestimmte Daten geloggt werden müssen. Und in diese Patterns konnte ich bestimmte Sachen einbetten. Ich glaube, das war mit geschweiften Klammern, das weiß ich nicht mehr ganz genau und innerhalb von diesen Einbettungen konnte ich dann das Framework dazu überreden, irgendwelche Netzwerk Calls oder solche Dinge zu tun. Und das Problem ist jetzt da gewesen, dass die Validierung offenkundig nicht gut genug war oder gar nicht validiert worden ist, was ich da bekomme. Und wenn man da mal versucht, eine root cause analysis zu machen, erst mal zu fragen: Warum ist das jetzt schiefgelaufen? Oder warum hat das jetzt nicht ausgereicht? Da stellt man fest, dass an ganz vielen Stellen in Programmen Input kommt und dieser Input wird irgendwie verarbeitet. Und ich habe sozusagen einen Interpreter gebaut, der den Input verarbeitet. Aber ich habe gar nicht so richtig verstanden, welche Struktur mein Input hat. Und dann komme ich in Sicherheitsprobleme rein, die durch einfache Validierungen nicht mehr so unbedingt zu lösen sind, weil ich beispielsweise irgendwelche Strings habe, die validiere ich vielleicht einmal an einer bestimmten Stelle, und dann sind sie aber immer noch ein String und ich kann an einer bestimmten Stelle im Code dann nicht mehr erkennen, ob ich das Ding jetzt schon validiert habe oder nicht. Und ich weiß auch nicht, was genau jetzt da drin ist und dann lasse ich vielleicht irgendeinen anderen Stück Code drauf laufen, was ein anderes Verständnis von diesem String hat. Und der Ansatz, den ich jetzt hier mal vorstellen möchte, dieses Parsen statt Validieren sagt aus, dass ich immer, wenn ich ein Stück Text oder ein Stück Input habe, der die Barriere überschreitet und von außen in mein Programm reinwandert, ich den wirklich auseinandernehmen muss, mir ganz genau überlegen muss: Was ist eigentlich die Syntax von dem Ding? Wie muss ich den verarbeiten und was kann eigentlich dabei passieren? Was kann da eigentlich drinstehen? Das ist mal ganz kurz zusammengefasst.
Lisa: Hast du schon irgendwelche Beispiele? Oder noch mal ein bisschen, um das zu konkretisieren. Wie kann ich mir das in der Praxis vorstellen? Das Parsen statt validieren.
Lars: Du hast in vielen Anwendungen, wo es schon mal vorgekommen ist, dass man irgendwelche Emailadressen akzeptieren muss von einem Web Formular, was auch der Christoph eingangs erwähnt hatte. Ich habe da eine Webanwendung, da stehen Formulare, wo ich die Mail Adresse eintippen muss und dann gibt es vielleicht irgendeine Validierung von dieser Emailadresse in JavaScript oder vielleicht in meiner Spring Boot Applikation oder was auch immer. Und dann landet die vielleicht irgendwo als String, dann wird die weitergeschickt an ein anderes System und dieses andere System hat dann vielleicht eine andere Validierung und hat vielleicht eine andere Auffassung davon, was eine korrekte Email Adresse ist. Und dann kann es bei solchen Fällen dann zu Problemen kommen. Oder der Klassiker, ich bin zu Zeiten der Forensoftware aufgewachsen und im Internet sozialisiert worden. Da gab es dann diesen schönen BBCode, wo man dann versucht hat, eine einfache Variante von HTML zu machen. Das wurde sehr oft dann mit Regex ‘validiert’ oder ‚sanitized‘ vielleicht auch teilweise. Und ganz viele Cross-Site scripting Attacken sind dann eben dahergekommen, weil das nicht korrekt gelaufen ist oder weil es zum Beispiel Verwirrung darüber gab, ob jetzt in der Datenbank der String jetzt im Original drinsteht oder schon transformiert drin steht. Ob da jetzt zum Beispiel schon escaped worden ist. Solche Verwirrungen können da entstehen. Und mal ganz kurz und knackig zusammengefasst: Eine Möglichkeit, sowas zu lösen ist, indem man sich ein Modell hinschreibt im Code, zum Beispiel eigene Klassen schreibt für ein Ding, was die Emailadresse enthält. Und dann würde man zum Beispiel ein für alle Mal im Konstruktor dieser Klasse Java schreiben, würde man dann eine bestimmte Validierungslogik oder irgendeine andere Parsinglogik laufen lassen. Vielleicht habe ich eine Bibliothek, die mir irgendwas checkt. Vielleicht habe ich eine Bibliothek, die Telefonnummern prüft und zum Beispiel schaut, ob der Country Code vorhanden ist und sonst irgendwas. Und dann zerlege ich zum Beispiel mein Input in einer Telefonnummer in Country Code und Vorwahl und Rest oder irgendwas. Und immer, wenn ich dann einen String entgegennehme und daraus eine Telefonnummer holen will, statt einmal einen Regex laufen zu lassen, würde ich dann mir ein Objekt von dieser Klasse konstruieren. Und dann habe ich immer, wenn ich ein Objekt von dieser Klasse habe, die Gewissheit, dass das Ding auch wirklich validiert ist und dass ich auch wirklich nur die definierten Zustände habe. Zugegebenermaßen sind jetzt Telefonnummern und Email Adressen ein sehr einfaches Beispiel. Damit kann man nicht viel falsch machen, denkt man vielleicht. Aber gerade zum Beispiel, wenn jetzt irgendjemand versucht in die Emailadresse ein Komma einzuschleusen und dann gibt man die vielleicht an einen Mailer weiter und dann sind es plötzlich zwei Email Adressen, nicht nur eine. Auch da gibt es schon durchaus Fallstricke, die dann da lauern können.
Lisa: Aber du sagtest gerade… Ich stimme dir zu für diese Richtung. Wenn ich als Nutzer meine Email Adresse irgendwo eingebe und du machst in deinem Programm eine Emailadresse als Objekt daraus, dann bist du dir sehr sicher in deiner Applikation, dass das wirklich eine Emailadresse ist, die du validiert hast. Aber in dem Moment, wo ich Dinge aus der Datenbank holen würde, da kann ich die Emailadresse dann nicht als die Emailadresse, da ist es erst mal wieder ein String, die ich dann wieder zurück formatierte. Gilt das trotzdem auch für diese Dinge, weil ich die auch dadurch laufen lasse, oder nicht? Warum ich diese Frage stelle: Wie ist es mit Bestandsdaten? Wir haben oft schon Bestandsdaten in irgendeinem System, die vielleicht nicht durch deine Email-Adressen-Validierung da hinten reingelaufen sind.
Lars: Ja, genau, das ist ein sehr guter Punkt. Ein paar Inspirationen für das, was ich für heute erzählen werde, kommen aus dem Forschungsbereich, der sich Language-theoretic security nennt. Und die haben da einen sehr konservativen Ansatz, in dem sie eigentlich alles, was aus dem Umsystem kommt, als potenziell problematisch betrachten. Beispielsweise, wenn man jetzt irgendwo eine gelayerte Anwendung hat, wo Daten von A nach B durchgereicht werden, dann sollte man sich nicht darauf verlassen, dass ein unteres System die Daten an das obere System bereits vollkommen validiert oder sonst irgendwas durchreicht. Das heißt, die würden sich auf den Standpunkt stellen: Ich bin meine eigene Bubble und in dieser Bubble muss ich schauen, was reinkommt. Und bei Datenbanken ist die Schwierigkeit, da habe ich nicht unbedingt ein Typsystem, was mir da helfen kann. Ganz viele von den Techniken, die man aus diesem Umfeld benutzt, um dieses Parsing zu ermöglichen, dass ich nicht einfach bloß Validierung, dann habe ich String rein, String raus, sondern String rein und dann ein strukturiertes Objekt raus. Dafür brauche ich irgendwie eine Unterstützung des Typsystems und das ist leider bei SQL-Datenbanken ein bisschen schwierig. Da gibt es zum Beispiel eine Strategie, die mir eigentlich ganz gut gefällt, wenn ich irgendein bestimmtes Legacy Format vielleicht entgegennehme oder vielleicht irgendein Binärformat von irgendwelchen Daten, vielleicht ein proprietäres Format. Und muss das dann sehr kompliziert parsen, dann kann ich mir auch überlegen, ob ich stattdessen dann, statt das Original in die Datenbank zu schreiben, irgendein Format benutze, was leichter zu parsen ist. Und da gibt es ganz viele Möglichkeiten. Da gibt es natürlich JSON. Da gibt es S-expressions, vielleicht sogar XML, wenn ich da Lust draufhabe. Formate, bei denen ich beim Parsen nicht viel falsch machen kann und wo ich dann gegebenenfalls weniger fehleranfälliger Parsen dafür machen kann. Ein schönes Beispiel für so fehleranfällige Formate ist das X509. Diese Zertifikate für TLS, die sind in ASN1 geschrieben. Das sind Systeme, das muss ich auch parsen. Und da gibt es ganz oft Probleme dabei, weil das Format sehr schwierig zu parsen ist. Und dann würde ich gegebenenfalls, wenn ich mit sowas hantiere, stattdessen JSON oder irgendwas in die Datenbank reinschreiben.
Christoph: Aber würdest du direkt JSON da reinschreiben? Solche Text Blocks oder nicht eher zu sagen: Ich versuche das Objekt, vielleicht, da ist ein Date drin, da sind zwei Ins drin und ein String, das zu Mappen. Wenn ich so ein ORM habe, wird das dann wahrscheinlich auch spalten zum Beispiel, die genau ein passendes Datenformat haben. JSON haben zwar manche auch nativ, manche Datenbanken, das ist verbreitet heutzutage, aber würde ich da nicht versuchen, ein besseres Format als einen Freitext zu finden?
Lars: Genau, das ist eigentlich der Goldstandard, glaube ich. Wenn man es tatsächlich schafft, die Daten, die von außen reinkommen, dass ich die so präzise beschreiben und modellieren kann, dass es irgendwie gut in meine Zielsprache oder mein Zielsystem reinpasst. Bei einem relationalen Datenbanksystem sind es Records mit verschiedenen Spalten, die überall gleich sind. Oder wenn ich nach Java einlese, muss ich es in einen Objektbaum reinbekommen. Je nachdem, was ich dann für Systeme habe. Die allermeisten Programmiersprachen, General Server Programmiersprachen, ob es jetzt Go ist, Java oder JavaScript oder was auch immer. Da habe ich ein großes Arsenal von Möglichkeiten, wie ich Dinge modellieren kann. Da muss ich mich schon sehr anstellen, um etwas nicht modelliert zu kriegen, was sich irgendwie schön anfühlt in meiner Sprache. Bei SQL-Datenbanken ist es etwas eingeschränkt, weil ich dann dieses relationale Format habe. Bei Dokumentdatenbanken wie MongoDB ist es noch mal was anderes, weil ich dann eher unstrukturierte Daten reinwerfe. Es kommt dann immer so ein bisschen auf das System an, aber ich will vielleicht mal versuchen, dass wir uns jetzt nicht so daran aufhängen an dieser Technologie, sondern dass wir uns vielleicht noch mal ein bisschen unterhalten über dieses Parsing an sich, dass das ein wichtiger Teil von korrekten Systemen ist, dass das Parsing gut definiert ist.
Christoph: Bevor wir dahin gehen, noch zwei Sachen. Einmal der Rücksprung: Email Adresse ist ganz einfach. Dem würde ich vehement widersprechen. Da gibt es im Internet, die können wir mal verlinken, die verschiedensten Ausprägungen, wie schlimm dieser Regex sein muss, um alle gültigen E Mail Adressen damit validieren zu können. Weil das ist wirklich ein Format, das erlaubt viele Sachen, die wir so gar nicht kennen, aber das nur als kleiner Einschub. Daran müssen wir uns auch nicht aufhängen, dass das jetzt vielleicht gar nicht das super passende Beispiel für einfach ist, sondern eigentlich ein Beispiel für schwer. Aber das andere noch mal zur Klärung für mich. Das heißt aber, dass wir dieses Parsing dann in jedem System eigentlich machen müssen, weil wenn wir in den Systemgrenzen, wenn ich das richtig verstanden habe, wenn nicht, musst du mich noch mal verbessern, dass wir zwischen den Systemen aber trotzdem irgendwie ein anderes Austauschformat haben, wir sprechen mit einem JavaScript System, das über ein Go-System geht und dann zur Datenbank, vom Input vorne kommt ein schweres Format rein, ASN.1 kommt rein, das repräsentieren wir irgendwo, aber an den Außengrenzen nehmen wir doch wieder irgendwas, um das zu übertragen. Dann aber vielleicht eher ein einfaches Format, JSON, XML, wobei einfach auch bei XML wieder in Frage steht, aber dass wir sagen: das nutzen wir, müssen aber trotzdem an jeder Stelle neu eine Art Parsing und Objekte haben, die das irgendwie mappen können. Das heißt, wir haben auch Mehraufwand, den wir uns da einhandeln.
Lars: Tendenziell schon, genau. Aber ist es natürlich, der Aufwand ist größer, wenn ich Legacy Formate habe, die vielleicht irgendwie mehrdeutig spezifiziert sind, wo es möglicherweise unterschiedliche Ideen davon gibt, was jetzt dann da korrekt ist oder nicht. Ganz klassisches Beispiel dafür ist Markdown und Markdown sieht oberflächlich erst mal sehr einfach aus, aber es gibt 25 verschiedene Flavours von Markdown und bei dem einen ist vielleicht die vierfache Einrückung mit einem Stern irgendwie eine Liste, die vierfach verschachtelt ist. Bei dem anderen ist es vielleicht ein Code Block, weil sie mit vier spaces eingerückt ist. Und es gibt natürlich diese Common Mark Spezifikation, was die meisten können, aber es gibt trotzdem noch 25 andere Interpretationen davon. Und dann würde ich an der einen Stelle, wo es in mein System reingeht, mir den Aufwand machen müssen und müsste das einmal richtig zerlegen und genau spezifizieren: Was ist jetzt eigentlich gemeint? Nehme ich jetzt Common Mark, nehme ich irgendeine Extension? Und dann habe ich vielleicht Daten im JavaScript System, was den einen Parser benutzt. Und ich könnte natürlich auf die Idee kommen und sagen: Okay, wenn es einmal validiert ist, ist gut und dann gebe ich es an mein Ruby System weiter und Ruby benutzt plötzlich Multi Markdown und dann kommt was ganz anderes raus. Das heißt, um es mir an der Stelle einfacher zu machen und diese Mehrdeutigkeit zu mindern, würde ich dann mir ein eigenes AST-Format ausdenken. Pandoc macht so was zum Beispiel. Wer es nicht kennt, Pandoc ist so ein Schweizer Taschenmesser für Konvertierungen zwischen verschiedenen Markup-Sprachen. Das kann dann zum Beispiel Word Dokumente nach HTML, nach PDF, nach was auch immer. Das kann alle möglichen Kombinationen konvertieren und die haben ein Konzept von Filtern. Ich habe einen Parser in einen Pandoc, dann habe ich einen AST dran, der aus dem Parser rausfällt und das ist auch eine relativ abstrakte Repräsentation von Dokumenten mit Absätzen und Sektionen, was auch immer. Und diese Filter sind jetzt wiederum einfach nur Binaries, wo ich Text reinwerfen kann und Text wieder rauskommt. Und diese Filter benutzen diesen Syntax Baum von Pandoc, um einfach zu verhindern, dass die Filter jetzt nochmal von vorne anfangen müssen, im Markdown zu parsen. Und dieses Format ist irgendwas S-expression artiges, um einfach sich einfacher zu stellen beim Parsen. Einfach ein Format zu benutzen, was einerseits schnell zu parsen ist, andererseits einfach zu parsen ist. Und dann kann ich damit mir einiges an Arbeit ersparen, aber grundsätzlich, Christoph, hast du schon recht. Je nachdem, wie viele Systemgrenzen ich dazwischen drin habe, wie gut ich den Code kontrolliere oder nicht, muss ich dann deutlich mehr Aufwand betreiben, als wenn ich einfach sage: Ja gut, ich verlasse mich jetzt darauf, dass mein API Gateway mal Regex draufgeklatscht hat und dann ist alles gut.
Christoph: Okay.
Lars: Vielleicht noch mal ein anderes Praxisbeispiel, an dem man sich den Unterschied ein bisschen deutlich machen kann, was Parsing wirklich vom Validieren abhebt? Ihr habt auch schon darüber gesprochen über das Thema SQL Injection, wie man das verhindern kann, in dem vergangenen Podcast. Es gibt da im Prinzip zwei Klassen von Lösungen gegen SQL Injections. Das eine ist SQL Escaping. Das heißt, ich bekomme irgendein Parameter, den ich in die SQL einsetze und dann escape ich die. Zum Beispiel die Anführungszeichen mit Backslash versehen und dann kann ich den in den String reinkopieren. Das wäre die eine Klasse von Ansätzen. Und die andere Klasse von Ansätzen sind Prepared Statements, wo ich das SQL Statement schon mal zur SQL Datenbank schicke, dann parse ich das schon mal mit ein paar Löchern drin. Man hat dann intern eine Repräsentation davon und dann kann ich einfach Sachen in die Löcher reinstopfen und habe die injection protection for free, weil das Datenbanksystem schon das als AST an die Hand gibt. Ich kann das dann nicht manipulieren, weil ich bin dann einfach nur der Aufrufer davon, aber grundsätzlich ist das die Implementierung davon. Und klar, String Kombination ist einfacher zu machen, wird vielleicht auch schneller, als dann noch mal einen zusätzlichen Round Trip zur Datenbank zu machen. Aber insbesondere wenn ich beispielsweise sehr oft diese Prepared Statement absetzen muss, bin ich dann wieder mit dem Ansatz schneller dran. Und ich würde jetzt mal so ganz flapsig sagen, dass Escaping mit String Koordination ist der Validierungsansatz. Das heißt, da muss ich einmal schauen, dass da ein valider Parameter rauskommt und muss schauen, dass ich nicht vergesse. Und der Prepared Statement Ansatz ist der Parsing Ansatz, wo ich vom System her das schon so vorgelegt bekomme, dass ich es eigentlich nicht falsch machen kann.
Christoph: Ja, das würden wir natürlich auch empfehlen bei SQL, das man Prepared Statements nimmt. Dabei gibt es ein Problem. Vielleicht noch mal zur Wiederholung. Ich glaube, das hatten wir damals auch gesagt. Man kann bei diesen SQL Prepared Statements nicht alles parametrisieren. So ein Operator in der Werkklausel kannst du nicht parametrisieren und einen Platzhalter reinmachen und dann ist man manchmal dazu gezwungen, auf eine Verkettung von Strings zu machen. Aber es ist natürlich nicht die Empfehlung, solange man Prepared Statement nehmen kann, dann sollte man das tun. Aber du hast gerade gesagt, was ich jetzt wieder im Kopf habe. So ein SQL Parser, der kommt schon mit. Wir hatten das vorhin so, das könnte aufwändiger werden. Aber du hattest eigentlich einen anderen Gedanken angesprochen, wie man da eher dran gehen müsste, dass es gar nicht so drauf geht auf konkrete Implementierungen, sondern dass man vielleicht in den Köpfen was ändern muss oder so. Vielleicht willst du das noch mal ansprechen, was du meinst?
Lars: Genauso, in dieser LangSec Community gibt es diesen Begriff des sogenannten Shotgun Parsings und das Shotgun Parsing ist nicht irgendein spezielles Programmierpattern, sondern das ist eine Philosophie, die in sehr vieler Software grassiert. Okay, das klingt jetzt sehr wertend, aber es ist eigentlich auch schlecht. Und wie das beschrieben wird, ist, dass dieses Shotgun Parsing eine Implementierungspraxis ist, wo man einerseits den Validierungscode oder den Parsing Code zusammen mischt mit tatsächlicher Verarbeitung von den Dingen. Beispielsweise bei Log4Shell könnte man sich jetzt auf den Standpunkt stellen: Okay, in dem Log Framework, wo ich eigentlich nur das Pattern für die Log Statements auseinandernehmen will und anschauen will, wird auch gleichzeitig noch nebenbei im Vorbeigehen noch eine Nebenwirkung ausgeführt, nämlich zum Beispiel einen ELDA Server anzufragen oder sonst irgendwas. Oder YAML unterstützt es auch. Ich weiß nicht genau, ob das überhaupt noch Bibliotheken by Default anschalten, aber war mal eine Zeitlang so, dass man, wenn man Ruby-YAML geladen hat, dass man dann irgendwelche Shell Scripte ausführen konnte währenddessen und das ist allein ein Vorgang, der passiert, während ich meine Daten einlese. Und Daten einlesen und Datenverarbeitung sollten eigentlich getrennt sein. Und genau das beschreibt diese Shotgun Parsing, dass ich mehr oder weniger in einem Abwasch alles mal zusammen mache. Und ein schöner historischer Vergleich dazu ist auch, wie die Compiler mal implementiert waren und die Compiler heute implementiert sind, die alten Compiler, wie zum Beispiel für Pascal, die haben auch Oneshot gemacht, das heißt, das ist praktisch vom Parsing bis zur Code Generierung alles in einem einzigen Ding abgelaufen. Während ich noch Token geparst habe, habe ich schon die vorherigen Token schon imitiert als Binärcode. Und heutzutage sind aber Compiler ganz anders. Sie arbeiten in mehreren Schichten. Zuerst muss der Parser komplett darüber, dann läuft der Typechecker komplett darüber, dann läuft der Optimizer komplett darüber. Und erst dann, wenn das alles fertig ist, werden dann vielleicht die (23:41) erzeugt. So kann man sich das ein bisschen verdeutlichen, dass es der falsche Weg ist, zu versuchen, möglichst viele Dinge in die Parsing- oder Validierungslogik mit reinzupacken, statt erstmal sich einen Syntaxbaum zu erzeugen, kurz durchzuatmen und dann den Syntaxbaum zu durchlaufen, ob da überhaupt alles so ist, wie ich mir das vorgestellt habe.
Christoph: Das hat aber natürlich auch Gründe, warum man das so macht, dass natürlich, wenn du alte Programmiersprachen ansprichst wie Pascal, waren das natürlich auch Ressource Constraints und das haben wir zum Teil heute immer noch so als Pattern, wenn wir zum Beispiel an Streaming HTML denken oder so oder auch bei XML, so ein Sax Parser. Und du willst vielleicht nicht Dokumente, die x Gigabyte haben, erst mal schön in den AST parsen, um dann zu merken: Mir explodiert der Speicher, sondern du willst die schon verarbeiten, während sie reinkommen und dann, was du verarbeitet hast, wieder wegwerfen. Das ist deutlich Ressourcen sparender. Bei dem Modell ist aber genauso, was du sagst, nicht bedacht oder was wir am Anfang mal gesagt haben, dass wir nicht davon ausgehen können. Und du hast das so schon gesagt, jeder ist in seiner Bubble das, was von außerhalb in die Bubble reinkommt, auch freundlich gesinnt ist. Das ist immer diese Annahme in diesem Modell und davon können wir heutzutage bei Webanwendungen gar nicht mehr davon ausgehen, sondern ich weiß nicht, was da die Empfehlung wäre. Ich würde sagen, man muss auch schauen, wie viel Daten kommen rein? Und wenn sie zu groß werden, dann muss man sie vielleicht auch ablehnen. Das ist wahrscheinlich jetzt ein, wo wir Gigabyteweise XML verarbeiten, wahrscheinlich auch eher die Ausnahme, sondern sehr spezielle Systeme dann. Und noch mal zur Ergänzung, wer das noch macht. Bei Python ist mit YAML, noch mal ein kleiner Einschub, da muss man explizit die Save Varianten von den Parsing Methoden wählen. Wenn man das normale nimmt, dann wird diese Ausführung der Shell Befehle einfach ausgeführt. Dann muss man sich sogar noch entscheiden, was ich als Design Entscheidung eigentlich falsch finde. Man hätte es abklemmen sollen, aber da sind wir wieder beim Thema Rückwärtskompatibilität und ähnliches. Aber das nur am Rande.
Lars: Genau. Ich glaube das ist auch so von der Kultur her vielleicht etwas, wo man mal ansetzen muss und sagen muss: Statt jetzt zu versuchen, irgendwie mit bestimmten Mitigations die schlechten Fälle rauszufiltern, dass man vielleicht zum Beispiel Foren-Software, die versucht dann vielleicht irgendwie so ein Script Take rauszufiltern oder sonst irgendwas, was auch so ein bisschen bei Sanitization mitschwingt, dass man eher den Mindset Shift hat eher in Richtung: Ich muss gucken, dass ich die erlaubten Fälle aufzähle, statt die nicht erlaubten Fälle versuche auszuschließen, weil dann kann mir auch wirklich nichts durch die Lappen gehen. Und genau das erziele ich mit diesem Parsing Ansatz, weil ich zu oft leider auch im Validation Code sehe, dass da eine Negativliste steht von Dingen, die nicht erlaubt sind. Dann hat man vielleicht ein paar Sachen einfach vergessen. Und wenn ich aber dazu gezwungen bin, meinen Input String in eine bestimmte Datenstruktur zu überführen, die vielleicht näher der Domäne entspricht, die vielleicht dem Relationalmodell entspricht, dann werde ich dazu gezwungen, den Ansatz zu fahren, die erlaubten Zustände aufzulisten und nicht irgendwie Sachen im Nachhinein quasi auszuschließen. Das hat den Nachteil, dass ich dann ein bisschen weniger flexibel bin und dann zum Beispiel, wenn ich große Datenmengen habe bei einem Stream, muss ich mir vielleicht was anderes überlegen. Dann muss ich mir das noch mal überlegen, ob meine Syntax jetzt die richtige ist, ob die sich irgendwie zur Streamingverarbeitung eignet. Bei HTML ist das so ein Punkt. Der Browser fängt schon an zu rendern, wenn er das erste HTML reinkriegt, aber der Browser wird auch die Script Text ausführen. Ich meine Software, wo du vielleicht ein Script Text nicht ausführen willst, aber da habe ich ganz andere Ansätze dann hinterher.
Christoph: Ein Browser ist auch auf vielen anderen Ebenen einfach so auf Sicherheit getrimmt mit Sandboxing und der Verarbeitung bei JavaScript Fehlern oder so. So schreibt sonst eigentlich keiner seine Software mit so einem Security Fokus. Und das war auch ein langer Lernprozess für die Browser Hersteller, damit sie das verstanden haben, wie sensibel das ist. Weil Browser sind nur dazu da, um fremde Daten einfach zu verarbeiten aus irgendwelchen Quellen, denen man eigentlich nicht vertrauen kann. Klar, das ist aber genau die Ausnahme von dem, was wir tagtäglich machen. Das machen wir eben nicht. Wir sind keine Browserprogrammierer.
Lars: Genau, und meistens haben wir auch nicht Gigabyteweise XML zum hinterherschicken.
Christoph: Das will ich hoffen. Das stimmt schon. Wenn du sagst, wir verpacken das jetzt in die Objekte oder so. Wir parsen das Objekt und arbeiten erst dann weiter, dann kann man natürlich auch noch weiterführende Security Mechanismen nehmen, indem man zum Beispiel dann sagt, diesen Objekten sind dann auch nur Operationen erlaubt, die weiterhin einen validen Status haben. Das heißt, ich nutze vielleicht nicht mehr Operatoren, die ich sonst in meiner Programmiersprache habe. Im Plus mache zwei Ins addieren oder Minus subtrahieren, die arithmetischen Operatoren, die nutze ich gar nicht mehr, sondern ich sage dann auch, wenn mein Objekt nur so ein In kapselt. Ich mache eine Addition aber als Methode, damit ich zum Beispiel den Wertebereich nicht überschreiten kann, dass ich solche Constraints einfach weiterfahren kann, um zu sagen, ich bleibe damit immer im validen Zustand, weil die Validierung, das hattest du auch kurz angesprochen, die ist nur für diesen einen Moment gültig und dann gebe ich das weiter und die anderen Systeme oder auch in meinem System andere Objekte haben dann trotzdem die Möglichkeit in den invaliden Status zu kommen, weil die das einfach weiterverarbeiten müssen und keine weitere Validierung ausführen und einfach sagen können: Ja, wir erlauben nur noch Operationen, die auch so zu einem validen Status führen. Da hilft ein ganz gutes Typsystem, wenn es dann soweit vorhanden ist.
Lars: Ja, das ist der Klassiker, dass ich irgendwo beim Input festgestellt habe, dass der Int nicht out of range ist und dann multipliziere ich ihn irgendwo mit drei und dann wird doch wieder out of range. Ich glaube, das haben alle hier schon mal gesehen, solchen Code, wie man das falsch macht.
Christoph: Es liegt auch so ein bisschen daran, dass es einfacher ist. Wenn ich alles über Methoden mache und alles kapsel, dann habe ich irgendwie 1000 Typen definiert für alles mögliche, die einfach nur einen Int in einer bestimmten Range definieren. Wenn wir es jetzt einmal übertreiben und für die einfachen Grundoperation muss ich jeweils eine Methode schreiben, dann ist es dann auch manchmal sehr unschön. Ich bevorzuge dann, was ich sonst nicht mache, weil es die Lesefähigkeit oder das Verständnis nicht oft erhöht, aber in dem Fall schon auch Programmiersprachen, die so einen Teil, ein Operator Overloading, erlauben. Dass ich sagen kann: Plus für die beiden Typen heißt aber was anderes. Es ist so ein Grat, auf dem man wandert. Das muss man sehen, ob sich das lohnt oder auch nicht.
Lars: Ja, genau, ich würde jetzt sagen und man nimmt gleich noch Programmiersprachen, wo noch ein gutes Typsystem mit dabei ist. Damit ich auch wirklich nichts falsch machen kann.
Christoph: Was sind deine Empfehlungen für gute Typsysteme, für Programmiersprachen mit guten Typ Systemen?
Lars: Ich bin eigentlich als Scala Fan bekannt, aber so streng nehme ich das damit gar nicht mehr. In Rust gibt es einige coole Ansätze, wo man sich mit dem Typsystem einige Dinge verhindern kann und natürlich auch, dass da Rust eine speichersichere Sprache ist, eignet sich also sehr gut als Replacement, wenn man C Code hat, der da oftmals Schwierigkeiten mit diesen Low Level Parsing Geschichten hat. SQL ist natürlich auch eine schöne Programmiersprache, ist allerdings, glaube ich, im Tagesgeschäft nicht so einfach einzusetzen. Aber auch so ein Mittelweg wie zum Beispiel in Kotlin gibt es da einfache Möglichkeiten, sich eigene Datentypen zu definieren oder irgendwie Lambda oder was auch immer hinzuschreiben, um da zumindest mal ein bisschen Druck wegzunehmen, dass man sich jetzt nicht zehn Mal wiederholen muss. Python ist tatsächlich auch nicht die schlechteste Wahl, weil es jetzt da auch einen Typechecker gibt dafür und man zumindest in dem Code, den man selbst kontrolliert, sich da auch eine bestimmte Menge an Disziplin verschaffen kann, um mit den Daten korrekt zu arbeiten. In JavaScript zum Beispiel ist es wiederum sehr schwierig, weil es da eigentlich gang und gäbe ist, sich irgendwelche Objekte von Bibliothek zu Bibliothek hin und her zu reichen. Einfach zu gucken: Gibt es da eine Property Mail auf diesem Objekt? Dann gucke ich die einfach an und dann ist das vielleicht ein String, der ist vielleicht validiert oder nicht. Das ist eigentlich ein Security Nightmare, wenn man Bibliotheken hat, von denen man nicht genau weiß, wie sie funktionieren. Aber man kann damit auch die entsprechende Disziplin schaffen. Was will ich jetzt damit sagen? Man kann sich nicht mit der Sprache so richtig rausreden. Man kann eigentlich mit jeder Sprache diszipliniert umgehen. In manchen Sprachen ist es nur schwieriger als in anderen.
Lisa: Ich fand es erstaunlich, dass du jetzt gar nicht Java genannt hast, wo das, glaube ich, die Sprache ist, auf die man am meisten trifft, wenn man irgendwelche Projekte in dem Umfeld macht. In Java wird es aber wohl auch irgendwie möglich sein, diszipliniert zu parsen, oder?
Lars: Ja, Parsing auf jeden Fall schon. Da kommt dann wieder das Problem rein, was Christoph gemeint hat, dass es da keinen Operator Overloading gibt. Und dann habe ich zum Beispiel einen Typ für Money, wo ich meine Geldbeträge reinschreibe, auf denen ich dann zum Beispiel nur Addition mit der gleichen Währung erlaube. Das wäre eine super einfache Einschränkung, die ich da machen wollen würde und dann kann ich den Plus Operator nicht benutzen, weil geht nicht. In Python geht es, in Scala geht es, in JavaScript geht es glaube ich auch nicht, Operator overloading. Muss man dann gucken, wie sehr man auf Schmerzen steht, wenn man immer X+Y ausschreiben muss.
Christoph: Ich will das Operator Overloading auch nicht zu sehr empfehlen. Das hat seine eigenen Probleme. Wenn man die Erwartungshaltung, was ein Operator macht, auch bricht, weil der einfach was anderes tut, weil man da was hingeschrieben hat, das sollte man dann mit Vorsicht einsetzen, aber gerade mit dem Beispiel, man hat irgendwie einen Money Typ, da wäre es schon relativ einfach. Ich addiere trotzdem Beträge, aber mit gewissen Einschränkungen, dass ich nicht Dollar und Euro einfach addieren kann. Das geht nicht, weil dann kann ich wieder sagen, die Operation schlägt fehl, ich öffne Exception oder ich könnte natürlich auch eine Umrechnung machen, weil ich die Typen kenne und weil zu dem Typ gespeichert ist: Das sind jetzt vier x Dollar und das andere sind y €. Dann könnte man so was natürlich einbauen, dass man sagt, das wird umgerechnet, hat natürlich auch wieder seine Probleme mit welchem Umrechnungskurs, wie, wo, wann, zeitlich und so müsste vielleicht dann festgehalten werden. Aber, das ist schon ganz gut.
Lars: Genau. Aber ich wollte vielleicht noch kurz den Bogen schlagen, dass wir uns jetzt nicht so sehr an den technischen Details aufhängen. Wir driften immer auf die technischen Details ab. Ich bin auch eigentlich ein Techniker im Herzen, aber das Schlagwort, was dahinter steht, ist, was auch in der funktionalen Programmierung sehr stark befolgt ist, Make Illegal states unrepresentable. Das heißt, ich muss mir mein Datenmodell irgendwie so stricken, dass es möglichst wenig falsch benutzt werden kann. Ich weiß nicht, ob das Sinn gemacht hat, die Beschreibung. Aber ich will eigentlich Bibliotheken oder Domainmodelle bauen, die man nicht falsch bedienen kann, weil wenn ich versuche irgendein Objekt falsch zu konstruieren, wird es mir schon frühzeitig um die Ohren fliegen, statt erst dann um die Ohren zu fliegen, wenn ich es weitergebe oder sonst irgendwas. Und in Sprachen wie Java oder Scala oder sonstwas kann ich das in bestimmten Fällen machen, indem ich Subklassen aufzähle, die nur die bestimmten Möglichkeiten, ich habe zum Beispiel irgendein Objekt, was es in fünf verschiedenen Ausprägungen gibt und statt jetzt die Ausprägung als String irgendwo zu speichern, mache ich als Enum und dann ist es faktisch unmöglich ein sechstes Enum Element zu benutzen, weil das gibt es einfach nicht, da würde der Compile meckern. Und immer wenn ich von String rein kriege, muss ich den dann parsen und gucken: Welches von diesen fünf Fällen ist es? Das ist so eine Technik, die ist super einfach zu benutzen: statt Strings einfach ein Enum. Wenn ich also diesen Ansatz verfolge, Make illegal states unrepresentable in meinem Domänen Modell oder in meiner Bibliothek oder wo auch immer, dann kann ich die auch jemand anderen geben, der oder die dann einfach sehr viel schwieriger irgendwas falsch machen kann. Und ich glaube, das ist auf jeden Fall schon mal ein guter Punkt, dass ich jetzt nicht immer sagen muss: Hey, lies jetzt hier diese zehn Kilometer Doku durch, wie die Syntax ausschaut, sondern hier hast du die Bibliothek und wenn du die falsch bedienst, dann fliegt es dir gleich um die Ohren, statt erst, wenn ich die Datenbank vollmache.
Christoph: Das finde ich sehr gut. Was mich so ein bisschen in dem gesamten Teil jetzt verwirrt, ist eigentlich, dass Parsing eigentlich der geringere Teil ist, sondern Parsing heißt in dem Fall sozusagen: Wir parsen das in Typen oder Objekte hinein, die das besser repräsentieren als Typen wie String oder Int oder so. Und das eigentliche Parsing machen wir dann gar nicht. Das hat mich am Anfang, als du es vorgeschlagen hast, ein bisschen abgeschreckt. Müssen wir jetzt überall einen Parser schreiben für alles Mögliche, aber das tun wir eigentlich gar nicht. Wenn ich das jetzt so richtig verstanden habe, JSON Parser gibt es, einen XML Parser gibt es, es gibt auch ASN.1 Parser, auch wenn das ein blödes Format ist, aber trotzdem, vielleicht will ich so ein Zertifikat haben. Und die Quintessenz, die jetzt für mich rauskommt, ist, wenn ich so ein Zertifikat parse, da bekomme ich einen Datentyp, das X509 TLS Zertifikat und dann habe ich die verschiedenen Attribute da drin und kann darauf Operationen ausführen. Und wenn es gut läuft, dann ist das, was du gerade gesagt hast, dann kann ich das nicht in ein State versetzen, der illegal wäre, der nicht da ist, sondern das kann ich damit gar nicht repräsentieren, weil ich habe nämlich jetzt erst gesagt, Parsing schlägst du so vor als Alternative zum Validieren. Und jetzt gerade, wo das aufgenommen ist vor zwei oder drei Tagen, ist die erste kritische Open SSL Lücke seit fünf Jahren rausgekommen. Wir haben sie jetzt auch wieder zurückgestuft auf High, nicht mehr auf Critical, aber wo es genau darum geht, dass das Parsen mal wieder schiefgelaufen ist, dass durch Parsen hier ein Buffer Overflow kommt. Da wird in einer Zertifikatskette, da kann man irgendwie so Main constraints machen und da kann eine Emailadresse drinstehen und die kann in Punycode da drinstehen, also wie repräsentiere ich Unicode in ASCII? Und da gibt es dieses Unicode Encoding. Und da gibt es ein Puffer Overflow da. Da dachte ich so: Das kann eigentlich nicht der Vorschlag sein, den Lars meint, dass man sagt, wir müssen jetzt alles parsen, weil Parsen ist eigentlich extrem komplex. Die Aufgabe, einen Parser zu schreiben ist jetzt gar nicht so einfach, als wenn ich irgendwie Strings hin und her kopiere, aber darum geht es eigentlich gar nicht, wenn ich das richtig verstanden habe, sondern Parsing heißt, wir verwandeln eine Repräsentation in einen Typen. Und das Wichtige ist, wir haben da später einen Typen und nicht das Parsing, weil ich kann den Standard JSON Parser nehmen, den Standard XML Parser und den YAML Parser und so.
Lars: Ich würde das nicht ganz unterschreiben, was du gesagt hast. Ja, zum großen Teil müssen wir zum Beispiel eCommerce Code oder irgendwelchen anderen Business Code schreiben, uns nicht mit so was wie Punycode oder irgendwas befassen. Wir haben dann irgendwie eher so fachliche Anforderungen, zum Beispiel: Was ist jetzt eine Adresse, was ist eine Telefonnummer, was ist vielleicht ein Markdown oder irgendwie so was? Und in vielen Fällen können wir einfach damit leben, dass wir in unserer Programmiersprache der Wahl, einen existierenden Browser haben. Schaut man sich Open SSL an, ist der Parser dummerweise in C geschrieben, eine Programmiersprache, die einfach nicht speichersicher ist und deswegen habe ich dann da relativ wenig gewonnen. Wenn ich mir dann durch das Parsing dann wiederum extra Probleme einhandele, weil ich jetzt zum Beispiel neue Buffer allozieren muss, was ich beim Validieren nicht unbedingt tun müsste. Da könnte ich vielleicht einfach darüber laufen lassen über den Character Array und dann schauen, ob das alles in Ordnung ist. Da habe ich einen neuen Buffer anlegen müssen, wo ich dann Sachen reinkopiere und der war vielleicht zu kurz. Da kann man sich schon auf den Standpunkt stellen, das hat es nie verkompliziert. Da würde ich auch zustimmen und dann einfach sagen: Gut, dann muss man das in Rust machen, wo dann der Parser einfach keinen Buffer overflow bekommen kann. Das ist no Free Lunch, aber irgendwo muss ich mich dann ein bisschen anstrengen. Weil du gesagt hast, Parser schreiben ist schwer. Das stimmt zwar auch zum Teil. Wenn ich euch jetzt zum Beispiel sagen würde, jetzt schreibt mir doch mal schnell einen JSON Parser selbst, weil wir jetzt zum Beispiel Jackson nicht vertrauen oder sonst irgendwas oder einen YAML Parser vielleicht, dann ist es auch wirklich schwierig. Es gibt aber mittlerweile, wenn man wirklich mal in dieses Problem reinläuft, auch Tools für sowas. Gerade sowas wie Parser Generatoren sind dann ein mögliches Tool dafür. In Java gibt es dieses Framework, das nennt sich Until(). Mit denen kann man schon mal eine ganze Reihe von Dingen abfrühstücken. Aber da gehen wir eigentlich jetzt wirklich zu sehr in den Compiler rein, um das zu machen. Aber ich glaube, was ich damit sagen will, ist, wenn man ein Stück Input verarbeiten muss, der irgendwie im ad hoc Format ist, wo man vielleicht nicht so genau weiß, wie der spezifiziert oder sonst irgendwas, dann muss ich mich sowieso drum kümmern. Dann kann ich dich auch einfach. Ich kann jetzt nicht einfach hinstellen: Okay, keine Ahnung, was Parsen ist, lasst mal einfach ein Regex schreiben und dann ist das schon in Ordnung. Die Arbeit geht davon nie weg. Und es ist dann leider einfach so, dass man den Aufwand betreiben muss. Und wenn man den Aufwand nicht betreibt, dann kann es sein, dass man hintenraus wieder Bugs bekommt, weil man hat das jetzt weitergegeben an irgendein anderes System, was dann Schindluder damit treibt.
Christoph:Ja, ich glaube, das wollte ich so in der Form auch gar nicht sagen, sondern klar, dass man sich darum kümmern muss. Das sind oft irgendwelche Adhoc-Formate, die irgendwelche fach- und kontextspezifische Sachen sind. Ich hatte nur vorher die Besorgnis, ich kümmere mich deutlich mehr, wie ich einen vernünftigen Parser schreib, und wenn ich jetzt ein Adhoc-Format hab und das repräsentiert Typen oder Objekte, die 5 Felder haben, dann ist das auch viel einfacher, als wenn ich jetzt einen Parser schreibe für JSON oder was weiß ich, PNG, JPG oder ähnlich komplexe Formate, wo das jetzt mal nicht so ganz einfach ist. Aber was ich jetzt mitgenommen hab, und das müssen wir auf jeden Fall trennen, das Parsing, das eigentliche Einlesen der Daten von der Verarbeitung auch. Das ist ein ziemlich wichtiger Punkt, das sind 2 verschiedene Dinge. Da hängen unterschiedliche Anforderungen dran und wenn man das vermischt, dann laufen wir ziemlich schnell in Probleme rein, die wir dann nicht haben wollen.
Lars: Ja genau. Ich glaub, das ist ein ganz wichtiger Punkt und weil du gerade PNG und JPEG gesagt hast, solche Formate, die jeder kennt, sind erstaunlich trickreich. Wenn man danach googelt, dann gibt es da ganz viele Vulnerabilities, die daher rühren, das jemand versucht, solche Formate zu entpacken oder zu konvertieren und dann hat diese person vielleicht übersehen, dass sich so eine Endlosschleife konstruieren kann. Da gibt es auch so einen schönen Begriff für. Das nennt man accidental turing complete, wo man aus Versehen irgendein Binärformat hat, wo man Instruktionen, wie ein Go to drin hat, dann führt das zu einem Denial of Service. Ja, auch so bekannte Formate können erstaunlich komplex sein.
Christoph: So, um jetzt nochmal drauf zu kommen. Das war ja dein Leserbrief, dein Audiokommentar direkt mit uns. Weil, eigentlich wollen wir Validierung ja trotzdem haben. Aber nicht so in der Form, wir haben jetzt validiert, string, und gehen dann weiter. Sondern, wenn wir das parsen, machen wir automatisch eine Validierung oder machen wir die nicht? Also, parsen wir die Daten, die reinkommen, in irgendein Objekt und validieren die dann und sagen, ok, das geht nicht so. oder springt uns das schon beim Parsen entgegen und wir sagen, das kann ich so nicht machen. Aber so eine Art von Validierung haben wir ja trotzdem, die haben wir ja da.
Lars: Genau, wenn wir jetzt nochmal die Compiler-Analogie bemühen würden, wäre das Parsen halt das Parsing und das Typechecking das Validieren. Also, es kann ja sein, in manchen Programmiersprachen ist es so, dass der Typchecker was macht, also Java vor 1.5 bis einschließlich 4 hat sowas, glaube ich, nie gemacht. Musste man alles noch irgendwo hinschreiben. Hat der Typechecker noch gesagt, ist korrekt oder ist halt nicht korrekt. Das ist jetzt natürlich sehr vereinfacht gesagt. Aber es kann sein, dass man mehrere Schritte braucht, und in einem Blogpost, den wir auch noch verlinken, der heißt auch Parsen und Validate, da hat sich die Autorin auf den Standpunkt gestellt, dass man sich nicht zu schade sein sollte, mehrere Parsing Passe zu machen. Also, dass man zum Beispiel erstmal das JSON erstmal in den JSON Syntax Tree mit JXON zum Beispiel übersetzt und dann diesen Syntax Tree nochmal in einen Domain Objekt übersetzt. JXON macht das zum Beispiel standardmäßig in Java so, dass, oder, die meisten nutzen JXON so, dass sie direkt den JXON Tree in JSON übersetzen lassen, und auch da kann man sich Fehler einhandeln. Da würde ich sagen, das sollte man in zwei Schritten machen, wenn das ein einigermaßen komplexes Format ist. Mehrschrittige Parsing-Prozesse sind nicht schlimm, wenn man natürlich die Ressourcen-Constraints nicht hat. Und man muss dann halt gucken, dass man die Validierungslogik dann so kompakt, die Validierungslogik, die keine Parsinglogik ist, so kompakt wie möglich gestaltet.
Christoph: Ja, und manche Sachen kann man ja auch nur im Nachhinein validieren, weil man die Semantik nicht. Also, mein Format kann ja zulassen, dass so bestimmte Bestimmungen sich ausschließen oder gleichzeitig gelten. Ich denke da immer an die Kirmes, wo man mindestens 12 Jahre oder 1,40m sein muss, um auf das Fahrgeschäft gehen zu können. Solche Semantiken kann man beim Parsing, glaube ich, nicht abdecken.
Lars: Ja, das wird schwierig.
Chrsitoph: Ok, also so als Schlusswort: eigentlich bleibt das noch richtig: Input-Validation. Output-Encoding. Aber wir können das noch ergänzen: Vor der Input-Validation, oder das Input verschieben wir jetzt, wir haben Input Parsing, dann haben wir eine Validation auf das, was rauskommt und dann machen wir ein Output-Encoding. Oder noch besser: Wir haben sowas wie beim SQL Prepared Statement, wie eine, ich weiß nicht, wie ich es beschreiben soll, gibt es einen generischen Begriff dafür, aber irgendwas, was kein Output-Encoding benötigt, aber so was wie eine parametrisierte API oder so was in der Art, wo ich das nicht brauche. Also, es gibt, sozusagen, an beiden Enden, vorne und hinten, Verbesserungen zu dem, was wir damals besprochen haben.
Lars: Genau. Das ist eine gute Zusammenfassung.
Christoph: Sehr gut. Ich sage vielen Dank. Ich habe heute einiges gelernt. Lisa, hast du noch ein nettes Schlusswort?
Lisa: Ich würde auch danke sagen. Ich musste sehr viel an ein Projekt denken, in dem wir viel DDD gemacht haben und Value Objects quasi so eingesetzt haben, wenn ich das jetzt richtig in Erinnerung habe. Auf jeden Fall guter Input von Lars. Danke auch an Christoph, dass du da mitdiskutiert hast und das so schön zusammengefasst hast gerade.
Lars: Danke, dass ihr mich für den Leserbrief eingeladen habt, dass ich meinen Senf dazugeben durfte.
Christoph: Und an alle Zuhörer:innen da draußen. Ihr könnt uns natürlich auch Feedback geben. An [email protected]. Wenn ihr Themenvorschläge habt zum Beispiel. Oder wenn ihr sagt, ich habe da noch eine Idee zu der Folge. Das kann man noch viel besser machen. Dann schreibt uns.
Lisa: Empfiehlt uns gerne oder lasst ein Like da. Wir freuen uns, dass ihr uns zuhört. Tschüß.
Christoph: Tschüß.
Lars: Tschüß.