李开复旗下安全宝网站设计公然侵权 (Last Updated: Dec 30, 2011)

乘年底密码泄漏之东风,李开复旗下的“安全宝”网站也提供了密码泄漏查询功能(http://www.weibo.com/1197161814/xDWVTcSAmhttp://lucky.anquanbao.com/)。
避开使用用户私隐作为营销工具的批评不谈,我们来看看这个网站所使用的图标。

打开页面,我立刻就认出了这个黑色的表情图标,感觉十分眼熟,于是 Google 了一下,得知这是张伟(Rokey Zhang http://www.rokey.net/)在网易UX部门任职期间设计的,全套表情图标如下:

图标盗用已经摆在眼前了,接着使用调试工具分析所谓“安全宝”的泄密查询网页,发现这次盗用的图标一共 5 个:

如我之前在博客上谈过的,这类事件在中国互联网圈出现是再平常不过的,只是出现在满嘴“创新”的“圣人”旗下的网站里,我就有义务写出来,让大家看看了。

有人要说,不就几个图标嘛,至于那么紧张吗?
那么如果你真感兴趣,请看我的另外一篇文章,我之前回应过这个问题:

《互联网是什么?》http://www.leaskh.com/2011/08/13/互联网是什么?/


Last Updated: Dec 30, 2011:

2011 年 12 月 29 日,我与 Rokey Zhang 前辈取得联系,Rokey Zhang 表示同一天,安全宝 相关人员也与他取得了联系,且 Rokey Zhang 已同意安全宝使用该套表情图标。

2011 年 12 月 30 日,安全宝 张峰 发邮件给我,原文节选如下:

黄先生,您好!
非常感谢您对安全宝公司的关注。关于您本文提到的图片,安全宝公司已经征得了原作者同意,并且在网站中已经进行了标识,因此并没有存在侵权的行为。
鉴于这种情况,抱着友好协商的态度,肯请您对您的文章作出相应修改,以免对安全宝公司造成负面影响,好吗?

读完邮件,我立刻访问 安全宝 发现其页脚已经添加图标设计声明,如图:

事已至此,也许法律上可以认定 安全宝 的确没有侵权。但问题是,对于这种先盗用,等到被揭发再联系作者申请使用权的行为我仍然觉得是可耻的。安全宝 这次遇到了一个心地善良的设计师,算是走运了,好自为之吧。

另外,Rokey Zhang 告诉我,这套图标可以免费授权给非商业用途,在 Iconfinder(http://www.iconfinder.com/browse/iconset/popo_emotions_the_blacy_png/#readme) 上也可以读到 Rokey Zhang 的以下声明:

Notifications:
Don not use them for commercial purpose!
pictures copyright reserved by Netease company.

那么,安全宝 的泄密查询功能到底算不算商业用途呢?这个当然不是我这些法盲可以定论的,留待法律专家定论吧。同时,也请法律专家定论一下,商业公司使用用户的私隐、机密信息提供这类服务,会不会触犯法律呢?

最后,让我这个法盲给 安全宝 再上一课吧。假如这套图标(The Blacy!)属于 Rokey Zhang 在网易期间的“职务作品”,那么图标的著作权就属于网易公司所有了,网易公司可以行驶完整的著作权。当然,网易不至于为了几个图标和你纠结。只是 安全宝 作为一个做安全产品的公司,这样的做事作风谈何让人放心?

别以为“抄功课”是一件很简单的事情,这里头的学问多着呢。本文仅以一个普通网民的角度,善意提醒一些所谓强调“创新”的公司,“创新”二字,来不得半点虚假,无论学术界还是商业社会,都如此。

关于山寨

成熟的市场不是:“你卖轮子,我也来做轮子卖,即使我做得没有你好,但是我可以卖得比你便宜”,这种竞争其实很低级;更为优化的是:“你的轮子不错,你竞争对手的轮子也都很好了,我来做轴承吧,我兼容你们的轮子”。可悲的是在智力如此密集的互联网创业圈,巨头们仍不停浪费资源重复造轮子。

山寨虽然威武,但是无疑是在浪费很多优秀人才的生命,这些人本该做更有价值的东西,这些人中的一些,本该满怀理想,改变世界。若干年后,我们暮然回首,我们曾赶上过眼前的繁荣,却耽误了一个时代。

关于 Javascript 数组处理的一些小事

昨天工作中,需要使用 localStorage 本地存储一个数组,用于缓冲一些数据,避免频繁的服务器存取,优化用户体验。但是出现一个很奇怪的现象,调试用了不少时间。这里把一些小经验分享一下,避免遇到类似情况的码农兄弟浪费时间。

localStorage 是 HTML5 的新功能之一,能快速存储大量的本地数据,使用 key-value 的访问方式,比 cookies 便利。然而它并不支持对象存储,当然也不支持数组,因 Javascript 中数组也属于对象,所以你需要先把对象和数组转为字符序列,比较方便也很通行的做法就是转成 JSON 了。

我把一个数组用 JSON.stringify() 编成 JSON 存入 localStorage,在取出后用 JSON.parse() 让其重新对象化。问题是取出来后,数组中出现了很多 null 元素,让我后续遍历这个数组的代码频繁报错。

先彻底检查了一下代码,没有发现明显的可疑之处。使用 WebKit 的调试工具看了一下 localStorage 的数组,果然是多了一些 null 在字符序列中。心想应该和 localStorage 没有直接关系,于是把怀疑点转向 JSON.stringify()。直接把 JSON.stringify() 之前的数组和之后的 JSON 字串输出,果然 null 是在 JSON.stringify() 阶段产生的。

问题来了:为什么 JSON.stringify() 后,原本干净的数组会出现若干看似无规律的 null 的呢?经过仔细的分析,发现我在数组代码中,对数组执行过 unshift() 和 delete 等操作。于是立刻想到是不是 delete 造成的垃圾遗留,但是为什么遍历时就没出现问题呢?想了一下,突然想起我习惯使用 for in 方式遍历数组,而不是比较传统的 for 循环,于是有了以下这个简单的实验:

<script>

    var arr = ['a', 'b', 'c', 'd', 'e'];

    delete arr[0];

    console.log('for:');
    var maxI = arr.length;
    for (var i = 0; i < maxI; i++) {
        console.log(arr[i]);
    }

    console.log('for in:');
    for (var j in arr) {
        console.log(arr[j]);
    }

    console.log('foreach:');
    arr.forEach(function(k) {
        console.log(k);
    });

    console.log('stringify:');
    console.log(JSON.stringify(arr));

</script>

程序输出:

for:
undefined
b
c
d
e

for in:
b
c
d
e

foreach:
b
c
d
e

stringify:
[null,"b","c","d","e"]

哈哈,问题终于冒出来了:

  • Javascript  中使用 delete 删除一个数组或对象的元素后,会将其标记为 undefined,而非彻底删除该索引;
  • Javascript 在 for in 或 foreach 遍历中,会自动跳过 undefined 元素,而普通 for 遍历则不会,JSON.stringify() 也不会;
  • 由于 JSON 缺少 undefined,于是 Javascript 将 undefined 转为 JSON 的时候,undefined 会被转为 null。

果断尝试把 delete arr[0]; 改为 arr.splice(0, 1); 问题得到解决。

用 Flora_Pac.py 生成自动翻墙的 pac 文件

源于人们对自由的向往,翻墙技术已渐趋成熟。愿意花点钱,购买海外 VPN 和 ssh 主机用于自由获取信息是目前比较有效的手段。如我之前文章中提及,这两种方式都有需要筛选出那些网站在墙外,那些网站在墙内,以较节约、高速的方式访问网络。八仙过海,各显神通,不少帮助人们解决这一问题,降低翻墙门槛的小项目出现了。较具代表性的有 chnroutes(http://code.google.com/p/chnroutes/) 项目和 autoproxy-gfwlist(http://code.google.com/p/autoproxy-gfwlist/) 项目。前者修改路由表,配合各种 VPN 使用,后者可以配合 AutoProxy for Firefox(https://addons.mozilla.org/firefox/addon/11009) 或导出(https://autoproxy2pac.appspot.com/)为 pac 文件,配合各种代理服务器,包括 ssh -D 使用。他们的原理稍有差异,chnroutes 只区分国内外 IP 段,让国外地址全部走翻墙路线,autoproxy-gfwlist 项目则精确记录着那些网站被墙。

我以往喜欢 ssh -D 生成 SOCKS 代理后,搭配自己的 pac 文件翻墙。最近由于各种原因转到了 VPN 阵营。感觉 VPN 搭配 chnroutes 的确很舒服,不用再关心那些网站被墙,不会因为 gfwlist 更新延迟而影响访问。于是我在想,有没有办法让使用 ssh -D 或者其他翻墙代理的用户能和使用 VPN 的用户那样省心呢?于是我站在巨人的肩膀上,基于 chnroutes 项目,结合 pac 文件的 dnsResolve() 和 isInNet() 函数,开发了 Flora_Pac 这个小项目。

Flora_Pac 使用 Python 开发,能自动抓取 apnic.net 的 IP 数据,找出所有国内的 IP 地址段,生成能让浏览器自动判断国内外 IP 地址的 pac 文件,让代理用户有等价于 VPN + chnroutes 的翻墙体验。Flora_Pac 使用十分简单,兼容各种平台:

####### 获得帮助:
$ python flora_pac.py -h
usage: flora_pac.py [-h] [-x [PROXY]]
Generate proxy auto-config rules.
optional arguments:
  -h, --help            show this help message and exit
  -x [PROXY], --proxy [PROXY]
                        Proxy Server, examples:
                            SOCKS 127.0.0.1:8964;
                            SOCKS5 127.0.0.1:8964;
                            PROXY 127.0.0.1:8964

####### 生成 pac 文件,国外 IP 通过代理 SOCKS 代理 127.0.0.1:8964 访问:
$ python flora_pac.py -x 'SOCKS 127.0.0.1:8964'
Fetching data from apnic.net, it might take a few minutes, please wait...
Rules: 3460 items.
Usage: Use the newly created flora_pac.pac as your web browser's automatic proxy configuration (.pac) file.

####### 生成 pac 文件,国外 IP 通过代理 HTTP 代理 127.0.0.1:8964 访问:
$ python flora_pac.py -x 'PROXY 127.0.0.1:8964'
Fetching data from apnic.net, it might take a few minutes, please wait...
Rules: 3460 items.
Usage: Use the newly created flora_pac.pac as your web browser's automatic proxy configuration (.pac) file.

程序跑完后,就会在当前目录产生 flora_pac.pac 文件,把它设为浏览器或系统代理设置的 pac 文件即可。

项目代码我放在 github 上开源了:https://github.com/Leask/Flora_Pac,其中 fetch_ip_data 函数 fork 自 chnroutes 项目。

不方便上 github 的朋友,直接复制以下代码保存为 flora_pac.py 就可以跑了:

#!/usr/bin/env python
#
# Flora_Pac by @leaskh
# www.leaskh.com, i@leaskh.com
#
# based on chnroutes project (by Numb.Majority@gmail.com)
#

import re
import urllib2
import argparse
import math

def generate_pac(proxy):
    results  = fetch_ip_data()
    pacfile  = 'flora_pac.pac'
    rfile    = open(pacfile, 'w')
    strLines = (
        "// Flora_Pac by @leaskh"
        "\n// www.leaskh.com, i@leaskh.com"
        "\n"
        "\nfunction FindProxyForURL(url, host)"
        "\n{"
        "\n"
        "\n    var list = ["
    )
    intLines = 0
    for ip,mask,_ in results:
        if intLines > 0:
            strLines = strLines + ','
        intLines = intLines + 1
        strLines = strLines + "\n        ['%s', '%s']"%(ip, mask)
    strLines = strLines + (
        "\n    ];"
        "\n"
        "\n    var ip = dnsResolve(host);"
        "\n"
        "\n    for (var i in list) {"
        "\n        if (isInNet(ip, list[i][0], list[i][1])) {"
        "\n            return 'DIRECT';"
        "\n        }"
        "\n    }"
        "\n"
        "\n    return '%s';"
        "\n"
        "\n}"
        "\n"%(proxy)
    )
    rfile.write(strLines)
    rfile.close()
    print ("Rules: %d items.\n"
           "Usage: Use the newly created %s as your web browser's automatic "
           "proxy configuration (.pac) file."%(intLines, pacfile))

def fetch_ip_data():
    #fetch data from apnic
    print "Fetching data from apnic.net, it might take a few minutes, please wait..."
    url=r'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest'
    data=urllib2.urlopen(url).read()

    cnregex=re.compile(r'apnic\|cn\|ipv4\|[0-9\.]+\|[0-9]+\|[0-9]+\|a.*',re.IGNORECASE)
    cndata=cnregex.findall(data)

    results=[]

    for item in cndata:
        unit_items=item.split('|')
        starting_ip=unit_items[3]
        num_ip=int(unit_items[4])

        imask=0xffffffff^(num_ip-1)
        #convert to string
        imask=hex(imask)[2:]
        mask=[0]*4
        mask[0]=imask[0:2]
        mask[1]=imask[2:4]
        mask[2]=imask[4:6]
        mask[3]=imask[6:8]

        #convert str to int
        mask=[ int(i,16 ) for i in mask]
        mask="%d.%d.%d.%d"%tuple(mask)

        #mask in *nix format
        mask2=32-int(math.log(num_ip,2))

        results.append((starting_ip,mask,mask2))

    return results

if __name__=='__main__':
    parser=argparse.ArgumentParser(description="Generate proxy auto-config rules.")
    parser.add_argument('-x', '--proxy',
                        dest = 'proxy',
                        default = 'SOCKS 127.0.0.1:8964',
                        nargs = '?',
                        help = "Proxy Server, examples: "
                               "SOCKS 127.0.0.1:8964; "
                               "SOCKS5 127.0.0.1:8964; "
                               "PROXY 127.0.0.1:8964")

    args = parser.parse_args()

    generate_pac(args.proxy)

我想,应该过不了多久就要解放了。期待着有那么一天:我们能一起呼吸自由的空气,我们不再需要折腾各种翻墙玩意。那时,生活应该会更美好一些吧。