CommentToMail代码分析与调试
CommentToMail 是typecho的一个基于 PHPMailer 的评论通知插件, 本文讨论基于1.2.3
===========================
一. 使用PHPMailer 发送邮件:
PHPMailer 包含3个文件:
class.smtp.php 发送邮件用的,php socket 实现smtp协议
class.pop3.php 接受邮件用的
class.phpmailer.php PHPMailer类
有3种邮件发送模式: smtp, mail, sendmail.
从 class.phpmailer.php 文件中
359
/**
360
* Sets Mailer to send message using SMTP.
361
* @return void
362
*/
363
public function IsSMTP() {
364
$this->Mailer = 'smtp';
365
}
366
367
/**
368
* Sets Mailer to send message using PHP mail() function.
369
* @return void
370
*/
371
public function IsMail() {
372
$this->Mailer = 'mail';
373
}
374
375
/**
376
* Sets Mailer to send message using the $Sendmail program.
377
* @return void
378
*/
379
public function IsSendmail() {
380
if (!stristr(ini_get('sendmail_path'), 'sendmail')) {
381
$this->Sendmail = '/var/qmail/bin/sendmail';
382
}
383
$this->Mailer = 'sendmail';
384
}
385
386
/**
387
* Sets Mailer to send message using the qmail MTA.
388
* @return void
389
*/
390
public function IsQmail() {
391
if (stristr(ini_get('sendmail_path'), 'qmail')) {
392
$this->Sendmail = '/var/qmail/bin/sendmail';
393
}
394
$this->Mailer = 'sendmail';
395
}
可知:
smtp 模式最为常用,直接配置好smtp服务器相关参数,就可以发送邮件了
mail 模式调用php自带mail函数来发送邮件,需要运行环境有邮件服务器.
sendmail 模式调用外部二进制程序(sendmail 或 qmail 或php.ini 中 sendmail_path指定)来发送邮件,据
/var/qmail/bin/sendmail 和 /var/qmail/bin/sendmail 可以推测,这个功能一般是在linux服务器上用的,
win服务器就不瞎琢磨了.
windows服务器,又不带邮件服务,综上,只有 smtp模式最靠谱了.
常用smtp邮件服务器:
website ssl host port user pass
mail.yeah.net false smtp.yeah.net 25 user=xxx@yeah.net(或xxx) ***
mail.163.com false smtp.163.com 25 user=xxx@163.com(或xxx) ***
exmail.qq.com false smtp.exmail.qq.com 25 user=xxx@yourdomain ***
(ssl的我没写,因为我测试没成功- -!!)
首先直接写一段代码调用PHPMailer发送邮件,以确定参数配置正确、"最小系统"正常工作.
本地测试正常,拿到服务器发现
1. PHP Warning: set_time_limit() has been disabled for security reasons,
这句意义不是很大,直接@或去掉.
2. SMTP Error: Could not connect to SMTP host.
发现是服务器禁用 fsockopen 所致, 将 class.smtp.php 中 @fsockopen 替换为 @pfscokopen (还好服务器没有禁用这个),发现可以正常发邮件了.
注: CommentToMail 1.2.3 附带的 class.smtp.php 里面就是@fsockopen,遇到此错误可以尝试此法.
还有 Plugin.php 中 SendMail() 中也用到了 fsockopen.
===========================
二. CommentToMail 代码分析:
①何时发送邮件
CommentToMail 插件 添加 finishComment 回调函数, CommentToMail_Plugin::toMail()
即 评论完成时 调用 toMail() 这个函数.
②如何发送邮件
toMail() 这个函数实现的功能就是 根据本条评论的信息 生成邮件信息(发送给谁,内容是什么,等)
并调用 SendMail() 来发送邮件, 之间数据通过临时文件(明白了cache目录的用处了)(gzdeflate,serialize) 和get来传递.
③SendMail() 做了什么?
它通过fsockopen GET 方式把包含邮件内容的临时文件的名字 传递给 /CommentToMail/send_mail.php
④send_mail.php 文件做了什么?
它通过替换 邮件模板 中关键词,产生邮件内容, 并创建 PHPMailer 实例,调用其 Send() 方法把邮件发送出去.
⑤PHPMailer
通过 socket 构造stmp协议,发出邮件.
===========================
三. CommentToMail 调试
因为评论完成时通过get方式调用 send_mail.php 执行, 所以客户端看不到 send_mail.php 的执行结果,不便调试,了解了其原理,可以 注释掉 send_mail.php 中 @unlink($file) 来保存临时文件以供分析和调试.
在typecho中完成一条评论,然后查看cache 目录内临时文件名称,
在 $smtp= unserialize(gzinflate(file_get_contents($file))); 后添加 print_r($smtp) 以查看临时文件内容
浏览器 访问 usr/plugins/CommentToMail/send_mail.php?mail=XXXX (其中XXXX=base64_encode($filename))
又发现服务器gzinflate被禁用- -!!.
于是干脆去掉gzdeflate,即
把 Plugin.php 中
file_put_contents('./usr/plugins/CommentToMail/cache/'.$filename, gzdeflate(serialize($smtp))); 改为
file_put_contents('./usr/plugins/CommentToMail/cache/'.$filename, serialize($smtp));
把 send_mail.php 中
$smtp= unserialize(gzinflate(file_get_contents($file))); 改为
$smtp= unserialize(file_get_contents($file));
再次在typecho中评论,产生新的临时文件,并在浏览器中访问 send_mail.php?mail=XXXX
发现可以正常读到 临时文件内容了,检查参数配置无误,邮件也发送成功了.
然后又发现当同时发送给被评论者和文章作者时, 邮件发送又不灵了, 先发的邮件总是能够成功,后法的一个总是失败.
调换 send_mail.php 中 向博主发信,向访客发信 的先后次序,仍是后一个失败.
开始想是不是因为服务器禁用 set_time_limit, 后面那个邮件发送超时了?
在send_mail.php 中各处添加时间检查,输出结果如下:
--------------------------
0.000: 将要载入phpmailer.
0.007: 载入phpmailer完毕.
0.009: 将要向访客发信
2012-04-04 18:51:02 向 yyyyy@163.com 发送邮件成功!
0.715: 向访客发信完毕
0.716: 将要向博主发信
SMTP Error: Could not authenticate.
2012-04-04 18:52:02 向 aaaaa@yurenchen.com 发送邮件错误: SMTP Error: Could not authenticate.
60.887: 向博主发信完毕
--------------------------
可以看到第一封邮件基本是秒发出去的,第二封消耗了60秒时间,并最终发送失败.
于是看了下PHPMailer 自带的smtp发送示例 test_db_smtp_basic.php , 大致流程是
new PHPMailer();
设置smtp 参数;
while(){
设置邮件参数;
Send();
ClearAddresses();
ClearAttachments();
}
再对比 send_mail.php, 发现
每次在send函数中创建 PHPMailer 实例, 并用$mail=NULL;销毁实例(插件作者大概也是用惯了JavaScript - -!!),
于是找找 PHPMailer 类的析构方法,发现没有.
于是把 $mail = new PHPMailer(); 拿到 send 函数外面,
并在 send 函数结尾添加
$mail->ClearAddresses();
$mail->ClearAttachments();
以清除接收邮箱地址 和 附件.
再次在浏览器访问 send_mail.php?mail=XXXX
输出结果:
------------------------------
0.000: 将要载入phpmailer.
0.008: 载入phpmailer完毕.
0.009: 将要向访客发信
2012-04-04 19:14:24 向 yyyyy@163.com 发送邮件成功!
0.588: 向访客发信完毕
0.588: 将要向博主发信
2012-04-04 19:14:24 向 aaaaa@yurenchen.com 发送邮件成功!
0.944: 向博主发信完毕
-------------------------------
直接秒发了,内牛满面啊 (T_T)
原来 send_mail.php 中这句 set_time_limit(0); 是这么悲剧来的啊
===========================
四. 关于CommentToMail 1.2.4
相对于1.2.3 的改动主要有以下几点,不过似乎引入的问题比解决的问题还多,但依然感谢带给我们CommentToMail插件的DEFE.
① 在 class.smtp.php 和 Plugin.php 中添加了 fsockopen 容错,
fsockopen 失败则尝试 pfsockopen,
再失败则尝试 stream_socket_client,
再失败 就要报错了:Failed to connect to server
② class.phpmailer.php中 IsSMTP 函数中 'smtp' 修改成了 'SMTP', 这一招似乎是江湖传言,
查看 class.phpmailer.php 代码:
571
// Choose the mailer and send through it
572
switch($this->Mailer) {
573
case 'sendmail':
574
return $this->SendmailSend($header, $body);
575
case 'smtp':
576
return $this->SmtpSend($header, $body);
577
default:
578
return $this->MailSend($header, $body);
579
}
产生的影响就是switch分支总是default,
就是PHPMailer总是用 mail 模式发信,这个在大部分服务器上应该都不能用.
③ send_mail.php 中 引入 class.phpmailer.php 的操作放到了 send 函数中,
不是一上来就载入class.phpmailer.php 文件,而是等到需要时才载入,来提高执行效率.
但是用的是 require 包含文件,当同时发送邮件给博主和被评论者时,两次调用send函数必然报错:
PHP Fatal error: Cannot redeclare class phpmailerException
④ 临时文件动态加密解密函数 mcode 疑似有问题, 没有仔细看加密算法,
直接去掉加密解密过程就可以用,加上则解密时失败.
仍然延续的问题:
① PHPMailer 实例化仍在 send 函数中进行, 导致发送第二封邮件时失败.
好吧,上传了调试后的代码: CommentToMail.rar
转载须注明出处: http://www.yurenchen.com/14.htm
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
香菇,蓝瘦,拾掇这个插件花了一天时间仍然不行,SMTP链接老是不成功