In der Java-Welt gibt es zwei grundlegende Methoden, um XML Dokumente zu lesen. Auf der einen Seite DOM, welche das komplette Dokument in den Speicher der Applikation einliesst und dem Anwender dann die Möglichkeit anbietet über die DOM-Knoten zu traversieren. Auf der anderen Seite hat sich SAX als Standard etabliert. Bei diesem Standard wird der Anwender beim parsen eines XML Dokumentes durch Callbacks (Observer-Muster) über den Inhalt des Dokumentes informiert. Nach einem solchen Callback vergisst der Parser den Zustand, der Anwender muss sich diesen merken. Alle weiteren Techniken (z.B. StAX) haben, auch weil sie noch keinen Einzug in die Java Standard APIs erhalten haben, noch keine grössere Verbreitung erfahren. Zumindest für StAX könnte sich das mit dem Release von Java 6, aka Mustang, ändern, da es in den Standard aufgenommen werden soll.
Sowohl SAX als auch DOM haben ihre Vor- und Nachteile. Ich selbst bevorzuge SAX, da es mit den Ressourcen des Systems sparsamer umgeht. Mit dem traversieren in DOM-Bäumen konnte ich mich auch nie richtig anfreunden. Dabei sind beide APIs in ihrem Grundgebrauch einfach zu verwenden, wenn man einige Dinge beachtet, die zu Fallen werden können. Im Folgenden werden einige solcher Fall für SAX beschrieben:
Pitfall 1: characters
Eine immer wieder gerne übersehene Pitfall beim einfach einlesen der Texte zwischen den Elementen ist die nicht zugesicherte Eigenschaft der Methode characters des ContentHandlers alle Zeichen zwischen zwei Tags zu liefern. Vielmehr kann der Parser immer nur Teile liefern und der Anwender muss selbst Sorge dafür tragen den Inhalt aufzusammeln. Bei meinen Entwicklungen hat sich das Idiom bewährt, dass ich in der Callbackmethode startElement, wenn das öffnende Tag für die Zeichen, die ich erwarte erscheint, einen StringBuffer anlege. Danach werden dann bei jedem characters-Callback einfach nur die übergebenen Parameter an den StringBuffer weiter gereicht.
Pitfall 2: characters und Denial of Service Attacks
In der characters können auch noch Sicherungsmassnahmen gegen Denial of Service Attacken eingebaut werden. Dies ist gerade dann wichtig, wenn Dokumente aus nicht vertrauenswürdigen Quellen verarbeitet werden müssen (z.B. RSS-Feeds, offener Upload). Dabei wird definiert wieviele Zeichen maximal in einem solchen Element vorhanden sein dürfen. Wird die Grenze überschritten, dann sollte das parsen des Dokumentes mit einem Fehler abgebrochen werden.
Pitfall 3: DTD Attacken
Wenn innerhalb eines Dokumentes eine erreichbare DTD im DOCTYPE definiert ist, dann kann diese DTD dazu führen, dass das parsen der DTD das System ins Nirvana reisst. Ein Beispiel hierfür ist die »Billion laughs attack«, die den Entity-Mechanismus ausnutzt.
<!DOCTYPE root [
<!ENTITY ha "Ha !">
<!ENTITY ha2 "&ha; &ha;">
<!ENTITY ha3 "&ha2; &ha2;">
<!ENTITY ha4 "&ha3; &ha3;">
<!ENTITY ha5 "&ha4; &ha4;">
…
<!ENTITY ha128 "&ha127; &ha127;">
]>
<root>&ha128;</root>
Seit JAXP 1.3 kann hierfür ein Sicherheitsfeature gesetzt werden. Es ist allerdings nicht explizit definiert, wie diese Sicherheitsfeatures aussehen.
DTDs verarbeiten sollte man also auch nur dann, wenn der DTD Quelle vertraut werden kann. Quellen, die über eine URL referenziert sind und deren Quelle man nicht selbst kontrollieren kann, sollten immer als nicht vetrauenswürdig angesehen werden.
Diese Pitfall gilt auch für die DOM Verarbeitung.
Pitfall 4: Entity Resolver
Das SAX Interface EntityResolver definiert in seiner Methode resolveEntity, dass jeder System Identifier, wenn er eine URL ist, erst aufgelöst werden muss, bevor der Callback ausgeführt werden darf. In der Grundintention ist das auch korrekt. Allerdings erzeugt dies ein paar grundlegende Probleme. Zum einen möchte man nicht unbedingt, dass die URL dereferenziert wird. Beispielsweise wenn derjenige, der eine DTD anbietet nicht mitbekommen soll, dass jemand man seinem Datenformat arbeitet (bei einer HTTP URL wird ein ganz normaler Request ausgelöst, der in den Log-Dateien des ausliefernden Servers registriert wird).
Ein weiterer Grund ist die Performanz des Systems. Eine Netzwerkrequest verzögert immer die Ausführung, was auch wiederum zu einer Denial of Service Attacke ausgenutzt werden kann, wenn der ausliefernde Server die Auslieferung verzögert und es eventuell erst zu einem time-out kommen muss.
In einer Enterpriseanwendung beim Kunden kann es durchaus möglich sein, dass eine Applikationen keine HTTP-Requests nach ausserhalb machen darf. Hier kann man zwar Ausnahmen definieren, aber dies verkompliziert die Konfiguration der Anwendung. im Java-Umfeld sollte man deswegen einen XML-Parser verwenden, bei dem man über Features dessen Verhalten steuern kann. Das Auflösungsverhalten des EntityResolvers des Xerces J 2 Parsers kann man beispielsweise mit dem Feature http://xml.org/sax/features/resolve-dtd-uris abschalten. Wird der Wert des Features bei der Parsererzeugung über die JAXP Schnittstelle auf false gesetzt, quatscht der Xerces nicht mehr in der Gegend herum
Allerdings muss der Enwtickler dann selbst für die Auflösung der Entities sorgen.
Fazit
XML als Datenformat scheint auf den ersten Blick einfach zu verarbeiten zu sein. Allerdings sollte man sich mit der Spezifikation schon auskennen, wenn es darum geht eigene XML Applikationen zu entwickeln. Dies gilt sowohl für die XML Spezifikation selbst, als auch für die APIs, mit denen XML verarbeitet wird. Insbesondere wenn der Entwickler Dokumente aus nicht vetrauenswürdigen Quellen verarbeiten muss, ist es unumgänglich sich mit einigen der hier beschriebenen Fallen auseinander zu setzen.
