Geklaute Blumen

Da war da noch diese Anekdote die mir die Mama vor kurzem erzählt, ich selbst kann mich nicht mehr daran erinnern:

Der Blumen-Vorfall

Es war irgendwann in den 1980ern, ich war ggf. so zwischen 8 und 12 Jahre alt, schätze ich, als ich meine Verwandtschaft in Bad Häring (in Tirol, Österreich, bei Kufstein) besucht hatte.

Da war ich mit meiner Cousine, der Doris, unterwegs gewesen.

Wir haben uns entschlossen, meine Tante zu besuchen, die auch im Ort gewohnt hat (inzwischen ist sie leider verstorben).

Da wir der Tante was Gutes tun wollten, haben wir ihr ein paar Blumen mitbringen wollen. Leider fehlte uns das Geld für Blumen.

Wir taten, was getan werden musste und haben kurzerhand heimlich einige Blumen aus dem Garten eben jender Tante die wir besuchen wollten entwendet.

Nun hatten wir Blumen.

Da wir auch ein Eis wollten, aber wie erwähnt kein Geld, kamen wir auf die originelle Idee, der Tante die Blumen nicht nur zu schenken, sondern zu verkaufen!

Das haben wir dann auch getan. Blumen geklaut, bei der Tante geklingelt, die Blumen verkaufen, Tante hat sich gefreut, wir haben Geld für Eis gehabt.

Herrlich!

Epilog

Ja, so war das damals, als die Welt noch (fast) in Ordnung war.

Ich selbst kann mich an die Geschichte nicht mehr erinnern; meine Mama konnte mir glaubhaft versichern, dass es sich so zugetragen hat. Zutrauen würde ich mir das auf jeden Fall!

WordPress-404-Meldungen bei geÄnderten Weblog-Posting-URLs abfangen

Heute liebe Kinder erzähle ich Euch was, von dem ich wenig Ahnung habe. Also eigentlich wie immer.

Vor kurzem habe ich aus SEO-Gründen meine Weblog-Posting-URLs von Ziffern auf sprechende URLs umgestellt.

Mein Weblog hier läuft mit WordPress. Die Umstellung ging sehr gut über „Dean’s Permalinks Migration 1.0„. Trotzdem habe ich noch ein paar Umlaute umgestellt teilweise, was zunächst so aussah als ob es funktioniert, ein paar Tage später aber doch zu 404-Fehlermeldungen geführt hat.

Und in den Google Webmaster Tools wurden mir gleich mal 481 tote Hyperlinks angezeigt. Hilfe!

Uwe arbeitet in PHP!

Nach ein Bisschen Recherche, erfolglosen mod_rewrite-Arbeiten und ein Bisschen PHP-Auffrischungen habe ich mich dazu entschlossen die „functions.php“-Datei in meinem Theme anzupassen.

Also die Datei die in „wp-content/themes/ThemeName/functions.php“ liegt.

Meine Idee war, bei einem Hook einzusteigen und dann ganz quick-and-dirty String-Ersetzungen zu machen und dann eine HTTP-301-Anwort zu senden.

Das hat gut geklappt. Falls Ihr das auch mal benötigt, hier der Ausschnitt aus der „functions.php“:

add_action('template_redirect', 'check_for_404');

