smtp_low_memory.phps 4.9 KB

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