<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">

  <title><![CDATA[andrew makes things]]></title>
  
  <link href="http://blog.andrewcantino.com/" />
  <updated>2012-02-15T12:01:01-08:00</updated>
  <id>http://blog.andrewcantino.com/</id>
  <author>
    <name><![CDATA[Andrew Cantino]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/andrew-makes-things" /><feedburner:info uri="andrew-makes-things" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><entry>
    <title type="html"><![CDATA[Fixing the Chrome background refresh bug]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/b_zwtObTOuY/" />
    <updated>2012-02-15T11:55:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2012/02/15/fixing-the-chrome-background-refresh-bug</id>
    <content type="html">&lt;p&gt;There is a &lt;a href="http://code.google.com/p/chromium/issues/detail?id=111218#makechanges"&gt;bug in the current version of Chromium&lt;/a&gt; (hence Google Chrome) that sometimes fails to redraw CSS background images when they&amp;#8217;re hidden and then re-shown.  This issue appeared on &lt;a href="http://mavenlink.com/tour"&gt;Mavenlink&amp;#8217;s Tour&lt;/a&gt; page.  Thomas Fuchs &lt;a href="http://mir.aculo.us/2011/12/07/the-case-of-the-disappearing-element/"&gt;discusses some possible solutions&lt;/a&gt;, but none of those worked for us.  Here is our ugly solution:&lt;/p&gt;

&lt;div&gt;&lt;script src='https://gist.github.com/1838523.js?file='&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;pre&gt;&lt;code&gt;function refreshBackgrounds(selector) {
  // Chrome shim to fix http://groups.google.com/a/chromium.org/group/chromium-bugs/browse_thread/thread/1b6a86d6d4cb8b04/739e937fa945a921
  // Remove this once Chrome fixes its bug.
  if (/chrome/.test(navigator.userAgent.toLowerCase())) {
    $(selector).each(function() {
      var $this = $(this);
      if ($this.css(&amp;quot;background-image&amp;quot;)) {
        var oldBackgroundImage = $this.css(&amp;quot;background-image&amp;quot;);
        setTimeout(function() {
          $this.css(&amp;quot;background-image&amp;quot;, oldBackgroundImage);
        }, 1);
      }
    });
  }
}

// You'll need to call this every time the event occurs that exposes the bug, such as changing tab divs.
refreshBackgrounds(&amp;quot;.something-with-a-background-image&amp;quot;);
refreshBackgrounds(&amp;quot;*&amp;quot;); // but it'll be slow!&lt;/code&gt;&lt;/pre&gt;&lt;/noscript&gt;&lt;/div&gt;


&lt;p&gt;Please let me know if you find something better!&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/b_zwtObTOuY" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2012/02/15/fixing-the-chrome-background-refresh-bug/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Hacking Google for fun and profit]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/ZPYevRQdu-I/" />
    <updated>2011-12-14T20:37:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit</id>
    <content type="html">&lt;p&gt;At the end of last year, Google announced their &lt;a href="http://googleonlinesecurity.blogspot.com/2010/11/rewarding-web-application-security.html"&gt;Vulnerability Reward Program&lt;/a&gt; which rewards security researchers for reported security and privacy holes in Google properties.  This sounded like an interesting challenge, and I set out to find security holes.  I found three, got paid, and am now in the &lt;a href="http://www.google.com/about/corporate/company/halloffame.html"&gt;Google Security Hall of Fame&lt;/a&gt;. All in all, a rewarding experience.&lt;/p&gt;

&lt;p&gt;Below I describe the three security holes that I found.&lt;/p&gt;

&lt;h2&gt;Determining if a user has emailed another user&lt;/h2&gt;

&lt;p&gt;In my opinion, this is the most subtle, but also the most disturbing, of the three bugs.  As with the other bugs that I found, this was an example of &lt;a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery"&gt;Cross Site Request Forgery&lt;/a&gt;- the practice of convincing a user&amp;#8217;s browser to make a request on their behalf to a remote server.  This type of attack generally only works when the user is logged in to the remote service.  In this case, if a user is already logged into Gmail (and they usually are), a malicious website could make a series of requests for Gmail profile images and, based on the return codes, determine whether or not the visitor had communicated with another Gmail user.  This worked because Gmail, as a well-intentioned privacy measure, would only show profile images to a viewer if they had had mutual contact.  Here is some example code that worked at the time:&lt;/p&gt;

&lt;figure class='code'&gt;&lt;figcaption&gt;&lt;span&gt;checkUsername&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;table&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre class="line-numbers"&gt;&lt;span class='line-number'&gt;1&lt;/span&gt;
&lt;span class='line-number'&gt;2&lt;/span&gt;
&lt;span class='line-number'&gt;3&lt;/span&gt;
&lt;span class='line-number'&gt;4&lt;/span&gt;
&lt;span class='line-number'&gt;5&lt;/span&gt;
&lt;span class='line-number'&gt;6&lt;/span&gt;
&lt;span class='line-number'&gt;7&lt;/span&gt;
&lt;span class='line-number'&gt;8&lt;/span&gt;
&lt;span class='line-number'&gt;9&lt;/span&gt;
&lt;span class='line-number'&gt;10&lt;/span&gt;
&lt;span class='line-number'&gt;11&lt;/span&gt;
&lt;span class='line-number'&gt;12&lt;/span&gt;
&lt;span class='line-number'&gt;13&lt;/span&gt;
&lt;span class='line-number'&gt;14&lt;/span&gt;
&lt;span class='line-number'&gt;15&lt;/span&gt;
&lt;span class='line-number'&gt;16&lt;/span&gt;
&lt;span class='line-number'&gt;17&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;td class='code'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='line'&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;checkUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;          &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;      &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;https://mail.google.com/mail/photos/&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%40gmail.com?1&amp;amp;rp=1&amp;amp;pld=1&amp;amp;r=&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;&lt;span class="nx"&gt;checkUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fbi-reports&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasEmailed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;  &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The current visitor &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasEmailed&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;has&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;has not&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; emailed the FBI.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;&lt;span class="nx"&gt;checkUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;wikileaks&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasEmailed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;  &lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;The current visitor &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasEmailed&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;has&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;has not&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; emailed WikiLeaks.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/figure&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;It should be clear why this is a serious privacy concern.  If you suspected someone of being a whistleblower, for example, you could make a page that probed a bunch of revealing email addresses and checked to see if any had been contacted.  Luckily, Google reports that they have now fixed this bug.  Cross Site Request Forgery attacks can usually be prevented by adding a &lt;a href="http://en.wikipedia.org/wiki/Cross-site_request_forgery#Other_approaches_to_CSRF"&gt;CSRF&lt;/a&gt; token (a unique and user-specific token) to every request.&lt;/p&gt;

&lt;h2&gt;Identification of a user&amp;#8217;s Gmail address&lt;/h2&gt;

&lt;p&gt;This bug would have allowed a malicious website to determine your Google username if you were simultaneously logged into your Google account and typed anything into a seemingly innocuous web form.  One of the fields in the form would actually be an iframe pointing to a public Google Document.  When the user typed into the field, they would really be entering text into the Google Document, and what appeared to be their cursor in the field would actually be the Google Document insertion point.  When a user typed into the field, the attacker could determine their username (and hence email address) by observing the publicly-displayed list of current document editors.&lt;/p&gt;

&lt;p&gt;Again, this is a type of Cross Site Request Forgery, specifically known as &lt;a href="http://en.wikipedia.org/wiki/Clickjacking"&gt;Clickjacking&lt;/a&gt;, which can be especially hard to prevent.  There are many types of Clickjacking, almost all of which use iframes.  One approach, which I used here, is to artfully display content from a target site in such a way as to look like it&amp;#8217;s part of the current page. Another approach is to hide the iframe invisibly under the user&amp;#8217;s cursor, moving it as the cursor moves, and causing the user to click on the other site without realizing it.&lt;/p&gt;

&lt;p&gt;Google correctly used the &lt;a href="http://msdn.microsoft.com/en-us/library/dd565647.aspx"&gt;X-XSS-Protection&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header"&gt;X-Frame-Options&lt;/a&gt; headers, but some browsers do not honor these.  The solution to this one is tricky, but it is generally to use &lt;a href="http://en.wikipedia.org/wiki/Framekiller"&gt;frame busting&lt;/a&gt;, to provide appropriate headers, to use CSRF tokens, and to not expose any user information without a direct user interaction.&lt;/p&gt;

&lt;h2&gt;Deletion of all future email&lt;/h2&gt;

&lt;p&gt;The third bug that I found was a fairly severe security hole that affected a portion of Gmail users.  Due to a missing CSRF token during the first step of the filter creation flow in the HTML-only version of Gmail, a malicious site could trick visitors into creating a Gmail filter that would delete all future received email.  This worked in the current (at the time) version of Firefox, but not in Chrome or Safari due to their correct handling of the x-frame-options header.  I didn&amp;#8217;t test it in IE.&lt;/p&gt;

&lt;p&gt;This security hole was exploitable via a combination of a classic Cross Site Request Forgery with a Clickjacking attack.  First, I discovered that it was possible to submit the first part of the filter creation flow in an iframe using JavaScript because Google had forgotten to include a unique CSRF token in the form.&lt;/p&gt;

&lt;figure class='code'&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;table&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre class="line-numbers"&gt;&lt;span class='line-number'&gt;1&lt;/span&gt;
&lt;span class='line-number'&gt;2&lt;/span&gt;
&lt;span class='line-number'&gt;3&lt;/span&gt;
&lt;span class='line-number'&gt;4&lt;/span&gt;
&lt;span class='line-number'&gt;5&lt;/span&gt;
&lt;span class='line-number'&gt;6&lt;/span&gt;
&lt;span class='line-number'&gt;7&lt;/span&gt;
&lt;span class='line-number'&gt;8&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;td class='code'&gt;&lt;pre&gt;&lt;code class='html'&gt;&lt;span class='line'&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;form&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;iframe&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;https://mail.google.com/mail/h/ignored/?v=prf&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;multipart/form-data&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;cf1_hasnot&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;adfkjhsdf&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;s&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;cf2_tr&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;cf1_attach&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;false&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;nvp_bu_nxsb&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;Next Step&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;display: none&amp;#39;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/figure&gt;


&lt;p&gt;I then positioned the iframe such that the &amp;#8220;Create Filter&amp;#8221; button on the subsequent page would fill the frame without showing the button border; only the word &amp;#8220;Create&amp;#8221; was visible.  A fake button was then shown around the iframe with a style that matched the gmail style such that when the user believed they were submitting a form with a submit button entitled &amp;#8220;Create,&amp;#8221; they were really creating a malicious and destructive filter in Gmail.&lt;/p&gt;

&lt;p&gt;Google says this has now been fixed.&lt;/p&gt;

&lt;h2&gt;Google&amp;#8217;s Response&lt;/h2&gt;

&lt;p&gt;In all three cases, Google responded promptly to my security report and fixed the bug within a reasonable amount of time.  I was given two $500 awards for the three bugs.  Google generously doubled these amounts when I chose to donate them to charity, so the &lt;a href="http://www.athensconservancy.org/"&gt;Athens Conservency&lt;/a&gt; and the &lt;a href="http://www.buckeyeforestcouncil.org/"&gt;Buckeye Forest Council&lt;/a&gt;, two of my favorite local charities in Athens, OH, received one thousand dollars each, care of Google.&lt;/p&gt;

&lt;p&gt;These were subtle bugs.  They took trial and error to find.  However, in total, I only spent a few spare evenings of my time.  If Google&amp;#8217;s products- some of the most secure in the world- are susceptible to these sorts of attacks, you can bet many others are as well.  Every programer makes these mistakes sometimes.  Security is too complicated for anyone to get right all of the time.  Check your code!&lt;/p&gt;

&lt;h2&gt;Take your security into your own hands&amp;#8230; or, why you should hack Google too!&lt;/h2&gt;

&lt;p&gt;Many companies try to silence security bug reporters through legal threats and sometimes even action, driving discoverers of bugs underground and onto the black market where such knowledge can do real harm.  Google has set an admirable example by creating a program that is enlightened, responsive, and well-run, and I hope other companies move in the same direction.&lt;/p&gt;

&lt;p&gt;I had a great time using &lt;a href="http://jsFiddle.net"&gt;jsFiddle&lt;/a&gt; to explore and demonstrate bugs.  You can do the same&amp;#8211; check out their &lt;a href="http://googleonlinesecurity.blogspot.com/2010/11/rewarding-web-application-security.html"&gt;guidelines&lt;/a&gt; and do your part to improve the security of products that you love.&lt;/p&gt;

&lt;p&gt;Enjoyed this post?  You should &lt;a href="https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fblog.andrewcantino.com%2Fblog%2F2011%2F12%2F14%2Fhacking-google-for-fun-and-profit%2F&amp;region=follow_link&amp;screen_name=tectonic&amp;source=followbutton&amp;variant=2.0"&gt;follow me on Twitter&lt;/a&gt;.&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/ZPYevRQdu-I" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2011/12/14/hacking-google-for-fun-and-profit/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[How to make your Rails app tweet the Twitter]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/EsokS-bU_wQ/" />
    <updated>2011-05-12T17:35:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2011/05/12/how-to-make-your-rails-app-tweet-the-twitter</id>
    <content type="html">&lt;p&gt;Suppose you want to build a Rails application for tracking popular links, and you want it to post the most popular links to Twitter automatically.  This quick tutorial will show you how to do that using the newest version of the Ruby &lt;a href="https://github.com/jnunemaker/twitter"&gt;twitter gem&lt;/a&gt;.  A little while ago I added the ability for &lt;a href="http://absurdlycool.com"&gt;Freebies Finder&lt;/a&gt; to tweet popular freebies.  I recently had to do this for another site and decided that a tutorial was in order.&lt;/p&gt;

&lt;h2&gt;Setup the accounts&lt;/h2&gt;

&lt;p&gt;We&amp;#8217;ll pretend that our website is called AwesomeLinks.com.  &lt;a href="https://twitter.com/signup" target="_blank"&gt;Signup for two Twitter accounts&lt;/a&gt;, AwesomeLinks and AwesomeLinksDev.  We need to create a Twitter application through which our website can post to these accounts.  Do this by logging into AwesomeLinks and visiting &lt;a href="https://dev.twitter.com/apps/new"&gt;https://dev.twitter.com/apps/new&lt;/a&gt;.  Select &amp;#8216;Client&amp;#8217; as the Application Type, skip the Callback URL, and select Read &amp;amp; Write access.  Twitter will give you a OAuth Consumer key and secret, which you will soon need.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h2&gt;Getting your OAuth Token and Secret&lt;/h2&gt;

&lt;p&gt;Now you need to authorize your new Twitter Application to post on both of your Twitter accounts.  For this, we use a script:&lt;/p&gt;

&lt;script src="https://gist.github.com/969776.js?file=get_token.rb"&gt;&lt;/script&gt;


&lt;p style="font-size: 0.8em"&gt;(If you used the Twitter gem in the past, you may have used &lt;code&gt;authorize_from_access&lt;/code&gt; for this, but that no longer works.  We now have to require and use oauth separately.)&lt;/p&gt;


&lt;p&gt;Fill in your Twitter Application&amp;#8217;s Consumer key and secret and run the script.  You will be prompted to visit a URL and then to enter the PIN that Twitter provides.  Do this for both of your new Twitter accounts and record the results in &lt;code&gt;config/twitter.yml&lt;/code&gt;, like so:&lt;/p&gt;

&lt;script src="https://gist.github.com/969776.js?file=twitter.yml"&gt;&lt;/script&gt;


&lt;h2&gt;Initializing the Twitter gem&lt;/h2&gt;

&lt;p&gt;Now, create an initializer in &lt;code&gt;config/initializers/twitter.rb&lt;/code&gt; and again include your Twitter App&amp;#8217;s key and secret:&lt;/p&gt;

&lt;script src="https://gist.github.com/969776.js?file=twitter_initializer.rb"&gt;&lt;/script&gt;




&lt;p style="font-size: 0.8em"&gt;(If you want to Tweet to multiple accounts, you can do this differently and instead make separate &lt;code&gt;Twitter::Client&lt;/code&gt; objects, each with their own OAuth tokens.)&lt;/p&gt;


&lt;h2&gt;Tweeting the Twitter&lt;/h2&gt;

&lt;p&gt;Finally, it&amp;#8217;s time to augment our Link model so that it can send tweets.  I decided to have it tweet the link&amp;#8217;s description and a shortened version of its URL using the following code:&lt;/p&gt;

&lt;script src="https://gist.github.com/969776.js?file=link.rb"&gt;&lt;/script&gt;


&lt;p&gt;The rest is up to you.  You could write a cronjob to automatically call &lt;code&gt;tweet!&lt;/code&gt; on every link, or only on those with enough popularity.  Have fun!&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/EsokS-bU_wQ" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2011/05/12/how-to-make-your-rails-app-tweet-the-twitter/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Why plug computers are a security nightmare]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/0tfgsQMvsvk/" />
    <updated>2011-02-28T18:03:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2011/02/28/why-plug-computers-are-a-security-nightmare</id>
    <content type="html">&lt;p&gt;The increasing availability of low profile &amp;#8220;wall-wart&amp;#8221; plug computers like the &lt;a href="http://www.plugcomputer.org/"&gt;SheevaPlug&lt;/a&gt; can be viewed as an emerging threat to physical network security.  For $99, a budding industrial espionagist could buy the SheevaPlug developer kit or the consumer &lt;a href="http://www.tonidoplug.com/"&gt;TonidoPlug&lt;/a&gt;, install some easily-available network intrusion testing software, and illicitly &amp;#8220;test&amp;#8221; the security of a competitor&amp;#8217;s network.&lt;/p&gt;

&lt;p&gt;While many of these techniques have been known for a while, the low form factor of plug computers and consumer netbooks, coupled with their rapidly decreasing price, could enable disposable intrusion tools and open new avenues for attack.  The current $99 SheevaPlug has no wireless capability and limited storage, but the manufacture has &lt;a href="http://www.prnewswire.com/news-releases/marvell-unveils-plug-computer-30-with-integrated-wireless-and-built-in-hard-drive-80693037.html"&gt;just announced&lt;/a&gt; an expanded model with wifi, bluetooth, and an internal hard drive.  Even without these advances, current generation plug computers can easily be expanded with a USB memory stick or external USB hard drive, USB wireless interface, and more.  For little more than $100 one could make a practically undetectable wireless bug that can be deployed in seconds.&lt;/p&gt;

&lt;p&gt;In fact, soon you may be able to just buy an all-in-one penetration plug computer, the &lt;a href="http://theplugbot.com/"&gt;PlugBot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;edit&lt;/strong&gt;: as was pointed out to me after posting this article, the described device already exists and can tunnel out over &lt;strong&gt;3G&lt;/strong&gt;.  &lt;a href="http://pwnieexpress.com/pwnplug3g.html"&gt;http://pwnieexpress.com/pwnplug3g.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With two wireless adapters and some simple software, a plug computer becomes a wireless bridge capable of automatically cracking wireless networks in range (both WEP and WPA are vulnerable these days; see &lt;a href="http://www.aircrack-ng.org/"&gt;aircrack&lt;/a&gt;).  Most locations have multiple 3rd party networks overlapping their physical space, which, if cracked, could be used as back channels for the plug computer to phone home.  The attacker could then tunnel into the company network undetected and completely bypass the company&amp;#8217;s external defenses by routing through an available 3rd party wireless network.  From the perspective of the attacked network, even if the intrusion is noticed, it appears to come from within their own physical space.&lt;/p&gt;

&lt;p&gt;A number of other uses come to mind for such devices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passive sniffing of internal network traffic using dsniff and sending it back to an attacker.  Many networks aren&amp;#8217;t sufficiently secured once you&amp;#8217;re past the perimeter firewalls.&lt;/li&gt;
&lt;li&gt;Physically connect two ethernet interfaces and use the plug computer as a man-in-the-middle proxy to sniff all traffic entering and leaving a workstation.&lt;/li&gt;
&lt;li&gt;Attach a camera or other sensor payload and use as an over-the-internet video bug.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Again, much of this has been possible for a while, but form factor is everything.  Also, I haven&amp;#8217;t seen people talking about the possibility of bridging multiple available wireless networks together for attack obfuscation and to avoid connecting through a company&amp;#8217;s edge network.  I don&amp;#8217;t think companies pay enough attention to passive physical monitoring and intrusion threats like this, especially given the insecurity of wireless encryption standards.  What do you think?&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/0tfgsQMvsvk" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2011/02/28/why-plug-computers-are-a-security-nightmare/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[How I do command-line accounting: Ledger and Reckon]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/fDIC-MndRNU/" />
    <updated>2010-11-06T18:03:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/11/06/command-line-accounting-with-ledger-and-reckon</id>
    <content type="html">&lt;p&gt;Ledger is a powerful yet simple double-entry accounting system with a command line interface, making it perfect for those of us who prefer our text editor and flat files over any confusing and inflexible accounting program.&lt;/p&gt;

&lt;p&gt;I&amp;#8217;ve been using Ledger for about a year now, including for balancing last year&amp;#8217;s budget.  In the process, I&amp;#8217;ve needed both to import CSV files of financial data from various sources and to label that data with the most appropriate account.  To do this, I&amp;#8217;ve written a Ruby gem called &lt;a href="https://github.com/iterationlabs/reckon"&gt;Reckon&lt;/a&gt; that can usually guess bank data CSV headings, and can also use &lt;a href="http://en.wikipedia.org/wiki/Bayesian_inference"&gt;Bayesian learning&lt;/a&gt; to automatically categorize each entry.  I think of this as Mint for the command line, but where you don&amp;#8217;t have to trust a 3rd party with your bank account passwords.&lt;/p&gt;

&lt;p&gt;Getting started with Ledger is pretty easy.  I recommend skimming the &lt;a href="http://cloud.github.com/downloads/jwiegley/ledger/ledger.pdf"&gt;official documentation (pdf)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I usually start out the year by making a new year.dat file and entering my business bank account&amp;#8217;s starting balance:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;2010/01/01 * Checking
    Assets:Bank:Checking            $5.000.00
    Equity:Opening Balances
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, I run Reckon on a CSV dump file from my bank: &lt;code&gt;reckon -f bank.csv -p&lt;/code&gt;&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;If the output looks reasonable, I convert to ledger format and label everything:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ reckon -f bank.csv -o output.dat

What is the account name of this bank account in Ledger? |Assets:Bank:Checking|

(Here I accept the default because I want to have all money flowing in and out of my bank account.)

+------------+--------+--------------------------+
| 2010/03/13 |  $100.00 | GOOGLE ADSENSE REVENUE |
+------------+--------+--------------------------+

Which account provided this income? ([account]/[q]uit/[s]kip) Income:Adsense

(I decided to call this Income:Adsense.)

+------------+---------+-----------------------------+
| 2010/03/14 | -$10.00 | FEE: INCOMING DOMESTIC WIRE |
+------------+---------+-----------------------------+

To which account did this money go? ([account]/[q]uit/[s]kip) |Income:Adsense| Expenses:Bank Fees

+------------+-----------+-----------------------+
| 2010/03/14 |  $1000.00 | WIRE TRANSFER DEPOSIT |
+------------+-----------+-----------------------+

Which account provided this income? ([account]/[q]uit/[s]kip) |Expenses:Bank Fees| Income:Various Sales

(Reckon guessed Expenses:Bank Fees, but I decided to call this Income:Blogging.)

+------------+-----------+------------------------+
| 2010/03/28 |  $300.00  | GOOGLE ADSENSE REVENUE |
+------------+-----------+------------------------+

Which account provided this income? ([account]/[q]uit/[s]kip) |Income:Adsense|

(Notice how this time it guessed Income:Adsense correctly.)

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, my output.dat file looks something like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;2010/01/01 * Checking
    Assets:Bank:Checking               $5.000.00
    Equity:Opening Balances

2010/03/13    GOOGLE ADSENSE REVENUE
    Assets:Bank:Checking               $100.00
    Income:Adsense                    -$100.00

2010/03/14    FEE: INCOMING DOMESTIC WIRE
    Expenses:Bank Fees                 $10.00
    Assets:Bank:Checking              -$10.00

2010/03/14    WIRE TRANSFER DEPOSIT
    Assets:Bank:Checking               $1000.00
    Income:Blogging                   -$1000.00

2010/03/28    GOOGLE ADSENSE REVENUE
    Assets:Bank:Checking               $300.00
    Income:Adsense                    -$300.00
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And if I run Ledger on it, I can get a breakdown:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ ledger -f output.dat balance

        $6390.00  Assets
       $-5000.00  Equity
          $10.00  Expenses
       $-1400.00  Income

$ ledger -f output.dat equity

    2010/11/06 Opening Balances
    Assets:Bank:Checking                    $6390.00
    Equity:Opening Balances                $-5000.00
    Expenses:Bank Fees                        $10.00
    Income:Adsense                          $-400.00
    Income:Blogging                        $-1000.00
                                               0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice that the income is negative because the money flowed from the income source (Google and Blogging) to the destination (my bank account).  This is double entry accounting, so the final sum is always zero, as seen in the last row.&lt;/p&gt;

&lt;p&gt;Finally, later in the year when there is new financial data, I want Reckon to learn from my existing .dat file, so I do:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;reckon -f bank.csv -l 2010.dat -o output.dat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To install Ledger and Reckon, see their Github pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iterationlabs/reckon"&gt;https://github.com/iterationlabs/reckon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jwiegley/ledger"&gt;https://github.com/jwiegley/ledger&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/fDIC-MndRNU" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/11/06/command-line-accounting-with-ledger-and-reckon/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Rails RSpec tests are CPU bound]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/Dnt0JGMFRlY/" />
    <updated>2010-10-26T18:03:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/10/26/rspec-is-cpu-bound</id>
    <content type="html">&lt;p&gt;Today I experimented with running a large Rails RSpec test suite on a RAM disk.  My hope was that by hosting either the MySQL server or the Rails project directory on the RAM disk, the test execution would be significantly increased.  If this were the case, I would feel (more) compelled to buy a &lt;a href="http://www.apple.com/macbookair/"&gt;certain new device with a solid-state disk drive&lt;/a&gt;.  Unfortunately, while I now have some slick scripts to bring up a RAM disk with either my Rails project or MySQL running on it, the improvements were on the order of 10 seconds over a 10 minute test run (the load time of Rails).  Thus, it is clear that these tests are CPU bound, not disk IO bound, and a SSD wouldn&amp;#8217;t help.&lt;/p&gt;




&lt;p&gt;If you have an SSD, can you corroborate this?&lt;/p&gt;




&lt;p&gt;&lt;span style="color: #bbb"&gt;(Tests were performed on a brand new Quad-Core Intel Core i7 iMac.)&lt;/span&gt;&lt;/p&gt;

&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/Dnt0JGMFRlY" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/10/26/rspec-is-cpu-bound/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Revolutionary]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/jwkjr7m5vug/" />
    <updated>2010-08-22T18:03:00-07:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/08/22/revolutionary</id>
    <content type="html">&lt;p&gt;Sometime in the late &amp;#8217;80s I convinced my parents to get an external hard drive for our Mac Plus to augment our extravagant two floppy disk drives.  We got a 70MB drive for about $400, &lt;a href="http://www.wolframalpha.com/input/?i=%24400+(1998+US+dollars)"&gt;which is about $500 in today&amp;#8217;s dollars&lt;/a&gt;.  Now you can get &lt;a href="http://slickdeals.net/forums/showthread.php?t=2190461"&gt;2TB for $100&lt;/a&gt;, or 10TB for $500.  This is an increase of about 14 million percent in about 20 years!  Talk about technological change!  I have no doubt that this growth - that storage is now basically free - can enable incredible new technologies.  What can we do with this?&lt;/p&gt;

&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/jwkjr7m5vug" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/08/22/revolutionary/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Multiple profiles in Chrome on Ubuntu]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/PaOnorEAvNM/" />
    <updated>2010-03-02T18:03:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/03/02/multiple-profiles-in-chrome</id>
    <content type="html">&lt;p&gt;If you&amp;#8217;re running Chrome on Ubuntu (likely elsewhere too), you can run multiple copies of Chrome with fully different profiles by launching it with the &lt;code&gt;--user-data-dir&lt;/code&gt; option.  For example, I run two copies of Chrome like so:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/opt/google/chrome/google-chrome --user-data-dir=~/.config/google-chrome-work &amp;amp;amp;
/opt/google/chrome/google-chrome --user-data-dir=~/.config/google-chrome-personal &amp;amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/PaOnorEAvNM" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/03/02/multiple-profiles-in-chrome/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Easy web scraping: Get the title of any URL with YQL]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/P4Ml_oXay3Y/" />
    <updated>2010-03-02T18:03:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/03/02/get-the-title-of-any-url-with-yql</id>
    <content type="html">&lt;p&gt;This snippet demonstrates how to get the title from any webpage using a simple YQL query and jQuery.  The title is fetched from &lt;code&gt;url&lt;/code&gt; and is placed in &lt;code&gt;#page_title&lt;/code&gt;.

&lt;div&gt;&lt;script src='https://gist.github.com/320406.js?file='&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;pre&gt;&lt;code&gt;// query: select * from html where url=&amp;quot;http://some.url.com&amp;quot; and xpath='//title'
var yql_url = &amp;quot;http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22&amp;quot; + encodeURIComponent(url) + &amp;quot;%22%20and%0A%20%20%20%20%20%20xpath%3D'%2F%2Ftitle'&amp;amp;format=json&amp;amp;callback=?&amp;quot;;

$.getJSON(yql_url, function(json) {
  if (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.results &amp;amp;&amp;amp; json.query.results.title) {
    $('#page_title').html(json.query.results.title);
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;/noscript&gt;&lt;/div&gt;


This was very handy for a project I&amp;#8217;m working on.&lt;/p&gt;

&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/P4Ml_oXay3Y" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/03/02/get-the-title-of-any-url-with-yql/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Using TSort in Ruby for Topological Sorting of ActiveRecord Models]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/y4kISIsmsc8/" />
    <updated>2010-01-29T18:03:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2010/01/29/using-tsort-in-ruby-for-topological-sorting-of-activerec</id>
    <content type="html">&lt;p&gt;Recently, I was building a project list application in Rails and I needed to be sure that sub-projects showed up in the list somewhere below their parents.&lt;/p&gt;




&lt;p&gt;A &lt;a title="topolocal sorting" href="http://en.wikipedia.org/wiki/Topological_sorting"&gt;topological sort&lt;/a&gt; does just what I needed, and I was pleased to discover that Ruby ships with a &lt;a title="TSort" href="http://ruby-doc.org/stdlib/libdoc/tsort/rdoc/classes/TSort.html"&gt;TSort&lt;/a&gt; module.  Here is how I extended Array to allow sorting of Projects:&lt;/p&gt;




&lt;div&gt;&lt;script src='https://gist.github.com/290045.js?file='&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;pre&gt;&lt;code&gt;# Ruby's TSort requires that you implement 
# tsort_each_node and tsort_each_child. I 
# extend Array so that it knows how to 
# TSort instances of Project, which has a 
# child_project_id pointing to another 
# Project.

require 'tsort'

class Array
  include TSort

  alias tsort_each_node each

  def tsort_each_child(node, &amp;amp;block)
    if node.is_a?(Project)
      [node.child_project].each(&amp;amp;block) if node.child_project?
    else
      node.each(&amp;amp;block) if node
    end
  end
end

# Then I can topologically sort a user's projects like so:
sorted_projects = current_user.projects.tsort&lt;/code&gt;&lt;/pre&gt;&lt;/noscript&gt;&lt;/div&gt;


&lt;p&gt;&lt;span style="font-size: 0.8em"&gt;(Aside: I later post-processed the list to indent sub-projects and position them just below their parents.  This post-processing was made easier by the fact that all sub-projects were already somewhere below their parents in the list.)&lt;/span&gt;&lt;/p&gt;
&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/y4kISIsmsc8" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2010/01/29/using-tsort-in-ruby-for-topological-sorting-of-activerec/</feedburner:origLink></entry>
  
  <entry>
    <title type="html"><![CDATA[Replacement for script onload in IE]]></title>
    <link href="http://feedproxy.google.com/~r/andrew-makes-things/~3/Tt9aZ7f39rI/" />
    <updated>2008-11-23T18:03:00-08:00</updated>
    <id>http://blog.andrewcantino.com/blog/2008/11/23/replacement-for-script-onload-in-ie</id>
    <content type="html">&lt;p&gt;&lt;i&gt;This is an old post from my last blog.&lt;/i&gt;&lt;/p&gt;




&lt;p&gt;Firefox and Safari support an onload event for SCRIPT elements.  That is, you can dynamically add a new script to a page, set its onload event to fire a callback, and know when the script has been successfully loaded.  You would want to do this because when you include a bunch of new SCRIPT tags in a page, there are no guarantees in what order the browser will decide to evaluate them, thus making dependencies among the scripts difficult to resolve.  Using onload to chain the script additions is one solution to this, however, Internet Explorer doesn&amp;#8217;t seem to support onload in SCRIPT tags.&lt;/p&gt;




&lt;p&gt;&lt;i&gt;Update: Owen in the comments says: Thanks for your post, but I have since found a more efficient way to do this and thought I might share with you. As you said you can use an onload event in Firefox, but in IE you can use the onreadystatechange event. Works from at least IE6. Haven’t tested earlier.&lt;/i&gt;&lt;/p&gt;




&lt;p&gt;My solution:&lt;/p&gt;




&lt;figure class='code'&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;div class="highlight"&gt;&lt;table&gt;&lt;tr&gt;&lt;td class="gutter"&gt;&lt;pre class="line-numbers"&gt;&lt;span class='line-number'&gt;1&lt;/span&gt;
&lt;span class='line-number'&gt;2&lt;/span&gt;
&lt;span class='line-number'&gt;3&lt;/span&gt;
&lt;span class='line-number'&gt;4&lt;/span&gt;
&lt;span class='line-number'&gt;5&lt;/span&gt;
&lt;span class='line-number'&gt;6&lt;/span&gt;
&lt;span class='line-number'&gt;7&lt;/span&gt;
&lt;span class='line-number'&gt;8&lt;/span&gt;
&lt;span class='line-number'&gt;9&lt;/span&gt;
&lt;span class='line-number'&gt;10&lt;/span&gt;
&lt;span class='line-number'&gt;11&lt;/span&gt;
&lt;span class='line-number'&gt;12&lt;/span&gt;
&lt;span class='line-number'&gt;13&lt;/span&gt;
&lt;span class='line-number'&gt;14&lt;/span&gt;
&lt;span class='line-number'&gt;15&lt;/span&gt;
&lt;span class='line-number'&gt;16&lt;/span&gt;
&lt;span class='line-number'&gt;17&lt;/span&gt;
&lt;span class='line-number'&gt;18&lt;/span&gt;
&lt;span class='line-number'&gt;19&lt;/span&gt;
&lt;span class='line-number'&gt;20&lt;/span&gt;
&lt;span class='line-number'&gt;21&lt;/span&gt;
&lt;span class='line-number'&gt;22&lt;/span&gt;
&lt;span class='line-number'&gt;23&lt;/span&gt;
&lt;span class='line-number'&gt;24&lt;/span&gt;
&lt;span class='line-number'&gt;25&lt;/span&gt;
&lt;span class='line-number'&gt;26&lt;/span&gt;
&lt;span class='line-number'&gt;27&lt;/span&gt;
&lt;span class='line-number'&gt;28&lt;/span&gt;
&lt;span class='line-number'&gt;29&lt;/span&gt;
&lt;span class='line-number'&gt;30&lt;/span&gt;
&lt;span class='line-number'&gt;31&lt;/span&gt;
&lt;span class='line-number'&gt;32&lt;/span&gt;
&lt;span class='line-number'&gt;33&lt;/span&gt;
&lt;span class='line-number'&gt;34&lt;/span&gt;
&lt;span class='line-number'&gt;35&lt;/span&gt;
&lt;span class='line-number'&gt;36&lt;/span&gt;
&lt;span class='line-number'&gt;37&lt;/span&gt;
&lt;span class='line-number'&gt;38&lt;/span&gt;
&lt;span class='line-number'&gt;39&lt;/span&gt;
&lt;span class='line-number'&gt;40&lt;/span&gt;
&lt;span class='line-number'&gt;41&lt;/span&gt;
&lt;span class='line-number'&gt;42&lt;/span&gt;
&lt;span class='line-number'&gt;43&lt;/span&gt;
&lt;span class='line-number'&gt;44&lt;/span&gt;
&lt;span class='line-number'&gt;45&lt;/span&gt;
&lt;span class='line-number'&gt;46&lt;/span&gt;
&lt;span class='line-number'&gt;47&lt;/span&gt;
&lt;span class='line-number'&gt;48&lt;/span&gt;
&lt;span class='line-number'&gt;49&lt;/span&gt;
&lt;span class='line-number'&gt;50&lt;/span&gt;
&lt;span class='line-number'&gt;51&lt;/span&gt;
&lt;span class='line-number'&gt;52&lt;/span&gt;
&lt;span class='line-number'&gt;53&lt;/span&gt;
&lt;span class='line-number'&gt;54&lt;/span&gt;
&lt;span class='line-number'&gt;55&lt;/span&gt;
&lt;span class='line-number'&gt;56&lt;/span&gt;
&lt;span class='line-number'&gt;57&lt;/span&gt;
&lt;span class='line-number'&gt;58&lt;/span&gt;
&lt;span class='line-number'&gt;59&lt;/span&gt;
&lt;span class='line-number'&gt;60&lt;/span&gt;
&lt;span class='line-number'&gt;61&lt;/span&gt;
&lt;span class='line-number'&gt;62&lt;/span&gt;
&lt;span class='line-number'&gt;63&lt;/span&gt;
&lt;span class='line-number'&gt;64&lt;/span&gt;
&lt;span class='line-number'&gt;65&lt;/span&gt;
&lt;span class='line-number'&gt;66&lt;/span&gt;
&lt;span class='line-number'&gt;67&lt;/span&gt;
&lt;span class='line-number'&gt;68&lt;/span&gt;
&lt;span class='line-number'&gt;69&lt;/span&gt;
&lt;span class='line-number'&gt;70&lt;/span&gt;
&lt;span class='line-number'&gt;71&lt;/span&gt;
&lt;span class='line-number'&gt;72&lt;/span&gt;
&lt;span class='line-number'&gt;73&lt;/span&gt;
&lt;span class='line-number'&gt;74&lt;/span&gt;
&lt;span class='line-number'&gt;75&lt;/span&gt;
&lt;span class='line-number'&gt;76&lt;/span&gt;
&lt;span class='line-number'&gt;77&lt;/span&gt;
&lt;span class='line-number'&gt;78&lt;/span&gt;
&lt;span class='line-number'&gt;79&lt;/span&gt;
&lt;span class='line-number'&gt;80&lt;/span&gt;
&lt;span class='line-number'&gt;81&lt;/span&gt;
&lt;span class='line-number'&gt;82&lt;/span&gt;
&lt;span class='line-number'&gt;83&lt;/span&gt;
&lt;span class='line-number'&gt;84&lt;/span&gt;
&lt;span class='line-number'&gt;85&lt;/span&gt;
&lt;span class='line-number'&gt;86&lt;/span&gt;
&lt;span class='line-number'&gt;87&lt;/span&gt;
&lt;span class='line-number'&gt;88&lt;/span&gt;
&lt;span class='line-number'&gt;89&lt;/span&gt;
&lt;span class='line-number'&gt;90&lt;/span&gt;
&lt;span class='line-number'&gt;91&lt;/span&gt;
&lt;span class='line-number'&gt;92&lt;/span&gt;
&lt;span class='line-number'&gt;93&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;td class='code'&gt;&lt;pre&gt;&lt;code class='javascript'&gt;&lt;span class='line'&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;importJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;script&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/javascript&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;src&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;wait_for_script_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;head&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;importCSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rel&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;stylesheet&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;text/css&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;media&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;screen&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;href&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;wait_for_script_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;head&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nx"&gt;head&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;wait_for_script_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;typeof &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;look_for&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;undefined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;       &lt;span class="nx"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;       &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;importCSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;some_stylesheet.css&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="nx"&gt;importJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;jquery-1.2.6.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;jQuery&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nx"&gt;importJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;some_script_that_uses_jquery.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;     &lt;span class="nx"&gt;importJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;another_one.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SomeClassOrVariableSetByTheScript&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;       &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;         &lt;span class="nx"&gt;importJS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a_script_that_uses_that_class_or_variable.js&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;       &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt;   &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;span class='line'&gt;
&lt;/span&gt;&lt;span class='line'&gt; &lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;&lt;/figure&gt;

&lt;img src="http://feeds.feedburner.com/~r/andrew-makes-things/~4/Tt9aZ7f39rI" height="1" width="1"/&gt;</content>
  <feedburner:origLink>http://blog.andrewcantino.com/blog/2008/11/23/replacement-for-script-onload-in-ie/</feedburner:origLink></entry>
  
</feed>

