當前位置:
首頁 > 知識 > YAML:可能並不是那麼完美

YAML:可能並不是那麼完美

YAML:可能並不是那麼完美


譯自: https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html

作者: Martin Tournoij

譯者: MjSeven

我之前寫過 為什麼將 JSON 用於人類可編輯的配置文件是一個壞主意 ,今天我們將討論 YAML 格式的一些常見問題。

默認情況下不安全

YAML 默認是不安全的。載入用戶提供的(不可信的)YAML 字元串需要仔細考慮。


!!python/object/apply:os.system

args: ["ls /"]

用 print(yaml.load(open("a.yaml"))) 運行它,應該給你這樣的東西:


bin etc lib lost+found opt root sbin tmp var sys

boot dev efi home lib64 mnt proc run srv usr

0

許多其他語言(包括 Ruby 和 PHP 1 )默認情況下也不安全(LCTT 譯註:這裡應該說的是解析 yaml)。 在 GitHub 上搜索 yaml.load 會得到驚人的 280 萬個結果,而 yaml.safe_load 只能得到 26000 個結果。

提個醒,很多這樣的 yaml.load() 都工作的很好,在配置文件中載入 yaml.load() 通常沒問題,因為它通常(雖然並不總是!)來自「可靠源」,而且很多都來自靜態的 YAML 測試文件。但是,人們還是不禁懷疑在這 280 萬個結果中隱藏了多少漏洞。

這不是一個理論問題。在 2013 年, 正是由於這個問題,所有的 Ruby on Rails 應用程序都被發現易受 遠程代碼執行攻擊。

有人可能會反駁說這不是 YAML 格式的錯誤,而是那些庫實現錯誤的的問題,但似乎大多數庫默認不是安全的(特別是動態語言),所以事實上這是 YAML 的一個問題。

有些人可能會反駁認為修復它就像用 safe_load() 替換 load() 一樣容易,但是很多人都沒有意識到這個問題,即使你知道它,它也是很容易忘記的事情之一。這是非常糟糕的 API 設計。


可能很難編輯,特別是對於大文件

YAML 文件可能很難編輯,隨著文件變大,這個難度會快速增大。

一個很好的例子是 Ruby on Rails 的本地化翻譯文件。例如:


en:

formtastic:

labels:

title: "Title" # Default global value

article:

body: "Article content"

post:

new:

title: "Choose a title..."

body: "Write something..."

edit:

title: "Edit title"

body: "Edit body"

看起來不錯,對吧?但是如果這個文件有 100 行怎麼辦?或者 1,000 行?在文件中很難看到 「where」,因為它可能在屏幕外。你需要向上滾動,但是你需要跟蹤縮進,即使遵循縮進指南也很難,特別是因為 2 個空格縮進是常態而且 製表符縮進被禁止 2

不小心縮進出錯通常不算錯誤,它通常只是反序列化為你不想要的東西。這樣只能祝你調試快樂!

我已經愉快地編寫 Python 長達十多年,所以我已經習慣了顯眼的空白,但有時候我仍在和 YAML 抗爭。在 Python 中,雖然沒有那種長達幾頁的函數,但數據或配置文件的長度沒有這種自然限制,這就帶來了缺點和損失了清晰度。

對於小文件,這不是問題,但它確實無法很好地擴展到較大的文件,特別是如果你以後想編輯它們的話。


這非常複雜

在瀏覽一個基本的例子時,YAML 看似「簡單」和「顯而易見」,但事實證明並非如此。 YAML 規範 有 23449 個單詞,為了比較, TOML 有 3339 個單詞, Json 有 1969 個單詞, XML 有 20603 個單詞。

我們中有誰讀過全部規範嗎?有誰讀過並理解了全部?誰閱讀過,理解進而記住所有這些?

例如,你知道 在 YAML 中編寫多行字元串有 9 種方法 嗎?並且它們具有細微的不同行為。

是的 :-/

如果你看一下 那篇文章的修訂歷史 ,它就會變得更加有趣,因為文章的作者發現了越來越多的方法可以實現這一點,以及更多的細微之處。

它從預覽開始告訴我們 YAML 規範,它表明(強調我的):


本節簡要介紹了 YAML 的表達能力。預計初次閱讀的人不可能理解所有的例子。相反,這些選擇用作該規範其餘部分的動機。

令人驚訝的行為

以下會解析成什麼( Colm O』Connor 提供的例子):


- Don Corleone: Do you have faith in my judgment?

- Clemenza: Yes

- Don Corleone: Do I have your loyalty?

結果為:


[

{"Don Corleone": "Do you have faith in my judgment?"},

{"Clemenza": True},

{"Don Corleone": "Do I have your loyalty?"}

]

那麼這個呢:


python: 3.5.3

postgres: 9.3

3.5.3 被識別為字元串,但 9.3 被識別為數字而不是字元串:


{"python": "3.5.3", "postgres": 9.3}

這個呢:


Effenaar: Eindhoven

013: Tilburg

013 是 蒂爾堡(Tilburg)()的一個流行音樂場地,但 YAML 會告訴你錯誤答案,因為它被解析為八進位數字:


{11: "Tilburg", "Effenaar": "Eindhoven"}

所有這一切,以及更多,就是為什麼許多經驗豐富的 YAMLer 經常會將所有字元串用引號引起來的原因,即使它不是嚴格要求。許多人不使用引號,而且很容易忘記,特別是如果文件的其餘部分(可能由其他人編寫)不使用引號。


