CommentToMail代码分析与调试

 

CommentToMailtypecho的一个基于 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 文件中


  /**
   * Sets Mailer to send message using SMTP.
   * @return void
   */
  public function IsSMTP() {
    $this->Mailer = 'smtp';
  }

  /**
   * Sets Mailer to send message using PHP mail() function.
   * @return void
   */
  public function IsMail() {
    $this->Mailer = 'mail';
  }

  /**
   * Sets Mailer to send message using the $Sendmail program.
   * @return void
   */
  public function IsSendmail() {
    if (!stristr(ini_get('sendmail_path'), 'sendmail')) {
      $this->Sendmail = '/var/qmail/bin/sendmail';
    }
    $this->Mailer = 'sendmail';
  }

  /**
   * Sets Mailer to send message using the qmail MTA.
   * @return void
   */
  public function IsQmail() {
    if (stristr(ini_get('sendmail_path'), 'qmail')) {
      $this->Sendmail = '/var/qmail/bin/sendmail';
    }
    $this->Mailer = 'sendmail';
  }

可知:

smtp 模式最为常用,直接配置好smtp服务器相关参数,就可以发送邮件了

mail 模式调用php自带mail函数来发送邮件,需要运行环境有邮件服务器.

sendmail 模式调用外部二进制程序(sendmailqmail 或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.phpPlugin.php 中添加了 fsockopen 容错,

fsockopen 失败则尝试 pfsockopen,

再失败则尝试 stream_socket_client,

再失败 就要报错了:Failed to connect to server

 

② class.phpmailer.phpIsSMTP 函数中 'smtp' 修改成了 'SMTP', 这一招似乎是江湖传言,

查看 class.phpmailer.php 代码:


      // Choose the mailer and send through it
      switch($this->Mailer) {
        case 'sendmail':
          return $this->SendmailSend($header, $body);
        case 'smtp':
          return $this->SmtpSend($header, $body);
        default:
          return $this->MailSend($header, $body);
      }

产生的影响就是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


标签: typecho php

已有 14 条评论 »

  1. 你干脆把你解决的1.2.3完美版打包了让人下载哈!很多人都反应用不了,你来一个修订版打包下载吧!希望能打包发我一份,自己再去改一个个对比很麻烦!

    1. 上传了,先右键另存为吧,.rar设置了mime类型,但没什么反应

  2. 分析得很好,博主很厉害哦。我只是个菜鸟,能实现功能,但很多东西都不会,尤其是面向对象,学习了。

    1. 嘿~
      又来一个高手,大师谦虚了哦.
      我才是菜鸟.

    2. 测试哦 测试哦

      我在想留言单靠邮箱认证是不是很容易被冒充,
      要是有人故意捣乱的话...

      [注:本条是博主的测试哦 ]

      1. 留言这个东西并不需要严密的验证,冒充也不会造成太大的危害,而且貌似只有博主能看到邮箱吧

        1. 是哦,但是每在一个博客留一次,就泄露一次呢.

  3. 感谢博主这篇文章……之前一直发不了邮件原来是这个问题,另外,QQ的SMTP服务器SSL的端口如下:

    如何设置POP3/SMTP的SSL加密方式?

    如果您的电子邮件客户端支持SSL,可以在设置中选择使用SSL。

    使用SSL的通用配置如下:
    接收邮件服务器:pop.qq.com,使用SSL,端口号995
    发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587
    账户名:您的QQ邮箱账户名(如果您是VIP帐号或Foxmail帐号,账户名需要填写完整的邮件地址)
    密码:您的QQ邮箱密码
    电子邮件地址:您的QQ邮箱的完整邮件地址

    再次感谢博主的修正版


    1. 呵呵,能帮到大家真是太好了~

  4. 来膜拜下,呵呵~


    1. 新版的已经出来了,应该都修复了这几个问题.

      1. 恩,下载了,还没怎么测试,呵呵~

  5. 非常感谢博主,这个问题困扰很多天了。php不是很熟悉,改代码有困难的。再次感谢!

    1. (*^__^*) 嘻嘻……
      能给大家带来一点点便利甚是欣慰.

      话说我好久没碰php了呢.

添加新评论 »

贴图表情