学好编程,报复社会

功能:通过Amazon Cloud Reader,获得已购买书籍的原文。

步骤:

  1. 访问 https://read.amazon.com/
  2. 右键想获得原文的书,选择Download & Pin
  3. 等待下载结束,书上出现绿色别针
  4. 点击进入阅读
  5. 打开链接(失效了,参见下面的源代码)。把其中文本复制到Amazon Reader的地址栏中
    (Safari下过;但在Chrome下测试出现诡异的错误。如果Chrome没效果,可以按F12进入Console Tab,把“javascript:”后面的部分粘贴到命令行中)
  6. 等一会儿,然后Ctrl-A & Ctrl-C
  7. 回馈社会

原理:Kindle Reader用了localStorage database来存储Pin到本地书籍,这些数据库中的内容除了被lz压缩外,没有特殊处理,所以只要把数据取出来解压缩即可。

不足:不能获得图片(图片也是被放进database的,实现起来不难,只是我懒得改了);文本是带html标签的,直接贴到支持html标签的编辑器中可能会出问题。

下载:没绕之前的源代码(原链接失效了,新的放在 gist 里,可能也用不了,参见 comment 或者自己调试)

当我们嘲笑UML的时候,我们应该嘲笑什么

按:上周偶尔谈起UML,想到自己UML、UP(统一过程)的知识有5-6年没更新了,干脆趁没忘光都吐出来。吐槽文,达意第一,术语使用不严谨。

——————————————————-

为了嘲笑UML,我们必须追溯UML的初衷,于是我们必须了解现实世界和软件世界之间天然的隔阂。虽然人们对软件已经习以为常,但是软件的构造并不像其他物理工具的制造那样透明。对软件一无所知的人来说,“编程”一词或多或少包含了“魔法”的成分。

如果对软件的内在机制有所了解,就可以更容易地表达软件与现实之间的鸿沟——即,描述现实世界的自然语言和描述软件世界的机器语言之间的失配(mismatch)。现实世界的需求不能轻易地被机器语言表达,这是鸿沟所在,或者更专业地说,是软件构造复杂度的来源。

一直以来,计算机行业用两种方法降低这种复杂度。第一种方法是对机器语言进行封装——通过引入高级语言、面向对象等概念,使编程语言可以更直接地描述现实世界。

(题外话,这是为什么从数(美)学上看编程语言越来越dirty。因为虽然Lisp很美——美到某本书某页最下方巴掌大的Lisp代码包含了计算的本质,但这种美却不能帮助Lisp更好地描述dirty的现实世界。)

但因为任何编程语言本质上都和图灵机等价,所以编程语言与现实世界的接近程度是有极限的。现存的编程语言依然难以直接描述复杂的现实。

于是计算机行业用第二个办法来弥合现实与软件的隔阂,这就是建模(modelling)——通过建模将现实世界抽象为模型,使现实世界更加清晰精确;编程语言不再直接描述现实世界,而是描述现实世界的模型。

“建模”包含两部分内容:首先是行为(“建”),即如何构造模型的方法;其次是结果(“模”),即作为方法制品的模型。

UML模型是一种用于构造软件的模型,是现实世界和软件世界的有效中介物。UML模型被用来描述现实世界的结构,并保证描述结果和软件结构天然近似。

但UML不是一种建模方法,UML没有定义将现实世界转化为UML模型的建模方法。实际上,使用UML模型的建模方法被称为“Unified Software Development Process(统一软件开发过程,缩写为UP)”。

UP的官方定义语是“用例驱动,以架构为中心,迭代增量的开发过程”。UP以模型为中介,降低软件开发的复杂度,帮助人们更容易地将现实世界的需求转化为软件。UML模型仅仅是UP的制品,而且仅仅是制品中的重要部分。一个未必恰当的比喻:UP是催动UML招式的内功心法。

遗憾的是现实中UP被丢弃、被遗忘、被不为人知。于是产生了各式各样对UML的误用:

– 比如,以为UML用例图就是用例;以为画小人圈圈就描述了需求。实际上,UP中首先要用文档附以领域模型和业务模型描述需求。每个用例都是从需求中抽取,并且是包含参与者、前置条件、后置保证、主要事件流、扩展事件流的完整文字描述。用例图仅仅用来追溯索引用例的图示。

