dynamisches SVG speichern lassen
Deus Figendi
- programmiertechnik
2 molily
Guten Morgen,
ich habe eine SVG mithilfe von Javascript verändert und würde das Resultat gerne vom Benutzer speichern lassen.
Dabei war meine Idee das vorhandene DOM zu serialisieren und dann als neuen Stream zu speichern.
Die erste Frage die dabei auftaucht: Gibt es das Serialisieren (also quasi DOM2text) als fertige Methode (oder Funktion) oder muss ich mir das noch basteln?
So und wenn ich dann diesen String hätte, der das Dokument enthält, wie biete ich es dem Benutzer an, damit er es speichern kann. Meine Ansätze waren diese:
Ich öffne ein neues Fenster und schreib den Kram da rein:
var Fenster = window.open(null,"SVG_saveme");
Fenster.document.write(DOM_string);
(ich hab echt überhaupt keine Erfahrung mit .write()
, ich benutze sonst nur DOM-Methoden)
Wenn ich Popups erlaube funktioniert das auch, es wird ein SVG (als Testdaten dann statisch) in einem neuen Fenster erstellt. Aber dann wird's schwierig.
Wenn ich das mit FireFox (4) mache, dann erscheint ein neuer Tab, der aber endlos auf "laden" bleibt, also dieser kleine Kringel, der eben dies anzeigt dreht sich ewig.
Wenn ich in diesem Fenster einfach Strg+S drücke und das dann als SVG speichere speichert er das Ursprungs-Dokument, welches das PopUp erstellt hat... nicht mein Wunsch.
Möglich ist allerdings, dass ich das Kontextmenü des SVG öffne, dort auf "Auswahl-Quelltext anzeigen" klicke und eben jenen Quelltext speichere. Das funktioniert ist aber ein blödes Workaround für den Benutzer.
In Chrome (Iron 8) muss ich ebenfalls PopUps erlauben, dann wird auch dort das SVG in einem neuen Fenster geöffnet. Mit diesem PopUp kann ich gar nichts machen, ein Menü (also das Schraubenschlüssel-Icon) wird nicht angezeigt, im Kontextmenü ist nichts nützliches zu finden und wenn ich Strg+S drücke passiert gar nichts.
Mit Chrome habe ich noch ein Nebenproblem, welches ich unten anspreche (oder in einem zweiten Beitrag, mal gucken), Opera und Safari hab ich noch nicht getestet.
Zweiter Ansatz: Einmal Server und zurück:
<?php
header('Content-type: image/svg+xml');
echo ($_POST['svg_string']);
?>
Da wäre meine erste Frage: Sieht da jemand ein Sicherheitsproblem? Muss ich was maskieren oder demaskieren?
Und die andere: Wie kriege ich denn eigentlich so einen POST-Request, aus Javascript an den Server abgesetzt, so dass ich eben als Antwort ein neues Fenster mit der Antwort erhalte. Mit AJAX bekäme ich das wahrscheinlich irgendwie hin, aber dann sind wir wieder bei dem Problem von oben, ich habe einen JS-String und muss den irgendwie in ein speicherbares Dokument kriegen. Also will ich die Antwort direkt verarbeiten ohne JS.
Also kann ich irgendwie einen POST-Request erzeugen, dessen Antwort in einem neuen Fenster landet? Muss ich vielleicht den XHTML-Namensraum einbinden, dann ein Formular erstellen, dort meinen DOM-String reinkippen um dieses Formular dann abzuschicken? Erscheint mir sehr aufwendig, aber wenn es sein muss... was muss ich da beim Escaping beachten?
Und nun noch das "Nebenproblem" mit Chrome: Wenn ich mein SVG drucken will, dann druckt Chrome die Hintergrundbilder nicht mit. Das ist aber quasi der Kern des JS-Programms, dass man die Flächen neu texturiert eben um sie auszudrucken. Mit Suchmaschinen habe ich herausgefunden, dass Chrome entsprechende Drucker-Einstellungen (alá "Hintergrund-Grafik mit drucken") noch fehlen. Stattdessen druckt Chrome alle Flächen schwarz (default bei SVG), sehr Toner-sparsam -.-
Oh und noch was: Opera (11) führt mein Script zwar aus, zeigt aber keines meiner Steuer-Elemente (dynamisch erstelltes SVG). Es legt sie alle an (kann ich in DragonFly sehen) aber rendert sie nicht.
Und abschließend noch ne Demo des Scripts... ja es ist nicht sooo sauber programmiert, viele globale Variablen wenig bis kein OOP aber... naja ich muss ja nicht stolz drauf sein, vielleicht schreibe ich später eine OOP-Variante.
Demo
Gibt es das Serialisieren (also quasi DOM2text) als fertige Methode (oder Funktion)
XMLSerializer kennen verschiedene Browser.
Ich öffne ein neues Fenster und schreib den Kram da rein:
var Fenster = window.open(null,"SVG_saveme");
Fenster.document.write(DOM_string);
Im Prinzip ja, nur solltest du vorher noch <http://de.selfhtml.org/javascript/objekte/document.htm#open@title=open> und nachher <http://de.selfhtml.org/javascript/objekte/document.htm#close@title=close> aufrufen.
> In Chrome (Iron 8) muss ich ebenfalls PopUps erlauben
Du solltest Fenster immer nach Benutzereingaben öffnen, nicht automatisch. Dann hast du mit Popup-Blockern keine Probleme.
> Mit diesem PopUp kann ich gar nichts machen, ein Menü (also das Schraubenschlüssel-Icon) wird nicht angezeigt, im Kontextmenü ist nichts nützliches zu finden und wenn ich Strg+S drücke passiert gar nichts.
Wenn du window.open mit nur zwei Parametern öffnest, sollte Chrome ein einfachen Tab öffnen.
> Mit Chrome habe ich noch ein Nebenproblem, welches ich unten anspreche (oder in einem zweiten Beitrag, mal gucken), Opera und Safari hab ich noch nicht getestet.
>
> Zweiter Ansatz: Einmal Server und zurück:
Auch sehr gut und vielleicht sogar besser, weil document.open/write/close von HTML ausgeht. Man kann zwar theoretisch einen MIME-Type als open-Parameter angeben, aber das unterstützt meines Wissens kein Browser wie gewünscht.
> ~~~php
<?php
> header('Content-type: image/svg+xml');
> echo ($_POST['svg_string']);
> ?>
Da wäre meine erste Frage: Sieht da jemand ein Sicherheitsproblem? Muss ich was maskieren oder demaskieren?
Das ist möglicherweise reflected XSS. Du solltest prüfen, ob das wirklich korrektes SVG ist, sowie Scripte filter. Das wird u.U. schwierig. Oder du verlagerst das auf eine separate Domain, wo es keine Cookie-Authentifizierung oder sonst irgendwelche privaten Daten abzugreifen gibt.
Demaskieren musst du, wenn dein PHP-Interpreter da magic quotes anwendet.
Und die andere: Wie kriege ich denn eigentlich so einen POST-Request, aus Javascript an den Server abgesetzt, so dass ich eben als Antwort ein neues Fenster mit der Antwort erhalte.
Indem du ein POST-Formular baust und es via JavaScript (oder einen herkömmlichen Button) absendest. Ob das in SVG geht, weiß ich nicht.
Muss ich vielleicht den XHTML-Namensraum einbinden, dann ein Formular erstellen, dort meinen DOM-String reinkippen um dieses Formular dann abzuschicken?
Wenn das nötig ist, weil es in SVG nichts entsprechendes gibt: Ja.
Andererseits könntest du das SVG-Dokument auch einfach in ein (X)HTML-Dokument einbinden, notfalls als iframe.
Erscheint mir sehr aufwendig, aber wenn es sein muss... was muss ich da beim Escaping beachten?
Nichts, das erledigt der Browser.
Mathias
Vielen Dank für deine Antwort.
XMLSerializer kennen verschiedene Browser.
Nach kurzer Recherche habe ich den Eindruck: Alle relevanten Browser. Cool, dankeschön!
Im Prinzip ja, nur solltest du vorher noch http://de.selfhtml.org/javascript/objekte/document.htm#open@title=open und nachher http://de.selfhtml.org/javascript/objekte/document.htm#close@title=close aufrufen.
Das war das erste PopUp meines Lebens :D
Wenn du window.open mit nur zwei Parametern öffnest, sollte Chrome ein einfachen Tab öffnen.
Na macht er aber nicht... ist aber Wurst, man kann da ja irgendwas dran einstellen, muss ich mich mal reinfuchsen.
Server und zurück...
Auch sehr gut und vielleicht sogar besser, weil document.open/write/close von HTML ausgeht.
Indem du ein POST-Formular baust und es via JavaScript (oder einen herkömmlichen Button) absendest. Ob das in SVG geht, weiß ich nicht.
Während ich deine Antwort las kam mir die Idee ob ich nicht einfach das XHTML-Formular in einem neuen Fenster/Tab erstellen kann/sollte und dieses dann absende. Also quasi beide Varianten mischen, damit sollt ich imho alle Probleme erschlagen, das Zielfenster bekommt wie es deiner Antwort nach erwartet HTML, ein Zielfenster für die Antwort existiert bereits, ein POST-Request wird "ordnungsgemäß" erstellt.
Ich mag diese Idee...
Das ist möglicherweise reflected XSS. Du solltest prüfen, ob das wirklich korrektes SVG ist, sowie Scripte filter. Das wird u.U. schwierig.
Ja, validieren kann ich das nicht. Scripte rausfiltern könnte aber klappen. Ist ja im Grunde schon erledigt wenn ich die Zeichenfolge <script (caseinsensitive) tilge. Dann fällt unter Umständen zwar ungültiger Code raus und das JS (oder was auch immer) steht irgendwie schief im Dokument. Aber das Problem tritt ja nur bei Angriffen auf und nicht versehentlich. Und wenn ein Angriff sichtbar aber ineffektiv ist ist es auch okay.
Was könnte noch gefährlich sein (denke an Flash)? <object> <embed> <frame> <iframe> <frameset> und eben <script>. Noch irgendwas, was Browser in SVG-Dokumenten in gefährlicher Art und Weise "fehlinterprettieren" könnten? (Sicherheitslücken die z.B. Bildformate betreffen kann ich nicht bekämpfen)
Oder du verlagerst das auf eine separate Domain, wo es keine Cookie-Authentifizierung oder sonst irgendwelche privaten Daten abzugreifen gibt.
Fällt raus, geht nicht.
Erscheint mir sehr aufwendig, aber wenn es sein muss... was muss ich da beim Escaping beachten?
Nichts, das erledigt der Browser.
Wenn ich was tue? Oder ist das egal?
Fenster.document.write('<textarea name="svg_string">'+svg_string+'</textarea>');
//oder
Fenster.document.write('<input type="hidden" name="svg_string">'+svg_string+'</input>');
//oder
Fenster.document.getElementsByName("svg_string")[0].appendChild(document.newTextNode(svg_string));
//oder
Fenster.document.getElementsByName("svg_string")[0].firstChild.data= svg_string;
In welchen Fällen wird der Browser richtig escapen?
Wie gesagt: Vielen Dank für die Erkenntnisse bis hier her :)