AbstractStream.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. foreach (explode("\n", trim($bytes)) as $line) {
  35. $this->debug .= \sprintf("> %s\n", $line);
  36. }
  37. }
  38. $bytesToWrite = \strlen($bytes);
  39. $totalBytesWritten = 0;
  40. while ($totalBytesWritten < $bytesToWrite) {
  41. $bytesWritten = @fwrite($this->in, substr($bytes, $totalBytesWritten));
  42. if (false === $bytesWritten || 0 === $bytesWritten) {
  43. throw new TransportException('Unable to write bytes on the wire.');
  44. }
  45. $totalBytesWritten += $bytesWritten;
  46. }
  47. }
  48. /**
  49. * Flushes the contents of the stream (empty it) and set the internal pointer to the beginning.
  50. */
  51. public function flush(): void
  52. {
  53. fflush($this->in);
  54. }
  55. /**
  56. * Performs any initialization needed.
  57. */
  58. abstract public function initialize(): void;
  59. public function terminate(): void
  60. {
  61. $this->stream = $this->err = $this->out = $this->in = null;
  62. }
  63. public function readLine(): string
  64. {
  65. if (feof($this->out)) {
  66. return '';
  67. }
  68. $line = @fgets($this->out);
  69. if ('' === $line || false === $line) {
  70. if (stream_get_meta_data($this->out)['timed_out']) {
  71. throw new TransportException(\sprintf('Connection to "%s" timed out.', $this->getReadConnectionDescription()));
  72. }
  73. if (feof($this->out)) { // don't use "eof" metadata, it's not accurate on Windows
  74. throw new TransportException(\sprintf('Connection to "%s" has been closed unexpectedly.', $this->getReadConnectionDescription()));
  75. }
  76. if (false === $line) {
  77. throw new TransportException(\sprintf('Unable to read from connection to "%s": ', $this->getReadConnectionDescription().error_get_last()['message'] ?? ''));
  78. }
  79. }
  80. $this->debug .= \sprintf('< %s', $line);
  81. return $line;
  82. }
  83. public function getDebug(): string
  84. {
  85. $debug = $this->debug;
  86. $this->debug = '';
  87. return $debug;
  88. }
  89. public static function replace(string $from, string $to, iterable $chunks): \Generator
  90. {
  91. if ('' === $from) {
  92. yield from $chunks;
  93. return;
  94. }
  95. $carry = '';
  96. $fromLen = \strlen($from);
  97. foreach ($chunks as $chunk) {
  98. if ('' === $chunk = $carry.$chunk) {
  99. continue;
  100. }
  101. if (str_contains($chunk, $from)) {
  102. $chunk = explode($from, $chunk);
  103. $carry = array_pop($chunk);
  104. yield implode($to, $chunk).$to;
  105. } else {
  106. $carry = $chunk;
  107. }
  108. if (\strlen($carry) > $fromLen) {
  109. yield substr($carry, 0, -$fromLen);
  110. $carry = substr($carry, -$fromLen);
  111. }
  112. }
  113. if ('' !== $carry) {
  114. yield $carry;
  115. }
  116. }
  117. abstract protected function getReadConnectionDescription(): string;
  118. }