Improved Browser Paint Events Bookmarklet

John Resig posted today about a nifty new feature available in Firefox nightlies, browser paint events. He also posted an example script and bookmarklet called TrackPaint. He goes into greater depth in his post, so I won’t bother here.

I wanted something more “real-time” and closer to the Quartz Debug utility included with the Mac OS X developer tools (which essentially provides this feature for all of OS X), so I present my modified bookmarklet and code:

// modified from John Resig’s example: http://ejohn.org/apps/paint/track.js
(function(){
    var container;
    
    window.addEventListener("MozAfterPaint", log, false);
    
    if ( document.body )
        bind();
    else
        window.addEventListener("load", bind, false);
        
    function bind()
    {
        container = document.createElement("div");
        document.body.appendChild(container);
    }

    function log(e)
    {
        window.removeEventListener("MozAfterPaint", log, false);

        var rects = e.clientRects;

        for ( var i = 0; i < rects.length; i++ ) {
            var rect = rects[i];
            var div = document.createElement("div");

            with (div.style) {
                background = "red";
                opacity = "0.1";
                position = "absolute";
                top = rect.top + "px";
                left = rect.left + "px";
                width = (rect.right – rect.left) + "px";
                height = (rect.bottom – rect.top) + "px";
            }

            container.appendChild(div);
        }
        
        window.setTimeout(function() {
            while (container.firstChild)
                container.removeChild(container.firstChild);
            
            window.setTimeout(function() {
                window.addEventListener("MozAfterPaint", log, false);
            }, 0);
        }, 100);
    }
})();

Rather than recording each event and displaying them when you click, this version immediately disables the MozAfterPaint event listener (to avoid the recursion issue), shows the translucent red divs, waits 100 ms, removes the rectangles, and re-enables the MozAfterPaint event listener.

It will miss some events during the 100 ms flash, but overall it seems to work pretty well. You could modify it to re-enable the event listener between adding and removing the divs, but I’m not sure it’s worth the effort.

Command line interpreter and REPL for JSCocoa

A few months ago I started working on a JavaScript to Objective-C bridge. We had already implemented Objective-C in JavaScript, so I figured “why not?”

Well, I never got very far, but thankfully Patrick Geiller apparently had the same idea and actually executed it: He announced JSCocoa today. It looks like it’s a solid bridge, about up to par with PyObjC and RubyCocoa.

While the included GUI interface for trying out JSCocoa is nice, I prefer command line interfaces for my languages, so I ripped out the few lines of code from my original bridge and plugged in JSCocoa.

Code and build instructions on GitHub.

It’s very bare bones at the moment: it will either read one or more file names from the command line arguments, or if no arguments are supplied it will present a no-frills REPL. Obviously line-editing, etc would be one of the next steps, but for now it works nicely with rlwrap.

#import <Foundation/Foundation.h>
#import "JSCocoaController.h"

void JSValuePrint(JSContextRef, JSValueRef, JSValueRef *);

int main (int argc, const char * argv[])
{
    [[NSAutoreleasePool alloc] init];
    id c = [JSCocoaController sharedController];
    JSGlobalContextRef ctx = [c ctx];
    
    if (argc > 1)
    {
        for (int i = 1; i < argc; i++)
            [c evalJSFile:[NSString stringWithFormat:@"%s", argv[i]]];
    }
    else
    {
        while (1)
        {
            char buffer[1024];
            
            printf("js> ");
            
            if (fgets(buffer, 1024, stdin) == NULL)
                exit(0);
            
            JSStringRef script = JSStringCreateWithUTF8CString(buffer);
            JSValueRef exception = NULL;
            
            if (JSCheckScriptSyntax(ctx, script, 0, 0, &exception) && !exception)
            {
                JSValueRef value = JSEvaluateScript(ctx, script, 0, 0, 0, &exception);
                
                if (exception)
                    JSValuePrint(ctx, exception, NULL);
                
                if (value && !JSValueIsUndefined(ctx, value))
                    JSValuePrint(ctx, value, &exception);
            }
            else
            {
                printf("Syntax error\n");
            }
            
            JSStringRelease(script);
        }   
        
    }
}

void JSValuePrint(
                  JSContextRef ctx,
                  JSValueRef value,
                  JSValueRef *exception)
{
    JSStringRef string = JSValueToStringCopy(ctx, value, exception);
    size_t length = JSStringGetLength(string);
    
    char *buffer = malloc(length+1);
    JSStringGetUTF8CString(string, buffer, length+1);
    JSStringRelease(string);
    
    puts(buffer);
    
    free(buffer);
}

Recovering Censored Text Using Photoshop and JavaScript

