AbstractStream.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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 = (new \DateTimeImmutable())->format('Y-m-d\TH:i:s.up');
  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. if (stream_get_meta_data($this->out)['timed_out']) {
  72. throw new TransportException(\sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
  73. }
  74. if (feof($this->out)) { // don't use "eof" metadata, it's not accurate on Windows
  75. throw new TransportException(\sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
  76. }
  77. if (false === $line) {
  78. throw new TransportException(\sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription().error_get_last()['message'] ?? ''));
  79. }
  80. }
  81. $this->debug .= \sprintf('[%s] < %s', (new \DateTimeImmutable())->format('Y-m-d\TH:i:s.up'), $line);
  82. return $line;
  83. }
  84. public function getDebug(): string
  85. {
  86. $debug = $this->debug;
  87. $this->debug = '';
  88. return $debug;
  89. }
  90. public static function replace(string $from, string $to, iterable $chunks): \Generator
  91. {
  92. if ('' === $from) {
  93. yield from $chunks;
  94. return;
  95. }
  96. $carry = '';
  97. $fromLen = \strlen($from);
  98. foreach ($chunks as $chunk) {
  99. if ('' === $chunk = $carry.$chunk) {
  100. continue;
  101. }
  102. if (str_contains($chunk, $from)) {
  103. $chunk = explode($from, $chunk);
  104. $carry = array_pop($chunk);
  105. yield implode($to, $chunk).$to;
  106. } else {
  107. $carry = $chunk;
  108. }
  109. if (\strlen($carry) > $fromLen) {
  110. yield substr($carry, 0, -$fromLen);
  111. $carry = substr($carry, -$fromLen);
  112. }
  113. }
  114. if ('' !== $carry) {
  115. yield $carry;
  116. }
  117. }
  118. abstract protected function getReadConnectionDescription(): string;
  119. }