function check_for_404()
{
  if ( is_404() )
  {
    checkRedirectSubstringReplace("%c2%abwo-genau-liegt-denn-dieses-internet%c2%bb", "wo-genau-liegt-denn-dieses-internet");
    checkRedirectSubstringReplace("ein-erster-test-naturlich", "ein-erster-test-natuerlich");
    checkRedirectSubstringReplace("noch-12-tage-20-rabatt", "noch-12-tage-20-prozent-rabatt" );
    checkRedirectSubstringReplace("40%C2%B0-fieber", "40-grad-fieber" );
    checkRedirectSubstringReplace("fuchs-uberfahren", "fuchs-ueberfahren" );
    checkRedirectSubstringReplace("skript-programmierer-job-nahe-stuttgart", "skript-programmierer-job-naehe-stuttgart" );
    checkRedirectSubstringReplace("krane-vorm-businesshaus", "kraene-vorm-businesshaus" );
    checkRedirectSubstringReplace("fremdworter", "fremdwoerter" );
    checkRedirectSubstringReplace("ich-bin-beruhmt", "ich-bin-beruehmt" );
    checkRedirectSubstringReplace("zotter-schokolade-in-goppingen", "zotter-schokolade-in-goeppingen" );
    checkRedirectSubstringReplace("gunther-sucht-was", "guenther-sucht-was" );
    checkRedirectSubstringReplace("schoner-parken", "schoener-parken" );
    checkRedirectSubstringReplace("16%C2%B0-c", "minus-16-grad-celcius" );
    checkRedirectSubstringReplace("minus-17-%c2%b0c", "minus-17-grad-celcius");
    checkRedirectSubstringReplace("werbemull-report-vom-12-marz-2010", "werbemuell-report-vom-12-marz-2010" );
    checkRedirectSubstringReplace("phanomene-bei-vollem-internet-explorer-cache", "phaenomene-bei-vollem-internet-explorer-cache" );
    checkRedirectSubstringReplace("offnungszeiten-rathaus-eislingen", "oeffnungszeiten-rathaus-eislingen" );
    checkRedirectSubstringReplace("das-langste-wort-der-welt", "das-laengste-wort-der-welt" );
    checkRedirectSubstringReplace("das-windows-forms-net-aquivalent-zu-spy", "das-windows-forms-net-aequivalent-zu-spy" );
    checkRedirectSubstringReplace("ausflug-zur-ph%C3%A6no-nach-wolfsburg", "ausflug-zur-phaeno-nach-wolfsburg" );
    checkRedirectSubstringReplace("%C2%ABwer-magerquark-hat-hat-die-welt-verstanden%C2%BB", "wer-magerquark-hat-hat-die-welt-verstanden" );
    checkRedirectSubstringReplace("uberprufen-ob-eine-website-komprimiert-ist", "ueberpruefen-ob-eine-website-komprimiert-ist" );
    checkRedirectSubstringReplace("ueberprufen-ob-eine-website-komprimiert-ist", "ueberpruefen-ob-eine-website-komprimiert-ist" );
    checkRedirectSubstringReplace("werbemull-report-vom-21-marz-2010", "werbemuell-report-vom-21-marz-2010" );
    checkRedirectSubstringReplace("zeta-producer-desktop-8---beta-version-verfugbar", "zeta-producer-desktop-8-beta-version-verfuegbar" );
    checkRedirectSubstringReplace("zeta-producer-desktop-8-%E2%80%92-beta-version-verfugbar", "zeta-producer-desktop-8-beta-version-verfugbar" );

    checkRedirectFull("archive/1002215", "/" );
    checkRedirectFull("archive/592", "/" );
    checkRedirectFull("index.php/archive", "/" );
    checkRedirectFull("index.php/page", "/" );
    checkRedirectFull("archive/date", "/" );

    checkRedirectFull("archive/388", "/" );
    checkRedirectFull("/archive", "/" );
  }
}

function doesMatchSubStringUrl( $urlMatch )
{
  $url = $_SERVER["REQUEST_URI"];

  if( stripos($url, $urlMatch)===false )
  {
    return false;
  }
  else
  {
    return true;
  }
}

function checkRedirectFull( $old, $new )
{
  if ( doesMatchSubStringUrl( $old )===true )
  {
    header('HTTP/1.1 301 Moved Permanently');
    header('Location: ' . $new);
    exit;
  }
}

function checkRedirectSubstringReplace( $old, $new )
{
  if ( doesMatchSubStringUrl( $old )===true )
  {
    $new_url = str_replace($_SERVER["REQUEST_URI"], $old, $new);

    header('HTTP/1.1 301 Moved Permanently');
    header('Location: ' . $new_url);
    exit;
  }
}

Frage an alle PHP-WordPress-Gurus da draußen: gibt es einen klevereren Weg?

Ressourcen

Folgende Infos haben mir geholfen:

STUN – „Session Traversal Utilities for NAT“, TURN und ICE

