VPNs, Proxies und Datenschutz

Original: VPNs, proxies and privacy.

In unserer Serie über Datenschutz und Sicherheit befassen wir uns mit echten VPNs, sicheren und anonymisierenden Web-Proxies, Browser-VPNs und erklären, worauf Sie bei einem VPN-Service achten müssen.

TEAMBOLG BLOG
Veröffentlicht am 17.04.2018 von Tarquin Wilton-Jones

—- Cover Bild —-

Normalerweise wird eine Verbindung zwischen Ihrem Browser und einer Website von Ihrem Browser zu Ihrem Computer, von Ihrem Computer zu Ihrem WiFi oder Heimnetzwerk (falls vorhanden), von Ihrem Heimnetzwerk zu Ihrem Internet Service Provider (ISP), von Ihrem ISP zu den nationalen Internet-Betreibern Ihres Landes, von den nationalen Internet-Betreibern Ihres Landes zu den nationalen Internet-Betreibern der Website, von den nationalen Internet-Betreibern des Landes der Website zu den Hosting-Providern der Website übertragen.

Das sind viele Schritte! In der Tat, der Verkehr kann auch auf dem Weg über andere Länder laufen, je nachdem, wo in der Welt Sie und die Website sich befinden.

Kein Anspruch auf Privatsphäre bei unsicheren Verbindungen

Bei einer unsicheren Verbindung kann jeder, der einen Teil dieser Verbindung kontrolliert oder teilt, die Daten sehen, die über die Verbindung gesendet wurden – sei es jemand anderes auf Ihrem Computer, Ihrem Netzwerk, Ihrem ISP, den Betreibern der verschiedenen Bereiche des Internets unterwegs, Ihrer Regierung und den Regierungen aller Länder unterwegs, dem Hosting-Provider oder jedem anderen, der eine Website auf demselben Host besitzt.

Es ist alles sichtbar.

Daten, die über sichere Verbindungen gesendet werden

Wenn eine Website eine sichere Verbindung anbietet (HTTPS-URLs mit gültigen Zertifikaten und hochwertiger Verschlüsselung) und Sie diese nutzen, können die über die Verbindung gesendeten Daten nur von Ihrem Browser und der Website eingesehen werden.

Moment, ist es wirklich so einfach?

Nicht wirklich. Um die Verbindung herzustellen, muss der Browser die IP-Adresse der Website über einen DNS-Dienst abfragen, der normalerweise von Ihrem ISP bereitgestellt wird. Er verwendet dann diese IP-Adresse, um die Verbindung herzustellen. Das bedeutet, dass jeder, der die Verbindung überwacht, die Domain der Website in einer DNS-Anfrage sieht und somit herausfinden kann, zu welcher Website Sie Verbindung aufnehmen, selbst wenn er nicht sehen kann, was gesendet wird.

Selbst wenn Sie in der Lage sind, einen sicheren DNS-Dienst zu verwenden, kann jeder, der die Verbindung überwacht, sehen, mit welcher IP-Adresse er verbunden ist und mit einem Reverse-DNS-Lookup herausfinden, welche Website Sie besuchen.

VPNs

Wenn Leute ein VPN zum Browsen benutzen, ist es normalerweise, weil sie eines von zwei sehr unterschiedlichen Dingen tun wollen:

  1. Ihre Netzwerkkommunikation vor anderen Benutzern ihres lokalen Netzwerks, ihrem ISP oder einer unterdrückenden Behörde zu verstecken.
  2. Ihre IP-Adresse aus Datenschutzgründen vor der Website verstecken, oder einfach nur um auf eine Website zuzugreifen, die den Zugang zu Verbindungen aus bestimmten Ländern blockiert.

— BILD 1 —

In ihrer reinsten Form bieten VPNs eine Möglichkeit, Ihren Computer sicher mit einem anderen Netzwerk zu verbinden, z. B. dem Arbeitsnetzwerk Ihres Arbeitgebers. Wenn Ihr Computer versucht, Daten über das Netzwerk zu senden, verschlüsselt ein VPN-Dienst auf Ihrem Computer die Daten und sendet sie über das Internet an den Ziel-VPN-Server, der sich in dem Netzwerk befindet, mit dem Sie sich verbinden möchten. Es entschlüsselt den Netzwerkverkehr und sendet ihn über das Zielnetzwerk, als ob Ihr Computer es selbst getan hätte. Die Antworten aus dem Netzwerk werden auf die gleiche Weise an Ihren Computer zurückgesendet.

Jeder, der einen anderen Teil der Verbindung auf dem Weg überwacht, kann nicht sehen, was gesendet wurde oder mit welchem Computer im Zielnetzwerk Ihr Computer verbunden war.

Klingt gut, aber ist es das, was die meisten VPN-Dienste tatsächlich tun? Die Antwort ist “Nein”. Hier kommen die Proxies ins Spiel.

Proxy-Dienste erklärt

Ein Proxy ist ein Dienst, der Anfragen an Websites im Namen Ihres Computers stellt. Der Browser ist so eingestellt, dass er sich über den Proxy verbindet. Wenn der Browser beginnt, eine Website zu laden, verbindet er sich mit dem Proxy auf die gleiche grundlegende Weise, wie er sich mit einer Website verbinden würde, und stellt seine Anfrage. Der Proxy stellt dann die Anfrage an die Website im Namen des Browsers, und wenn die Website antwortet, sendet er die Antwort an den Browser zurück.

