Tag Javascript

  • Carl Württemberg und die unvollständige Revolution

    Immer noch bin ich mit meinem Museumspass unterwegs, doch nicht immer sind es die Gegenstände in den Ausstellungen, die die tiefsten Eindrücke hinterlassen. So war das neulich auch im württembergischen Landesmuseum (vor dem Klicken ggf. einen Blick auf ach so: die Webseite werfen) in Stuttgart. Vorausgeschickt: zwischen dem ehemaligen Kuriositätenkabinett der Potentaten des Schwabenlandes und allerlei keltischen Schätzen gibts da durchaus viel Beeindruckendes oder zumindest, tja, Kurioses.

    Was nicht bleiben sollte

    Den erwähnten tiefsten Eindruck hinterließ jedoch die Wand neben dem Ausgang. Dort ist nämlich eine dieser Mahntafeln zur Privatisierung gesellschaftlicher Aufgaben[1] angebracht, auf denen Menschen zur Dankbarkeit für allerlei Personen und Firmen aufgerufen werden („Logos“):

    Auf einer braunen Wand angebrachte weiße Schilder mit diversen Logos unter der Überschrift „Exclusive Partners“ („Wüstenrot-Stiftung“, „Bosch“).  Ganz vorne jedoch ein Wappen mit Krönchen und allem, darunter „Carl Herzog von Württemberg“.

    So traurig diese Beflimmerung light schon im Allgemeinen ist: dass da ein Nachkomme der alten Autokraten und Gewaltherrscher sein ultrakitschiges Herrschaftszeichen mit Krone und Spruchband („Furchtlos und treu“ – hatten wir nicht schon mehr als genug Sprüche mit „Treue“ während der letzten hundert Jahre?) im öffentlichen Raum – und dann noch ganz vorne – platzieren darf, das ist klar ein Symptom einer Demokratisierung, die allzu früh steckengeblieben ist.

    Nun will ich mich nicht beschweren, dass die bis dann herrschenden Schurken 1918 ihre Köpfe behalten durften. Keine Frage, der Umgang mit Louis XVI im Jahr 1793 war selbstverständlich ein schlimmer Verstoß gegen das Prinzip RiwaFiw („Radikalität ist wichtig, aber Freundlichkeit ist wichtiger“).

    Dass aber die ausgehenden FürstenInnen einen Gutteil der Dinge, die sie sich mit Feuer und Schwert von ihren Untertanen geholt hatten, behalten durften und ihnen das jetzt die Mittel gibt, Duftmarken und vermutlich auch Agenden in unseren Museen zu setzen, das ist auch ein schlimmer Verstoß gegen RiwaFiw. Die damaligen Gesetzgeber haben allzu radikal am Prinzip von Eigentum festgehalten, statt sich an einer ganz schlichten, intuitiven und freundlichen Ethik zu orientieren. Sagen wir: „was du bekommst, indem du Menschen quälst, soll zumindest diesen Menschen wieder nutzen, wenn dir die Gewaltmittel ausgehen, um deine Ansprüche durchzusetzen“.

    In Sachen Schaden und Spott tritt dazu die weitere Alimentierung der Nachfahren der alten Obrigkeiten, so (vermutlich auch im württembergischen Landesmuseum) durch den „Ankauf“ von Plunder, der bereits 1918 hätte sozialisiert werden müssen.

    Besonders auffällig war das im Zusammenhang mit Carl Württembergs Spießgesellen aus dem anderen Landesteil, stark archaisierend „Haus Baden“ genannt, das vor rund dreißig Jahren mit Teilen seines Tafelsilbers versuchte, das Land zu erpressen. Einen kurzen Eindruck der damaligen Debatten gibt ein Blogpost von 2006 zu diesem Thema, in dem einige FAZ-Passagen illustrieren, weshalb das weitere Gebiet der durchlauchtigen Besitztümer auch außerhalb des württembergischen Landesmuseums für schlechte Laune sorgt; diese Geschichte geht übrigens immer noch weiter, wie weitere Beiträge auf dem Blog aus dem letzten Jahrzehnt belegen.

    Ach so: Die Webseite

    Ich kann diesesn Post nicht ohne einen scharfen Themenbruch beenden. Als ich nämlich oben aufs württembergische Landesmuseum verlinkt habe, habe ich selbstverständlich erstmal nachgesehen, was dort eigentlich steht und sah: Nichts. Alles weiß. Dabei hat die Seite eine Crapicity von gerade mal 6 (also: nur 5 Byte Digitalsoße auf 1 Byte lesbaren Text), was für kommerziell produzierte Webseiten in der heutigen Zeit an sich recht ordentlich ist.

    Das hat mich milde neugierig gemacht, warum die Anzeige trotzdem so vergurkt ist. Was soll ich sagen? Es ist etwas unbedachtes CSS, das den Kram hier kaputt macht – eingestanden natürlich nur in Verbindung mit der üblichen Javascriptitis, ohne die die Zuständigen gleich selbst gesehen hätten, dass was kaputt ist. Auf reinen Textbrowsern wie wie w3m ist die Seite ordentlich nutzbar. Dillo mit seiner reduzierten CSS-Interpretation liefert ebenfalls ein brauchbares Rendering.

    Immerhin, die Museumsleute haben offenbar ein gewisses Problembewusstsein im Hinblick darauf, dass die „großen“ Browser ihren Inhalt weiß auf weiß darstellen. Auf der Erklärung zur Barrierefreiheit des Museums heißt es nämlich:

    Einige der Helligkeitskontraste sind zu klein und damit schlecht wahrnehmbar.

    Das ist nicht falsch. Allerdings netto doch einen Hauch euphemistisch:

    Screenshot eines Browserfensters für www.landesmuseum-stuttgart.de: Alles ist weiß
    [1]Nun, eigentlich sind das Mahnmale der unzureichenden Besteuerung von Unternehmen und Reichen; denn offensichtlich haben die Mäzene Geld, das sie genauso gut der Gesellschaft zur Verfügung stellen (also als Steuer zahlen) könnten, mit dem sie sich jetzt aber privaten Einfluss auf öffentliche Museen kaufen. Und immerhin: hatte mensch sie ordentlich besteuert, könnten sie die Mussen nicht zwingen, ihre (also: der Museen, nicht der Mäzene) BesucherInnen mit doofen Logos und Wappen zu belästigen.
  • Saner Timestamps With DIT: In Pelican and Beyond

    The other day Randall Munroe posted XKCD 2867:

    This lament about time calculus struck me as something of a weird (pun alarm) synchronicity, as one evening or two before that I had written a few lines of flamboyant time-related code.

    Admittedly, I was neither concerned with “sin to ask” nor with „impossible to know“: Both are a consequence of the theory of relativity, which literally states that (against Newton) there is no absolute time and hence when two clocks are in two different places, even synchronising them once is deep science.

    Sold on Decimal Internet Time

    No, my coding was exclusively about the entirely unnecessary trouble of having to account for time zones, daylight savings time, factors of 60, 24, sometimes 30, 31, 29, or 28, and quite a few other entirely avoidable warts in our time notation. Civil time on Earth is not complicated because of physics. On human scales of time, space, velocity, gravitation, and precision, it is not particularly hard to define an absolute time even though it physically does not exist.

    Rather, civil time calculations are difficult because of the (pun alarm) Byzantine legacy from Babylon – base-60 and base-12, seven-day weeks, moon calendar – exacerbated by misguided attempts of patching that legacy up for the railway age (as in: starting in 1840, by and large done about 1920). On top of that, these patches don't work particularly well even for rail travel. I speak from recent experience in this particular matter.

    Against this backdrop I was almost instantly sold on DIT, the Decimal Internet Time apparently derived from a plan a person named Anarkat (the Mastodon link on the spec page is gone now) proposed: Basically, you divide the common day in what currently is the time zone UTC-12 into 10 parts and write the result in decimal. Call the integer part “Dek” and the first two digits after the dot “Sim”. That's a globally valid timestamp precise to about a (Babylonian) minute. For example, in central Europe what's now 14:30 (or 15:30 during daylight savings time; sigh!) would be 0.62 in DIT, and so would Babylonian 13:30 in the UK or 8:30 in Boston, Mass. This may look like a trivial simplification, but makes a universe of a difference in how much less painful time calculations become.

    I admit I'd much rather have based time keeping on the second (the SI unit of time), but I have to give Anarkat that the day is much more important in most people's lives than the second. Thus, their plan obviously is a lot saner for human use than any I would have come up with (“let's call the kilosecond kes and use that instead of an hour…”)[1].

    If you use pelican…

    Since I think that this would be a noticeably better world if we adopted DIT (clearly, in a grassrootsy step-by-step process), I'd like to do a bit of propaganda for it. Well, a tiny bit perhaps, but I am now giving the timestamps of the posts on this blog in StarDIT, which is an extension of DIT where you count the days in a (Gregorian, UTC-12) year and number the years from the “Holocene epoch”, which technically means “prepend a one to the Gregorian year number“ (in other words, add 10'000 to “AD”).

    Like DIT itself, with sufficient adoption StarDIT would make some people's lives significantly simpler, in this case in particular historians (no year 0 problem any more!). I would like that a lot, too, as all that talk about “Domini” doesn't quite cater to my enlightened tastes.

    How do I do produce the starDITs? Well, I first wrote a rather trivial extension for my blog engine, pelican, which adds an attribute starDIT to posts. You will find it as ditdate.py in my pelican plugins repo on codeberg. Activate it by copying the file into your blog's plugins directory and adding "ditdate" to the PLUGINS list in your pelicanconf.py. You can then use the new attribute in your templates. In mine, there is something like:

    <a href="http://blog.tfiu.de/mach-mit-bei-dit.html">DIT</a>
    <abbr class="StarDIT">{{ article.starDIT[:-4] }}</abbr>
    (<abbr class="date">{{ article.date.strftime("%Y-%m-%d") }}</abbr>)
    

    If you don't use pelican…

    I have also written a Python module to convert between datetimes and DITs which shows a Tkinter UI when called as a program:

    A small grey window on top of some bright background; sans-serif letters say 12023:351 (small) 1.08.5 (large).

    I have that on my desktop now. And since alarmingly many people these days use a web browser as their primary execution platform, I have also written some HTML/Javascript to have the DIT on a web page and its title (also hosted here).

    Both of these things are in my dit-py repo on codeberg, available under CC0: Do with them whatever you want. (Almost) anything furthering the the cause of DIT is – or so I think I have argued above – very likely progress overall.

    [1]If you speak German or trust automatic translation, I have a longer elaboration of DIT aspects I don't like in a previous blogpost.
  • Mach mit bei DIT

    [In case you're coming here from an English-language article, see here]

    A small grey window on top of some bright background; sans-serif letters say 12023:351 (small) 1.08.5 (large).

    Hier zeigt meine DIT-Uhr die Zeit (und das Datum) in meinem sawfish-Dock. Nein, das ist kein Startrek-Unfug. Ich hoffe stattdessen, dass etwas in dieser Art im Laufe der Zeit zum In-Accessoire werden wird: Wer keins hat, darf nicht mehr Digitalisierung sagen [nun: glücklicherweise hat niemand, der_die sowas wollen könnte, Mittel, mit denen so ein Verbot durchzusetzen wäre].

    Heraus aus der babylonischen Verwirrung!

    Es gibt nach 3000 Jahren nicht mehr allzu viele Gründe, sauer auf die großen KriegsherrInnen aus Babylon und ihre mesopotamischen KollegInnen zu sein. Mit dem babylonischen Klerus sieht das anders aus: Nicht nur sexagesimale Koordinaten etwa in der Astronomie geht auf ihn zurück, sondern auch all der krumme Kram mit Faktoren von 60 oder 24 oder 7, mit dem wir uns völlig ohne Not[1] immer noch in der Zeitrechnung herumschlagen.

    Keine Schuld haben die mesopotamischen PriesterInnen am Ärgernis Zeitzonen und dem damit zusammenhängenden Sommerzeit-Elend, aber ich wollte auch die schon ewig loswerden, nicht nur wie neulich aus Betroffenheit.

    So hat die Decimal Internet Time (DIT) mein Herz (fast) im Sturm genommen, ein Vorschlag, die Zeit durch Zehnteln des Tages zu notieren. Dieser Stundenersatz heißt Dek (von Dekatag) und entspricht fast zweieinhalb (nämlich 24/10) babylonischen Stunden.

    Selbst für sehr grobe Zeitangaben werden Deks in der Regel nicht reichen, weshalb sie in hundert Sims (von Decimal Minute) aufgeteilt werden. So ein Sim entspricht 86 Sekunden, ist also ziemlich nahe an einer babylonischen Minute. Das wäre wohl so die Einheit für Verabredungen: „Mittagessen um neun komma fünfundsiebzig“ oder meinetwegen „fünfungzwanzig vor null“, denn um die 100 Sekunden warten sollten für niemand ein Problem sein, und viel genauer fährt die Bahn nicht mal in der Schweiz. Aber weils Dezimal ist, wärs auch kein Problem, einfach nach den Zehnern aufzuhören: „Ich breche dann um 7.8 auf“, eine Angabe, die etwa auf eine Viertelstunde genau ist – sehr menschengemäß in meinem Buch.

    Ich finde das total plausibel; wenn euch das demgegenüber komisch vorkommt, ist das, ich muss es euch sagen, sehr parallel zur Abneigung von in imperialen Einheiten aufgewachsenen Leuten, etwas wie „ein Meter Fünfundachtzig“ zu sagen, wo doch „six foot two inches“ soo viel intuitiver ist.

    Um ein Gefühl für die Dezimalzeit zu bekommen, hätte ich folgende Kurzreferenz für BRD-Gewohnheiten anzubieten:

    DIT MEZ in Worten
    0 Mittag (13:00)
    1.5 Nachmittag (~16:30)
    2 Früher Abend (~18:00)
    3 Abend (20:00)
    4.5 Mitternacht
    6 Unchristliche Zeit (3:30)
    7.5 Morgen (7:00)
    9 Vormittag (10:30)

    Deseks: Vielleicht nicht so nützlich

    Weniger begeistert bin ich von der kleinsten Zeiteinheit von DIT, der Dezimalsekunde, Desek oder kurz Sek; das ist ein Tag/100'000, gegenüber einem Tag/86'400 bei der SI-Sekunde.

    Als SI-Taliban hätte ich die ganze dezimale Zeitrechnung ja ohnehin lieber auf die Sekunde aufgebaut und die Kilosekunde (ungefähr eine Viertelstunde) als Stundenersatz etabliert. Zwar gebe ich zu, dass die DIT-Wahl des Bezugs auf den Tag für menschliche Nutzung ein besserer Plan ist als die Kilosekunde (von der es 86.4 in einem Tag gibt, was eingestandenermaßen blöd ist).

    Aber für rein menschliche Nutzung (Verabredungen, Tagesplan, Fahrpläne…) spielen Zeiten im Sekundenbereich in der Regel keine Rolle, und so hätte ich die Deseks einfach rausgelassen und gesagt: Wers genauer braucht, soll zu den PhysikerInnen gehen und von denen die Sekunde nehmen. Dass ein Sim ziemlich genau aus 86.4 von diesen SI-Sekunden besteht, ist eher eine putzige Kuriosität als eine praktische Schwierigkeit, und jedenfalls nicht nennenswert lästiger als die 60 Sekunden, die eine babylonische Minute hat.

    Und nein, die physikalische Sekunde als Tag/100,000 umzudefinieren lohnt den Aufwand nicht; dafür ist die Erdrotation längst zu ungenau, und dann wir wollen ohnehin den Schaltsekunden-Unfug nicht mehr. Die Sekunde ist Physik, die braucht nichts mit menschlichen Zeiten zu tun zu haben. Insofern: Es wäre schöner, wenn es keine Desek gäbe, aber ich will auch nicht streiten.

    Good riddance, Zeitzonen

    Der neben der Nutzung des Dezimalsystems zweite große Fortschritt von DIT ist, dass sie auf der ganzen Welt einheitlich verläuft. Es gibt also in DIT keine Zeitzonen mehr.

    Mehr nebenbei ist das so gemacht, dass das babylonische 12 Uhr, die Mittagszeit bzw. 5 Deks in DIT, in der aktuellen UTC-12-Zeitzone (der „frühesten“, die es gibt), tatsächlich ungefähr mit der Kulmination der Sonne, also einer naiven Mittagsdefinition, zusammenfällt. Aber das spielt – im Gegensatz zum etwas antibritisch klingenden Sentiment in der DIT-Spec – eigentlich keine Rolle. Relevant ist nur, dass DIT-Uhren auf der ganzen Welt den gleichen Wert anzeigen. Ich darf meine Fantasie von neulich für DIT aktualisieren:

    Wäre es wirklich ein Problem, wenn Menschen, die in Kasachstan leben, 2 Deks für eine gute Zeit fürs Mittagessen halten würden und sich die Leute in New York eher so gegen siebeneinalb Deks über ihres hermachten? Ich wette, alle würden sich schnell dran gewöhnen. Es ist jedenfalls einfacher als das Sommerzeit-Mantra „spring forward, fall back“.

    Eingestanden: Wenn ich DIT entworfen hätte, hätte ich die auf die Referenz 12 babylonische Stunden entfernt von UTC verzichtet, denn alle anständigen Zeitstempel sind bereits jetzt in UTC. Wenn mensch für die DIT davon weggeht, verschränken sich Datum und Zeit bei der Umrechnung dieser anständigen Zeitstempel zu DIT – beim Übergang von babylonischer Zeit zu DIT kann sich also auch das Datum ändern.

    Das ist eine Komplikation, die keinen erkennbaren Nutzen hat; es ist eben kein Privileg, dass die Sonne um 5 Deks kulminiert, und so ist der Versuch albern, dabei möglichst wenige Menschen „zu bevorzugen“. Aber seis drum.

    Das Datum zur Zeit: StarDIT

    Insbesondere spielt das keine Rolle mehr, wenn mensch auch das Datum in DIT schreibt. Dazu gibt es eine Erweiterung von DIT zu größeren Zeiträumen hin, die im Vorschlag StarDIT genannt wird. Ob die Gesellschaft schon durchnerdet genug ist, um mit so einem Namen durchzukommen? Weiß nicht.

    An sich ist, wo wir schon bei Namen sind, ja auch das I, „Internet“, in DIT nicht so richtig seriös. Ich würde es vielleicht lieber als „International“ lesen – Internationalismus ist und bleibt einer der sympathischeren Ismen.

    Im StarDIT-Plan jedenfalls besteht das Datum aus (gregorianischem) Jahr zu einer leicht entchristlichten Epoche sowie der laufenden Tagesnummer innerhalb eines Jahres, mit einem Doppelpunkt getrennt, also für heute etwa 12023:350. Wer Wochen haben will, nimmt den Zehneranteil und schreibt ein x dahinter; aktuell haben wir also die Woche 35x.

    Zehntagewochen bergen ein wenig das Risiko, dass aus fünf Arbeitstagen acht werden; ein analoger Effekt hat schon dem Französischen Revolutionskalender (in meiner Geschichtserzählung) den Hals gebrochen. Aber wir müssen ja gerade sowieso über drastische Arbeitszeitverkürzung reden, um irgendwie die immer noch wachsende CO₂-Emission in den Griff zu kriegen. Da könnte der Übergang zu DIT durchaus mit einem Zwischenmodell mit weiterhin fünf Tagen Lohnarbeit, dafür dann auch fünf Tagen Selbstbestimmung („Wochenende“) zusammengehen – bevor die Lohnarbeit weiter abnimmt, natürlich.

    Putzig, wenn auch nicht allzu praktikabel für den Alltag, finde ich die DIT-Idee, die christliche Epoche (zu meinen eigenen Bedenken vgl. Fußnote 1 hier) durchs Holozän-Jahr zu ersetzen. Das ist wie das normale Gregorianische Jahr, nur dass die Zählung 9'999 vdCE anfängt (das heißt: Zählt einfach 10'000 zu ndCE-Jahren dazu).

    Es ist sicher prima, wenn die Leute nicht mehr durch Kennungen wie „v. Chr“ oder „n. Chr“ letztlich fromme Märchen verbreiten, und es ist auch großartig, wenn das Jahr-0-Problem (es gibt nämlich kein Jahr 0: die derzeitige Jahreszählung geht direkt von 1 v. zu 1 n., und drum ist auch die DIT-Referenzepoche etwas krumm) zumindest aus der post-mittelsteinzeitlichen Geschichtsschreibung komplett verschwindet. Ob jedoch das ein Deal ist, wenn mensch dafür mit einer Extraziffer in Jahreszahlen bezahlen muss? Fünf ist, haha, eben nicht zwingend Trümpf.

    Andererseits: Das StarDIT ist trivial aus der gewohnten Jahreszahl auszurechnen, und realistisch würden die Leute auch mit DIT im Alltag wohl weiterhin „Dreiundzwanzig“ oder „Zwanziger Jahre“ sagen und nicht „Zwölftausenddreiundzwanzig“ oder „zwölftausendzwanziger Jahre“. Insofern: Meinen Segen haben sie.

    Implementation: Python und Javascript

    Um nun mit gutem Beispiel voranzugehen, will ich selbst ein Gefühl für DIT bekommen. Dazu habe ich ein Python-Modul geschrieben, das Konversionen von Python-Datetimes von und nach DIT unterstützt. Das ist so wenig Code, dass ich lieber niemand verführen würde, das als Dependency zu importieren. Drum habe ich es auch nicht ins pyPI geschoben; guckt einfach in mein codeberg-Repo. Meine vorgeschlagene Vorgehensweise ist copy-paste (oder halt einfach das Modul in den eigenen Quellbaum packen).

    Das Modul funktioniert auch als Programm; legt es dazu einfach in euren Pfad und macht es ausführbar. Es zeigt dann eine DIT-Uhr in einem Tkinter-Fenster an. Ich habe das in meinen Sawfish-Dock aufgenommen – siehe das Eingangsbild.

    Ich habe außerdem noch ein Stück Javascript geschrieben, das DITs ausrechnen und anzeigen kann. Es ist eingebettet in der Datei dit.html im Repo oder unter https://blog.tfiu.de/media/2023/dit.html erreichbar. Menschen, die (ganz anders als ich) breit Tabs in ihren Browsern nutzen, können die Webseite öffenen und haben mit etwas Glück (wenn der Browser nämlich das Javascript auch …

  • How to Pin a Wifi Access Point in Debian – and Why You Probably Don't Want to in Lufthansa Planes

    A vertical gradient from black to light blue, lots of unfilled template variables in double curly braces in white.

    That's what you see in Lufthansa's onboard wifi when you don't let just about anyone execute client-side Javascript on your machine. See below for a more useful URI in the onboard wifi.

    I have already confessed I was flying recently (albeit only in German). What was new versus the last time I've been in a plane five years ago[1]: Not only did wifi signals apparently no longer confuse the aircraft's navigation systems but there was actually an onboard wifi network with no less than seven access points within my machine's range.

    Somewhat surprisingly, I had a hard time getting a connection that would not break after a few seconds. I'll confess that's not the first time I've had trouble connecting to fancy networks recently, where the syslog contained cryptic messages like:

    kernel: wlan0: deauthenticated from <redacted> (Reason: 252=<unknown>)
    kernel: wlan0: disassociated from <redacted> (Reason: 1=UNSPECIFIED)
    

    In all these cases, there were a lot of access points with the same ESSID around, and so I suspect whatever selects the access points is currently broken on my machine; it chooses really weak access points and then gets badly mangled signals. While I'm waiting for this to heal by itself, I am resorting to manually picking and pinning the access points. In case you use ifupdown to manage your wifi, perhaps this little story is useful for you, too.

    The first part is to pick an access point. To do that, I ignore the warning of the authors of iw (from the eponymous package) not to parse its output and run:

    sudo iw wlan0 scan | egrep "^BSS|signal: .*dBm|SSID:"
    

    Nachtrag (2023-11-02)

    Well, in non-plane situations it's wise to get the SSIDs, too, so you see which APs actually are for the network you want to join. Hence, I've updated the grep in the command line above.

    The output of this looked like this on the plane I was in:

    BSS 00:24:a8:83:37:93(on wlan0)
            signal: -68.00 dBm
    BSS 00:24:a8:ac:1d:93(on wlan0)
            signal: -41.00 dBm
    BSS 00:24:a8:83:37:82(on wlan0)
            signal: -62.00 dBm
    BSS 00:24:a8:ac:1d:82(on wlan0)
            signal: -48.00 dBm
    BSS 00:24:a8:83:37:91(on wlan0)
            signal: -60.00 dBm
    BSS 00:24:a8:83:76:53(on wlan0)
            signal: -76.00 dBm
    BSS 00:24:a8:83:77:e2(on wlan0)
            signal: -82.00 dBm
    

    The things after the “BSS” are the MAC addresses of the access points, the numbers after signal is some measure for the power that reaches the machine's antenna[2] from that access point, where less negative means more power. So, with the above output you want to pick the access point 00:24:a8:ac:1d:93.

    With ifupdown, you do that by editing the stanza for that Wifi and add a wireless-ap line; for me, this then looks like:

    iface roam inet dhcp
      wireless-essid Telekom_FlyNet
      wireless-ap 00:24:a8:ac:1d:93
    

    – and this yields a stable connection.

    I must say, however, that the services on that network (I'm too stingy for actual internet access, of course) are a bit lacking, starting with the entirely botched non-Javascript fallback (see above). At least there is http://services.inflightpanasonic.aero/inflight/services/flightdata/v1/flightdata where you will see some basic telemetry in JSON. Or wait: it's actually perimetry if you see speed, height, and other stuff for the plane you're on.

    Fetching the numbers from the json you will save a lot of power versus the web page that becomes extremely network-chatty and CPU-hoggy (on webkit, at least) once you let Lufthansa execute Javascript. I'm afraid I have too much flight shame (and hence too little use for it) to cobble something nice together with that API and qmapshack. But it certainly looks like a fun project.

    [1]Ah wait… now that I think again, I seem to remember that during one of my last sinful travels there has already been a plane that had on-board Wifi. But it certainly is a nicer story with the little lie of news when coming back after five years.
    [2]Since “dBm” stands for „decibel milliwatt“, you could compute that power as 10s ⁄ 10  W. I'd not trust the absolute numbers, as they would indicate here that one access point is a factor of ten thousand stronger than another one, which sounds implausible primarily because I'd be surprised if the circuitry of the Wifi card could deal with such a high dynamic range. And “I'm getting 0.0001 milliwatts from the AP“ is a statement in dire need of interpretation anyway (e.g., “in the carrier? Bolometric?”). But let's not go there.
  • How to Disable pdf.js in Webkit on Debian

    A window of the zathura PDF viewer showing the GDPR.

    This is how I want my PDFs rendered. And I want a j to scroll down a bit. That pdf.js fails on both accounts is just the first two of its defects.

    When I upgraded to Debian bookworm, I noticed with great dismay that the webkit browser engine it comes with has a pdf.js-based PDF renderer built in.

    That means that my preferred browser, luakit, is basically broken when dealing with PDFs: where I disable Javascript (i.e., by default), I see nothing at all. Where I allow Javascript, my PDFs appear in a UI I consider rather nasty. On top of that, I lose the nice archive of PDFs I've recently read that came with luakit's viewpdf extension. That holds true even if I do manage to properly open the PDF in my preferred renderer (zathura) using pdf.js's Save, as that blindly calls all PDFs “document.pdf”.

    Regrettably, there doesn't seem to be a runtime switch to turn off the in-browser PDF rendering. After poking around a bit in webkit's source code, I have convinced myself that I won't add that switch myself. I am just not desperate enough to start hacking on one of the major browser engines.

    But there is a build-time switch to turn pdf.js off. I have always shied away from building my own webkit packages because there's so horribly much code and C++ compilers are so terribly resource-hungry. But my suffering with the pdf.js disaster has reached a level that made me overcome that horror. So, here's how to build a Webkit such that browsers based on it will again handle PDFs properly (sc. by handing them over to the system). All this is for Debian bookworm and derivatives; let's hope it won't be necessary beyond that.

    1. Get the source:

      mkdir -p src/webkit
      cd src/webkit
      apt-get source webkit2gtk
      cd webkit2gtk*
      

      This will only work if you have configured a source repo for your suite in your /etc/apt/sources.list (or equivalent) and run apt update after that.

      This is pulls in about 50 Megabytes, which in itself is an argument in favour of netsurf. But these 50 Megs are peanuts compared to what's coming: by the time you've done a full build, this directory will have exploded into more than 3 GB (in i386). Let's fix the web so plain browsing doesn't require such monsters.

    2. Configure your build. Fortunately, you mostly only touch the debian/rules file. In there, change:

      ENABLE_SOUP2=YES
      ENABLE_SOUP3=YES
      ENABLE_GTK4=YES
      

      to (presumably):

      ENABLE_SOUP2=YES
      ENABLE_SOUP3=NO
      ENABLE_GTK4=NO
      

      That's for luakit that is built on top of soup2; if your browser uses a different API, make a different choice here. Each build takes forever and gobbles up about 3 Gigs in the process, so be stingy here.

      Then, locate the line -DENABLE_MINIBROWSER=ON (which currently concludes the EXTRA_CMAKE_ARGUMENTS) and change it to:

      -DENABLE_MINIBROWSER=ON \
      -DENABLE_PDFJS=OFF \
      -DENABLE_JOURNALD_LOG=OFF
      

      Disabling the journald log is not strictly necessary, but it helps building on non-systemd boxes, and I doubt it actually hurts anyone.

      Nachtrag (2024-01-21)

      At least with 2.40.3, this procedure ends in a:

      dh_install: error: missing files, aborting
      

      presumably because we are not building for two APIs. I think that's a bug, but from dh_install's manpage I cannot even understand why it thinks it should fail because of missing files, and consequently futzing around with debian/not-installed or the various options went nowhere. Because I'm really grumpy with the whole state of affairs, I quickly resigned into simply emptying all debian/*.install files not pertinent to the packages I want to build.

    3. Remove the systemd build dependency. We can do that because we have just disabled the JOURNALD_LOG. So, in debian/control, delete the line:

      libsystemd-dev [linux-any],
      
    4. Install the build dependencies:

      sudo apt-get build-dep webkit2gtk
      

      On non-systemd boxes, this will say something like:

      libelogind0 : Conflicts: libsystemd0
      

      because you have not removed the libsystemd dependency from apt's database in step (3), and webkit at this point doesn't know it could build with libelogind0-dev, too. Don't worry about it as long as all the other build-dependencies came in.

    5. Make a changelog entry so your system knows your build is “newer” than Debian's and you can later tell it's your custom build:

      dch -i
      

      You probably want to put something like “rebuild with PDFJS disabled“ in there, but that's exclusively for your own comfort unless you start distributing your package.

    6. Do the build:

      dpkg-buildpackage -j6 -b -uc -us -rfakeroot
      

      Do that on a cold day, because this will turn your machine into a space heater for several hours (unless you have a very fast machine, in which case you probably don't need another space heater in the first place).

    7. When this is done, you will have about a dozen binary packages in the build directory's parent. You probably don't want to dpkg -i *.deb, as there's no point installing debug packages (for starters). For luakit, I've run this:

      sudo dpkg -i gir1.2-javascriptcoregtk-4.0_2.*.deb gir1.2-webkit2-4.0_2.*.deb libjavascriptcoregtk-4.0-18_2.*.deb libjavascriptcoregtk-4.0-bin_2.*.deb libjavascriptcoregtk-4.0-dev_2.*.deb libwebkit2gtk-4.0-37_2.*.deb libwebkit2gtk-4.0-dev_2.*.deb
      

      This could be a slight over-installation.

    By the way, in case the build fails somewhere in the middle but is fundamentally sane, you can resume it by calling:

    fakreroot debian/rules binary
    

    Doing dpkg-buildpackage as above resets the build and will discard everything the computer has built in perhaps hours.

    Given the extreme cost of building a webkit, getting pdf.js out in this way is not a long-term plan, at least if you want your webkit to be halfway up-to-date (which is a good idea in particular if you're indiscriminate as to who can execute Javascript in your browser). Until someone kindly implants a run-time switch, I'm going to shut out pdfjs-infested upgrades until some really, really unnerving (that is, even more unnerving than usual) webkit vulnerability surfaces. To do that, I'm dropping:

    # my webkit with patched-out pdfjs
    Package: libjavascriptcoregtk-4.0-18
    Pin: version 2.40.5-1~deb12u1.1
    Pin-Priority: 1001
    

    into /etc/apt/preferences.d/10pins (where your version will probably different; check the version tag in the names of the generated package files). That will make the messages from apt upgrade quite a bit uglier, and of course I'll have a webkit with published security bugs (you have been warned in case you're doing as I do). But in my book that's totally worth it just to get rid of the wretched pdf.js.

  • Humboldtforum: Neue Maßstäbe beim „Besuchserlebnis“

    Ich weiß, ich sollte nicht, aber jetzt bin ich schon mal neugierig auf die Kompromisse, die die Reaktion bei ihrem Neubau des Berliner Schlosses hat schließen müssen. Und so wollte ich auf der Webseite des Humboldt-Forums (da versagt die Crapicty-Metrik derzeit: sie ist nur 3.6) nachsehen, wann das Ding wohl offen hat.

    Ohne Javascript ist im luakit das hier zu sehen:

    Screenshot: kleine Titelzeile, dann Schwarz

    Eine Suche nach „Öffnung“ sorgt für dieses Bild:

    Screenshot: Weißer Hintergrund, davor „Öffnung“ als Such-Match.

    Ja, das ist weißer Text auf weißem Grund. Wer den Mauszeiger geschickt schiebt, wird enthüllen, dass dort „Nach der Natur/Eröffnungsausstellung des Humboldt Labors [sic!]“ steht.

    Beim Weiterscrollen kommen ein paar hüpfende Punkte, wo mensch noch am ehesten Öffnungszeiten erwarten würde, und dann am Fuß der Seite zum Hohn:

    Screenshot: halbformatiertes Cookiebanner mit „um ihnen das bestmögliche Besuchserlebnis zu bieten“

    Liebe Humboldtforum-Leute: Ich will kein Besuchserlebnis, ich will wissen, wann ihr aufhabt. Um rauszukriegen, wie ihr mir das ermöglicht, braucht ihr kein Tracking, ihr müsst eure Webseite nur mal kurz mit netsurf probieren. Genau genommen habt ihr mit öffentlicher Förderung auch keine Dark Patterns (das schwarz unterlegte „ALLE AUSWÄHLEN“) nötig, und eigentlich ohnehin keine Übergriffe auf die Privatsphäre eurer NutzerInnen.

    Aber ach ja, kaputte Webseiten sind leider ziemlich normal (und als solche leider auch nicht postwürdig). Deshalb war ich von vorneherein nicht mit netsurf unterwegs. Ich hatte einen ganz ordinären Webkit-Browser, und weil ich ja normalerweise friedlich bin, habe ich halt in Gott*es Namen Javascript eingeschaltet. Als das Rendering auch dann noch scheinbar hängen blieb, habe ich selbst local storage nachgelegt.

    Hat alles nichts gebraucht, außer… der Webkit hat spontan 100% CPU gezogen. Ich habe ein wenig versucht, dem nachzugehen, bis unerwartet nach rund zwei Minuten wirklich ein modales Cookiebanner hochpoppte. Der Fairness halber: dann konnte wirklich die Öffnungszeiten sehen (10:30 bis 18:30).

    Haben die Webseiten-Leute des Humboldt-Forums ihre Tracking-Erkenntnisse vielleicht wirklich zur Optimierung des „Besuchserlebnisses“ verwendet? Als Erlebnis nämlich darf der Besuch der Humboldt-Webseite wirklich zählen.

  • OpenSSL, Syslog, and Unexpected Consequences of Usrmerge: Upgrading to bookworm

    A few weeks after the release of Debian bookworm, I have recently dist-upgraded my main, ah well, workstation, too. As mentioned in my bullseye upgrade post, that box's file system is ancient, and the machine does many things in perhaps unusual ways, which includes still booting with sysvinit rather than systemd for quite a few reasons. Hence, it always brings up the some interesting upgrade probl^H^H^H^H^Hchallenges. While for bullseye, the main… um… challenge for me was the migration to python3, this time the big theme was dropped crypto engines.

    Rsyslogd, wmnet

    Much more harmless than those, but immediately visible after the upgrade, was that my syslog display remained empty. The direct reason was that the rsyslog daemon was not running. The reason for that, in turn, was that there was not even a init script for it in /etc/init.d, let alone rc.d links to it. But the rsyslogd package was installed. What would the purpose be of installing a daemon package without an init script?

    The Debian bug tracker had something like an answer: the maintainer took it out, presumably to shed files they considered cruft in the age of systemd. Although I have to concur with Naranyan's remark in the bug report that rsyslog will typically be in place exactly when systemd (with its own log daemon) is not, at least that bug (#1037039) offers the (simple) fix: Install the orphan-sysvinit-scripts package.

    Something a bit harder to explain is that the nice wmnet applet for monitoring transfers on network interfaces came up blank after the upgrade. This is fixed by passing a -n option to it, which tells it to draw into a normal window rather than something suitable for the Windowmaker dock. Wmnet (as perhaps other Windowmaker applets, too) tries to guess where to draw based on some divination. Perhaps my window manager sawfish started to pretend it's Windowmaker in bookworm? Or indicated to wmnet in some other way it was living in a Windowmaker dock? Hm: Given that the last changelog entry on sawfish itself is from 2014 (which I consider a sign of quality), that seems unlikely, but then I can't bring myself to investigate more closely.

    The usr Merge and Bashism in the Woodwork

    Although I had merged the root and usr file systems on that box last time I migrated to a new machine, I had postponed doing the usrmerge thing (i.e., making the content of /bin and /usr/bin identical) on the box until the last possible moment – that is, the bookworm installation – because I had a hunch some hack I may have made 20 years ago would blow up spectacularly.

    None did. Except… it turned out I had linked /bin/sh to /bin/bash for some long-forgotten and presumably silly reason; if you had asked me before the upgrade, I'd have confidently claimed that of course all my little glue scripts are executed by Debian's parsimonious dash rather than the relatively lavish bash. Turns out: they weren't.

    With the installation of the usrmerge package during the bookworm dist-upgrade that is over. /bin/sh is now dash as I had expected it to be all the time. I have to admit I am a bit disappointed that I do not notice any difference in system snappiness at all.

    But I did notice that plenty of my scripts were now failing because they contained a bashism: Comparison for string equality in POSIX-compliant [ ... ] constructs is not the C-like == but the SQL-like = even though bash accepts both. I don't know when I forgot this (or, for that matter, whether I ever knew it), but a dozen or so of my (often rather deeply embedded) shell scripts started to fail with messages like:

    script name: 22: [: tonline: unexpected operator
    

    So, repeat after me: In shell scripts, compare strings with = and numbers with -eq. And I have to admit that this experience made me a bit more sympathetic to the zero shell paradigm behind systemd. But for the record: I still think the advantages of having hooks for shell scripts almost everywhere overall outweigh these little annoyances.

    The OpenSSL Upgrade

    With the bookworm upgrade, a fair number of hashes and ciphers were declared “legacy” in openssl, which means that in the default configuration, it will reject them. That had a few rather disruptive consequences: For one, I needed to update a few snake-oil certificates I had generated for playing with https on my box.

    Also, fetchmail failed for a POP server I had configured with a message like:

    fetchmail: <hostname> SSL connection failed.
    fetchmail: socket error while fetching from <whatever>
    

    I was puzzled for a while until I realised that the recipe said:

    with proto TLS1
    

    That was probably valuable in, like, 2004, to suppress ancient (relatively) easily breakable SSL versions, but by now it didn't let fetchmail negotiate crypto that was still allowed by openssl. Removing the proto TLS1 fixed that problem.

    The most unnerving breakage, however, was that my preferred disk crypto, encfs (cf. this advocacy in German), broke for some volumes I had created more than a decade ago: they failed to mount because openssl now refuses (I think) the blowfish cipher. I fiddled around a bit with re-enabling legacy algorithms as per Debian bug 1014193 but quickly lost my patience with the slightly flamboyant semantics of openssl.cnf. To my surprise, downgrading to encfs_1.9.5-1+b2_i386.deb from bullseye (by briefly re-adding the sources.list lines) let me mount the old volumes again. I then simply created new encfs volumes and rsync -av-ed from the old decrypted volume into the new decrypted volume. Finally, after unmounting everything encfs, I overwrote the old encrypted volumes with the new encrypted volumes and upgraded back to bookworm encfs.

    Since I can't explain why downgrading encfs would have fixed the problem as I've analysed it and hence suspect that a part of my analysis (and fix) is wrong, I'd strongly recommend to run:

    encfsctl info <encrypted volume>
    

    on each encfs directory you have before the upgrade. If it says something like:

    Filesystem cipher: "ssl/blowfish", version 2:1:1 (using 3:0:2)
    

    or even just:

    Version 5 configuration; created by EncFS 1.2.5 (revision 20040813)
    

    (where I have not researched the version where encfs defaults became acceptable for bookworm openssl; 1.9 is ok, at any rate), copy over the decrypted content into a newly created encfs container; it's quick and easy.

    Relatedly, bookworm ssh also disallows a few crypto methods now deemed insecure by default, in particular SHA-1 hashes for host keys. Now, I have to connect to a few hosts I cannot upgrade (either because I'm not root or because they are stuck on some ancient kernel because of proprietary kernel components). For these, when trying to connect I now get messages like this:

    Unable to negotiate with 192.168.20.21 port 22: no matching host key type found. Their offer: ssh-rsa,ssh-dss
    

    You could reasonably argue I should discard boxes of that type. On the other hand, nobody will spend 50'000 Euro to eavesdrop on my communications with these machines[1] – that's the current estimate for producing a hash collision for an ssh host key, which this is about. Hence, I'm happy to risk man-in-the-middle attacks for these machines.

    To deal with such situations, openssh lets you selectively re-allow SHA-1 hashes on RSA host keys. Helpfully, /usr/share/doc/openssh-client/NEWS.Debian.gz gives a recipe to save those hosts; put host stanzas like:

    Host ancient-and-unupdatable.some.domain
      HostKeyAlgorithms=+ssh-rsa
      PubkeyAcceptedKeyTypes +ssh-rsa
    

    into ~/.ssh/config (and do read ssh_config (5) if you are not sure what I'm talking about, regardless of whether or not you have this particular problem). Incidentally, just to save that one machine where you forgot to update your ancient DSA public key, you can for a brief moment change the second line to:

    PubkeyAcceptedKeyTypes +ssh-rsa,ssh-dsa
    

    If you don't have an RSA key yet, create one (ssh-genkey -t rsa) – RSA keys work even on the most venerable openssh installations that don't yet know about the cool ed25519 keys. Connect to the server, install the RSA public key, and re-remove the ssh-dsa part in the config again.

    Kudos to the openssh maintainers for keeping compatibility even in crypto over more than 20 years. And shame on many others – including me – who don't manage to do that even in non-crypto software.

    Terrible Font Rendering in Swing

    One of the more unexpected breakages after the upgrade was that some Java Swing (a once-popular GUI toolkit) applications suddenly had terribly jagged fonts, such as my beloved TOPCAT:

    Part of a screenshot of a menu with horribly jaggy letters

    I cannot exactly say why this looks so terrible[2]. Perhaps in the age of 300 dpi displays font hinting – which is supposed to avoid overly jagged pixelisation when rendering vector fonts at low resolutions – has become out of fashion, perhaps OpenJDK now …

  • Deutscher Wetterdienst: Nachricht schlecht, Technik gut

    Die schlechte Nachricht zuerst:

    Europakarte mit Regenmengen für Juni 2023, braun ist zu trocken, und u.a. im Oberrheingraben und im türkisch-syrischen Erdbebengebiet ist es ziemlich braun.

    Rechte: DWD (vgl. Text)

    Ja, es ist trocken hier im Oberrheingraben, und noch mehr ausgerechnet im türkisch-syrischen Erdbebengebiet. Aber nein, für einen einzelnen Monat ist das einfach nur die übliche Gemeinheit der Natur und kein Signal des Klimawandels. Aber von denen haben auch wirklich genug andere.

    Ich verblogge das hier jedoch aus einem ganz anderen Grund: Ich will dem Deutschen Wetterdienst ein großes Lob aussprechen, denn der Dienst, der diese Bilder macht, funktioniert einwandfrei[1] ohne Javascript: Schnell, unproblematisch, mit verschwindender CPU-Belastung am Client, geht auch im netsurf, insgesamt weniger Daten übertragen als andere für ihr doofes Javascript-Framework brauchen.

    So sollte das Netz sein. Genau so. Und so könnte es auch sein, ohne dass es irgendwem außer der Belästigungs-„Industrie” weh täte. Danke, DWD!

    Und ihr anderen mit euren Krapizität-300-Webseiten, bei denen ohne Javascript local storage noch nicht mal statischer Text kommt: Schämt euch.

    [1]Na gut, fast einwandfrei: Die Auswahl der anderen Parameter haben sie im Augenblick ohne Javascript vermurkst.
  • Ach Bahn, Teil 13: Besser wirds am XX.XX.XXXX

    Die Bahn behauptet ja gerne, all die indiskreten „Analytik“-Skripte, die sie über ihre Webseite ausliefert, dienten irgendwie dazu, die „User Experience“ zu verbessern. Wenn das so ist, so hoffe ich, dass ihnen ihre Analytik-Dienstleister Hinweise zur profunden Nutzlosigkeit von Meldungen dieser Art geben:

    Screenshot der Bahnseite mit einer Meldung „Zum XX.XX.XXX werden die technischen Systeme von bahn.de umgestellt [...] Mehr Informationen finden Sie unter d2.

    Aber nennt mich konservativ: Ich sehe dem angekündigen XX.XX.XXXX mit wenig Freude entgegen, denn trotz aller Analytik ist die Bahn-Webseite über die Jahre für mich immer schlechter bedienbar geworden. Während die ersten Formulare schnell luden, praktisch ohne Belastung für heutige CPUs renderten und mit Browser-Hausmitteln tastaturbedienbar waren, ist die heutige Javascript-Wüste in jeder Hinsicht lästig. Das einzige Feature jedoch, das mir schon in den ganz alten Bahnseiten fehlte, fehlt immer noch: Wenn sie schon wissen, wer ich bin und dass ich eine Bahncard 50 habe, dann könnten sie doch voreinstellen, dass ich auch Bahncard 50-Fahrkarten kaufen will.

    Nach etwas Kontemplation habe ich übrigens verstanden, dass ich das „d2“ in der Meldung oben ignorieren muss und kann, wohingegen das Ding mit dem Pfeil davor zwar völlig aus dem Layout fällt, aber den Satz im Absatz darüber komplettiert und mithin der Link sein wird, unter dem es „mehr Informationen“ geben soll. Also klickte ich auf die „Zukunft der Bahn“ und war nach dem oben Gesagten nur sehr mäßig enttäuscht, als ich Folgendes zu sehen bekam:

    Screenshot: „Sie haben in Ihrem Browser JavaScript deaktiviert, dies wird jedoch von unserer Anwendung benötigt.“

    Liebe Bahn: Ich will keine „Anwendung“. Ich will ein Formular, über das ich Fahrplanauskünfte bekomme und vielleicht noch Fahrkarten kaufen kann. Ja, das geht ohne Javascript, und eine öffentliche Infrastruktur – um einen Gegenbegriff zum „modernen Dienstleistungsunternehmen“ einzuführen – sollte das auch dann möglich machen, wenn sie gegenwärtig in eine etwas ungeeignete Rechtsform gezwungen ist.

  • What to do when github eats 100% CPU in luakit

    I can't help it: As probably just about every other programming life form on this planet I have to be on github now and then. Curse the network effect and all those taking part in it (which would by now include me).

    Anyway, that's why the last iteration of luakit bug #972 (also on github. Sigh) bit me badly: as long as the browser is on a github page, it will spend a full 100% of a CPU on producing as many error messages as it can, each reading:

    https://github.githubassets.com/<alphabet soup>1:8116:
    CONSOLE JS ERROR Unhandled Promise Rejection:
    TypeError: undefined is not an object (evaluating 'navigator.clipboard.read')
    

    Github being a commercial entity I figured it's a waste of time trying to fill in a bug report. And the problem didn't fix itself, either.

    So, I went to fix it (in a fashion) with userscript. Since the problem apparently is that some github code doesn't properly catch a missing (or blacklisted) clipboard API in a browser (and I still consider blacklisting that API an excellent idea), I figured things should improve when I give github something similar enough to an actual clipboard. It turns out it does not need to be terribly similar at all. So, with a few lines of Javascript, while github still sucks, at least it doesn't eat my CPU any more.

    What do you need to do? Just create a userscript like this (for luakit; other browsers will have other ways):

    cd
    mkdir -p .local/share/luakit/scripts
    cat > .local/share/luakit/scripts/github.user.js
    

    Then paste the following piece of Javascript into the terminal:

    // ==UserScript==
    // @name          clipboard-for-github
    // @namespace     http://blog.tfiu.de
    // @description   Fix github's 100% CPU usage due to unhandled clipboard errors
    // @include       https://github.com*
    // ==/UserScript==
    navigator.clipboard = Object()
    navigator.clipboard.read = function() {
            return "";
    }
    

    As usual with this kind of thing, at least have a quick glance at what this code does; these four lines of source code sufficient here at least are easy to review. Finish off with a control-D, go to a luakit window and say :uscripts-reload.

    If you then go to, say bug #972, your CPU load should stay down. Of course, as long as github blindly tries to use the navigator.clipboard object for „copy link“-type operations, these still won't work. But that's now github's problem, not mine.

    And anyway: Give up Github.

  • Eine neue Metrik für Webseiten: Crapicity

    Screenshot einer Webseite mit großem Banner "Crapicity" über einer Eingabezeile und einer Balkengrafik, die nach Lognormal-Verteilung aussieht.

    Die Krapizität der Webseiten, auf die ich hier so verlinkt habe (und noch ein paar mehr): works with netsurf!

    Kurz nachdem das Web seine akademische Unschuld verloren hat, Ende der 1990er, habe ich den UNiMUT Schwobifying Proxy geschrieben, ein kleines Skript, das das ganze Web in Schwäbisch, hust, erlebbar machte. Tatsächlich hat mir das gegen 2002 meine 15 Minuten des Ruhms verschafft, inklusive 200'000 Zugriffen pro Tag (fürs damalige Netz rasend viel), Besprechungen auf heise.de, spiegel.de und, für mich offen gestanden am schmeichelndsten, in Forschung aktuell (also gut: Computer und Kommunikation) im Deutschlandfunk.

    Ein heimlich verwandtes Web-Experiment war dass Dummschwätzranking von 1998, das – nicht völlig albern – die Dichte von (damals gerade modernen) Heißdampfworten in Webseiten beurteilt hat – dafür interessieren[1] sich bis heute Leute.

    Beide Spielereien sind praktisch abgestellt, teils, weil das kommerzielle Internet und SEO solche Sachen rechtlich oder praktisch sprengen. Teils aber auch, weil sie darauf bauen, dass das, was Leute im Browser sehen, auch in etwa das ist, was im HTML drinsteht, das so ein Programm von den Webservern bekommt. Das aber ist leider im Zuge der Javascriptisierung des Web immer weniger der Fall.

    Nun ist sicherlich die Habituierung der Menschen an „lass einfach mal alle Leute, von denen du was lesen willst, Code auf deiner Maschine ausführen“ die deutlich giftigere Folge des Post-Web-1-Megatrends. Aber schade ist es trotzdem, dass zumindest im kommerziellen Web kein Mensch mehr in die Seiten schreibt, was nachher in den dicken Javascript-Browsern angezeigt werden wird.

    Und weil das schade ist, habe ich einen postmodernen Nachfolger des Dummschwätzrankings geschrieben: Die Crapicity-Maschine (sorry, nicht lokalisiert, aber sprachlich sowieso eher arm). Ihre Metrik, namentlich die Crapicity oder kurz c7y: das Verhältnis der Länge lesbaren Textes (das, was ohne Javascript an Zeichen auf den Bildschirm zu lesen ist) zur Gesamtlänge der Seite mit allem eingebetteten Markup, Javascript und CSS (also: externes Javascript, CSS usf nicht gerechnet). In Python mit dem wunderbaren BeautifulSoup-Modul ist das schnell berechnet:

    def compute_crapicity(doc):
      """returns the crapicity of html in doc.
    
      doc really should be a str -- but if len() and BeautifulSoup() return
      something sensible with it, you can get away with something else, too.
      """
      parsed = BeautifulSoup(doc, "html.parser")
      content_length = max(len(parsed.text), 1)
      return len(doc)/content_length
    

    Um diese knappe Funktion herum habe fast 700 Zeilen herumgeklöppelt, die Ergebnisse in einer SQLite-Datenbank festhalten und ein Webinterface bereitstellen. Als Debian-und-eine-Datei-Programm sollte das recht einfach an vielen Stellen laufen können – wer mag, bekommt die Software bei codeberg.

    Die Web-Schnittstelle bei https://blog.tfiu.de/c7y hat aber den Vorteil, dass sich die Scores sammeln. Spielt gerne damit rum und empfiehlt es weiter – ich fände es putzig, da vielleicht 10'000 Seiten vertreten zu haben. Ich habe selbst schon knapp 200 Web-Ressourcen durchgepfiffen, meistenteils Links aus dem Blog hier.

    Im Groben kommt raus, was wohl jedeR erwartet hat: Das kommerzielle Netz stinkt, alter Kram und Techno-Seiten sind meist ganz ok. Allerdings habe ich den aktuellen Spitzenreiter, eine Reddit-Seite mit einem c7y von über 17'000, noch nicht ganz debuggt: Erstaunlicherweise ist die Seite auch im netsurf und ohne Javascript lesbar. Wie sie das macht: nun, das habe ich in 800 kB Wirrnis noch nicht rausgefunden, und der Quellcode der Seite sieht so schrecklich aus, dass der Score sicherlich verdient ist.

    Ich nehme mal an, dass derzeit alle youtube-Seiten bei c7y=8222 liegen; dort ist ja durchweg ohne Javascript nichts zu sehen, und so haut auch dieser Score prima hin. Bei taz.de (gerade 892) geht es vielleicht nicht so gerecht zu, denn die Seite funktioniert in der Tat auch ohne Javascript ganz gut. Eventuell ist hier BeautifulSoup schuld. Hochverdient hingegen sind die 682 von nina.no – das ist ohne Javascript leer. Eine Twitter-Seite liegt bei 413, Bandcamp bei 247.

    Vernüftige Seiten liegen dagegen zwischen etwas über eins (minimales Markup) und zehn (z.B. wenig Text mit viel CSS). Dishonorable Mention: uni-heidelberg.de liegt trotz Akademia bei 177. Tatsächlich ist die Seite auch in normalen Browsern[2] halbwegs lesbar. Der schlechte Score liegt vor allem an eingebetten SVGs, ist also schon ein ganz klein wenig unfair. Aber ehrlich: wer für ein bisschen Glitz ein paar hundert Zeichen Text auf satte 680k aufbläht, hat eine große crapicity verdient, auch wenn die Seite selbst nicht richtig kaputt ist. Wer unbedingt so viel Glitz haben will, soll externe Bilder verwenden – die muss ich nicht runterladen, wenn ich nicht will.

    Wer interessante Krapizitäten findet: Die Kommentarbox wartet auf euch.

    [1]Na gut, viel von dem Interesse kam aus erkennbar aus SEO-Kreisen; das war dann auch einer der Gründe, warum ich das eintragen von Links beim Dummschwätzranking abgestellt habe.
    [2]Definiert als: Alles außer den Monstren Firefox, Chrome, Webkit und ihren Derivaten.
  • Oracle, Mozilla, W3C: Broken Links, Broken Web, Slugs in a Bucket

    Type plate of a DEC PDP 12 minicomputer.  A large rotary switch is set to offline.

    This post is about why today I had intense fantasies about having an offline switch at least as large as the one on this DEC PDP-12 photographed at Vienna Observatory in 2018.

    I give you it has become somewhat trite to state the obvious: The Web (and quite possibly most of the Internet) is broken. Admittedly, a few relatively simple measures – a few well-placed dnsmasq statements, a default-no-javascript browser and a general avoidance of for-profit pages – can hide quite a bit of that brokenness. Today, however, it hit me hard; and two nonprofits were part of what makes me once more seriously consider a livelihood as a gardener.

    A long history of evil getting worse

    I must concede that it didn't take the pure evil of Oracle to fool around with redirects instead of 404s and to wantonly tear down static pages that could have been maintained forever at zero cost. Even the lesser evil that Sun was did that, as evinced by this 2006 capture from the Web Archive. That's already less than ten years after JDK 1.1 was released a quarter century ago (February 1997).

    Which brings me to another reason to want to retire: This link has been broken for more than 15 years now. I suspect I'm imagining the W3C to be a lot better staffed than it actually is, and yes, touching normative content is a tricky thing to do. But either through an Erratum or as an editorial change the W3C could and should have fixed the clickjacked link some time within those 15 years. That would have been particularly thoughtful given that my reaction probably isn't too untypical: Rather than go to the Internet Archive to fetch the original documentation right away, I looked for DecimalFormat in today's Java documentation. And yay!, it says:

    It also supports […] scientific notation […] Example: "0.###E0" formats the number 1234 as "1.234E3".

    Except it didn't for me. The numbers were stubbornly unformatted regardless of what I did as soon as I tried any sort of scientific notation. Which drove me entirely crazy until I ran the browser from a terminal and saw log output there:

    error
    xsltFormatNumberConversion : error in format string '0.###E0', using default
    

    It still took a while until I realised the problem was not actually with the format string – that I had taken literally from the modern documentation: Gnah! – but that JDK 1.1 just didn't have the feature. I actually had to go to an older capture of Sun's documentation to figure that out (do donate to the Internet Archive, please).

    That was not the end of my webaches of the day.

    Mozilla: Crashes in the supply chain

    Proclamation: I'm a huge fan of the MDN whenever I do “web development“. You may hence get an idea of my desperation when, minutes after I had somewhat worked out the Sun-Oracle-W3C debacle, I was looking up the border-spacing CSS property at MDN and just saw a brief flash of content and then this:

    Screenshot: an empty browser window

    What deviltry…? From folks who are at least reasonably concerned about standards compliance and accessability? Well, a quick glance into the developer tools made my me shiver. Here's what I found in the error console:

    [Error] The source list for Content Security Policy directive 'script-src' contains an invalid source: ''report-sample''. It will be ignored.
    [and a lot more CSP stuff]
    [Error] Failed to load resource: Unacceptable TLS certificate (analytics.js, line 0)
    "Error executing Glean task: NotFoundError: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found."
    [Error] (Glean.core.Dispatcher) – "Error initializing dispatcher, won't execute anything further." – "There might be more error logs above."
    [Error] TypeError: null is not an object (evaluating 'localStorage.getItem')
    [Error] TypeError: null is not an object (evaluating 'localStorage.getItem')
    

    So… the Mozilla folks list all kinds of new-fangled (i.e., bullseye Webkit doesn't know about them) Content Security Policies. I'll allow they do that out of concern for my privacy – though frankly, I'm not much worried about cross-site scripting from MDN, in particular because the site generally works fine without Javascript (I don't remember when or why I switched it on for them). Be that as it may, after all the CSP-ing, they turn around and try to rat me out to Google analytics (which fails here because google-analytics.com resolves to 127.0.0.1 on my boxes). Hu?

    Want to report a bug? Feed Microsoft!

    Anyway, I had Javascript on, and then I got the blank page because rendering crashes when the thing cannot do Javascript local storage. If you wonder why I keep that off by default, check out my rant against it; sorry: in German). That's why I get a blank page. It all goes belly-up starting with a message “Unable to read theme from localStorage“ from witin some React code. Call me a purist, but I don't even see how you'd need the ghastly React Javascript “framework“ <cough> for a documentation site. Why this would need to store a theme at all and why this should be crashing the whole doument[2] when it can't do that is entirely beyond me.

    Given that Mozilla are (to some extent) good guys, I wanted to file a bug. They also were home to the premier bugtracker of the 2000s, bugzilla, so… No. Even the contribution guide [warning: github link] – a static document! – sits in github, collecting behavioural surplus for Microsoft.

    Ah… sorry, Mozilla, if React and github are your ideas of an open, accessible, and standards-compliant Web, there is no point filing bugs against MDN. That's not a good use of the surveillance capital I have to give.

    But then: being a gardener isn't all that idyllic either:

    Lots of slugs in a bucket

    Given that, Oracle and Mozilla are not much worse. So, I guess I'll keep doing computers for a little while, at least as long as I can somehow still work around the postmodern Web's breakage most of the time.

    [1]Well, actually I was looking at a local mirror of that, but if turns out that looking at the online material right away wouldn't have made a difference.
    [2]I just notice that the existence of the phrase “crashing a document“ is a rather characteristic symptom of the state of the Web.
  • Magie im Spam

    Leider vermag mich halbwegs origineller Spam durchaus mal nerdzusnipen (ref; zuvor). Und so habe ich mich heute über ein Angebot für dieses Gerät gefreut:

    Werbebild von irgendeinem Zwischenstecker und seiner Verpackung

    Rechte: Tja, halt bei irgendeinem Spammer.

    Beschrieben wird das im begleitenden Text (genauer: der text/plain-Alternative) als:

    Fangen Sie mit E-Energy schon heute an zu sparen! E-Energy sprecher die erhaltene reaktive Elektroenergie und gibt die aktive Elektroenergie an das Ger&#228;t ab, welches sich im selben Stromkreis mit dem Stromsparer befindet.

    Funktionsweise von E-Energy &gt;&gt;

    Energieeinsparung -50% + Ihr Rabatt -50%!

    Das ist natürlich kompletter Unfug, der mit Physik ungefähr so viel zu tun hat wie in die Zukunft blickende Glaskugeln. Demgegenüber hat selbst der Technobabble der originalen Star-Trek-Serie noch jede Menge Plausibilität. Schön!

    Ebenfalls schön ist, dass stil- und genresicher das, was diese Mail erzeugt hat, nicht in der Lage war, Entities aus dem doofen HTML in ordentliches Unicode zu übersetzen. Dennoch sind die Spammer in der Hinsicht den Schurken von cleverreach voraus, denn immerhin steht etwas im Plain Text.

    Was dort allerdings völlig unklar bleibt: Ist das ein Versuch, Leute zum Klicken auf giftige Links zu verleiten oder will da wirklich wer was verkloppen? In der HTML-Alternative stehen Links drin, und zwar auf eine Domain dirabore.co.in. Habt Verständnis, dass ich keinen Link draufsetze; zwar habe ich nicht viel Pagerank zu vergeben, aber auch den sollen sie nicht haben.

    Das wiederum redirectet (mit einem selbst unterschriebenen Zertifikat) auf eine wirre Seite auf litbeurope.com (soll das Vertipper von libreurope fangen?) mit folgendem Teaser:

    Screenshot mit Text: „So bestrafte ein einfacher Dortmunder die E.ON für den BETRUG and der ganzen Bevölkerung“

    Im Weiteren behauptet der „Text“, eon habe den Einsatz des e-energy-Wunderdings gerichtlich verbieten lassen wollen. Interessanter sind die (vermutlichen) Kauf-Links auf der Seite, denn die haben ohne Javascript keine Ziele. Rauspopeln, welches Javascript die setzt oder diese Leute gar Javascript bei mir ausführen lassen – nun, so neugierig bin ich dann auch nicht.

    Die Frage, ob das Betrug mit funktionslosen Steckern oder nicht ausgeführten Lieferungen ist oder ob das doch nur ein elaborierter Versuch ist, Leuten Malware unterzuschieben: Das muss ich dann wohl offen lassen. Dennoch schönen Dank an die AutorInnen für unterhaltsamen Spam.

    Ergänzung 2022-11-25: Oh wow. Das ist mehr als ein wirres Randphänomen. Es geht offenbar gerade groß im Netz rum – jedenfalls warnt die Bundesnetzagentur vor Maschen dieses Typs (heise online dazu). Was für eine katastrophale Niederlage für unser Schulsystem…

  • Bahnauskuft auf antiken Geräten – und auf Codeberg

    Foto: Altes Mobiltelefon mit Terminal, das eine etwas kryptische Bahnauskunft zeigt

    Bahnauskunft von 2022 auf einem Nokia N900 von 2009: Es braucht inzwischen etwas Mühe, um das gebastelt zu kriegen.

    Als die Bahn-Webseite nicht mehr ordentlich auf kompakten Browsern wie dillo funktionierte und auch nicht per WAP– also Mitte der 2010er Jahre –, habe ich mir ein ein kleines Skript geschrieben, das die wesentlichen Infos zur Zugauskunft aus dem HTML herausklaubte und dann in einem einfachen Kommandozeilen-Interface darstellte. Das war, worum es im letzten Sommer bei meinem Rant gegen Zwangs-Redirects umittelbar ging. Der weitere Hintergrund: Ich will Zugauskünfte von meinem alten Nokia N900 aus bekommen (und im Übrigen seit der Abschaltung von UMTS auch über eine 2G-Funkverbindung, also etwas wie 10 kB/s)[1].

    Nachdem das – jedenfalls nach Maßstäben von Programmen, die HTML auf Webseiten zerpflücken – überraschend lang gut ging, ist das im Rahmen der derzeitigen Verschlimmbesserung der Bahn-Seite neulich kaputt gegangen. Obendrauf ist die Javascript-Soße auf bahn.de damit so undurchsichtig geworden, dass mich die Lust, das Skript zu pflegen, sehr nachhaltig verlassen hat. In dieser Lage kam ein Vortrag über die Bahn-APIs, den jemand bei der Gulasch-Programmiernacht 2019 gehalten hat, gerade recht. Also: Das Video davon.

    In diesem Video habe ich gelernt, dass mein „unpromising“ im Rant vor einem Jahr,

    I know bahn.de has a proper API, too, and I'm sure it would be a lot faster if I used it, but alas, my experiments with it were unpromising [...],

    einen tiefen Hintergrund hat. Die Bahn hat nämlich keine API für die Fahrplanauskunft.

    Was es aber stattdessen gibt: die HaFAS-API, auf die die Reiseplanung der Bahn-App selbst aufsetzt. Und es stellt sich heraus, dass Leute schon mit viel Fleiß ausbaldowert haben, wie die so funktioniert, etwa in pyhafas.

    Mit pyhafas kann ich all das schreckliche HTML-parsing aus dem alten bahnconn.py durch ein paar Aufrufe in pyhafas rein ersetzen. Aber leider: pyhafas ist echt modernes Python, und weil es viel mehr kann als es für bahnconn.py bräuchte, wäre das Rückportieren davon nach Python 2.5 ein ernsthaftes Projekt; mehr habe ich aber auf meinem N900 nicht. Außerdem bin ich bekennender Fan von ein-Modul-und-stdlib-Programmen: die brauchen keine Installation und laufen zudem mit allem, das irgendwie Python verdauen kann, also etwa auch jython oder sowas, was spätestens dann in Frage steht, wenn Abhängigkeiten C-Code enthalten.

    Deshalb habe ich aus pyhafas die Dinge, die bahnconn dringend braucht, abgeschaut und eine minimale, Python-2.5-verträgliche Implementation gebastelt. Das Ergebnis: ein neues bahnconn. Holt es euch, wenn ihr Bahnauskunft auf älteren Geräten haben wollt. Ich habe es jetzt nicht auf Atari TTs probiert, aber ich kann mir gut vorstellen, dass es selbst da noch benutzbar ist.

    Codeberg

    Gerade, als ich den Code einfach wieder hier auf dem Blog abwerfen wollte, habe ich beschlossen, das könne ein guter Anlass sein, endlich mal einen zweiten Blick auf Codeberg zu werfen.

    Bisher habe ich nämlich für allen etwas langlebigeren oder größeren Code (also: nicht einfach nur am Blog abgeworfenen Kram), ganz DIY, ein eigenes Subversion-Repository betrieben. Was in den letzten Jahren neu dazukam, habe ich in git+ssh+cgit gesteckt.

    Natürlich hat das niemand mehr gesehen; nicht mal Suchmaschinen gucken mehr auf sowas, seit aller Code der Welt bei github landet. Deshalb, und auch, weil ich Monstren wie gitea und gitlab wirklich nicht auf meiner Maschine haben will (allerdings: cgit ist ok und würde für Publikation auf subversion-Niveau reichen), habe ich mich mit dem Gedanken, dass mein Kram auf einer öffentlichen Plattform besser aufgehoben sein mag, mehr oder minder abgefunden.

    Auf Github bin ich beruflich schon so viel zu viel unterwegs, und der Laden ist deutlich zu nah am Surveillance Capitalism. Zwar kenne ich hinreichend Projekte und Firmen, die ihnen Geld geben, so dass sie gewiss ein konventionell-kapitalistisches Geschäftsmodell fahren könnten; aber schon da fehlt mir der Glaube. Obendrauf hat mir Microsoft in meinem Leben schon so viel Kummer bereitet, dass ich ihnen (bzw. ihrem Tochterunternehmen) nicht noch mehr KundInnen zutreiben will.

    Codeberg, auf der anderen Seite, wird von einem Verein betrieben und macht generell vieles richtig, bis hin zu Einblendungen von Javascript-Exceptions (warum machen das eigentlich nicht alle?), so dass die Seite nicht einfach heimlich kaputt ist, wenn ich Local Storage verbiete (gitea, die Software, auf der Codeberg soweit ich sehe aufsetzt, kann leider immer noch nicht ohne).

    Von dem gitea-Krampf abgesehen hat gestern alles schön funktioniert, nichts an der Anmeldeprozedur war fies oder unzumutbar. Codeberg hat hiermit erstmal das Anselm Flügel Seal of Approval. Ich denke, da werde ich noch mehr Code hinschaffen. Und mal ernsthaft über Spenden nachdenken.

    [1]Janaja, und natürlich nervte mich die fette Bahn-Webseite mit all dem Unsinn darauf auch auf dem Desktop und auch schon vor der gegenwärtigen Verschlimmbesserung.
  • Ach Bahn, Teil 9: Das Bahnhofs-WLAN

    Screenshot: Bahn-Fehlermeldung „Verbindungsfehler“

    Hat jemand in den letzten drei Jahren vom Bahnhofs-Captive Portal mal irgendwas anderes gesehen als das hier?

    Versteht wer das Bahnhofs-WLAN der Bahn? Ich habe das soeben aufgegeben, nachdem ich mir den „Verbindungsfehler“, den ich, glaube ich, seit vor Corona bei jedem Versuch der Verbindung mit Netzen mit der SSID WIFI@DB bekommen habe, genauer angesehen habe.

    Natürlich sitzt auch auf diesen Netzen leider ein Captive Portal. Diese sind generell so gemacht, dass in dem Netz nach dem WLAN-Verbindungsaufbau erstmal alle HTTP-Requests auf (leider normalerweise horrös verjavascriptete – es gibt aber auch löbliche Ausnahmen) Provider-Seiten umgeleitet werden, auf denen mensch dann irgendeinen Quatsch abklicken soll. Ganz ehrlich: Ich habe diesen Quatsch noch nie gelesen, zumal ich glaube, dass er rechtlich nicht bindend ist und sowieso davon ausgehe, dass diese Netze mich aufs Kreuz legen wollen.

    So weit, so ärgerlich. In WIFI@DB sieht es erstmal aus, als würde die Bahn das auch so machen:

    $ curl -v http://blog.tfiu.de
    [...]
    < HTTP/1.1 302 Found
    < Location: https://wifi.bahn.de/
    

    Die Login-Seite wäre demnach wifi.bahn.de. Von dort sollte jetzt die Javascript-Grütze samt riesigen Bildern kommen, die mensch von grottigen Captive Portals gewöhnt ist. Aber nein:

    $ curl -v https://wifi.bahn.de
    [...]
    < HTTP/1.0 302 Found
    < Location: /sp/7cwojgdj/connectionError
    

    Da wird mensch (bzw. computer) ganz platt gleich auf die Fehlerseite weitergeleitet – ok, da ist noch irgendeine Zeichenkette mit Trackinggeschmack dabei, die aber während meiner Experimente trotz variierender User Agents und MAC-Adressen konstant blieb. Um derwail mal was Nettes zu sagen: die Weiterleitung geht ganz ohne Javascript.

    Die Fehlerseite selbst hat dann übrigens fast Captive Portal-Standard. Sie kommt mit minifiziertem jquery (zur Darstellung von „Verbindungsfehler“?!) und einem 2000 Pixel breiten JPEG (aber: nur 150k groß; normal ist alles unter 2 Megabyte sub-par für Captive Portals).

    Wenn nun das mit der unmittelbaren Weiterleitung auf die Fehlerseite nur heute so wäre, würde ich sagen, gut, ist halt gerade kaputt. Aber wenn mich meine Erinnerung nicht sehr trügt, habe ich das schon lange nicht mehr anders gesehen.

    Dass bei der Bahn das Außergewöhnliche normal ist, habe ich neulich schon festgestellt. Aber dass so Kram einfach immer kaputt ist, scheint mir doch etwas unplausibel. Hat wer eine Erklärung?

  • Wifi im Metronom: Ui

    Screenshot: Etwas zerrupfte Felder einer einfachen Webseite

    Das Captive Portal im WLAN vom Metronom, gerendert in einem netsurf: Nicht perfekt, aber es funktioniert, ganz ohne Javascript und anderen Zauber.

    Ich sage ja nur ungern etwas Positives über Epiphänomene der Bahnprivatisierung, aber wo ich gerade in einem Metronom sitze und ich wiederholt über absurd komplexe Captive Portals geschimpft habe: Die Metronoms (bzw. ihr Dienstleister Icomera) machen das tatsächlich mal so, dass das halbwegs akzeptabel ist: Es funktioniert ohne Javascript und Local Storage, auch im netsurf (wenn auch interessanterweise nicht mit dillo; ich habe das nicht debuggt) und überträgt in restriktiv konfigurierten Browsern gerade mal 6 kiB für die Seite. Selbst ein webkit kommt, solange Javascript aus ist, mit rund 50 kiB raus.

    Wenn das irgendwer von Icomera liest: Ziemlich großes Lob[1]! Wenn das hier jemand liest, der/die ein kommerzielles WLAN anbieten will, es aber nicht einfach ganz offen lassen will: Geht zu Icomera und sagt: „Ich will das, was die Metronoms bekommen haben“.

    Full Disclosure: Ich bin an sich ein zähnefletschender Feind von „Dienstleistern“ wie Icomera, habe aber ansonsten tatsächlich nichts mit denen zu tun.

    PS: Kein Licht ohne Schatten im Geschäft von IT-„Dienstleistung“: Icomera blockt leider Port 587, der inzwischen Standard fürs Maileinliefern ist. Ach, Icomera: Drängt die Leute doch bitte nicht noch mehr zu webmail (und gmail). Aber immerhin haben sie ssh offen gelassen, so dass halbwegs unproblematische Selbsthilfe möglich ist.

    [1]Für ganz großes Lob müsstet etwas zurückhaltenderes CSS verwenden oder zumindest den z-index eures Dialogs über den Footer heben.
  • Ach Bahn, Teil 6: Vernünftiges HTML ist möglich

    Über absurd komplexe Captive Portals in öffentlichen WLANs, die insbesondere die Ausführung von Unmengen von Javascript erfordern – was besonders kitzlig ist, da in der Situation der Accesspunkt der ultimative Man-in-the-middle ist –, habe ich mich hier schon öfter geärgert. Was wäre so verkehrt an einer einfachen HTML-Seite, die kurz die Regeln erläutert, wenns dringend sein muss, noch ein Häkchen verlangt und sich ansonsten auf <input type="submit" value="Connect"/> (für Interoperabilität; ich selbst hätte auch nichts gegen ein <button>Connect</button>) beschränkt? Nebennutzen: Sowas abzuschicken wäre leicht automatisierbar[1].

    Stellt sich heraus: Wer auch immer diesen Mist bastelt, könnte auch anders. Jetzt gerade fahre ich – zum ersten Mal mit 9-Euro-Ticket[2] – in einem S-Bahn-Zug der Bahn und bin mit einem WIFI@DB-Netz verbunden. Beim Versuch, die nötigen Beschwörungen fürs Internet zu vollführen, kam eine ganz schlichte Meldung: „We are offline“, so schlicht, dass ich mir gleich den Quelltext angucken musste, und siehe da (ich habe ein paar Leerzeilen gekürzt):

    <html lang="de"><head>
        <meta http-equiv="Cache-Control" content="no-store">
        <meta http-equiv="Cache-Control" content="no-cache">
        <meta http-equiv="Pragma" content="no-cache">
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>SRN Startseite</title>
        <link rel="stylesheet" href="scss/styles.css">
    </head>
    <body>
    We are offline...
    </body></html>
    

    Gut: Das Übertöten bei der Cache-Kontrolle ist nicht optimal, und auch nicht, dass englischer Text als Deutsch ausgezeichnet ist; generell wäre ich zwecks einfacher Parsbarkeit auch immer für XHTML zu haben, und wenn sie schon UTF-8 als Zeichensatz haben, sollten sie aus typografischen Gründen statt ... lieber … (alias U+2026) schreiben – aber anonsten: So kann modernes HTML („modern“ wg. des dämlichen viewport-metas) auch aussehen.

    Ach so: Das stylesheet gibt natürlich ein 404, und würde ich die Kennung meiner Netzwerkkarte nicht ohnehin auswürfeln, wäre nicht amüsiert, dass die URL, unter der der Kram ausgeliefert wird, genau diese Kennung enthält, aber verglichen mit den üblichen Javascript-Wüsten wäre das wirklich ein Fortschritt. Warum gehen Captive Portals nicht immer so?

    Wobei: ganz so einfach ist das natürlich auch nicht. Zieht mensch nämlich einfach irgendeine Webseite, kommt als Request Body des Redirects auf die obige schlichte Seite sowas hier:

    <HTML><BODY><H2>Browser error!</H2>Browser does not support redirects!</BODY>
    <!--
    <?xml version="1.0" encoding="UTF-8"?>
    <WISPAccessGatewayParam
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="http://www.wballiance.net/wispr_2_0.xsd">
    <Redirect>
    <MessageType>100</MessageType>
    <ResponseCode>0</ResponseCode>
    <VersionHigh>2.0</VersionHigh>
    <VersionLow>1.0</VersionLow>
    <AccessProcedure>1.0</AccessProcedure>
    <AccessLocation>CDATA[[isocc=,cc=,ac=,network=HOTSPLOTS,]]</AccessLocation>
    <LocationName>CDATA[[94800463058]]</LocationName>
    <LoginURL>https://www.hotsplots.de/auth/login.php?res=wispr&amp;uamip=192.168.44.1&amp;uamport=80&amp;challenge=7e9b9ebbcbaa23dcee39cd94b42695fd</LoginURL>
    <AbortLoginURL>http://192.168.44.1:80/abort</AbortLoginURL>
    <EAPMsg>AQEABQE=</EAPMsg>
    </Redirect>
    </WISPAccessGatewayParam>
    -->
    </HTML>
    

    Heilige Scheiße – HTML-Tags in Großbuchstaben und in einen HTML-Kommentar eingebettetes XML: da waren klare SpezialexpertInnen am Werk. Wenn die Leute, die sich das ausgedacht haben, das hier lesen: Ich mache zum Sonderpreis von 9 Euro eine Fortbildung in XML Namespaces und wie sie für so Zeug einzusetzen wären.

    Ansonsten sollte ich wahrscheinlich mal bei http://www.wballiance.net vorbeischauen; vielleicht lässt sich mit diesem Zeug ja irgendwas basteln, das die lästigen Vorschaltseiten, die sowas ausliefern, ganz ohne Javascript webzaubert?

    Nur nicht gerade jetzt, denn: We are offline....

    [1]Also gut: Was wäre verkehrt daran, den Captive-Portal-Scheiß ganz zu lassen?
    [2]Ich würde gerne sagen, dass ich extra zu dessen Erwerb zum Bahnhof Mainz Römisches Theater gefahren bin, denn das ist schon einer der schönsten Bahnhofsnamen der Republik, aber das wäre eine Lüge.
  • Affen zählen ist schwer

    Nachdem mich gestern die Publikationen der Gruppe von Kathelijne Koops so gelockt haben, habe ich gleich eine durchgeblättert, und zwar „How to measure chimpanzee party size? A methodological comparison“ von Kelly van Leeuwen und KollegInnen (doi:10.1007/s10329-019-00783-4, Preprint).

    Bevor ich das lobe, muss ich etwas mosern. Erstens, weil das Ganze von unfreier Software nur so strotzt – die statistische Auswertung ist mit SPSS gemacht (geht ja auch anders), und das Paper wurde wohl in Word geschrieben, auch wenn die Metadaten des Preprints etwas verwirred aussehen (leicht redigiert):

    $ pdftk ZORA198831.pdf dump_data
    InfoKey: ModDate
    InfoValue: D:20220128142734+01&apos;00&apos;
    InfoKey: Creator
    InfoValue: Acrobat PDFMaker 17 f&#252;r Word
    InfoKey: CreationDate
    InfoValue: D:20220128142734+01&apos;00&apos;
    InfoKey: Producer
    InfoValue: GPL Ghostscript 9.25
    

    Warum da nacheinander ein „PDFMaker für Word“ und dann (?) nochmal ein Ghostscript drübergelaufen sind? Hm. Das PDF vom Verlag ist übrigens nochmal anders gemacht und meldet „Acrobat Distiller 10.1.8 (Windows)“ als die Software, die das PDF geschrieben hat. Uh. Ein wenig neugierig wäre ich nun schon, woraus das destilliert wurde.

    Zweitens ist nicht schön, dass die Open-Access-Webseite der Uni Zürich „You need to enable JavaScript to run this app.“ sagt. Das ist in diesem Fall um so weniger angebracht, als sie auch ohne Javascript eine ganz brauchbare Seite ausliefert. Allerdings fehlen in dem Word-generierten PDF die Abbildungen und Tabellen, und sie sind auch nicht erkennbar verlinkt. Immerhin sind beim Verlag (Springer) „Online Resources“ offen (während sie von Leuten, die nicht für hinreichend reiche Unis arbeiten, absurde 37.40 Euro fürs formatierte PDF haben wollen). Zumindest im Falle der ziemlich sinnlos gestapelten Ergebnisse der verschiedenen Methoden in Abbildung 1 ist das Fehlen der Abbildungen aber hier vielleicht sogar verschmerzbar.

    Ich würde noch nicht mal auf die Tests, die die AutorInnen so durchgeklickt haben, furchtbar viel geben, auch wenn sie immerhin ein wenig statistsiche Abbitte geleistet haben (das ist die realweltliche Bedeutung des dann und wann angerufenen hl. Bonferroni).

    Screenshot von Tabelle 1 aus dem bespreochenen Artikel

    Mein persönliches Highlight aus dem Artikel: Eine qualitative Betrachtung einiger systematischer Effekte. Rechte beim Japan Monkey Centre und Springer Japan KK (aus doi:10.1007/s10329-019-00783-4).

    Wirklich schade ist es aber um die Tabelle 1 (wenn die Abbildung hier nicht reicht: Libgen kann helfen). Sie liefert eine schöne Quintessenz der qualitativen Betrachtungen zu möglichen systematischen Fehlern, und die geben gute – und vor allem im Vergleich zu entsprechenden Betrachtungen in der Physik auch recht greifbare – Beispiele für das, von dem ich in meinem Lob von small data geredet habe. Van Leeuwen et al schätzen nämlich die Größe von umherziehenden Schimpansengruppen. Weil die Tiere nun in den Baumkronen umherturnen und noch dazu vielleicht nicht so gern gezählt werden, ist das nicht ganz einfach, und die Leute probieren vier verschiedene Verfahren:

    • Hingehen und Affen zählen
    • Eine Fotofalle aufstellen und sehen, wie viele Schimpansen auf den Bildern sind
    • Anrücken, wenn die Tiere weg sind und zählen, wie viele Tagesnester – leichte Konstrukte aus Blättern und Zweigen, in denen Schimpansen kleine Nickerchen halten – in den Bäumen sind
    • Anrücken, wenn die Tiere weg sind und zählen, wie viele Schlafnester – elaborierte Konstruktionen, in denen ein Schimpanse die Nacht (aber immer nur eine) verbringt – in den Bäumen sind.

    In einer idealen Welt würde für eine gegebene Gruppe immer die gleiche (kleine natürliche) Zahl rauskommen, also vielleicht 5. Und ich finde die erste wertvolle Einsicht schon mal: Selbst einer 5 kann mensch in vielen Bereichen der Wissenschaft nicht vertrauen. Na gut: Als Astronom sollte ich da nicht mit Steinen werfen, denn wir kommen ja auch mit acht, neun oder zehn (Planeten im Sonnensystem) ins Schleudern.

    Wenig überraschenderweise lieferten verschiedene Methoden tatsächlich verschiedene Ergebnisse, und zwar systematisch. Zur Erklärung schlagen die AutorInnen unter anderem vor:

    • Direkte Beobachtungen werden vermutlich große Gruppengrößen bevorzugen, da sich kleinere Gruppen noch scheuer gegenüber Menschen verhalten werden als große – und umgekehrt die Menschen größere Gruppen wegen mehr Geschrei auch leichter finden.
    • Umgekehrt werden direkte Beobachtungen eher einzelne Tiere übersehen, wenn diese besonders scheu sind, was zu einer systematischen Unterschätzung speziell bei besonders wenig an Menschen gewöhnten Gruppen führen wird.
    • Die Fotofallen könnten ähnliche Probleme haben, wenn die Schimpansen ihre Existenz spitzkriegen. Offenbar gibt es da Vermeidungsverhalten. Und natürlich haben Fotofallen nur ein endliches Gesichtsfeld, so dass sie bei realen Schimpansengrupen recht wahrscheinlich einzelne Tiere nicht erfassen werden.
    • Bei den Tagesnestern werden eher Tiere übersehen, weil einige sich gar keine Tagesnester bauen, etwa, weil sie gar kein Nickerchen halten. Und außerdem sind diese Nester häufig so locker gezimmert, dass Menschen sie übersehen. Das kann aber durchaus auch zu einer Überschätzung der mittleren Gruppengröße führen, weil kleinere Tageslager gar nicht auffallen; ähnlich würde es sich auswirken, wenn sich ein Tier zwei oder gar mehr Tagesnester baut.
    • Bei Nachtnestern könnte die Gruppengröße überschätzt werden, weil sich vielleicht mehrere Gruppen zur Übernachtung zusammentun (was dann den Übergang von systematischen Fehlern in interessante Ergebnisse markiert). Demgegenüber dürften die Probleme mit übersehenen kleinen Nachtlagern wie auch mit übersehenen Nestern bei Nachtnestern weniger ins Gewicht fallen als bei Tagnestern, einfach weil sie viel aufwändiger gebaut sind.

    Nun reichen die Daten von van Leeuwen et al nicht, diese Systematiken ordentlich zu quantifizieren, zumal sie sehr wahrscheinlich auch von allerlei Umweltbedingungen abhängig sind – im Paper geht es in der Hinsicht vor allem um die Verfügbarkeit von Obst (mit der die Gruppengröße wachsen könnte, weil mehr Tiere gleichzeitig essen können, ohne sich in die Quere zu kommen) und um die Anwesenheit fortpflanzungsbereiter Schimpansinnen.

    Dass systematische Fehler sehr wohl qualitative Ergebnisse ändern können, zeigt die Studie schön. So werden Gruppen laut Fotofallenmethode größer, wenn sie fortpflanzungsbereite Frauen umfassen; dieses Ergebnis verschwindet aber, wenn die Gruppengrößen durch direkte Beobachtungen geschätzt werden. Durch Nestzählung ist zu dieser Frage keine Aussage möglich, weil jedenfalls ohne viel Kletterei nicht herauszubekommen ist, wie es mit Geschlecht und Zykluslage der NestbauerInnen ausgesehen haben mag.

    Und auch wenn die Arbeit nicht auseinanderhalten kann, wie weit die größeren Gruppen, die sich bei Betrachtung der Nachtnester ergeben, Folge systematischer Fehler bei der Erfassung sind oder durch das Verhalten der Tiere verursacht werden: Klar ist jedenfalls, dass mensch bis auf Weiteres lieber keine Schlüsse von Nachtzählungen aufs Tagesverhalten zieht.

    Was ja auch ein schönes Ergebnis ist.

  • View with Netsurf

    A screenshot of a browser window

    An early version of this post rendered in netsurf.

    I believe about the worst threat to software freedom these days is web browsers. That is not only because they already are, for many people out there, a more relevant applications platform than their primary operating system, and that almost everything that gets run in them is extremely non-Free software. I've been linking to a discussion of this problem from these pages since this blog's day one as part of my quip on “best viewed with javascript disabled“.

    No, they are also a threat because the “major” browser engines are so humunguous that they are in effect locking out most platforms (which simply don't have enough power to run them). And they are because the sheer size and complexity of their code bases make it essentially impossible for an individual to fix almost any relevant bug in them related to rendering, javascript execution, or network interactions.

    That is why I am so grateful to the authors and maintainers of both dillo (Debian: dillo) and netsurf (Debian: netsurf-gtk, mainly), small browsers with maintainable code bases. While dillo is really basic and is missing so much of CSS and modern HTML that on today's web even many non-adversarial sites become barely usable, netsurf is usually just fine for websites respecting user rights.

    Flex layouts and the article elements: The good part of 20 years of web development after the Web 1.0.

    I have to admit I nevertheless only use it in very specific contexts, mostly because luakit with its vi-like key bindings and lua extensiblity in the end usually wins out even though I don't trust the webkit rendering engine for two cents[1]. And that is why I hadn't noticed that this blog has rendered a lot worse than it should have in netsurf. This is particularly shameful because that was mostly because I have taken liberties with web standards that I should not have taken. Apologies: Netsurf was right and I was wrong.

    I have improved that quite a bit this morning. Given I am using flex layouts quite liberally here, and these don't work in Debian stable's netsurf, the rendered pages do look quite a bit different in netsurf than on the “major” browsers. But the fallbacks are ok as far as I am concerned. Since flex layouts are among the few “innovations“ in the post-Web 1.0 ecosystem that are actually a good idea, I gladly accept these fallbacks. Let me stress again that it is a feature of a friendly web rather than a bug that pages look different in different user agents.

    Dillo, regrettably, is another matter because of the stupid^Wunderconsidered colour games I'm playing here. As things are right now, the light background below text like this one sits on an HTML5 article element, which dillo ignores. Hence, the text is black on dark green, which, well, may be barely readable but really is deeply sub-optimal. Since I consider the article element and its brethren real progress in terms of markup (the other positive “innovation” post Web-1.0), I will not change that markup just to make this render better in dillo. I may finally re-think the silly dark green background soon-ish, though.

    [1]If you feel like this, too, let's team up and massage luakit's front end to work with netsurf's rendering engine. Given the close entanglement of luakit with the webkitgtk API, this certainly will result in a very different program, and almost certainly there would be no way to re-use luakit extensions. Still, I could very well see such a thing become my main browser.
  • Wes Brot ich ess…

    Ein schrumpeliger Apfel

    Würdest du diesen Apfel in einem Supermarkt kaufen? Geht nicht mehr. Ich habe ihn vorhin gegessen. Also: Das, was Wurm und Balkonlagerung davon übrig gelassen haben. Auf der anderen Seite dürfte das Ding einen Behandlungsindex um die Null gehabt haben – siehe unten.

    Neulich hat die Parteistiftung der Grünen, die Böll-Stiftung, einen Pestizidatlas herausgegeben, eine Sammlung von Infografiken und Karten über den Einsatz von Giften aller Art in der Landwirtschaft. Wie üblich bei diesen Atlanten, haben sie das dankenswerterweise unter CC-BY publiziert, und besser noch: Die Sachen sind auch ohne Javascript leicht zugänglich[1].

    Ich hatte mir davon einige Kopfzahlen erhofft, denn ich habe wirklich kein gutes Gefühl dafür, was so an Giften auf den Feldern (und Weinbergen!) in meiner Umgebung landet und was das bedeutet. In der Hinsicht hatte ich kein Glück. Im Atlas gibts zwar haufenweise Zahlen, aber wirklich überzeugen konnten mich nur wenige, oft genug, weil sie letztlich Metriken ohne Bedeutung sind. Ein gutes Beispiel für diese Kategorie ist die Masse der Agrochemikalen (verwendet z.B. auf S. 11, S. 15, S. 44), die wohl als Proxy für „Umfang des Gifteinsatzes“ stehen soll.

    Das halte ich für profund fehlerhaft. Neonikotinoide, Glyphosat und DDT (um mal ein paar Pole aufzumachen) sind in spezifischer Giftigkeit, Wirkprofilen, Umweltauswirkungen, Kinetik und eigentlich jeder anderen Hinsicht fast völlig verschieden voneinander. „Eine Tonne Pestizid“ sagt daher so gut wie nichts aus. Obendrauf kommt noch ein kleiner Faktor Unsicherheit, ob sich die Masse auf Wirkstoffe, fertige Rezepturen oder irgendwas dazwischen bezieht, aber das wird wohl in diesem Geschäft kaum mehr als einen kleinen Faktor ausmachen – verglichen mit dem Grundproblem (in dem wir vermutlich über Faktoren von einigen tausend sprechen) wohl vernachlässigbar.

    Ähnlich schwerwiegende Einwände hätte ich zur breiten Mehrheit der Zahlen in dem Atlas: Vage beeindruckend, aber immer ein gutes Stück unterhalb der Schwelle von Wohlfundiertheit und allgemeinen Anwendbarkeit, die ein paar Ziffern zu einer Orientierung gebenden Kopfzahl machen könnten.

    Es gibt jedoch auch ohne schlagende Zahlen von werkübergreifender Bedeutung einige Einsichten, die wertvoll sind, so etwa auf S. 33 die Bankrotterklärung der Idee, durch grüne Gentechnik den Pestizideinsatz zu reduzieren. In Brasilien, wo transgene Pflanzen die Landwirschaft vollständig dominieren, sind 2019 47% mehr Pestizide ausgebracht worden als 2009. Gut: Soja (darauf schaut der Rest der Grafik, und das wird wohl auch den Pestizidverbrauch dominieren) ist in diesem Zusammenhang ein schlechtes Beispiel, denn das populäre transgene Soja („Roundup ready“) ist ja gerade designt, um große Mengen Herbizide zu überleben. Dazu sind wieder blind Massen angegeben, und die angesichts galloppierender Rodungen in Brasilien vermutlich rasch wachsende Anbaufläche wäre eigentlich auch noch einzurechnen, wenn die Zahlen einen analytischen Blick erlauben wollten.

    Aussagekräftiger wären für die behandelte Frage Zahlen für Mais gewesen (nämlich den mit der Bt-Abwehr gegen den Maiszünsler) und folglich auch Insektizide beim Mais. Aber seis drum: Die Grafik zeigt auch ohne methodische Strenge, dass es so nicht weiter gehen kann.

    A propos Mais: Dass der mit recht wenig Chemie auskommt, hat mich schon verblüfft:

    Mit "Schlechte Nachrichten für Apfel-Fans" überschriebene Grafik

    Grafik von Seite 14 des Pestizidatlasses. Die Caption im Atlas deutet an, dass der „Behandlungsindex“ etwas wie die mittlere Anzahl von Anwendungen von Pflanzenschutzmitteln ist; ob das wirklich so ist: Wer weiß? CC-BY Pestizidatlas

    Dass Wein heftig pflanzengeschützt wird, ist hier in der Gegend unübersehbar. Bei Hopfen und Äpfeln überrascht es mich aber, denn hiesige Apfelbäume in Streulagen, um die sich im Wesentlichen niemand kümmert, liefern durchaus sehr essbare Äpfel; hinreichend viele und große, um mir den ganzen Winter über die Basis für mein Frühstücksmüsli zu liefern (das Foto oben zeigt den von heute).

    Klar haben fast alle Hautdefekte, und in vielen wohnte auch mal ein Wurm – aber das tut ihrer Essbarkeit wirklich keinen Abbruch. Aus dieser Erfahrung heraus hätte ich erwartet, dass schon mit recht moderaten Interventionen supermarktkompatible Äpfel erreichbar wären. Das stimmt offenbar so nicht. Die letzten 50% zum makellosen Produkt – und wahrscheinlich auch die Spalierzucht in Monokultur – scheinen Äpfel von einer ganz einfachen zu einer ganz heikelen Kultur zu verwandeln.

    Meine Lieblingsgrafik ist schließich auf Seite 39:

    Eine Kopfzahl gibt auch das nicht her. Als Beleg für das alte Motto „Wes Brot ich ess, des Lied ich sing“ kann das aber durchaus durchgehen. Und als Illustration dafür, wie problematisch es ist, Wissenschaft – wie wir das in unserer Drittmittelkultur nun mal tun – über Geld zu regulieren.

    [1]Na ja, blöderweise ist ohne Javascript so ein doofes animiertes GIF neben jedem Ding, das runtergeladen werden kann. Tipp an die WebseitenmacherInnen: Wenn ihr diese Sorte Gimmick schon braucht, stattet ihn doch wenigstens mit einem display: none im CSS aus. Per Javascript könnt ihr das display-Attribut dann nach Bedarf konfigurieren. Nettoeffekt: UAs ohne JS (aber mit elementarem CSS) sehen keine blinkenden Trümmer.
  • Ach, Bahn, Teil 2: Maustracking ist Quatsch

    Screenshot: Blockiertes Javascript auf bahn.de

    F12 in den üblichen Browsern führt auf verschiedene Sorten von „Web-Inspektoren“. In deren „Network“-Tabs könnt ihr die Flügel'sche Metrik bestimmen: Wie viele Dateien von wie vielen Hosts zieht so eine Seite? Die Bahn-Seite schneidet dabei nicht gut ab, nicht zuletzt aufgrund des nutzerInnenfeindlichen Javascripts, das der Screenshot zeigt.

    Wieder mal habe ich auf bahn.de eine Fahrkarte gekauft, und wieder hakte es an vielen Ecken. Immer noch hat der Server darauf bestanden, ich sei eingeloggt (und hat mir nicht die Möglichkeit gegeben, mich einzuloggen), hat mir aber nicht meine Buchungs-Voreinstellungen („eine Person mit Bahncard 50“) angeboten, hat wieder ganze Seiten mit je nur einer einzelnen Frage ausgeliefert, hat kaputte Zahlungsoptionen angeboten, quittierte einen Browser-Reload mit hartem Ausloggen („fang einfach nochmal an“) und verlangte von mir ein Login beim Kartenkauf, obwohl es mich ja die ganze Zeit schon als eingeloggt geführt hat. Immerhin – niemand soll sagen, ich würde nur motzen – war das Captcha vom letzten Herbst endlich weg (eine Reaktion auf meine diesbezüglichen Mails aus dem Herbst-Blogpost habe ich natürlich nie bekommen).

    Jedenfalls reichen eigentlich ein, zwei Transaktionen auf bahn.de locker, um einige (modulo Ökonomie) leicht zu behebende Usability-Schwächen zu finden. Aber gut: Gerade bei Webseiten bin ich ja schon froh, wenn sie wenigestens ihre Quirks behalten und nicht alle paar Wochen wieder was anders (zumeist: anders kaputt) ist. Ich erwähne das alles überhaupt nur, weil bahn.de in meinem Browser mal wieder so hakelig war, dass ich nachgesehen habe, wessen Javascript mir die Bahn neben ihrem eigenen (das dürfen sie ausführen) noch andrehen will. Unter den geblockten Quellen befand sich ein Laden namens m-pathy.com. Dessen Homepage (kleines Lob am Rande: das geht, ohne Code von m-pathy.com auf der lokalen Maschine laufen zu lassen) radebrecht mit Deppen-Leerzeichen:

    m-pathy UX Insight G4 macht sichtbar, was Ihre Kunden erleben.

    Unsere Analyse Lösung ermöglicht die Auswertung tausender feingranularer Interaktionsdaten aus Mausspuren, Touch-Gesten, Klicks und Formularinteraktionen.

    Das Ergebnis sind belegbare und priorisierbare Handlungsempfehlungen für die Optimierung Ihrer klassischen oder mobilen Webseite.

    Also: Die Bahn sammelt und verarbeitet Daten über meine Mausbewegungen (na ja, natürlich nicht über meine, weil das Javascript dieser Leute bei mir nicht läuft, aber von fast allen anderen ihrer KundInnen), um so zu tun, als würde sie ihre Webseite „optimieren“.

    Da die Usability der Webseite zumindest in meinem „Erleben“ spürbar abgenommen hat – vor allem wohl wegen der Förderung von „Partnerangeboten“ und anderen Marktingkrams, aber seis drum –, würde ich schließen, dass „m-pathy UX Insight G4“ schlicht nicht funktioniert. Ok: Kann sein, dass die hautnahe Bespitzelung der NutzerInnen durch die Bahn funktioniert und sich eben nur niemand um „belegbare und priorisierbare Handlungsempfehlungen“ kümmert. Dann könnte die Bespitzelung aber auch unterbleiben, oder? Oder sind die Zwecke der Bahn ganz andere als die bei m-pathy angegebenen? Allein, mir würden da nicht viele einfallen. Was bitte soll aus meinen Mausbewegungen über der Bahn-Webseite für die Bahn schon folgen?

    Einfach mal offen (also: ohne Fragebogen) mit 10 oder 50 NutzerInnen der Webseite reden und ihnen zuhören wäre ganz klar gigantisch viel datensparsamer und, so bin ich sicher, auch erheblich wirksamer, wenn die Bahn die Seite wirklich verbessern wollte.

    Liebe Bahn, darf ich euch noch einen Neujahrsvorsatz vorschlagen? Es wäre: Buchen ohne Javascript, vielleicht sogar mit einem lokalen, Debian-paketierbaren Programm, das auf einer wohldokumentierten API aufsetzt.

  • Zu Fuß im Zug ins Netz

    Screenshot

    Wer hinreichend Geduld und Kompetenz hat, bekommt in den Zügen von Go-Ahead am Ende so eine Seite vom Captive Portal.

    Zu den ärgerlichen Folgen des Irrsinns vom „geistigem Eigentum“ gehören Captive Portals, also Webseiten, auf die mensch in öffentlichen WLANs erstmal umgeleitet wird. Erst, wer Familienpackungen Javascript ausführen lässt und schließlich per Häkchen lügt, er_sie habe die Nutzungsbedingungen gelesen und anerkannt, darf ins Netz. Allein fürs Öffnen dieser Sicherheitslücke („gehen Sie in irgendein unbekanntes Netz und lassen Sie ihren Browser allen Code ausführen, der da rauskommt, und dann ziehen Sie noch megabyteweise Bilder – vielleicht ist ja in einem der Bilder-Decoder auch noch ein Buffer Overflow“) verdient die Geistiges-Eigentum-Mafia Teeren und Federn.

    Na ja, und erstaunlich oft ist der Mist einfach kaputt. Eine besondere Kränkung für die Ingenieursabteilung meines Herzens ist, wenn die ganze aufregende Hi-Tech, mit der mensch in fahrenden Zügen ins Netz kann, prima geht, aber trotzdem kein Bit durch die Leitung zu kriegen ist, weil ein „Web-Programmierer“ im doofen Captive Portal gemurkst hat.

    Kaputt sah das WLAN für mich heute in einem Zug von Go-Ahead aus. Die Geschichte, wie ich mich dennoch ins Netz vorgekämpft habe, finde ich im Hinblick auf manuelles Fummeln an IP-Netzen instruktiv, und so dachte ich mir, ich könnte im nächsten Zug (betrieben von der Bahn und deshalb noch nicht mal mit kaputtem Internet ausgestattet) zusammenschreiben, was ich alles gemacht habe. Das Tooling, das ich dabei verwende, ist etwas, öhm, oldschool. So sollte ich statt ifconfig und route heute wohl lieber ein Programm mit dem schönen Namen ip verwenden. Aber leider finde ich dessen Kommandozeile immer noch ziemlich grässlich, und solange die guten alten Programme aus grauer Vorzeit immer noch auf eigentlich allen Linuxen rumliegen, kann ich mich einfach nicht zur Migration durchringen.

    Am Anfang stand die einfache ifupdown-Konfiguration für das Zug-Netz:

    iface roam inet dhcp
      wireless-essid freeWIFIahead!
    

    in /etc/network/interfaces.d/roam. Damit kann ich sudo ifup wlan0=roam laufen lassen, und der Kram sollte sich verbinden (wenn ihr die Interface-Umbenamsung der systemd-Umgebung nicht wie ich abgeschaltet habt, würde vor dem = einer der kompizierten „vorhersagbaren“ Buchstabensuppen der Art wp4e1 oder so stehen).

    Nur: das Netz kam nicht hoch. Ein Blick nach /var/log/syslog (wie gesagt: etwas altbackenes Tooling; moderner Kram bräuchte hier eine wilde journalctl-Kommandozeile) liefert:

    Jan  2 1████████ victor kernel: wlan0: associate with be:30:7e:07:8e:82 (try 1/3)
    Jan  2 1████████ victor kernel: wlan0: RX AssocResp from be:30:7e:07:8e:82 (capab=0x401 status=0 aid=4)
    Jan  2 1████████ victor kernel: wlan0: associated
    Jan  2 1████████ victor kernel: IPv6: ADDRCONF(NETDEV_CHANGE): wlan0: link becomes ready
    Jan  2 1████████ victor dhclient[18221]: Listening on LPF/wlan0/█████████████████
    Jan  2 1████████ victor dhclient[18221]: Sending on   LPF/wlan0/█████████████████
    Jan  2 1████████ victor dhclient[18221]: Sending on   Socket/fallback
    Jan  2 1████████ victor dhclient[18221]: DHCPDISCOVER on wlan0 to 255.255.255.255 port 67 interval 6
    Jan  2 1████████ victor dhclient[18221]: DHCPDISCOVER on wlan0 to 255.255.255.255 port 67 interval 14
    Jan  2 1████████ victor dhclient[18221]: DHCPDISCOVER on wlan0 to 255.255.255.255 port 67 interval 1
    Jan  2 1████████ victor dhclient[18221]: No DHCPOFFERS received.
    

    Das bedeutet: Das lokale „Router“ (Access Point, AP) hat mich ins Netz gelassen („associated“; das ist quasi das Stecken des Netzkabels), aber dann hat der DHCP-Server, der mir eigentlich eine IP-Adresse hätte zuteilen sollen (mit der ich dann übers lokale Netz hinauskommen könnte), genau das nicht getan: „No DHCPOFFERS received“.

    Wenn das so ist – der Rechner ist Teil von einem Ethernet-Segment, sieht mithin Netzwerkverkehr, bekommt aber keine IP-Adresse und kann also mit niemandem über TCP/IP reden –, lohnt sich ein Blick in die Pakete, die im Netzwerksegment unterwegs sind. Das geht mit fetten Grafikmonstern wie wireshark, aber für einen schnellen Blick tut es tcpdump allemal. Da wir aber noch keine Internet-Verbindung haben, können wir sicher keine IP-Adressen („192.168.1.15“) zu Namen („blog.tfiu.de“) auflösen. Tatsächlich würde der Versuch tcpdump schon zum Stehen bringen. Deshalb habe ich per -n bestellt, Adressen numerisch auszugeben:

    tcpdump -n
    

    Ich sehe dabei einen Haufen ARP-requests, also Versuche, die lokalen Adressen von Ethernet-Karten für IP-Adressen herauszufinden, von denen der Router meint, sie müssten im lokalen Ethernet-Segment sein, etwa:

    12:█████████383626 ARP, Request who-has 10.1.224.139 tell 10.1.0.1, length 28
    

    Aus dieser Zeile allein kann ich schon mal raten, dass das Gateway, also der Router, über den ich ins Internet kommen könnte, wohl die Maschine 10.1.0.1 sein wird. Dabei sind Adressen aus dem 10er-Block leicht magisch, weil sie nicht (öffentlich) geroutet werden und daher (im Gegensatz zu normalen IP-Adressen) im Internet beliebig oft vorkommen können. Sie sind deshalb für relativ abgeschottete Unternetze wie hier im Zug populär (ins richtige Netz gehts dann per Network Address Translation NAT) – und Maschinen mit .1 hinten dran sind konventionell gerne Router.

    Wenn ich mit dem Gateway reden will, brauche ich immer noch selbst eine IP-Adresse. Ohne DHCP-Server bleibt mir wenig übrig als mir selbst eine zu nehmen. Das ist normalerweise ein unfreundlicher Akt, denn wer eine Adresse wiederverwendet, die jemand anders in dem Teilnetz schon hat, macht die Verbindung für den anderen Rechner im Effekt kaputt. Insofern: Wer etwas wie das Folgende tut, sollte erstmal für eine Weile dem tcpdump zusehen und sicherstellen, dass sonst niemand mit der gewählten Adresse unterwegs ist. Wer öfter zu Stunts dieser Art genötigt ist, möge sich arping ansehen – damit kann mensch kontrolliert nachsehen, ob eine Adresse frei ist.

    In meinem Fall war es ruhig im Netz – es hat ja vermutlich auch kaum jemand sonst eine Verbindung bekommen. Ich fühlte mich also hinreichend sicher, irgendwas zu probieren, das der Router wohl als „im eigenen Netz“ akzeptieren würde. Die so geratene Adresse habe ich versuchsweise auf meine Netzwerkschnittstelle geklebt:

    sudo ifconfig wlan0 inet 10.1.0.105 up
    

    Ein guter erster Tipp für diese Zwecke ist eine Adresse, bei der nur das letzte Byte anders ist als in der Router-Adresse (in antikem Jargon: „im selben Klasse C-Netz“). In diesem Fall wäre es denkbar, noch mehr zu ändern, denn im antiken Jargon ist 10.0.0.0 ein A-Netz („die Netzmaske ist 255.0.0.0“), was mit dem Kommando oben (das nicht explizit eine andere Netzmaske gibt) den lokalen Rechner Pakete, die an eine 10.irgendwas-Adresse gehen, an wlan0 schicken lässt. Allerdings macht fast niemand mehr Routen nach diesen Regeln, und so ist es nicht unwahrscheinlich, dass der Router allzu weit entfernte Adressen routen will oder jedenfalls nicht einfach wieder ins lokale Netz-Segment zurückschickt. So in etwa ist die Logik, die mich auf die IP-Adresse oben gebracht hat.

    Wenn meine Vermutungen richtig waren, hätte ich nach dem ifconfig mit dem vermuteten Router bei 10.1.0.1 IP sprechen können. Der Klassiker zur Konnektivitätsprüfung ist ping, und wirklich:

    ping 10.1.0.1
    

    kriegte brav Paket für Paekt zurück. Wow! Immerhin schon IP!

    Ein DHCP-Server konfiguriert als nächstes normalerweise die „Default-Route“, also das, was der Rechner mit Paketen tun soll, für die er nichts anderes weiß. Rechner am Rande des Netzes (und wir ungewaschenen Massen sind eigentlich immer mehr oder weniger am Rande des Netzes) schicken in der Regel alle Pakete, die nicht ins lokale Netz gehen, an einen (und nur einen) Router, nämlich ihr Gateway. Dieses Gateway legt mensch mit meinen alten Werkzeugen so fest:

    sudo route add default gw 10.1.0.1
    

    Damit könnte ich im Netz sein. Ein schnelles ssh auf eine meiner Maschinen im Netz führt aber zu nichts: meine Maschine kann keine Namen auflösen. Ach ja, das ist noch etwas, das normalerweise ein DHCP-Server macht: der lokalen Maschine Adressen geben, an denen sie Namen auflösen kann (die DNS-Server). Über (inzwischen potenziell sehr komplizierte) Umwege enden diese Adressen in gewissem Sinn in der Datei /etc/resolv.conf; dort erwartet sie jedenfalls die C-Bibliothek.

    Nun ist aber der DHCP-Server gerade kaputt. Die Namen von DNS-Servern, die aus einem bestimmten Netz heraus funktionieren, kann mensch jedoch nicht raten. Manchmal – typisch bei privaten Netzen – tut es der Router selbst. Andererseits betreibt Google unter 8.8.8.8 einen DNS-Server, und so ungern ich Google-Dienste empfehle: Die 8.8.8.8 verwende ich in Notsituationen wie dieser. Nur bin ich ja immer noch im Captive Portal, und der Router mag meine Versuche, mit dem Google-DNS zu reden, unterbinden. Viele Captive Portals tun das nicht (aus relativ guten Gründen). Und wirklich:

    ping 8.8.8.8
    

    kommt gut zurück. Andererseits kann mir das Captive Portal natürlich alles vorspielen. Ist da wirklich ein DNS-Server?

    Das Tool der Wahl zum Spielen mit DNS-Servern ist dig [1]. Im einfachsten Fall bekommt dig einen Namen als Parameter, das wird hier zu nichts führen, denn noch hat mein Rechner ja kein DNS. Eine Stufe komplizierter übergibt mensch noch eine Nameserver-Adresse hinter einem @, also:

    dig blog.tfiu.de @8.8.8.8
    

    Und das kommt zurück! Mit der richtigen Information, nicht irgendeinem Mist, den sich das Captive Portal ausdenkt. Damit kann ich für meine temporäre Netzverbindung mein /etc/resolv.conf ändern zu:

    nameserver 8.8.8.8 …
  • Fernseh-Livestreams mit Python und mpv

    Nachtrag (2022-10-11)

    Ich habe das Programm jetzt auf codeberg untergebracht

    Die Unsitte, alles in den Browser zu verlegen, ist ja schon aus einer Freiheitsperspektive zu verurteilen – „die Plattform“ gibt die Benutzerschnittstelle vor, kann die Software jederzeit abschalten und sieht (potenziell) noch den kleinsten Klick der NutzerIn (vgl. WWWorst App Store). Bei Mediatheken und Livestreams kommt noch dazu, dass videoabspielende Browser jedenfalls in der Vergangenheit gerne mal einen Faktor zwei oder drei mehr Strom verbraucht haben als ordentliche Videosoftware, von vermeidbaren Hakeleien aufgrund von schlechter Hardwarenutzung und daraus resultierendem Elektroschrott ganz zu schweigen.

    Es gibt also viele Gründe, speziell im Videobereich den Web-Gefängnissen entkommen zu wollen. Für übliche Videoplattformen gibt es dafür Meisterwerke der EntwicklerInnengeduld wie youtube-dl oder streamlink.

    Für die Live-Ströme der öffentlich-rechtlichen Anstalten hingegen habe ich zumindest nichts Paketiertes gefunden. Vor vielen Jahren hatte ich von einem Freund ein paar screenscrapende Zeilen Python erledigt dazu. In diesen Zeiten müssen allerdings Webseiten alle paar Monate komplett umgeschrieben („jquery ist doch total alt, angular.js hat auch schon bessere Tage gesehen“) und regelauncht werden, und das Skript ging mit einem Relaunch ca. 2015 kaputt. Als ich am Freitag die Tagesschau ansehen wollte, ohne DVB-Hardware zu haben, habe ich mich deshalb nach einer Neufassung des Skripts umgesehen.

    Das Ergebnis war eine uralte Seite mit mpv-Kommandozeilen und ein Verweis auf ein von den MediathekView-Leuten gepflegtes Verzeichnis von Live-Strömen. Da stehen zwar oben alt aussehende Timestamps drin, das Log aber zeigt, dass der Kram durchaus gepflegt wird.

    Aus letzterem habe ich livetv.py (ja, das ist ein Download-Link) gestrickt, ein weiteres meiner Ein-Datei-Programme. Installiert mpv (und Python, klar), macht chmod +x livetv.py und sagt dann ./livetv.py ARD Livestream – fertig. Bequemer ist es natürlich, das Skript einfach irgendwo in den Pfad zu legen.

    Das Argument, das das Programm haben will, kann irgendeine Zeichenfolge sein, die eindeutig einen Sender identifiziert, also nur in einem Sendernamen vorkommt. Welche Sender es gibt, gibt das Programm aus, wenn es ohne Argumente aufgerufen wird:

    $ livetv.py
    3Sat Livestream
    ...
    PHOENIX Livestream
    

    Mit der aktuellen Liste könnt ihr z.B. livetv.py Hamburg sagen, weil „Hamburg“ (auch nach Normalisierung auf Kleinbuchstaben) nur in einer Stationsbezeichnung vorkommt, während „SWR“ auf eine Rückfrage führt:

    $ livetv.py SWR
    SWR BW Livestream? SWR RP Livestream?
    

    „SWR BW“ (mit oder ohne Quotes auf der Kommandozeile) ist dann eindeutig, woraufhin livetv an den mpv übergibt.

    Ich gehe davon aus, dass die Anstalten die URLs ihrer Streams auch weiterhin munter verändern werden. Deshalb kann sich das Programm neue URLs von den MediathekView-Leuten holen, und zwar durch den Aufruf:

    $ livety.py update
    

    Das schreibt, wenn alles gut geht, die Programmdatei neu und funktioniert mithin nur, wenn ihr Schreibrechte auf das Verzeichnis habt, in dem livetv.py liegt – was besser nicht der Fall sein sollte, wenn ihr es z.B. nach /usr/local/bin geschoben habt.

    A propos Sicherheitsüberlegungen: Der update-Teil vertraut gegenwärtig ein wenig den MediathekView-Repo – ich entschärfe zwar die offensichtlichsten Probleme, die durch Kopieren heruntergeladenen Materials in ausführbaren Code entstehen, aber ich verspreche nicht, raffinierteren Angriffen zu widerstehen. Abgesehen vom update-Teil halte ich das Programm für sicherheits-unkritisch. Es redet selbst auch nicht mit dem Netz, sondern überlässt das dem mpv.

    Livetv.py sagt per Voreinstellung dem mpv, es solle einen „vernünftigen“ Stream aussuchen, was sich im Augenblick zu „2 Mbit/s oder weniger“ übersetzt. Wer eine andere Auffassung von „vernünftig“ hat, kann die --max-bitrate-Option verwenden, die einfach an mpvs --hls-bitrate weitergereicht wird. Damit könnt ihr

    $ livetv.py --max-bitrate min arte.de
    

    für etwas sagen, das für die Sender, die ich geprüft habe, auch auf sehr alten Geräten noch geht,

    $ livetv.py --max-bitrate max arte.fr
    

    für HD-Wahnsinn oder

    $ livetv.py --max-bitrate 4000000 dw live
    

    für einen Stream, der nicht mehr als 4 MB/s verbraucht.

    Technics

    Die größte Fummelei war, die Kanalliste geparst zu bekommen, denn aus Gründen, für die meine Fantasie nicht ausreicht (MediathekView-Leute: Ich wäre echt neugierig, warum ihr das so gemacht habt), kommen die Sender in einem JSON-Objekt (statt einer Liste), und jeder Sender hat den gleichen Schlüssel:

    "X" : [ "3Sat", "Livestream", ...
    "X" : [ "ARD", "Livestream", ...
    

    – ein einfaches json.loads liefert also ein Dictionary, in dem nur ein Kanal enthalten ist.

    Auch wenn ich sowas noch nie gesehen habe, ist es offenbar nicht ganz unüblich, denn der json-Parser aus der Python-Standardbibliothek ist darauf vorbereitet. Wer ein JSONDecoder-Objekt konstruiert, kann in object_pairs_hook eine Funktion übergeben, die entscheiden kann, was mit solchen mehrfach besetzen Schlüsseln pasieren soll. Sie bekommt vom Parser eine Sequenz von Schlüssel-Wert-Paaren übergeben.

    Für meine spezielle Anwendung will ich lediglich ein Mapping von Stationstiteln (in Element 3 der Kanaldefinition) zu Stream-URLs (in Element 8) rausziehen und den Rest der Information wegwerfen. Deshalb reicht mir Code wie dieser:

    def load_stations():
      channels = {}
      def collect(args):
        for name, val in args:
          if name=="X":
            channels[val[2]] = val[8]
    
      dec = json.JSONDecoder(object_pairs_hook=collect)
      dec.decode(LIST_CACHE)
    
      return channels
    

    – das channels-Dictionary, das collect nach und nach füllt, ist wegen Pythons Scoping-Regeln das, das load_stations definiert. Die collect-Funktion ist also eine Closure, eine Funktion, die Teile ihres Definitionsumfelds einpackt und mitnehmt. So etwas macht das Leben von AutorInnen von Code sehr oft leichter – aber vielleicht nicht das Leben der späteren LeserInnen. Dass die collect-Funktion als ein Seiteneffekt von dec.decode(...) aufgerufen wird und dadurch channels gefüllt wird, braucht jedenfalls erstmal etwas Überlegung.

    Der andere interessante Aspekt am Code ist, dass ich die Liste der Live-Streams nicht separat irgendwo ablegen wollte. Das Ganze soll ja ein Ein-Datei-Programm sein, das einfach und ohne Installation überall läuft, wo es Python und mpv gibt. Ein Blick ins Commit-Log der Kanalliste verrät, dass sich diese allein im letzten Jahr über ein dutzend Mal geändert hat (herzlichen Dank an dieser Stelle an die Maintainer!). Es braucht also eine Möglichkeit, sie aktuell zu halten, wenn ich die Liste nicht bei jedem Aufruf erneut aus dem Netz holen will. Das aber will ich auf keinen Fall, weniger, um github zu schonen, mehr, weil sonst github sehen kann, wer so alles wann livetv.py verwendet.

    Ich könnte die Liste beim ersten Programmstart holen und irgendwo im Home (oder gar unter /var/tmp) speichern. Aber dann setzt zumindest der erste Aufruf einen Datenpunkt bei github, und zwar für neue NutzerInnen eher überraschend. Das kann ich verhindern, wenn ich die Liste einfach im Programm selbst speichere, also selbstverändernden Code schreibe.

    Das ist in interpretierten Sprachen eigentlich nicht schwierig, da bei ihnen Quellcode und ausgeführtes Programm identisch sind. Zu den großartigen Ideen in Unix gehört weiter, dass (das Äquivalent von) sys.argv[0] den Pfad zur gerade ausgeführten Datei enthält. Und so dachte ich mir, ich ziehe mir einfach den eigenen Programmcode und ersetze die Zuweisung des LIST_CACHE (das json-Literal von github) per Holzhammer, also regulärem Ausdruck. In Code:

    self_path = sys.argv[0]
    with open(self_path, "rb") as f:
      src = f.read()
    
    src = re.sub(b'(?s)LIST_CACHE = """.*?"""',
      b'LIST_CACHE = """%s"""'%(in_bytes.replace(b'"', b'\\"')),
      src)
    
    with open(self_path, "wb") as f:
      f.write(src)
    

    Dass das Schreiben ein eigener, fast atomarer Schritt ist, ist Vorsicht: Wenn beim Ersetzen etwas schief geht und das Programm eine Exception wirft, ist das open(... "wb") noch nicht gelaufen. Es leert ja die Programmdatei, und solange es das nicht getan hat, hat mensch eine zweite Chance. Ähnlich übrigens meine Überlegung, das alles in Binärstrings zu bearbeiten: beim Enkodieren kann es immer mal Probleme geben, die am Ende zu teilgeschriebenen Dateien führen können. Vermeide ich Umkodierungen, kann zumindest die Sorte von Fehler nicht auftreten.

    Wie dem auch sei: Dieser Code funktioniert nicht. Und zwar in recht typischer Weise innerhalb der Familie von Quines und anderen Selbstanwendungsproblemen: das re.sub erwischt auch seine beiden ersten Argumente, denn beide passen auf das Muster LIST_CACHE = """.*?""". Deshalb würden von livetv.py update auch diese beiden durch das json-Literal mit den Senderdefinitionen ersetzt. Das so geänderte Programm hat zwei Syntaxfehler, weil das json natürlich nicht in die String-Literale passt, und selbst wenn es das täte, gingen keine weiteren Updates mehr, da die Such- und Ersatzpatterns wegersetzt wären.

    Eine Lösung in diesem Fall ist geradezu billig: in Python kann mensch ein Leerzeichen auch als '\x20' schreiben (das ASCII-Zeichen Nummer 0x20 oder 32), und schon matcht der reguläre Ausdruck nicht mehr sich selbst:

    re.sub(b'(?s)LIST_CACHE\x20= """.*?"""',
      b'LIST_CACHE\x20= """%s"""'...
    

    Sicherheitsfragen

    Ein Programm, das Daten aus dem Netz in sich selbst einbaut, muss eigentlich eine Ecke vorsichtiger vorgehen als dieses hier. Stellt euch vor, irgendwer bekommt etwas wie:

    { "Filmliste": [....,
      "X": ["...
      "Igore": ['"""; os.system("rm -r ~"); """']
    }
    

    in das MediathekView-Repo committet; das würde für die MediathekView immer noch prima funktionieren, das Objekt mit dem Schlüssel Ignore würde fast sicher tatsächlich einfach ignoriert.

    Wer dann allerdings livetv.py update laufen lässt, bekommt den ganzen Kram in Python-Quelltext gepackt, und der Inhalt des Ignore-Schlüssels wird vom Python-Parser gelesen. Der sieht, wie der lange String mit den drei Anführungszeichen geschlossen wird. Danach kommt eine normale Python-Anweisung. Die hier das Home-Verzeichnis der NutzerIn löscht. Python wird die treu ausführen. Bumm.

    So funktioniert das in Wirklichkeit zum Glück nicht, denn ich escape im realen Code Anführungszeichen (das .replace(b'"', b'\\"')). Damit …

  • Kohlendioxid auf dem Balkon

    Nicht offensichtlich korrelierte Kurven von CO_2, Windgeschwindigkeit und Temperatur

    CO2-Konzentrationen auf meinem Straßenbalkon, zusammen mit Windgeschwindigkeiten und Temperaturen. Das ist ein SVG, es lohnt sich also durchaus, in einem separaten Browserfenster in den Plot zu zoomen.

    Ich habe neulich eine längere Zeitreihe mit CO2-Konzentrationen auf meinem „vorderen” Balkon genommen. Zur Einordnung: Das Messgerät steht so etwa 10 Meter über und 15 Meter neben einer halbwegs viel befahrenen Straße. Ob das wohl etwas mit den wilden Schwankungen zu tun hat, die in der Kurve oben vor allem um den 9.11. herum zu sehen sind? Muss ich meine Einschätzung von neulich, einzelne Autos seien selbst im mittleren Nahbereich im CO2 kaum nachzuweisen (nun: an der frischen Luft, natürlich), revidieren?

    Verheizt jemand 100000 Tonnen Kohlenstoff am Tag?

    Wer die Kurven von Windgeschwindigkeit[1] und CO2-Konzentration vergleicht, könnte schon glauben wollen, ohne externe Frischluftzufuhr (also bei niedrigen Windgeschwindigkeiten) gehe das CO2 lokal merklich nach oben. Wirklich überzeugen kann mich aber keine Korrelation zwischen den verschiedenen geplotteten Größen.

    Darum gehe ich die Frage zunächst deduktiv an: woher könnten die enormen Schwankungen der CO2-Konzentration wohl kommen? Wir reden hier von einer Spanne zwischen 260 ppm und über 400 ppm, wobei es vorkommen kann, dass ich innerhalb von wenigen Stunden 100 ppm mehr CO2 sehe. Der langfristig ansteigende Trend macht mir übrigens weniger Sorgen: Wenn die Photosyntheserate Richtung Winter dramatisch sinkt, die Emission aber z.B. wegen Heizung eher zunimmt, ist das angesichts der beschränkten globalen Durchmischung der Atmosphäre auf der Erde zu erwarten[2], auch wenn das vielleicht nicht gerade innerhalb von zwei Wochen vonstatten gehen sollte.

    Mit den Werkzeugen aus dem Artikel zu meiner Heizleistung von neulich kann mensch abschätzen, was so eine Konzentrationsschwankung in einer lokal gut durchmischten Atmosphäre in, sagen wir, verbranntem Kohlenstoff bedeuten würde.

    Dafür muss ich erst überlegen, wie viele CO2-Teilchen ΔNCO2, oder der Bequemlichkeit halber eher welche CO2-Stoffmenge ΔnCO2 = NCO2 ⁄ A („in mol”) es braucht, um die Konzentration (in ppm, also CO2-Molekülen pro Million Teilchen insgesamt) innerhalb eines angenommenen Volumens V um das Δcppm zu erhöhen, das ich aus dem Plot ablese. Gemäß meinen Rezepten von neulich ist das:

    ΔnCO2 = (V)/(Vm)⋅Δcppm⋅106, 

    wobei Vm wieder das Normvolumen ist (22.4 Liter pro mol); das A von oben war die Avogadro-Konstante. Um herauszukriegen, wie viel Kohlenstoff (sagen wir, in Kilogramm) ich verbrennen muss, um diese Änderung quasi durch „frisches“ CO2 hinzukriegen, muss ich das nur noch mit dem Atomgewicht von Kohlenstoff uC multiplizieren.

    Das Atomgewicht ist, weil Kohlenstoffkerne meist 6 Protonoen und 6 Neutronen enthalten, mit 12 g/mol gut abgeschätzt (ganz genau ist das nicht, vor allem weil in der Atmosphäre auch etwas C-13 und sogar ein wenig C-14 herumschwebt). In dieser Kopfzahl steht das Gramm aus historischen Gründen. Das Mol wurde so definiert, dass die Zahl der Nukleonen im Kern so in etwa das Atomgewicht liefert, als in der Wissenschaft das cgs-System (aus Zentimeter, Gramm und Sekunde) seine große Zeit hatte. Würde mensch das Mol in den heutigen SI-Zeiten (na gut: die meisten AstronomInnen bleiben dem cgs verhaftet und reden zum Beispiel über Energien in erg) definieren, wäre die Avogadro-Konstante um einen Faktor 1000 (nämlich den Faktor zur SI-Einheit Kilogramm) größer.

    Wie auch immer: Wenn ich mir mal vorstelle, dass das, was ich da auf meinem Balkon messe, repräsentativ für den Umkreis von 10 km und bis in eine Höhe von 2 km wäre (mensch ahnt schon: Ich eröffne hier eine Reductio ad absurdum), komme ich auf ein Volumen von

    V = 2⋅π⋅(10  km)2⋅(2  km) ≈ 1.3⋅1012  m3

    was mit Vm ≈ 0.02 m3 ⁄  mol, einer Änderung von 100 ppm, die mensch als Sprung am 9. und 10.11. sehen kann, sowie der Formel oben auf

    ΔmC  = uC(V)/(Vm)⋅Δcppm⋅106  ≈ 0.012 kg ⁄ mol(1.3⋅1012  m3)/(0.02 m3 ⁄  mol)⋅100⋅10 − 6  ≈ 8⋅107  kg

    oder achzigtausend Tonnen verbrannten Kohlenstoff führt. Das klingt nach richtig viel und ist es auch. Aber das Volumen, das ich hier betrachte, sind eben auch 1200 Kubikkilometer, und wer sich erinnert, dass ein Kubikmeter eines normalen Gase bei Normalbedingungen um die 1 kg wiegt, kann leicht ausrechnen, dass die Luft in diesem Volumen 1.2⋅1012  kg (oder 1.2 Milliarden Tonnen – Luft in großen Mengen ist überhaupt nicht leicht) wiegen wird. Dieser ganze Kohlenstoff macht also ungefähr 0.07 Promille (oder 70 Milionstel) der Masse der Atmosphäre aus, was ganz gut mit den 100 ppm in Teilchen zusammengeht, die wir in die ganze Rechnung reingesteckt haben.

    Andersrum gerechnet

    Tatsächlich kann mensch die Kohlenstoffmasse, die eine Erhöhung der Teilchenkonzentration in einem Gasvolumen bewirkt, auch so herum abschätzen. Der Umrechnungsfaktor von Teilchen- zu Massenkonzentration ist der Faktor zwischen den Dichten von CO2 und Luft. Das Verhältnis dieser Dichten ist wiederum das der jeweiligen Atommassen, solange jedes Teilchen das gleiche Volumen einnimmt; das schließlich folgt aus der Annahme, dass die Gase ideal sind, was wiederum für unsere Abschätzungen überallhin gut genug ist.

    Für CO2 ist das mit den überwiegend vorkommenden Isotopen von Sauerstoff und Kohlenstoff 16 + 16 + 12 = 44, für Luft, wenn wir nur auf den Stickstoff N2 schauen, 14 + 14 = 28. Demnach macht 1 ppm in der Teilchenzahl von CO2 44 ⁄ 28 ≈ 1.6 ppm in der Masse aus, solange die CO2-Konzentration so gering ist, dass tatsächlich N2 die Dichte dominiert.

    Andererseits macht Kohlenstoff nur 12 ⁄ 44 ≈ 0.3 an der Masse im CO2 aus, die Zunahme an Kohlenstoff ist demnach nur ein Drittel von dem gerade berechneten 1.6, also etwas wie 0.5. Folglich werden aus 100 ppm Änderung in der Teilchenzahl etwas wie 100⋅0.5 = 50  ppm Änderung in der Masse; wer das genauer rechnet, bekommt auf diese Weise natürlich das gleiche Resultat wie oben raus.

    Wie herum mensch das auch rechnet, es ist klar, dass niemand in der kurzen Zeit so viel Kohlenstoff verbrennt. Ein schneller Reality Check: Meine Kohlendioxid-Kopfzahl war, dass die BRD 2/3 Gigatonnen im Jahr emittiert, was mit dem C/CO2-Verhältnis von 0.3 von oben ungefähr 200 Megatonnen Kohlenstoff entspricht, oder irgendwas wie gut 500000 Tonnen am Tag. Damit wäre die Zunahme, die ich hier sehe, rund ein Sechstel des gesamten Kohlenstoffbudgets der BRD, und mehr, wenn der Anstieg schneller als in einem Tag vonstatten geht: Das ist (fast) natürlich Quatsch.

    Aber was ist es dann? Noch immer gefällt mir die These ganz lokaler Schwankungen nicht. Wenn hier wirklich nur das CO2 von Autos und Heizungen nicht mehr weggepustet würde, müsste die Korrelation zwischen CO2 und Wind viel deutlicher sein.

    Ist es eine die Abgasfahne des GKM?

    Nächster Versuch: Rund 12 km westlich von meiner Wohnung läuft das Großkraftwerk Mannheim („GKM“). Wenn das Volllast fährt und meine Wohnung in seine Abgasfahne kommt, könnte das so ein Signal geben?

    Nun, so ein Kraftwerk liefert ungefähr 1 Gigawatt elektrische Leistung (wie mir der Wikipedia-Artikel dazu gerade verrät: darunter 15% des deutschen Bahnstroms), was bei einem Wirkungsgrad von 1/3 (ok, bei modernen Kohlekraftwerken ist das noch ein wenig mehr, aber als Kopfzahl taugt es) auf 3 Gigawatt thermische Leistung führt (tatsächlich nennt die Wikpedia eine Bruttoleistung von 2146 MW für das GKM).

    Aus den 394 kJ/mol, die bei der Verbrennung von Kohlenstoff frei werden (vgl. den Artikel zu meiner thermischen Leistung) könnte mensch jetzt die CO2-Emission aus der Bruttoleistung ableiten, aber ich bin mal faul und sehe beim WWF nach, der für Kraftwerke dieser Größenordnung ansagt, für eine Kilowattstunde Strom (wir sind dann also wieder bei der Nutzleistung) werde rund ein Kilogramm CO2 emittiert.

    Wenn das Kraftwerk also Volldampf (rund ein GW netto) macht, wird es etwa

    109  W⋅0.001 kg ⁄ Wh = 106 kg ⁄ h

    CO2 emittieren, also etwa 1000 Tonnen, was wiederum mit unserem 0.3-Faktor zwischen Kohlenstoff und CO2 zu einem Kohleverbrauch von 300 Tonnen pro Stunde führt.

    Damit leert das Kraftwerk unter Vollast ein Großes Rheinschiff in zehn Stunden – das scheint mir zwar schon sehr schnell zu gehen, ist aber auch nicht gänzlich unplausibel. Gegenrechnung: Das WWF-Dokument von oben nennt 7.7⋅109  kg ⁄ a als CO2-Emission des GKM im Jahr 2006. Mit der Ur-Kopfzahl π ⋅ 1e7 Sekunden pro Jahr übersetzt sich das in eine mittlere Emission von etwa 200 kg pro Sekunde oder gut 1000 Tonnen pro Stunde. Das passt fast zu gut, denn als jemand, der das Kraftwerk von seiner Leseecke aus sehen kann, kann ich zuverlässig sagen, dass das Ding keineswegs durchläuft. Andererseits hatte das Kraftwerk 2006 auch noch einen Block weniger, und überhaupt ist in der Rechnung genug Luft für Stillstandszeiten.

    Nehmen wir …

Seite 1 / 2 »

Letzte Ergänzungen