My friend Andrew recently posted a teaser for a new project he’s working on, but with part of the headline pixelated to obscure what the project actually is. My curiosity got the best of me and I decided to do what any self-respecting geek would do: write a program to figure out what the censored text said.

Ultimately I failed to recover most of the censored text (except “to”), so I had to cheat a little. The following video is the program running on a very similar image I created. This proves it works in ideal conditions, but needs some improvement to work in less than ideal cases.

(and no, as far as I know my friend’s project has nothing to do with eating monkeys)

Applying a filter like Photoshop’s “mosaic” filter obscures the original data, but doesn’t remove it entirely. If we can reconstruct an image with *known* text that looks very similar to original image, then we can be pretty sure the original text is the same as our known text. This is very similar in principle to brute-force cracking a password hash. For a more detailed explanation see this article.

Photoshop was an obvious choice since I needed to recreate the exact same fancy styling as the original image, then apply the exact same mosaic filter. I figured I would have to write a script that tells Photoshop to generate images, then use an external tool to actually compare them to the original.

It turns out that Photoshop CS3 has all the features necessary to pull the whole thing off without any other programs or tools. The most important feature is the JavaScript scripting environment built into Photoshop, which is far more powerful than the AppleScript environment (and a *much* nicer language, in my opinion).

CS3 added two other features that are critical to this task: Smart Filters, and Measurements. Smart Filters lets you edit a layer (namely the text with effects applied) *after* you apply a filter that would have previously require rasterization. This lets us apply the censoring filter to our styled text, and later change the text without having to manually reapply the filter. The “measurements” feature lets you record various statistics about an image or portion of an image: in our case we’ll want the “average gray value” of the “difference” between the original and generated images.

picture-10.png

First we need to prepare the environment. Open the original image in Photoshop, and attempt to replicate the original un-censored text as closely as possible (you need *some* uncensored text as a reference). Place your text layer on top of the original and toggle between normal and “difference” blending modes to see how you close you are. Ideally everything will be black in “difference” mode. It’s very important to precisely match the font, size, spacing, color, effects like drop shadows or outlines, and even the background. If these are off even by a little bit it will throw things off. I ended up having to cheat because I couldn’t match the slick styling of the original text with my lame Photoshop design skills.

Once the text matches and is lined up perfectly, select the layer then choose “Convert for Smart Filters” from the “Filter” menu. Now select the censored portion of the text and apply the same filter used on the original image, again matching it as closely as possible. For the mosaic filter, you can line up the “grid” by adjusting the origin and size of the selection (yeah, it’s a pain).

picture-11.png

Finally, make sure your layer is on top of the original, and the blending mode on your layer is set to “difference”. Double-click the Smart Object layer to open it’s source document, and adjust the variables listed at the top of the JavaScript to match the names and layers. Also, in the menu “Analysis”: “Select Data Points”: “Custom…” make sure only “Gray Value (Mean)” is checked.

Code

Rather than attempting to explain it in detail here, just read the code and comments. Here’s a quick summary:

  1. Start with the first character. Try setting it to each of the possibilities (a through z, and a space), and record the difference score between the original image and generated image. Only look at the first half of the current character (since the second half will be influenced by the *next* character).
  2. Sort the results. Lower scores are better (less different)
  3. Now try each of the top 3 characters along with every possibility for the *next* character. This time record score for the whole width of the current character since we’re checking the next character as well.
  4. Pick the best choice, either the best permutation out of all 81 combinations (3 best * 27 possible), or out of the 3 averages for each best.
  5. Repeat for the next character until done.
// change these parameters based on document names and layer ordering
baseDocName = "base.psd";
baseDocTextLayer = 0;
textDocName = "The easy way to do somethingss12.psb";
textDocTextLayer = 0;

knownString = "The easy way "; // the part of the string that’s already known
missingLength = 20; // number of characters to figure out

method = 3;
debug = false;

