Mein erstes Skript
Tom2 (der authentifizierte)
- perl
Hi Leute
Nach meinen ersten paar Perl-Basteleien habe ich nun ein Skript geschrieben, welches mir wirklich sehr gefällt! Es handelt sich dabei um einen CGI-LibXSLT-Adapter. Keine Frage, es ist sehr trivial, aber ich habe CPAN sinnvoll genutzt und auch die UTF-8-Codierung funktioniert. Nun habe ich aber noch ein paar Fragen an erfahrene Perl-Programmierer.
Das Skript nimmt die Parameter p (Pfad der XML-Datei) und s (Pfad des XSL-Stylesheets) entgegen und gibt das transformierte Resultat zurück. Falls p keine XML-Datei, sondern nur eine Textdatei ist, dann wird ein HTML-Dokument daraus gebaut. Das Problem ist nun, dass für p auch absolute Pfade, sowie relative Pfade mit einem '../' erlaubt sind. Das möchte ich verhindern. Wie macht man das Plattformübergreifend und ausreichend sicher? Möglichkeiten, welche mir einfallen sind:
Der zweite Knackpunkt steht unter dem Kommentar Load Data:
Ist $path eine Textdatei, jedoch keine XML-Datei, dann schlägt der Versuch die Datei zu laden fehl und $xml->toString() ist leer. Es wäre wohl besser, vor dem Laden zu prüfen, ob es sich um XML-Daten handelt.
Aus dem Bauch heraus würde ich die ersten sagen wir mal 100 Zeichen der angeblichen XML-Datei einlesen und dann folgenden Pseudocode anwenden:
Wenn $xml_fragment '<?xml' enthällt
Datei als XML laden
Andernfalls wenn $xml_fragment '<html' enthällt
Datei als HTML laden
Sonst
Textdatei in HTML umwandeln
Verhällt diese Lösung? Oder handle ich mir damit neue Probleme ein?
Natürlich bin ich auch um sonst jeden Tip froh, der meinen Perl-Code verbessert.
Gruss & Dank
Tom2
Hier ist mein Code:
#!/usr/bin/perl -w
use strict;
use warnings;
use CGI::Carp qw(fatalsToBrowser);
use utf8;
use encoding 'utf8';
use CGI;
use XML::LibXML;
use XML::LibXSLT;
my $cgi = new CGI;
$cgi->charset('utf8');
print $cgi->header(-type=>'text/html');
my $path = $cgi->param('p');
if (not (-T "$path")) {
$path = 'index.html';
}
my $style = $cgi->param('s');
if (not -T "$style") {
$style = 'xsl/div.xsl';
}
my $parser = XML::LibXML->new();
$parser->load_ext_dtd(0);
$parser->validation(0);
$parser->recover(1);
$parser->keep_blanks(1);
my $xslt = XML::LibXSLT->new();
my $xml = $parser->parse_file($path);
if ($xml->toString() eq '') {
$xml = text2html($path);
}
my $xsl = $xslt->parse_stylesheet_file($style);
my $results = $xsl->transform($xml);
print $xsl->output_as_chars($results);
sub text2html {
my $source = shift;
# Read the text file
my $file = '<html xmlns="http://www.w3.org/1999/xhtml"><body><pre>';
open(FILE, $source);
while (my $buffer = <FILE>) {
if (!utf8::is_utf8($buffer)) {
utf8::decode($buffer);
}
$file .= $buffer;
}
close(FILE);
$file .= '</pre></body></html>';
# Generate XHTML document
my $xml = XML::LibXML::Document->new('1.0', 'utf-8');
$xml->createInternalSubset('html',
"-//W3C//DTD XHTML 1.0 Strict//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd");
my $root = $xml->createElement('html');
$root->appendChild($parser->parse_balanced_chunk($file));
$xml->setDocumentElement($root);
return $xml;
}
Hey,
Das Skript nimmt die Parameter p (Pfad der XML-Datei) und s (Pfad des XSL-Stylesheets) entgegen
du lässt also den Benutzer auf deinem Dateisystem herumfuhrwerken? Ist ja irre toll. Blacklisting funktioniert nicht.
Wenn du sicher sein willst, ersetze den ganzen Kram durch eine Dispatchtabelle. Alles, was nicht unter deiner Fuchtel steht, wird abgewiesen. Die Lösung hat die Vorteile, dass der Dateityp schon meta mitgegeben wird und du dich nicht mit Untainting umherschlagen brauchst.
my %textfiles = (
gruenkohl => 'res/gruenkohl.txt',
# fülle mich
);
my %xmlfiles = (
zwiebel => 'roflcopter/z.xml',
# mich auch
);
if (
(exists $textfiles{$path})
or
(exists $xmlfiles{$path})
) {
# Verarbeitung
} else {
die "invalid p: $path";
};
Natürlich bin ich auch um sonst jeden Tip froh, der meinen Perl-Code verbessert.
Du arbeitest in einer Umgebung, in der feindliche Daten von außerhalb zu erwarten sind, sprich CGI. Benutze Taintmode.
#!/usr/bin/perl -T
Character encoding
use utf8;
use encoding 'utf8';
Das ist doppeltgemoppelt. Beide macht dasselbe, nämlich du darfst den Perlquellcode in UTF-8 schreiben, z.B. Bezeichner und literale Strings. Du nimmst dies aber gar nicht in Anspruch. Entferne die Pragmas.
if (not (-T "$path")) {
$path = 'index.html';
}
Bezweckst du etwas mit der Evaluierung der Variable $path hier? Wenn nicht, dann entferne die Quotes. Perliger schreibt man es so:
$path = 'index.html' unless -T $path;
sub text2html {
Stelle Subdeklarationen an den Anfang des Programms. Es ist guter Stil und bewahrt dich davor, die Sub vorwärts deklarieren zu müssen, um die Missinterpretation als unbekanntes Bareword zu verhindern, wenn du sie jemals ohne Klammern aufrufen solltest.
open(FILE, $source);
Benutze die Dreiargumenteform der open-Funktion. Benutze lexikalische Filehandles. *Prüfe immer, immer Rückgabewerte von Systemaufrufen!*
open my $fh, '<', $source or die "could not open <$source> for reading: $!";
if (!utf8::is_utf8($buffer)) {
utf8::decode($buffer);
}
Bitte ausreichend in der Quelle kommentieren. Was bezweckt das? Wieso hast du das Konstrukt verwendet?
Mir ist das nicht geheuer. Das sieht aus wie Symptombekämpfung ohne Berücksichtung des fundamentalen Sachverhalts.
Hallo Unbekannter
Bist du etwa CK? Anscheinend machst du gute Fortschritte beim Japanisch lernen ! ;-)
du lässt also den Benutzer auf deinem Dateisystem herumfuhrwerken? Ist ja irre toll. Blacklisting funktioniert nicht.
Ich habe keine andere Wahl. Ansonsten verliert das Skript massiv an dynamik. Aber ich bin mir der Gefahr bewusst, darum frag ich hier. Ich will jedoch keine Blacklist, sondern eher eine Whitelist: alles, was unter einem bestimmten Verzeichnissen liegt, soll dem User gezeigt werden. Die Sache ist nicht enorm kritisch, da dieser Bereich des Servers per AuthDigest geschützt ist und ich keine bösen User habe. Nichts desto trotz will ich natürlich so sicher wie möglich programmieren.
Wenn du sicher sein willst, ersetze den ganzen Kram durch eine Dispatchtabelle. Alles, was nicht unter deiner Fuchtel steht, wird abgewiesen.
Das geht leider nicht, da ich nicht sagen kann, welche Daten auf dem Server liegen. Diese werden per WebDAV von mehreren Benutern verwaltet.
Du arbeitest in einer Umgebung, in der feindliche Daten von außerhalb zu erwarten sind, sprich CGI. Benutze Taintmode.
#!/usr/bin/perl -T
Danke für den Hinweis! Ich werde mich diesbezüglich mal schlau googeln.
> > # Character encoding
> > use utf8;
> > use encoding 'utf8';
> Das ist doppeltgemoppelt. Beide macht dasselbe, nämlich du darfst den Perlquellcode in UTF-8 schreiben, z.B. Bezeichner und literale Strings. Du nimmst dies aber gar nicht in Anspruch. Entferne die Pragmas.
Das stimmt leider nicht ganz. use utf8 kann ich ohne Probleme entfernen, wenn ich jedoch use encoding 'utf8' entferne, dann kommt mein Inhalt beim Browser nicht mehr als UTF-8 an.
> > if (not (-T "$path")) {
> > $path = 'index.html';
> > }
> Bezweckst du etwas mit der Evaluierung der Variable $path hier? Wenn nicht, dann entferne die Quotes. Perliger schreibt man es so:
> ~~~perl
> $path = 'index.html' unless -T $path;
>
Ich _hatte_ das so aus einem Tutorial... An diese Perl-Konstrukte muss ich mich noch ein wenig gewöhnen.
open(FILE, $source);
Benutze die Dreiargumenteform der open-Funktion. Benutze lexikalische Filehandles.
open my $fh, '<', $source or die "could not open <$source> for reading: $!";
Macht man das weil der Code konsistener ($ für alle Variablen) wird oder hat das noch andere Gründe?
> \*Prüfe immer, immer Rückgabewerte von Systemaufrufen!\*
Asche auf mein Haupt...
> > if (!utf8::is\_utf8($buffer)) {
> > utf8::decode($buffer);
> > }
> Bitte ausreichend in der Quelle kommentieren. Was bezweckt das? Wieso hast du das Konstrukt verwendet?
Du hast recht, in diesem Beispiel brauche ich das nicht. Während der Entwicklung habe ich den Dateiinhalt jedoch direk in den Ausgabestream geleitet, und dann bekam ich bei einer ISO-8895-1-codierten Datei kein UTF-8 zurück.
> Mir ist das nicht geheuer. Das sieht aus wie Symptombekämpfung ohne Berücksichtung des fundamentalen Sachverhalts.
Das siehst du richtig: Ich habe nicht immer UTF-8-codierte Dateien, will diese jedoch immer als UTF-8 ausgeben. Das lässt sich leider nicht ändern.
Vielen Dank für deine guten Hinweise, die bringen mich wirklich weiter.
Gruss
Tom2
ich keine bösen User habe.
Unachtsamkeit kann auch Schaden anrichten, dazu bedarf es keiner Bosartigkeit.
Bleibe bei der Dispatchtabelle. Du kannst trotzdem dynamisch programmieren. Mein Beispielcode sollte nur das Prinzip veranschaulichen und nicht andeuten, dass die erlaubten Werte fest einprogrammiert sein sollen.
Lese zuerst die Dateinamen aus dem WebDAV-Verzeichnis in den Hash als Werte. Die dazugehörigen Schlüssel erzeugst du durch einen Normalisierungsprozess, im einfachsten Fall durch 1:1-Abbildung. Lass dir was schlaueres einfallen. (Alle Leser: Vorschläge?)
Der Hash ist die Whitelist. Wie schon im letzten Posting gesagt, wenn ein Schlüssel kommt, den es nicht gibt, einfach abweisen.
wenn ich jedoch use encoding 'utf8' entferne, dann kommt mein Inhalt beim Browser nicht mehr als UTF-8 an.
Ah, du hast das grundlegende Problem doch schon erkannt, Daten müssen vor der Ausgabe kodiert werden. Leider benutzt du ein unbrauchbares Mittel dafür. Mein Artikel http://forum.de.selfhtml.org/archiv/2006/7/t133539/#m866028 bietet andere, generalisierte Ansätze, von denen auch einige in eine Zeile passen. Entferne das Pragma und sattle auf eins von denen um.
Benutze die Dreiargumenteform der open-Funktion. Benutze lexikalische Filehandles.
Macht man das weil der Code konsistener ($ für alle Variablen) wird oder hat das noch andere Gründe?
Du hast keinen Modus gewählt, deshalb wurde das Filehandle defaultmäßig zum Schreiben/Lesen geöffnet. Stell dir vor, du hättest das FH in eine Sub gegeben, die nicht von dir stammt, und der fremde Code schreibt munter in die Datei, obwohl du bloß lesen wolltest. Die Dreiargumenteform zwingt dich, über den Zugriffsmodus nachzudenken. Schreibzugriffe auf eine zum nur-Lesen geöffnete Datei schlagen fehl.
Herkömliche FH vom Globtyp Filehandle, deren Bezeichner typischerweise aus Konvention großgeschrieben werden, bleiben bis zum Ende des Programms bestehen und verschmutzen den Namensraum. Und wie jeder weiß, sind globale Bezeichner pfuibähbäh. Skalare als FH hingegen lassen sich im Gültigkeitsbereich einschränken. Variablen sollten aus dem Namensraum entfernt werden, wenn sie nicht mehr gebraucht werden. So vermeidet man eine weitere Quelle an Programmierfehlern. Typisches Beispiel:
my @inhalt;
{ # nackter Block erzeugt neuen G'bereich
open my $fh, '<', 'foo' or die;
@inhalt = (<$fh>);
close $fh;
};
# $fh ist ab hier nicht mehr gültig und kann erneut verwendet werden
Ich habe nicht immer UTF-8-codierte Dateien, will diese jedoch immer als UTF-8 ausgeben.
Die Dekodierung ist sowieso fällig, seien die ankommenden Datei UTF-8 oder Latin-9 oder sonstwas. Es ist falsch, anzunehmen, es ist UTF-8, und wenn nicht, normaler Plaintext, eben weil es keinen Plaintext gibt. Ersetze dein Konstrukt, welches nicht alle Fälle abdeckt, also durch Encode::decode() oder andere Lösung aus verlinktem Archivartikel.
Der Haken an der Geschichte ist nun besser sichtbar, dass du wissen musst, in welcher Kodierung die Datei vorliegt. Sorge also dafür, dass irgendwie Metadaten für jede Datei vorliegen. Falls das nicht geht (das Leben ist bekanntlich fiese und gemein), setze eine Heuristik ein. http://forum.de.selfhtml.org/archiv/2006/9/t136083/#m883628
Am besten verlagerst du diese Problematik nach außerhalb deines Programms. Setze vorraus, dass dein Programm nur UTF-8-kodierte Dateien zu essen bekommt. Konvertiere Fremdformate vorher.