<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Aiur]]></title>
  <link href="http://blog.yxwang.me/atom.xml" rel="self"/>
  <link href="http://blog.yxwang.me/"/>
  <updated>2012-03-28T18:10:43+08:00</updated>
  <id>http://blog.yxwang.me/</id>
  <author>
    <name><![CDATA[zellux]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[用 Git 管理命令行配置]]></title>
    <link href="http://blog.yxwang.me/2012/03/managing-dotfiles-with-git/"/>
    <updated>2012-03-28T16:28:00+08:00</updated>
    <id>http://blog.yxwang.me/2012/03/managing-dotfiles-with-git</id>
    <content type="html"><![CDATA[<p>以前部署新机器时都要把一堆配置文件 scp 过去，今天折腾了下用 Git 统一管理这些配置文件。</p>

<p>做起来很简单，创建一个 dotfiles 目录，把所有要同步的配置文件都放到这个目录下，并重命名去掉文件名开头的点，以免被 Git 忽略。写了一个脚本链接这些配置文件到 HOME 目录：</p>

<figure class='code'><figcaption><span>link-files.rb</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="c1">#!/usr/bin/env ruby</span>
</span><span class='line'>
</span><span class='line'><span class="n">safe_mode</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">include?</span> <span class="s1">&#39;--safe&#39;</span>
</span><span class='line'>
</span><span class='line'><span class="n">files</span> <span class="o">=</span> <span class="sx">%w(zshrc tmux.conf gitconfig vimrc emacs gitignore_global LS_COLORS)</span>
</span><span class='line'><span class="n">files</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">file</span><span class="o">|</span>
</span><span class='line'>  <span class="k">unless</span> <span class="n">safe_mode</span> <span class="ow">and</span> <span class="no">File</span><span class="o">.</span><span class="n">exists?</span><span class="p">(</span><span class="s2">&quot;</span><span class="si">#{</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;HOME&#39;</span><span class="o">]</span><span class="si">}</span><span class="s2">/.</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2">&quot;</span><span class="p">)</span>
</span><span class='line'>    <span class="sx">%x(ln -s -i -v $PWD/</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="sx"> ~/.</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="sx">)</span>
</span><span class='line'>    <span class="nb">puts</span> <span class="s2">&quot;.</span><span class="si">#{</span><span class="n">file</span><span class="si">}</span><span class="s2"> linked&quot;</span> <span class="k">if</span> <span class="n">safe_mode</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>为了方便同步脚本到 GitHub，我在 .zshrc 中定义了两个命令用来上传、下载最近的脚本配置：</p>

<figure class='code'><figcaption><span>.zshrc</span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nb">alias </span>pull-dotfiles<span class="o">=</span><span class="s1">&#39;pushd $HOME/dotfiles &amp;&amp; git pull origin master &amp;&amp; ./link-files.rb --safe; popd&#39;</span>
</span><span class='line'><span class="nb">alias </span>push-dotfiles<span class="o">=</span><span class="s1">&#39;pushd $HOME/dotfiles &amp;&amp; git add -A &amp;&amp; git commit -m &quot;Update&quot; &amp;&amp; git push origin master; popd&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>pull-dotfiles</code> 可以获取最新的配置，<code>push-dotfiles</code> 则是提交最近的改动到 Git 服务器。我的配置文件在 <a href="https://github.com/zellux/dotfiles">https://github.com/zellux/dotfiles</a>，希望对大家有用。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[利用 ETag 优化 Rails 应用]]></title>
    <link href="http://blog.yxwang.me/2012/02/etags-in-rails/"/>
    <updated>2012-02-18T01:02:00+08:00</updated>
    <id>http://blog.yxwang.me/2012/02/etags-in-rails</id>
    <content type="html"><![CDATA[<p>ETag 是 HTTP 协议的一部分，可以用来检测客户端的缓存是否仍然有效。不少网站都实现了对 ETag 的支持，在 HTTP 响应头中加入当前传送内容的 ETag。以 heroku.com 为例：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -I www.heroku.com
</span><span class='line'>HTTP/1.1 200 OK
</span><span class='line'>Server: nginx
</span><span class='line'>Date: Fri, 17 Feb 2012 17:36:44 GMT
</span><span class='line'>Content-Type: text/html; <span class="nv">charset</span><span class="o">=</span>utf-8
</span><span class='line'>Connection: keep-alive
</span><span class='line'>Etag: <span class="s2">&quot;f74bb78aa48d36e6a0b2072a131b20b9&quot;</span>
</span><span class='line'>Cache-Control: public, max-age<span class="o">=</span>300
</span><span class='line'>Content-Length: 15481
</span></code></pre></td></tr></table></div></figure>


<p>可以看到响应头中给出了请求内容的 ETag 为 f74&#8230;b9。接下来我们再次向服务器发起请求，并通过 If-None-Match 字段告之服务器我们已经获得过一份 ETag 为 f74&#8230;b9 的内容。服务器收到请求并生成页面后，如果发现页面的 ETag 不变，就会返回 304，声明缓存有效：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>curl -I -H <span class="s1">&#39;If-None-Match: &quot;f74bb78aa48d36e6a0b2072a131b20b9&quot;&#39;</span> www.heroku.com
</span><span class='line'>HTTP/1.1 304 Not Modified
</span><span class='line'>Server: nginx
</span><span class='line'>Date: Fri, 17 Feb 2012 17:37:03 GMT
</span><span class='line'>Connection: keep-alive
</span><span class='line'>Last-Modified: Fri, 17 Feb 2012 17:34:00 GMT
</span><span class='line'>Cache-Control: public, max-age<span class="o">=</span>300
</span><span class='line'>ETag: <span class="s2">&quot;f74bb78aa48d36e6a0b2072a131b20b9&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>浏览器在访问网页时，记录下上一次服务器返回的 ETag，在下一次访问该网页时发送这个 ETag，这样就能有效的利用本地缓存，减少不必要的网络传输了。</p>

<p>Nginx 和 Apache 都很好的支持了 ETag 功能，它们会为静态资源计算 Hash，并将它作为 ETag 返回给客户端。 而对于 CPU 负载较高的应用，还有一个可以优化的空间。Web 服务器在处理客户端请求时，通常的过程是从数据库读取所需数据，交给模版引擎生成 HTML，计算该页面的 ETag，再回复客户端。事实上所需的数据读取完成后，就足以判断缓存是否有效了。</p>

<p>Rails 早在版本 2.2 就加入了 stale? 方法方便开放人员自定义 ETag。以一个博客文章页面为例，页面内容由文章和评论内容生成，如果这两份内容都没有变化，就可以认为客户端缓存依然有效了：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">def</span> <span class="nf">show</span>
</span><span class='line'>  <span class="vi">@article</span> <span class="o">=</span> <span class="no">Article</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span><span class="p">)</span>
</span><span class='line'>  <span class="vi">@comments</span> <span class="o">=</span> <span class="vi">@article</span><span class="o">.</span><span class="n">comments</span><span class="o">.</span><span class="n">all</span>
</span><span class='line'>  <span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="ss">:etag</span> <span class="o">=&gt;</span> <span class="o">[</span><span class="vi">@article</span><span class="p">,</span> <span class="vi">@comments</span><span class="o">]</span><span class="p">)</span>
</span><span class='line'>    <span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="nb">format</span><span class="o">|</span>
</span><span class='line'>      <span class="c1"># ...</span>
</span><span class='line'>    <span class="k">end</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>除此之外，还可以通过 Last Modified 字段控制缓存有效性，Rails 也同样提供了很方便的支持。但是ETag 方法控制缓存更为精确，而且在服务器时间变化时用 Last Modified 容易出错，因此 ETag 往往是更被推荐的方案。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[转换 Visio 图片为 EPS 格式]]></title>
    <link href="http://blog.yxwang.me/2012/01/visio-export-to-eps/"/>
    <updated>2012-01-02T20:10:00+08:00</updated>
    <id>http://blog.yxwang.me/2012/01/visio-export-to-eps</id>
    <content type="html"><![CDATA[<p>Windows 7 及 Visio 2010 下验证可行：</p>

<ul>
<li>添加本地打印机，类型选择 Generic -> MS Publisher Color Printer</li>
<li>在 Visio 中打印，选择新添加的打印机，选中 Print to file，点 Properties -> Advanced</li>
<li>Document Options -> PostScript Options -> PostScript Output Option 中，选择 Encapsulated PostScript (EPS)</li>
<li>保存时别忘了加后缀名 .eps</li>
</ul>


<p>这样输出的 EPS 文件的边框是整个 A4 页面，需要用 epstool 把它们周围的白边框去掉：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>epstool --copy --bbox figure.eps figure-fixed.eps</span></code></pre></td></tr></table></div></figure>


<p>epstool 在常见的几个软件源中应该都能找到。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[一些 ZvT 的心得]]></title>
    <link href="http://blog.yxwang.me/2011/12/zvt-notes/"/>
    <updated>2011-12-05T13:38:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/12/zvt-notes</id>
    <content type="html"><![CDATA[<p>一直在国服大师组混，最让我头疼，同时研究的也最多得对战就是 ZvT 了。这里写一点自己关于 ZvT 的心得，欢迎探讨。</p>

<h2>前期</h2>

<ol>
<li><p>探路农民不能少，我一般在 14d 的时候拉个农民出去探路。探到对方位置后农民别急着走，要在对方路口停留一会儿，注意对手的第一个枪兵有没有跑出来，以便做好防 rush 的准备。同时这也避免了对方农民在你基地旁造一个欺骗性的地堡时，拉过多的农民下来防守。</p></li>
<li><p>分矿处女王造出来后第一件事是铺菌毯而不是补虫卵。第三个女王也一定要出，保证菌毯铺开的速度。</p></li>
<li><p>防火车的话，我现在还是倾向于用蟑螂。用蟑螂的另一个好处在于配合小狗应付对手第一波坦克+枪兵的压制比较有效，第一波出来的时候自爆速度往往还没有好，在没有菌毯的地方很难起效果。</p></li>
<li><p>T 开局还有几个变种，如果看到对方出了火车，数量不多，而且其他地面部队也不多的话，还是老老实实补个 bv 造防空吧。</p></li>
</ol>


<h2>中期</h2>

<ol>
<li><p>还是关于侦查。对方二矿开始运作后，小狗要时不时的看看对方家门口。一要注意对手兵力组合，二要注意攻防。如果枪兵不多也没升攻防，对手就有可能用机械化部队了。</p></li>
<li><p>如果对手是标准的枪兵+坦克的组合，我倾向于防下对手第一波部队或者自己飞龙出来后再开三矿。不要因为对手开矿早，就以为自己也能随意补农民发展经济了。现在 T 第一波的 timing 都抓得蛮准，三矿农民补早了很有可能第一波就被推掉了。</p></li>
<li><p>相反，打机械化组合就要利用对方部队成形前的真空期尽早开矿，这也是前面强调小狗侦查的原因。</p></li>
<li><p>开三矿后记得在主矿分矿上都码上几个地堡，这点可以好好像雀茶学学。T 空投你的目的不单单是骚扰经济，而是让自己的主力部队能舒舒服服的推到合适的位置。防下对手一船空投后发现对手已经在咽喉位置架好坦克，摆好枪兵阵形了，这时候就很难打下来。</p></li>
<li><p>对方坦克阵慢慢推进的时候切记一定要耐心，对手总能出现疏漏的。同时飞龙记得吃掉落单的补给部队。</p></li>
<li><p>小狗+自爆和对方枪兵+坦克打正面的时候，能包最好包，不能包也要记得拉一队小狗拦住枪兵，干扰走位。</p></li>
<li><p>怎么打机械化。发现得早的话我一般就做好龙狗换家的准备了，成功率也不低。但是发现晚的话，只能硬着头皮打正面了。现在 GSL 上比较常见是在对手刚出家门，坦克还没架起的时候，用蟑螂吸引火力，同时大量的自爆上去换雷神。我试了几次效果不是很好，可能是时机没选择好的缘故吧。</p></li>
<li><p>飞龙一定要保存好。攒多了威慑力非常大，能很有效的牵制对手的部队。</p></li>
<li><p>天梯上有时还能看到另一个比较奇葩的战术：爆维京。听起来很不靠谱的战术，但是实战中经常能把人打懵（Z 人口补不上，对方维京成型后地面骚扰能力也很强）。当然这种战术应对起来也不难，多预留点人口，每个分矿都码几个堡，准备一两队小狗，接着出飞龙就好了。类似的战术还有火车女妖流，都是能打得你很不舒服，但是一旦放下来就没啥威力的战术。</p></li>
</ol>


<h2>后期</h2>

<ol>
<li><p>我觉得 ZvT 后期的关键在于防空投，因为自爆+狗+感染+母巢王虫的组合基本不怎么怕 T 打正面。但是三攻三防的枪兵拆分矿的能力很强，对应方法，也就只有放好领主侦查，同时在分矿补更多的堡了。</p></li>
<li><p>出母巢王虫后记得拉上所有的女王，加血效果会让对手很无语。</p></li>
</ol>


<h2>其他补充</h2>

<p>一个女王加虫卵的技巧。星际2里有一个切换主基地的快捷键，默认是<code>Backspace</code>，我把它设置成了<code>Tab</code>上边那个键。把所有女王编队后，按住<code>Shift</code>，点切换键，按<code>v</code>后再点一个基地，再按切换键，再<code>v</code>一个基地，这样一轮循环下来可以给所有基地注上虫卵。</p>

<p>不过这么做有一个问题就是女王数量比基地数少的时候，会出现女王到处跑的现象。我现在用的方法是不按<code>Shift</code>，点切换键快速切到基地视角，如果这个基地旁有女王就控制它注卵。这样操作上麻烦了一点，而且注卵时间上有个判断的延迟，但是灵活性高了不少。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[转用 octopress 了]]></title>
    <link href="http://blog.yxwang.me/2011/11/migrated-to-octopress/"/>
    <updated>2011-11-29T11:13:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/11/migrated-to-octopress</id>
    <content type="html"><![CDATA[<p><a href="https://github.com/mojombo/jekyll">jekyll</a> 是一个静态博客生成工具，可配置性很强。但是它的配置对于初学者不是很友好，没有现成的模版，需要自己从头搭一个。<a href="http://octopress.org/">octopress</a> 大大简化了这一配置过程，在 jekyll 的基础上提供了一个默认主题，以及一些常用的插件。</p>

<h3>Why</h3>

<p>在 github 上捣鼓了一阵子 octopress 后，决定把原来的 wordpress 博客的数据转移到这个 octopress 博客上了。相对于 wordpress，octopress 的优点在于：</p>

<ul>
<li><p>支持 <a href="http://daringfireball.net/projects/markdown/">Markdown</a> 语法。Markdown 是 github、stackoverflow 上的默认标记语言，写笔记我也一直用这个。Mac 平台上有不少好用的 Markdown 编辑器，例如收费的 <a href="http://bywordapp.com/">Byword</a>，免费的 <a href="http://mouapp.com/">Mou</a>，这些工具都增加了写日志时的愉悦感。</p></li>
<li><p>静态。对主页空间没有要求，甚至放到 github pages 上都可以。静态页面如果要加评论，可以考虑 <a href="http://www.disqus.com/">disqus</a> 等第三方 JS 工具。</p></li>
<li><p>对内嵌代码支持很好。内置了 <a href="http://pygments.org/">pygments</a> ，这里有一份支持语言的<a href="http://pygments.org/languages/">列表</a>。值得一提的是 octopress 还支持内嵌 Gist。</p></li>
<li><p>日志文件都在本地，而且是纯文本，管理很方便（可以用 git），也不用担心租用的服务器数据丢失等问题。</p></li>
<li><p><code>rake new_post; rake gen_deploy</code> 这样写博客很过瘾 :)</p></li>
</ul>


<h2>How</h2>

<p>关于 wordpress 到 octopress 的数据转移，本文结尾的两篇参考文章已经说得很详细了，这里再补充几点：</p>

<ul>
<li><p>编码：jekyll 的 wordpressdotcom.rb 用了 yaml 库生成博客文章的 meta 信息，碰到中文标题会出现乱码，换用 <a href="https://github.com/rkj/ya2yaml">ya2yaml</a> 后问题解决。</p></li>
<li><p>博客图片：把原来的 wp-content 目录复制过来，再统一改下路径即可。</p></li>
<li><p>文章格式：wordpress 导出的文章内容格式比较特殊，不是标准的 HTML，因为它的换行都是有意义的，考虑到这点我就把文章保存成 .markdown 后缀了，效果也不错。Vito 的博客上还介绍了 <a href="https://github.com/cousine/downmark_it">downmark_it</a> 这个把 HTML 转成 Markdown 的工具。</p></li>
<li><p>评论：用了 disqus，它还支持导入 wordpress 上过去的评论。</p></li>
<li><p>代码高亮：之前博客用的是一个基于 JS 的 <a href="http://alexgorbatchev.com/SyntaxHighlighter/">SyntexHighlighter</a>，octopress 自带了 pygments 作为语法高亮工具，两者高亮标记不一样，需要用 nokogiri 转换下。</p></li>
<li><p>RSS：虽然 octopress 自带了生成 Atom 的插件，但是只能生成一个，而之前博客的 /feed/ 和 /rss.xml 都有人订阅，所以得在 nginx 配置里加了几条重写规则保证这些 URL 都有效。</p></li>
<li><p>主题：octopress 支持用 <a href="http://sass-lang.com/">SCSS</a> 自定义主题。现在这个用的主题还是默认的，改天再考虑要不要把原来的主题也移植过来。</p></li>
<li><p>写作：建议在 Rakefile 的 new_post 方法结尾启动 Markdown 编辑器打开新生成的文件，这样就免去手动查找的麻烦了。</p></li>
</ul>


<p>这个是<a href="https://gist.github.com/1403202">修改后的 wordpressdotcom.rb</a>，根据我的博客的情况加了一些特殊情况的处理，有同样需求的朋友可以参考下。</p>

<p>参考：</p>

<ul>
<li><a href="https://github.com/mojombo/jekyll/wiki/Blog-Migrations">jekyll 的 wiki</a></li>
<li><a href="http://vitobotta.com/how-to-migrate-from-wordpress-to-jekyll/">Vito 的博客日志</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ActiveRecord 的一些细节]]></title>
    <link href="http://blog.yxwang.me/2011/11/notes-on-active-record/"/>
    <updated>2011-11-24T15:04:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/11/notes-on-active-record</id>
    <content type="html"><![CDATA[<h3>对象属性</h3>

<p>ActiveRecord 对象在数据库中的属性并不是以实体变量的方式保存的，如果要为一个属性设置默认值的话，</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">Item</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span><span class='line'>  <span class="k">def</span> <span class="nf">category</span>
</span><span class='line'>    <span class="vi">@category</span> <span class="o">||</span> <span class="s1">&#39;n/a&#39;</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>这样的实现是不可行的。读取和修改这些属性时应该使用 read_attribute 和 write_attribute：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">Item</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
</span><span class='line'>  <span class="k">def</span> <span class="nf">category</span>
</span><span class='line'>    <span class="n">read_attribute</span><span class="p">(</span><span class="ss">:category</span><span class="p">)</span> <span class="o">||</span> <span class="s1">&#39;n/a&#39;</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<h3>Hash 和相等性</h3>

<p>ActiveRecord 的 hash 值是根据主键的值计算出来的，这就意味着未保存对象的 hash 值是不可靠的。同样两个 model 对象的相等比较（即==操作符）也是基于主键的，所以两个 model 对象即使它们的其他属性不一样，仍有可能被当作相等。</p>

<h3>查找</h3>

<p>find_by_attribute 方法后面加个 ! 号，即使用 find_by_attribute!，就能在找不到对象的时候触发一个 RecordNotFound 异常，而不是返回 nil。</p>

<p>find_or_initialize_by 和 find_or_create_by 也是两个好用的方法，它们在找不到对象时分别使用 new 和 create 新建一个，并用查找的属性初始化新建的对象。</p>

<h3>手写 SQL</h3>

<p>不得不手写 SQL 同时又要防止注入攻击的一个比较简洁的写法是</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="no">Order</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="s2">&quot;name = :name and pay_type = :pay_type&quot;</span><span class="p">,</span> <span class="n">params</span><span class="o">[</span><span class="ss">:order</span><span class="o">]</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<h3>回调函数</h3>

<p>出于性能考虑，after_find 和 after_initialize 只能通过函数声明的方式定义，即不能用类似 <code>before_validation :normalize_fields</code> 这样的形式。</p>

<p>参考</p>

<ul>
<li><a href="http://www.amazon.com/Rails-Way-Addison-Wesley-Professional-Ruby/dp/0321601661/ref=sr_1_1?ie=UTF8&amp;qid=1322123636&amp;sr=8-1">Rails 3 Way</a></li>
<li><a href="http://www.amazon.com/Agile-Development-Rails-Pragmatic-Programmers/dp/1934356549/ref=sr_1_1?ie=UTF8&amp;qid=1322123646&amp;sr=8-1">Agile Web Development with Rails</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[REST 服务的方法]]></title>
    <link href="http://blog.yxwang.me/2011/11/methods-in-restful-applications/"/>
    <updated>2011-11-20T21:12:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/11/methods-in-restful-applications</id>
    <content type="html"><![CDATA[<p>HEAD 方法和 GET 方法比较像，但是它不返回对象的实际表示，只返回一个 HTTP 头。HEAD 可以用来查看对象修改时间、大小等信息，Amazon S3 的客户端就用它来读取文件元信息。</p>

<p>用 PUT 和 POST 创建对象时的一个区别在于，使用前者时客户端知道被创建对象的 URL（例如 <code>/items/3</code>），而后者则不需要客户端了解（例如 <code>/items/new</code>）。</p>

<p>OPTIONS 用来查看客户端对某个资源有那些可用的操作。</p>

<p>正确的设计应当保证：</p>

<ul>
<li>GET 和 HEAD 是安全的，即不会修改任何对象状态。多次调用它们的结果应当和只调用一次甚至不调用一样。</li>
<li>GET、HEAD、PUT 和 DELETE 方法是幂等（idempotent）的。多次调用它们的结果应当和只调用一次一样。</li>
</ul>


<p>这两点保证了在一个不可靠的网络中，客户端仍能进行有效的操作。</p>

<p>参考：<a href="http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260/ref=sr_1_1?ie=UTF8&amp;qid=1321795517&amp;sr=8-1">Restful Web Services</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[render 方法的可选参数]]></title>
    <link href="http://blog.yxwang.me/2011/11/options-for-render/"/>
    <updated>2011-11-20T19:42:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/11/options-for-render</id>
    <content type="html"><![CDATA[<p><code>:content_type</code> 设置返回内容的 MIME 类型</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">render</span> <span class="ss">:file</span> <span class="o">=&gt;</span> <span class="n">filename</span><span class="p">,</span> <span class="ss">:content_type</span> <span class="o">=&gt;</span> <span class="s1">&#39;application/rss&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>:layout</code> 指定 layout</p>

<p><code>:status</code> 指定返回的 HTTP 代码</p>

<p><code>:location</code> 指定 HTTP 头中的 Location 字段</p>

<p>Rails 生成的 controller 代码中，<code>create.json</code> 方法在生成对象后会将 Location 设置为新生成对象的 json 地址：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="nb">format</span><span class="o">|</span>
</span><span class='line'>  <span class="k">if</span> <span class="vi">@item</span><span class="o">.</span><span class="n">save</span>
</span><span class='line'>    <span class="nb">format</span><span class="o">.</span><span class="n">html</span> <span class="p">{</span> <span class="n">redirect_to</span> <span class="vi">@item</span><span class="p">,</span> <span class="n">notice</span><span class="p">:</span> <span class="s1">&#39;Item was successfully created.&#39;</span> <span class="p">}</span>
</span><span class='line'>    <span class="nb">format</span><span class="o">.</span><span class="n">json</span> <span class="p">{</span> <span class="n">render</span> <span class="n">json</span><span class="p">:</span> <span class="vi">@item</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="ss">:created</span><span class="p">,</span> <span class="n">location</span><span class="p">:</span> <span class="vi">@item</span> <span class="p">}</span>
</span><span class='line'>  <span class="k">else</span>
</span><span class='line'>    <span class="nb">format</span><span class="o">.</span><span class="n">html</span> <span class="p">{</span> <span class="n">render</span> <span class="n">action</span><span class="p">:</span> <span class="s2">&quot;new&quot;</span> <span class="p">}</span>
</span><span class='line'>    <span class="nb">format</span><span class="o">.</span><span class="n">json</span> <span class="p">{</span> <span class="n">render</span> <span class="n">json</span><span class="p">:</span> <span class="vi">@item</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="p">:</span> <span class="ss">:unprocessable_entity</span> <span class="p">}</span>
</span><span class='line'>  <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>参考：<a href="http://guides.rubyonrails.org/layouts_and_rendering.html#using-render">http://guides.rubyonrails.org/layouts_and_rendering.html#using-render</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[rspec 跳过指定测试]]></title>
    <link href="http://blog.yxwang.me/2011/11/skip-specs-by-tags/"/>
    <updated>2011-11-20T16:17:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/11/skip-specs-by-tags</id>
    <content type="html"><![CDATA[<p>有些测试比较耗时间，而且很少被修改，如果能在测试的时候跳过它们就能让 spec 快不少。</p>

<p>跳过测试的方法很简单，spec 的 describe 方法可以给对应的测试加上标签，例如</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">describe</span> <span class="no">SalesController</span><span class="p">,</span> <span class="ss">:slow</span> <span class="o">=&gt;</span> <span class="kp">true</span> <span class="k">do</span>
</span><span class='line'>    <span class="c1"># specs</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>接下来只要在 spec/spec_helper.rb 中声明跳过这个标签即可：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="no">RSpec</span><span class="o">.</span><span class="n">configure</span> <span class="k">do</span> <span class="o">|</span><span class="n">config</span><span class="o">|</span>
</span><span class='line'>  <span class="n">config</span><span class="o">.</span><span class="n">filter_run_excluding</span> <span class="ss">:slow</span> <span class="o">=&gt;</span> <span class="kp">true</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>


<p>与 filter_run_excluding 相反的是 filter_run，指定会被运行的标签，不包含在这个列表中的测试将被忽略。</p>

<p>参考：<a href="http://www.dixis.com/?p=283">http://www.dixis.com/?p=283</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[一个简单有效的 hash 算法]]></title>
    <link href="http://blog.yxwang.me/2011/09/recipe-for-hashing/"/>
    <updated>2011-09-17T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/09/recipe-for-hashing</id>
    <content type="html"><![CDATA[<p>最近要给某个类写一个 hash 方法，这个类包括一些整型和字符串属性，需要把它们都放到 hash 中。担心自己想出来的 hash 算法会造成比较严重的冲突，网上搜了一下，发现 <a href="http://www.sixwhits.com/documentation/SuggsDocs/Java/Effective%20Java%20-%20Programming%20Language%20Guide.pdf">Effective Java</a> 中已经介绍过一种简单有效的算法了：</p>

<ol>
<li>将任一非零常数赋值给 result</li>
    <li>找到该类中所有需要包含在 hash 中的属性，并根据它们的类型进行计算 c</li>
<ol>
<li>对于布尔类型，将它们转换为 0/1</li>
    <li>对于 byte, char, short, 和 int 类型，将它们转换为 int</li>
    <li>对于长整型 long，计算高位和低位的异或结果 (int) (f ^ (f >>> 32))</li>
    <li>对于 float 类型，采用它的二进制表示，在 Java 中为 Float.floatToIntBits，Ruby 中我估计用 Float.hash 也可以</li>
    <li>对于 double 类型，调用 Double.doubleToIntBits 后再次用前面的方法处理得到的 long 类型</li>
    <li>对于数组，利用方法 3 合并计算结果</li>
    <li>对于其它的对象，递归调用该对象的 hash 方法</li>
</ol>
<li>将计算结果合并到 result 变量：result = 37 * result + c</li>
    <li>返回 result</li>
</ol>


<p>另外这个博客的域名已经改成了 <a href="http://blog.yxwang.me">blog.yxwang.me</a>，原来的域名 <a href="http://techblog.iamzellux.com">techblog.iamzellux.com</a> 仍然可用，但会重定向到新的域名。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[让 Emacs 支持 Lion 的全屏模式]]></title>
    <link href="http://blog.yxwang.me/2011/07/full-screen-emacs-in-lion/"/>
    <updated>2011-07-28T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/07/full-screen-emacs-in-lion</id>
    <content type="html"><![CDATA[<p>前几天给我的 MacBook Pro 装上了 Lion，不过原来的 Emacs 并不支持在 Lion 下全屏运行。github 上搜了下发现已经有让 Emacs 支持全屏模式的<a title="补丁" href="https://gist.github.com/1096074">补丁</a>了，Homebrew 中这个补丁也已经<a title="被吸收" href="https://github.com/ColinHebert/homebrew/commit/cf59b2a1beb8bc4260ed1c63b3ceb1d50914f89e">被吸收</a>。</p>

<p>直接用 <code>brew install emacs --cocoa --srgb</code> 似乎会碰到编译错误：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='text'><span class='line'>Finding pointers to doc strings...
</span><span class='line'>Finding pointers to doc strings...done
</span><span class='line'>Dumping under the name emacs
</span><span class='line'>unexec: cannot write section __data
</span><span class='line'>--- List of All Regions ---
</span><span class='line'>   address     size prot maxp
</span><span class='line'>--- List of Regions to be Dumped ---
</span><span class='line'>   address     size prot maxp
</span><span class='line'>--- Header Information ---
</span><span class='line'>Magic = 0xfeedfacf
</span><span class='line'>CPUType = 16777223
</span><span class='line'>CPUSubType = -2147483645
</span><span class='line'>FileType = 0x2
</span><span class='line'>NCmds = 20
</span><span class='line'>SizeOfCmds = 3464
</span><span class='line'>Flags = 0x00200085
</span><span class='line'>Highest address of load commands in input file: 0x5dd000
</span><span class='line'>Lowest offset of all sections in __TEXT segment:   0x22f0
</span><span class='line'>--- List of Load Commands in Input File ---
</span></code></pre></td></tr></table></div></figure>


<p>github issues 上已经有人<a title="报告这个问题" href="https://github.com/mxcl/homebrew/issues/6495">报告这个问题</a>了，解决方法也很简单，运行 <code>brew edit emacs</code> 打开 emacs 的安装脚本，在 <code>def install</code> 的后面加上两行：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'>  <span class="k">def</span> <span class="nf">install</span>
</span><span class='line'>    <span class="n">ENV</span><span class="p">[</span><span class="s">&#39;CFLAGS&#39;</span><span class="p">]</span><span class="o">=</span><span class="s">&#39;-fno-pie -O2&#39;</span>
</span><span class='line'>    <span class="n">ENV</span><span class="p">[</span><span class="s">&#39;LDFLAGS&#39;</span><span class="p">]</span><span class="o">=</span><span class="s">&#39;-fno-pie&#39;</span>
</span><span class='line'>    <span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="s">&quot;--prefix=#{prefix}&quot;</span><span class="p">,</span>
</span><span class='line'>            <span class="s">&quot;--without-dbus&quot;</span><span class="p">,</span>
</span><span class='line'>            <span class="s">&quot;--enable-locallisppath=#{HOMEBREW_PREFIX}/share/emacs/site-lisp&quot;</span><span class="p">,</span>
</span><span class='line'>            <span class="s">&quot;--infodir=#{info}/emacs&quot;</span><span class="p">]</span>
</span><span class='line'>    <span class="c"># ...</span>
</span></code></pre></td></tr></table></div></figure>


<p>再运行一次 <code>brew install emacs</code>，就能在 /usr/local/Cellar/emacs/23.3 下找到支持全屏模式的 Emacs.app 了。<code>M-x ns-toggle-fullscreen</code> 可以在全屏/非全屏模式之间切换。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[利用 netgrowl 向 Windows / Mac OS X 发送消息]]></title>
    <link href="http://blog.yxwang.me/2011/03/send-notifications-with-netgrowl/"/>
    <updated>2011-03-02T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/03/send-notifications-with-netgrowl</id>
    <content type="html"><![CDATA[<p>我平时用的系统是 Windows 7 和 Mac OS X，实验室项目一般都是 ssh 远登到 Ubuntu 和 Linux 上开发的。有时碰到内核和虚拟机等项目编译比较耗时，编译开始后要时不时的看一下编译任务是否完成，或者有没有中途出错，这时候如果有个通知系统就比较方便了。</p>

<p>Google 了一把找到了 <a title="netgrowl" href="http://the.taoofmac.com/space/projects/netgrowl" target="_blank">netgrowl</a> 这个好东东，它是一个开源的 Python 模块，实现了 Growl 协议，可以向 Mac 或 Windows 上的 Growl 服务发送通知。使用也非常方便，先用 GrowlRegistrationPacket 函数注册一个应用，接着就可以用 GrowlNotificationPacket 发送通知了：</p>

<p><u>notify.py</u></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="c">#!/usr/bin/python</span>
</span><span class='line'>
</span><span class='line'><span class="kn">from</span> <span class="nn">netgrowl</span> <span class="kn">import</span> <span class="o">*</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">sys</span>
</span><span class='line'>
</span><span class='line'><span class="n">title</span> <span class="o">=</span> <span class="s">&quot;Notification from Ubuntu&quot;</span>
</span><span class='line'><span class="n">desc</span> <span class="o">=</span> <span class="s">&quot;&quot;</span>
</span><span class='line'><span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">:</span>
</span><span class='line'>    <span class="n">title</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
</span><span class='line'>    <span class="n">desc</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
</span><span class='line'>
</span><span class='line'><span class="n">addr</span> <span class="o">=</span> <span class="p">(</span><span class="s">&quot;10.131.251.101&quot;</span><span class="p">,</span> <span class="n">GROWL_UDP_PORT</span><span class="p">)</span>
</span><span class='line'><span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span><span class="n">SOCK_DGRAM</span><span class="p">)</span>
</span><span class='line'><span class="n">p</span> <span class="o">=</span> <span class="n">GrowlRegistrationPacket</span><span class="p">(</span><span class="n">application</span><span class="o">=</span><span class="s">&quot;Ubuntu&quot;</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">&quot;i&quot;</span><span class="p">)</span>
</span><span class='line'><span class="n">p</span><span class="o">.</span><span class="n">addNotification</span><span class="p">(</span><span class="s">&quot;Ubuntu Notifications&quot;</span><span class="p">,</span> <span class="n">enabled</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</span><span class='line'><span class="n">s</span><span class="o">.</span><span class="n">sendto</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">payload</span><span class="p">(),</span> <span class="n">addr</span><span class="p">)</span>
</span><span class='line'><span class="n">p</span> <span class="o">=</span> <span class="n">GrowlNotificationPacket</span><span class="p">(</span><span class="n">application</span><span class="o">=</span><span class="s">&quot;Ubuntu&quot;</span><span class="p">,</span>
</span><span class='line'>    <span class="n">notification</span><span class="o">=</span><span class="s">&quot;Ubuntu Notifications&quot;</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="n">title</span><span class="p">,</span>
</span><span class='line'>    <span class="n">description</span><span class="o">=</span><span class="n">desc</span><span class="p">,</span> <span class="n">priority</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
</span><span class='line'>    <span class="n">sticky</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">&quot;i&quot;</span><span class="p">)</span>
</span><span class='line'><span class="n">s</span><span class="o">.</span><span class="n">sendto</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">payload</span><span class="p">(),</span><span class="n">addr</span><span class="p">)</span>
</span><span class='line'><span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<p>这里的 addr 是接收方的地址，GrowlRegistrationPacket 和 GrowlNotificationPacket 中需要指定 Growl 远程服务的密码。</p>

<p>然后是一个简化 notify.py 调用的 shell 脚本：</p>

<p><u>growl.sh</u></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="c">#!/bin/bash</span>
</span><span class='line'>
</span><span class='line'><span class="nv">cmd</span><span class="o">=</span><span class="nv">$@</span>
</span><span class='line'><span class="nv">$cmd</span>
</span><span class='line'>python ~/bin/notify.py Done <span class="s2">&quot;$cmd under $PWD is finished&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>把 growl.sh 加入到 PATH 中，之后只要运行 <u>growl.sh make all</u> 就能运行 make all 命令 ，并且在执行完成后向 Growl 客户端发送消息了。如果安装了 <a title="BoxCar" href="http://boxcar.io" target="_blank">BoxCar</a>，还能把这条消息转发到 iPhone / iPad 上。</p>

<p>P.S. Growl for Windows 可以在<a title="这里" href="http://www.growlforwindows.com/" target="_blank">这里</a>找到。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[良性代码，恶意利用：浅谈 Return-Oriented 攻击（二）]]></title>
    <link href="http://blog.yxwang.me/2011/01/return-oriented-intro-2/"/>
    <updated>2011-01-16T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2011/01/return-oriented-intro-2</id>
    <content type="html"><![CDATA[<p>在上一篇文章中我们介绍了 <a href="http://techblog.iamzellux.com/2010/11/return-oriented-intro-1/">return-oriented 这种攻击手段</a>，它的强大之处在于攻击者不需要插入恶意代码，通过构造特殊的函数返回栈利用程序中原有的代码即可达到攻击者的目地。</p>

<p>北卡州立大学的学者们提出了一种防止 return-oriented 攻击的思路，思路很简单，一句话概括，就是去掉代码里所有的 ret 指令！</p>

<p>思路很简单，真正做起来还是很复杂的。x86 中的 ret 指令只有一个字节，即 0xc3。要去掉所有的 0xc3，不仅要修改原来代码中的 ret 指令，还要移除其他指令片段中的 0xc3（例如 movl $0xc3, %rax）。接下来我们来看看 EuroSys 10 上的这篇文章是怎么解决这些问题的。</p>

<p>首先是原来就作为 ret 指令用的 0xc3 代码。注意 return-oriented 之所以成功一大原因就是 <strong>ret 指令在返回时不会检查栈上的返回地址是否正确。</strong>要保证这一点，需要引入一个间接跳转层。传统的调用过程是调用者把返回地址压入栈上，然后被调用函数返回时从栈上得到返回地址并返回。现在我们加入一个新的跳转表，这张表里记录了所有的返回地址，而且它不在栈上，因此不能被攻击者修改。当调用者调用函数时，把返回地址在表中的序号压入栈上；函数返回时，从栈上读出地址序号，再查表得到实际地址，然后返回。通过引入这样一层额外的地址转换机制，攻击者就不能通过修改栈上返回地址让函数返回到任意地址了。</p>

<p>接下来我们要解决其他指令引入的 0xc3，这里面也分几类情况。首先是由于寄存器分配引起的。例如 movl %rax, %rbx 对应的机器码是 48 89 c3，这边就有个 0xc3。对于这一类代码，只需要在编译器做寄存器分配时把有可能产生 0xc3 的情况排除掉即可。</p>

<p>另一类是代码中直接使用了 0xc3 作为直接数。这种情况需要对代码进行适当的修补，以 cmp $0xc3, %ecx 为例，0xc3 这个直接数可以通过 0xc4 - 1 得到，于是这条指令可以被修改为：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='nasm'><span class='line'><span class="nf">mov</span> <span class="kc">$</span><span class="mh">0xc4</span><span class="p">,</span> <span class="o">%</span><span class="nv">reg</span>
</span><span class='line'><span class="nf">dec</span> <span class="o">%</span><span class="nv">reg</span>
</span><span class='line'><span class="nf">cmp</span> <span class="o">%</span><span class="nv">reg</span><span class="p">,</span> <span class="o">%</span><span class="nb">ecx</span>
</span></code></pre></td></tr></table></div></figure>


<p>到这里所有包含 0xc3 的代码都已经被修改成具有同等功能的不包含 0xc3 的版本了，也就彻底杜绝了 ret 指令被用来做 return-oriented 攻击的可能。对具体实现细节有兴趣的同学可以读一下<a href="http://www.csc.ncsu.edu/faculty/jiang/pubs/EUROSYS10.pdf" target="_blank">这篇论文</a>，作者借助 LLVM 生成了一个没有 0xc3 的 FreeBSD 内核。</p>

<p><span id="annotationID_1" class="annotation">如果一个程序没有 0xc3，是不是意味着 return-oriented 攻击也从根本上被阻止了呢？</span></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[良性代码，恶意利用：浅谈 Return-Oriented 攻击（一）]]></title>
    <link href="http://blog.yxwang.me/2010/11/return-oriented-intro-1/"/>
    <updated>2010-11-19T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/11/return-oriented-intro-1</id>
    <content type="html"><![CDATA[<p>众多的安全漏洞中，栈溢出（stack-based buffer overflows）算是非常常见的了。一方面因为程序员的疏忽，使用了 strcpy、sprintf 等不安全的函数，增加了栈溢出漏洞的可能。另一方面，因为栈上保存了函数的返回地址等信息，因此如果攻击者能任意覆盖栈上的数据，通常情况下就意味着他能修改程序的执行流程，从而造成更大的破坏。</p>

<p>对于栈溢出漏洞，传统的攻击方式是嵌入攻击代码，然后修改栈上的返回地址，使它指向攻击代码段，从而执行攻击者指定的代码。本科时候上过一门计算机系统基础，其中的<a href="http://techblog.iamzellux.com/2007/12/ics-lab-3/">某一个lab</a>就要求学生做这么一件事。</p>

<p>现在的安全技术已经能比较好的防范传统的栈溢出攻击了。常见的技术有这么几种：</p>

<p>随机空间，前面提到攻击者需要修改栈上返回地址，使它指向注入的代码起始地址。但如果用户栈的起始地址是随机分布的，甚至每次新建一个栈帧时的地址都有一定程度的随机波动，要获得准确的返回地址就很困难了。这个技术大大增加了代码嵌入攻击的难度，但是却没用从理论上杜绝成功的可能性。攻击者可以使用大量的空指令（nop），并在可以修改的区域重复添加攻击代码，以此增加攻击成功的几率。</p>

<p>W ^ X，把所有的可读可写页标记为不可执行，也就是说攻击者无法添加或修改可执行代码，这样包含了嵌入代码的页面就无法被攻击者调用执行了。Windows 的 DEP、 Linux 的 PaX 都利用了这一项技术。</p>

<p>此外还有一些诸如 StackGuard 等栈保护手段，不过由于它们对性能影响很大，实际中使用并不广泛。</p>

<p>这些手段使得在受保护的进程中利用栈溢出嵌入恶意代码并执行变得几乎不可能，然而这并不意味着栈溢出漏洞没有利用的价值了。聪明的黑客们想到了另外一种自定义程序行为的途径：利用程序或者动态库中原有的代码。这些代码虽然本身是良性的，但适当利用的话，同样可以产生恶意的效果。</p>

<p>最简单的手段就是著名的 return-to-libc 攻击。libc 中有一些函数可以用于执行其他的进程，例如 execve 和 system。这些函数很容易被攻击者利用，只要找到一个栈溢出漏洞，并适当的构造函数调用参数，并使栈上返回地址指向这些函数的起始地址，攻击者就能以这个程序的权限执行任意其他程序了！注意这里所有执行的代码都是合法的，所以前面提到的W<sup>X技术对此就无能为力了。</sup></p>

<p>return-to-libc 这种攻击方式也有一个局限，就是需要代码库中有 system 这样符合要求的函数，如果对于内核代码，或是检查调用来源的库，return-to-libc 就不那么给力了。于是另一种理论上更强大、也更难构造的攻击方式浮出水面，也就是标题的 return-oriented 攻击。</p>

<p>关于 return-oriented 攻击，我之前的<a href="http://techblog.iamzellux.com/2009/08/return-oriented-rookit/">一篇博文</a>已经介绍过这个概念了，这里再解释一下。</p>

<p>一般程序中都包含着大量的返回指令（ret），它们通常位于一个函数的结尾，或是中途返回的地方。而这些返回指令之前的一两条指令，成为了 return-oriented 攻击指令的来源。攻击者要做的就是把这些零零碎碎的指令拼接起来，拼成一段恶意的代码。这里的难点有两个地方，一是怎么找到符合要求的代码片段，二是找到之后怎么拼接。</p>

<p>先来看第一个问题，可用的代码片段虽然多，但是都是固定的。这就意味着原来的一条指令现在可能需要多条指令执行后得到相同的效果了。举例来说，要把一个寄存器赋值为 4 的话，可能没有现成的直接赋值的代码片段，需要一条赋值为 1 的指令，和三条寄存器加 1 的指令拼凑而成。这样通过拼凑，受限的指令可以完成一些基本的操作，再由这些基本的操作，组成一段有实际意义的攻击代码。这里涉及到不少编译相关的知识，具体细节就不赘述了。</p>

<p>关于第二个问题，因为前面找到的代码片段都是以 ret 指令结尾的，所以只要把栈上的返回地址改成片段1的起始地址，代码片段1执行之后就会通过 ret 指令返回，此时读取的返回地址还是在被攻击的栈上，所以攻击者只要把对应位置的值改成代码片段2的起始地址，就能紧接着执行代码片段2了，如此循环，只要栈够大，就可以把攻击片段跑完。</p>

<p>对于 Linux 内核、glibc 这些庞大的程序来说，ret 指令前面一两条指令组成的代码库非常巨大，基本上可以达到图灵完备的要求了，也就是说，只要栈够大，任何程序都能由这些代码片段表达出来。另外这里为什么强调“一两条指令”呢？当然四五条甚至十几条指令的复用也是可以的，只是这样会大大增加搜索空间，要通过这些可能的代码片段生成一个程序需要太多的时间了。</p>

<p>那么如何防范 return-oriented 攻击呢？之后的博文里，我会介绍一些和它相关的国外研究。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[软件修改 Caps Lock 状态]]></title>
    <link href="http://blog.yxwang.me/2010/10/change-caps-lock-state-by-software/"/>
    <updated>2010-10-19T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/10/change-caps-lock-state-by-software</id>
    <content type="html"><![CDATA[<p>我经常使用的几台电脑中的<a href="http://techblog.iamzellux.com/2009/05/caps-lock-to-ctrl/">Caps Lock键都被我改成了Ctrl键</a>，这样修改以后用起Emacs来就顺手多了。</p>

<p>最近在Windows上用VMware Remote Control远登虚拟机调试内核的时候，问题就出来了：可能是这个浏览器插件的bug，有时键盘的Caps Lock会被莫名打开。然后我的这个键盘键位又比较少，不想再让Caps Lock键替换另一个用得更少的按键了，于是想到了软件关闭的方法。</p>

<p>搜了下Stackoverflow找到个很好用的Python库<a href="http://www.rutherfurd.net/python/sendkeys/">SendKeys</a>，只要两行代码就能在Windows下模拟Caps Lock按键了：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="kn">import</span> <span class="nn">SendKeys</span>
</span><span class='line'><span class="n">SendKeys</span><span class="o">.</span><span class="n">SendKeys</span><span class="p">(</span><span class="s">&quot;{CAPSLOCK}&quot;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外在Linux要模拟按键，可以直接访问/dev/console：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="kn">import</span> <span class="nn">fcntl</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">os</span>
</span><span class='line'>
</span><span class='line'><span class="n">KDSETLED</span> <span class="o">=</span> <span class="mh">0x4B32</span>
</span><span class='line'>
</span><span class='line'><span class="n">console_fd</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s">&#39;/dev/console&#39;</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">O_NOCTTY</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="c"># Turn on caps lock</span>
</span><span class='line'><span class="n">fcntl</span><span class="o">.</span><span class="n">ioctl</span><span class="p">(</span><span class="n">console_fd</span><span class="p">,</span> <span class="n">KDSETLED</span><span class="p">,</span> <span class="mh">0x04</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="c"># Turn off caps lock</span>
</span><span class='line'><span class="n">fcntl</span><span class="o">.</span><span class="n">ioctl</span><span class="p">(</span><span class="n">console_fd</span><span class="p">,</span> <span class="n">KDSETLED</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p><a href="http://stackoverflow.com/questions/2171408/how-to-change-caps-lock-status-without-key-press">原问题地址</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[gcc 内联汇编中 %c 的用法]]></title>
    <link href="http://blog.yxwang.me/2010/08/gcc-operand-substitution/"/>
    <updated>2010-08-25T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/08/gcc-operand-substitution</id>
    <content type="html"><![CDATA[<p>这几天看KVM代码的时候看到里面有个内联汇编的语法很陌生（下面的代码截取了部分内联汇编片段）：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class='nasm'><span class='line'><span class="nf">asm</span> <span class="p">(</span>
</span><span class='line'>    <span class="err">&quot;</span><span class="nf">mov</span> <span class="o">%</span><span class="nv">c</span><span class="p">[</span><span class="nv">rax</span><span class="p">](</span><span class="o">%</span><span class="mi">3</span><span class="p">),</span> <span class="o">%%</span><span class="nv">rax</span> <span class="err">\</span><span class="nv">n</span><span class="err">\</span><span class="nv">t</span><span class="s">&quot;</span>
</span><span class='line'><span class="s">    &quot;</span><span class="nv">mov</span> <span class="o">%</span><span class="nv">c</span><span class="p">[</span><span class="nv">rbx</span><span class="p">](</span><span class="o">%</span><span class="mi">3</span><span class="p">),</span> <span class="o">%%</span><span class="nv">rbx</span> <span class="err">\</span><span class="nv">n</span><span class="err">\</span><span class="nv">t</span><span class="s">&quot;</span>
</span><span class='line'><span class="s">    &quot;</span><span class="nv">mov</span> <span class="o">%</span><span class="nv">c</span><span class="p">[</span><span class="nv">rdx</span><span class="p">](</span><span class="o">%</span><span class="mi">3</span><span class="p">),</span> <span class="o">%%</span><span class="nv">rdx</span> <span class="err">\</span><span class="nv">n</span><span class="err">\</span><span class="nv">t</span><span class="s">&quot;</span>
</span><span class='line'><span class="s">    &quot;</span><span class="nv">mov</span> <span class="o">%</span><span class="nv">c</span><span class="p">[</span><span class="nv">rsi</span><span class="p">](</span><span class="o">%</span><span class="mi">3</span><span class="p">),</span> <span class="o">%%</span><span class="nv">rsi</span> <span class="err">\</span><span class="nv">n</span><span class="err">\</span><span class="nv">t</span><span class="s">&quot;</span>
</span><span class='line'><span class="s">    &quot;</span><span class="nv">mov</span> <span class="o">%</span><span class="nv">c</span><span class="p">[</span><span class="nv">rdi</span><span class="p">](</span><span class="o">%</span><span class="mi">3</span><span class="p">),</span> <span class="o">%%</span><span class="nv">rdi</span> <span class="err">\</span><span class="nv">n</span><span class="err">\</span><span class="nv">t</span><span class="s">&quot;</span>
</span><span class='line'><span class="s">      : &quot;</span><span class="err">=</span><span class="nv">q</span><span class="s">&quot; (fail)</span>
</span><span class='line'><span class="s">      : &quot;</span><span class="nv">r</span><span class="s">&quot;(vcpu-&gt;launched), &quot;</span><span class="nv">d</span><span class="s">&quot;((unsigned long)HOST_RSP),</span>
</span><span class='line'><span class="s">    &quot;</span><span class="nv">c</span><span class="s">&quot;(vcpu),</span>
</span><span class='line'><span class="s">    [rax]&quot;</span><span class="nv">i</span><span class="s">&quot;(offsetof(struct kvm_vcpu, regs[VCPU_REGS_RAX])),</span>
</span><span class='line'><span class="s">    [rbx]&quot;</span><span class="nv">i</span><span class="s">&quot;(offsetof(struct kvm_vcpu, regs[VCPU_REGS_RBX])),</span>
</span><span class='line'><span class="s">    [rcx]&quot;</span><span class="nv">i</span><span class="s">&quot;(offsetof(struct kvm_vcpu, regs[VCPU_REGS_RCX]))</span>
</span><span class='line'><span class="s">      : &quot;</span><span class="nv">cc</span><span class="s">&quot;, &quot;</span><span class="nv">memory</span><span class="err">&quot;</span> <span class="p">)</span><span class="c1">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>stackoverflow上问了下才知道这是gcc的<a href="http://gcc.gnu.org/onlinedocs/gccint/Output-Template.html#Output-Template" target="_blank">operand substitution语法</a>，%c后面跟上常量名，就能在内联汇编中使用这个常量了。</p>

<p>以这段代码为例，vcpu是struct kvm_vcpu类型，<u>[rax]&#8221;i&#8221;(offsetof(struct kvm_vcpu, regs[VCPU_REGS_RAX])</u>这句话把vcpu->regs[VCPU_REGS_RAX]相对于vcpu的偏移赋值给了rax这一常量。接下来回到第一行<u>mov %c<a href="%3">rax</a>, %%eax</u>，%c[rax]等于前面得到的偏移量，加上vcpu并取值后就是vcpu->regs[VCPU_REGS_RAX]中保存的值了，这个指令会把这个值保存在%rax寄存器中，从而完成了vcpu的rax寄存器恢复工作。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[利用 HTTPS 代理访问 GitHub]]></title>
    <link href="http://blog.yxwang.me/2010/05/git-through-https-proxy/"/>
    <updated>2010-05-14T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/05/git-through-https-proxy</id>
    <content type="html"><![CDATA[<p>网上找了不少设置方法，终于翻出来一个可行的，和大家分享下。</p>

<ol>
<li><p>安装corkscrew，ArchLinux和Ubuntu的源里就有，也可以从<a href="http://www.agroman.net/corkscrew/" target="_blank">http://www.agroman.net/corkscrew/</a>下载源码编译一个。</p></li>
<li><p>修改~/.ssh/config</p></li>
</ol>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>Host gitproxy
</span><span class='line'>User git
</span><span class='line'>Hostname ssh.github.com
</span><span class='line'>Port 443
</span><span class='line'>ProxyCommand corkscrew proxy.example.com 3128 %h %p
</span><span class='line'>IdentityFile /home/username/.ssh/id_rsa
</span></code></pre></td></tr></table></div></figure>


<p>修改其中的proxy.example.com和3128为代理IP和端口，如果代理需要帐号密码，就在ProxyCommand这一行的最后加上密码文件，内容为<strong>用户名:密码</strong>。</p>

<p>IdentitiFile的参数是对应于GitHub中帐号的私钥地址。</p>

<ol>
<li>使用git@gitproxy作为新地址访问GitHub即可，例如要clone git@github.com:foo/bar.git，执行git clone git@gitproxy:foo/bar.git即可。</li>
</ol>


<p>原文地址：<a href="http://www.wetware.co.nz/blog/2010/03/cant-access-github-behind-proxy-or-firewall/" target="_blank">http://www.wetware.co.nz/blog/2010/03/cant-access-github-behind-proxy-or-firewall/</a></p>

<div id="_mcePaste" style="overflow: hidden; position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px;">Host gitproxy
User git
Hostname ssh.github.com
Port 443
ProxyCommand /usr/local/bin/corkscrew proxy.example.com 3128 %h %p
IdentityFile /home/jordan/.ssh/id_rsa</div>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[跨站脚本攻击和 BluePrint]]></title>
    <link href="http://blog.yxwang.me/2010/03/blueprint/"/>
    <updated>2010-03-03T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/03/blueprint</id>
    <content type="html"><![CDATA[<p>Blueprint: Robust prevention of cross-site scripting attacks for existing browsers</p>

<p>这篇论文提出了一种防范是跨站脚本攻击(XSS)的新的方法，发在IEEE S&amp;P 2009上，作者是UIUC的Mike Ter Louw。</p>

<p>所谓跨站脚本攻击，简单地说就是在网页中注入非法的脚本代码，从而达到攻击的效果。比较著名的例子有当年在MySpace上泛滥的<a href="http://en.wikipedia.org/wiki/Samy_(XSS)" target="_blank">Samy蠕虫</a>，通过特殊的脚本注入手段，每一位访问Samy主页的用户，他们的主页都会被修改加上一段Samy is my hero文字，并且他们的主页也会被植入攻击代码，从而把这段脚本扩散给更多的用户。</p>

<p>通常防范跨站脚本攻击的方式有两种。一种做在服务器端，为每一段用户上传的内容做检查，并剔除恶意代码。但这种方式很难保证能过滤掉所有的恶意字符串，一方面攻击方法防不甚防，有兴趣的朋友可以参考下<a href="http://ha.ckers.org/xss.html" target="_blank">XSS Cheat Sheet</a>，上面给出了很多一般人很难想到的攻击代码的组合方式。另一方面由于现在大多数论坛和博客都支持一些基本的文本修饰标签，所以简单的标签剔除或者重新编码都不可行。</p>

<p>另一种方法是做在浏览器端，但是由于浏览器无法区分某一段脚本到底是来源于不可信的用户还是可信的站点，所以这种方法实现起来也有很大的困难。</p>

<p>这里实现防范措施的一个难点在于，<strong>Web应用把生成HTML的返回给浏览器后，就不参与浏览器的HTML解析工作了</strong>。这样浏览器就不知道哪部分出现脚本是安全的，哪部分出现是不安全的。</p>

<p>BluePrint就着眼于这个点，提出了一种让Web应用“参与”HTML解析工作的设计。下面通过论文里面的一个例子，简单介绍下它的防范机制。</p>

<p>假如一位恶意的用户在一个博客上上传了这样一段含有恶意代码的留言：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;p&gt;</span>
</span><span class='line'>Here is a page you might find
</span><span class='line'><span class="nt">&lt;b</span> <span class="err">&quot;&quot;&quot;</span><span class="nt">&gt;&lt;script&gt;</span><span class="nx">doEvil</span><span class="p">(.</span> <span class="p">.</span> <span class="p">.)</span><span class="nt">&lt;/script&gt;</span>&quot;&gt;very<span class="nt">&lt;/b&gt;</span>
</span><span class='line'>interesting:
</span><span class='line'><span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">&quot; &amp;#14; javasc&amp;#x0A;ript:doEvil(. . .);&quot;</span><span class="nt">&gt;</span>
</span><span class='line'>Link<span class="nt">&lt;/a&gt;</span>
</span><span class='line'><span class="nt">&lt;/p&gt;&lt;p</span> <span class="na">style=</span><span class="s">&quot;nop:expres/*xss*/sion(doEvil(. . .))&quot;</span><span class="nt">&gt;</span>
</span><span class='line'>Respectfully,
</span><span class='line'>Eve
</span><span class='line'><span class="nt">&lt;/p&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以看到，这段代码里包含了很多可能引发脚本执行的代码，而要在服务器端把这些所有隐藏的攻击可能找出来是一件比较困难的事。那么BluePrint是怎么在不知道这段代码是否含有恶意代码的前提下处理的呢？</p>

<p>首先，这种由用户上传的不可信的字符串会先在服务器端被解析成一棵树，就像HTML在浏览器中被解析一样，这棵HTML解析树可以用一些简单的DOM API来生成，例如appendChild, createElement等。这些描述如何生成HTML解析树的方法会和数据值（URL、标签属性等）一起，通过特殊的编码（Base64）传递给浏览器。例如上面这段代码，最后在浏览器接收到的HTML中，会变成这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;code</span> <span class="na">style=</span><span class="s">&quot;display:none;&quot;</span> <span class="na">id=</span><span class="s">&quot;__bp1&quot;</span><span class="nt">&gt;</span>
</span><span class='line'>=Enk/sCkhlcmUgaXMgYSBwYWdlIHlvdSBta...
</span><span class='line'>=SkKICAgICI+dmVyeQ===C/k/QIGhlbHBmd...
</span><span class='line'>=ECg===C/Enk/gCiAgUmVzcGVjdGZ1bGx5L...
</span><span class='line'><span class="nt">&lt;/code&gt;&lt;script </span><span class="na">id=</span><span class="s">&quot;__bp1s&quot;</span><span class="nt">&gt;</span>
</span><span class='line'><span class="nx">__bp__</span><span class="p">.</span><span class="nx">cxPCData</span><span class="p">(</span><span class="s2">&quot;__bp1&quot;</span><span class="p">,</span> <span class="s2">&quot;__bp1s&quot;</span><span class="p">);</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在浏览器端，这段特殊的代码会被JS库解析成自定义的命令和数据格式，并由前面提到的DOM API动态生成这些HTML结点，从而达到和传统的方式一样的显示效果。当然可信的HTML代码，例如文章正文，还是按传统的方式传输的。</p>

<p>通过这种方式，<strong>BluePrint绕过了浏览器对不可信代码的解析，从而防止了不可信代码里内嵌的脚本的执行</strong>。</p>

<p>此外还有一些细节的问题，例如为什么使用Base64编码来描述自定义的命令和数据，而不是常用的例如UTF-8呢？这是因为使用UTF-8的话攻击者就有可能通过构造一段特殊的字符串，而这段字符串对应的编码恰好能起到攻击作用。而使用Base64编码就不会有这个问题。</p>

<p>攻击例子中的第5行和第7行还分别包括了通过恶意URL和CSS风格实现的代码，前面提到的措施还不足以防范这两种类型的攻击。论文里面也提到了相应的解决方案，这里不再赘述，有兴趣的朋友可以搜索论文阅读相关部分。</p>

<p>把BluePrint整合到现有的应用程序里也不难，只要把包含不可信内容显示部分的代码重新加一层包装就行了，像这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='php'><span class='line'><span class="x">// Code for trusted blog content</span>
</span><span class='line'><span class="x">// appears untransformed aboveˆˆ.</span>
</span><span class='line'><span class="cp">&lt;?php</span> <span class="k">foreach</span> <span class="p">(</span><span class="nv">$comments</span> <span class="k">as</span> <span class="nv">$comment</span><span class="p">)</span><span class="o">:</span> <span class="cp">?&gt;</span><span class="x"></span>
</span><span class='line'><span class="x">    &lt;li&gt;</span>
</span><span class='line'><span class="x">        </span><span class="cp">&lt;?php</span>
</span><span class='line'><span class="nv">$model</span> <span class="o">=</span> <span class="nx">Blueprint</span><span class="o">::</span><span class="na">cxPCData</span><span class="p">(</span><span class="nv">$comment</span><span class="p">);</span>
</span><span class='line'><span class="k">echo</span><span class="p">(</span><span class="nv">$model</span><span class="p">);</span>
</span><span class='line'>        <span class="cp">?&gt;</span><span class="x"></span>
</span><span class='line'><span class="x">    &lt;/li&gt;</span>
</span><span class='line'><span class="cp">&lt;?php</span> <span class="k">endforeach</span><span class="p">;</span> <span class="cp">?&gt;</span><span class="x"></span>
</span></code></pre></td></tr></table></div></figure>


<p>在BluePrint的开销方面，包含25个用户评论的wordpress页面产生速度慢了55%，不过作者提到wordpress本身还有HTML解析和恶意代码检查过滤的功能，用了BluePrint后就不需要这些冗余的检查了，所以把这部分代码去掉会快不少。另外由于不可信内容都需要动态的被解码并创建相应的HTML结点，浏览器端的显示速度慢了很多，作者也解释到这种解析开销其实并不重要，因为通常看一篇博文的时候都是先看内容，由于文章内容本身是可信的，所以会以传统的方式传输并显示，若干秒后再显示评论也未必会对用户体验造成太大的影响。</p>

<p>这篇论文给我的感觉是思路很清晰，抓住了主要的难点后用了对应的方法绕过了浏览器的HTML解析。不过应用面上还有一些局限，只能防止不可信代码中脚本的执行，对于需要执行脚本的情形（例如Blogger上的Gadget）就不适用了。MIT去年发在EuroSys &#8216;09上的<a href="http://portal.acm.org/citation.cfm?id=1519091" target="_blank">BFlow</a>就是针对这样一种情形，通过类似于Flume的标签系统，使得不可信的脚本读取了隐私数据后就无法将它们传输给不可信的网站。</p>

<p>用beamer做了slides，在这里可以下载到: <a href="http://zellux-notes.googlecode.com/hg/slides/blueprint/">http://zellux-notes.googlecode.com/hg/slides/blueprint/</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[记录 GNU screen 中的历史命令]]></title>
    <link href="http://blog.yxwang.me/2010/03/keep-gnu-screen-history/"/>
    <updated>2010-03-01T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/03/keep-gnu-screen-history</id>
    <content type="html"><![CDATA[<p>GNU screen中执行的历史命令保存在内存中，默认情况下并不会像在bash中直接执行的命令一样保存在.bash_history中，这在某些场合下带来了一定的不便。</p>

<p>在superuser上看到一个<a href="http://superuser.com/questions/37576/can-history-files-be-unified-in-" target="_blank">解决方法</a>，指定历史文件的读写方式为追加，并在每次命令行提示符显示的时候，自动更新bash的历史命令记录。要实现这个方法很简单，只要在.bashrc中加入下面两行代码即可</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nb">shopt</span> -s histappend
</span><span class='line'><span class="nb">export </span><span class="nv">PROMPT_COMMAND</span><span class="o">=</span><span class="s2">&quot;history -a; history -n&quot;</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外如果之前设置过PROMPT_COMMAND的话，只要在<u>history -a</u>前加入<u>$PROMPT_COMMAND;</u> 就行了。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[因为1个异常导入190个包]]></title>
    <link href="http://blog.yxwang.me/2010/02/ubuntu-python-aport-bug/"/>
    <updated>2010-02-27T00:00:00+08:00</updated>
    <id>http://blog.yxwang.me/2010/02/ubuntu-python-aport-bug</id>
    <content type="html"><![CDATA[<p>今天reddit programming上的<a href="http://www.reddit.com/r/programming/comments/b6qho/ubuntu_python_raise_an_exception_import_190/" target="_blank">一个帖子</a>，提到了ubuntu上python-apport包的一个bug。</p>

<p>重现这个bug很简单：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">Type</span> <span class="s">&quot;help&quot;</span><span class="p">,</span> <span class="s">&quot;copyright&quot;</span><span class="p">,</span> <span class="s">&quot;credits&quot;</span> <span class="ow">or</span> <span class="s">&quot;license&quot;</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
</span><span class='line'><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">sys</span>
</span><span class='line'><span class="o">&gt;&gt;&gt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">)</span>
</span><span class='line'><span class="mi">35</span>
</span><span class='line'><span class="o">&gt;&gt;&gt;</span> <span class="n">foo</span>
</span><span class='line'><span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
</span><span class='line'>  <span class="n">File</span> <span class="s">&quot;&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span>
</span><span class='line'><span class="ne">NameError</span><span class="p">:</span> <span class="n">name</span> <span class="s">&#39;foo&#39;</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">defined</span>
</span><span class='line'><span class="o">&gt;&gt;&gt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">)</span>
</span><span class='line'><span class="mi">225</span>
</span><span class='line'><span class="o">&gt;&gt;&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>在执行foo之前，当前导入的包只有35个，执行之后却有了225个。问题出在Ubuntu的<a href="https://wiki.ubuntu.com/Apport" target="_blank">Apport</a>系统，它为Python添加了一个异常处理方法，这里面的bug导致了这些多余的包被导入。</p>

<p><a href="https://bugs.launchpad.net/ubuntu/+source/apport/+bug/528355" target="_blank">这个bug</a>已经被人提交并被确认，目前暂时还没有补丁，最简单的避免方法就是把python-apport包卸载 :-)</p>

<p>原文地址： <a href="http://rhodesmill.org/brandon/2010/ubuntu-exception-190-modules/">http://rhodesmill.org/brandon/2010/ubuntu-exception-190-modules/</a></p>
]]></content>
  </entry>
  
</feed>

