AbstractStream.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Mailer\Transport\Smtp\Stream;
  11. use Symfony\Component\Mailer\Exception\TransportException;
  12. /**
  13. * A stream supporting remote sockets and local processes.
  14. *
  15. * @author Fabien Potencier <fabien@symfony.com>
  16. * @author Nicolas Grekas <p@tchwork.com>
  17. * @author Chris Corbyn
  18. *
  19. * @internal
  20. */
  21. abstract class AbstractStream
  22. {
  23. /** @var resource|null */
  24. protected $stream;
  25. /** @var resource|null */
  26. protected $in;
  27. /** @var resource|null */
  28. protected $out;
  29. protected $err;
  30. private string $debug = '';
  31. public function write(string $bytes, bool $debug = true): void
  32. {
  33. if ($debug) {
  34. $timestamp = date('c');
  35. foreach (explode("\n", trim($bytes)) as $line) {
  36. $this->debug .= sprintf("[%s] > %s\n", $timestamp, $line);
  37. }
  38. }
  39. $bytesToWrite = \strlen($bytes);
  40. $totalBytesWritten = 0;
  41. while ($totalBytesWritten < $bytesToWrite) {
  42. $bytesWritten = @fwrite($this->in, substr($bytes, $totalBytesWritten));
  43. if (false === $bytesWritten || 0 === $bytesWritten) {
  44. throw new TransportException('Unable to write bytes on the wire.');
  45. }
  46. $totalBytesWritten += $bytesWritten;
  47. }
  48. }
  49. /**
  50. * Flushes the contents of the stream (empty it) and set the internal pointer to the beginning.
  51. */
  52. public function flush(): void
  53. {
  54. fflush($this->in);
  55. }
  56. /**
  57. * Performs any initialization needed.
  58. */
  59. abstract public function initialize(): void;
  60. public function terminate(): void
  61. {
  62. $this->stream = $this->err = $this->out = $this->in = null;
  63. }
  64. public function readLine(): string
  65. {
  66. if (feof($this->out)) {
  67. return '';
  68. }
  69. $line = @fgets($this->out);
  70. if ('' === $line || false === $line) {
  71. $metas = stream_get_meta_data($this->out);
  72. if ($metas['timed_out']) {
  73. throw new TransportException(sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
  74. }
  75. if ($metas['eof']) {
  76. throw new TransportException(sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
  77. }
  78. if (false === $line) {
  79. throw new TransportException(sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription()).error_get_last()['message']);
  80. }
  81. }
  82. $this->debug .= sprintf('[%s] < %s', date('c'), $line);
  83. return $line;
  84. }
  85. public function getDebug(): string
  86. {
  87. $debug = $this->debug;
  88. $this->debug = '';
  89. return $debug;
  90. }
  91. public static function replace(string $from, string $to, iterable $chunks): \Generator
  92. {
  93. if ('' === $from) {
  94. yield from $chunks;
  95. return;
  96. }
  97. $carry = '';
  98. $fromLen = \strlen($from);
  99. foreach ($chunks as $chunk) {
  100. if ('' === $chunk = $carry.$chunk) {
  101. continue;
  102. }
  103. if (str_contains($chunk, $from)) {
  104. $chunk = explode($from, $chunk);
  105. $carry = array_pop($chunk);
  106. yield implode($to, $chunk).$to;
  107. } else {
  108. $carry = $chunk;
  109. }
  110. if (\strlen($carry) > $fromLen) {
  111. yield substr($carry, 0, -$fromLen);
  112. $carry = substr($carry, -$fromLen);
  113. }
  114. }
  115. if ('' !== $carry) {
  116. yield $carry;
  117. }
  118. }
  119. abstract protected function getReadConnectionDescription(): string;
  120. }