function main()
{
    baseDoc = documents[baseDocName];
    textDoc = documents[textDocName];

    // get the top left corner of the text layer in the main doc
    var mainBounds = baseDoc.artLayers[baseDocTextLayer].bounds,
        mainX = mainBounds[0].as("px"),
        mainY = mainBounds[1].as("px");
    
    // possible characters include space and lowercase.
    var possibleCharacters = [" "];
    for (var i = 0; i < 26; i++)
    {
        possibleCharacters.push(String.fromCharCode("a".charCodeAt(0) + i));
        //possibleCharacters.push(String.fromCharCode("A".charCodeAt(0) + i)); // uncomment for uppercase letters
    }

    var fudgeFactor = 3,    // number of top choices to try
        guess = "";         // guessed letters so far

    for (var charNum = 0; charNum < missingLength; charNum++)
    {
        results = [];
    
        // get the beginning and potential end (width of a "M") of the next character
        var w1 = getStringBounds(knownString + guess),
            w2 = getStringBounds(knownString + guess + "M");

        // PASS 1: half the potential width, since we’re not looking at the next character yet

        // half the width of "M"
        setSelection(mainX, mainY, (w1[2].as("px") + w2[2].as("px")) / 2, 15);//w2[3].as("px"));
    
        // get the score for every letter
        for (var i = 0; i < possibleCharacters.length; i++)
        {
            var val = getStringScore(knownString + guess + possibleCharacters[i])
        
            var res = { ch: possibleCharacters[i], v: val };
            results.push(res);
        }

        // sort from best (lowest) to worst score
        results = results.sort(function (a,b) { return a.v – b.v; });
        
        // method 1: too simple, poor results
        if (method == 1)
        {
            guess += results[0].ch;
        }
        else
        {
            // PASS 2: full (potential) width of the current character, testing each of the few top matches and every possible next character
            
            // full width of "M"
            setSelection(mainX, mainY, w2[2].as("px"), 15);//w2[3].as("px"));
        
            var minValue = Number.MAX_VALUE,
                minChar = null,
                minSum = Number.MAX_VALUE,
                minSumChar = null;
            
            // try the few best from the first pass
            for (var i = 0; i < fudgeFactor; i++)
            {
                var sum = 0;
                for (var j = 0; j < possibleCharacters.length; j++)
                {
                    // get the score for the potential best PLUS each possible next character
                    var val = getStringScore(knownString + guess + results[i].ch + possibleCharacters[j])
                
                    sum += val;
                    
                    if (val < minValue)
                    {
                        minValue = val;
                        minChar = results[i].ch;
                    }
                }    
                if (sum < minSum)
                {
                    minSum = sum;
                    minSumChar = results[i].ch;
                }
            }
        
            // if the results aren’t consistent let us know
            if (debug && results[0].ch != minSumChar || minChar != minSumChar)
                alert(minChar + "," + minSumChar + " (" +results[0].ch + "," + results[1].ch+ "," + results[2].ch+ ")");
            
            if (method == 2)
            {
                // method 2: best of all permutations
                guess += minChar;
            }
            else
            {
                // method 3: best average
                guess += minSumChar;
            }
        }
        WaitForRedraw();
    }
}

// measure the gray value mean in the current selection
function getMeasurement()
{
    // delete existing measurements
    app.measurementLog.deleteMeasurements();
    
    // record new measurement
    app.activeDocument = baseDoc;
    app.activeDocument.recordMeasurements();//MeasurementSource.MEASURESELECTION, ["GrayValueMean"]);
    
    // export measurements to a file
    var f = new File ("/tmp/crack-tmp-file.txt");
    app.measurementLog.exportMeasurements(f);//, MeasurementRange.ACTIVEMEASUREMENTS, ["GrayValueMean"]);
    
    // open the file, read, and parse
    f.open();
    var line = f.read();
    var matches = line.match(/[0-9]+(\.[0-9]+)?/);
    if (matches)
    {
        var val = parseFloat(matches[0]);
        return val;
    }
    return null;
}

// sets the value of the test string
function setString(string)
{
    app.activeDocument = textDoc;
    app.activeDocument.artLayers[textDocTextLayer].textItem.contents = string;

    WaitForRedraw();
}

// gets the difference between the original and test strings in the currently selected area
function getStringScore(string)
{
    setString(string);
    
    // save document to propagate changes parent of smart object
    app.activeDocument = textDoc;
    app.activeDocument.save();
    
    // return the average gray value
    return getMeasurement();
}

// get the bounds of the text
function getStringBounds(string)
{
    app.activeDocument = textDoc;
    // set the string of the text document
    setString(string);
    // select top left pixel. change this if it’s not empty
    app.activeDocument.selection.select([[0,0], [0,1], [1,1], [1,0]]);
    // select similar pixels (i.e. everything that’s not text)
    app.activeDocument.selection.similar(1, false);
    // invert selection to get just the text
    app.activeDocument.selection.invert();
    // return the bounds of the resulting selection
    return app.activeDocument.selection.bounds;
}

// sets the base document’s selection to the given rectange
function setSelection(x, y, w, h)
{
    app.activeDocument = baseDoc;
    app.activeDocument.selection.select([[x,y], [x,y+h], [x+w,y+h], [x+w,y]]);
}

