Files
selima-perl/lib/php/monica/mail.inc.php
2026-03-10 21:31:43 +08:00

1342 lines
44 KiB
PHP

<?php
// File name: mail.inc.php
// Description: PHP class to compose an RFC 822 e-mail message
// Date: 2002-06-29
// Author: imacat <imacat@pristine.com.tw>
// Copyright: Copyright (C) 2002-2009 Pristine Communications
// Set the include path
if (!defined("INCPATH_SET")) {
require_once dirname(__FILE__) . "/incpath.inc.php";
}
// Referenced subroutines
require_once "monica/cgiemu.inc.php";
require_once "monica/curtime.inc.php";
require_once "monica/errhndl.inc.php";
require_once "monica/geoip.inc.php";
require_once "monica/getlang.inc.php";
require_once "monica/guest.inc.php";
require_once "monica/https.inc.php";
require_once "monica/lninfo.inc.php";
require_once "monica/login.inc.php";
require_once "monica/md5.inc.php";
require_once "monica/mimeenc.inc.php";
require_once "monica/mimetype.inc.php";
require_once "monica/rfc1521.inc.php";
require_once "monica/rfc822.inc.php";
require_once "monica/server.inc.php";
require_once "monica/unicode.inc.php";
require_once "monica/upload.inc.php";
require_once "monica/xfileio.inc.php";
// References:
// RFC 821 SIMPLE MAIL TRANSFER PROTOCOL
// RFC 822 STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES
// RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies
// RFC 1766 Tags for the Identification of Languages
// RFC 1806 Communicating Presentation Information in Internet Messages: The Content-Disposition Header
// RFC 1864 The Content-MD5 Header Field
// RFC 2183 Communicating Presentation Information in Internet Messages: The Content-Disposition Header Field
// RFC 2369 The Use of URLs as Meta-Syntax for Core Mail List Commands and their Transport through Message Header Fields
// RFC 2919 List-Id: A Structured Field and Namespace for the Identification of Mailing Lists
// Mail: An RFC-822 e-mail message
// 2005-05-06: All input information must be in UTF-8
class Mail
{
protected $_body = null;
protected $_parts = array();
protected $_charset = null;
protected $_modified = false;
protected $_output = null;
protected $_attaches = array();
// RFC 821 headers/properties
protected $_mail_from = null;
protected $_rcpt_to = null;
// RFC 822 headers/properties
protected $_from = null;
protected $_subject = null;
protected $_sender = null;
protected $_to = null;
protected $_cc = null;
protected $_bcc = null;
protected $_reply_to = null;
// RFC 1521 MIME headers/properties
protected $_type = "text/plain";
protected $_id = null;
// RFC 1766 Content-Language
protected $_lang = null;
// RFC 1806/2183 Content-Disposition
protected $_disposition = null;
protected $_filename = null;
protected $_filemtime = null;
protected $_filesize = null;
// RFC 1864 Content-MD5
protected $_md5 = false;
// RFC 2387 MIME multipart/related headers/properties
protected $_related_type = null;
protected $_related_start = null;
// RFC 2369/2919 Mailing Lists headers/properties
protected $_list_id = null;
protected $_list_help = null;
protected $_list_subscribe = null;
protected $_list_unsubscribe = null;
protected $_list_post = null;
protected $_list_owner = null;
protected $_list_archive = null;
// Non-standard headers
protected $_errors_to = null;
protected $_precedence = null;
protected $_mailer = "Monica mail.inc.php http://www.pristine.com.tw/";
protected $_any_header = array();
protected $_boundary_len = 32;
// __construct: Initialize the e-mail message
function __construct()
{
// Nothing to initialize
}
// RFC 821 headers/properties
// mail_from: Set/retrieve the MAIL FROM: sender
function mail_from($email = null)
{
if (func_num_args() > 0) {
$this->_mail_from = $email;
$this->_modified = true;
}
return $this->_mail_from;
}
// rcpt_to: Set/retrieve the RCPT TO: recipients (to be used in send())
function rcpt_to($email = null)
{
if (func_num_args() > 0) {
// Reset it
if (is_null($email)) {
$this->_rcpt_to = null;
// Add it
} else {
if (!is_array($this->_rcpt_to)) {
$this->_rcpt_to = array();
}
if (!in_array($email, $this->_rcpt_to)) {
$this->_rcpt_to[] = $email;
}
}
}
return is_null($this->_rcpt_to)? null:
implode(",", $this->_rcpt_to);
}
// RFC 822 headers/properties
// from: Set/retrieve the From: header
function from($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_add_addr($this->_from, $email, $name);
}
return $this->_ret_addrs($this->_from);
}
// subject: Set/retrieve the subject
function subject($subject = null)
{
if (func_num_args() > 0) {
$this->_subject = $subject;
$this->_modified = true;
}
return $this->_subject;
}
// sender: Set/retrieve the Sender: header
function sender($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_set_addr($this->_sender, $email, $name);
}
return $this->_ret_addr($this->_sender);
}
// to: Set/retrieve the To: header
function to($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_add_addr($this->_to, $email, $name);
}
return $this->_ret_addrs($this->_to);
}
// cc: Set/retrieve the Cc: header
function cc($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_add_addr($this->_cc, $email, $name);
}
return $this->_ret_addrs($this->_cc);
}
// bcc: Set/retrieve the Bcc: header
function bcc($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_add_addr($this->_bcc, $email, $name);
}
return $this->_ret_addrs($this->_bcc);
}
// reply_to: Set/retrieve the Reply-To: header
function reply_to($email = null, $name = null)
{
if (func_num_args() > 0) {
$this->_add_addr($this->_reply_to, $email, $name);
}
return $this->_ret_addrs($this->_reply_to);
}
// RFC 1521 MIME headers/properties
// charset: Set/retrieve the desired character set
// Note that this is the desired character set. The input information
// should still in UTF-8
function charset($charset = null)
{
if (func_num_args() > 0) {
$this->_charset = $charset;
$this->_modified = true;
}
return $this->_charset;
}
// type: Set/retrieve the MIME content type
function type($type = null)
{
if (func_num_args() > 0) {
$this->_type = $type;
$this->_modified = true;
}
return $this->_type;
}
// id: Set/retrieve the content ID.
function id($id = true)
{
if (func_num_args() > 0) {
// Set a content ID.
if ($id) {
if (is_null($this->_id)) {
$this->_id = $this->_new_msgid();
$this->_modified = true;
}
// Unset the content ID.
} else {
if (!is_null($this->_id)) {
$this->_id = null;
$this->_modified = true;
}
}
}
return $this->_id;
}
// RFC 1766 Content-Language
// lang: Set/retrieve the language
function lang($lang = null)
{
if (func_num_args() > 0) {
// Reset it
if (is_null($lang)) {
$this->_lang = null;
// Add it
} else {
if (!is_array($this->_lang)) {
$this->_lang = array();
}
$this->_lang[] = $lang;
}
$this->_modified = true;
}
if (is_null($this->_lang)) {
return null;
} else {
$langs = array();
for ($i = 0; $i < count($this->_lang); $i++) {
$langs[] = ln($this->_lang[$i], LN_NAME);
}
return implode(", ", $this->_lang);
}
}
// RFC 1806/2183 Content-Disposition
// disposition: Set/retrieve the MIME content disposition
// "inline" or "attachment"
function disposition($disposition = null)
{
if (func_num_args() > 0) {
if (is_null($disposition)) {
$this->_disposition = null;
} else {
switch ($disposition) {
case "inline":
case "attachment":
$this->_disposition = $disposition;
break;
default:
trigger_error("bad content disposition $disposition", E_USER_ERROR);
break;
}
}
$this->_modified = true;
}
return $this->_disposition;
}
// filename: Set/retrieve the MIME content disposition filename
function filename($filename = null)
{
if (func_num_args() > 0) {
$this->_filename = $filename;
$this->_modified = true;
}
return $this->_filename;
}
// filemtime: Set/retrieve the MIME content disposition modification time
function filemtime($mtime = null)
{
if (func_num_args() > 0) {
$this->_filemtime = $mtime;
$this->_modified = true;
}
return $this->_filemtime;
}
// filesize: Set/retrieve the MIME content disposition size
function filesize($size = null)
{
if (func_num_args() > 0) {
$this->_filesize = $size;
$this->_modified = true;
}
return $this->_filesize;
}
// RFC 1864 Content-MD5
// md5: Set/retrieve the MD5 digest flag
function md5($md5 = true)
{
if (func_num_args() > 0) {
$this->_md5 = $md5? true: false;
$this->_modified = true;
}
return $this->_md5;
}
// RFC 2387 MIME multipart/related headers/properties
// related_type: Set/retrieve the multipart/related type
function related_type($reltype = null)
{
if (func_num_args() > 0) {
$this->_related_type = $reltype;
$this->_modified = true;
}
return $this->_related_type;
}
// related_start: Set/retrieve the multipart/related start
function related_start($relstart = null)
{
if (func_num_args() > 0) {
$this->_related_start = $relstart;
$this->_modified = true;
}
return $this->_related_start;
}
// RFC 2369/2919 Mailing Lists headers/properties
// list_id: Set/retrieve the list ID.
function list_id($id = null, $phrase = null)
{
if (func_num_args() > 0) {
// Reset it
if (is_null($id)) {
$this->_list_id = null;
// Set it
} else {
$this->_list_id = array(
"id" => $id,
"phrase" => $phrase,
);
}
$this->_modified = true;
}
if (is_null($this->_list_id)) {
return null;
}
$ret = "<" . $this->_list_id["id"] . ">";
if (!is_null($this->_list_id["phrase"])) {
$phrase = $this->_list_id["phrase"];
if (rfc822_phrase_need_quoting($phrase)) {
$phrase = "\"" . addslashes($phrase) . "\"";
}
$ret = "$phrase $ret";
}
return $ret;
}
// list_help: Set/retrieve the list help address
function list_help($url = null)
{
if (func_num_args() > 0) {
$this->_add_list_url($this->_list_help, $url);
}
return $this->_ret_list_urls($this->_list_help);
}
// list_subscribe: Set/retrieve the list subscribe address
function list_subscribe($url = null)
{
if (func_num_args() > 0) {
$this->_add_list_url($this->_list_subscribe, $url);
}
return $this->_ret_list_urls($this->_list_subscribe);
}
// list_unsubscribe: Set/retrieve the list unsubscribe address
function list_unsubscribe($url = null)
{
if (func_num_args() > 0) {
$this->_add_list_url($this->_list_unsubscribe, $url);
}
return $this->_ret_list_urls($this->_list_unsubscribe);
}
// list_post: Set/retrieve the list post address
// False means "List-Post: NO"
function list_post($url = null)
{
if (func_num_args() > 0) {
if ($url === false) {
$this->_list_post = false;
} else {
$this->_add_list_url($this->_list_post, $url);
}
}
if ($this->_list_post === false) {
return "NO";
} else {
return $this->_ret_list_urls($this->_list_post);
}
}
// list_owner: Set/retrieve the list owner address
function list_owner($url = null)
{
if (func_num_args() > 0) {
$this->_add_list_url($this->_list_owner, $url);
}
return $this->_ret_list_urls($this->_list_owner);
}
// list_archive: Set/retrieve the list archive address
function list_archive($url = null)
{
if (func_num_args() > 0) {
$this->_add_list_url($this->_list_archive, $url);
}
return $this->_ret_list_urls($this->_list_archive);
}
// Non-standard headers
// errors_to: Set/retrieve the Errors-To: header
function errors_to($email = null)
{
if (func_num_args() > 0) {
if (is_null($email)) {
$this->_errors_to = null;
} else {
if (!is_array($this->_errors_to)) {
$this->_errors_to = array();
}
$this->_errors_to[] = $email;
}
$this->_modified = true;
}
return is_null($this->_errors_to)? null:
implode(", ", $this->_errors_to);
}
// precedence: Set/retrieve the precedence, mentioned in RFC 2076
// "bulk" or "first-class"
function precedence($precedence = null)
{
if (func_num_args() > 0) {
if (is_null($precedence)) {
$this->_precedence = null;
} else {
switch ($precedence) {
case "bulk":
case "first-class":
$this->_precedence = $precedence;
break;
default:
trigger_error("bad precedence $precedence", E_USER_ERROR);
break;
}
}
$this->_modified = true;
}
return $this->_precedence;
}
// mailer: Set/retrieve the mailer
function mailer($mailer = null)
{
if (func_num_args() > 0) {
$this->_mailer = $mailer;
$this->_modified = true;
}
return $this->_mailer;
}
// any_header: Set/retrieve any header
function any_header($name, $value = null)
{
// Find the index of that header
unset($ndx);
for ($i = 0; $i < count($this->_any_header); $i++) {
if ($this->_any_header[$i]["name"] = $name) {
$ndx = $i;
break;
}
}
// Set the value
if (func_num_args() > 1) {
if (isset($ndx)) {
$this->_any_header[$ndx]["value"] = $value;
} else {
// Only add a non-null value
if (!is_null($value)) {
$this->_any_header[] = array(
"name" => $name,
"value" => $value,
);
}
}
$this->_modified = true;
return $value;
// Retrieve the value
} else {
if (isset($ndx)) {
return $this->_any_header[$ndx]["value"];
} else {
return null;
}
}
}
// body: Set/retrieve the body
function body($body = null)
{
if (func_num_args() > 0) {
$this->_body = $body;
$this->_modified = true;
}
return $this->_body;
}
// addpart: Add an MIME part
// $mail is a Mail object
function addpart($mail)
{
if (is_null($mail)) {
$this->_parts = array();
} else {
$this->_parts[] = $mail;
}
$this->_modified = true;
return $mail;
}
// from_file: Set the mail from a file
function from_file($file)
{
$this->_type = check_mime_type($file);
$this->_body = xfread($file);
$this->_modified = true;
return;
}
// as_attach: Set the mail as an attachment
function as_attach($file, $filename = null)
{
$this->_type = check_mime_type($file);
$this->_disposition = "attachment";
$this->_filename = !is_null($filename)? $filename:
basename($file);
$this->_filemtime = filemtime($file);
$this->_filesize = filesize($file);
$this->_body = xfread($file);
$this->_modified = true;
return $this->_body;
}
// as_attach_upload: Set the an attachment from $_SESSION
function as_attach_upload($sn)
{
$FILES =& file_deposit();
$file =& $FILES[$sn];
$this->_charset = getlang(LN_CHARSET);
$this->_type = $file["type"];
$this->_disposition = "attachment";
$this->_filename = $file["name"];
$this->_filesize = $file["size"];
$this->_body = $file["content"];
$this->_modified = true;
return $this->_body;
}
// add_attach: Add an attachment
function add_attach($file, $filename = null)
{
if (is_null($file)) {
$this->_attaches = array();
} else {
// Create the attachment
$attach = new Mail();
$attach->as_attach($file, $filename);
$this->_attaches[] = $attach;
}
$this->_modified = true;
return;
}
// add_attach_upload: Add an attachment from $_SESSION
function add_attach_upload($sn)
{
if (is_null($sn)) {
$this->_attaches = array();
} else {
// Create the attachment
$attach = new Mail();
$attach->as_attach_upload($sn);
$this->_attaches[] = $attach;
}
$this->_modified = true;
return;
}
// output: Output the mail message
function output($is_full_msg = true)
{
// Output before
if (!is_null($this->_output) && !$this->_modified) {
return $this->_output;
}
// Preserve the original timeout
$timeout = ini_get("max_execution_time");
ini_set("max_execution_time", 0);
// Set the attachment
$mail = $this->_get_attached_mail();
// Check the message
if ($is_full_msg) {
$mail->_check();
}
// Set the mail body
// Set the body first, since we need to know Content-Transfer-Encoding
// and MIME multipart boundary first
$body = "";
unset($content_transfer_encoding);
// A single content
if ( is_null($mail->_type)
|| substr($mail->_type, 0, 10) != "multipart/") {
if (!is_null($mail->_body)) {
$body = $mail->_body;
// A text message
if (substr($mail->_type, 0, 5) == "text/") {
// Convert to the desired character set
// This piece of code shall be refined
if ( $mail->_type == "text/plain"
&& !is_usascii_printable($body)
&& !is_null($this->_charset)
&& $this->_charset != "UTF-8") {
$body = iconv("UTF-8", $this->_charset, $body);
}
$body = str_replace("\r\n", "\n", $body);
$body = str_replace("\n", "\r\n", $body);
if ($mail->_md5) {
$md5 = md5_base64($body);
}
// Not in US-ASCII, containing long lines, or MD5 is in use
if ( !is_usascii_printable_text($body)
|| preg_match("/[^\r\n]{76}/", $body)
|| $mail->_md5) {
$body = qpencode($body);
$content_transfer_encoding = "quoted-printable";
}
// A piece of RFC-822 e-mail message
} elseif (substr($mail->_type, 0, 8) == "message/") {
// Do nothing
// A piece of binary data
} else {
if ($mail->_md5) {
$md5 = md5_base64($body);
}
$body = base64_encode($body);
$body = preg_replace("/(.{76})/", "$1\r\n", $body);
$content_transfer_encoding = "base64";
}
}
// A multipart MIME content
} else {
$parts = array();
for ($i = 0; $i < count($mail->_parts); $i++) {
$parts[] = $mail->_parts[$i]->output(false);
}
$everything = implode("", $parts);
// Create a boundary
do {
$boundary = "=_";
while (strlen($boundary) < $mail->_boundary_len) {
switch (mt_rand(0, 2)) {
case 0:
$boundary .= chr(mt_rand(0, 9) + ord("0"));
break;
case 1:
$boundary .= chr(mt_rand(0, 25) + ord("A"));
break;
case 2:
$boundary .= chr(mt_rand(0, 25) + ord("a"));
break;
}
}
} while (strpos($everything, $boundary) !== false);
// Add the boundary
for ($i = 0; $i < count($parts); $i++) {
$parts[$i] = "--$boundary\r\n" . $parts[$i] . "\r\n";
}
// Compose the body
$body = implode("", $parts) . "--$boundary--\r\n";
}
// Ensure a CRLF is in the end
if (substr($body, -2) != "\r\n") {
$body .= "\r\n";
}
$headers = array();
// RFC 822 headers
// RFC-822 suggests the header order as:
// "Return-Path", "Received", "Date", "From", "Subject", "Sender",
// "To", "cc", etc.
// We do not set the Received: header. It is added before mail is sent.
// Set the Date: header
if ($is_full_msg) {
$headers[] = "Date: " . date("r", NOW) . "\r\n";
}
// Set the From: header
if (!is_null($mail->_from)) {
$headers[] = "From: " . $mail->_out_addrs($mail->_from) . "\r\n";
} elseif ($is_full_msg) {
$pwent = posix_getpwuid(posix_geteuid());
$addr = array(
"name" => $pwent["gecos"],
"email" => $pwent["name"] . "@" . fqdn(),
);
$headers[] = "From: " . $mail->_out_addr($addr) . "\r\n";
}
// Set the Subject: header
if (!is_null($mail->_subject)) {
$subject = $this->_subject;
$subject = b64hdr_encode($subject, $this->_charset);
$headers[] = "Subject: $subject\r\n";
}
// Set the Sender: header
if (!is_null($mail->_sender)) {
$need = false;
// Multiple From:
if (count($mail->_from) > 1) {
$need = true;
// Different than the only From:
} elseif ( $mail->_from[0]["email"] !== $mail->_sender["email"]
|| $mail->_from[0]["name"] !== $mail->_sender["name"]) {
$need = true;
}
if ($need) {
$headers[] = "Sender: " . $mail->_out_addr($mail->_sender) . "\r\n";
}
}
// Set the To: header
if (!is_null($mail->_to)) {
$headers[] = "To: " . $mail->_out_addrs($mail->_to) . "\r\n";
}
// Set the Cc: header
if (!is_null($mail->_cc)) {
$headers[] = "Cc: " . $mail->_out_addrs($mail->_cc) . "\r\n";
}
// Set the Bcc: header
if (!is_null($mail->_bcc)) {
$headers[] = "Bcc: " . $mail->_out_addrs($mail->_bcc) . "\r\n";
}
// Destination must exist (by RFC 822 4.1)
/* if ( $is_full_msg
&& is_null($mail->_to)
&& is_null($mail->_cc)
&& is_null($mail->_bcc)) {
$headers[] = "Bcc: \r\n";
} */
// Set the Reply-To: header
if (!is_null($mail->_reply_to)) {
$headers[] = "Reply-To: " . $mail->_out_addrs($mail->_reply_to) . "\r\n";
}
// Set the Message-ID: header
if ($is_full_msg) {
$id = !is_null($mail->_id)? $mail->_id: $mail->_new_msgid();
$headers[] = "Message-ID: <$id>\r\n";
}
// RFC 1521 MIME headers
// Set the MIME-Version: header
if ($is_full_msg && !is_null($mail->_type)) {
$headers[] = "MIME-Version: 1.0\r\n";
}
// Set the Content-Type: header
if (!is_null($mail->_type)) {
$type = $mail->_type;
if (substr($type, 0, 5) == "text/" && !is_null($mail->_charset)) {
$type .= "; charset=" . $mail->_charset;
}
// Attachment filename -- deprecated
/* if (!is_null($mail->_filename)) {
$filename = $mail->_filename;
$filename = b64hdr_encode($filename, $this->_charset);
if (rfc1521_value_need_quoting($filename)) {
$filename = "\"" . addslashes($filename) . "\"";
}
$type .= ";\r\n name=$filename";
} */
if (substr($type, 0, 10) == "multipart/") {
// Add type and start parameter to multipart/related
if ($type == "multipart/related") {
$reltype = $mail->_related_type;
if (is_null($reltype)) {
$reltype = $mail->_parts[0]->type();
}
$type .= ";\r\n type=\"$reltype\"";
// "start" parameter is broken in most e-mail client (??? why?)
/* $relstart = $mail->_related_start;
if (is_null($relstart)) {
$relstart = $mail->_parts[0]->id(true);
}
$type .= ";\r\n start=\"$relstart\""; */
}
$type .= ";\r\n boundary=\"$boundary\"";
}
$headers[] = "Content-Type: $type\r\n";
}
// Set the Content-ID: header
// Do not generate Content-ID for complete messages. Complete
// messages has Message-ID instead.
if (!$is_full_msg && !is_null($mail->_id)) {
$headers[] = "Content-ID: <" . $mail->_id . ">\r\n";
}
// Set the Content-Transfer-Encoding: header
if (isset($content_transfer_encoding)) {
$headers[] = "Content-Transfer-Encoding: $content_transfer_encoding\r\n";
}
// RFC 1766 Content-Language
if (!is_null($mail->_lang)) {
$langs = array();
for ($i = 0; $i < count($mail->_lang); $i++) {
$langs[] = ln($mail->_lang[$i], LN_NAME);
}
$headers[] = "Content-Language: " . implode(", ", $langs) . "\r\n";
}
// RFC 1806/2183 Content-Disposition
if (!is_null($mail->_disposition)) {
$disposition = $mail->_disposition;
if (!is_null($mail->_filename)) {
$filename = $mail->_filename;
// Note: Eudora cannot handle encoded MIME parameter values
$filename = b64hdr_encode($filename, $this->_charset);
if (rfc1521_value_need_quoting($filename)) {
$filename = "\"" . addslashes($filename) . "\"";
}
$disposition .= ";\r\n filename=$filename";
}
if (!is_null($mail->_filemtime)) {
$disposition .= ";\r\n modification-date=\""
. date("r", $mail->_filemtime) . "\"";
}
if (!is_null($mail->_filesize)) {
$disposition .= ";\r\n size=" . $mail->_filesize;
}
$headers[] = "Content-Disposition: $disposition\r\n";
}
// RFC 1864 Content-MD5
if ($mail->_md5) {
$headers[] = "Content-MD5: $md5\r\n";
}
// RFC 2369/2919 Mailing Lists headers
// Set the List-ID: header
if (!is_null($mail->_list_id)) {
$list_id = "<" . $mail->_list_id["id"] . ">";
if (!is_null($mail->_list_id["phrase"])) {
$phrase = $mail->_list_id["phrase"];
if (rfc822_phrase_need_quoting($phrase)) {
$phrase = "\"" . addslashes($phrase) . "\"";
}
$phrase = b64hdr_encode($phrase, $this->_charset);
$list_id = "$phrase $list_id";
}
$headers[] = "List-ID: $list_id\r\n";
}
// Set the List-Help: header
if (!is_null($mail->_list_help)) {
$headers[] = "List-Help: "
. $mail->_out_list_urls($mail->_list_help) . "\r\n";
}
// Set the List-Subscribe: header
if (!is_null($mail->_list_subscribe)) {
$headers[] = "List-Subscribe: "
. $mail->_out_list_urls($mail->_list_subscribe) . "\r\n";
}
// Set the List-Unsubscribe: header
if (!is_null($mail->_list_unsubscribe)) {
$headers[] = "List-Unsubscribe: "
. $mail->_out_list_urls($mail->_list_unsubscribe) . "\r\n";
}
// Set the List-Post: header
if (!is_null($mail->_list_post)) {
$list_post = ($mail->_list_post !== false)?
$mail->_out_list_urls($mail->_list_post): "NO";
$headers[] = "List-Post: $list_post\r\n";
}
// Set the List-Owner: header
if (!is_null($mail->_list_owner)) {
$headers[] = "List-Owner: "
. $mail->_out_list_urls($mail->_list_owner) . "\r\n";
}
// Set the List-Archive: header
if (!is_null($mail->_list_archive)) {
$headers[] = "List-Archive: "
. $mail->_out_list_urls($mail->_list_archive) . "\r\n";
}
// Set the Errors-To: header
if ($is_full_msg && !is_null($mail->_errors_to)) {
$headers[] = "Errors-To: " . implode(", ", $mail->_errors_to) . "\r\n";
}
// Set the Precedence: header
if ($is_full_msg && !is_null($mail->_precedence)) {
$headers[] = "Precedence: " . $mail->_precedence . "\r\n";
}
// Set the X-Mailer: header
if ($is_full_msg && !is_null($mail->_mailer)) {
$headers[] = "X-Mailer: " . $mail->_mailer . "\r\n";
}
// Compose it
$this->_output = implode("", $headers) . "\r\n" . $body;
$this->_modified = false;
// Restore the timeout
ini_set("max_execution_time", $timeout);
return $this->_output;
}
// send: Send the mail message
function send()
{
// Send the mail with sendmail
return $this->_send_with_sendmail();
}
// gsend: Send the mail message if the user is not a guest
function gsend()
{
if (!is_guest()) {
return $this->send();
}
}
// _new_msgid: Obtain a new message ID.
function _new_msgid()
{
static $MSGIDS = array();
// Epoch time and minisecond
list($msec, $sec) = explode(" ", microtime());
settype($sec, "integer");
$msec *= 1000000;
settype($msec, "integer");
// A random serial number
do {
$sn = mt_rand(0, 9999);
} while (in_array($sn, $MSGIDS));
$MSGIDS[] = $sn;
// Compose it
return sprintf("%10d.%06d.%05d.%04d.monica@%s",
$sec, $msec, getmypid(), $sn, fqdn());
}
// _get_attached_mail: Get the mail with its attachment
// Actually, this will convert the mail to multipart/mixed
// and add the attachments
function _get_attached_mail()
{
// Make a copy of myself
$mail = $this;
// Return myself if there is no attachment
if (count($mail->_attaches) == 0) {
return $mail;
}
// Not multipart/mixed -- convert to multipart/mixed
if (is_null($mail->_type) || $mail->_type != "multipart/mixed") {
// Pass the content to the body part
$body = new Mail();
$body->_charset = $mail->_charset;
$body->_type = $mail->_type;
$body->_id = $mail->_id;
$body->_lang = $mail->_lang;
$body->_md5 = $mail->_md5;
// Set the body
// The origin is multipart/xxx
if (!is_null($mail->_type) && substr($mail->_type, 0, 10) == "multipart/") {
$body->_parts = $mail->_parts;
} else {
$body->_body = $mail->_body;
}
// Reset these values
$mail->_parts = array();
$mail->_body = null;
$mail->_type = "multipart/mixed";
$mail->_id = null;
$mail->_lang = null;
$mail->_md5 = false;
$mail->addpart($body);
}
// Add each attachment
foreach ($mail->_attaches as $attach) {
$mail->addpart($attach);
}
return $mail;
}
// _set_addr: Set an address
function _set_addr(&$addr, $email, $name)
{
// Reset it
if (is_null($email)) {
$addr = null;
// Set it
} else {
$addr = array(
"name" => $name,
"email" => $email,
);
}
$this->_modified = true;
}
// _add_addr: Add an address to an address list
function _add_addr(&$list, $email, $name)
{
// Reset it
if (is_null($email)) {
$list = null;
// Add it
} else {
if (!is_array($list)) {
$list = array();
}
$list[] = array(
"name" => $name,
"email" => $email,
);
}
$this->_modified = true;
}
// _ret_addr: Return an address
function _ret_addr($addr)
{
if (is_null($addr)) {
return null;
} elseif (is_null($addr["name"])) {
return $addr["email"];
} else {
$name = $addr["name"];
if (rfc822_phrase_need_quoting($name)) {
$name = "\"" . addslashes($name) . "\"";
}
return sprintf("%s <%s>", $name, $addr["email"]);
}
}
// _ret_addrs: Return a list of addresses
function _ret_addrs(&$list)
{
if (is_null($list)) {
return null;
} else {
$addrs = array();
for ($i = 0; $i < count($list); $i++) {
$addrs[] = $this->_ret_addr($list[$i]);
}
return implode(", ", $addrs);
}
}
// _out_addr: Output an address
function _out_addr($addr)
{
$out = $addr["email"];
if (!is_null($addr["name"])) {
$name = $addr["name"];
if (rfc822_phrase_need_quoting($name)) {
$name = "\"" . addslashes($name) . "\"";
}
$name = b64hdr_encode($name, $this->_charset);
$out = "$name <$out>";
}
return $out;
}
// _out_addrs: Output a list of addresses
function _out_addrs(&$list)
{
$addrs = array();
for ($i = 0; $i < count($list); $i++) {
$addrs[] = $this->_out_addr($list[$i]);
}
return implode(",\r\n ", $addrs);
}
// _add_list_url: Add a list URL to a list URL list
function _add_list_url(&$list, $url)
{
// Reset it
if (is_null($url)) {
$list = null;
// Add it
} else {
if (!is_array($list)) {
$list = array();
}
$list[] = $url;
}
$this->_modified = true;
}
// _ret_list_urls: Return a list of list URLs
function _ret_list_urls(&$list)
{
if (is_null($list)) {
return null;
} else {
$urls = array();
for ($i = 0; $i < count($list); $i++) {
$urls[] = "<" . $list[$i] . ">";
}
return implode(", ", $urls);
}
}
// _out_list_urls: Output a list of list URLs
function _out_list_urls(&$list)
{
$urls = array();
for ($i = 0; $i < count($list); $i++) {
$urls[] = "<" . $list[$i] . ">";
}
return implode(",\r\n ", $urls);
}
// _out_trace: Output the trace information (the Received: header)
// See RFC 2821 4.4 Trace Information
function _out_trace()
{
// The trace information
$trace_info = array();
// From-domain
$from_tcpinfo_domain = gethostbyaddr($_SERVER["REMOTE_ADDR"]);
if ($from_tcpinfo_domain == $_SERVER["REMOTE_ADDR"]) {
$from_tcpinfo_domain = null;
}
if (is_null($from_tcpinfo_domain)) {
$from_tcp_info = sprintf("[%s]", $_SERVER["REMOTE_ADDR"]);
} else {
$from_tcp_info = sprintf("%s [%s]",
$from_tcpinfo_domain, $_SERVER["REMOTE_ADDR"]);
}
$trace_info[] = "from webclient ($from_tcp_info)";
// The invoking user, as a from comment
if (is_null(get_login_sn())) {
$from_comment = "invoked by anonymous web user"
. " country " . geoiplookup();
} else {
$from_comment = "invoked by web user " . get_login_id()
. " S/N " . get_login_sn()
. " country " . geoiplookup();
}
$trace_info[] = "($from_comment)";
// By-domain
// Apache implementation
if (is_apache()) {
$by_address_literal = $_SERVER["SERVER_ADDR"];
// Microsoft IIS implementation
} elseif (is_iis()) {
$by_address_literal = $_SERVER["LOCAL_ADDR"];
// Else, do DNS query
} else {
$by_address_literal = gethostbyname($_SERVER["SERVER_NAME"]);
}
if (array_key_exists("HTTP_HOST", $_SERVER)) {
$by_domain = preg_replace("/:\d+$/", "", $_SERVER["HTTP_HOST"]);
} else {
$by_domain = $by_address_literal;
}
$by_tcpinfo_domain = gethostbyaddr($by_address_literal);
if ($by_tcpinfo_domain == $by_address_literal) {
$by_tcpinfo_domain = null;
}
if (is_null($by_tcpinfo_domain)) {
$by_tcp_info = sprintf("[%s]", $by_address_literal);
} else {
$by_tcp_info = sprintf("%s [%s]",
$by_tcpinfo_domain, $by_address_literal);
}
$trace_info[] = sprintf("by %s (%s)", $by_domain, $by_tcp_info);
// VIA Link and WITH Protocol
$trace_info[] = "via TCP with HTTP";
// For recipients
$rcpts = $this->_get_rcpts();
if (count($rcpts) > 0) {
for ($i = 0; $i < count($rcpts); $i++) {
$rcpts[$i] = "<" . $rcpts[$i] . ">";
}
$trace_info[] = "for " . implode(" ", $rcpts);
}
return sprintf("Received: %s ;\r\n\t%s\r\n",
implode("\r\n\t", $trace_info), date("r", NOW));
}
// _check: Check if it is ready to be output
// Refer to RFC 822
function _check()
{
// When no valid From: exists, default to the current user of the running process
// Check if a valid Sender: exists with multiple From:
if ( !is_null($this->_from)
&& count($this->_from) > 1
&& is_null($this->_sender)) {
trigger_error("Multiple From: without a valid Sender: (RFC-822)", E_USER_ERROR);
}
// Check if valid contents exist
if ( !is_null($this->_type)
&& substr($this->_type, 0, 10) == "multipart/"
&& count($this->_parts) == 0) {
trigger_error("MIME Multipart without any valid part", E_USER_ERROR);
}
}
// _get_sender: Obtain the sender
function _get_sender($fallback = false)
{
// MAIL FROM: specified
if (!is_null($this->_mail_from)) {
return $this->_mail_from;
// Sender specified
} elseif (!is_null($this->_sender)) {
return $this->_sender["email"];
// Obtain the sender from From:
} elseif (!is_null($this->_from)) {
return $this->_from[0]["email"];
// Nothing left
} elseif (!$fallback) {
return null;
// Use the current user
} else {
$pwent = posix_getpwuid(posix_geteuid());
return $pwent["name"] . "@" . fqdn();
}
}
// _get_rcpts: Collect the recipients list
function _get_rcpts()
{
// Recipients specified
if (!is_null($this->_rcpt_to)) {
return $this->_rcpt_to;
}
// Obtain the recipients from To:, Cc: and Bcc:
$rcpts = array();
for ($i = 0; $i < count($this->_to); $i++) {
if (!in_array($this->_to[$i]["email"], $rcpts)) {
$rcpts[] = $this->_to[$i]["email"];
}
}
for ($i = 0; $i < count($this->_cc); $i++) {
if (!in_array($this->_cc[$i]["email"], $rcpts)) {
$rcpts[] = $this->_cc[$i]["email"];
}
}
for ($i = 0; $i < count($this->_bcc); $i++) {
if (!in_array($this->_bcc[$i]["email"], $rcpts)) {
$rcpts[] = $this->_bcc[$i]["email"];
}
}
return $rcpts;
}
// _send_with_sendmail: Send the mail with Sendmail
function _send_with_sendmail()
{
// Obtain the sender
$sender = $this->_get_sender();
// Collect the Recipients list
$rcpts = $this->_get_rcpts();
// No recipients found
if (count($rcpts) == 0) {
trigger_error("No recipients found", E_USER_ERROR);
}
// Send with Sendmail
$sendmail = array("/usr/sbin/sendmail", "-odb");
if (!is_null($sender)) {
$sendmail = array_merge($sendmail, array("-f", $sender));
}
$sendmail = array_merge($sendmail, $rcpts);
$mail = $this->_out_trace() . $this->output();
// Sendmail must escape "." on a line. See RFC 821 SMTP 4.5.2
$mail = preg_replace("/^(\.+)\r$/m", ".$1", $mail);
return gxruncmd($sendmail, $mail);
}
}
// b64hdr_encode: Encode a piece of header text with Base-64
// Refer to RFC-1522 4.1
function b64hdr_encode($text, $charset)
{
// US-ASCII printable -- no need to encode it
if (is_usascii_printable($text)) {
return $text;
}
// No desired character set available
if (is_null($charset)) {
return $text;
}
// Desired character set not UTF-8
if ($charset != "UTF-8") {
// Try to convert into the desired character set
$GLOBALS["php_errormsg"] = null;
set_error_handler("null_error_handler");
$converted = iconv("UTF-8", $charset, $text);
restore_error_handler();
// Conversion OK -- in the desired character set
if (is_null($GLOBALS["php_errormsg"])) {
return "=?$charset?B?" . base64_encode($converted) . "?=";
}
}
// Else -- send in UTF-8
return "=?UTF-8?B?" . base64_encode($text) . "?=";
}
?>