Headers.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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 $headers = [];
  41. private $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): self
  73. {
  74. return $this->add(new MailboxListHeader($name, Address::createArray($addresses)));
  75. }
  76. /**
  77. * @param Address|string $address
  78. *
  79. * @return $this
  80. */
  81. public function addMailboxHeader(string $name, $address): self
  82. {
  83. return $this->add(new MailboxHeader($name, Address::create($address)));
  84. }
  85. /**
  86. * @param string|array $ids
  87. *
  88. * @return $this
  89. */
  90. public function addIdHeader(string $name, $ids): self
  91. {
  92. return $this->add(new IdentificationHeader($name, $ids));
  93. }
  94. /**
  95. * @param Address|string $path
  96. *
  97. * @return $this
  98. */
  99. public function addPathHeader(string $name, $path): self
  100. {
  101. return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path)));
  102. }
  103. /**
  104. * @return $this
  105. */
  106. public function addDateHeader(string $name, \DateTimeInterface $dateTime): self
  107. {
  108. return $this->add(new DateHeader($name, $dateTime));
  109. }
  110. /**
  111. * @return $this
  112. */
  113. public function addTextHeader(string $name, string $value): self
  114. {
  115. return $this->add(new UnstructuredHeader($name, $value));
  116. }
  117. /**
  118. * @return $this
  119. */
  120. public function addParameterizedHeader(string $name, string $value, array $params = []): self
  121. {
  122. return $this->add(new ParameterizedHeader($name, $value, $params));
  123. }
  124. /**
  125. * @return $this
  126. */
  127. public function addHeader(string $name, $argument, array $more = []): self
  128. {
  129. $parts = explode('\\', self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class);
  130. $method = 'add'.ucfirst(array_pop($parts));
  131. if ('addUnstructuredHeader' === $method) {
  132. $method = 'addTextHeader';
  133. } elseif ('addIdentificationHeader' === $method) {
  134. $method = 'addIdHeader';
  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): self
  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. if (($c = self::HEADER_CLASS_MAP[$name] ?? null) && !$header instanceof $c) {
  198. throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $c, get_debug_type($header)));
  199. }
  200. }
  201. public function toString(): string
  202. {
  203. $string = '';
  204. foreach ($this->toArray() as $str) {
  205. $string .= $str."\r\n";
  206. }
  207. return $string;
  208. }
  209. public function toArray(): array
  210. {
  211. $arr = [];
  212. foreach ($this->all() as $header) {
  213. if ('' !== $header->getBodyAsString()) {
  214. $arr[] = $header->toString();
  215. }
  216. }
  217. return $arr;
  218. }
  219. /**
  220. * @internal
  221. */
  222. public function getHeaderBody(string $name)
  223. {
  224. return $this->has($name) ? $this->get($name)->getBody() : null;
  225. }
  226. /**
  227. * @internal
  228. */
  229. public function setHeaderBody(string $type, string $name, $body): void
  230. {
  231. if ($this->has($name)) {
  232. $this->get($name)->setBody($body);
  233. } else {
  234. $this->{'add'.$type.'Header'}($name, $body);
  235. }
  236. }
  237. public function getHeaderParameter(string $name, string $parameter): ?string
  238. {
  239. if (!$this->has($name)) {
  240. return null;
  241. }
  242. $header = $this->get($name);
  243. if (!$header instanceof ParameterizedHeader) {
  244. throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  245. }
  246. return $header->getParameter($parameter);
  247. }
  248. /**
  249. * @internal
  250. */
  251. public function setHeaderParameter(string $name, string $parameter, ?string $value): void
  252. {
  253. if (!$this->has($name)) {
  254. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name));
  255. }
  256. $header = $this->get($name);
  257. if (!$header instanceof ParameterizedHeader) {
  258. throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
  259. }
  260. $header->setParameter($parameter, $value);
  261. }
  262. }