mail()関数に渡す本文の改行コードはCRLF?LF?

  • 11
    Like
  • 0
    Comment
More than 1 year has passed since last update.

疑問に思ったきっかけ

Phalcon とメールとテンプレートと

PHP BLTというイベントでの発表の一つ
大枠はPhalconの話であるが、メール送信時の改行コードの扱い方についての話があった。

以前、mail()関数を使ってメール送信で問題が起こり、同じような方法で回避した覚えがある。
気になったので調べてみようと思った。

まずはマニュアルを見てみる

PHP: mail - Manual

bool mail ( string $to , string $subject , string $message [, string $additional_headers [, string $additional_parameters ]] )

message

送信するメッセージ。

改行コードは CRLF (\r\n) となります。各行の長さは 70 文字を超えては いけません。

additional_headers(オプション)

メールヘッダの最後に挿入される文字列。

通常、これは追加のヘッダ(From、Cc、Bcc)のために用いられます。 複数のヘッダを追加する場合は CRLF(\r\n)で区切ります。 外部からのデータを用いてヘッダを組み立てる際には、想定外のヘッダが注入されることを防ぐための処理が必要です。

このように、ヘッダも本文もCRLFで改行をするようにと書いてある。

実装を見てみる

PHP5系と7.0.0とで大きな違いはないため、7.0.0のソースを見てみます
mail.c at php-7.0.0

sendmailコマンドのプロセスを立て、それに対してヘッダ・本文の情報を出力している(399行目から)

part-of-mail.c
        fprintf(sendmail, "To: %s\n", to);
        fprintf(sendmail, "Subject: %s\n", subject);
        if (hdr != NULL) {
            fprintf(sendmail, "%s\n", hdr);
        }
        fprintf(sendmail, "\n%s\n", message);
        ret = pclose(sendmail);

To や Subject、additional_headersの後、messageの前後の改行はLFである。
つまり複数のadditional_headersや複数行のmessageの改行をCRLFにすると、LFのみとCRLFが混ざった文字列をsendmailコマンドに渡すことになる。

実践

LFのみで改行した場合、CRLFで改行した場合の2通りを試してみた。

検証環境

PHP : 5.5.29
MTA : Postfix 2.6.6

LFのみで改行

mail-lf.php
<?php
mail(
    'to@example.com',
    'Test Subject',
    "row1\nrow2\nrow3",
    "From: from@example.com\nX-Mailer: hoge mail",
);
To: to@example.com
Subject: Test Subject
X-PHP-Originating-Script: 5000:mail-lf.php
From: from@example.com
X-Mailer: hoge mail

row1
row2
row3

LFのみで改行して、特に問題は見られない

CRLFで改行

mail-crlf.php
<?php
mail(
    'to@example.com',
    'Test Subject',
    "row1\r\nrow2\r\nrow3",
    "From: from@example.com\r\nX-Mailer: hoge mail",
);
To: to@example.com
Subject: Test Subject
X-PHP-Originating-Script: 5000:mail-crlf.php
From: from@example.com
X-Mailer: hoge mail

row1

row2

row3

CRLFで改行をすると、本文に余計な改行が入る。
ヘッダは問題なし

LFのみで改行するほうが正しいのでは?

考察とまとめ

マニュアルには次のような記述がある。

注意:

メッセージが受信されなかった場合には、LF(\n)のみを使ってみてください。 Unix の MTA の中には、自動的に LF を CRLF に変換してしまう もの (有名なところでは、» qmail など) があります(もし CRLF を利用していた場合、CR が重複してしまいます)。 ただし、これは最後の手段です。というのも、これは » RFC 2822 に違反しているからです。

MTAによって、改行コードを変えるなどの対応が必要な場合があるらしい。
今回、Postfixでしか検証をしていない。他のMTA、例えばSendmailであれば結果は違うかも?
要検証

sendmailコマンドを使わずに、PHPからSMTPのリクエストを送信するような場合にはCRLFで改行する必要はあると思う。
sendmailコマンドを使う(mail()関数を使う)場合には、改行が意図した通りになされているか確認したほうが良い。

もっと時間のある時に、詳細に調べたい。