Bookmarklets: Kleine Helferlein

bookmarkBookmarklets sind eigentlich ganz normale Links, die aber Javascript beinhalten. Sie können aus einer beliebigen Website direkt an den bevorzugten Bookmark-Ort (Toolbar, Menü, Sidebar, …) gezogen und verwendet werden. Die Idee ist so einfach und genial, dass es schon grosse Verzeichnisse für sie gibt. Da sie in Javascript programmiert sind, kann man auch den Quellcode direkt ansehen und für sich anpassen.

Beispiele sind:

  • Das auf der aktuellen Seite markierte Wort nachschlagen, übersetzen, auf der ganzen Seite markieren, …
  • Seitendaten (Bilder, Links, Texte) oder -metadaten (Frische, Grösse, …) auslesen.
  • Seite verändern: (Schrift, Farbe, …)

Hier ein Trivialbeispiel: ddo.com ist ein MMPORG, das den Service „my.ddo.com“ anbietet. Dort können Detaildaten über die Charakteren der Spieler abgerufen werden indem eine spezielle URL nach dem Schema http://my.ddo.com/character/welt/name/ zusammengestellt wird. Das erste Bookmarklet nimmt vom Benutzer Welt und Name entgegen und ruft die entsprechende URL auf (Test: Senthessel auf Ghallanda):

<a href="javascript:void(Tn=prompt('Toon name:',''));void(Sr=prompt('Server:',''));if(Tn && Sr) location.href='http://my.ddo.com/character/'+escape(Sr.toLowerCase())+'/'+escape(Tn.toLowerCase())+'/'">Suche DDO-Toon</a>

Es ist voll bookmarkfähig und sieht folgendermassen aus: Suche DDO-Toon.

Ein zweites Bookmarklet sucht auf einer bestimmten Welt und nimmt als Namen entweder den auf einer Webseite markierten Text oder die Eingabe in einem Dialog:

<a href="javascript:Tn=document.getSelection();if(!Tn){void(Tn=prompt('Toon name:',''))};if(Tn) location.href='http://my.ddo.com/character/ghallanda/'+escape(Tn.toLowerCase())+'/'">Search DDO-Toon On Ghallanda</a>

Beispiel: Search DDO-Toon On Ghallanda

Folgendes Bookmarklet sucht ein entsprechendes Ausbildungsmodul von I-CH (Quelltext anzeigen für die Source 😉 ): Modulsuche auf I-CH

Am besten nimmt man ein bestehendes Bookmarklet und passt es auf seine Bedürfnisse an.


Hier eine Liste der kompletten DDO-Bookmarklets:

Firefox-Extension für das Bee5 affiliate System

flying-bee5Über Bee5 habe ich ja früher schon geschrieben. Sie haben jetzt eine Tools-Offensive gestartet: alle Tools, die ihr API verwenden, werden an den Verkäufen die mit dem Tool getätigt werden beteiligt. Aus meiner Sicht eine gute Offerte und eine Win-Win-Situation: Anwender können das System einfacher nutzen, die Programmierer werden für die Arbeit entschädigt, Bee5 wird bekannter und alle sind glücklich.

Das war eine ideale Gelegenheit zu lernen, wie eine Firefox-Extension geschrieben wird. Bemerkenswerterweise ist sie sogar fertig geworden. Sie befindet sich im Tools-Verzeichnis auf der Bee5 Seite (Login wird benötigt. Klar, sonst macht die ganze Toolbar keinen Sinn!).

Das Bee5 Affiliate-System

Kurzrepetition: Bee5 handelt mit Shops und anderen Sites Verträge aus, um für die Beschaffung von Kunden und Verkäufen entlöhnt zu werden (Provisionen). Der Grossteil dieser Beträge wird an jene weitergegeben, die die Kunden beschaffen (Links weitergeben, selber in den Partnershps einkaufen, …). Damit identifiziert werden kann, wer was eingekauft hat, hat Bee5 einen Kurzurl-Dienst ins Leben gerufen. Wird ein Partner-Shop über eine solche URL angesurft, weiss Bee5 wem der Provisionsanteil zusteht. Dieser Kurzurldienst kann aber auch für beliebige URLs benutzt werden, nicht nur für die „Partner“.
Einfach gesagt läuft es folgendermassen ab:

  1. Man Surft zu einem Partnershop.
  2. Aus der URL wird über die Bee5-Seite eine Kurzurl gemacht.
  3. Man klickt auf diesen Link oder gibt ihn weiter.
  4. Es wird eingekauft.
  5. Der Provisionsanteil wird ausbezahlt.

