m:n Beziehungen zu Lookup-Tables mit einem SQL-Befehl erstellen

puzzleBei diesen Beitrag habe ich echt überlegt, ihn unter einem anderen Namen zu veröffentlichen. Ist der Inhalt doch eigentlich sowas von trivial und für Menschen mit mehr als zwei Hirnzellen wahrscheinlich sowieso offensichtlich. Aber das Gefühl, das mich durchflutet hat als mich Erkenntnis küsste war so erfüllend und erhebend, dass ich dieses gerne mit Euch teilen würde.

SQL ist eine geniale Erfindung. Zwar wird es im Moment arg von den schmächtigen noSQL bedrängt, aber es wird wie triumphieren *star-wars-musik-on*, denn die Anderen werden untergehen wie die noAngels, es wird gewinnen wie die Maden über die noMaden, häufiger verwendet werden, so wie es das Ah gegenüber noAh wird und edler sein, wie der Orden gegenüber noRden! Ich bin heute noch auf der Suche nach der vernünftigen Frage, die nicht in ein einziges SQL-Statement verpackt werden kann.*star-wars-musik-off*

Beispiel-Schema

Doch zurück zum Problem: Es seien Entitäten gegeben, die m:n Beziehungen zu „lookup-Werten“ haben. Ein bestehender Wert in einem Set also, das sich selten ändert wie beispielsweise Kategorien, Zustände, Farben, Ländercodes, Geschlechter oder so etwas. Ein weiterer Use Case wäre ein Import, bei dem die Datensätze schon erstellt wurden und nun nur noch die Verbindungen fehlen.

Hier ein Beispiel:
Personen beinhalten, ja ratet mal, Personen.Hier vereinfacht nur mit Namen und Vornamen.
Personentypen bezeichnen die Art der Person. Beispielsweise Lernender, Lehrperson, Verwaltungsangestellter oder auch Schulleiter.

Jede Person kann mehreren Personentypen entsprechen. Diagnose: klassisches m:n mit Verbindungstabelle (personen_personentypen).

Weiterlesen

MySQL berücksichtigt keine Gross- und Kleinschreibung bei select?

Manchmal habe ich echt das Gefühl, ein paar Jahre ganz feste kognitiv eingeschränkt gewesen zu sein. Per Zufall stellte ich heute fest, was das „ci“ bei den Datenbank Collations in Mysql heisst.

<musik typ="leicht-nervig" creator="Synthesizer" dauer="10" />
Moderator: Wahaaas bedeutet das Zeh Ih (_ci) bei den Datenbank Collations in MySQL?
Kandidat: Hmmm, uff, kann ich es im Kontext haben?
Moderator: Natürlich! Zeh Ih wie in utf8_general_ci.
Kandidat: Ui, das weiss ich nicht, gibt es Auswahlmöglichkeiten?
Moderator: Ja sicher:

  1. collation identifier
  2. childish internet
  3. clown institut
  4. corporate identity
  5. case insensitive


Und die Lösung: CASE INSENSITIVE

Was bedeutet _ci in der Praxis?

Tabelle:

+----+----------+----------+------------------------------------------+
| id | username | salt     | password                                 |
+----+----------+----------+------------------------------------------+
|  1 | Admin    | 2IT8mKiX | 8fd334609270a4b78c536dbdee4182b5b1d00bb7 |
+----+----------+----------+------------------------------------------+

Tja, folgender Tabelleneintrag wird gefunden mit folgenden Selects:

SELECT * FROM users WHERE username='Admin'; -- Klar, logo
SELECT * FROM users WHERE username='ADMIN'; -- Ui
SELECT * FROM users WHERE username='admiN'; -- Uiui
SELECT * FROM users WHERE username='admin'; -- Uiui, huch

Und das impliziert wiederum, dass ein User, der sich ADMIN nennt, unter Umständen vor dem eigentlichen Admin gefunden wird. Das ist doch eher hässlich. Wieso ist mir das nicht früher aufgefallen..?

Und die Lösung?

Die Collation ändern! Von utf8_general_ci zu utf8_bin und schon verhält sich die Datenbank erwartungskonform.

Was bedeutet dies für Rails?

Zum Einen sollte die Collation in der Datei config/database.yml festgelegt werden:

development:
  adapter: mysql
  encoding: utf8
  collation: utf8_bin
  database: lomas_development
  pool: 5
  username: lomas
  password: sagichnit
  socket: /var/run/mysqld/mysqld.sock