Dies mag den Vorteil bieten, dass die Website Ihre IP-Adresse nicht sehen kann (was die zweite Gruppe von Nutzern anspricht), aber ein normaler Proxy wird Ihre IP-Adresse über den X-Forwarded-For-Header an die Website senden. Schließlich wollen die Proxy-Besitzer nicht beschuldigt werden, wenn Sie versuchen, eine Website anzugreifen – so wissen die Website-Besitzer, dass es sich tatsächlich um einen Angriff von Ihrer IP-Adresse handelt.

Natürlich können Sie auch versuchen, einen gefälschten X-Forwarded-For-Header zu Ihren Anfragen hinzuzufügen, um zu versuchen, jemand anderem die Schuld zuzuweisen, aber Websites können eine Liste bekannter und vertrauenswürdiger Proxy-Adressen verwenden, um festzustellen, ob Ihr X-Forwarded-For-Header möglicherweise gefälscht ist.

Die meisten Proxies, so genannte HTTPS-Proxies, können ebenfalls sichere Verbindungen direkt an die Website weiterleiten, da sie diese ohne die Zertifikate der Website nicht entschlüsseln können. Dies ermöglicht die Nutzung von HTTPS-Websites über einen Proxy.

Ein Proxy kann auch versuchen, die Verbindung zu entschlüsseln, aber dazu muss er dem Browser ein falsches Zertifikat – sein eigenes Root-Zertifikat – vorlegen, das der Browser als nicht vertrauenswürdig erkennt und deshalb eine Fehlermeldung anzeigt, um Sie vor dem Abfangen zu schützen. Dies wird manchmal zum Debuggen von Websites verwendet. Dabei muss die Person, die den Test durchführt, das Zertifikat des Proxys akzeptieren. Dies wird manchmal auch von Antivirenprodukten durchgeführt, damit sie die Verbindung scannen können.

Sichere Web-Proxies

Sichere Web-Proxies ermöglichen eine sichere Verbindung zum Proxy, auch wenn die zu verbindende Website eine HTTP-Verbindung (oder eine unsichere HTTPS-Verbindung) verwendet. Dies hat den Vorteil, dass andere Benutzer Ihres lokalen Netzwerks die Netzwerkdaten nicht sehen können (was die erste Gruppe von Benutzern anspricht). Sie können sehen, dass Sie sich mit einem sicheren Web-Proxy verbinden (obwohl die Verbindung wirklich nur wie eine sichere Website-Verbindung aussieht), aber sie können nicht sehen, welche Daten über diese Verbindung gesendet werden. Natürlich kann die Website immer noch den X-Forwarded-For-Header sehen, so dass sie immer noch Ihre IP-Adresse kennt (unerwünscht für die zweite Gruppe von Benutzern).

Um vertrauenswürdig zu sein, verwendet ein sicherer Web-Proxy auch Zertifikate zum Nachweis seiner Identität, so dass Sie wissen können, dass Sie sich mit dem richtigen sicheren Web-Proxy verbinden – andernfalls könnte jemand Ihre Proxy-Verbindung abfangen und einen gefälschten sicheren Web-Proxy präsentieren, so dass er Ihre Verbindung zu ihm überwachen kann.

Anonymisierende Proxies

Ein anonymisierender Proxy ist im Grunde nur ein Proxy oder ein sicherer Web-Proxy, der den X-Forwarded-For-Header bei der Verbindung zu Webseiten nicht sendet. Dies bedeutet, dass die Website Ihre IP-Adresse nicht sehen kann, wodurch Sie für die Website anonym bleiben (was für die zweite Gruppe von Nutzern attraktiv ist).

Einige Dienste bieten auch die Möglichkeit, die Seite abzufangen, um JavaScript und andere unerwünschte Inhalte zu entfernen, aber das bedeutet, dass Sie auch den Proxy-Besitzer mit allen Anmeldungen versorgen müssen, und der Proxy-Besitzer kann sehen, was Sie tun, auch auf sicheren Websites. Es tauscht nur ein Datenschutzrisiko gegen ein anderes aus.

Es scheint, dass ein anonymisierender sicherer Web-Proxy beide Fälle auf einmal lösen würde, aber es ist nicht so einfach, und es gibt viele andere Dinge zu beachten, z.B. wie Ihr Netzwerk und Ihr Computer eingerichtet sind. Ihr Computer kann auch DNS-Anfragen senden, wenn Sie eine Verbindung zu einer Website herstellen, CRL- und OCSP-Anfragen, wenn Sie Website-Zertifikate verwenden (wenn CRLSet nicht verfügbar ist), und der Browser kann auch andere Anfragen senden, z. B. Anfragen zum Schutz vor Malware oder Miniaturansichten. Hier kann ein VPN besser sein (aber es ist wichtig zu beachten, dass die meisten es nicht sind).

Es bedeutet auch, dass der Proxy-Dienst die Schuld zugewiesen bekommt, wenn ein Benutzer den Proxy benutzt, um einen Angriff zu starten. Um dies zu vermeiden, können die Proxy-Besitzer Verbindungen drosseln oder Logins verlangen und Protokolle der Verbindungen führen, so dass die richtige Person zur Verantwortung gezogen werden kann. Dies widerspricht dem Zweck für jeden, der versucht, den Proxy zum Schutz der Privatsphäre zu nutzen.

Worauf Sie bei einem VPN-Dienst achten sollten

In den meisten Fällen sind VPN-Dienste nichts anderes als ein anonymisierender, sicherer Web-Proxy mit der Bezeichnung “VPN”. Sie behaupten oft, dass sie “sichere Website-Verbindungen” oder “Ihre Website-Verbindungen verschlüsseln”. Beides stimmt nicht, aber viele Unternehmen greifen auf solche Formulierungen zurück, um mit der Konkurrenz Schritt zu halten. Ein solcher VPN-Dienst kann unmöglich eine Verbindung zu einer Website sichern, da er nur einen Teil dieser Verbindung kontrolliert.

