GitHub 上的 GeoJSON 資料將會使用 OpenStreetMap 的資料呈現

兩個禮拜前 GitHub 公佈的消息,透過 OpenStreetMap 的圖資,來強化 GitHub 上 GeoJSON 的呈現:「Improving map data on GitHub」。

官方給了個 GIF 動畫展示說明:

Google 的 Jsonnet

GoogleJSON 延伸發展出來的描述性語言,Jsonnet:「Jsonnet: a more elegant language for composing JSON」。

// Jsonnet Example
{
    person1: {
        name: "Alice",
        welcome: "Hello " + self.name + "!",
    },
    person2: self.person1 { name: "Bob" },
}

所對應的 JSON 會是:

{
   "person1": {
      "name": "Alice",
      "welcome": "Hello Alice!"
   },
   "person2": {
      "name": "Bob",
      "welcome": "Hello Bob!"
   }
}

可以透過 self 與運算做不少事情。不知道拿來當設定檔如何...

看日期推出一陣子了,去年八月就放出來了...

用 jq 操作 JSON 文件

jq 用一陣子了 (因為配合著 AWS 官方的 AWS Command Line Interface 一起用),剛好看到有人介紹:「jq is sed for JSON」。

現在 Ubuntu 的 package repository 內都有 jq 了 (Ubuntu – Package Search Results -- jq),雖然版本舊了一點,不過基本功能都穩定了,除非有特別的需求,不然應該夠用。

照原文章的範例,假設 1.json 是這樣:

[
  {
    "type": "message",
    "user": "U024HFHU5",
    "text": "hey there",
    "ts": "1385407681.000003"
  },
  {
    "type": "message",
    "user": "U024HGJ4E",
    "text": "right back at you",
    "ts": "1385407706.000006"
  }
]

而你下這樣的 command 就可以抽出來:

$ jq "[.[] | { the_user: .user, the_text: .text }]" 1.json
[
  {
    "the_user": "U024HFHU5",
    "the_text": "hey there"
  },
  {
    "the_user": "U024HGJ4E",
    "the_text": "right back at you"
  }
]

很好用的工具 :o

Hjson:the Human JSON

前幾天看到「Hjson, the Human JSON」這東西,想要在 JSON 上面提出拓展,讓人更好維護。

有幾個設計是大家已經想很久了。

首先是允許註解:

{
  # specify rate in requests/second
  "rate": 1000
}

再來是允許 ending trailing comma,這點在新的 JavaScript Engine 裡面是允許的,但在 JSON 規格裡是不允許的,對於 copy-paste 時就得很小心有沒有中獎:

{
  one: 1,
  two: 2,
}

另外幾個特點就還好。

object 的 key 沒有特殊情況時可以省略 double quote:

{
  key: "value"
}

甚至 value 是 single line 時也可以省略:

{
  text: look ma, no quotes!
}

而且當沒有 double quote 時不需要處理 escape 問題:

{
  path: c:\windows
  inject: <div class="important"></div>

  # escapes work inside quotes
  escape: "c:\\windows"
}

然後逗點可以省略,給的範例也突顯出對腦袋不直覺的問題 (ambiguous),這邊的 1 是 integer 還是 string?

{
  one: 1
  two: 2
}

多行,用 ''' 應該是借用了 Python 的想法?

{
  haiku:
    '''
    JSON I love you.
    But strangled is my data.
    This, so much better.
    '''
}

規格後面有提到 syntax,可以看到定義。

Hjson 算是一個開始吧,YAML 的設計需要極長的 training 時間才能正確使用,不知道 SaltStack 會不會有人馬上寫 adapter 出來接?(因為 SaltStack 已經可以接 JSON 與 YAML,只要有人把該接的接上去就可以了)

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 可以先支援?至於其他的東西就還要再想想...