– 比如,以为UML只包含类图;以为类图是银弹;以为使用UML就是画一张事无巨细的类图。其实UP中包含构造系统类图的完整流程方法,这包括:首先抽象现实世界的概念关系构造领域模型类图。而后以用例为中介精化领域类图,从用例中抽取包含新辨识概念关系的分析类图。而分析类图又会在后面的过程中被精化为设计类图,直到被用来指导具体实现。

– 比如,因为各种误用,以为软件开发的复杂性是UML造成的,以为摆脱UML就摆脱了软件开发的复杂性。实际上,软件开发天然复杂。摆脱UML不意味着人们可以用编程语言直接描述(具备一定复杂度的)最终系统。相反人们还是会以“土法炼钢”的方式构造自己的中介物。

此处我也无意为UP布道。不过我想说当我们像上面那样使用UML并造成混乱的时候,我们不应该嘲笑UML,UML应该嘲笑我们,因为我们才是混乱的根源。

当我们嘲笑UML的时候,我们到底应该嘲笑什么?

UML或曰UP的最大问题在于:它们认为模型是解决一切问题的银弹。

实际上以建模来弥平现实与软件的鸿沟,需要经历两个步骤:首先是对现实进行抽象,将现实转化为一种更精确更易理解的模型;第二步则是将模型“具象”到软件世界,即用编程语言实现获得的模型。其中的第一个步骤中,模型作为最终制品很重要;但第二个步骤必须落实到编程语言对软件实现上。

UP没有认清这一点,所以UP没有定义出模型使用的边界,没有给出在抽象过程结束后抛弃模型的时机,也因此没有将注意力集中到编程语言对软件的实现上。相反UP要求对模型不断精化,要求模型不断接近最终的软件。

这导致UML或者更广义建模研究的终极理想成为:通过精化的模型直接生成运行软件,或者更直白地说企图用模型替代编程语言。

然而无论直观感觉,还是多年的研究结果都表明:在企图精确描述软件世界时,模型并不比编程语言更优越——即,在深入到一定细节后,表达等量的内容,模型和编程语言同样复杂(如果前者不是更复杂的话)。

所以以模型替代编程语言的所有努力看上去是注定会失败的。UML是所有这些徒劳无功的形象代言人。

因此,我想,只有当我们看到那些事无巨细的类图、极端精确的时序图,看到那些双向转换模型的纷繁规则,看到那些神似编程语言的“建模语言”时,我们才可以去嘲笑UML。

因为这些多少代表了UML/UP以为自负可以达到,但其实不可能达到的境地。

在线代码着色

写论文或ppt的时候,偶尔需要贴一点代码。

早年用Ultraedit的时候就直接截屏。后来转到Vim之后,问题就来了。Vim的字体和文档完全不能搭,只好单独找地方着色,于是陆陆续续用过以下方案:

  • Eclipse,之前还偶尔用这大玩意,后来根本就不装了;
  • Google Code,svn过去让google染色。后来我觉得这样太sb了;
  • Quick Highlighter,online版本合用的只找到这个。可是每次都要提交到后台,刷页面很麻烦,支持语言种类虽然多,但其实完全用不到,于是放弃了;
  • SyntaxHighlighter,后来一直用。建个专门的文件,每次改这个文件,时间长了也不爽……

前两天写ppt时实在受不了了,今天痛定思痛,基于SyntaxHighlighter写了一个Online的。

有需要的就拿去用吧,http://qizhao.sakinijino.com/tools/sh.html

p.s. 目前在非webkit的浏览器下好像会重复下载brush的js,导致着色的性能问题。暂时没搞清楚原因,先凑合着吧。

动态语言中的设计模式

今儿迁主机的时候,顺便把旧文档分类整理了一下。发现自己在04年还写过这么一篇…… 居然还投给了《程序员》…… 那时候真是太有闲情逸致了,我操……

读了读,除了“检视”这种港台腔外。发现我那会儿就有把简单事写复杂的天赋…… 恩,以下愿者上钩……

——时光倒流的分割线———

动态语言中的设计模式

Grady Booch在为《Design Patterns》一书所写的序言中谈到,“软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径”。 从设计模式出现的那一天起,这众多的模式就成为面向对象软件开发过程中一笔宝贵的财富,而对设计模式的总结工作也从来没有停止过。先是GoF的《Design Patterns》中23种经典模式的编目和命名,而后广大开发人员在此基础上不断扩充。这种种工作都为人们积累了重要的设计经验。

