Greasemonkey Hacks/Syndication

From WikiContent

< Greasemonkey Hacks
Revision as of 23:10, 11 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Greasemonkey Hacks


Contents

Hacks 85–89: Introduction

Syndication is not a new technology, but it has gained popularity and mainstream attention as personal weblogs have exploded onto the Web. Simply put, a syndicated feed is a machine-readable representation of frequently updated content. As bloggers post new articles, their publishing software automatically updates their syndicated feed with headlines, links, and excerpts—or even the full article content.

Feeds are not limited to weblogs. Do you read news online? Most major news sites offer syndicated feeds, so you can keep up to date without visiting dozens of sites to find the latest news. Many offer category-specific feeds, so you can track only the articles you care about. There are even feed-specific search engines that allow you to subscribe to a keyword search and automatically find new topic-specific articles anywhere on the Web.

To subscribe to a syndicated feed, you need a news aggregator, a specialized software application that manages feeds across hundreds of thousands of sites. There are many such programs to choose from, for Windows, Mac OS X, Linux, and every other operating system. I personally use Bloglines ( http://www.bloglines.com), a web-based news aggregator. It is fast, free, and lets me stay up to date regardless of where I am or whose computer I'm borrowing.

The hacks in this chapter will help you get the most out of syndication and syndicated feeds.

Automatically Display Unread Items in Bloglines

Bloglines already defines a function to display unread items. Save yourself a click and call it automatically.

Bloglines (http://www.bloglines.com) is a web-based aggregator for syndicated feeds. It has a two-pane interface; the left pane displays your subscriptions, and the right pane displays the content from those subscriptions. The only thing I dislike about this nice interface is that I always use it the same way: every time I visit Bloglines, I want to see everything I haven't seen before— that is, all the unread items.

In Bloglines, displaying all unread items is just one click away. Clicking on the root level of your subscriptions in the left pane displays the unread items in the right pane. But since I always want to do this, I wrote a user script to automate that one click.

The Code

This user script runs on Bloglines. Since Bloglines already defines the JavaScript function we want to call to display unread items, the script is simply one line of code. There's one little wrinkle: Bloglines uses frames, so this script will end up being executed on each frame. Only the top frame defines the function we want to call, so we simply check whether it is defined before calling it.

Save the following user script as bloglines-autoload.user.js:

	// ==UserScript==
	// @name		  Bloglines Autoloader
	// @namespace	  http://diveintomark.org/projects/greasemonkey/
	// @description	  Auto-display unread items in Bloglines
	// @include		  http://bloglines.com/myblogs*
	// @include		  http://www.bloglines.com/myblogs*
	// ==/UserScript==

	if (typeof doLoadAll != 'undefined') {
		doLoadAll();
	}

Running the Hack

To see this script in action, you will need a Bloglines account and at least one subscription. You can sign up for a free account at http://www.bloglines.com.

After installing the user script (Tools → Install This User Script), go to http://www.bloglines.com/myblogs. Instead of simply displaying your list of subscriptions with the number of unread items next to each subscription, Bloglines will automatically jump to displaying all the unread items in all your subscriptions, as shown in Figure 10-1.

If there are no unread items, Bloglines displays a message "There are no new items to display" in the pane on the right.

Figure 10-1. Autodisplaying unread items in Bloglines

Autodisplaying unread items in Bloglines

Zap Ugly XML Buttons

Make weblogs more palatable by replacing those orange XML buttons.

If you read weblogs regularly, you have undoubtedly seen an orange XML button. What is this? It's a link to the site's syndicated feed, suitable for reading in a dedicated news aggregator.

So, why does it say "XML," and why is it that dreadful shade of orange? Nobody knows. It seems to be specifically designed to clash with every possible color scheme. This hack replaces it with a plain-text link that says "Feed."

Tip

Learn more about syndication at http://atomenabled.org.

The Code

This user script runs on all pages. It identifies the orange XML buttons by their size, so it won't catch custom buttons that are a different size. But it catches most of them.

Save the following user script as zapxmlbuttons.user.js:

	// ==UserScript==
	// @name		Zap XML buttons
	// @namespace	http://diveintomark.org/projects/greasemonkey/
	// @description	convert orange XML buttons to text
	// @include		*
	// ==/UserScript==

	var snapXMLImages = document.evaluate(
		"//img[@width='36'][@height='14']",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

	for (var i = XML syndicated feed buttonssnapXMLImages.snapshotLength - 1; i >= 0; i--) {
		var syndicationXML buttonselmXMLImage = snapXMLImages.snapshotItem(i);
		if (/(xml|rss)/i.test(elmXMLImage.src)) {
			var elmXMLText = document.createTextNode('Feed');
			elmXMLImage.parentNode.replaceChild(elmXMLText, elmXMLImage);
		}
	}

Running the Hack

Before installing the user script, go to http://radio.weblogs.com/0001011/, which includes an orange XML button, as shown in Figure 10-2.

Figure 10-2. XML button

XML button

Now, install the user script (Tools → Install This User Script) and refresh http://radio.weblogs.com/0001011/. You will see the XML button is gone, replaced by a plain-text link, as shown in Figure 10-3.

Figure 10-3. Feed link

Feed link

Hacking the Hack

This hack makes the Web a prettier place, but it doesn't actually make it any more useful. If you click a feed link, it still just shows you the raw RSS or Atom feed, or worse, offers to download it. There is an emerging standard among desktop news aggregators for subscribing to feeds by using a feed:// URL instead of http://. News aggregators register themselves as the handler for the feed:// protocol, and when you click one, it launches your news aggregator and offers to subscribe to the feed.

We can make one small change to this script to make feed links launch your external news aggregator when you click them.

Add this code at the end of the loop:

	for (var i = XML syndicated feed buttonssnapXMLImages.snapshotLength - 1; i >= 0; i--) {
		var syndicationXML buttonselmXMLImage = snapXMLImages.snapshotItem(i);

		if (/(XML syndicated feed buttonsxml|rss)/i.test(syndicationXML buttonselmXMLImage.src)) { 
			var elmXMLText = document.createTextNode('Feed');
			elmXMLImage.parentNode.replaceChild(elmXMLText, elmXMLImage);
			var elmLink = elmXMLText.parentNode;
			while (elmLink && elmLink.nodeName.toUpperCase() != 'A' &&
				  elmLink.nodeName.toUpperCase() != 'BODY') {
				elmLink = elmLink.parentNode;
			}
			if (elmLink && elmLink.nodeName.toUpperCase() == 'A') {
				elmLink.href = elmLink.href.replace(/^http:\/\//, 'feed://');
			}
		}
	}

Now when you click a feed link, Firefox will automatically launch your desktop news aggregator and prompt you to subscribe to the feed.

Squeeze More Feeds into the Bloglines Sidebar

Tweak Bloglines' style to see more subscriptions without scrolling.

Bloglines uses frames to provide a quick overview of your subscriptions, even as you're reading news. The name of each feed is displayed in bold when the feed contains new unread items. Unfortunately, the layout of the sidebar is space-inefficient, with a larger header and font, so you can see only a limited number of feeds without scrolling.

This user script removes the pretty but useless header from the sidebar and reduces the height of each feed link so that many more feeds are visible at the same time.

The Code

This user script runs on the Bloglines feed-reading page. It checks to make sure Bloglines is in reading mode; if it is in editing mode (for example, adding or reordering your subscriptions), the formatting of the sidebar is not changed.

The code itself is divided into four parts:

Modify font style
First, it sets the font style for the entire sidebar to a smaller size and reduces the interline spacing, so that more lines display in the same vertical space.
Change the header
Next, it hides the Bloglines logo and the tabs for adding and editing feeds. The links for adding and editing feeds are important, but they don't need to be displayed so prominently. Instead of removing them completely, the script moves them to the bottom of the frame, below the list of subscriptions.
Remove extra padding
To make the subscription list flush with the top of the browser window, the script sets the cellpadding attribute of the main <table> to 0.
Reduce logos
We have one last problem: the line height still expands to fit the height of the logo displayed next to each feed. To work around this, the script reduces the size of each feed image to fit within the smaller line height.

Save the following user script as bloglines-sidebar-squeezer.user.js:

	// ==UserScript==
	// @name		   Bloglines BloglinessidebarSidebar Squeezer
	// @namespace	   http://www.allpeers.com/blog/greasemonkey
	// @description  Squeezes the feeds in the Bloglines side panel
	// @include	   http://bloglines.com/myblogs_subs*
	// @include	   http://www.bloglines.com/myblogs_subs*
	// ==/UserScript==

	// based on code by Matthew Gertner
	// and included here with his gracious permission

	document.body.style["font"] = "x-small/1.2em Verdana, Arial, Helvetica, " +
		"sans-serif;";

	var divs = document.getElementsByTagName("div");
	var menudiv = null;
	var i;
	for(i = 0; i < divs.length; i++) {
			var divclass = divs[i].getAttribute("class");
			if (divclass == "header-list" || divclass == "tabs")
				divs[i].style["display"] = "none";
			else if (divclass == "hnav")
				menudiv = divs[i].parentNode.removeChild(divs[i]);
			else if (divclass == "account" && menudiv != null)
				   divs[i].parentNode.insertBefore(menudiv, divs[i]);
	}

	var tables = document.getElementsByTagName("table");
	tables[0].setAttribute("cellpadding", "0");

	var imgs = document.getElementsByTagName("img");
	for(i = 0; i < imgs.length; i++) {
			imgs[i].setAttribute("height", "13");
	}

Running the Hack

Before installing the script, log into Bloglines and navigate to your Bloglines reading page at http://www.bloglines.com/myblogs/. Bloglines displays your list of subscriptions in the frame on the left, as shown in Figure 10-4.

Figure 10-4. Standard Bloglines sidebar

Standard Bloglines sidebar

Now, install the user script (Tools → Install This User Script) and refresh the page. The script squeezes the most out of the subscription frame to display more feeds, as shown in Figure 10-5.

Now you can feed your news addiction with 40% less scrolling!

Matthew Gertner

Figure 10-5. Squeezed Bloglines sidebar

Squeezed Bloglines sidebar

Automatically Collect Syndicated Feeds

Autodiscover RSS and Atom feeds as you browse, and then generate a subscriptions file you can import into any news aggregator.

Are you new to the world of syndication? If you've heard about using syndicated RSS or Atom feeds to keep up with your favorite sites in a news aggregator, here's an easy way to get started. Install the Feed Collector user script in this hack, and then simply visit the sites you visit regularly. Feed Collector will find the syndicated feeds automatically.

At the bottom-right corner of your browser window, you'll see a running total of the number of feeds collected so far. When you're ready, you're one click away from creating a subscriptions file that contains all the sites you've visited. The subscriptions file is in an XML format that's compatible with virtually every news aggregator.

Tip

For more information on syndicated feeds, visit http://www.atomenabled.org.

The Code

This user script runs on all web pages. The code is longer than most of the other hacks in this book, but it breaks down into four distinct parts:

  1. The getPageFeeds function autodiscovers syndicated feeds using a combination of XPath, regular expressions, JavaScript string functions, and elbow grease. Most syndication-enabled publishing software supports feed autodiscovery by embedding a special <link> element in the page that points to the page's syndicated feed.
  2. The functions getHistoryCount, getHistoryItem, getAllHistoryItems, findHistoryItemByURL, addToHistory, and clearHistory manage storing the collected feeds in the Firefox preference database.
  3. The buildSubscriptionFile function uses DOMParser, XMLSerializer, and core DOM methods to construct the XML subscription file.
  4. The impenetrable mess of innerHTML goo at the very bottom of the script adds the two links to the bottom-right corner of the page you're visiting, to display or clear your collected feeds.

Tip

This script passes all the tests in the autodiscovery test suite, available at http://diveintomark.org/tests/client/autodiscovery/.

Save the following user script as feedcollector.user.js:

	// ==UserScript==
	// @name		   Feed Collector
	// @namespace	   http://diveintomark.org/projects/greasemonkey/
	// @description  collect auto-discovered feeds in visited pages
	// @include	   http://*
	// ==/UserScript==

	function getPageFeeds() {
		var dateNow = new Date();
		var elmPossible = document.evaluate("//*[@rel][@type][@href]",
			document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		var arFeeds = new Array();
		for (var i = 0; i < elmPossible.snapshotLength; i++) {
			var elm = elmPossible.snapshotItem(i);
			if (!elm.rel) { continue; }
			if (!elm.type) { continue; }
			if (!elm.href) { continue; }

			var sNodeName = elm.nodeName.toLowerCase();
			if ((sNodeName != 'link') && (sNodeName != 'a')) { continue; }
			var sRel = elm.rel.toLowerCase();
			var bRelIsAlternate = false;
			var arRelValues = sRel.split(/\s/);
			for (var j = arRelValues.length - 1; j >= 0; j--) {

				bRelIsAlternate = bRelIsAlternate ||
				(arRelValues[j] == 'alternate');
			}
			if (!bRelIsAlternate) { continue; }
			var sType = elm.type.toLowerCase().trim();
			if ((sType != 'application/RSS feedsrss+xml') &&
				(sType != 'application/Atom feedsatom+xml') &&
				(sType != 'text/xml')) { continue; }
			var syndicationfeed collectionurlFeed = elm.href.trim();
			var sTitle = elm.title.trim() || '';
			feedsarFeeds.push({href: urlFeed,
				  title: sTitle,
				  type: sType,
				  homepage: location.href});
		}
		return arFeeds;
	}

	function getHistoryCount() {
		return GM_getValue('count', 0);
	}

	function getHistoryItem(iHistoryIndex) {
		var urlFeed = GM_getValue(iHistoryIndex + '.href', '');
		if (!urlFeed) { return null; }
		var sTitle = GM_getValue(iHistoryIndex + '.title', '');
		var sType = GM_getValue(iHistoryIndex + '.type', '');
		var sHomepage = GM_getValue(iHistoryIndex + '.homepage', '');
		return {href: urlFeed,
				title: sTitle,
				type: sType,
				homepage: sHomepage};
		}

		function getAllHistoryItems() {
			var iHistoryCount = getHistoryCount();
			var arFeeds = new Array();
			for (var i = 0; i < iHistoryCount; i++) {
				arFeeds.push(getHistoryItem(i));
			}
			return arFeeds;
		}

		function findHistoryItemByURL(urlFeed) {
			var iHistoryCount = getHistoryCount();
			for (var i = 0; i < iHistoryCount; i++) {
				var oHistory = getHistoryItem(i);

			if (oHistory.href == syndicationfeed collectionurlFeed) {
				return i;
			}
		}
		return -1;
	}

	function addToHistory(oFeedInfo) {
		var iHistoryCount = getHistoryCount();
		if (findHistoryItemByURL(oFeedInfo.href) != -1) { return; }
		if (document.title && oFeedInfo.title) {
			sFeedTitle = document.title + ' - ' + oFeedInfo.title;
		} else if (document.title) {
			sFeedTitle = document.title;
		} else if (oFeedInfo.title) {
			sFeedTitle = oFeedInfo.title;
		} else {
			sFeedTitle = oFeedInfo.href;
		}
		sFeedTitle = sFeedTitle.replace(/\s+/g, ' ');
		sFeedTitle = sFeedTitle.replace(/[^A-Za-z0-9\- ]/g, '');
		var sType = oFeedInfo.type;
		sType = sType.substring(sType.indexOf('/') + 1);
		if (sType.indexOf('+') != -1) {
			sType = sType.substring(0, sType.indexOf('+'));
		} else {
			sType = 'RSS feedsrss';
		}
		GM_setValue(iHistoryCount + '.href', oFeedInfo.href);
		GM_setValue(iHistoryCount + '.title', sFeedTitle);
		GM_setValue(iHistoryCount + '.homepage', oFeedInfo.homepage);
		GM_setValue(iHistoryCount + '.type', sType);
		GM_setValue('count', iHistoryCount + 1);
	}

	function clearHistory() {
		var iHistoryCount = getHistoryCount();
		for (var i = 0; i < iHistoryCount; i++) {
			GM_setValue(i + '.href', '');
			GM_setValue(i + '.title', '');
			GM_setValue(i + '.homepage', '');
			GM_setValue(i + '.type', '');
		}
		GM_setValue('count', 0);
	}

	function appendNew(elmRoot, elmParent, sNodeName) {
		var elmChild = elmRoot.createElement(sNodeName);
		elmParent.appendChild(elmChild);
		return elmChild;
	}

	function buildSubscriptionFile() {

		var oParser = new DOMParser();
		var elmRoot = oParser.parseFromString('<opml/>', 'application/xml');
		elmRoot.documentElement.setAttribute('version', '1.0');
		var nodeComment = elmRoot.createComment(
			'Save this using "File/Save Page As…", and then import it ' +
			'into your news aggregator.');
		elmRoot.documentElement.appendChild(nodeComment);
		var elmHead = appendNew(elmRoot, elmRoot.documentElement, 'head');
		var elmTitle = appendNew(elmRoot, elmHead, 'title');
		elmTitle.appendChild(elmRoot.createTextNode('syndicationfeed collectionFeed Collector'));
		var dateNow = new Date();
		var elmDate = appendNew(elmRoot, elmHead, 'dateCreated');
		elmDate.appendChild(elmRoot.createTextNode(dateNow.toGMTString()));
		var elmOwnerName = appendNew(elmRoot, elmHead, 'ownerName');
		var elmBody = appendNew(elmRoot, elmRoot.documentElement, 'body');
		var elmOutline = appendNew(elmRoot, elmBody, 'outline');
		elmOutline.setAttribute('title', 'Subscriptions');
		var iHistoryCount = getHistoryCount();
		var Atom feedsarFeeds = getAllHistoryItems();
		for (var i = 0; i < iHistoryCount; i++) {
			var oFeedInfo = feedsarFeeds[i];
			var elmItem = appendNew(elmRoot, elmOutline, 'outline');
			elmItem.setAttribute('title', oFeedInfo.title);
			elmItem.setAttribute('htmlUrl', oFeedInfo.homepage);
			elmItem.setAttribute('type', oFeedInfo.type);
			elmItem.setAttribute('xmlUrl', oFeedInfo.href);
		}
		var serializer = new XMLSerializer();
		return serializer.serializeToString(elmRoot);
	}

	function RSS feedsdisplayFeeds(event) {
		var sSubscriptionData = buildSubscriptionFile();
		GM_openInTab('data:application/xml,'+ sSubscriptionData);
		event.preventDefault();
	}

	function clearFeeds(event) {
		var iHistoryCount = getHistoryCount();
		clearHistory();
		var elmFeedCollector = document.getElementById('feedcollector');
		if (elmFeedCollector) {
			elmFeedCollector.parentNode.removeChild(elmFeedCollector);
		}
		event.preventDefault()
	}

	String.prototype.trim = function() {
		var s = this;
		s = s.replace(/^\s+/, '');
		s = s.replace(/\s+$/, '');

		return s;
	}

	var Atom feedsarFeeds = feedsgetPageFeeds();
	for (var i = 0; i < RSS feedsarFeeds.length; i++) {
		addToHistory(syndicationfeed collectionarFeeds[i]);
	}
	var iHistoryCount = getHistoryCount();
	if (!iHistoryCount) { return; }
	var elmWrapper = document.createElement('div');
	elmWrapper.id = 'feedcollector';
	elmWrapper.innerHTML = '<div style="position: fixed; bottom: 0; ' +
		'right: 0; padding: 1px 4px 3px 4px; background-color: #ddd; ' +
		'color: #000; border-top: 1px solid #bbb; border-left: 1px ' +
		'solid #bbb; font-family: sans-serif; font-size: x-small;">' +
		'<a href="#" title="Display collected feeds" ' +
		'id="feedcollectordisplay" style="background-color: transparent; ' +
		'color: black; font-size: x-small; font-family: sans-serif; ' +
		'text-decoration: none;">' + iHistoryCount + ' feed' +
		(iHistoryCount > 1 ? 's' : '') + ' collected</a> &middot; ' +
		'[<a href="#" title="Clear list of collected feeds" ' +
		'id="feedcollectorclear" style="background-color: transparent; ' +
		'color: black; font-size: x-small; font-family: sans-serif; ' +
		'text-decoration: none;">clear</a>]</div>';
	document.body.insertBefore(elmWrapper, document.body.firstChild);
	document.getElementById('feedcollectordisplay').addEventListener(
		'click', displayFeeds, true);
	document.getElementById('feedcollectorclear').addEventListener(
		'click', clearFeeds, true);

Running the Hack

After installing the user script (Tools → Install This User Script), start visiting your favorite syndication-enabled sites. I went to three of my personal favorites: Sam Ruby's (http://www.intertwingly.net/blog/), Joe Gregorio's (http://bitworking.org), and Tim Bray's (http://tbray.org/ongoing/). In the bottom-right corner of your browser window, you will see an updated count of the number of feeds collected so far, as shown in Figure 10-6.

Figure 10-6. Three feeds collected so far

Three feeds collected so far

When you're ready to import your collected feeds into your news aggregator, simply click the "Display collected feeds" link. This generates an XML subscription file in a format supported by all aggregators, as shown in Figure 10-7.

Figure 10-7. XML subscription file

XML subscription file

Save this subscription file to your local computer by selecting Save Page As… from the File menu. I called mine subscriptions.xml, but you can name it anything you like.

Now, go to your news aggregator and find the Import Subscriptions option. In Bloglines, log in to get to My Feeds, and then choose Edit → Import Subscriptions. Click Browse and select the subscription file you just saved, as shown in Figure 10-8.

Figure 10-8. Importing subscriptions in Bloglines

Importing subscriptions in Bloglines

Click Import, and Bloglines will import your collected subscriptions. As shown in Figure 10-9, in this case, all three sites were imported successfully.

Figure 10-9. Feeds imported successfully

Feeds imported successfully

If you click My Feeds again, you will see the three imported subscriptions, as shown in Figure 10-10.

Figure 10-10. Bloglines with imported subscriptions

Bloglines with imported subscriptions

Bloglines remembers all past items, but it maxes out at showing you the most recent 200 for each site. Looks like I have a lot of reading to do!

Syndicate Encrypted Content

Publish sensitive data securely and decrypt it on the fly.

I have a problem. It's actually a pretty common problem. I have data that I want to syndicate to myself, but I don't want you to see it. It's private. Now this data could be my credit card balance, my Google AdSense earnings, or internal bug reports for the day job. Whatever it is, I want the information in a form suitable for syndication but not available to everyone.

There is a solution. I could password-protect my feed. But that causes a problem, because my aggregator would then need to know my password. Now, my aggregator of choice is Bloglines, and I'm sure they're nice folks, but I really don't want to give them my password. One security breach, and my credit card history will be splattered across the Web.

The solution is to have a user script that detects the encrypted content and, using the right key, decrypts the encrypted content. I can continue to use Bloglines to poll the feed and present me with new items as they appear, but the decryption is done in my browser.

So, here is the whole scenario:

  1. My content, which is going to sit inside the description element of an RSS feed, is going to be encrypted.
  2. That feed is syndicated.
  3. I will subscribe to that feed in Bloglines (or any other web-based aggregator).
  4. When I view items in that feed in Bloglines, the description is initially displayed encrypted, but the Greasemonkey script detects the encrypted content and decrypts it on the fly and replaces the encrypted content with the decrypted content.

Here is an example of the microformat we're using to transport our encrypted content:

	<div class="encrypted Blowfishblowfish" >
		<p>The following data is encrypted. Please install
			the SecureSyndication Greasemonkey script to
			view the encrypted content.
		</p>
		<div class="encdata">WORK:C7FDD…15C4AC0643B86</div>
	</div>

The outer div has a class of "encrypted blowfish", which means that the contents of div are encrypted. (Blowfish is the actual name of the encryption algorithm. I'm not making that up.) The content of <div class="encdata"> is the encrypted data. The first thing in the encrypted data is a key name, then a colon, and then the Blowfish-encrypted content. This allows different keys to be used for different feeds; the decryption script can have an array of keys and look up the password for each one when it's time to decrypt the data.

If there are other elements in the div, they are ignored. That lets us put a nice paragraph in there explaining what is going on to those who are unfamiliar with encrypted content. When our user script runs, it will decrypt the data and replace the innerHTML of the outer div with that decrypted content, thus overwriting the explanation. If someone subscribes to the feed who is not running the decryption script (or doesn't have the right password associated with this key), he will see the explanatory paragraph and the original encrypted data.

The Code

This script runs on Bloglines using a JavaScript implementation of Bruce Schneier's Blowfish algorithm. The rest of the code loops over all the div elements with a class attribute that contains "encrypted blowfish". For each block of encrypted content, we break the content at the colon to get the key name and the encrypted data. We create a new Blowfish context, initialize it with the indicated private key, decrypt the contents, and replace the outer div element with the decrypted data.

Save the following user script as securesyndication.user.js:

	// ==UserScript==
	// @name		  Secure syndicationSyndication
	// @namespace	  http://bitworking.org/projects/securesyndication/script/
	// @include		  http://bloglines.com/*
	// @include		  http://www.bloglines.com/*
	// @include		  http://bitworking.org/projects/securesyndication/*
	// @description	  Allows decrypting content in syndication feeds
	// ==/UserScript==

	keys = {"WORK": "TESTKEY"};

	N = 16;

	var ORIG_P = [
			0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344,
			0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89,
			0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C,
			0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
			0x9216D5D9, 0x8979FB1B
	];

	var ORIG_S = [];

	ORIG_S[0] =
		[	0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7,
			0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99,
			0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16,
			0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E,

			0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE,
			0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013,
			0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF,
			0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E,
			0xD71577C1, 0xBD314B27, 0x78AF2FDA, 0x55605C60,
			0xE65525F3, 0xAA55AB94, 0x57489862, 0x63E81440,
			0x55CA396A, 0x2AAB10B6, 0xB4CC5C34, 0x1141E8CE,
			0xA15486AF, 0x7C72E993, 0xB3EE1411, 0x636FBC2A,
			0x2BA9C55D, 0x741831F6, 0xCE5C3E16, 0x9B87931E,
			0xAFD6BA33, 0x6C24CF5C, 0x7A325381, 0x28958677,
			0x3B8F4898, 0x6B4BB9AF, 0xC4BFE81B, 0x66282193,
			0x61D809CC, 0xFB21A991, 0x487CAC60, 0x5DEC8032,
			0xEF845D5D, 0xE98575B1, 0xDC262302, 0xEB651B88,
			0x23893E81, 0xD396ACC5, 0x0F6D6FF3, 0x83F44239,
			0x2E0B4482, 0xA4842004, 0x69C8F04A, 0x9E1F9B5E,
			0x21C66842, 0xF6E96C9A, 0x670C9C61, 0xABD388F0,
			0x6A51A0D2, 0xD8542F68, 0x960FA728, 0xAB5133A3,
			0x6EEF0B6C, 0x137A3BE4, 0xBA3BF050, 0x7EFB2A98,
			0xA1F1651D, 0x39AF0176, 0x66CA593E, 0x82430E88,
			0x8CEE8619, 0x456F9FB4, 0x7D84A5C3, 0x3B8B5EBE,
			0xE06F75D8, 0x85C12073, 0x401A449F, 0x56C16AA6,
			0x4ED3AA62, 0x363F7706, 0x1BFEDF72, 0x429B023D,
			0x37D0D724, 0xD00A1248, 0xDB0FEAD3, 0x49F1C09B,
			0x075372C9, 0x80991B7B, 0x25D479D8, 0xF6E8DEF7,
			0xE3FE501A, 0xB6794C3B, 0x976CE0BD, 0x04C006BA,
			0xC1A94FB6, 0x409F60C4, 0x5E5C9EC2, 0x196A2463,
			0x68FB6FAF, 0x3E6C53B5, 0x1339B2EB, 0x3B52EC6F,
			0x6DFC511F, 0x9B30952C, 0xCC814544, 0xAF5EBD09,
			0xBEE3D004, 0xDE334AFD, 0x660F2807, 0x192E4BB3,
			0xC0CBA857, 0x45C8740F, 0xD20B5F39, 0xB9D3FBDB,
			0x5579C0BD, 0x1A60320A, 0xD6A100C6, 0x402C7279,
			0x679F25FE, 0xFB1FA3CC, 0x8EA5E9F8, 0xDB3222F8,
			0x3C7516DF, 0xFD616B15, 0x2F501EC8, 0xAD0552AB,
			0x323DB5FA, 0xFD238760, 0x53317B48, 0x3E00DF82,
			0x9E5C57BB, 0xCA6F8CA0, 0x1A87562E, 0xDF1769DB,
			0xD542A8F6, 0x287EFFC3, 0xAC6732C6, 0x8C4F5573,
			0x695B27B0, 0xBBCA58C8, 0xE1FFA35D, 0xB8F011A0,
			0x10FA3D98, 0xFD2183B8, 0x4AFCB56C, 0x2DD1D35B,
			0x9A53E479, 0xB6F84565, 0xD28E49BC, 0x4BFB9790,
			0xE1DDF2DA, 0xA4CB7E33, 0x62FB1341, 0xCEE4C6E8,
			0xEF20CADA, 0x36774C01, 0xD07E9EFE, 0x2BF11FB4,
			0x95DBDA4D, 0xAE909198, 0xEAAD8E71, 0x6B93D5A0,
			0xD08ED1D0, 0xAFC725E0, 0x8E3C5B2F, 0x8E7594B7,
			0x8FF6E2FB, 0xF2122B64, 0x8888B812, 0x900DF01C,
			0x4FAD5EA0, 0x688FC31C, 0xD1CFF191, 0xB3A8C1AD,
			0x2F2F2218, 0xBE0E1777, 0xEA752DFE, 0x8B021FA1,
			0xE5A0CC0F, 0xB56F74E8, 0x18ACF3D6, 0xCE89E299,
			0xB4A84FE0, 0xFD13E0B7, 0x7CC43B81, 0xD2ADA8D9,
			0x165FA266, 0x80957705, 0x93CC7314, 0x211A1477,
			0xE6AD2065, 0x77B5FA86, 0xC75442F5, 0xFB9D35CF,
			0xEBCDAF0C, 0x7B3E89A0, 0xD6411BD3, 0xAE1E7E49,
			0x00250E2D, 0x2071B35E, 0x226800BB, 0x57B8E0AF,
			0x2464369B, 0xF009B91E, 0x5563911D, 0x59DFA6AA,

			0x78C14389, 0xD95A537F, 0x207D5BA2, 0x02E5B9C5,
			0x83260376, 0x6295CFA9, 0x11C81968, 0x4E734A41,
			0xB3472DCA, 0x7B14A94A, 0x1B510052, 0x9A532915,
			0xD60F573F, 0xBC9BC6E4, 0x2B60A476, 0x81E67400,
			0x08BA6FB5, 0x571BE91F, 0xF296EC6B, 0x2A0DD915,
			0xB6636521, 0xE7B9F9B6, 0xFF34052E, 0xC5855664,
			0x53B02D5D, 0xA99F8FA1, 0x08BA4799, 0x6E85076A
	];
	ORIG_S[1] =
		[	0x4B7A70E9, 0xB5B32944, 0xDB75092E, 0xC4192623,
			0xAD6EA6B0, 0x49A7DF7D, 0x9CEE60B8, 0x8FEDB266,
			0xECAA8C71, 0x699A17FF, 0x5664526C, 0xC2B19EE1,
			0x193602A5, 0x75094C29, 0xA0591340, 0xE4183A3E,
			0x3F54989A, 0x5B429D65, 0x6B8FE4D6, 0x99F73FD6,
			0xA1D29C07, 0xEFE830F5, 0x4D2D38E6, 0xF0255DC1,
			0x4CDD2086, 0x8470EB26, 0x6382E9C6, 0x021ECC5E,
			0x09686B3F, 0x3EBAEFC9, 0x3C971814, 0x6B6A70A1,
			0x687F3584, 0x52A0E286, 0xB79C5305, 0xAA500737,
			0x3E07841C, 0x7FDEAE5C, 0x8E7D44EC, 0x5716F2B8,
			0xB03ADA37, 0xF0500C0D, 0xF01C1F04, 0x0200B3FF,
			0xAE0CF51A, 0x3CB574B2, 0x25837A58, 0xDC0921BD,
			0xD19113F9, 0x7CA92FF6, 0x94324773, 0x22F54701,
			0x3AE5E581, 0x37C2DADC, 0xC8B57634, 0x9AF3DDA7,
			0xA9446146, 0x0FD0030E, 0xECC8C73E, 0xA4751E41,
			0xE238CD99, 0x3BEA0E2F, 0x3280BBA1, 0x183EB331,
			0x4E548B38, 0x4F6DB908, 0x6F420D03, 0xF60A04BF,
			0x2CB81290, 0x24977C79, 0x5679B072, 0xBCAF89AF,
			0xDE9A771F, 0xD9930810, 0xB38BAE12, 0xDCCF3F2E,
			0x5512721F, 0x2E6B7124, 0x501ADDE6, 0x9F84CD87,
			0x7A584718, 0x7408DA17, 0xBC9F9ABC, 0xE94B7D8C,
			0xEC7AEC3A, 0xDB851DFA, 0x63094366, 0xC464C3D2,
			0xEF1C1847, 0x3215D908, 0xDD433B37, 0x24C2BA16,
			0x12A14D43, 0x2A65C451, 0x50940002, 0x133AE4DD,
			0x71DFF89E, 0x10314E55, 0x81AC77D6, 0x5F11199B,
			0x043556F1, 0xD7A3C76B, 0x3C11183B, 0x5924A509,
			0xF28FE6ED, 0x97F1FBFA, 0x9EBABF2C, 0x1E153C6E,
			0x86E34570, 0xEAE96FB1, 0x860E5E0A, 0x5A3E2AB3,
			0x771FE71C, 0x4E3D06FA, 0x2965DCB9, 0x99E71D0F,
			0x803E89D6, 0x5266C825, 0x2E4CC978, 0x9C10B36A,
			0xC6150EBA, 0x94E2EA78, 0xA5FC3C53, 0x1E0A2DF4,
			0xF2F74EA7, 0x361D2B3D, 0x1939260F, 0x19C27960,
			0x5223A708, 0xF71312B6, 0xEBADFE6E, 0xEAC31F66,
			0xE3BC4595, 0xA67BC883, 0xB17F37D1, 0x018CFF28,
			0xC332DDEF, 0xBE6C5AA5, 0x65582185, 0x68AB9802,
			0xEECEA50F, 0xDB2F953B, 0x2AEF7DAD, 0x5B6E2F84,
			0x1521B628, 0x29076170, 0xECDD4775, 0x619F1510,
			0x13CCA830, 0xEB61BD96, 0x0334FE1E, 0xAA0363CF,
			0xB5735C90, 0x4C70A239, 0xD59E9E0B, 0xCBAADE14,
			0xEECC86BC, 0x60622CA7, 0x9CAB5CAB, 0xB2F3846E,
			0x648B1EAF, 0x19BDF0CA, 0xA02369B9, 0x655ABB50,
			0x40685A32, 0x3C2AB4B3, 0x319EE9D5, 0xC021B8F7,
			0x9B540B19, 0x875FA099, 0x95F7997E, 0x623D7DA8,
			0xF837889A, 0x97E32D77, 0x11ED935F, 0x16681281,

			0x0E358829, 0xC7E61FD6, 0x96DEDFA1, 0x7858BA99,
			0x57F584A5, 0x1B227263, 0x9B83C3FF, 0x1AC24696,
			0xCDB30AEB, 0x532E3054, 0x8FD948E4, 0x6DBC3128,
			0x58EBF2EF, 0x34C6FFEA, 0xFE28ED61, 0xEE7C3C73,
			0x5D4A14D9, 0xE864B7E3, 0x42105D14, 0x203E13E0,
			0x45EEE2B6, 0xA3AAABEA, 0xDB6C4F15, 0xFACB4FD0,
			0xC742F442, 0xEF6ABBB5, 0x654F3B1D, 0x41CD2105,
			0xD81E799E, 0x86854DC7, 0xE44B476A, 0x3D816250,
			0xCF62A1F2, 0x5B8D2646, 0xFC8883A0, 0xC1C7B6A3,
			0x7F1524C3, 0x69CB7492, 0x47848A0B, 0x5692B285,
			0x095BBF00, 0xAD19489D, 0x1462B174, 0x23820E00,
			0x58428D2A, 0x0C55F5EA, 0x1DADF43E, 0x233F7061,
			0x3372F092, 0x8D937E41, 0xD65FECF1, 0x6C223BDB,
			0x7CDE3759, 0xCBEE7460, 0x4085F2A7, 0xCE77326E,
			0xA6078084, 0x19F8509E, 0xE8EFD855, 0x61D99735,
			0xA969A7AA, 0xC50C06C2, 0x5A04ABFC, 0x800BCADC,
			0x9E447A2E, 0xC3453484, 0xFDD56705, 0x0E1E9EC9,
			0xDB73DBD3, 0x105588CD, 0x675FDA79, 0xE3674340,
			0xC5C43465, 0x713E38D8, 0x3D28F89E, 0xF16DFF20,
			0x153E21E7, 0x8FB03D4A, 0xE6E39F2B, 0xDB83ADF7
	];
	ORIG_S[2] =
		[	0xE93D5A68, 0x948140F7, 0xF64C261C, 0x94692934,
			0x411520F7, 0x7602D4F7, 0xBCF46B2E, 0xD4A20068,
			0xD4082471, 0x3320F46A, 0x43B7D4B7, 0x500061AF,
			0x1E39F62E, 0x97244546, 0x14214F74, 0xBF8B8840,
			0x4D95FC1D, 0x96B591AF, 0x70F4DDD3, 0x66A02F45,
			0xBFBC09EC, 0x03BD9785, 0x7FAC6DD0, 0x31CB8504,
			0x96EB27B3, 0x55FD3941, 0xDA2547E6, 0xABCA0A9A,
			0x28507825, 0x530429F4, 0x0A2C86DA, 0xE9B66DFB,
			0x68DC1462, 0xD7486900, 0x680EC0A4, 0x27A18DEE,
			0x4F3FFEA2, 0xE887AD8C, 0xB58CE006, 0x7AF4D6B6,
			0xAACE1E7C, 0xD3375FEC, 0xCE78A399, 0x406B2A42,
			0x20FE9E35, 0xD9F385B9, 0xEE39D7AB, 0x3B124E8B,
			0x1DC9FAF7, 0x4B6D1856, 0x26A36631, 0xEAE397B2,
			0x3A6EFA74, 0xDD5B4332, 0x6841E7F7, 0xCA7820FB,
			0xFB0AF54E, 0xD8FEB397, 0x454056AC, 0xBA489527,
			0x55533A3A, 0x20838D87, 0xFE6BA9B7, 0xD096954B,
			0x55A867BC, 0xA1159A58, 0xCCA92963, 0x99E1DB33,
			0xA62A4A56, 0x3F3125F9, 0x5EF47E1C, 0x9029317C,
			0xFDF8E802, 0x04272F70, 0x80BB155C, 0x05282CE3,
			0x95C11548, 0xE4C66D22, 0x48C1133F, 0xC70F86DC,
			0x07F9C9EE, 0x41041F0F, 0x404779A4, 0x5D886E17,
			0x325F51EB, 0xD59BC0D1, 0xF2BCC18F, 0x41113564,
			0x257B7834, 0x602A9C60, 0xDFF8E8A3, 0x1F636C1B,
			0x0E12B4C2, 0x02E1329E, 0xAF664FD1, 0xCAD18115,
			0x6B2395E0, 0x333E92E1, 0x3B240B62, 0xEEBEB922,
			0x85B2A20E, 0xE6BA0D99, 0xDE720C8C, 0x2DA2F728,
			0xD0127845, 0x95B794FD, 0x647D0862, 0xE7CCF5F0,
			0x5449A36F, 0x877D48FA, 0xC39DFD27, 0xF33E8D1E,
			0x0A476341, 0x992EFF74, 0x3A6F6EAB, 0xF4F8FD37,
			0xA812DC60, 0xA1EBDDF8, 0x991BE14C, 0xDB6E6B0D,
			0xC67B5510, 0x6D672C37, 0x2765D43B, 0xDCD0E804,

			0xF1290DC7, 0xCC00FFA3, 0xB5390F92, 0x690FED0B,
			0x667B9FFB, 0xCEDB7D9C, 0xA091CF0B, 0xD9155EA3,
			0xBB132F88, 0x515BAD24, 0x7B9479BF, 0x763BD6EB,
			0x37392EB3, 0xCC115979, 0x8026E297, 0xF42E312D,
			0x6842ADA7, 0xC66A2B3B, 0x12754CCC, 0x782EF11C,
			0x6A124237, 0xB79251E7, 0x06A1BBE6, 0x4BFB6350,
			0x1A6B1018, 0x11CAEDFA, 0x3D25BDD8, 0xE2E1C3C9,
			0x44421659, 0x0A121386, 0xD90CEC6E, 0xD5ABEA2A,
			0x64AF674E, 0xDA86A85F, 0xBEBFE988, 0x64E4C3FE,
			0x9DBC8057, 0xF0F7C086, 0x60787BF8, 0x6003604D,
			0xD1FD8346, 0xF6381FB0, 0x7745AE04, 0xD736FCCC,
			0x83426B33, 0xF01EAB71, 0xB0804187, 0x3C005E5F,
			0x77A057BE, 0xBDE8AE24, 0x55464299, 0xBF582E61,
			0x4E58F48F, 0xF2DDFDA2, 0xF474EF38, 0x8789BDC2,
			0x5366F9C3, 0xC8B38E74, 0xB475F255, 0x46FCD9B9,
			0x7AEB2661, 0x8B1DDF84, 0x846A0E79, 0x915F95E2,
			0x466E598E, 0x20B45770, 0x8CD55591, 0xC902DE4C,
			0xB90BACE1, 0xBB8205D0, 0x11A86248, 0x7574A99E,
			0xB77F19B6, 0xE0A9DC09, 0x662D09A1, 0xC4324633,
			0xE85A1F02, 0x09F0BE8C, 0x4A99A025, 0x1D6EFE10,
			0x1AB93D1D, 0x0BA5A4DF, 0xA186F20F, 0x2868F169,
			0xDCB7DA83, 0x573906FE, 0xA1E2CE9B, 0x4FCD7F52,
			0x50115E01, 0xA70683FA, 0xA002B5C4, 0x0DE6D027,
			0x9AF88C27, 0x773F8641, 0xC3604C06, 0x61A806B5,
			0xF0177A28, 0xC0F586E0, 0x006058AA, 0x30DC7D62,
			0x11E69ED7, 0x2338EA63, 0x53C2DD94, 0xC2C21634,
			0xBBCBEE56, 0x90BCB6DE, 0xEBFC7DA1, 0xCE591D76,
			0x6F05E409, 0x4B7C0188, 0x39720A3D, 0x7C927C24,
			0x86E3725F, 0x724D9DB9, 0x1AC15BB4, 0xD39EB8FC,
			0xED545578, 0x08FCA5B5, 0xD83D7CD3, 0x4DAD0FC4,
			0x1E50EF5E, 0xB161E6F8, 0xA28514D9, 0x6C51133C,
			0x6FD5C7E7, 0x56E14EC4, 0x362ABFCE, 0xDDC6C837,
			0xD79A3234, 0x92638212, 0x670EFA8E, 0x406000E0
	];
	ORIG_S[3] =
		[	0x3A39CE37, 0xD3FAF5CF, 0xABC27737, 0x5AC52D1B,
			0x5CB0679E, 0x4FA33742, 0xD3822740, 0x99BC9BBE,
			0xD5118E9D, 0xBF0F7315, 0xD62D1C7E, 0xC700C47B,
			0xB78C1B6B, 0x21A19045, 0xB26EB1BE, 0x6A366EB4,
			0x5748AB2F, 0xBC946E79, 0xC6A376D2, 0x6549C2C8,
			0x530FF8EE, 0x468DDE7D, 0xD5730A1D, 0x4CD04DC6,
			0x2939BBDB, 0xA9BA4650, 0xAC9526E8, 0xBE5EE304,
			0xA1FAD5F0, 0x6A2D519A, 0x63EF8CE2, 0x9A86EE22,
			0xC089C2B8, 0x43242EF6, 0xA51E03AA, 0x9CF2D0A4,
			0x83C061BA, 0x9BE96A4D, 0x8FE51550, 0xBA645BD6,
			0x2826A2F9, 0xA73A3AE1, 0x4BA99586, 0xEF5562E9,
			0xC72FEFD3, 0xF752F7DA, 0x3F046F69, 0x77FA0A59,
			0x80E4A915, 0x87B08601, 0x9B09E6AD, 0x3B3EE593,
			0xE990FD5A, 0x9E34D797, 0x2CF0B7D9, 0x022B8B51,
			0x96D5AC3A, 0x017DA67D, 0xD1CF3ED6, 0x7C7D2D28,
			0x1F9F25CF, 0xADF2B89B, 0x5AD6B472, 0x5A88F54C,
			0xE029AC71, 0xE019A5E6, 0x47B0ACFD, 0xED93FA9B,
			0xE8D3C48D, 0x283B57CC, 0xF8D56629, 0x79132E28,

			0x785F0191, 0xED756055, 0xF7960E44, 0xE3D35E8C,
			0x15056DD4, 0x88F46DBA, 0x03A16125, 0x0564F0BD,
			0xC3EB9E15, 0x3C9057A2, 0x97271AEC, 0xA93A072A,
			0x1B3F6D9B, 0x1E6321F5, 0xF59C66FB, 0x26DCF319,
			0x7533D928, 0xB155FDF5, 0x03563482, 0x8ABA3CBB,
			0x28517711, 0xC20AD9F8, 0xABCC5167, 0xCCAD925F,
			0x4DE81751, 0x3830DC8E, 0x379D5862, 0x9320F991,
			0xEA7A90C2, 0xFB3E7BCE, 0x5121CE64, 0x774FBE32,
			0xA8B6E37E, 0xC3293D46, 0x48DE5369, 0x6413E680,
			0xA2AE0810, 0xDD6DB224, 0x69852DFD, 0x09072166,
			0xB39A460A, 0x6445C0DD, 0x586CDECF, 0x1C20C8AE,
			0x5BBEF7DD, 0x1B588D40, 0xCCD2017F, 0x6BB4E3BB,
			0xDDA26A7E, 0x3A59FF45, 0x3E350A44, 0xBCB4CDD5,
			0x72EACEA8, 0xFA6484BB, 0x8D6612AE, 0xBF3C6F47,
			0xD29BE463, 0x542F5D9E, 0xAEC2771B, 0xF64E6370,
			0x740E0D8D, 0xE75B1357, 0xF8721671, 0xAF537D5D,
			0x4040CB08, 0x4EB4E2CC, 0x34D2466A, 0x0115AF84,
			0xE1B00428, 0x95983A1D, 0x06B89FB4, 0xCE6EA048,
			0x6F3F3B82, 0x3520AB82, 0x011A1D4B, 0x277227F8,
			0x611560B1, 0xE7933FDC, 0xBB3A792B, 0x344525BD,
			0xA08839E1, 0x51CE794B, 0x2F32C9B7, 0xA01FBAC9,
			0xE01CC87E, 0xBCC7D1F6, 0xCF0111C3, 0xA1E8AAC7,
			0x1A908749, 0xD44FBD9A, 0xD0DADECB, 0xD50ADA38,
			0x0339C32A, 0xC6913667, 0x8DF9317C, 0xE0B12B4F,
			0xF79E59B7, 0x43F5BB3A, 0xF2D519FF, 0x27D9459C,
			0xBF97222C, 0x15E6FC2A, 0x0F91FC71, 0x9B941525,
			0xFAE59361, 0xCEB69CEB, 0xC2A86459, 0x12BAA8D1,
			0xB6C1075E, 0xE3056A0C, 0x10D25065, 0xCB03A442,
			0xE0EC6E0E, 0x1698DB3B, 0x4C98A0BE, 0x3278E964,
			0x9F1F9532, 0xE0D392DF, 0xD3A0342B, 0x8971F21E,
			0x1B0A7441, 0x4BA3348C, 0xC5BE7120, 0xC37632D8,
			0xDF359F8D, 0x9B992F2E, 0xE60B6F47, 0x0FE3F11D,
			0xE54CDA54, 0x1EDAD891, 0xCE6279CF, 0xCD3E7E6F,
			0x1618B166, 0xFD2C1D05, 0x848FD2C5, 0xF6FB2299,
			0xF523F357, 0xA6327623, 0x93A83531, 0x56CCCD02,
			0xACF08162, 0x5A75EBB5, 0x6E163697, 0x88D273CC,
			0xDE966292, 0x81B949D0, 0x4C50901B, 0x71C65614,
			0xE6C6C7BD, 0x327A140A, 0x45E1D006, 0xC3F27B9A,
			0xC9AA53FD, 0x62A80F00, 0xBB25BFE2, 0x35BDD2F6,
			0x71126905, 0xB2040222, 0xB6CBCF7C, 0xCD769C2B,
			0x53113EC0, 0x1640E3D3, 0x38ABBD60, 0x2547ADF0,
			0xBA38209C, 0xF746CE76, 0x77AFA1C5, 0x20756060,
			0x85CBFE4E, 0x8AE88DD8, 0x7AAAF9B0, 0x4CF9AA7E,
			0x1948C25C, 0x02FB8A8C, 0x01C36AE4, 0xD6EBE1F9,
			0x90D4F869, 0xA65CDEA0, 0x3F09252D, 0xC208E69F,
			0xB74E6132, 0xCE77E25B, 0x578FDFE3, 0x3AC372E6
	];
	
	function BlowfishCtx () {
		this.P = [];
		this.S = [];
		for (var i=0; i<4; i++) {
			this.S[i] = [];

		}
		return this;
	};

	function uns32Add(a, b) {
		var sum = (a + b) & 0xFFFFFFFF;
		var retval;
		if (sum < 0) {
			sum = -sum;
			var lw = ((sum & 0xFFFF) ^ 0xFFFF) + 1;
			var uw = ((sum >> 16) ^ 0xFFFF);
			retval = 65536 * uw + lw;
		} else {
			retval = sum;
		}
		return retval;
	}

	function split32(a) {
		var r = a & 0xFFFFFFFF;
		var retval = [];
		if (r < 0) {
			r = -r;
			retval[0] = ((r & 0xFFFF) ^ 0xFFFF) + 1;
			retval[1] = ((r >> 16) ^ 0xFFFF);
		} else {
			retval[0] = r & 0xFFFF;
			retval[1] = r >> 16;
		}
		return retval;
	}

	function uns32Xor(x, y) {
		xs = split32(x);
		ys = split32(y);
		return 65536 * (xs[1] ^ ys[1]) + (xs[0] ^ ys[0]);
	}

	function F(ctx, x) {
		var a, b, c, d;
		var y;

		d = x & 0xFF;
		x >>= 8;
		c = x & 0xFF;
		x >>= 8;
		b = x & 0xFF;
		x >>= 8;
		a = x & 0xFF;
		y = uns32Add(ctx.S[0][a], ctx.S[1][b]);
		y = uns32Xor(y, ctx.S[2][c]);
		y = uns32Add(y, ctx.S[3][d]);

		return y;
	}
	
	function blowfish_encrypt(ctx, Xl, Xr) {
		var temp;

		for (var i = 0; i < N; ++i) {
		  Xl = uns32Xor(Xl,ctx.P[i]);
		  Xr = uns32Xor(F(ctx, Xl), Xr);

		  temp = Xl;
		  Xl = Xr;
		  Xr = temp;
		}

		temp = Xl;
		Xl = Xr;
		Xr = temp;

		Xr = uns32Xor(Xr, ctx.P[N]);
		Xl = uns32Xor(Xl, ctx.P[N + 1]);

		  return [Xl, Xr];
		}

		function blowfish_decrypt(ctx, Xl, Xr){
		  var temp;

		  for (i = N + 1; i > 1; --i) {
			Xl = uns32Xor(Xl, ctx.P[i]);
			Xr = uns32Xor(F(ctx, Xl), Xr);

			temp = Xl;
			Xl = Xr;
			Xr = temp;
		  }

		  temp = Xl;
		  Xl = Xr;
		  Xr = temp;

		  Xr = uns32Xor(Xr, ctx.P[1]);
		  Xl = uns32Xor(Xl, ctx.P[0]);

		  return [Xl, Xr];
		}

		function blowfish_init(ctx, key) {
			var data, datal, datar;
			var i, j, k;
			for (i = 0; i < 4; i++) {
			  for (j = 0; j < 256; j++)

			ctx.S[i][j] = ORIG_S[i][j];
		}
		
		j = 0;
		for (i = 0; i < N + 2; ++i) {
		  data = 0x00000000;
		  for (k = 0; k < 4; ++k) {
			data = ((data << 8) & 0xFFFFFFFF) | key.charCodeAt(j);
			j = j + 1;
			if (j >= key.length) {
				j = 0;
			}
		  }
		  ctx.P[i] = ORIG_P[i] ^ data;
		}
		datal = 0x00000000;
		datar = 0x00000000;
		var res = [];

		for (i = 0; i < N + 2; i += 2) {
		  res = blowfish_encrypt(ctx, datal, datar);
		  datal = res[0];
		  datar = res[1];
		  ctx.P[i] = datal;
		  ctx.P[i + 1] = datar;
		}
		for (i = 0; i < 4; ++i) {
		  for (j = 0; j < 256; j += 2) {
			  res = blowfish_encrypt(ctx, datal, datar);
			  datal = res[0];
			  datar = res[1];
			  ctx.S[i][j] = datal;
			  ctx.S[i][j + 1] = datar;
		  }
		}
	}

	function decrypt(context, s) {
		var L = 0, R = 0;
		L = parseInt(s.substring(0, 8), 16);
		R = parseInt(s.substring(8, 16), 16);
		r = blowfish_decrypt(context, L, R);
		var s = "";
		if (r[0] & 0xFFFF) {
			s += String.fromCharCode(r[0] & 0xFFFF);
		}
		if (r[0] >> 16) {
			s += String.fromCharCode(r[0] >> 16);
		}
		if (r[1] & 0xFFFF) {
			s += String.fromCharCode(r[1] & 0xFFFF);
		}

		if (r[1] >> 16) {
			s += String.fromCharCode(r[1] >> 16);
		}
		return s;
	}

	function decrypt_string(context, s) {
		output = "";
		while (s.length) {
			output += decrypt(context, s);
			s = s.substring(16, s.length);
		}
		return output;
	}

	var enc_divs = document.evaluate("//div[contains('encrypted contentencrypted blowfish', " +
		"@class)]//div[contains('encdata', @class)]",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var i = 0; i < enc_divs.snapshotLength; i++) {
		var tdiv = enc_divs.snapshotItem(i);
		var div_content = tdiv.innerHTML.split(":");
		if (div_content.length < 2) { continue; }
		if (!(div_content[0] in keys)) { continue; }
		var context = new BlowfishCtx();
		blowfish_init(context, keys[div_content[0]]);
		tdiv.parentNode.innerHTML = decrypt_string(
			context, div_content.slice(1).join(':'));
	}

Running the Hack

Before you install the user script, log into Bloglines (http://www.bloglines.com), click Add to add a subscription, and subscribe to the Secure Syndication test feed (http://bitworking.org/projects/securesyndication/index.rss). When you view the entry in Bloglines, all you will see is encrypted data, as shown in Figure 10-11.

Figure 10-11. Encrypted feed

Encrypted feed

Now, install the script (Tools → Install This User Script) and refresh the Bloglines page. Select the encrypted feed subscription, and then select All Items from the drop-down menu to force Bloglines to redisplay the encrypted entry. You will briefly see the encrypted data, and then the script will decrypt the data and replace it with the decrypted message, as shown in Figure 10-12.

Figure 10-12. Decrypted feed

Decrypted feed

Currently, the encryption key is stored in the script, thus forcing you to hand-edit the script for every new key you use. It would be easy enough to store the keys with GM_setValue and retrieve them with GM_getValue. This would allow you to enter new keys manually by going to about:config and adding a new value. Or you could construct a graphical interface for adding a new key.

To store passwords in the Firefox preferences database, change the for loop:

	for (var i = 0; i < enc_divs.snapshotLength; i++) {
		var tdiv = enc_divs.snapshotItem(i);
		var div_content = tdiv.innerHTML.split(":");
		if (div_content.length != 2) { continue; }
		var pass = GM_getValue('key.' + div_content[0]);
		if (!pass) { continue; }
		var context = new BlowfishCtx();
		blowfish_init(context, pass);
		tdiv.parentNode.innerHTML = decrypt_string(context, div_content[1]);
	}

If the script encounters an encrypted area for which it doesn't have the key, it will simply leave the encrypted data as-is. But we could extend this further, to insert a form above the encrypted data that prompts the user for the password and then saves it with GM_setValue:

	var enc_divs = document.evaluate("//div[contains('encrypted blowfish', " +
		"@class)]//div[contains('encdata', @class)]",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

		for (var i = 0; i < enc_divs.snapshotLength; i++) {
			var tdiv = enc_divs.snapshotItem(i);
			var div_content = tdiv.innerHTML.split(":");
			if (div_content.length < 2) { continue; }
			var pass = GM_getValue('key.' + div_content[0]);
			if (!pass) {
				var elmForm = document.createElement('form');
				elmForm.id = div_content[0];
				elmForm.innerHTML = 'Password: <input type="password">';
				elmForm.addEventListener('submit', function(e) {
				var elmPassword = e.target.getElementsByTagName('input')[0];
				var pass = elmPassword.value;
				GM_setValue('key.' + e.target.id, pass);
				e.preventDefault();
				var context = new BlowfishCtx();
				blowfish_init(context, pass);
				var tdiv = e.target.nextSibling;
				tdiv.parentNode.innerHTML = decrypt_string(context,
				tdiv.innerHTML.split(':').slice(1).join(':'));
				}, true);
				tdiv.parentNode.insertBefore(elmForm, tdiv);
				continue;
			}
			var context = new BlowfishCtx();
			blowfish_init(context, pass);
			tdiv.parentNode.innerHTML = decrypt_string(
				context, div_content.slice(1).join(':'));
		}

Now, instead of simply showing the encrypted data, the script displays a small form in which you can enter the password to decrypt the data, as shown in Figure 10-13.

Figure 10-13. Entering a password to decrypt the feed

Entering a password to decrypt the feed

The script traps the onsubmit event and stores the password you entered, and then proceeds to decrypt the data and display it.

Joe Gregorio

Personal tools