Leider wird die Collation bei Tests in Ruby on Rails nicht berücksichtigt! Das treibt mich zur Verzweiflung, echt!

Ausserdem gibt es noch etwas zu beachten bei validates_uniqueness_of. validates_uniqueness_of ist case-sensitive. Das heisst, auch hier werden admin und ADMIN akzeptiert. Erst ein weiterer Parameter verbietet dies:

  validates_uniqueness_of :username, :case_sensitive => false

Kleine Tricks in Ruby on Rails

Hier eine kleine Sammlung von Dingen, die mir geholfen haben beim Lösen der Probleme die sich einem N00b so stellen.

Exportieren von Datensätzen ins YAML Format

Rails Fixtures sind Testdatensätze, die im relativ übersichtlichen YAML-Format gespeichert werden. Möchte man bestehende Datenbankinhalte als Testdatensätze verwenden, so können Sie direkt in YAML exportiert werden.

Ich halte mich an den Beitrag von NetManiac. Den Code habe ich nur soweit ergänzt, als dass ich das Exportieren der magischen Felder „id“, „created_at“ und „updated_at“ verhindere und den Dateinamen ändere, damit keine bestehenden Fixturen überschrieben werden.

Aufgerufen mit rake extract_fixtures['mymodel'] speichert es mymodel.dumped.yml im richtigen Verzeichnis.

Datei: libs/tasks/extract_fixtures.rake:

desc 'Create YAML test fixtures from data in an existing database.
Defaults to development database. Set RAILS_ENV to override. Use args
table and limit to dump one table and limit number of records'

task :extract_fixtures, :table, :limit, :needs => :environment do |t, args|
  args.with_defaults(:table => nil, :limit => nil)
  limit = args.limit.nil? ? "" : "LIMIT #{args.limit}"
  sql = "SELECT * FROM %s #{limit}"
  skip_tables = ["schema_info" ]
  if args.table.nil?
    tables = ActiveRecord::Base.connection.tables - skip_tables
  else
    tables = [ "#{args.table}"]
  end

  ActiveRecord::Base.establish_connection
  tables.each do |table_name|
    i = "000"
    File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.dumped.yml" , 'w' ) do |file|
      data = ActiveRecord::Base.connection.select_all(sql % table_name)
      file.write data.inject({}) { |hash, record|
        record.delete_if{|key, value| ['id', 'created_at', 'updated_at'].include?(key)}
        hash["#{table_name}_#{i.succ!}"] = record
        hash
      }.to_yaml
    end
  end
end

ActiveRecord Attribute beim Auslesen mit Defaultwerten überschreiben

Wenn ein Attribut immer beim Auslesen mit eineb bestimmten Wert belegt werden soll wenn noch nichts vorhanden ist, so geht dies mittels folgendem Codefragment:

def my_attribute
  read_attribute(:my_attribute) or another_attribute.capitalize+" "+yet_another_attribute
end

has_and_belongs_to_many mit Checkboxen in Fieldsets

Eine Rolle hat viele Permissions und umgekehrt. Um solche Assoziationen abzubilden, kann man collection_select verwenden, das aber leider nur ein hässliches Listfeld ausgibt. Eine ambitioniertere Variante arbeitet mit Checkboxen.

HABTM mit Checkboxen

HABTM mit Checkboxen

Zuerst wird in Permission.get_indexed_by_controller ein Hash of Hashes gemacht, der als ersten Schlüssel die Überschriften hat (controller) als zweiten Schlüssel die Feldbeschriftungen mit dem Zustand als Inhalt. Folgendes Partial gibt ein Formular aus und versieht sie mit „(Un-)Check all“ Links:

<%= link_to_function 'Check all', "$$('input.permission').each(function(checkbox) { checkbox.checked = true; })" %> |
<%= link_to_function 'Uncheck all', "$$('input.permission').each(function(checkbox) { checkbox.checked = false; })" %>
<% @permissions_indexed_by_controller=Permission.get_indexed_by_controller %>
<br />
<%- for controller in @permissions_indexed_by_controller.keys -%>
  <% field_set_tag controller do %>
    <%- for permission in @permissions_indexed_by_controller[controller] -%>
      <%= check_box_tag 'role[permission_ids][]', permission.id, @role.permissions.include?(permission),{ :id => "role_permission_ids_"+ permission.id.to_s, :class => "permission"} -%><%= permission.description %>
    <%- end -%>
  <% end %>