—- BILD 2 —-

Mit anderen Worten, das VPN wird nicht als reines VPN verwendet, sondern als Proxy. Während die Verbindung zwischen Ihrem Browser und dem VPN-Server sicher verläuft, muss er das Netzwerk des VPN-Servers verlassen und ins Internet zurückkehren, um eine Verbindung zur Website herzustellen. Die Verbindung zur Website ist genauso unsicher (oder sicher, wenn sie HTTPS verwendet) wie immer. Die Verbindung konnte in der zweiten Hälfte der Fahrt noch abgefangen werden. Alles, was das VPN in diesem Fall tun kann, ist, ein wenig Privatsphäre über einen Teil der Verbindung hinzuzufügen.

Verbesserung der Privatsphäre

Wenn wir über VPNs sprechen, müssen wir uns dringend von der Verwendung von “Secure” verabschieden und über die Verbesserung der Privatsphäre sprechen, denn das ist es, was ein sicherer Web-Proxy oder VPN-as-a-Proxy tatsächlich tut.

Theoretisch müsste ein VPN-as-a-Proxy nicht anonymisiert sein, aber in der Praxis sind es fast alle.

Der größte Unterschied zwischen einem sicheren Web-Proxy und einem VPN-as-a-Proxy besteht darin, dass das VPN – bei Verwendung eines geeigneten VPN-Dienstes auf dem Computer – den gesamten relevanten Datenverkehr erfassen kann, nicht nur den vom Browser initiierten Datenverkehr. Ein VPN kann auch DNS, OCSP, CRL und jeden anderen vom Browser erzeugten Streuverkehr erfassen, der sich nicht auf die Verbindung zur Website selbst bezieht (z. B. Malware-Schutzprüfungen). In einigen Fällen kann der Browser die Anzahl dieser Anfragen reduzieren, wenn er einen sicheren Web-Proxy verwendet, z.B. wenn er eigene DNS-Anfragen stellt, aber es gibt immer noch Fälle, die nicht auf allen Systemen zuverlässig erfasst werden können. Daher ist ein VPN-as-a-Proxy besser als ein sicherer Web-Proxy, der vorgibt, ein VPN zu sein.

Darüber hinaus geht es bei sicheren (HTTPS) Verbindungen um viel mehr als nur um Verschlüsselung. Sie versichern auch, dass die Verbindung zu einer Website geht, die ein vertrauenswürdiges Zertifikat besitzt, das beweist, dass niemand die Verbindung abgefangen und eine gefälschte Kopie der Website vorgelegt hat. Ein VPN kann das nicht ändern, und es kann keine unsichere Verbindung in eine sichere Verbindung verwandeln. Ohne das Zertifikatshandling ist auch eine vollständig verschlüsselte Verbindung nicht sicher.

Browser-VPNs

Wenn eine Browser-Anwendung eine Funktion oder eine Erweiterung anbietet, die behauptet, ein VPN zu sein, das nur für diese einzelne Anwendung funktioniert, ist es ein gutes Zeichen, dass es sich nicht um ein VPN, sondern um einen anonymisierenden sicheren Web-Proxy handelt. Das macht es nicht schlecht, es bedeutet nur, dass es wahrscheinlich Einschränkungen gibt, die es daran hindern, den gesamten Datenverkehr, der mit der Verbindung zusammenhängt, zu erfassen. Es kann keinen DNS-Verkehr erfassen (aber in einigen Fällen kann es, abhängig von der Implementierung). Es darf keine vom System durchgeführten Prüfungen zum Sperren von Zertifikaten erfassen. Das bedeutet, dass es zwar den größten Teil des Datenverkehrs verbergen kann, aber dennoch kleine Informationsbits an dem Proxy vorbeikommen können, und jemand, der den Netzwerkverkehr von Ihrem Computer aus überwacht, könnte immer noch in der Lage sein, herauszufinden, welche Websites Sie besuchen – eine wichtige Überlegung zum Datenschutz, wenn Sie zur ersten Gruppe von Benutzern gehören.

Ein VPN-as-a-Proxy ist in diesem Fall viel besser, da er den gesamten Datenverkehr vom Computer erfasst. Dies bedeutet, dass Sie nicht die gleiche Wahl haben; entweder der gesamte Datenverkehr aus allen Anwendungen läuft über das VPN, oder nichts. Sie können nicht nur den Datenverkehr einer einzelnen Anwendung über das VPN laufen lassen.

Jedoch können sowohl ein anonymisierender VPN-as-a-Proxy als auch ein anonymisierender sicherer Web-Proxies Ihre IP-Adresse effektiv vor der Website verstecken, so dass die zweite Gruppe von Benutzern gut abgedeckt werden kann.

Weitere Tipps

  • Deaktivieren Sie alle Plugins, die Ihre IP-Adresse auf andere Weise preisgeben könnten.
  • Deaktivieren Sie in Vivaldi die Übertragung Ihrer lokalen IP-Adresse per WebRTC (Einstellungen – Privatsphäre – WebRTC IP-Verhalten – Übertrage IP für bessere WebRTC-Leistung).
  • Verwenden Sie ein sauberes Profil oder einen privaten Browsermodus, um vorhandene Cookies oder Cache-Dateien, die zur Identifizierung verwendet werden können, zu entfernen.

