GlimmerBlocker Logo

GlimmerBlocker

HTTP based ad blocker for Mac OS X

No hacks, no instability
Upgrade Safari whenever you like, no need to wait for an upgraded hack

GlimmerBlocker is no longer maintained and should no longer be used as web sites have migrated from using plain http to using https (i.e. encrypted) which prohibits modification by a proxy unless you resort to install custom SSL certiticates on the client.

You should use a browser extension instead.

It was made as a hobby project during 2007-2008 by Peter Speck. It features both a custom http server and http client and a Mac OS X control panel.

GlimmerBlocker did not only block requests but also allowed for adding custom css and javascript to the pages. It also allowed for modification of the page html before the browser receives the html, which made some modifications much easier than DOM based modifications.

Object reference

There are 4 types of scripts running inside GlimmerBlocker, and some objects are only defined for some of the script types:

Sections:

General objects

These objects and functions are defined for all script types.

gb.log("I had %d birds and a %s dog", 42, "black");
// Logging to the console (use the "Open Log" button on the developer tab).
// The first parameter is a format string just like console.log() in Firebug:
//   %s: string
//   %o: dump the object in JSON-like format (no escape of keywords, supports NaN).
//   %d: decimal (no formatting yet)
//   %f: floating point (no formatting yet)

gb.dlog(...);
// same parameters as gb.log(), but logs only when the proxy server debug logging
// (gb.debug.enabled) is enabled.
// The parameters will still be evaluated, so this is only for simple logging.

var s = gb.versionString;
// contains the current GlimmerBlocker version, Read-only.
s = "1.3.5b20";

var i = gb.versionCompare(v);
// compares the version to v, and returns -1, 0 or 1. Use this for testing GB versions, e.g. 
if (gb.versionCompare("1.3.5b20") >= 0) fancyStuff();

var b = gb.debug.enabled;
var b = gb.debug.verbose;
var b = gb.debug.httpServer;
var b = gb.debug.httpClient;
var b = gb.debug.networkConfig;
var b = gb.debug.filters;
// flags which tells the state of the proxy server debug checkboxes.  Read-only booleans.

var s = gb.stringify(obj);
// converts the object into a JSON-like string using same format as %o in gb.log()

var html = gb.htmlEncode("Paul & Allan says '1 < 2'.");
// Encodes some text as html. The example returns
html = "Paul &amp; Allan says '1 &lt; 2'.";

var html = gb.attrEncode("Paul & \"Spooky\" says '1 < 2'.");
// Encodes some text as html attribute value, but without quotes.
// The example returns
html = 'Paul &amp; &#34;Spooky&#34; says &#39;1 &lt; 2&#39;.';

var a = "value=" + gb.attrQuote("simple");
var b = "value=" + gb.attrQuote("Paul & \"Spooky\" says '1 < 2'.");
// Encodes some text as html attribute value including quotes, if required.
// The example returns
a = 'value=simple';
b = 'value=\'Paul &amp; "Spooky" says &#39;1 &lt; 2&#39;.\'';
b = "value='Paul &amp; \"Spooky\" says &#39;1 &lt; 2&#39;.'"; // equivalent to the above

var ss = gb.urlEncode(s);
// encode a string with %-escapes.  Similar to escape() and encodeURIComponent() in browsers.
// gb.urlEncode("Hello, D'Angleterre") -> "Hello%2C%20D%27Angleterre"

var s = gb.urlDecode(ss);
// decodes a string with %-escapes: opposite of gb.urlEncode()
// gb.urlDecode("Hello%2C%20D%27Angleterre") -> "Hello, D'Angleterre"

var js = gb.jsEncode(s);
// encode a string to be a Javascript string token:
// gb. jsEncode(null) -> "null"
// gb. jsEncode("Hello, D'Angleterre") -> "'Hello, D\x27Angleterre'"
//           OBS: the result includes single quotes.

The gb.request object

Script types:

var s = gb.request.url; // the full URL of the request
s = "http://www.google.com/search?q=apple";

var s = gb.request.urlHost;
// only the host part of the URL
s = "www.google.com";

var port = gb.request.urlPort;
// only the portnumber of the URL
port = 80;

var s = gb.request.urlHostPort;
// the host and port, separated by colon.
s = "www.google.com:80";

var s = gb.request.urlPath;
// only the path part of the URL, excl. query. Never empty, starts always with "/".
s = "/search";

var s = gb.request.urlQuery; // only the query path of the URL, excl. query. Never null.
s = "q=apple";

var s = gb.request.method; // Changing the method easily breaks everything.
s = "GET";