然而尽管设计模式本身应该与具体语言实现无关,但设计模式是从实践中总结而来的,因此在成文过程中它又不可避免的与特定的语言相关联。目前由于在工业中C++、Java等静态语言占据主导地位,所以人们总结出来的设计模式通常也是基于这些语言的。

如今计算机硬件仍然依照摩尔定律所提出的趋势飞速发展,软件的复杂度也越来越高,相对而言软件开发效率却一直没有质的飞跃。因此执行效率相对低下但开发效率较高的动态语言愈发受到人们的关注。但是想在动态语言中使用目前总结出的大量设计模式却存在一定的困难,因为静态语言与动态语言的语义差异很大,如果只是照搬目前这些设计模式并不能充分发挥动态语言的语言特性,有时候把某些静态语言的模式运用到动态语言上甚至是作茧自缚、画蛇添足了。

Python作为一种完全面向对象的动态语言,因为其简洁、易用、灵活的特点已经越来越受到开发人员的青睐。以下行文中就将用Python实践几种简单的模式,以此来逐步展示Python作为动态语言所具有的语言特性对于设计模式实现的影响。

1.    运用动态类型特性实现灵活性更高的Abstract Factory

不妨让我们从《Design Patterns》中所提到的第一种模式Abstract Factory开始。

实际上同样是面向对象的语言,把C++的代码翻译成Python的代码并不难。下面Python代码就是由《Design Patterns》中提供的用于创建迷宫相关产品的抽象工厂类MazeFactory翻译得到:


class MazeFactory:
  def makemaze(self):
    return Maze()
  def makewall(self):
    return Wall()
  def makeroom(self, n):
    return Room(n)
  def makedoor(self, r1, r2):
    return Door(r1, r2)

这个类就可以用于创建Maze的基本产品了,同时我们可以继承这个类已得到其他系列产品的工厂,比如魔法迷宫EnchantedMaze的抽象工厂:


class EnchantedMazeFactory(MazeFactory):
  def makeroom(self, n):
    return EnchantedRoom (n)
  def makedoor(self, r1, r2):
    return EnchantedDoor (r1, r2)

这样我们就不再需要将有关Maze风格的代码硬编码到具体Maze的创建过程中,而当创建不同风格的Maze时只需要替换不同的Factory就行了。
不过这么简单的翻译C++代码很无趣,而且也不能充分体现Python作为动态语言为编程人员带来的便利。下面让我们来看一看Python语言中的动态特性能为我们做些什么。

略加察看后我们就会发现这样实现的MazeFactory具有一个不足之处:很难向MazeFactory里添加新种类的产品。当需要支持新类型的产品时(比如一个陷阱产品Trap以及EnchantedTrap),我们需要修改MazeFactory以及它的所有子类,这是一件很枯燥的事情。而且我们还不得不改变MazeFactory的接口,这更是我们不愿意看到的。

对于这个问题GoF的书中向我们介绍了一个比一般Abstract Factory更灵活的解决办法,即只实现一个统一的make函数,通过传递给make不同的参数来确定要创建的产品的类型。

然而对于C++这样的静态语言,实现这种解决方法很困难。因为为了确定make返回值的类型,我们不得不将所有的产品继承自一个公共基类。但是Abstract Factory考虑的只是产品的风格系列,而同一系列不同类型的产品间逻辑上可能不存在明确的公共基类——比如Maze和Wall。而且即使使用公共基类也会有导致大量的向下强制类型转换出现。这些都是我们编程时希望避免的。

此时动态语言中动态类型特性的优势充分体现出来。动态类型允许一个变量在运行时刻绑定到不同类型的对象上,也就是说所有变量——包括函数的参数和返回值——都可以是任意类型的。因此我们不必要求Maze、Room等不同类型产品具有公共基类,也能很容易就能实现这种拥有统一make函数的工厂:


class Maze:…
class Wall:…
class Room:…

…

class MazeFactory(object):(注1)
  def make(self, typename, *args):
    if typename == 'maze': return Maze()
    elif typename == 'wall': return Wall()
    elif typename == 'room': return Room(args[0])
    else typename == 'door': return Door(args[0], args[1])

class EnchantedMazeFactory(MazeFactory):
  def make(self, typename, *args):
    if typename == 'room': return EnchantedRoom (args[0])
    elif typename == 'door': return EnchantedDoor (args[0], args[1])
    else: return super(EnchantedMazeFactory, self).make(typename, args)