Bleiben Sie dran für weitere Tipps in unserer Serie zum Thema Datenschutz und Sicherheit.


Rechtliche Hinweise

Original Artikel und Diskussion: VPNs, proxies and privacy.
Dies ist eine inoffizielle Übersetzung auf freiwilliger Basis und ist nicht autorisiert oder geprüft von dem Unternehmen Vivaldi. Alle in der Übersetzung eventuell enthaltenen Fehler sind meine eigenen. Falls Ihnen Fehler auffallen, schreiben Sie bitte einen Kommentar unter diesem Blog Post.

In the Name of Accessibility: Check Your Alt Attributes!

Occasionally I am browsing the net with images switched off because I am only interested in the text and don’t want to download gigantic amounts of decorative images – especially since several pages started to use HDPI images which are 4 times the size needed and thus HUGE downloads – and I noticed that seemingly some authors are not aware that they are missing an important accessibility feature, that exists since about the web was invented:

The alt attribute on images.

While people who don’t switch off image loading or can see well and don’t need to use assistive technologies like e.g. screen readers to get hold of the content, users who don’t or can’t display images for various reasons are at loss in such a situation, so please, dear web developers, check if all of your non-decorative images have those alt attributes.

An easy way is to temporarily overwrite the page styles with the following stylesheet:

html::before {
    content: "All visible images are either missing alt attributes or the alt attributes don't follow the specification!" !important;
    font-family: sans-serif !important;
    font-size: 20px !important;
    font-weight: bold !important;
    text-align: center !important;
    color: #F8F8F8 !important;
    background-color: #EE3333 !important;
    padding: 5px 0 !important;
    margin: 0 !important;
    border: none !important;
    width: 100% !important;
    float: none !important;
    position: static !important;
    display: inline-block !important;
}
html {
    color: #202020 !important;
    background-color: #F8F8F8 !important;
    padding: 0 !important;
    margin: 0 !important;
    border: none !important;
    width: auto !important;
    max-width: none !important;
    display: block !important;
}
html * {
    visibility: hidden !important;
}
html area,
html img {
    visibility: visible !important;
}
html area[alt],
html img[alt] {
    visibility: hidden !important;
}
html area[alt="*"],
html area[alt=""],
html img[alt="*"],
html img[alt=""],
html img[alt^=" "] {
    outline: 4px solid rgb(51, 102, 204) !important;
    visibility: visible !important;
}

You might need to adapt it if you use a Styling Extension, this is written for direct use in Vivaldi.

In Vivaldi you can add this as page action. Just save it as Image_Alt_Debugger.css to [path to]\Application\#.#.###.##\resources\vivaldi\user_files and after the next browser restart you will see the Entry “Image Alt Debugger” in the list of page actions. (Hint: Underscores in the name get replaced by Spaces for display)

HTH 🙂

PS: Sadly you have to do that for each and every update of Vivaldi because or some unknown reason they put this stuff into the Application directory instead of the User Data directory.

Why Vivaldi’s Reader (and others) sometimes don’t do what you expect

TL;DR

Vivaldi Read Mode was was never meant for pages like Twitter, Youtube comments, or Facebook chats but for articles

As general rule of thumb you can assume that everything that has a continuous or adjacent text chunk of more than 300 characters counts as content. It may be split in multiple adjacent paragraphs, but must look content-y enough (i.e. consist of sentences) and may not contain too many links, videos or images inside of the content area (outside is fine) and does not belong to one of the “stop” classes and IDs like e.g. “comment”, “footer”, “ad”-vertisement and many others.

/TL;DR

Still there?

History

The Vivaldi Read View is – like the read view in Mozilla Firefox and the Apple Safari – based on the Readability(TM) code that was released as a labs experiment by arc90 in about 2009 under an open source license (some versions under MIT, some under Apache license). Later arc90 changed it to a server supported version that is available at readability.com

The Intention Behind It

Readability was never meant to be an ad-blocker, but always as a on-demand reader view to switch on for *articles*, meaning: Longer passages of text (important!)

It was never intended to be used on pages like Facebook, with its gazillions of short text snippets, Youtube video comments, Twitter feeds and generally not on any page that does not contain a sizable longer chunk of text in one article.

It was meant to make reading of longer texts distraction free by removing e.g. advertisements, page navigation, comments and videos or images that don’t belong to the main article content, and to re-style it with readable fonts and colors to make reading more pleasurable. 

How?!

Of course the code is not really “intelligent” (it has to be fast and may not use up too many resources), so it has to trust on some kind of heuristics to detect where the main content might be. While generally it works quite well, it may fail on some pages, especially “if the HTML is totally hosed” (not my words, that was a comment of one of the original arc90 developers)

A (simplified and not complete) Explanation:

First steps:

  • Remove all scripts.
  • Remove all styles.
  • Ignore HTML block level elements like paragraphs and divisions with less than 25 characters completely.
  • Remove HTML block level elements that have “stop” classes or IDs or tags that indicate that they are definitely not content but something else like e.g navigation, footers, comments or advertisements etc.pp.

After that the reader loops through all paragraphs, and

  • calculates the over-all score for text length by the following formula: 
    rounded-down((pure-Text character count of a page element)/100)
    and adds it to the parent element (you might see it as a container). This means: A paragraph with less than 100 characters of text does not get any bonus at all.
  • adds a base score of 1 for each remaining paragraph to the parent element 
  • assigns a score to them based on how content-y they look. This score gets added to their parent node.
  • adds additional scores that are is determined by things like number of commas (+), class names and IDs (+/-), image and link density (More than 25% of the text is links? Too many images per paragraph? Punish it!) etc.
  • punishes List, Headline, Forms and Address and some other Elements with negative scores because they are normally not part of articles, and if they are, they are usually in the same parent container as the paragraphs in a real article, so the combined score of the parent element is still high enough to count.
  • adds half of the resulting score to the grandparent elements.

