forked from PHPMailer/PHPMailer
-
Notifications
You must be signed in to change notification settings - Fork 901
/
smtp_low_memory.phps
131 lines (117 loc) · 4.83 KB
/
smtp_low_memory.phps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
<?php
/**
* SMTP low memory example.
*/
namespace PHPMailer\PHPMailer;
require '../vendor/autoload.php';
/**
* This class demonstrates sending an already-built RFC822 message via SMTP
* by extending PHPMailer's SMTP class.
* It uses less memory than PHPMailer's usual approach because it keeps
* the message as a single string rather than splitting its lines into
* an array, which can consume very large amounts of memory if you have
* large attachments. The downside is that it's somewhat slower.
* This is mainly of academic interest, but shows how you can change how
* core classes work without having to alter the library itself.
*/
class SMTPLowMemory extends SMTP
{
public function data($msg_data)
{
//This will use the standard timelimit
if (!$this->sendCommand('DATA', 'DATA', 354)) {
return false;
}
/* The server is ready to accept data!
* According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
* so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
* smaller lines to fit within the limit.
* We will also look for lines that start with a '.' and prepend an additional '.'.
* NOTE: this does not count towards line-length limit.
*/
//Normalize line breaks
$msg_data = str_replace(["\r\n", "\r"], "\n", $msg_data);
/* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
* of the first line (':' separated) does not contain a space then it _should_ be a header and we will
* process all lines before a blank line as headers.
*/
$firstline = substr($msg_data, 0, strcspn($msg_data, "\n", 0));
$field = substr($firstline, 0, strpos($firstline, ':'));
$in_headers = false;
if (!empty($field) && strpos($field, ' ') === false) {
$in_headers = true;
}
$offset = 0;
$len = strlen($msg_data);
while ($offset < $len) {
//Get position of next line break
$linelen = strcspn($msg_data, "\n", $offset);
//Get the next line
$line = substr($msg_data, $offset, $linelen);
//Remember where we have got to
$offset += ($linelen + 1);
$lines_out = [];
if ($in_headers && $line === '') {
$in_headers = false;
}
//We need to break this line up into several smaller lines
//This is a small micro-optimisation: isset($str[$len]) is equivalent to (strlen($str) > $len)
while (isset($line[self::MAX_LINE_LENGTH])) {
//Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
//so as to avoid breaking in the middle of a word
$pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
//Deliberately matches both false and 0
if (!$pos) {
//No nice break found, add a hard break
$pos = self::MAX_LINE_LENGTH - 1;
$lines_out[] = substr($line, 0, $pos);
$line = substr($line, $pos);
} else {
//Break at the found point
$lines_out[] = substr($line, 0, $pos);
//Move along by the amount we dealt with
$line = substr($line, $pos + 1);
}
//If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
if ($in_headers) {
$line = "\t" . $line;
}
}
$lines_out[] = $line;
//Send the lines to the server
foreach ($lines_out as $line_out) {
//RFC2821 section 4.5.2
if (!empty($line_out) && $line_out[0] === '.') {
$line_out = '.' . $line_out;
}
$this->client_send($line_out . self::LE);
}
}
//Message data has been sent, complete the command
//Increase timelimit for end of DATA command
$savetimelimit = $this->Timelimit;
$this->Timelimit *= 2;
$result = $this->sendCommand('DATA END', '.', 250);
//Restore timelimit
$this->Timelimit = $savetimelimit;
return $result;
}
}
/**
* We need to use a PHPMailer subclass to make it use our SMTP implementation.
* @package PHPMailer\PHPMailer
*/
class PHPMailerLowMemory extends PHPMailer
{
/**
* Patch in the new SMTP class.
* @return SMTP
*/
public function getSMTPInstance()
{
if (!is_object($this->smtp)) {
$this->smtp = new SMTPLowMemory();
}
return $this->smtp;
}
}