Zeichensatzkodierung bei Webapplikationen


 

Ausgangspunkt

Die Postgres-Datenbank des ZID speichert die Daten in der Zeichensatz-Kodierung "UNICODE". (Was das im Detail ist und wie das in Beziehung zu UTF-8 steht, ist im ersten Teil eines Papers von Simon Cozens erklärt. [Dieser Artikel setzt sich eigentlich mit perl und UNICODE auseinander]. Um es kurz zu machen: UTF-8 ist ein "Unicode Transformation Format", das weit verbreitet ist, weil es kompatibel zur "Kodierung" ASCII oder zu ISO-8859-1(5) in dem Sinne ist, dass Standardzeichen (NICHT aber umlaute usw.) gleich wie dort kodiert werden.)

Postgres unterstützt die Konvertierung verschiedener Zeichensätze von und nach UNICODE auf dem Weg zwischen Klient und Server. D.h. der Klient muss dem Server nur mitteilen, welchen Zeichensatz er selbst verwendet um Daten an den Server zu schicken oder abgefragte Daten darzustellen, und der Server konvertiert die Daten dann bei Einfügungen/Änderungen NACH und bei Abfragen VON UNICODE. Diese Konvertierung wird für sehr viele Kodierungen standardmässig unterstützt. (siehe Multibyte Support in der Postgresql Dokumentation; die Tabelle 7-2 enthält die schon eingebauten konvertierbaren Kodierungen).

Problem

Werden Daten über ein Webinterface (Formular) und ein Skript beliebiger Sprache (php, perl, java) in die Datenbank eingefügt, kann das bearbeitende Skript die Zeichensatzkodierung dieser Daten dem Postgres-Server natürlich mitteilen.

Allerdings müssen bei der Programmierung des Skripts Vorkehrungen getroffen werden, um im Zusammenspiel mit dem BROWSER klare Informationen zu bekommen, mit welcher Zeichensatzkodierung die Daten vor Ort von den BenutzerInnen eingegeben wurden, bzw. mit welcher Kodierung der Browser die Daten dem Skript übermittelt!

(N.B.: Das analoge Problem gibt es natürlich auch bei der DARSTELLUNG von Daten, die ein Skript aus einer Datenbank holt und dem Browser schickt: Es muss sichergestellt sein, dass der Browser die Daten mit der richtigen Zeichensatzkodierung anzeigt!)

Lösung: Zeichensatzkodierung explizit spezifizieren

Es ist über die in diesem link genannten 3 Wege möglich, dem Browser mitzuteilen, welchen Zeichensatz dieser zur Darstellung der vom Skript gesendeten Seite verwenden soll:

  • Im http-Header: Setzen des "charset"-Parameters im "Content-Type" Feld.

  • Übermittlung eines "Meta"-Elementes im Kopf der html-Datei.

  • Setzen des "charset"-Parameters bei der Angabe des Hyperlinks.

(Erläuterungen siehe unten)

Im Test hat sich gezeigt, daß alle gängigen Browser diese Information auswerten und den Zeichensatz entsprechend setzen.

Die mit diesen Schritten explizit spezifizierte Zeichensatzkodierung wird auch vom Browser für Daten verwendet, die er aus einem Formular via "Submit" an das Skript rückübermittelt.

[ up ]

Konkrete Vorgehensweise

Unsere Empfehlung ist es, überall die Zeichensatzkodierung UTF-8/UNICODE zu verwenden, da nach unseren Informationen alle aktuellen Browser UTF-8 als Zeichensatzkodierung unterstützen und dadurch nicht ständig Konvertierungen notwendig sind.

Selbstverständlich sind auch andere Varianten möglich, es muss nur aus der nachstehenden Tabelle eine Kodierung gewählt werden, die vom Datenbankserver in UNICODE umgewandelt werden kann. Ausserdem muss in allen unten angeführten Beispielen "UTF-8" durch die Bezeichnung der entsprechenden Zeichensatzkodierung ersetzt werden. (ACHTUNG: Die in der Tabelle unten angeführten Bezeichnungen der Zeichensatzkodierungen sind die Bezeichnungen, wie sie PostgreSQL verwendet und unterscheiden sich von den Bezeichungen, wie sie in http/html verwendet werden. Eine Liste aller Zeichensätze und ihrer verschiedenen Bezeichnungen findet sich bei der Internet Assigned Numbers Authority (IANA). )