When that all is done and the parent or grandparent has a high enough score, it is seen as content and gets displayed, everything else gets removed.

Probably you can imagine now, how many pitfalls are there in which content detection may fall, so please take a break if you see it fail and think about what might have caused it this time.

Personal side note (strong language warning)

All in all content detection is a bіtch and can definitely fail on some pages, especially if the “Webmasters” (I call them Crapmasters) don’t know what a HTML validator is and have never heard about structured pages and accessibility. I am speaking out of experience: Back in 2009 I started with a userscript and later made an made an extension (cleanPages, see the old my.opera page on archive org) based the full original arc90 code and fine tuned it for Opera Presto (and ported it later for the new Opera thing). It had over 250k installs and while it was fun to tweak for better results, it was a hell of a lot of work. I wrote more than 200 versions with generic fixes for “just another couple of new pages that fail” but in the end I gave in and called it a day  – there are too many broken pages out there where the webmasters seemingly do not want people to read the content. Their wish is my command 😉

So please be gentle with the Vivaldi developers – yes, there is still some fine-tuning to be done, but that is really time consuming. It will probably have to wait because there are some other, more difficult and bigger things in the pipe (hint, hint 😀 )

Thank You!

Disclaimer: While I am a “Soprano” (aka external tester for internal builds), all the views in this text are my private views and do not necessarily reflect the views or opinions of Vivaldi (the company) or any of it’s owners or employees.

Always ask for the download location (hack)

Deprecated – is now built in.

Several users, one of them me, prefer Vivaldi to ask for the download location for every download. Sadly unchecking the option Settings > Downloads: “always save to default download location” does not work for some types of downloads, especially downloads with generated files or downloads initiated with Javascript.

It is possible to do that with a little hack, but it comes with a caveat:

It breaks the “open” button behavior of the download dialog – so you have to open it manually after download.

 

Having said that, here it is:

  • look for the user profile directory of your Vivaldi in vivaldi://about  (V-Button > Help > About) and open that in your file manager.
  • close Vivaldi
  • open the file named preferences with a decent editor (You should backup that file before, just in case, you know …)
  • search for:
    "download":{
    (including the quotation marks etc.)
  • look for the } that follows immediately after that
  • before the } add the following:
    ,"prompt_for_download":true
    (including the comma and the quotation marks etc.)
  • Save the file.

 

Done. From now on Vivaldi should ask before every download – but be aware of the caveat!

 

 

 

How to get the data of the SmartRSS Extension from Opera 15++ to Vivaldi

Open Vivaldi and Opera 15+

 

In Vivaldi:

  • go to https://addons.opera.com/en/extensions/details/smart-rss/?display=en 
  • click “Add to Opera” 
  • on the popup thingy click on “get it anyway”
  • save to a place where you can find it again. 
  • open vivaldi://plugins
  • drag and drop the extension file from your file manager to the vivaldi://plugins tab

 

In Opera 15+:

  • right click on the extension’s button
  • on the popup click “Options”
  • in the options tab click on one of the “Export” buttons. I personally prefer “smart” because I want the contents of the feeds too, otherwise OPML, which works for some other readers too  
  • click on the new link that shows after you pressed the export button
  • save it in a location where you can find it again

You can close Opera now.

 

In Vivaldi:

  • open the smart RSS tab by clicking on the extension’s icon
  • click on the wrench icon
  • in the popup click on “Options”
  • in the options tab click on appropriate the import button (depending to which format you exported)
  • select previously exported file
  • wait until the import is finished successfully (there is some status information showing next to the button)

 

After those steps all feeds should be imported to Vivaldi.

 

HTH 🙂

Followup on styling Vivaldi with CSS

Outdated. No longer needed! We have Themes now 🙂

Following up the post https://quhno.vivaldi.net/2015/07/02/some-quick-vivaldi-panels-css-hacks-for-better-readability-or-accessibility/

I’ve made some smallish changes that take care of some minor things I don’t like with the UI. They can be applied the same way as in the previous blog post.

This time I move the notes editor scrollbar to the right, make the speed dial navigation a little bit less high so that it aligns with the header of the web-panels, moved the speed dial items a bit upwards and colored the panel scrollbars for the dark UI.

Broken CSS? Lint it!

While looking for something completely different I stumbled upon CSS LINT.

Quote:

Will hurt your feelings*

(And help you code better)

http://csslint.net/

Not the usual beautifier but does what it says: It tells you what you can and should improve in a similar rigorous way as JSLint does for JavaScript. Be prepared for a long list of Errors or Warnings 😉

 

While it did not hurt my feelings (Sticks and stones …) it gave me several valuable hints how to improve my CSS-Fu …

Restoring the Old Opera 12 Space Bar Behavior in Vivaldi

In the good old days of “The Real Opera(TM)” there was one feature for lazy people like me that I heavily used: The Space Bar!

I can hear you saying “The space bar is no feature, it is a key on the keyboard!” but that is only half of the truth:

If no edit field was focused but the page itself and you pressed the space bar, “The Real Opera(TM)” scrolled one page down. Nothing special so far, almost all browsers today do that (guess from which browser they copied that function ;)) but the other browsers missed one thing:

If you were on a paginated page or if the page had a LINK or an A rеl="neхt" element, or if simply the linktext contained “Next” or something similar in one of many languages,”The Real Opera(TM)” took you to the next page after you had scrolled to the bottom and hit the space bar again. I am one of those persons who actually read pages until the bottom and often follow the link to the next part of the article, so trying to restore this behavior was a must.

OK. Stop babbling and show us the code instead!

// ==UserScript== 
// @name Restore O12 space bar behavior
// @version 0.19 
// @author Roland "QuHno" Reck 
// @include http://*.* 
// @include https://*.* 
// ==/UserScript== 
(function (window) {
  "use strict";
  /*jshint browser: true, devel: false, debug: true, evil: true, forin: true, undef: true, bitwise: true, eqnull: true, noarg: true, noempty: true, eqeqeq: true, boss: true, loopfunc: true, laxbreak: true, strict: true, curly: true, nonew: true */
  function is_number(obj) {
    return !isNaN(obj - 0);
  }
  function make_ipattern(string) {
    return '/' + string + '/i';
  }
  function split_hash(obj) {
    //return (url + '#a').split('#')[0];
    return (obj.pathname + obj.search);
  }
  function getScrollMaxY() {
    return document.documentElement.scrollHeight - document.documentElement.clientHeight;
  }
  function scrollToPos(el, x, y) {
    el.scrollLeft = x;
    el.scrollTop = y;
  }
  var i,
  j,
  HREF = split_hash(window.location),
  PROTOCOL = window.location.protocol,
  HOST = window.location.host,
  HOSTPATTERN = make_ipattern(HOST),
  A,
  LRN,
  theParent,
  searchDepth,
  stop = false,
  linkContainer = false,
  regexps = {
    trim: /^\s+|\s+$/g,
    normalize: /\s{2,}/g,
    relLink: /^next$/gi,
    nextLink: /(Next\s*(page)?|Neste\s*(side)?|N(ä|ae)chste\s*(Seite)?|Weiter(e.*)?|Vorw(ä|ae)rts|Volg(ende)?\s*(bladsy|pagina)?|Verder|(Page)?\s*Suiv(ant)?(e)?(s)?|(Page)?\s*(prochaine)|Avanti|(Pag(ina)?)?\s*Succ(essiv(e|a|o)|Prossim(e|a|o))|Altr(a|o)|(P(á|a)gina)?\s*(S(e|i)guie?nte)|Próxim(a|os?)|Nästa\s*(sida)?|Næste?\s*(side)?|下一頁|下一页|Sonraki|Следующая|Далее| 下一页|下一张|下一篇|下一章|下一节|下一步|下一个|下页|后页|下一頁|下一張|下一節|下一個|下頁|後頁|다음|다음\s*페이지|次へ|Seuraava|Επόμενη|Следващ(а|о|и)\s*(страница|сайт)?|Нататък|След(в|н)а|(Пълен)?\s*Напред|Dalej|Następn(a|e|y)|Więcej|Tovább|Köv(etkező|\.)|Bővebben|Înaint(ar)?e(ază)?|Avansează|(Pagina\s*)?Următoa?r(e|ul)?|>([^\|]|$)|»([^\|]|$)|→)/i,
    extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|teaser/i,
    back: /(back|prev|earl|old|new|zurück|vorige|rückwärts|назад|<|«)/i,
    images: /\.jpe?g$|\.png$|\.webp$|\.gif$/i,
    cut: /[?#]/
  },
  actHREF,
  prevHREF,
  imageResult,
  shouldImagePreview = false,
  shouldFF = false,
  UUID = '43D82723-A99E-4BFD-ACDC-B7D8270EE75C';


  /* *********************************************************************************************
  FIND LINK TO NEXT PAGE
   */
  function analyze() {

    HREF = split_hash(window.location);
    PROTOCOL = window.location.protocol;
    HOST = window.location.host;
    HOSTPATTERN = make_ipattern(HOST);
    LRN = false;
    linkContainer = false;

    //* STEP 1: Look if the webmaster knew rel=  *//
    // If there is a <link rel="next" ... and it it points to the same origin: Use it.
    LRN = document.querySelector('link[rel="next"]');
    if (!!LRN && LRN.href.indexOf(window.location.origin) < 0) {
      LRN = false;
    }
    // If there is a <A rel="next" ... and it it points to the same origin: Use it.
    if (!LRN) {
      linkContainer = document.body.querySelector('A[rel="next"]');
      if (!!linkContainer && linkContainer.href.indexOf(window.location.origin) < 0) {
        linkContainer = false;
      }
    }

    if (!linkContainer) {

      A = document.body.querySelectorAll('A');
      for (i = 1, j = A.length - 2; i < j; [i++]) {
        // Skip this link if does not have the same origin
        if (A[i].href.indexOf(window.location.origin) < 0) {
          continue;
        }

        //* STEP 2: Try to find out if the classes and IDs of the parent elements give a hint on the next link. *//
        actHREF = split_hash(A[i]);
        prevHREF = split_hash(A[i - 1]);
        theParent = A[i];

        // We limit the search depth to 6 to avoid wading through too many elements
        searchDepth = 6;

        stop = false;
        while (searchDepth > 0 && stop === false && theParent.parentElement && theParent.parentElement !== document.body) {
          /*
          if (!theParent.parentElement) {
          // searchDepth = 0;
          break;
          }
           */

          theParent.classAndId = theParent.className + ' z ' + theParent.id;
          searchDepth = searchDepth - 1;

          // This is quite probably the wrong link.
          // May be this should be done outside of the loop because some navigations contain classes like "prev next" in the parent elements
          if (theParent.classAndId.match(regexps.back)) {
            stop = true;
            break;
          }

          // If we don't get "hard evidence" we don't want links in print or discussion things
          if (theParent.classAndId.match(regexps.extraneous)) {
            // stop = true;
            break;
          }

          // this could be the next link, if there is no better match later
          if (theParent.classAndId.match(regexps.nextLink)) {
            linkContainer = A[i];
          }
          theParent = theParent.parentElement;
        }
        //*
        // seems this hurts more than it is worth the time it saves
        if (stop) {
          continue;
        }
        // stop = false;
        //*/

        //* STEP 3: Look for numbered pagination like e.g. in forums *//
        if (is_number(A[i].textContent) && A[i].textContent > 0) {
          if (is_number(A[i - 1].textContent) === false || (A[i - 1].textContent - 0 === 0)) {
            if (is_number(A[i + 1].textContent) && A[i + 1].textContent > 0 && actHREF !== HREF && A[i].textContent !== '1') {
              linkContainer = A[i];
              continue;
            }
          }
          if (is_number(A[i - 1].textContent) && A[i - 1].textContent > 0) {

            // if the previous link is to the page where we are right now it is quite possible that this is the link to the next page
            if (prevHREF === HREF) {
              linkContainer = A[i];
              continue;

              // this is for skipped pagination where the actual page is not linked, so the link to the previous page is 2 lower than that to the next page.
            } else if ((A[i].textContent - A[i - 1].textContent - 0) === 2) {
              linkContainer = A[i];
              continue;
            }
          }
          // I don't know anymore why this was important, but it breaks somewhere if this is not in.
          if (prevHREF === HREF) {
            linkContainer = A[i];
            continue;
          }
        }

        //* STEP 4: Match IDs. Almost as good as rel, therefore: If there is a good ID, we take it and stop searching *//
        if (A[i].id) {
          if (A[i].id.match(regexps.nextLink)) {
            linkContainer = A[i];
            break;
          }
        }

        //* STEP 5: CLASSes are weaker than ID, so store but don't stop. If there is no better Link we take it. *//
        if (A[i].className) {
          if (A[i].className.match(regexps.nextLink)) {
            linkContainer = A[i];
            continue;
          }
        }

        //* STEP 6: We are down to textContent - quite weak, but better than nothing *//
        if (A[i].textContent) {
          if (A[i].textContent.replace(/\s+/g, ' ').length < 25 && A[i].textContent.match(regexps.nextLink)) {
            linkContainer = A[i];
            continue;
          }
        }

        //* STEP 7: TITLE, even weaker ... *//
        if (A[i].getAttribute("title")) {
          if (A[i].textContent.replace(/\s+/g, ' ').length < 25 && A[i].getAttribute("title").match(regexps.nextLink)) {
            linkContainer = A[i];
            continue;
          }
        }

      }

    }

    // Comment this out if you don't want a hint about the next link
    // set borders _and_ outlines because in most cases websites don't alter both at the same time
    if (!!linkContainer) {
      linkContainer.style.border = '1px solid #FF0000';
      linkContainer.style.outline = '1px solid #FFff00';
      if (!document.querySelector('a[rel="next"]')) {
        linkContainer.setAttribute('rel', 'next');
        // Experimental: Maybe the Fast Forward button reacts on that at some time in the future ... probably not.
        var evt = document.createEvent('Event');
        evt.initEvent('load', false, false);
        window.dispatchEvent(evt);
      }
    }
  }
  function doFastForward() {
    /* Hard coded exceptions for fucked up search engine pages */
    var temp = null;
    /* extra saussage for startpage.com */
    if (HOST.indexOf('startpage.com') > -1) {
      temp = document.querySelector('#nextnavbar > form > a > span.i_next');
      console.log('startpage.com fucked up');
      temp.click();
      return false;
    }

    /* Normal stuff */
    /* we've got a hard link rel */
    if (LRN && LRN.href) {
      window.location.href = LRN.href;
      return false;
    }
    if (!!linkContainer) {
      linkContainer.click();
    }
  }

  var handleKeyDown = function (e) {
    if (e.key === "PageDown" && shouldFF) {
      doFastForward();
    }
    // Don't space2next if the focus is in an editable area
    // Did I forget something?
    if (/INPUT|SELECT|TEXTAREA|CANVAS/.test(e.target.tagName) || e.target.isContentEditable || document.designMode === 'on') {
      return;
    }

    // ToDo: If there are linked imageExtensions, add the logic to show them

    // If the page is already scrolled to the bottom and the user hits space, go to the next page if a link was found.
    if (e.keyCode === 32 && shouldFF) {
      doFastForward();
    }
  };

  window.addEventListener('scroll', function (e) {

    // don't analyze if the user did not scroll to the bottom
    if (window.scrollY >= getScrollMaxY() && window.scrollY > 0) {
      analyze();
      shouldFF = true;
    } else if (window.scrollY < getScrollMaxY()) {
      console.log(shouldFF);
      shouldFF = false;
    }
  }, false);
  window.addEventListener('keydown', handleKeyDown, false);
})(window);

 

  • Copy the code into a decent text editor of your choice (Please make sure your editor is set to UTF-8 or you will get problems)
  • Save it as restoreSpaceBar.user.js (yes, with 2 dots.) to some place where you can find it again
  • Open vivaldi://extensions
  • Drag and drop that file on (in?) the page (you might need to enable the developer mode on the extensions management page before)
  • Confirm that you want to install it. That’s it.

 

Warning: It is still a work in progress and may break things.

If you have any idea how to improve it:

Feel free to leave a comment 🙂

PS: I totally forgot to put some tracking code into the monster RegExp 😀

Some quick Vivaldi Javascript hacks to add additional functions

Warning: Do not apply this code to a current version of Vivaldi. It will not work anymore because the Vivaldi code has changed!

 

As follow-up to the previous entry about Hacking the CSS, this time we add some new functions to Vivaldi. The Idea is based on the posts you can find at http://habrahabr.ru/users/23rd/topics/ (in Russian, Google translate helps if you don’t speak Russian). I changed them a bit to avoid some errors I saw in the debugging console and to make sure that they only do what they should (there were some minor glitches where unexpected things happened if you clicked in the wrong place – especially if you run the browser in UI debugging mode)

I ported 2 of the scripts:

“Click to minimize/hide the tab” which deactivates the current tab and shows the last active tab. (don’t click at the [x] – that still closes the tab ;))