它不方便

因為它太複雜了,它所聲稱的可移植性被誇大了。例如,考慮以下這個從 YAML 規範中獲取的示例:

? - Detroit Tigers

- Chicago cubs

:

- 2001-07-23

? [ New York Yankees,

Atlanta Braves ]

: [ 2001-07-02, 2001-08-12,

2001-08-14 ]

拋開大多數讀者可能甚至不知道這是在做什麼之外,請嘗試使用 PyYAML 在 Python 中解析它:


yaml.constructor.ConstructorError: while constructing a mapping

in "a.yaml", line 1, column 1

found unhashable key

in "a.yaml", line 1, column 3

在 Ruby 中,它可以工作:


{

["Detroit Tigers", "Chicago cubs"] => [

#<Date: 2001-07-23 ((2452114j,0s,0n),+0s,2299161j)>

],

["New York Yankees", "Atlanta Braves"] => [

#<Date: 2001-07-02 ((2452093j,0s,0n),+0s,2299161j)>,

#<Date: 2001-08-12 ((2452134j,0s,0n),+0s,2299161j)>,

#<Date: 2001-08-14 ((2452136j,0s,0n),+0s,2299161j)>

]

}

這個原因是你不能在 Python 中使用列表作為一個字典的鍵:


>>> {["a"]: "zxc"}

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: unhashable type: "list"

而這種限制並不是 Python 特有的,PHP、JavaScript 和 Go 等常用語言都有此限制。

因此,在 YAML 文件中使用這種語法,你將無法在大多數語言中解析它。

這是另一個從 YAML 規範的示例部分中獲取的:


# Ranking of 1998 home runs

---

- Mark McGwire

- Sammy Sosa

- Ken Griffey

# Team ranking

---

- Chicago Cubs

- St Louis Cardinals

Python 會輸出:


yaml.composer.ComposerError: expected a single document in the stream

in "a.yaml", line 3, column 1

but found another document

in "a.yaml", line 8, column 1

然而 Ruby 輸出:


["Mark McGwire", "Sammy Sosa", "Ken Griffey"]

原因是單個文件中有多個 YAML 文檔(--- 意味開始一個新文檔)。在 Python 中,有一個 load_all 函數來解析所有文檔,而 Ruby 的 load() 只是載入第一個文檔,據我所知,它沒有辦法載入多個文檔。

在實現之間存在很多不兼容 。


目標實現了嗎?

規範說明:


YAML 的設計目標是降低優先順序:

  • YAML 很容易被人類閱讀。
  • YAML 數據在編程語言之間是可移植的。
  • YAML 匹配敏捷語言的原生數據結構。
  • YAML 有一個一致的模型來支持通用工具。
  • YAML 支持一次性處理。
  • YAML 具有表現力和可擴展性。
  • YAML 易於實現和使用。

那麼它做的如何呢?


YAML 很容易被人類閱讀。

只有堅持一小部分子集時才有效。完整的規則集很複雜 —— 遠遠超過 XML 或 JSON。


YAML 數據在編程語言之間是可移植的。

事實並非如此,因為創建常見語言不支持的結構太容易了。


YAML 匹配敏捷語言的原生數據結構。

參見上面。另外,為什麼只支持敏捷(或動態)語言?其他語言呢?


YAML 有一個一致的模型來支持通用工具。

我甚至不確定這意味著什麼,我找不到任何詳細說明。


YAML 支持一次性處理。

這點我接受。


YAML 具有表現力和可擴展性。

嗯,是的,但它太富有表現力(例如太複雜)。


YAML 易於實現和使用。

$ cat `ls -1 ~/gocode/src/github.com/go-yaml/yaml/*.go | grep -v _test` | wc -l

9247

$ cat /usr/lib/python3.5/site-packages/yaml/*.py | wc -l

5713

結論

不要誤解我的意思,並不是說 YAML 很糟糕 —— 它肯定不像 使用 JSON 那麼多的問題 —— 但它也不是非常好。有一些一開始並不明顯的缺點和驚喜,還有許多更好的替代品,如 TOML 和其他更專業的格式。

就個人而言,當我有選擇時,我不太可能再次使用它。

如果你必須使用 YAML,那麼我建議你使用 StrictYAML ,它會刪除一些(雖然不是全部)比較麻煩的部分。


反饋

你可以發送電子郵件至 martin@arp242.net 或 創建 GitHub issue 以獲取反饋、問題等。


腳註

  1. 在 PHP 中你需要修改一個 INI 設置來獲得安全的行為,不能只是調用像 yaml_safe() 這樣的東西。PHP 想盡辦法讓愚蠢的東西越發愚蠢。幹得漂亮! ?
  2. 不要在這裡做空格與製表符之爭,如果這裡可以用製表符的話,我可以(臨時)增加製表符寬度來使它更易讀——這是製表符的一種用途。 ?

via: https://arp242.net/weblog/yaml_probably_not_so_great_after_all.html

作者: Martin Tournoij 選題: lujun9972 譯者: MjSeven 校對: wxy

本文由 LCTT 原創編譯, Linux中國 榮譽推出


點擊「了解更多」可訪問文內鏈接

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Linux技術 的精彩文章:

我的個人電子郵件系統設置:notmuch、mbsync、Postfix和 dovecot
使用 gorilla/mux 進行 HTTP 請求路由和驗證

TAG:Linux技術 |