而创建Maze的代码可能是:


mf = EnchantedMazeFactory ()
mz = mf.make('maze')
r1 = mf.make('room', 1)
mz.addroom(r1)

make函数第三个参数*args表示把从第三个开始以后所有的参数接受为一个list,产品的构造函数从这个list中取出相应的参数用于构造对象。super是内置函数,用于返回一个变量的基类对象。

这样利用Python动态类型的特性(注2),我们用很小的代价实现了一个具有更多灵活性的MazeFactory了。现在当我们要支持新的产品类型时,我们只需要添加标示新产品的参数,而保持了MazeFactory接口的稳定。

不过估计你看到这一堆if、else会感到很不爽(其实就是个switch),我也是。更好的做法是编制一个产品的字典,利用这个字典来索引要产生类型,后面的部分我们将具体介绍这个方法。

动态类型是动态语言的一个基本特性,它能简化类层次、去除不必要的强制类型转换,提高语言表达能力以及自由性。实际上有很多模式都因为静态类型检查的限制造成了不必要的类层次和强制类型转换。比如一个用于遍历包含许多不同类型元素容器的Iterator,就可能要求所有的元素都继承自同一基类以便实现getCurrentItem,而实际上这些不同类型的元素逻辑上并不需要一个公共基类。利用动态类型特性可以使这种模式的实现更加简单灵活。

2.    动态语言不需要一些静态语言中的设计模式

前面提到我们需要编制一个产品的字典,利用这个字典索引要产生类型,这样我们就可以通过类似index['room']的代码来创建产品了。同时我还希望赋予这个MazeFactory动态配置产品类型的能力。用什么模式?Prototype?没错,这的确是运用Prototype的地方,让我们来实现它:


import copy
class MazeFactory:
  def __init__(self):
    self.index = {'maze': Maze(),
      'wall': Wall(),
      'room': Room(),
      'door': Door()}
  def make(self, typename,):
    return copy.deepcopy(self.index[typename])
  def registtype(self, typename, instance):
    self.index[typename] = instance
  def unregisttype(self, typename):
    del self.index[typename]

copy是Python的一个内部模块,其中包含deepcopy函数用于深拷贝对象。registtype、unregisttype函数则用来动态增删产品类型。
似模似样,还不错。不过Python中有没有更好的做法呢?

首先检视一下Prototype的初衷。因为C++并不提供类一级的对象——也就是说类本身不是对象,这就给生成产品以及动态控制产品类型造成了一定的困难,Proto Type模式的出现就是为了解决这个问题。然而像Smalltalk一样,对于Python来说类本身(也包括函数,模块)也是一个对象——type类的对象,可以对对象进行的每样操作都可以使用到类上。利用这点便利,我们不使用Prototype模式就可以实现这个MazeFactory:


class MazeFactory:
  def __init__(self):
    self.index = {'maze': Maze,
      'wall': Wall,
      'room': Room,
      'door': Door}
  def make(self, typename, *args):
    return apply(self.index[typename], args)
  def registtype(self, typename, type):
    self.index[typename] = type
  def unregisttype(self, typename):
    del self.index[typename]

apply是一个内置函数,它接受两个参数一个函数、一个参数list,它的作用是把args作为参数传给类构造函数运行。

现在当我们要添加新的产品类型时,在字典中添加新的索引值对即可。比如我要在MazeFactory中添加一个新产品Trap,只需要在index里加入:'trap': Trap。而动态添加类型时也只需要传入相应的类即可:mf.registtype('trap', Trap)

可以看到我们已经得到了一个静态、动态都比较容易管理产品类型很灵活的工厂。

设计模式中一部分模式的作用就是让程序更灵活,拥有更多的动态特性。而动态语言可能在语言一级就已经支持了这些特性,所以这部分模式也就不需要了。Prototype是一个例子,类似的还有Template Method。实际上因为上面提到的动态类型特性,任何函数都可以接受任意类型的变量做参数,所以Template Method模式也就没有任何价值了。

3.    运行时刻修改类结构来实现Singleton

Singleton是一个非常有用的模式(注3),比如前面的MazeFactory就很有可能需要做成一个单件。让我们来尝试实现这个模式。首先还是按照C++的方法来实现一下:


class Singleton(object):
  instance = None
  def __new__(cls):
    if cls.instance is None:
      cls.instance = object.__new__(cls)
    return cls.instance