var s = gb.request.referer;
s = "http://ifixit.com"; // The referer header. Might be null.

var s = gb.request.contentType;  // The full content-type of the *request*, not the response.
s = "application/x-www-form-urlencoded"; // Typically null.

var s = gb.request.baseContentType;  // Content-type without any semicolon-options.
s = "application/x-www-form-urlencoded"; // Typically null.

var h = gb.request.headers;
// returns a Javascript object which contains all the request headers.
// The attribute name is the proper mixed-case name of the header (e.g. "User-Agent"),
//    and the value is an array of strings.
// If you assign to gb.request.headers["uSeR-aGeNt"],
//    it will still set the attribute "User-Agent".
// If you set an attribute to null or an empty string, the attribute will be deleted.
// Tip: use gb.log() to see the headers:
gb.log("h = %o", gb.request.headers);
// will log something similar to:
h = {
    Cookie:['BookCusCountry=DK'],
    'User-Agent':['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6; en-us) ...'],
    'Accept-Encoding':['gzip'],
    'Accept-Language':['en-us'],
    Host:['glimmerblocker.org'],
    Accept:['*/*']
};

var s = gb.request.clientAddr;
// returns client ip address as dotted decimal in a Javascript string.
s = "10.1.2.3";

var s = gb.request.clientHostname;
// returns hostname for client ip address using reverse dns lookup
s = "dhcp42.internal.company.com";

Changing any of the gb.request attributes in a request-modification script makes GlimmerBlocker use the new values when getting the content from the server. So if you change gb.request.url, it will not result in GlimmerBlocker sending back a redirect response to Safari. Instead, the new url will be used when getting the content from the server, e.g. a completely different server. So you can make Safari think it fetches one URL, but in reality show content from a completely different URL. If you want to make a redirect, use gb.response.sendRedirect('...');

You cannot assign a new object to gb.request.headers, only change attributes of gb.request.headers: See the 'other consideration' section at bottom of this page.

The gb.response object

Script types:

If shallow, the object contains an empty dummy response with status = 200 and empty text/html content.

If the shallow response header is modified, and/or the global t object is set to a non-empty string, that will be used as a response and the web-server will not be contacted. You can use this as an advanced blocker which returns a "<script>window.close();</script>" response which closes pop-unders. This is how the gb.response.sendRedirect() method works: it just modifies the header, but doesn't do any "send this back to Safari" magic behind the scenes.

var i = gb.response.status;
// http status code, e.g. 200 or 404. Integer.

var s = gb.response.statusMessage;
// http status message, e.g. "OK" or "Not found".

var s = gb.response.contentType;  // The full content-type, including any charset option.
s = "text/html; charset=UTF-8";

var s = gb.response.baseContentType;  // The base content-type without any options.
s = "text/html";

var h = gb.response.headers;
// returns a Javascript object which contains all the response headers.
// The attribute name is the proper mixed-case name of the header (e.g. "Cache-Control"),
//    and the value is an array of strings.
// If you assign to gb.request.headers["cAchE-cOntRol"],
//    it will still set the attribute "Cache-Control".
// If you set an attribute to null or an empty string, the attribute will be deleted.
// Tip: use gb.log() to see the headers:
gb.log("h = %o", gb.response.headers);
// will log something similar to:
h = {
    'Cache-Control':['max-age=600'],
    Expires:['Sat, 13 Jun 2009 19:06:37 GMT'],
    'Accept-Ranges':['bytes'],
    Server:['Apache/2.2.9 (Unix)'],
    'Content-Type':['text/html; charset=UTF-8']
}

You cannot assign a new object to gb.response.headers, only change attributes of gb.response.headers: See the 'other consideration' section at bottom of this page.

gb.response.sendRedirect(url);
// Modifies the response so it becomes a simple redirect:
// gb.response.status  = 302,
// gb.response.statusMessage = "Redirected to...";
// gb.response.headers['Location']  = url;
// gb.response.headers['content-type'] = 'text/plain';
// t = "Redirected to...";
// All the other headers are kept as-is.

gb.response.sendHtml(html);
// Sets response to a simple text/html response:
// gb.response.status  = 200
// gb.response.statusMessage = "OK";
// gb.response.headers['Location']  = '';
// gb.response.headers['content-type'] = 'text/html';
// t = html;
// All the other headers are kept as-is.

gb.response.sendText(txt);
// Like sendHtml, but uses content-type = text/plain.

gb.response.sendUnknownHostErrorPage(host);
// Sets response to GlimmerBlocker's "host not found" error page.
// The host argument is used for the search entry.

