Headers.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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, IdentificationHeader::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, IdentificationHeader::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): void
  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. $headerClass = self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class;
  124. if (\is_array($headerClass)) {
  125. $headerClass = $headerClass[0];
  126. }
  127. $parts = explode('\\', $headerClass);
  128. $method = 'add'.ucfirst(array_pop($parts));
  129. if ('addUnstructuredHeader' === $method) {
  130. $method = 'addTextHeader';
  131. } elseif ('addIdentificationHeader' === $method) {
  132. $method = 'addIdHeader';
  133. } elseif ('addMailboxListHeader' === $method && !\is_array($argument)) {
  134. $argument = [$argument];
  135. }
  136. return $this->$method($name, $argument, $more);
  137. }
  138. public function has(string $name): bool
  139. {
  140. return isset($this->headers[strtolower($name)]);
  141. }
  142. /**
  143. * @return $this
  144. */
  145. public function add(HeaderInterface $header): static
  146. {
  147. self::checkHeaderClass($header);
  148. $header->setMaxLineLength($this->lineLength);
  149. $name = strtolower($header->getName());
  150. if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) {
  151. throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
  152. }
  153. $this->headers[$name][] = $header;
  154. return $this;
  155. }
  156. public function get(string $name): ?HeaderInterface
  157. {
  158. $name = strtolower($name);
  159. if (!isset($this->headers[$name])) {
  160. return null;
  161. }
  162. $values = array_values($this->headers[$name]);
  163. return array_shift($values);
  164. }
  165. public function all(?string $name = null): iterable
  166. {
  167. if (null === $name) {
  168. foreach ($this->headers as $name => $collection) {
  169. foreach ($collection as $header) {
  170. yield $name => $header;
  171. }
  172. }
  173. } elseif (isset($this->headers[strtolower($name)])) {
  174. foreach ($this->headers[strtolower($name)] as $header) {
  175. yield $header;
  176. }
  177. }
  178. }
  179. public function getNames(): array
  180. {
  181. return array_keys($this->headers);
  182. }
  183. public function remove(string $name): void
  184. {
  185. unset($this->headers[strtolower($name)]);
  186. }
  187. public static function isUniqueHeader(string $name): bool
  188. {
  189. return \in_array(strtolower($name), self::UNIQUE_HEADERS, true);
  190. }
  191. /**
  192. * @throws LogicException if the header name and class are not compatible
  193. */
  194. public static function checkHeaderClass(HeaderInterface $header): void
  195. {
  196. $name = strtolower($header->getName());
  197. $headerClasses = self::HEADER_CLASS_MAP[$name] ?? [];
  198. if (!\is_array($headerClasses)) {
  199. $headerClasses = [$headerClasses];
  200. }
  201. if (!$headerClasses) {
  202. return;
  203. }
  204. foreach ($headerClasses as $c) {
  205. if ($header instanceof $c) {
  206. return;
  207. }
  208. }
  209. throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), implode('" or "', $headerClasses), get_debug_type($header)));
  210. }
  211. public function toString(): string
  212. {
  213. $string = '';
  214. foreach ($this->toArray() as $str) {
  215. $string .= $str."\r\n";
  216. }
  217. return $string;
  218. }
  219. public function toArray(): array
  220. {
  221. $arr = [];
  222. foreach ($this->all() as $header) {
  223. if ('' !== $header->getBodyAsString()) {
  224. $arr[] = $header->toString();
  225. }
  226. }
  227. return $arr;
  228. }
  229. public function getHeaderBody(string $name): mixed
  230. {
  231. return $this->has($name) ? $this->get($name)->getBody() : null;
  232. }
  233. /**
  234. * @internal
  235. */
  236. public function setHeaderBody(string $type, string $name, mixed $body): void
  237. {
  238. if ($this->has($name)) {
  239. $this->get($name)->setBody($body);
  240. } else {
  241. $this->{'add'.$type.'Header'}($name, $body);
  242. }
  243. }
  244. public function getHeaderParameter(string $name, string $parameter): ?string
  245. {
  246. if (!$this->has($name)) {
  247. return null;
  248. }
  249. $header = $this->get($name);
  250. if (!$header instanceof ParameterizedHeader) {
  251. throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  252. }
  253. return $header->getParameter($parameter);
  254. }
  255. /**
  256. * @internal
  257. */
  258. public function setHeaderParameter(string $name, string $parameter, ?string $value): void
  259. {
  260. if (!$this->has($name)) {
  261. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name));
  262. }
  263. $header = $this->get($name);
  264. if (!$header instanceof ParameterizedHeader) {
  265. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  266. }
  267. $header->setParameter($parameter, $value);
  268. }
  269. }