Vor kurzem haben wir mit einem Kunden diskutiert wie denn Tools wie Skype oder TeamViewer auf Netzwerkebene funktionieren.

Nun, Skype artbeiten mit dem sogenannten „Session Traversal Utilities for NAT“ (STUN), ebenso wie Microsoft Windows Live Messenger; ich gehe davon aus, dass es TeamViewer ähnlich macht, ggf. noch ergänzt durch weitere ähnliche Protokolle/Verfahren.

Neben STUN erwähnt der Wikipedia-Artikel noch das verwandte Protokoll „Traversal Using Relay NAT“ (TURN) und die verwandte Technik „Interactive Connectivity Establishment“ (ICE).

Auch für Peer-to-Peer-Netzwerke (P2P) gibt es entsprechende Techniken wie z.B. „UDP Hole Punching“ oder „TCP Hole Punching“ um via NAT zu funktionieren.

Implementierungen

Alles eine spannende Sache, und inzwischen auch gut dokumentiert.

Ich selbst habe früher während des Studiums und der Diplomarbeit auch viel mit Sockets gearbeitet, kleine Übungs-Chats programmiert oder mal einen HTTP-Server. Natürlich nie über Firewalls und NAT hinweg, das war damals zum Glück nie nötig.

Zurück zum Thema.

Ich habe eine Weile rumgesucht, ob es Implementationen von STUN in .NET gibt. Tatsächlich gibt es ein paar davon, z.B. einen CodeProject-Artikel für einen STUN-Client und einen SIP-Stack-Artikel (VoIP) vom selben Autor.

STUN/TURN/ICE für Windows Communication Foundation (WCF)?

Was ich leider nicht gefunden habe ist eine Implementation von STUN, TURN oder ICE für Microsofts Windows Communication Foundation (WCF).

Ich bin noch recht neu mit WCF, denke aber dass es technisch schon möglich und vor allem sinnvoll ist, zusätzlich zu HTTP und Co. auch noch STUN als zuschaltbares Protokoll zur Verfügung zu haben.

Ggf. gibt’s da ja doch was?

Update 2011-04-23

Für HTTP habe ich was interessantes gefunden: „Comet“ als Überbegriff für verschiedenen Techniken im HTTP-Bereich um vom Server Benachrichtigungen zum Client zu senden. Namentlich z.B. auch „Ajax Push“ oder „HTTP Server Push“ genannt. Interessant! (Gibt’s auch für ASP.NET)

Telefonsex

Da war da noch die folgende Anekdote:

In den 80er-Jahren war ich im Mörike Gymnasium Göppingen auf der Schule. (25 Mädchen und 6 Jungs pro Klasse, das hat mich geprägt, ist aber eine andere Geschichte).

Auf unserem Schulweg kamen wir täglich am Apostel-Hotel in Göppingen vorbei.

Deshalb lag es auch nahe, als wir, damals vielleicht so 12-13 Jahre, bei unseren unregelmäßigen Telefonstreichen eines Tages bei einer Telefonsexnummer anriefen und die Dame baten:

„Bitte kommen Sie ins Apostel-Hotel“.

Naiverweise dachten wir, das funktioniert halt so. Zum Glück wurden wir dann umgehend aufgeklärt:

„Hey Süßer, ich mach nur Telefonsex, keine Hausbesuche“.

Somit war das von da an auch ein für alle Mal geklärt.

XML-Fehlermeldung „Hexadezimaler Wert ist ein ungültiges Zeichen“

Fehlerbeschreibung

Beim Lesen von XML-Dokumenten in .NET mit dem XmlDocument oder dem XmlReader oder XmlTextReader kommt eine Fehlermeldung in der Form:

System.Xml.XmlException

—————-

‚‘, hexidezimaler Wert 0x1F, ist ein ungültiges Zeichen. Zeile 10, Position 13.

—————-