“Right-click [+] button to paste and go in a new tab.

 

For testing reasons you can deactivate each of them by removing the first slash at the leading //*.

How to …?

Look in the application folder of Vivaldi for browser.html in the ressources/vivaldi folder and open it with a text editor.

Insert after the string and save it.

Create a folder in the same directory as browser.html and rename the folder to custom_ui . It should be already in the right place if you followed Method 2 in the previous entry about Hacking the CSS.

Save the following code as custom.js into the new folder:

 

(function () {     // get the whole browser UI. Do this only once!     var browser = document.body.querySelector('#browser');          // we need to emulate a click sometimes. Do this only once!     var dispatchMouseEvent = function (target, var_args) {         var e = document.createEvent("MouseEvents");         e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));         target.dispatchEvent(e);     };      /*     Single click on a tab to hide it and show the last active tab.      */ //*     var list = [];     function listTabs() {         var tabs = browser.querySelectorAll('#tabs>.tab');         list = [];         for (var i = 0; i < tabs.length; i++) {             list.push(tabs[i]);         }         list.sort(function (a, b) {             return recent(b) - recent(a);         });     }          function recent(tab) {         var page = document.querySelector('.webpageview webview[tab_id="' + tab.dataset.tabId + '"]');         if (page) {             page = page.parentNode.parentNode.parentNode.parentNode;             return parseInt(page.style.zIndex);         }         return 0;     };      function startAction(e) {         for (var i = 0; i < e.path.length; i++) {             // clicking on tab group switches should not fire, neither should clicking anywhere in the webpageview             if (e.path[i].classList && (e.path[i].classList.contains('tab-indicator') || e.path[i].classList.contains('webpageview'))) {                 break;             }             if (e.path[i].classList && e.path[i].classList.contains('active')) {                 var active = browser.querySelector('.tab.active');                 listTabs();                 dispatchMouseEvent(list[1], 'mousedown', true, true);                 break;             }         }     };          browser.addEventListener('mousedown', function (e) {         // Make sure that it fires on left mouse click only         //  e = e || window.event;         switch (e.which) {         case 1:             startAction(e);             break; // left         case 2:             break; // middle         case 3:             break; // right         }     }); //*/       /*     Right click on plus-button to paste and go      */ //*     var isItMouse = false; // Exclude responses from keyboard      //Tweak for paste in this input-field     var hiddenInput = document.createElement("input");     hiddenInput.type = "text";     browser.appendChild(hiddenInput);     hiddenInput.style.width = "0px";     hiddenInput.style.height = "0px";     hiddenInput.style.display = "none";      browser.addEventListener('contextmenu', function (e) {         // Area near square         if (e.target.className.toString().indexOf('newtab') > -1) {             isItMouse = true;             document.execCommand('paste');             return;         }         // Plus-symbol         // changed to parentElement instead of parentNode         if (e.target.parentElement.className.indexOf('newtab') > -1) {             initPaste();             return;         }         // Square         if (e.target.parentElement.parentElement.className.indexOf('newtab') > -1) {             initPaste();             return;         }     });      function initPaste() {         isItMouse = true;         hiddenInput.style.display = "block";         hiddenInput.focus();         document.execCommand('paste');     }      document.addEventListener('paste', function (e) {         if (isItMouse) {             isItMouse = false;             var url = e.clipboardData.getData('text/plain');             hiddenInput.style.display = "none"; //hide input-field for pasting              var re = new RegExp('\\r\\n', 'g'); // Delete newline characters             url = url.replace(re, '');                          // Search engines             var searchEngine = 'https://google.com/webhp?q=';             // var searchEngine = 'http://yandex.ru/search/?text=';             // var searchEngine = 'https://duckduckgo.com/?q=';             // ... or insert search string of your favorite search engine                          var active = browser.querySelector('.tab.active');             var webview = document.querySelector('#webview-container webview[tab_id="' + active.dataset.tabId + '"]');              if (url.length > 0) {                 if (checkUrl(url)) {                     webview.executeScript({                         code : "window.open('" + url + "','_blank')"                     });                 } else if (checkUrlWithoutProtocol(url)) {                     webview.executeScript({                         code : "window.open('http://" + url + "','_blank')"                     });                 } else {                     webview.executeScript({                         code : "window.open('" + searchEngine + url + "','_blank')"                     });                 }             }             //    console.log(url)         }     });          //Check url     var patternUrl = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?/#]\S*)?$/i;     var patternUrlWithout = /^(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,3})).?)(?::\d{2,5})?(?:[/?/#]\S*)?$/i;          //url with protocol     function checkUrl(str) {         return patternUrl.test(str);     }          //url without protocol     function checkUrlWithoutProtocol(str) {         return patternUrlWithout.test(str);     } //*/ })();