YAML 的地雷

因為碰 SaltStack,而官方建議用的格式是 YAML (雖然也支援 JSON,但文件幾乎都是 YAML),所以被迫要學一堆奇怪的 YAML hack,在官方文件上甚至寫了一篇「YAML Idiosyncrasies」讓大家參考,用 Idiosyncrasies 這個詞彙比較中性,但需要專文來寫就可以想像 YAML 有多 !@#$%^...

然後文章裡面也發現 SaltStack 在亂搞,於是就快起笑了...

首先是建議 indent 為 2 spaces,另外禁用 tab,這些到是沒什麼好抱怨的。但 dict 的設計就讓人崩潰,像是這樣的結構:

foo:
  - bar:
    baz1: abc
    baz2: def

你以為對應的 JSON 是:

{
  "foo": {
    "bar": {
      "baz1": "abc",
      "baz2": "def"
    }
  }
}

但實際對應的 JSON 中,bar、baz1、baz2 視同一層:

{
  "foo": {
    "bar": null,
    "baz1": "abc",
    "baz2": "def"
  }
}

因為其實對應的 YAML 是:

foo:
  - bar:
  - baz1: abc
  - baz2: def

你就不能把最上面的 YAML 定義成 syntax error 嗎... =_=

接下是 SaltStack 的惡搞時間,因為 YAML parser 會把 644 當作數字傳進去,所以這樣的設定:

/etc/vimrc:
  file:
    - managed
    - source: salt://edit/vimrc
    - mode: 644

SaltStack 會收到 644 (十進位),而如果你寫成 0644 時,就會被讀成八進位,也就是 420 (十進位):

/etc/vimrc:
  file:
    - managed
    - source: salt://edit/vimrc
    - mode: 0644

我覺得後面這個是比較正確的寫法,所以應該要會動,但 SaltStack 對這部份 workaround,會變成 chmod 420 /etc/vimrc,然後就噴飯了...

另外 2013_01_12 這種字串會被解讀成 20130102 (十進位),這會不會太歡樂...

反正用下去後應該會再踩更多地雷,繼續看下去吧...

JSON Patch...

與上篇一樣,都是在「Please. Don't Patch Like An Idiot.」這篇裡看到的:「JavaScript Object Notation (JSON) Patch」(RFC 6902)。

配合 JSON Pointer (RFC 6901) 的語法,下面的操作很清楚表示了想要做什麼:

[
    { "op": "test", "path": "/a/b/c", "value": "foo" },
    { "op": "remove", "path": "/a/b/c" },
    { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
    { "op": "replace", "path": "/a/b/c", "value": 42 },
    { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
    { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]

再加上 HTTP header 裡的 If-Match 可以用來處理起始版本,test 也可以阻擋某些異常狀況,基本的都有了?接下來應該找 Canonical JSON 的方案,這樣可以直接拿 hash 的值當版本?

在 JSON 裡的 XPath...

在「Please. Don't Patch Like An Idiot.」這篇文章裡看到「JavaScript Object Notation (JSON) Pointer」(RFC 6901)。

類似 XML 的 XPath,JSON Pointer 可以查詢 JSON object。

比較特別是拿 ~ 這個符號當特殊字元,原本的 ~ 變成 ~0,而 / 變成 ~1,所以這個 JSON object:(取自 RFC 內的範例)

{
      "foo": ["bar", "baz"],
      "": 0,
      "a/b": 1,
      "c%d": 2,
      "e^f": 3,
      "g|h": 4,
      "i\\j": 5,
      "k\"l": 6,
      " ": 7,
      "m~n": 8
   }

使用這些 JSON pointer 會得到後面這些結果:

    ""           // the whole document
    "/foo"       ["bar", "baz"]
    "/foo/0"     "bar"
    "/"          0
    "/a~1b"      1
    "/c%d"       2
    "/e^f"       3
    "/g|h"       4
    "/i\\j"      5
    "/k\"l"      6
    "/ "         7
    "/m~0n"      8

規格還蠻簡單的...

在 HTML 內嵌 JSON object 時要注意的事情...

有時候我們會因為效能問題,在 HTML 內嵌入 JSON object,而不是再多一個 HTTP request 取得。

但「嵌入」的行為如果沒有處理好,就產生非常多 XSS attack vector 可以玩。

首先最常犯的錯誤是使用錯誤的 escape function:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<?= addslashes($str) ?>";
</script>
</body>
</html>

這樣可以用 </script><script>alert(1);// 攻擊 $str。因為 addslashes() 並不會過濾到這個字串,而產生這樣的 HTML:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "</script><script>alert(1);//";
</script>
</body>
</html>

而這個字串會造成 DOM parser 解讀上產生不是我們預期的行為:

可以看到在字串裡面的 </script> 被拆開了。

這是因為瀏覽器會先拆解產生 DOM tree,再把 <script></script> 內的程式碼交給 JavaScript engine 處理。所以在一開始產生 DOM tree 的時候,是看不懂 JavaScript 程式邏輯的...

正確的方法是用 json_encode() 處理,因為 PHPjson_encode() 預設會把 / (slash) 變成 \/ (這是 JSON spec 裡合法的轉換):

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = <?= json_encode($str) ?>;
</script>
</body>
</html>

這會產生出:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<\/script><script>alert(1);//";
</script>
</body>
</html>

但上面這段 HTML 與 PHP code 仍然有問題,如果 $str<!--<script 時,你會發現 DOM 又爛掉了:

<!DOCTYPE HTML>
<html>
<body>
<script>
var a = "<!--<script>";
</script>
</body>
</html>

escape.alf.nu 的 Level 15 就是利用這個問題,再加上其他的漏洞而完成 XSS 攻擊。

為了這個問題去 StackOverflow 上問:「Why does <!--<script> cause a DOM tree break on the browser?」,才又發現上面這段 code 並不是合法的 HTML5 (先不管 head & title 的部份,補上後仍然不是合法的 HTML5)。

原因在於 DOM parser 對 <script></script> 的特殊處理:「4.3.1.2 Restrictions for contents of script elements」。(話說這段 ABNF 差點讓我翻桌...)

解法是在 <script></script> 的開頭與結尾加上 HTML 註解:(這剛好是 HTML 4.01 建議的方法)

<!DOCTYPE HTML>
<html>
<body>
<script>
<!--
var a = "<!--<script>";
-->
</script>
</body>
</html>

那段 ABNF 的目的是希望可以盡可能往後找到 --></script> 結尾的地方。

當然你也可以用 json_encode()JSON_HEX_TAG<> 硬轉成 \u003c\u003e 避開這個問題,但這使得呼叫 json_encode() 時要多一個參數 (而非預設參數),用起來比較卡...

這個問題會變得這麼討厭,是因為 DOM parser 與 JavaScript 語法之間有各自的處理方式,然後又有些 pattern 是之前的 spec 遺留下來的包袱 (像是 HTML 4.01 在「18.3.2 Hiding script data from user agents」裡有提到用 <!----> 包裝 <script></script>),變成在設計 HTML5 時都要考慮進去相容...

之前會習慣用 <!--//--> 包裝 <script></script> 倒不是這個原因,而是因為不這樣做的話,jQuery 在 IE 使用 html() 時遇到有 <script></script> 的字串會爛掉,所以後來寫的時候變成習慣了...

反而因為這個習慣而避開了這個問題...

超難搞啊...

拿 Google Spreadsheet 當作 JSON backend...

在「Use a Google Spreadsheet as your JSON backend」這篇看到的,整篇的重點是這個 url:

https://spreadsheets.google.com/feeds/list/PUT-KEY-HERE/od6/public/values?alt=json

中間的 PUT-KEY-HERE 是文件的 key。當然,文件本身要公開發佈出去才能夠過 JSON 取得資料。

另外原文章裡面也有提供 JSONP 的方式可以使用,還蠻有趣的方式...

WebFinger 協定

Finger 是個 1977 年發展的協定 (RFC 742 - NAME/FINGER,以及 1991 年的 RFC 1288 - The Finger User Information Protocol),現在幾乎廢棄不用了...

2013 年,基於 OpenID 協定的 WebFinger 出現了!而且是進入 Standards Track 狀態了:「WebFinger is now RFC 7033!」。

用了 OpenID 基礎以及 JSON 格式... 看起來 blog 可以先支援?至於其他的東西就還要再想想...

PHP 5.4 的 json_encode 增加 JSON_UNESCAPED_SLASHES...

剛剛翻資料發現 json_encode 奇怪的老問題總算有解...

這樣的程式碼:

<?php
echo json_encode("http://www.google.com/"), "\n";

會輸出這樣的結果:

"http:\/\/www.google.com\/"

這是合法的 JSON 沒錯 (JSON 規格允許 string 裡面使用 \/),但看起來就很不爽啊,明明 / 就是可以合法輸出的字元... 然後剛剛看到 PHP 5.4.0 加上這些東西:

JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, and JSON_UNESCAPED_UNICODE options were added.

測了 JSON_UNESCAPED_SLASHES,看起來舒服多了:

<?php
echo json_encode("http://www.google.com/", JSON_UNESCAPED_SLASHES), "\n";

輸出結果是:

"http://www.google.com/"

PostgreSQL 9.3...

前幾天 PostgreSQL 宣佈 9.3 發佈:「PostgreSQL 9.3 released!」。

如同預測的,9.3 對 JSON 的處理能力變強了,加上可以自訂 index (寫 function 自訂 index 的內容),這使得 PostgreSQL 可以做 JSON-based document database,可以參考 9.3 的「PostgreSQL: Documentation: 9.3: JSON Functions and Operators」以及前一版 9.2 的「PostgreSQL: Documentation: 9.2: JSON Functions」。

MongoDB 就別來亂了... XD

紐約公共圖書館提供的 Library:將地圖 OCR 成向量資料...

紐約公共圖書館 (NYPL) 丟出個有趣的東西:「Map polygon and feature extractor」,敘述的地方就有這樣的說明:

Like OCR for maps

可以把這樣的地圖圖檔:

轉成:

這樣子... 也可以 GeoJSON 輸出 :p

這屬於 Open Data 的工作,紐約公共圖書館本身就是全世界第三大圖書館,美國第二大的圖書館 (僅次於第一的國會圖書館與第二的大英圖書館),做完後可以把館內的地圖館藏整個數據化讓人重複使用 (而非僅僅將紙本掃描成圖片資料的「電子化」),這包括了以前的手繪地圖啊...

程式主要是用 Python 寫,另外在 repository 有看到 RScheme 的存在... (GitHub 的統計)

諾貝爾獎的網站有 API 了...

在「Nobel Prize Gets Official API」看到諾貝爾獎的網站提供 API,讓人存取歷年諾貝爾獎得主的資訊了 XDDD

官方的新聞稿在「Open Data about the Nobel Prize now Available」這邊,API 資訊則在「Developer Zone」這邊。

舉例來說,http://api.nobelprize.org/v1/prize.json?year=1901 可以抓出 1901 年諾貝爾獎得主的資料 (JSON 格式)。

而除了 JSON API 外,另外還在 data.nobelprize.org 提供 Linked Data 格式的資源。

資料不多,但蠻有趣的...