bei System.Xml.XmlTextReaderImpl.Throw(Exception e)
bei System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
bei System.Xml.XmlTextReaderImpl.Throw(Int32 pos, String res, String[] args)
bei System.Xml.XmlTextReaderImpl.ThrowInvalidChar(Int32 pos, Char invChar)
bei System.Xml.XmlTextReaderImpl.ParseCDataOrComment(XmlNodeType type, Int32& outStartPos, Int32& outEndPos)
bei System.Xml.XmlTextReaderImpl.ParseCDataOrComment(XmlNodeType type)
bei System.Xml.XmlTextReaderImpl.ParseElementContent()
bei System.Xml.XmlTextReaderImpl.Read()
bei System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace)
bei System.Xml.XmlLoader.LoadDocSequence(XmlDocument parentDoc)
bei System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace)
bei System.Xml.XmlDocument.Load(XmlReader reader)
bei System.Xml.XmlDocument.LoadXml(String xml)

Bzw. in Englisch:

System.Xml.XmlException

—————–

“, hexadecimal value 0x02, is an invalid character. Line 19, position 5.

—————–

at System.Xml.XmlTextReaderImpl.Throw(Exception e)
at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
at System.Xml.XmlTextReaderImpl.ThrowInvalidChar(Int32 pos, Char invChar)
at System.Xml.XmlTextReaderImpl.ParseCDataOrComment(XmlNodeType type, Int32& outStartPos, Int32& outEndPos)
at System.Xml.XmlTextReaderImpl.ParseCDataOrComment(XmlNodeType type)
at System.Xml.XmlTextReaderImpl.ParseElementContent()
at System.Xml.XmlTextReaderImpl.Read()
at System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace)
at System.Xml.XmlLoader.LoadDocSequence(XmlDocument parentDoc)
at System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace)
at System.Xml.XmlDocument.Load(XmlReader reader)
at System.Xml.XmlDocument.LoadXml(String xml)

Ursache

Es ist wohl tatsächlich ein für den XML-Parser ungültiges Zeichen enthalten.

Wie das Zeichen jeweils herein kam ist mir unbekannt. Da ich den XML-Text jeweils selbst auch wegschreibe (basierend auf einem gehosteten Internet Explorer im Design-Modus), gehe ich davon aus, dass aus dem Internet Explorer ungültige Zeichen „durchrutschen“.

Eigentlich hätte ich erwartet dass beim Generieren des XML-Dokuments die .NET-XML-Routinen ungültige Zeichen bereits herausfiltern bzw. korrekt escapen.

Lösung

Auf dieser Website wird als Lösung beschrieben, die ungültigen Zeichen zu entfernen.

Mithilfe eines Regulären Ausdrucks habe ich das dann gemacht. Hier ist ein Code-Ausschnitt:

private static Regex _rx;

public static string CleanInvalidXmlCharacters(
string xml)
{
if (string.IsNullOrEmpty(xml))
{
return xml;
}
else
{
if (_rx == null)
{
//
http://www.discogs.com/help/forums/topic/189304
//
http://meinews.niuz.biz/regex-t248608.html?s=3e8c40fe6d48a1a36e66437f3a3ec944&
_rx = new Regex(@“[\x01-\x08\x0B\x0C\x0E-\x1F]“, RegexOptions.Compiled);
}

return _rx.Replace(xml, string.Empty);
}
}

Diese Funktion rufe ich jeweils auf, bevor ich eine Zeichenfolge an die XmlDocument.Load( string xml )-Methode übergebe.

Ich bin mir unsicher ob das die perfekte Lösung ist; sie funktioniert (übrigens in dem Programm hier) und scheint keine Seiteneffekte zu haben.

Andere Meinungen dazu?

Check whether Browsable attribute is set to TRUE

To check, whether an element in .NET has set the browsable attribute, use the following code:

public static bool HasBrowsableAttributeTrue(
    object o )
{
    if ( o==null )
    {
        return false;
    }
    else
    {
        var fi = o.GetType().GetField( o.ToString() );

        var attributes =
            (BrowsableAttribute[])fi.GetCustomAttributes(
                typeof( BrowsableAttribute ),
                false );

        if ( attributes != null && attributes.Length > 0 )
        {
            return attributes[0].Browsable;
        }
        else
        {
            return false;
        }
    }
}

This works even if the object is null.