// pauses for Photoshop to redraw. taken from reference docs.
function WaitForRedraw()
{
    // return; // uncomment for slight speed boost
    var eventWait = charIDToTypeID("Wait")
    var enumRedrawComplete = charIDToTypeID("RdCm")
    var typeState = charIDToTypeID("Stte")
    var keyState = charIDToTypeID("Stte")
    var desc = new ActionDescriptor()
    desc.putEnumerated(keyState, typeState, enumRedrawComplete)
    executeAction(eventWait, desc, DialogModes.NO)
}

main();

The raw code and sample Photoshop file are available on GitHub.

Issues

This problem is particularly tricky for proportional fonts, since if you get any character wrong and it’s width is different than the actual character, then all subsequent characters will be misaligned, causing more incorrect guesses, compounding the problem even more, and so on. I’m not sure how to deal with this, other than improving the overall matching quality. Ideally we would test every possible combination for the entire string, but that would require 27^n tests, where n is the number of unknown characters. This is obviously not feasible.

With the simplistic method of iterating over each position and trying each possible character, it turned out that almost every single “guess” was for the letters “m” or “w”. This was because for positions where the original was narrower characters, the “m” would “bleed” over into the *next* position, improving the score regardless of how well it actually matched the current character. To get around this, we only look at the difference for the first *half* of the character’s position.

Since looking at the first half of the character removes some valuable information, we then do a second pass using the top several guesses from the first pass, this time looking at the full width of the current character along with each of the possible next characters (27 tests + 3 runs times 27 tests results in 108 tests per character).

Further improvements could definitely be made, but I’ve already spent several hours too many on this.

The current algorithm runs at about 3 characters per minute. The overhead of Photoshop saving the Smart Object document on every individual test case is significant. If this were a special purpose program manipulating images directly it would likely be much faster. The tradeoff, of course, is you have all of Photoshop’s flexibility at your disposal for matching the original document’s font, size, style, spacing, and censoring effects, which is very important. For small amounts of text speed isn’t a problem.

Conclusion

While my original goal of recovering the censored text on my friend’s page was never achieved, the project was a success. It works well on my test image, and I learned about 3 obscure but cool and useful features of Photoshop!

Oh, and *that’s* why ██████████ uses black ink to ██████ their ██████!

git bisect run

This feature of git is too cool not to blog about: git bisect, and more specifically, git bisect run.

“git bisect” is a tool that facilitates a binary search of changes to your git repository to help find where a bug was introduced. You can walk through the process manually using “git bisect {good,bad,skip}“, or if you can write a script that automates checking for the bug, you can use “git bisect run scriptname” to have git do all the work for you.

The script should return 0 if the bug does not exist, and some other number (except 125) if the bug does exist.

In my case, Cappuccino’s “steam” build tool was failing, so I wrote a simple script that would test it by trying to run “steam” on Foundation:

#!/bin/sh

# install a known working copy of the build tools
pushd ../tmp/Tools
sudo sh install-tools
popd

# build whatever version of Cappuccino git bisect has checked out for us
rm -rf $STEAM_BUILD
ant release

# install the freshly built tools
pushd $STEAM_BUILD/Cappuccino/Tools
sudo sh install-tools
popd

# run steam on Foundation to see if the built tools work
rm -rf $STEAM_BUILD
pushd Foundation
steam
RETURN=$?
popd

# return the recorded return value
exit $RETURN

I provided “git bisect start” with a known bad commit and a known good commit, then ran “git bisect run”:

git bisect start c1e882ace1dd29aea98d9247db304fe5d5077df7 d6c0f8802a2fd3a07e14418de7744ae04ae4499e
git bisect run ../test.sh

Sure enough, a few minutes later “git bisect” reported exactly which commit caused the problem:

01177a7e0237b1bd026cf0c4ca923fced8536772 is first bad commit
commit 01177a7e0237b1bd026cf0c4ca923fced8536772
Author: name removed to protect the not-so-innocent