Die Firefox Extension sollte folgende Features haben, um den Prozess zu erleichtern:

  • Anzeigen, wenn man sich auf einer Partnerseite von Bee5 befindet.
  • Per Knopfdruck diese Seite über einen Bee5 Link aufsuchen.
  • Die aktuelle Seite im Browser in einen Bee5 Link umwandeln.
  • Beliebige Links in Bee5 Links umwandeln.
  • Über das Guthaben bei Bee5 informieren.

Herausgekommen ist eine Extension, die sich hauptsächlich als Toolbar zeigt:

Die Bee5 Extension in Aktion

Die Bee5 Extension in Aktion

Anatomie einer Firefox Extension

Das ganze Prinzip, das hinter einer Firefox Extension steht ist ziemlich interessant. Grundsätzlich werden die GUI-Elemente in XML, genauer in XUL definiert. Für den Browser wird ein Overlay kreiert, in dem man seine Dinge in die Toolbar, das Menü und/oder in die Statusbar einhängt. Alle Ereignisse werden mittels Javascript implementiert. Ein Ausschnitt aus dem Overlay:

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="Bee5TB-Overlay">
    <stringbundleset id="stringbundleset">
        <stringbundle id="Bee5TB-stringBundle" src="chrome://bee5bar/locale/bee5bar.properties"></stringbundle>
    </stringbundleset>
    <script type="application/x-javascript" src="chrome://bee5bar/content/bee5bar.js"></script>
    <toolbox id="navigator-toolbox">
        <toolbar id="Bee5TB-Toolbar" toolbarname="&bee5bar.toolbarname;" accesskey="&bee5bar.toolbarname.key;" class="chromeclass-toolbar" context="toolbar-context-menu" hidden="false" persist="hidden">
            <toolbaritem flex="0" id="Bee5TB-MainMenuTBItem">
                <toolbarbutton id="Bee5TB-MainMenu" type="menu" tooltiptext="&bee5bar.menu.tooltiptext;" label="&bee5bar.menu;" accesskey="&bee5bar.menu.key;">
                    <menupopup>
                        <menuitem label="&bee5bar.menu.website;" accesskey="&bee5bar.menu.website.key;" tooltiptext="&bee5bar.menu.website.tooltiptext;" oncommand="Bee5Bar.loadURL('http://bee5.de/beeoNu53a88')"></menuitem>
                        <menuitem label="&bee5bar.menu.mybee5;" accesskey="&bee5bar.menu.mybee5.key;" tooltiptext="&bee5bar.menu.mybee5.tooltiptext;" oncommand="Bee5Bar.loadURL('http://bee5.de/my/')"></menuitem>
                        <menuitem label="&bee5bar.menu.statistics;" accesskey="&bee5bar.menu.statistics.key;" tooltiptext="&bee5bar.menu.statistics.tooltiptext;" oncommand="Bee5Bar.loadURL('http://bee5.de/my/statistic-abstract.html')"></menuitem>

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?

UWA Widgets coden

Web 2.0, Just Do It…

Je besser man etwas kennt, desto besser kann man darüber lästern… Ich habe beschlossen, mich ganz praktisch durch das Tal der Tränen zu kämpfen und mich auf dieser Wanderung mit den technischen Teilen von Web 2.0 herumzuschlagen. Wie fühlt sich Ajax an? Wieviel Schatten spendet der DOM-Tree? XML oder JSON, JavaScript und APIs sollten die Dämonen auf meinem Weg sein.

Das Resultat

Das Resultat kann im Ecosystem angesehen und installiert werden. Es ist ein Widget, das Adressen über tel.search.ch sucht.
Tel.search.ch bietet ein geniales, gut dokumentiertes REST API an.

Ein paar Screenies?
So siehts in Netvibes aus:
Das tel.search.ch Widget in Netvibes

So in iGoogle:
Das tel.search.ch Widget in iGoogle

Und so in der Sidebar von Windows Vista:
tel.search.ch UWA Widget in Windows Vista Sidebar

Weiterlesen

Ein Ajax Baum der sich sein Zustand merkt

Man gönnt sich ja sonst nichts… Ich wollte für eine mit dem Symfony Framework erstellte Webanwendung eine Baumansicht. Da leichtes implemenieren langweilig ist, musste es schon ein Ajaxbaum mit Kontextmenü und allem Schnickschnack sein. Nach längerem Suchen fiel die Wahl auf Dojo, einem Toolkit mit vielen Widgets und sonstigen Spassmachern. Der Baum besitz viele Nodes, darum sollten diese dynamisch nachgeladen werden (ok, etwas Masochismus war auch dabei). Die Anwendung besteht eigentlich aus herkömmlichen Seitenwechseln, und so musste der Baum auch seinen Zustand halten können. Nundenn, nach laaaaaanger Probierephase (die Dokumentation ist nicht gerade erschlagend) und der Hilfe vom Web wurde es dennoch Realität *freu*.