Datenbankkodierung mögliche Klientenkodierungen
UNICODE EUC_JP, SJIS, EUC_KR, UHC, JOHAB, EUC_CN, GBK, EUC_TW, BIG5, LATIN1 bis LATIN10, ISO_8859_5, ISO_8859_6, ISO_8859_7, ISO_8859_8, WIN, ALT, KOI8, WIN1256, TCVN, WIN874, GB18030, WIN1250

Anforderung an Skripts

Um die Zeichensatzkodierung zu "erzwingen", müssen folgende Schritte in allen Skripts durchgeführt werden: (siehe Specifying the character encoding und The Form element in den html-recommendations der w3c.org.)

Der erste und der vierte Schritt sind immer notwendig, der zweite ist empfehlenwert, der dritte Schritt ist nur erforderlich, wenn ein Formular für Eingaben in die Datenbank verwendet wird.

Die unten gemachten konkreten Angaben, beziehen sich auf die Kodierung mit UTF-8. Wenn ein anderer Zeichensatz verwendet wird, sind entsprechende Anpassungen notwendig.

http-Header

Es muss ein http-Header mit folgendem Inhalt an den Browser übermittelt werden:

Content-Type: text/html; charset=utf-8

Meta-Tag

Ausserdem ist es sinnvoll, ein Meta-Tag folgender Art so früh wie möglich im Kopfteil des html-textes zu übermitteln:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Formularattribute

Da Eingaben an Skripts über html-Formulare laufen, ist es außerdem noch gut, das accept-charset=-attribut und das enctype=-attribut des FORM-tags folgendermassen zu verwenden:

<form action="http://server.com/cgi/handle" method="post" enctype="multipart/form-data" accept-charset="utf-8">

Dem Datenbankserver die Zeichensatzkodierung mitteilen

Damit der Datenbankserver die vom Klienten übermittelten Daten bei Einfügungen/Änderungen bzw. die an den Klienten geschickten Daten bei Abfragen richtig umwandelt (oder nicht umwandelt, wenn beide Kodierungen auf UTF-8/UNICODE gesetzt sind) ist es notwendig, dem Datenbankserver explizit mitzuteilen, welche Zeichensatzkodierung der Klient verwendet.

Das geht zum Beispiel durch die Übermittlung des SQL Befehls

SET CLIENT_ENCODING TO 'UNICODE'

Optional: Setzen des "charset"-Parameters bei der Angabe des Hyperlinks

Zusätzlich zu den bereits genannten Methoden, die Zeichensatzkodierung mitzuteilen, ist es auch möglich, bereits im Verweis auf eine Seite deren Kodierung bekanntzugeben:

< a href="page_utf8.html" charset="UTF-8">Mit UTF-8 kodierte Seite</a>

Zeichensatzkodierung des Skripts

Im allgemeinen übermitteln Skripts an den Browser nicht nur Daten aus einer Datenbank, sondern z.B. auch Zeichenketten, die direkt im Skript als Konstanten definiert sind.

Diese Zeichenketten müssen bei der Übermittlung zum Browser natürlich in dem Zeichensatz kodiert sein, der dem Browser mit Hilfe des http-Header und/oder des meta-Tags mitgeteilt wurde, da es sonst zu falschen Darstellungen von Umlauten und Sonderzeichen kommt.

Um das zu erreichen, gibt es zwei Möglichkeiten:

1. Das gesamte Skript in dieser Zeichensatzkodierung zu speichern. Im Falle von UTF-8 heisst das z.B., dass ein Editor verwendet werden muss, der die Skript-Dateien im UTF-8 Format speichern und bearbeiten kann. Sind die Skripts schon in einer anderen Zeichensatzkodierung gespeichert, gibt es auch die Möglichkeit, sie am Server mit Hilfe des Konvertierungsprogrammes iconv zu konvertieren. (z.B.: iconv -f ISO8859-1 -t UTF-8 LATIN1_skript > UNICODE_skript)

