SMime.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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\Mime\Crypto;
  11. use Symfony\Component\Mime\Exception\RuntimeException;
  12. use Symfony\Component\Mime\Part\SMimePart;
  13. /**
  14. * @author Sebastiaan Stok <s.stok@rollerscapes.net>
  15. *
  16. * @internal
  17. */
  18. abstract class SMime
  19. {
  20. protected function normalizeFilePath(string $path): string
  21. {
  22. if (!file_exists($path)) {
  23. throw new RuntimeException(sprintf('File does not exist: "%s".', $path));
  24. }
  25. return 'file://'.str_replace('\\', '/', realpath($path));
  26. }
  27. protected function iteratorToFile(iterable $iterator, $stream): void
  28. {
  29. foreach ($iterator as $chunk) {
  30. fwrite($stream, $chunk);
  31. }
  32. }
  33. protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart
  34. {
  35. rewind($stream);
  36. $headers = '';
  37. while (!feof($stream)) {
  38. $buffer = fread($stream, 78);
  39. $headers .= $buffer;
  40. // Detect ending of header list
  41. if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) {
  42. $headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]);
  43. break;
  44. }
  45. }
  46. $headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd)));
  47. fseek($stream, $headersPosEnd + \strlen($headerBodySeparator));
  48. return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type']));
  49. }
  50. protected function getStreamIterator($stream): iterable
  51. {
  52. while (!feof($stream)) {
  53. yield str_replace("\n", "\r\n", str_replace("\r\n", "\n", fread($stream, 16372)));
  54. }
  55. }
  56. private function getMessageHeaders(string $headerData): array
  57. {
  58. $headers = [];
  59. $headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData)));
  60. $currentHeaderName = '';
  61. // Transform header lines into an associative array
  62. foreach ($headerLines as $headerLine) {
  63. // Empty lines between headers indicate a new mime-entity
  64. if ('' === $headerLine) {
  65. break;
  66. }
  67. // Handle headers that span multiple lines
  68. if (!str_contains($headerLine, ':')) {
  69. $headers[$currentHeaderName] .= ' '.trim($headerLine);
  70. continue;
  71. }
  72. $header = explode(':', $headerLine, 2);
  73. $currentHeaderName = strtolower($header[0]);
  74. $headers[$currentHeaderName] = trim($header[1]);
  75. }
  76. return $headers;
  77. }
  78. private function getParametersFromHeader(string $header): array
  79. {
  80. $params = [];
  81. preg_match_all('/(?P<name>[a-z-0-9]+)=(?P<value>"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches);
  82. foreach ($matches['value'] as $pos => $paramValue) {
  83. $params[$matches['name'][$pos]] = trim($paramValue, '"');
  84. }
  85. return $params;
  86. }
  87. }