<%- end -%>

Wichtig ist, dass im entsprechenden Controller in der update Funktion ein leeres Array erzeugt wird wenn nichts übergeben wird, sonst ändert sich nichts wenn man alle Häckchen rausnimmt:

params[:role][:permission_ids] ||= []

Erzeugen eines Hash of Hashes in Ruby

Die Hash/Array implementierung in PHP ist immernoch ungeschlagen. Will man in Ruby ein Hash of Hashes machen und einen Wert mittels << hinzufügen wenn noch kein leerer Hash besteht, so tut es blöd. Insbesondere wenn man Records in einem Hash abspeichen will, hilft folgendes Idiom:

      (ret[map_key] ||= []) << record

Dynamisches Navigationsmenü mit Highlightning in Ruby on Rails

Und es kam so: Gerade eben aufgestanden und am Gliedersortieren, sprach der Duschkopf zu mir: „Hey, du!“. Obwohl überrascht über diese ungewohnte Intimität des Duzens, war meine Aufmerksamkeit geweckt. „Jetzt mal ehrlich“, so sprachs weiter, „mit dem Symfony Framework hälst Du es gleich wie mit dem Latein: Oftmals angefangen, aber nie ganz warm geworden, gelle.“. Mein Hirn lief schon an der Grenze seiner morgentlichen Leistungsfähigkeit, aber eine Antwort darauf war wahrscheinlich auch gar nicht verlangt. „Du weisst schon, dass PHP eine Templatesprache ist, oder? Wieso hörst du nicht auf mit der Verulkung und versuchst Dich endlich mal am Original?“. Ich entgegnete was in der Richtung, dass die genuinen Römer (TM) schon ziemlich lange die Arena von unten ansehen. „Nein, du Dumpfbacke! Ruby on Rails sollst du versuchen und Freude daran haben! Frohlocken! Beweg deinen alternden Denkapparat du Assemblerheini!“

Natürlich folge ich allen Anweisungen meiner Badezimmerarmaturen, und so habe ich mich unverzüglich an die Arbeit gemacht. Die Standardinstallation hat es natürlich nicht getan (…und Zeit hat man zu viel…), Rails 2 musste es sein, mittlerweile released als Rails 2-2. Auch eine Entwicklungsumgebung musste her, und ich habe mich für Netbeans entschieden, da sie für zwei weitere Projekte (Handygames und JavaFX) eh angesehen werden wollte. Wie das Glück so spielt, hatten auch die Netzbohnen gerade ein grösseres Update auf 6.5 durchgemacht und kleinere Fluchereien mit verwurschtelten Projekten ausgelöst. Naja.

Mit verschiedenen Offline- und Onlinequellen habe ich mich an die Arbeit gemacht, und muss sagen, Ruby on Rails ist, hmm, ungewohnt. Bis jetzt geht es überraschend flüssig, auch wenn ich bei jeder Zeile 4 verschiedene Quellen konsultieren muss um es richtig (TM) zu machen.
Weiterlesen

Maximaler Fluss (maxflow) eines Netzwerkes in Java

MaxFlowUm im Avaloqix Wettbewerb einen einigermassen sinnvollen Agenten zustande zu kriegen, braucht es sehr wahrscheinlich eine Funktion, die den maximalen Fluss eines gegebenen Netzwerkes berechnet. Dafür gibt es eine Vielzahl von Algorithmen mit verschiedenen Aufwänden und in verschiedenen Komplexitätsstufen.

