2017-04-11 141 views
1

我正在使用Python 3.6.0b2。'utf-8'編解碼器無法編碼字符' udcc2':代理不允許

我解析了很多電子郵件。這個特定的電子郵件是一個問題,因爲我無法打印電子郵件地址的顯示名稱。試圖打印的電子郵件地址顯示名稱給出:

UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed 

下面是測試情況下一段代碼,顯示瞭如何重現該問題:

(venv3.6) [email protected]:/opt/mailripper$ cat test.py 
from email import policy 
from email.headerregistry import Address 
from email.parser import BytesHeaderParser, BytesParser 

email_bytes = b'From: =?utf-8?Q?John_Smith=2C_Prince2=C2=AE=2CPMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C_ITIL=C2=AE=2C_ISTQB=C2=AE?= <[email protected]>\r\n' 
msg = BytesHeaderParser(policy=policy.default).parsebytes(email_bytes) 
print(msg['from']) 
print(msg['from'].addresses[0].display_name) 

這裏是如由上面的代碼生成的錯誤:

(venv3.6) [email protected]:/opt/mailripper$ python test.py 
"John Smith, Prince2®,PMP®, CSM� �, ITIL®, ISTQB®" <[email protected]> 
Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    print(msg['from'].addresses[0].display_name) 
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed 

這裏是作爲OSX電子郵件客戶端,這似乎能夠解析就OK了(這是截圖,剪裁要小)顯示的顯示名稱:

enter image description here

我的目標是能夠處理沒有統一代碼錯誤的任何電子郵件,也無需編寫自定義的Unicode錯誤處理代碼 - 這可能嗎?

任何人都可以建議我可以做些什麼來避免顯示電子郵件地址顯示名稱時出現Unicode錯誤?

回答

3

這裏有一個棘手的問題。你的直接例子並不難:根據RFC 2047的規則,它是無效的。 email.parser模塊有理由拒絕它。但是,電子郵件充滿了根據規則無效的內容。電子郵件工具經常努力工作以挽救甚至是來自無效內容的東西。您想要您的工具處理無效內容嗎?

以下是您的示例無效的內容。我縮短了一點。它的相關部分讀取,

b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C?= <[email protected]>\r\n' 

這可能是最初的字符串:From: John, PMP®, CSM®, <[email protected]>

這是一個Python字節字符串,包含From:標頭,作爲編碼字。規格爲RFC 2047, MIME Part Three: Message Header Extensions for Non-ASCII Text

在該示例中,您會看到兩個序列,分別爲=?utf-8?Q??=RFC 2047, Section 2, "Syntax of encoded-words"告訴我們這些標記兩個編碼字的開始和結尾,並且它們使用UTF-8字符集和Quoted-Printable編碼。在「PMP」之後,有序列=C2=AE。這將編碼2個字節的UTF-8序列0xC2 0xAE,這是字符'®'。序列=2C對1個八位字節的UTF-8(和ASCII)序列0x2C進行編碼,這是字符','。

第一個?=和第二個=?utf-8?Q?之間的部分爲\r\n。這是字面的,不是根據RFC 2047編碼的。它是通過插入一行結尾和一個前導空白來延長長標題行。這也是非常合法的。

現在照顧「CSM」。注意有一個序列=C2,然後是第一個?=,它結束了第一個編碼字。在第二個=?utf-8?Q?開始第二個編碼字之後,有一個序列=AE。這是同樣的2個八位字節的UTF-8序列0xC2 0xAE,再次表示字符'®'。但是,UTF-8字符的兩個八位字節被分割爲相鄰的編碼字

這是違反RFC 2047, Section 5, "Use of encoded-words in message headers" *的規則。它說有:

每個「編碼字」必須代表一個整數個字符。
一個多字節字符不能在相鄰的'編碼字中分割。

輸入這兩種效果的要麼是有效的:

b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2=AE?=\r\n =?utf-8?Q?=2C?= <[email protected]>\r\n' 
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM?=\r\n =?utf-8?Q?=C2=AE=2C?= <[email protected]>\r\n' 

(這是因爲我讀的規範我沒有運行代碼來檢查。)

現在,你問兩個問題:

我的目標是能夠處理任何電子郵件沒有統一代碼錯誤,並沒有 編寫自定義的Unicode錯誤處理的C頌歌 - 這可能嗎?

我的建議是「否」。如果您想處理任何電子郵件,您需要準備好處理形成不正確的電子郵件。您將需要編寫自定義錯誤處理代碼 - 不僅針對Unicode問題,而且針對所有問題 - 以應對毫無疑問會被清除的奇怪內容。

任何人都可以建議我可以做些什麼來避免在顯示電子郵件地址顯示名稱時出現Unicode錯誤?

在這個例子中,我可以看到三種方法:

  1. 看看的classemail.policy.EmailPolicy(**kw),看看你能弄清楚如何擴展它來處理這類的編碼不正確的內容。您正在通過policyBytesHeaderParser(policy=policy.default).parsebytes(email_bytes)這個班級的親屬。

  2. 預處理所有標題行,查看連續的編碼字的末尾和字節處的字節。使用您自己的代碼修復它,然後將更正的標題輸入到BytesHeaderParser()。也許你可以寫一個regular expression這可以檢測到問題。

  3. 將你的電話打包到BytesHeaderParser()的異常處理程序中,該程序將僅在失敗的行中嘗試修復#2。固定線路後,您可以再次嘗試BytesHeaderParser()

還會有其他問題。考慮構建您的代碼,以便能夠適應越來越多的修復無效內容,因爲您發現您需要它們。

相關問題