2. Jede Ausgabe von im Skript selbst gepeicherten Daten an den Browser explizit von der Skript-internen Kodierung in die Browser-Kodierung zu konvertieren (ACHTUNG: Das bezieht sich NICHT auf aus der Datenbank stammende Daten; Diese sind schon in UTF8 kodiert!). Dies geschieht ebenfalls mit Hilfe eines iconv-Aufrufes. In php genügt dafür ein Aufruf der Funktion iconv() (Details zu php bzw. perl siehe in den Beispiel-Skripts weiter unten)

Beispiele

Zur Verdeutlichung des Ganzen sollen folgende Beispiele dienen:

php

<?php   
$charset = "utf-8";
$pg_enc  = "UNICODE";

# header 
header("Content-Type: text/html; charset=$charset");

# meta tag
echo "<HEAD>";
echo " <META http-equiv=\"Content-Type\" content=\"text/html; charset=$charset\">";
echo "</HEAD>";

# formular mit enctype und accept-charset
echo "<HTML>";
echo " <BODY>";
echo "  <FORM method=\"post\" action=\"einskript.php\" " .
        "enctype=\"multipart/form-data\" accept-charset=\"$charset\">";

[...]

# iconv, falls das skript selbst nicht in UTF-8 kodiert ist
echo iconv("ISO-8859-1", "UTF-8", "Text mit Umlauten und Sonderzeichen: äöü ÄÖÜ ß.<br>\n");

# verbindung zur datenbank herstellen
$connect_string = "host=$host dbname=$db user=$user password=$pwd";
$dbh = pg_connect($connect_string);
if (!$dbh) {
  $err = pg_last_error();
  die("could not connect using connect_string ". $connect_string .
      " Error:" . $err );
}


# klienten zeichensatzkodierung dem datenbankserver mitteilen
$query = "SET CLIENT_ENCODING TO '" . $pg_enc . "'";
$sth = pg_query($dbh, $query)
   or die("could not execute query \"$query\": " . pg_last_error());

[...]

# charset parameter eines links
$thisscript=$_SERVER["PHP_SELF"];
echo "<a href=\"${thisscript}s\" charset=\"ISO-8859-1\">hier die LATIN1/ISO-8859-1 source</a> dieses skripts.";

echo " </BODY>";
echo "</HTML>";

?>

perl

use CGI;
use strict;
use warnings;

use DBI;

# pragma, damit in zeichensatzspezifischen Funktionen Unicode verwendet wird
use utf8;

my $charset = "utf-8";
my $pg_enc  = "UNICODE";

# HTTP Header
print header(-type  =>  "text/html; charset=$charset");

# meta tag
print "<HEAD>";
print " <META http-equiv=\"Content-Type\" content=\"text/html; charset=$charset\">";
print "</HEAD>";

# formular mit enctype und accept-charset
print "<HTML>";
print " <BODY>";
print "  <FORM method=\"post\" action=\"einskript.php\" " ,
      "enctype=\"multipart/form-data\" accept-charset=\"$charset\">";

[...]

# iconv, falls das skript selbst nicht in UTF-8 kodiert ist
use Text::Iconv;
my $converter = Text::Iconv->new("ISO-8859-1", "UTF-8");
my $converted = $converter->convert("Text mit Umlauten und Sonderzeichen in LATIN1: äöü ÄÖÜ ß ");
print $converted;

# datenbankverbindung herstellen
my $dbh = DBI->connect("dbi:Pg:dbname=$db;host=$host", "$user", "$pwd", {PrintError => 0})
   or die("Could not connect to database using connectstring:" .
          "\"dbi:Pg:dbname=$db;host=$host\", \"$user\", \"$pwd\"" .
          " Error: " . DBI->errstr);

# Server die Klientenzeichensatzkodierung mitteilen
my $stmt = $dbh->prepare('SET CLIENT_ENCODING TO ?') 
  or die("Could not prepare query, aborting");
$stmt->execute($pg_enc)
  or die(" Couldnt execute statement:  " .$stmt->errstr);

[...]

# charset parameter eines links
my $thisscript=$ENV{"SCRIPT_NAME"};
print "<a href=\"${thisscript}s\" charset=\"UTF-8\">hier die source</a> dieses skripts.";



print " </BODY>";
print "</HTML>";