Der erste, älteste und einfachste Algorithmus ist wohl derjenige von Ford und Fulkerson. Er ist aber relativ aufwändig in der Berechnung (O(#Kanten*#Knoten*Grösste-Kapazität)), da der maximale Fluss in den Aufwand hineinspielt. Etwas besser ist der Algorithmus von Tarjan und Goldberg. Er wird von der Uni Karlsruhe anschaulich erklärt und bei Google Code findet man auch eine Implementation davon.

Für Graphen mit kleiner Kantendichte ist allerdings der Edmonds-Karp-Algorithmus schneller (laut Wikipedia).

Wenn man die von der Avaloqix-Oberfläche unter Wettbewerbsbedingungen erzeugten Graphen anschaut, ergibt sich folgendes Bild:

  • Bei 20 Knoten werden 40-50 Kanten generiert, das ergibt eine Dichte von ungefähr 0,263
  • Bei 60 Knoten werden 160 Kanten generiert, das ergibt eine Dichte von ungefähr 0,090

Das darf wohl mit Fug und Recht als undicht, ääh, geringe Dichte bezeichnet werden, also machen wir uns doch an Edmonds Karp!

Implementation

Bei der Implementation habe ich mich sehr nahe an die Beispielimplementation von Wikipedia gehalten. Die einzelnen Teile sind etwas aufgeräumter und kommentierter:

/**
 * Edmonds-Karp algorithm with O(V³E) complexity
 *
 * This implementation is a slightliy modified source from Wikipedia. See
 * http://en.wikipedia.org/wiki/Edmonds_karp
 */

class MaxFlow {

    public static final int UNVISITED = 0, VISITED = 1;

    private int[][] res_capacity; // Residual capacity
    private int[] parent; // Parent node in Breadth First Search (BFS)
    private int[] mark; // BFS: Already visited?
    private int[] min_capacity; // Minimal flow
    private int size; // Number of nodes
    private int sink;

    public MaxFlow() {
    }

    public int getMaxflow(int[][] graphMatrix, int source, int sink) {
        this.sink = sink;
        this.size = graphMatrix.length;

        int max_flow = 0; // THE max flow for which we are doing this
        // circus

        int[][] flow = new int[size][size]; // Our flow
        res_capacity = new int[size][size]; // Remaining capacity

        parent = new int[size];
        min_capacity = new int[size];
        mark = new int[size];

        // Before we start, all capacities are residual
        // System.arraycopy may be faster: see http://www.javaspecialists.eu/archive/Issue124.html
       
        // The following is wrong, as Feri in the comments has found out.
        // clone() is one-dimensional! *pats-head*
        // res_capacity=graphMatrix.clone();
        //
        //Faster:
        for (int i = 0; i < size; i++) {
            System.arraycopy(graphMatrix[i], 0, res_capacity[i], 0, size);
        }

        // Search in BFS manner through the graph, Path by Path
        while (BFS(source)) {
            max_flow += min_capacity[sink];
            int v = sink, u;
            while (v != source) {
                u = parent[v];
                flow[u][v] += min_capacity[sink];
                flow[v][u] -= min_capacity[sink];
                res_capacity[u][v] -= min_capacity[sink];
                res_capacity[v][u] += min_capacity[sink];
                v = u;
            }
        }
        return max_flow;
    }

    /**
     * Breadth First Search in O(V²)
     *
     * @param source
     *            The source node
     */

    private boolean BFS(int source) {
        int first, last; // Queue pointers
        int[] queue; // Queue pointers

        queue = new int[size];
        for (int i = 0; i < size; i++) {
            mark[i] = UNVISITED;
            min_capacity[i] = Integer.MAX_VALUE;
        }

        first = last = 0;
        queue[last++] = source;
        mark[source] = VISITED;

        while (first != last) { // While "queue" not empty.
            int v = queue[first++];
            for (int u = 0; u < size; u++)
                if (mark[u] == UNVISITED && res_capacity[v][u] > 0) {
                    min_capacity[u] = Math.min(min_capacity[v],
                            res_capacity[v][u]);
                    parent[u] = v;
                    mark[u] = VISITED;
                    if (u == sink)
                        return true;
                    queue[last++] = u;
                }
        }
        return false;
    }
}

Resultat

Als innere Klasse angelegt, arbeitet diese Implementation wunderbar in einem Avaloqix-Agenten. Tests haben gezeigt, dass das Resultat nicht nur stimmt, sondern auch mit den Ausgaben der Spieloberfläche korrespondiert. Auch bei sehr grossen Netzwerken braucht sie auf meinem Schlaftop nur ein paar wenige Millisekunden.

Avaloqix: Unterhaltsamer Java-Programmierwettbewerb

Flow im HirnIch liebe Wettbewerbe und ich liebe das Spielen. Meine inneres, urmännliches Ich springt voll auf das kompetitive Element solcher Veranstaltungen an. Zum Erlegen von Höhlenbären oder für Sportwettkämpfe eigne ich mich definitiv nicht, darum bin ich froh, wenn ab und zu ein Kräftemessen auf meinem – eher wetwarelastigen – Gebiet stattfindet.

Leider hat es jetzt seit längerer Zeit kein Codeduel (ein spassiger, von Microsoft Schweiz gesponserter/organisierter Event in welchem SOAP-Services gegeneinander angetreten sind, für den es aber leider keine offizielle Website mehr gibt) mehr gegeben und für die kommerzielle Version solcher Wettbewerbe habe ich im Moment wirklich keine Zeit.

Über die Seite des Schweizerischen Jahres der Informatik bin ich auf Avaloqix gestossen und war sofort begeistert. Am Tag der Informatik am 29. August 2008 in Zürich wird es ein Event speziell zu diesem Wettbewerb geben.

Das Spiel

Es geht darum, einen Spieler für eine abgewandelte Version des Shannon Switching Games zu programmieren. Kurz erklärt: Es gibt eine Quelle und ein Senke, die über ganz viele Röhren und Sammelpunkte miteinander verbunden sind. Im ersten Durchgang versucht der eine Spieler einen möglichst grossen Durchfluss zu erzielen indem er Röhren freischaltet, während der andere Spieler ebendies durch das Ausbauen von Röhren zu verhindern versucht. In einer zweiten Runde werden die Rollen der Spieler in demselben Röhrengeflecht getauscht. Gewonnen hat, wer als Durchflussmaximierer den grösseren Durchfluss erzielen konnte.
Weiterlesen

Automatisches und mehrfaches submitten von Formularen

Wirre Gedanken

FormularDieser Beitrag ist dem Titz gewidmet. Dem Titz, der sich aufgeregt hat :pirate-grumble: , obwohl es gar nicht nötig gewesen wäre und der sich nun mit einer gewissen Teilnehmerredundanz anfreunden muss. Hauptsache ist doch, das PHP fliesst und die Variablen bleiben sauber… Und ein Bierchen würde ich auch noch springen lassen :bier: …

Eine wichtige Frage zu Beginn: Wieso sollten wir denn automatisch und mehrfach Formulare submitten wollen? Hmm, um den Titz zu ärgern? Weil wir es können? Weil man manchmal tun muss, was man tun muss? Weil man seine 84 Kinder an einem elektronischen Fussballturnier anmelden will :laola: (hat eigentlich schon jemand bemerkt, dass ich neue Smilies und unglaublich Freude daran habe?)? Oder weil man über einen Wettbewerb gestolpert ist, der ebendies nicht verbietet (also das Mehrfachsubmitten, nicht das Kinderanmelden oder die neuen Smilies) und der kein Captcha hat (vielleicht, weil es der Titz vergessen hat)?

Nachtrag 31.07.2008: Schenken mir doch so unglaublich nette Mitarbeiter einer Versicherung heute morgen am Bahnhof einen Müsliriegel. Und auf diesem eher gesunden Teil hat es, ja rate, oh wissbegieriges Volk, einen Wettbewerb. Ob der Titz wohl am Abend für eine andere Firma weitercoded? Ich werde mich auf jeden Fall während der Zugfahrt mal damit befassen :computer: .

Vorgehen

Erster Schritt: Selenium IDE

Man könnte nun wie wild losprogrammieren, oder aber einen einfacheren Weg wählen. Ein guter Startpunkt für automatisiertes Browsen generell ist die Selenium IDE. Dieses geniale Teil für den Firefox zeichnet wie ein Macrorecorder alles auf, was im Browser gemacht wird. Hat man die Teilnahme beim Wettbewerb einmal so aufgezeichnet, so müssen nur noch die click’s, die das Form absenden, durch clickAndWait’s ersetzt werden, damit vor dem Weiterausfüllen (bei mehrseitigen Formularen) auf die neue Seite gewartet wird. Es empfiehlt sich, eine Abschlussüberprüfung als letzten Schritt hinzuzufügen, um um kontrollieren zu können, ob die Kinder erfolgreich angemeldet wurden (markieren des „Dankeblabla“, dann rechte Maustaste und assertTextPresent).

Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

Ein Wettbewerb über 2 Seiten. Aufgezeichnet und bearbeitet mit der Selenium IDE.

Diesen Testcase kann man nun abspeichern und eigentlich immer wieder ausführen. Ein Klick reicht und Firefox rasselt alles schön durch. Den Namen leicht verändern kann man durch Editieren des Skripts…

Für den zweiten Schritt sollte man das Testscript als PHP exportieren.

Zweiter Schritt: Selenium RC

Die Selenium RC Komponente kann den Browser fernsteuern und so ferngesteuert am Wettbewerb teilnehmen. Um sie unter Linux Debian zum Laufen zu kriegen, war ein Bisschen Gemurkse notwendig.

Weiterlesen

Mit PHP einen Monat mit verlinkbaren Daten anzeigen

CalendarFür eine Applikation, in der man Ressourcen reservieren kann, wollte ich ein Kalender mit verlinkbaren Tagen anzeigen (grad wie im Meeting Room Booking System). Da ich aus dem Alter raus bin, in dem man alles selber machen muss und ausserdem eine PHP-Datumsfunktionsphobie (PHPYpiresiaChronophobia) entwickelt habe, wollte ich eigentlich etwas Fertiges verwenden. Es gibt vorallem Kalender in JavaScript, ein Tutorial das den Kommentaren nach nicht funktioniert (und das mittlerweile vom Web verschwunden ist), Scripts ohne Ausgabebeispiele, Riesenmonster (deren Anwendungsbeispiele mehr Code haben als eine Eigenimplementation) oder Kalender, die zwar gut sind, aber irgendwie nicht gepasst haben (I18n, …). Ich möchte auch nix mit Ajax, Sigolin und Meister Propper, denn es soll eine Enterprise-Applikation geben, und da ist JavaScript halt nicht so opportun (und erspart mir wieder ein paar graue Haare mehr). Aber vor allem (aber das verschweige ich hier) hat das wie eine spannende Aufgabe ausgesehen, und als Kontrollfreak will ich es eben doch selber gemacht haben 😀 .

Ziel

Das Ziel ist es einen Kalender zu erhalten, der die Wochen anzeigt, bei dem ein Tag (Event) markiert werden kann und das heutige Datum speziell ausgezeichnet ist. Er sollte also in etwa so aussehen (die Monatsnamen und Namen der Wochentage sollten sich auch noch der Sprache anpassen):

PHP-Kalender

Man sollte entweder ein Monat und ein Jahr übergeben können oder einfach nichts, wobei dann der aktuelle Monat und das aktuelle Jahr verwendet werden.

Vorgehen und Implementation

Ich habe mich entschlossen, dem Enterprisefassadefrontsidedecontrollerkurtmitgurtgimmeabreak-Pattern zu folgen… Quatsch, meine Gewohnheit und die Stimmen in meinem Kopf haben mich dazu gezwungen, den Kalender in zwei verschiedenen Funktionen zu implementieren. PHP hat ein absolut geniales Array-Handling, und wenn ich an solchen datenmanipulierenden Aufgaben arbeite habe ich mir angewöhnt, zuerst die reinen Daten in einem (mehrdimensionalen, ev. assoziativen) Array abzubilden. Dies hat den grossen Vorteil, dass ich die aufbereiteten Daten nicht nur für die HTML-Ausgabe, sondern auch von anderen Funktionen aus benutzen kann. Das Ziel wäre ein Array, dass auf der einen Dimension die Wochen und auf der Anderen die Wochentage hat. Man nehme den ASCIIstift hervor und zeichne ein Beispiel für Juni 2008:

[22] => {   '',   '',   '',   '',   '',   '',  '1'}
[23] => {  '2',  '3',  '4',  '5',  '6',  '7',  '8'}
[24] => {  '8',  '9', '10', '11', '12', '13', '14'}
...
[27] => { '31',   '',   '',   '',   '',   '',   ''}

Die Benamsung ist wie folgt:
PHP-Kalender benamst
Weiterlesen

AIML: Intelligenz im XML

BrainWenn man schon keine eigene Intelligenz besitzt, ist man froh, dass es sie auch in künstlicher Form gibt. Bis vor einiger Zeit wurde ziemlich proprietär rumgewurstelt, aber nun gibt es AIML, die Artificial Intelligence Markup Language die mit XML und Standardisierung das Verwenden unter verschiedenen Plattformen möglich macht. XML und Intelligenz ist also doch nicht per se ein Widerspruch.

Interessant ist das Ziel dieser Bemühungen (wie auf Pandorabots beschrieben):

Inhalte zu erstellen, die den Besucher dazu veranlassen so lange wie möglich mit dem Bot zu reden.

Auf der Basis von AIML wurden verschiedene Interpreter für verschiedene Programmiersprache geschaffen, Inhaltsdateien erstellt, die alle von der offiziellen Homepage oder der Tools Page heruntergeladen werden können.

AIML als XML (Gimme Code!)

AIML ist ziemlich komplex. Es gibt die Möglichkeit einer Unterteilung in Themen, der Extraktion von Mustern, der spezifischen Auswahl der Antworten etc, etc… Die englischen Übersichten und Tutorials gehen tiefer in die Materie.

AIML Tags

Die wichtigsten Tags von AIML:
Weiterlesen

PHP_SELF ist böse! Potentielles Cross Site Scripting (XSS)!

Was haben wir gelernt?

EvilJa, uns wurde gelehrt, dass man wenn immer möglich nicht Dateinamen direkt, sondern eine Variable angeben soll, die für den Dateinamen steht. Warum konnte mir zwar noch niemand so genau sagen, aber ich nehme an, dass es darum geht, dass der Dateinamen oder der Pfad ändern könnte. So habe ich ziemlich oft ganz brav geschrieben:

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method ="post">
...
</form>

Und (zum Glück) bin ich nicht der Einzige. Eine kleine Suche auf Googles Codesearch hat gezeigt, dass mindestens Mambo, PHPMyFAQ, Einige WordPressthemes und viele mehr dasselbe Problem haben. Ich bin bei Weitem auch nicht der Erste, der über Probleme mit dieser Technik erfahren und darüber geschrieben hat.

Problem? mit $_SERVER['PHP_SELF']?

Lasst mich ausholen: Der Apache Webserver hat eine Option AcceptPathInfo, welche standardmässig auf On ist. Mit dieser Option mappt der Apache beliebig lange Pfade auf Dateien, sofern diese irgendwie Bestandteil des Pfades sind. Der Rest wird in Umgebungsvariablen mitgegeben.

Gimme Code

Nehmen wir an, es gäbe eine Datei /subdir/mypath.php welche so aussieht:

<?php
echo "<pre>";
echo "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n";
echo "PHP_SELF:    ".$_SERVER['PHP_SELF']."\n";
echo "SCRIPT_NAME: ".$_SERVER['SCRIPT_NAME']."\n";
echo "</pre>";
?>

Bei einem Aufruf von: http://localhost/subdir/mypath.php/additional/stuff/nonsense.php?para=4 mappt Apache netterweise alles auf unsere Datei http://localhost/subdir/mypath.php und verstaut den Rest im $_SERVER Array. Die Ausgabe ist:

REQUEST_URI: /subdir/mypath.php/additional/stuff/nonsense.php?para=4
PHP_SELF:    /subdir/mypath.php/additional/stuff/nonsense.php
SCRIPT_NAME: /subdir/mypath.php

PHP_SELF übernimmt also den ganzen Krempel und würde ihn bei unserem Form auch so darstellen. Angenommen, wir haben ein PHP-Script mit der URL http://localhost/contact/myform.php mit folgendem Inhalt:
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method ="post">

Ruft man dieses nun mit folgender URL auf (die aufwändiger als notwendig konstruiert und des besseren Verständnisses wegen nicht URL-encodiert ist):
http://localhost/contact/myform.php/"></form>Hier ein Javascript: <script>alert('gotcha');</script><form action="/contact/myform.php
erhält man:
<form action="/"></form>Hier ein Javascript: <script>alert('gotcha');</script><form action="/contact/myform.php" method ="post">

Also vollkommen gültiges HTML (sogar das Form funktioniert) mit fremdbestimmbaren Seiteninhalt. Das ist ja wohl hässlich…

Theorie! Gib mir Praxis!

Ich weiss nicht, wie lange folgende Links funktionieren, beziehungsweise diese Sites anfällig für diese Art von XSS sind:

Digital Postcard (Kein XSS, aber mein Text 🙂 ):
Postcard XSS

Multimediatreff: (Mittlerweile behoben)
Multimediatreff

Jobs. ch (Mittlerweile behoben):
Jobs.ch

Was nutzt das dem bösen Hacker?

Text in Fremdpages einbauen, Phishing, Indentitätenklau und noch einiges Weiteres. Ein Folgeartikel wird mindestens eine Anwendung zeigen.

Was tun?

Ganz einfach: Das oft verschmähte $_SERVER['SCRIPT_NAME'] verwenden!!!

BTW: Viele Variablen in $_SERVER sind anfällig, aber alles muss ich ja auch nicht verplappern, oder?