Python3.0 の smtplib.py がそのままだと使えない
Python2.x だとうまくいくんだが、Python3.0 だとうまくいかないことがいくつか。
最終的な目標は
#!/usr/bin/env python3.0 import smtplib from email.mime.text import MIMEText from email.header import Header from email.utils import formatdate e = 'iso-2022-jp' m = MIMEText('日本語', 'plain', e) m['Subject'] = Header('あいう', e) m['To'] = 'to@example.com' m['From'] = 'from@gmail.com' m['Date'] = formatdate() s = smtplib.SMTP('smtp.gmail.com', 587) s.ehlo() s.starttls() s.ehlo() s.login('from@gmail.com', 'google-password') s.sendmail('from@gmail.com', 'to@example.com', m.as_string()) s.quit()
がちゃんと動くようになること。
まず、smtplib.SMTP.login が失敗して 502 Unrecognized command が返ってくる。
これは login 内で定義されている encode_plain で base64 エンコードしたときに、改行がついてしまうのが原因だ。
eol='' としてやることで、これを直すことができる。
さらに、encode_base64 *1 の第1引数は bytes 型なので encode() してやる必要がある。
SEE ALSO:
- http://bugs.python.org/issue5259
- http://bugs.python.org/issue5304
- http://www.python.org/doc/3.0/library/binascii.html#binascii.a2b_base64
次に、日本語を含めたメールを smtplib.SMTP.sendmail で送ろうとすると UnicodeEncodeError で失敗する。
sendmail の処理はだいたい以下のような流れになっている。
- mail で MAIL コマンドを送る
- rcpt で RCPT コマンドを送る
- data で DATA コマンドを送る
- quotedata で . のクオートと改行コードの CRLF 化をする
- send でそれを送る
ここで問題なのは、 send は引数が str のインスタンスの場合、ascii 決め打ちで encode している点。
send の引数が str のインスタンスでない場合はそのまま送られるため、sendmail の引数をあらかじめ encode('iso-2022-jp') しておけばいいように思えるが、そうすると今度は quotedata で re.sub を使っており、re.sub の第3引数は str でなければならないため、bytes のインスタンスだと TypeError になってしまう。
なんというか設計が間違っているような気がしないでもない。
とりあえず日本語環境で動かすなら、iso-2022-jp 決め打ちでいける気がするので、そのように変更する。
SEE ALSO:
最終的に、smtplib.py はこのようになった。
--- smtplib.py.bak 2009-03-30 20:32:10.000000000 +0900 +++ smtplib.py 2009-03-30 21:18:20.000000000 +0900 @@ -302,7 +302,7 @@ if self.debuglevel > 0: print('send:', repr(s), file=stderr) if hasattr(self, 'sock') and self.sock: if isinstance(s, str): - s = s.encode("ascii") + s = s.encode("iso-2022-jp") try: self.sock.sendall(s) except socket.error: @@ -542,7 +542,7 @@ return encode_base64(response) def encode_plain(user, password): - return encode_base64("\0%s\0%s" % (user, password)) + return encode_base64("\0{0}\0{1}".format(user, password).encode(), eol='') AUTH_PLAIN = "PLAIN"
encode_base64 のところでは、ついでに format を使うように変更してある。