类的__new__方法是一个特定的函数,它在对象创建前被执行返回一个cls类的对象。instance则负责保存单件对象。

依循C++的思路,这个程序完全正确,但是看起来缺少一点感觉。让我们查看一下Singleton对象创建的过程,第一次构造对象时创建一个新的对象,以后构造时则不再创建而只是返回第一次创建的结果。

可以看到这是一个典型的运行时刻修改类结构的例子。Python来做自是手到擒来:


class Singleton(object):
  def __new__(cls):
    cls.instance = object.__new__(cls)
    cls.__new__ = cls._new
    return cls.instance
  def _new(type, cls):
    return cls.instance
    _new = classmethod(_new)(注4

classmethod函数的作用是把一个成员方法变成一个类方法。

整个类的代码很简单,第一次进行构造时创建对象,同时用_new函数替换__new__函数,而以后__new__(实际上就是_new)只是返回第一次创建的对象。

能够在运行时刻修改程序结构是动态语言区别于静态语言的一个重要特点,Python中它主要表现在可以动态添加、删除、修改对象(包括类和函数)的方法和变量。这个特性可以用来帮助实现Singleton、Decorator、Strategy等一些要求运行时刻动态改变对象职能的模式。

4.    用Functional Programming简化Command模式

最后要谈到的一点就是Functional Programming。在设计模式里谈到FP可能不很切合。如果我们注意一下会发现GoF《Design Patterns》的副标题是“Elements of Reusable Object-Oriented Software”(可复用面向对象软件的基础)。实际上我们日常谈到的设计模式主要是针对OO Programming来说——因为工业中主要应用的是面向对象编程语言。而FP是区别于OOP的另一种编程风格。

但是Python作为一种面向对象的动态语言,它对FP和OOP的编程都提供了良好的支持。在OO的设计模式中运用一些Functional Programming的思想也会收获一定的好处。下面以Command模式为例讲述这一点。

因为有时候请求操作的对象不知道被请求操作对象的任何信息(打开文档的按钮对象不会知道任何文档对象的信息)。Command模式用以解决这个问题,根本目的在于把被调用操作的对象与实现该操作的对象解耦。而Command模式的实现方式是“把函数层面的任务提升到了类的层面”(注5)。因为FP中函数本身就是第一类对象,所以我们不必再像C++中那样运用继承组合来迂回地实现这一点。FP实现Command模式是非常简单的。


class Button:
  def click(self):pass

这样我们就已经有了一个Button类。看起来什么都没有?别急,先让我们实现一个用于打开文档的Button看看:


opendoc = Button()
opendoc.click = doc.open

现在当opendoc这个按钮被点击时,仅仅需要执行opendoc.click,就等价调用了doc.open这个函数了。这段代码看似简单,但观察下会发现Button和Document已经被很好地解耦,只是FP的实现中间没有那么多迂回,而思路、效果与Command模式是一样的。

再来看看MacroCommand。MacroCommand的目的在于一次执行一系列的Command——也就是函数。有了FP的办法,我们也不必再用Composite模式搞得很复杂,而是利用匿名函数lambda关键字和内置FP函数map实现为:

marcocommand = lambda commands: map(lambda command: command(), commands)

map依次把commands中每个的command传给第一个参数lambda command: command(),而这个参数本身也是一个函数,用来执行传入的command。现在如果我们要实现一个Find & Replace的Button,我们只要这么写:


findreplace = Button()
findreplace.click = lambda: marcocommand([doc.find, doc.replace])

当然Function Programming本身是很复杂的,仅仅在设计模式中一些简单的运用不能充分体现FP的思维方法和能力。但FP作为区别于OOP的编程风格,将其适当用在设计模式中,的确可以优雅简单地实现一些原本复杂的模式。而不同编程风格的尝试和融合也有利于拓宽我们的思路。

结语

通过Python的模式实践,我们可以看到动态语言的种种特性对各种设计模式的实现方式的影响,它们在简化了部分模式实现的同时更带来了较大的灵活性。当然天下没有免费的午餐,动态语言在语言一级提供更大灵活性是以执行效率的下降为代价的,个中的利弊还要广大开发人员去权衡。

本文只是浮光掠影地进行了一些简单的讲述,其中不免有许多疏漏。要想更好地掌握动态语言中设计模式的应用,必须要对各种设计模式以及动态语言的特性有着清晰的了解,并在实践中不断尝试才能做到。

注1: Python中继承自object的class表示一个type,否则是一个classobj。一些特定的操作必须是type类型才能执行,比如super函数要求传入一个type类型的参数、__new__函数也只有type类型才支持。如果需要用到这样操作,则必须让你的class继承自object。

注2: 值得注意的是Python实际上是一种强类型动态类型的语言,而不是弱类型的语言。关于强弱类型的具体解释可以参考这篇文章《Typing: Strong vs. Weak, Static vs. Dynamic》By Aahz。

注3: 实际上由于Singleton模式不能很好地适用于多线程环境,多线程环境中使用更多的可能是Singleton的变种Double-checked locking模式。不过理解了Python中Singleton实现,DCL也是大同小异,所以这里仍然用基本的Singleton模式来解说。

注4: Python2.4中提供新的语法Decorators for Functions and Methods来使类似操作更容易的实现。具体的语法和相关说明见PEP 318

注5: 语自《Agile Software Development》By Robert Martin

2004-11-25左右成稿

SproutCore – Client-Side RoR

引:昨天吐了一大篇出来,今天没什么力气,捡要点写。

首先我是Ruby on Rails fan。其次RoR出来之后,很多人都在抄它这事实就不说了。因此考虑如今的web应用和客户端应用已经是同构的了——web应用的架构其实是客户端的MVC的一个简化,所以咱为啥不搞个客户端应用的RoR出来?不偶然的是,现在这世道只要你想得到的就有人做,比如人家JavascriptMVC都tm出到2.0了。这玩意我原来试过,还行。

不过今天又发现之前在feeds被略过(信息过载要命呀)的另一个玩意,SproutCore。1.0RC,亮点是Apple开源出来的,Demo上看性能不错。一句话来说就是一个客户端应用的RoR,framework+building tools,架构就是纯正MVC(包含observer)。

SproutCore这类东西和YUI、Extjs这类的区别,基本上就是PHP和RoR的区别。用SproutCore能做的是YUI完全能做。但我的观点是YUI、PHP这个层次的框架(PHP+一堆包也算个框架了)最大的问题在于不能帮助开发人员构建应用的整体架构。虽然它们的能力完全没有问题,但如果你是一新手,那就很容易在一开始直接晕菜,而老手则经常要在一开始做很多Copy&Paste的dirty work。相反RoR、SproutCore都提供一个强制但合理的完整应用架构。你用这框架就直接得到一个骨架,在上面直接添加具体的应用内容就行了。这类框架同时提供一系列building tool来帮助你跟随这个架构来构建应用,所以构建应用本身也就成了应用架构学习的过程——特别是针对新手。(RoR在开发时的优点其实真不用我废话了。)

当然客户端用到MVC这么重载的架构,一定得是很rich很rich的客户端了。不过我是觉得在未来几年里,客户端一定会变得现在想不到的重载,这个观点不算激进。如果你坚信页面用HTML+jQuery就完事了,那咱就不相与谋了,但如果你有一天你考虑要用到YUI、Extjs这级别的东西,不妨关注下SproutCore的发展。不要小看漂亮架构的力量,现在YUI这玩意ms基本的东西已经做绝了,新加的多是些稀奇古怪的功能。但是看看SproutCore的DataSource实现,这是我现在看到最漂亮的客户端Data Access实现了。

你看我的技术讨论好像有点又大又空,一点实例都没有。告诉你,其实我现在特别讨厌技术,写这些字,耽误我多少看书的时间!所以有兴趣你就自己看SproutCore这项目的wiki去!最后再顺便愤青地表示一下这项目wiki被墙的愤怒,mlgbd!睡觉去,完。

OAuth协议更新到1.0a

上个月24号发布了针对上次那个漏洞修补的更新版协议1.0a。协议本身已经是最终的版本了,但暂时还没有合并到OAuth的官网中,按邮件列表中的说法是因为还有一些license的工作要做。下面简单说一下这次的修补。

首先回顾一下针对上次漏洞的攻击:

  1. 攻击者到一个合法的Consumer站点获得一个request token
  2. 利用trap site让受害者授权这个request token
  3. 之后针对callback的不同,有几种不同的处理方法:
    1. 如果Provider站点对第二步(请求用户授权)中的callback没有限制,那么攻击者可以利用trap site将callback设为自己的站点,然后通过这个callback判断受害者何时完成授权。等到受害者授权后,攻击者访问合法的callback来获得受害者的access token
    2. 其他的情况下(使用预定义的静态callback、没有callback由用户手工完成授权),那么攻击者和受害者之间进行竞争,当攻击者恰好在受害者授权和访问callback的间隙访问合法的callback,那么攻击者就获得了受害者的access token

接下里来看这次协议对于漏洞的修补:

  1. 1.0协议中callback在oauth认证的第二步(请求用户授权)中通过url参数设置并明文传输。新的1.0a协议中,callback作为oauth第一步(获取request token)的参数(oauth_callback),并参与到签名过程。此外当没有callback时,callback值必须设为oob(out-of-bound)。
  2. Provider在返回request token的同时,返回一个oauth_callback_confirmed参数,其值为true,表示Provider支持1.0a协议。
  3. 用户完成授权后,Provider在redirect到callback时,提供一个新的oauth_verifier参数(无法被猜测的随机字串),该参数会被用在request token换取access token的过程中。如果callback值为oob,那么Provider需要提供页面显示oauth_verifier参数值。而Consumer可以引导用户将这个参数值填入适当位置,来完成授权。

问题的解决:

  1. 通过预先指定callback,并将其作为request token的一部分,可以避免3.1中,攻击者假造callback的情况
  2. 通过oauth_verifier参数,可以避免3.2中,攻击者和受害者竞争访问callback页面的问题。(在使用预定义静态callback或者无callback的情况下,callback无法对攻击者保密。但因为攻击者无法获知oauth_verifier参数值,因此无法换取access token)

协议本身很长,主要修改集中在第6节。此外原先的附录B现在转正成为第11节并增加了一些新的安全建议。

这里还有一些概略的介绍本次修补的文档:An Idiot’s Guide to OAuth 1.0aMoving from OAuth 1.0 to OAuth 1.0a

恩,大抵如此。学术完毕,好累。。。囧。。。

chrome下跑palm webOS的应用

之前一些是在firefox下试的时候的改动,后来在webkit下发现完全不必要,囧。。。全删了,出奇的简单,就是一堆解压缩,Orz。。。

————————————————-

webkit大一统,firefox退散~ 很简单:

1. 到 http://palm.cdnetworks.net/rom/pre_p100eww/webosdoctorp100ewwsprint.jar 下个pre rom
2. 第一次解压缩
3. 找resources/webOS.tar,第二次解压缩
4. 找nova-cust-image-castle.rootfs.tar.gz,第三以及第四次解压缩。。。
5. 把usr目录扔到某个http server根目录下(实际应该只需要usr/palm的内容),但是http server的根目录要是usr
6. usr\palm\frameworks\mojo\builtins\palmInitFramework175_7.js的第33518行(囧。。),改成window[‘palmInitFramework175_7’] = $palmInitFramework175_7;
7. 找一个应用,比如usr\palm\applications\com.palm.app.calculator,在index.html中mojo.js的script标签下加上 <script src=”/usr/palm/frameworks/mojo/builtins/palmInitFramework175_7.js” type=”text/javascript”  />;
8. 找个webkit的浏览器(chrome 2或者safari 4,我就试了高版本,不知道低版本行不行),打开index就ok了~

ps. ms很多应用会显示“This is the hidden window of a faceless application”以及一个Lauch按钮。。。没具体看,我现在就知道calculator可以用~ 啊呜~

PhoneGap,人人都爱js

phonegap.comWeb 2.0 expo大众选择奖(可惜之前reader里被噪音盖过去的东西)。支持用html+js编跨手机平台(iPhone、Android和黑莓)应用,附加的一个项目是xuijs.com(ms被盾了),可以理解为针对手机浏览器的jQuery,因为去掉了很多针对桌面浏览器兼容性的代码,所以体积小了很多。

乍一看确实不错。不过最近又在新闻里冒出来是因为App Store把基于这个开发的应用都给拒了,囧。

附加阅读:

男1:你看你在我领导下学了多么nb的一门语言呀
男2:你是这么领导的。对面是知识的海岸,我们站在这一岸,你把我踹下去,说游,然后我拼了自己的命学会了。。。

what the hell is opensocial?

花了一天多的时间写了一个可能是“符合规范”的豆瓣秀 OpenSocial Gadget。然后零零碎碎合起来花了大概两天的时间试着把它提交到各个号称支持OpenSocial的容器里,均不成功,目睹怪现象若干。。。陈列于此:

1. iGoogle:iGoogle提供了一个可用于调试OpenSocial的Sandbox,这个Sandbox是Google在各次宣传OpenSocial活动中展示的工具。不过当本猫在iGoogle Sandbox中调试好gadget,然后提交到iGoogle的App Directory中安装之后发现,iGoogle非Sandbox的普通用户使用的版本不支持OpenSocial,囧。。。(今天参加Google Devfest的时候又试了一下,结论上Sandbox也不支持OpenSocial了,而今天Google自己的演示也全是Orkut了)

2.Orkut:Google的当家SNS。另一个Google在活动中展示的Sandbox。首先Orkut中文版里提交gadget的文件总是显示“抱歉,XXX错误”。。。切换到Orkut英文版,暂时可以用了。不过gadget的标题里的中文字符,会被莫名其妙的Url Encode。。。不管怎么说,勉强可以用吧,调试完毕,欣欣然然submit。显示“审核”,等了整整一天(时差,时差问题),一封拒信打回来,说只接收够“production”的gadget。。。老大们好歹给点线索哪里不够吧。。。自己猜了半天,东改改西改改。然后再去提交,提示“这个url不能提交,因为已经提交过了”。。。囧,暂时放弃中。。。(p.s. Orkut的opensocial实现里有bug,一些时候会出现取不到UserPrefs的问题,不过和之下的比这不算什么。。。)

3. Myspace中国:好大一个SNS,好多用户,然后基本上认为开发人员都是超人。。。首先花了几分钟没在页面上找到进入developer页面的链接,结果只能google “myspace developer”(不愧和google是合作关系,帮他增加了2个pv)。。。进入页面创建一个gadget,提示我输入一个email地址,输了自己的email,提示“已经被用了”。。。我靠,仔细看提示,要是输入不能和任何注册用户和提交Gadget重复的唯一标示gadget的email地址。。。What the hell is that… 帮助“合作伙伴”增加gmail注册数?通过给gmail加"."的方式通过。。。下一步上传gadget图标,提示"16×16",我上传了一个64X64的,提示“尺寸不对”。。。ok,开ps,我是前端、后端、技术、美工全能猫。。。最后终于可以开始弄代码了,把在orkut下测试过的代码粘过去,运行。想设置自己的豆瓣用户名,找不到设置的地方,囧。。。仔细找了半天,未果,上“合作伙伴”搜,发现原来Myspace的opensocial根本不支持Google gadget那一堆xml标签——有一个大哥说得很直白,“MySpace has a different development pattern”。。。

4. Hi5:另一个好大的SNS,然后被盾了。。。搜了,偷偷发现,原来sandbox的链接还可以用(呀,是不是又犯法了)。不过上去一试,毫无疑问的也不支持gadget的标签。。。

5. YiQi:填完表单,点提交按钮。我点呀点,我点呀点,它就是没反应。。。看代码,按钮onclick了一个叫applyapp之类的函数,然后页面load的js里没有这个函数。。。

6. 51:原来5不仅可以1ml,还可以1OpenSocial。ok,创建,又开始填表。第一个就让我填“真实姓名”,而写明了不能改,以后要想分钱(51B,啊,不是,是51币)全靠这名字了。。。填了,接着,让我填身份证号,囧。。。嘿咻嘿咻终于快填完了,忽然出现一个“添加应用通知地址”,后面跟着一个链接“详细说明”。没禁住诱惑,偷偷一点,页面跳转,再后退,强制不缓存,什么都没了。。。T_T。。。最后,弄好之后,毫无疑问的,显然也不支持gadget规范里的标签。

好了,上面是一大堆没什么意义的吐槽,下面写一点点稍微有点小价值的建议。

1. 如果你去看opensocial的文档,里面的例子都会是gadget的例子。如果你真的打算做跨container的opensocial应用,就忘掉gadget裸用html+js吧,因为除了orkut之外,没发现哪个container支持gadget规范。

2. 如果你是基于兴趣爱好想写opensocial的应用,有点心理准备。各个container都假设你是领工资或者是为了发大财才用这个container。既然你是为了钱或者为了理想做这个,那么无论用户体验多差、系统多难用,你还是会坚持用下去。而如果你是凭爱好,嘿嘿,嘿嘿嘿嘿,嘿嘿嘿嘿嘿嘿。