Headers.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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\Header;
  11. use Symfony\Component\Mime\Address;
  12. use Symfony\Component\Mime\Exception\LogicException;
  13. /**
  14. * A collection of headers.
  15. *
  16. * @author Fabien Potencier <fabien@symfony.com>
  17. */
  18. final class Headers
  19. {
  20. private const UNIQUE_HEADERS = [
  21. 'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
  22. 'message-id', 'in-reply-to', 'references', 'subject',
  23. ];
  24. private const HEADER_CLASS_MAP = [
  25. 'date' => DateHeader::class,
  26. 'from' => MailboxListHeader::class,
  27. 'sender' => MailboxHeader::class,
  28. 'reply-to' => MailboxListHeader::class,
  29. 'to' => MailboxListHeader::class,
  30. 'cc' => MailboxListHeader::class,
  31. 'bcc' => MailboxListHeader::class,
  32. 'message-id' => IdentificationHeader::class,
  33. 'in-reply-to' => UnstructuredHeader::class, // `In-Reply-To` and `References` are less strict than RFC 2822 (3.6.4) to allow users entering the original email's ...
  34. 'references' => UnstructuredHeader::class, // ... `Message-ID`, even if that is no valid `msg-id`
  35. 'return-path' => PathHeader::class,
  36. ];
  37. /**
  38. * @var HeaderInterface[][]
  39. */
  40. private array $headers = [];
  41. private int $lineLength = 76;
  42. public function __construct(HeaderInterface ...$headers)
  43. {
  44. foreach ($headers as $header) {
  45. $this->add($header);
  46. }
  47. }
  48. public function __clone()
  49. {
  50. foreach ($this->headers as $name => $collection) {
  51. foreach ($collection as $i => $header) {
  52. $this->headers[$name][$i] = clone $header;
  53. }
  54. }
  55. }
  56. public function setMaxLineLength(int $lineLength)
  57. {
  58. $this->lineLength = $lineLength;
  59. foreach ($this->all() as $header) {
  60. $header->setMaxLineLength($lineLength);
  61. }
  62. }
  63. public function getMaxLineLength(): int
  64. {
  65. return $this->lineLength;
  66. }
  67. /**
  68. * @param array<Address|string> $addresses
  69. *
  70. * @return $this
  71. */
  72. public function addMailboxListHeader(string $name, array $addresses): static
  73. {
  74. return $this->add(new MailboxListHeader($name, Address::createArray($addresses)));
  75. }
  76. /**
  77. * @return $this
  78. */
  79. public function addMailboxHeader(string $name, Address|string $address): static
  80. {
  81. return $this->add(new MailboxHeader($name, Address::create($address)));
  82. }
  83. /**
  84. * @return $this
  85. */
  86. public function addIdHeader(string $name, string|array $ids): static
  87. {
  88. return $this->add(new IdentificationHeader($name, $ids));
  89. }
  90. /**
  91. * @return $this
  92. */
  93. public function addPathHeader(string $name, Address|string $path): static
  94. {
  95. return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path)));
  96. }
  97. /**
  98. * @return $this
  99. */
  100. public function addDateHeader(string $name, \DateTimeInterface $dateTime): static
  101. {
  102. return $this->add(new DateHeader($name, $dateTime));
  103. }
  104. /**
  105. * @return $this
  106. */
  107. public function addTextHeader(string $name, string $value): static
  108. {
  109. return $this->add(new UnstructuredHeader($name, $value));
  110. }
  111. /**
  112. * @return $this
  113. */
  114. public function addParameterizedHeader(string $name, string $value, array $params = []): static
  115. {
  116. return $this->add(new ParameterizedHeader($name, $value, $params));
  117. }
  118. /**
  119. * @return $this
  120. */
  121. public function addHeader(string $name, mixed $argument, array $more = []): static
  122. {
  123. $parts = explode('\\', self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class);
  124. $method = 'add'.ucfirst(array_pop($parts));
  125. if ('addUnstructuredHeader' === $method) {
  126. $method = 'addTextHeader';
  127. } elseif ('addIdentificationHeader' === $method) {
  128. $method = 'addIdHeader';
  129. }
  130. return $this->$method($name, $argument, $more);
  131. }
  132. public function has(string $name): bool
  133. {
  134. return isset($this->headers[strtolower($name)]);
  135. }
  136. /**
  137. * @return $this
  138. */
  139. public function add(HeaderInterface $header): static
  140. {
  141. self::checkHeaderClass($header);
  142. $header->setMaxLineLength($this->lineLength);
  143. $name = strtolower($header->getName());
  144. if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) {
  145. throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
  146. }
  147. $this->headers[$name][] = $header;
  148. return $this;
  149. }
  150. public function get(string $name): ?HeaderInterface
  151. {
  152. $name = strtolower($name);
  153. if (!isset($this->headers[$name])) {
  154. return null;
  155. }
  156. $values = array_values($this->headers[$name]);
  157. return array_shift($values);
  158. }
  159. public function all(string $name = null): iterable
  160. {
  161. if (null === $name) {
  162. foreach ($this->headers as $name => $collection) {
  163. foreach ($collection as $header) {
  164. yield $name => $header;
  165. }
  166. }
  167. } elseif (isset($this->headers[strtolower($name)])) {
  168. foreach ($this->headers[strtolower($name)] as $header) {
  169. yield $header;
  170. }
  171. }
  172. }
  173. public function getNames(): array
  174. {
  175. return array_keys($this->headers);
  176. }
  177. public function remove(string $name): void
  178. {
  179. unset($this->headers[strtolower($name)]);
  180. }
  181. public static function isUniqueHeader(string $name): bool
  182. {
  183. return \in_array(strtolower($name), self::UNIQUE_HEADERS, true);
  184. }
  185. /**
  186. * @throws LogicException if the header name and class are not compatible
  187. */
  188. public static function checkHeaderClass(HeaderInterface $header): void
  189. {
  190. $name = strtolower($header->getName());
  191. if (($c = self::HEADER_CLASS_MAP[$name] ?? null) && !$header instanceof $c) {
  192. throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $c, get_debug_type($header)));
  193. }
  194. }
  195. public function toString(): string
  196. {
  197. $string = '';
  198. foreach ($this->toArray() as $str) {
  199. $string .= $str."\r\n";
  200. }
  201. return $string;
  202. }
  203. public function toArray(): array
  204. {
  205. $arr = [];
  206. foreach ($this->all() as $header) {
  207. if ('' !== $header->getBodyAsString()) {
  208. $arr[] = $header->toString();
  209. }
  210. }
  211. return $arr;
  212. }
  213. public function getHeaderBody(string $name)
  214. {
  215. return $this->has($name) ? $this->get($name)->getBody() : null;
  216. }
  217. /**
  218. * @internal
  219. */
  220. public function setHeaderBody(string $type, string $name, mixed $body): void
  221. {
  222. if ($this->has($name)) {
  223. $this->get($name)->setBody($body);
  224. } else {
  225. $this->{'add'.$type.'Header'}($name, $body);
  226. }
  227. }
  228. public function getHeaderParameter(string $name, string $parameter): ?string
  229. {
  230. if (!$this->has($name)) {
  231. return null;
  232. }
  233. $header = $this->get($name);
  234. if (!$header instanceof ParameterizedHeader) {
  235. throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  236. }
  237. return $header->getParameter($parameter);
  238. }
  239. /**
  240. * @internal
  241. */
  242. public function setHeaderParameter(string $name, string $parameter, ?string $value): void
  243. {
  244. if (!$this->has($name)) {
  245. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name));
  246. }
  247. $header = $this->get($name);
  248. if (!$header instanceof ParameterizedHeader) {
  249. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  250. }
  251. $header->setParameter($parameter, $value);
  252. }
  253. }