Date:   Tue Sep 30 17:29:56 2008 -0700

   Fix keys conflict in CPDictionary

   [#77 state:resolved]

:040000 040000 bf4ceafbf439aa54790fc57a2a78dec8283abadf b269f85f3f4ed4d08e4c8261ee409e9d88255b1d M    AppKit
:040000 040000 f9c1fdb3c02c7c899285c8cb8123b29e99a17e46 48cc5aefde707119fe956dd882dcd8f8182e7012 M    Foundation
:040000 040000 b780743d17ddb0ebda188703725e9dabf23fd458 c72750dabd99ca42d9144f3074c68c42b727d028 M    Objective-J
bisect run success

YouTube Fullscreen Bookmarklet

I find it incredibly annoying when an embedded YouTube video can’t be made fullscreen, and I have to switch to YouTube.com just to watch it.

So, I wrote this simple little bookmarklet which modifies the embed code for any YouTube videos to allow fullscreen. It could also easily be made into a user script for GreaseMonkey, etc, to perform the modifications automatically.

Bookmarklet

Source:

var os = document.getElementsByTagName("object");
for (var i = 0; i < os.length; i++)
{
    var o = os[i].cloneNode(true);
    o.innerHTML = ‘<param name="allowFullScreen" value="true"></param>’ + o.innerHTML;
    for (var j = 0; j < o.childNodes.length; j++)
    {
        if (o.childNodes[j].name == "movie")
            o.childNodes[j].value += "&fs=1";
        else if (o.childNodes[j].nodeName.toUpperCase() == "EMBED") {
            o.childNodes[j].src += "&fs=1";
            o.childNodes[j].setAttribute("allowfullscreen", "true");
        }
    }
    os[i].parentNode.replaceChild(o, os[i]);
}

Try it out on this page (I’m too lazy to actually make it fullscreen).

Note that currently it doesn’t actually check to make sure it’s a YouTube video that it’s modifying, so it might stomp all over other types of embeds.

A simple Google Charts API "framework"

The Google Charts API is a useful little tool for generating charts. The “API” is actually just a set of parameters you pass to a single URL endpoint: http://chart.apis.google.com/chart

sample chart

It’s a very capable API, and you could write an entire framework around it (as some people have), but I don’t think it’s necessary. A few little helper functions and Google’s documentation is all you really need. Here’s the heart of my “framework” in JavaScript:

function gchart_build(options)
{
    var params = [];
    for (option in options)
        params.push(option + "=" + options[option]);
    return "http://chart.apis.google.com/chart?" + params.join("&");
}

And PHP:

function gchart_build($options)
{
    $params = array();
    foreach ($options as $option => $value)
        $params[] = $option . "=". $value;
    return "http://chart.apis.google.com/chart?" . implode("&", $params);
}

And here’s a simple bar chart example in PHP:

$chart_url = gchart_build(array(
    "cht"   => "bvs",
    "chs"   => "400×250",
    "chbh"  => "14,2,0",
    "chd"   => "t:3,4,4,1,3,2,2,0,1,3,11,5,7,5,5,7,7,5,3,2,3,3,6,6",
    "chds"  => "0,11"
));

The nice thing about this “frameworks” is it takes 30 seconds to implement in many languages, and the actual API is identical across every language, since it just uses a hash object and the original API as a very simple domain-specific language.

This could be improved with a few more helper functions for different parts of the Google Chart API, but this function remains the most important.

Here’s a simple testbed for trying out the JavaScript version. Simple edit the JavaScript and click “Update!” to see the results:
gchart_tester.html

Adding Growl Notifications to Facebook

Yesterday Facebook added the “Live Feed”, which is like the existing News Feed, but automatically updates the page without requiring the user to refresh. It simply polls Facebook’s servers every few seconds, rather than the slightly fancier “comet”-style long-polling they do for Facebook chat:

Live Feed vs Facebook Chat

Polling is perfectly sufficient for this sort of updating (it’s not particularly latency sensitive), but none of this is relevant to the rest of this post anyhow.

As neat as it is, I’m not going to sit around all day watching Facebook, waiting for updates. I want to be notified of updates unobtrusively, at which point I can decide if I want to ignore them or check it out. Growl is perfect for this.

Many OS X apps use Growl to display notifications to the user. They use either Distributed Objects or a UDP protocol called GrowlTalk to post the notifications, neither of which is suitable for a client-side web app. Google Gears promises to provide NotificationAPI at some point, but it’s not currently ready. Fluid also has a notification API, but we need Firefox’s Greasemonkey plugin to inject some JavaScript.

Brian Dunnington wrote a version of Growl for Windows, which adds one neat feature: an HTTP interface. This lets the browser (or anything else the speaks HTTP) post notifications directly. Unfortunately the OS X version of Growl doesn’t have this interface built in, but it’s nearly trivial to create a bridge to the GrowlTalk protocol in Python:

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from socket import AF_INET, SOCK_DGRAM, socket
from urlparse import urlparse
from cgi import parse_qs
import simplejson
import netgrowl

class GrowlBridgeHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        try:
            # parse url
            u = urlparse(self.path)
            if u.path == "/":
                
                # parse query string
                q = parse_qs(u.query)
                print q
                
                # parse json payload
                j = simplejson.loads(q[‘d’][0])
                print j
                
                # create and send the notification
                p = netgrowl.GrowlNotificationPacket(
                    description=(j.has_key(‘description’) and j[‘description’] or "Description"),
                    title=(j.has_key(‘title’) and j[‘title’] or "Title"),
                    priority=(j.has_key(‘priority’) and j[‘priority’] or 0),
                    sticky=True) #(j.has_key(‘sticky’) and j[‘sticky’] or False))
                growlserver.sendto(p.payload(), addr)
                
                # send a 200 http response
                self.send_response(200)
                self.send_header(‘Content-type’, ‘text/html’)
                self.end_headers()
                return
            
            return
                
        except IOError:
            self.send_error(404, ‘File Not Found: %s’ % self.path)

def main():
    try:
        # prepare the growl socket
        global addr, growlserver
        addr = ("localhost", netgrowl.GROWL_UDP_PORT)
        growlserver = socket(AF_INET,SOCK_DGRAM)
        print "Assembling registration packet like growlnotify’s (no password)"
        p = netgrowl.GrowlRegistrationPacket()
        p.addNotification()
        print "Sending registration packet"
        growlserver.sendto(p.payload(), addr)
        
        # start the http server
        httpserver = HTTPServer((, 9889), GrowlBridgeHandler)
        print ‘started growlbridge…’
        httpserver.serve_forever()
        
    except KeyboardInterrupt:
        print ‘^C received, shutting down server’
        httpserver.socket.close()
        
        growlserver.close()
        print "Done."

if __name__ == ‘__main__’:
    main()

growlbridge.py.gz

Of course this opens up a port on your machine, so you should take the necessary precautions to firewall it. It uses Rui Carmo’s netgrowl and also requires Bob Ippolito’s simplejson.

We can now use Brian’s growl.js library with both Mac OS X and Windows versions of Growl. The next step is to connect it up to Facebook with Greasemonkey:

// ==UserScript==
// @name           Facebook News Feed Notifier
// @namespace      http://tlrobinson.net/
// @description    Notify user of new Facebook News Feed items via Growl
// @include        http://*.facebook.com/home.php
// ==/UserScript==

function GM_init() {
    
    var fbNewsFeedNotification = new Growl.NotificationType("Facebook News Feed", true);
    Growl.register("Facebook", [fbNewsFeedNotification]);
    
    unsafeWindow.HomeFeed.prototype._addStoriesToQueueOriginal = unsafeWindow.HomeFeed.prototype._addStoriesToQueue
    unsafeWindow.HomeFeed.prototype._addStoriesToQueue = function(stories) {
        this._addStoriesToQueueOriginal(stories);
        
        var testDiv = document.createElement("div");
        for (var i = 0; i < stories.length; i++)
        {
            testDiv.innerHTML = stories[i];
            var spans = testDiv.getElementsByTagName("span");
            
            var message = (spans.length > 0) ? spans[0].textContent : "Unknown update";
            
            Growl.notify(fbNewsFeedNotification, "Facebook News Feed", message, Growl.Priority.Normal, false);
        }
    }
}

// Add growl.js
var GM_GROWL = document.createElement(‘script’);
GM_GROWL.src = ‘http://www.tripthevortex.com/growl/growl.js’;
GM_GROWL.type = ‘text/javascript’;
document.getElementsByTagName(‘head’)[0].appendChild(GM_GROWL);

// Check if growl.js’s loaded
function GM_wait() {
    if(typeof unsafeWindow.Growl == ‘undefined’)
    {
        console.log("waiting");
            window.setTimeout(GM_wait, 100);
    }
    else
    {
        Growl = unsafeWindow.Growl;
        GM_init();
    }
}
GM_wait();

This fairly simple userscript loads growl.js, then overwrites one of Facebook’s JavaScript functions that gets called when updating the feed, HomeFeed.prototype._addStoriesToQueue(). The new function should call the original one (so that the feed is still updated), but it should also post a new notification for each new feed story.

That’s about it. Run “python growlbridge.py” if you’re on a Mac (make sure you have netgrowl and simplejson), or Brian Dunnington’s Growl for Windows, install the above Greasemonkey userscript, and open up http://www.new.facebook.com/home.php?tab=2.

Unfortunately, there appears to be some bugs (or new security restrictions) in Firefox 3 that prevents this userscript from working correctly, but it works fine in Firefox 2.

In the future growl.js could be swapped out for the Gears or Fluid notification APIs, or anything else (ideally some standard). Also, hopefully Growl for OS X will have the same HTTP interface built in.

MobileMe and (lack of) encryption

AppleInsider.com posted an article today claiming the lack of SSL on MobileMe has caused “unnecessary panic” and MobileMe is in fact secure. This is 100% false.

I’m not sure what to make of the article. I feel like the author said a bunch of big words hoping that most people would assume he knew what he was talking about and move on. Let’s try to break it down:

Data transaction security in MobileMe’s web apps is based upon authenticated handling of JSON data exchanges between the self contained JavaScript client apps and Apple’s cloud, rather than the SSL web page encryption used by HTTPS.

So the “JSON data exchanges” are “authenticated”. This is like saying “You can only read your email if you’re logged in”. I should hope so.

The only real web pages MobileMe exchanges with the server are the HTML, JavaScript, and CSS files that make up the application, which have no need for SSL encryption following the initial user authentication

This implies that while the app itself isn’t encrypted, it doesn’t need to be since the data itself is. This would be nice, but:

  • As Andrew Wooster points out, if a man-in-the-middle attack is possible, the app itself can by hijacked, and all bets are off. In addition to providing encryption, SSL can protect against MITM attacks. See below.
  • The data is actually NOT encrypted. Doh. See screenshots below.
  • Even if MITM attacks are taken out of the equation, and Apple wanted to encrypt the data but not the app, it’s simply not possible to make HTTPS requests from a page loaded over plain old HTTP due to the same origin policy. There are numerous tricks (iframes, script tags, etc) to get around the same origin policy but none that I’m aware of would be useful in this sort of situation.

Viewing MobileMe’s traffic in your network sniffer of choice (I prefer Charles and Wireshark) shows your data is actually unencrypted.

Calender:

Email:

Finally, the article states:

This has caused some unnecessary panic among web users who have equated their browser’s SSL lock icon with web security. And of course, Internet email is not a secured medium anyway once it leaves your server.

SSL is, in fact, the standard for securing web apps. I am much more inclined to trust SSL, which is known to work well, than a proprietary solution (or in MobileMe’s case, none at all).

Regarding email being unencrypted, it is true that email is often unencrypted between mail servers, but the more important link is between the user and their mail server, especially with widespread WiFi usage. Any sane person will use POP, IMAP, or web mail over SSL, which is more than MobileMe can claim to offer. And don’t forget it’s not just email we’re talking about: calendars, contacts, files, etc are also transmitted in the clear by MobileMe.

*[Clarification: this article only pertains to the MobileMe web interface. IMAP email and OS X syncing do offer encryption]*

The article reminded me of the recent Mozilla SSL policy bad for the Web article and ensuing comments on Hacker News. Some people are upset that Firefox 3 makes it harder for users to visit sites with self-signed SSL certificates. They claim this is bad for the web because it forces anyone who wishes to use encryption to pay a certificate authority (CA) for a signed SSL certificate, which goes against the openness of the Web.

They point to the fact that self-signed certificates can offer encryption without authentication. This is true, until a MITM attack is possible, at which point the encryption becomes useless. The attacker simply inserts himself between you and the server, encrypting both channels with his own self-signed certificate, while still intercepting all communication. This is much worse than no encryption at all, since the user may naively believe it’s a secure connection.

If browsers were to blindly accept self-signed certificates, the system would break down. Just because you wanted to save $15 on a SSL certificate, I would no longer be warned of MITM attack on my bank website. Clearly not acceptable.

So they suggest making the warning less obtrusive, but then average users who don’t know what SSL will simply ignore it. The more sites that use self-signed certs, the more they ignore it, and the more they become used to it, until the day they go to their bank’s website and get a warning they ignore out of habit. It’s a user interface problem just as much as a technical problem.

Making the warning scary and difficult to ignore accomplishes two things. It makes it harder for the user to accidentally ignore it, and it encourages webmasters to use CA signed certificates, which are far more secure.

My point is that when thinking about security you must take the whole system into account, not just pieces of it. SSL encryption on MobileMe’s login page is useless if the attacker can then sniff all the data, just as SSL encryption is useless if you can’t be sure who you’re talking to.

Useful Mac OS X-specific command line utilities

One of the greatest strengths of Mac OS X, for developers in particular, is that it has a very elegant and consistent graphical user interface as well as an excellent command line interface. I’m not going to cover the basics like “ls” and “cd”, but rather point out some Mac OS X specific tools that are less well known than they should be.

Many of these are the command line equivalents for the GUI versions available in OS X. Combining them with other command line tools can be very powerful and huge time savers. See the “man” pages for more details of each.

open – Opens a file, application, or directory in GUI-land. Very useful.

screencapture – Take a screenshot. Exactly the same as 3 or 4 (or even 4 then ) plus more.

say – Text to speech. Give it a file name, string of text, or pipe the output of another program to it. Options for difference voices,
saving the result to a file, etc. Fun hobby: ssh into a computer being used by someone else and start speaking to them using “say”.

pbcopy and pbpaste – Copy and paste to/from the OS X pasteboard.

srm – Secure “rm”. Like “rm” but overwrites deleted data. “-m” gives you DoD compliant erasing!

osascript – Run AppleScripts (or other OSA languages) from the command line. (I use this in the “term” script)

hdiutil – create and open disk images (.dmg)

defaults – view and set various hidden settings. Check out macosxhints.com for some of these.

These are just a few Mac OS X specific commands I found most useful. Amit Singh has a fairly comprehensive list over at his excellent kernelthread.com website.

ANSI escape sequences in JavaScript (color on the command line!)

Recently I’ve been doing a lot of work in Rhino, Mozilla’s JavaScript interpreter written in Java. While it’s a bit slower than some of the other JavaScript interpreters out there it has the advantage of being able to call Java libraries. This is immensely useful since JavaScript is pretty bare-bones in terms of built-in libraries.

Anyway, I had a simple logging system with the usual “error”, “warn”, “info”, etc levels, which was great except the important errors and warnings would get lost in the hundreds of info and debug messages. I didn’t know much about ANSI escape codes except that it allows for neat things like coloring text in your terminal, so I wrote a little code to help with this:

ANSI_ESC            = String.fromCharCode(0x1B);
ANSI_CSI            = ANSI_ESC + ‘[‘;
ANSI_TEXT_PROP      = ‘m’;
ANSI_RESET          = ‘0’;
ANSI_BOLD           = ‘1’;
ANSI_FAINT          = ‘2’; // unsupported?
ANSI_NORMAL         = ’22’;
ANSI_ITALIC         = ‘3’; // unsupported?
ANSI_UNDER          = ‘4’;
ANSI_UNDER_DBL      = ’21’; // unsupported?
ANSI_UNDER_OFF      = ’24’;
ANSI_BLINK          = ‘5’;
ANSI_BLINK_FAST     = ‘6’; // unsupported?
ANSI_BLINK_OFF      = ’25’;
ANSI_REVERSE        = ‘7’;
ANSI_POSITIVE       = ’27’;
ANSI_CONCEAL        = ‘8’;
ANSI_REVEAL         = ’28’;
ANSI_FG             = ‘3’;
ANSI_BG             = ‘4’;
ANSI_FG_INTENSE     = ‘9’;
ANSI_BG_INTENSE     = ’10’;
ANSI_BLACK          = ‘0’;
ANSI_RED            = ‘1’;
ANSI_GREEN          = ‘2’;
ANSI_YELLOW         = ‘3’;
ANSI_BLUE           = ‘4’;
ANSI_MAGENTA        = ‘5’;
ANSI_CYAN           = ‘6’;
ANSI_WHITE          = ‘7’;

ANSIControlCode = function(code, parameters)
{
    if (parameters == undefined)
        parameters = "";
    else if (typeof(parameters) == ‘object’ && (parameters instanceof Array))
        parameters = parameters.join(‘;’);
    return ANSI_CSI + String(parameters) + String(code);
}

// simple text helpers:

ANSITextApplyProperties = function(string, properties)
{
    return ANSIControlCode(ANSI_TEXT_PROP, properties) + String(string) + ANSIControlCode(ANSI_TEXT_PROP);
}

var colorCodeMap = {
    "black"   : ANSI_BLACK,
    "red"     : ANSI_RED,
    "green"   : ANSI_GREEN,
    "yellow"  : ANSI_YELLOW,
    "blue"    : ANSI_BLUE,
    "magenta" : ANSI_MAGENTA,
    "cyan"    : ANSI_CYAN,
    "white"   : ANSI_WHITE
}

ANSITextColorize = function(string, color)
{
    if (colorCodeMap[color] == undefined)
        return string;
    return ANSITextApplyProperties(string, ANSI_FG + colorCodeMap[color]);
}

Download here: ansi_sequences.js

ANSIControlCode(code, parameters) takes in a control code as a string (a single alphabetic character, see above Wikipedia article), and zero or more parameters, either as a single string/number or array of strings/numbers (again, see above Wikipedia article). It returns a string with the control sequence.

There are also two helpers functions for setting text properties:

ANSITextApplyProperties(string, properties) wraps “string” in a pair of control sequences. First the sequence to set text properties (“m” along with the given parameters), second the reset text properties sequence (“m” with no parameters). It returns the wrapped string. This lets you easily apply a number of properties to a string.

ANSITextColorize(string, color) simply sets the foreground color of a string, and returns the wrapped string.

Here are examples of the helper functions:

Note that color control codes must be combined with either ANSI_FG or ANSI_BG.

Also note that this is completely useless in the browser. Only shells can understand these control codes, so use it in Rhino and other command line JavaScript interpreters (while this code is written in JavaScript, the same concepts be applied to any other programming language that can run in a shell: C, Python, Ruby, etc).