gb.response.sendCantConnectErrorPage(host);
// Like sendUnknownHostErrorPage(), but message is
// "Can't connect to host".

Content modification

Because content modification often is performed using multiple Javascript statements, the text content is stored in the global variable t to avoid unwieldily long statements.

Global utility functions:

replace(/regexp/, "some text");
// is a shorthand for:
t = t.replace(/regexp/, "some text");

Utility functions in the gb object:

gb.zapScriptElements();
// removes all <script>..</script> elements from t.

gb.zapStyleElements();
// removes all <style>..</style> elements from t.

gb.zapIframeElements();
// removes all <iframe>..</iframe> elements from t.

gb.addScriptElement(contents);
// Adds a <script>contents</script> element in t just before the </body> tag
// (i.e. end of document).

gb.addStyleElement(contents);
// Adds a <style>contents</style> element in t just before the </head> tag
// (i.e. end of header so it overrides loaded stylesheets).

gb.setDocumentTitle(txt)
// Updates/creates the <title>...</title> element with the new text.
// The argument is automatically html-encoded,
// so you don't need to care about <, > and &.

gb.insertAtHeadStart(contents);
// Inserts the contents just after the <head> tag.
// Useful for adding <meta..> tags.

gb.insertAtHeadEnd(contents);
// Inserts the contents just before the </head> tag.
// Useful for adding external stylesheets, scripts, etc.

gb.insertAtBodyEnd(contents);
// Inserts the contents just before the </body> tag.
// Useful for adding external scripts, etc.

var b = gb.transformCharset(txt, from, to);
// Useful when servers send the response body in a different charset than
// what's declared in the content-type header or xml charset attribute.
// The function encodes the chars in 'txt' to bytes using the 'from' charset.
// Then it decodes the bytes to a string using the 'to' charset, which almost always should be 'UTF-8'.
// If a server sends UTF-8 declared as ISO-8859-1, use:
t = gb.transformCharset(t, 'ISO-8859-1', 'UTF-8');
// Available in version 1.6.7 and later.

var hostPrefix = gb.attachmentDownloadHostPrefix();
// returns a "download as attachment" hostname prefix.
// The servlet takes two parameters:
//   1) url:  the absolute URL of the file to download.
//   2) filename: the filename which Safari should use.
// The servlet fetches the content at the URL and forwards it to Safari, but adds
//    a "content-disposition" header so the file always is downloaded as an attachment.
// The servlet checks the content-type header sent by the server
//    and changes the filename to match it.
// The reason for using a host prefix instead of a static domain such as
//    'glimmerblocker.test' is to get Safari send the cookies for the domain.
// See YouTube download filter for example.
a.href = "http://" + hostPrefix + ".example.com" +
  "?url=" + escape("http://example.com/thingy.flv") +
  "&filename=" + escape("weird-movie.suffix");
// The prefix is unique for each GlimmerBlocker installation.

Network state

The gb.network object is documented on the Proxies page. It tells the state of the network interfaces (e.g. ip-address and AirPort network name), and allows you to specify a nested http/socks proxy, and to use a different dns server than the Mac's default.

Other considerations

The gb.* objects are magic objects and not normal javascript objects. This is similar to special javascript objects in browsers, e.g. location.href = "http://ruc.dk" doesn't just assign a value, but makes the browser load a new page.

// This doesn't work in a browser:
var s = location.href;
s = "http://ruc.dk"; // only 's' gets a new value, location.href is untouched.

// This doesn't work in GlimmerBlocker
var s = gb.request.referer;
s = "http://ruc.dk"; // only changes 's', the referer is unchanged.

// This works:
location.href = "http://ruc.dk"; // in a browser
gb.request.referer = "http://ruc.dk"; // in GlimmerBlocker

//  OBS: you cannot replace full sub-objects in gb.*, e.g.
gb.response.headers = { "Location":"http://ruc.dk", "User-Agent":"Mine"};
// and you must change each attribute separately, like this:
gb.response.headers["Location"] = "http://ruc.dk";
gb.response.headers["User-Agent"] = "Mine";

Each script uses its own global scope in Javascript, so a script is not able to see nor modify variables used by other rules. GlimmerBlocker caches the last 25 executed scripts to optimize performance: the compiled version of the script and the its scope (i.e. global variables). There is no support for persistence whatsoever.

The request & response flow.

GlimmerBlocker uses the Rhino Javascript runtime with the language level set to 1.7

If you find any errors, omissions, or have any helpful comments to improve the documentation, please send email to: [feedback@glimmerblocker.org]