Hilfreiche Websites:

Tipps generell:

  • var djConfig = {isDebug: true }; aktiviert die Debugeingaben, dojo.debug(‚Blah‘); kann für Debugausgaben benutzt werden.
  • saveExpandedIndices und restoreExpandedIndices sind methoden des erweiterten tree-Controllers! Er muss also require’d und gemixint werden (siehe unten)
  • Der Baumzustand wird in einem Cookie gespeichert, damit hat man in der Applikation nichts mehr damit zu tun.

Wie das Ganze in Symfony integriert wurde kann gerne nachgefragt werden.

Nundenn, gimme Code:

<script type="text/javascript" src="/js/dojo.js"></script>
<script type="text/javascript">
var djConfig = {isDebug: true }; // Comment if debugguing
dojo.require("dojo.widget.Tree");
dojo.require("dojo.widget.TreeSelector");
dojo.require("dojo.widget.TreeNode");
dojo.require("dojo.widget.TreeContextMenu");
dojo.require("dojo.widget.TreeLoadingController");
dojo.require("dojo.widget.TreeControllerExtension");
// Do something if a node is clicked
function modulTreeSelectFired() {
    var treeSelector = dojo.widget.manager.getWidgetById('modulTreeSelector');
    var treeNode = treeSelector.selectedNode;

    < !get a reference to the songDisplay div –>
    //var hostDiv = document.getElementById("songDisplay");

    var isFolder = treeNode['isFolder'];
    if ( !isFolder) {
       //var song = treeNode['title']
       //hostDiv.innerHTML = "You clicked on "+song;
    } else {
       //hostDiv.innerHTHML = "";
    }
    //hostDiv.style.display = "";
}

// Set up Dojo and the tree
function init() {
    var treeSelector = dojo.widget.manager.getWidgetById('modulTreeSelector');
    dojo.event.connect(treeSelector,'select','modulTreeSelectFired');

    var modulTree = dojo.widget.manager.getWidgetById('modulTree');
    dojo.event.topic.subscribe(modulTree.eventNames.collapse, "saveExpandedIndices");
    dojo.event.topic.subscribe(modulTree.eventNames.expand, "saveExpandedIndices");

    // add extensions to controller
    dojo.lang.mixin(dojo.widget.byId('modulTreeController'), dojo.widget.TreeControllerExtension.prototype);

    // Restore old state
    restoreExpandedIndices();
 }

// Save the tree state
function saveExpandedIndices() {
        // You can save this object as tree persistent state
        indices = dojo.widget.byId('modulTreeController').saveExpandedIndices(
            dojo.widget.byId('modulTree')
        );
        var flatIndices = dojo.json.serialize(indices);
        //dojo.debug(flatIndices);
        dojo.io.cookie.setCookie('modulTree/saveindices',flatIndices, 365, null, null, null);
    }

// Restore the tree state
function restoreExpandedIndices() {
        flatIndices = dojo.io.cookie.getCookie('modulTree/saveindices');
        indices = dojo.json.evalJson(flatIndices);
        //dojo.debug(flatIndices)
        if(indices) {
            dojo.widget.byId('modulTreeController').restoreExpandedIndices(dojo.widget.byId('modulTree'), indices
            );
        }
    }

// Initialize
dojo.addOnLoad(init);</script>

Der HTML-Code sollte eigentlich klar sein: Controller, Kontextmenü und Tree aufbauen (nur die erste Hierarchiestufe) und der PHP-Teil ist nach Vorgabe. Sind mehr Details gewünscht, liefere ich diese gerne nach.

Stichworte: persistent tree state, remembering tree state

Ergänzung, Juli 2008: Oky, Ihr habt recht: Gimme Code! Schwatzen kann jeder!

Hier mal den Quellcode (eher das Gerüst, bzw proof-of-concept 🙂 ) des Moduls und den Inhalt des web/js Verzeichnisses. Hilft das was? Braucht es mehr, damit es sinnvoll ist?

Geschrieben wurde es mit Symfony 1.0 und Dojo 0.4 Rev: 6258.

In der Zwischenzeit bin ich aber von dem Dojo-Ajax Zeux wieder etwas weggekommen. Ich progge für Intra- und Extranetapplikationen, und da sind zu viele Browser mit zu vielen Konfigurationen im Einsatz. Für „öffentliche“ Websites ist es sicher schön, auch wenn es (wie bei Symfony üblich) nicht voll Ajax ist, sondern halt nur Komponentenweise, was ich nicht als schön empfinde…

Ich bin zurück zum guten alten PHP-Layersmenü (Beitrag dazu folgt noch). Das fallbackt sehr schön ohne Java Script.