1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-mail for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-mail/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-mail/blob/master/LICENSE.md New BSD License
7 */
8
9namespace Laminas\Mail\Header;
10
11use Laminas\Mime\Mime;
12
13class GenericHeader implements HeaderInterface, UnstructuredInterface
14{
15    /**
16     * @var string
17     */
18    protected $fieldName = null;
19
20    /**
21     * @var string
22     */
23    protected $fieldValue = null;
24
25    /**
26     * Header encoding
27     *
28     * @var null|string
29     */
30    protected $encoding;
31
32    /**
33     * @param string $headerLine
34     * @return GenericHeader
35     */
36    public static function fromString($headerLine)
37    {
38        list($name, $value) = self::splitHeaderLine($headerLine);
39        $value  = HeaderWrap::mimeDecodeValue($value);
40        $header = new static($name, $value);
41
42        return $header;
43    }
44
45    /**
46     * Splits the header line in `name` and `value` parts.
47     *
48     * @param string $headerLine
49     * @return string[] `name` in the first index and `value` in the second.
50     * @throws Exception\InvalidArgumentException If header does not match with the format ``name:value``
51     */
52    public static function splitHeaderLine($headerLine)
53    {
54        $parts = explode(':', $headerLine, 2);
55        if (count($parts) !== 2) {
56            throw new Exception\InvalidArgumentException('Header must match with the format "name:value"');
57        }
58
59        if (! HeaderName::isValid($parts[0])) {
60            throw new Exception\InvalidArgumentException('Invalid header name detected');
61        }
62
63        if (! HeaderValue::isValid($parts[1])) {
64            throw new Exception\InvalidArgumentException('Invalid header value detected');
65        }
66
67        $parts[1] = ltrim($parts[1]);
68
69        return $parts;
70    }
71
72    /**
73     * Constructor
74     *
75     * @param string $fieldName  Optional
76     * @param string $fieldValue Optional
77     */
78    public function __construct($fieldName = null, $fieldValue = null)
79    {
80        if ($fieldName) {
81            $this->setFieldName($fieldName);
82        }
83
84        if ($fieldValue !== null) {
85            $this->setFieldValue($fieldValue);
86        }
87    }
88
89    /**
90     * Set header name
91     *
92     * @param  string $fieldName
93     * @return GenericHeader
94     * @throws Exception\InvalidArgumentException;
95     */
96    public function setFieldName($fieldName)
97    {
98        if (! is_string($fieldName) || empty($fieldName)) {
99            throw new Exception\InvalidArgumentException('Header name must be a string');
100        }
101
102        // Pre-filter to normalize valid characters, change underscore to dash
103        $fieldName = str_replace(' ', '-', ucwords(str_replace(['_', '-'], ' ', $fieldName)));
104
105        if (! HeaderName::isValid($fieldName)) {
106            throw new Exception\InvalidArgumentException(
107                'Header name must be composed of printable US-ASCII characters, except colon.'
108            );
109        }
110
111        $this->fieldName = $fieldName;
112        return $this;
113    }
114
115    public function getFieldName()
116    {
117        return $this->fieldName;
118    }
119
120    /**
121     * Set header value
122     *
123     * @param  string $fieldValue
124     * @return GenericHeader
125     * @throws Exception\InvalidArgumentException;
126     */
127    public function setFieldValue($fieldValue)
128    {
129        $fieldValue = (string) $fieldValue;
130
131        if (! HeaderWrap::canBeEncoded($fieldValue)) {
132            throw new Exception\InvalidArgumentException(
133                'Header value must be composed of printable US-ASCII characters and valid folding sequences.'
134            );
135        }
136
137        $this->fieldValue = $fieldValue;
138        $this->encoding   = null;
139
140        return $this;
141    }
142
143    public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
144    {
145        if (HeaderInterface::FORMAT_ENCODED === $format) {
146            return HeaderWrap::wrap($this->fieldValue, $this);
147        }
148
149        return $this->fieldValue;
150    }
151
152    public function setEncoding($encoding)
153    {
154        if ($encoding === $this->encoding) {
155            return $this;
156        }
157
158        if ($encoding === null) {
159            $this->encoding = null;
160            return $this;
161        }
162
163        $encoding = strtoupper($encoding);
164        if ($encoding === 'UTF-8') {
165            $this->encoding = $encoding;
166            return $this;
167        }
168
169        if ($encoding === 'ASCII' && Mime::isPrintable($this->fieldValue)) {
170            $this->encoding = $encoding;
171            return $this;
172        }
173
174        $this->encoding = null;
175
176        return $this;
177    }
178
179    public function getEncoding()
180    {
181        if (! $this->encoding) {
182            $this->encoding = Mime::isPrintable($this->fieldValue) ? 'ASCII' : 'UTF-8';
183        }
184
185        return $this->encoding;
186    }
187
188    public function toString()
189    {
190        $name = $this->getFieldName();
191        if (empty($name)) {
192            throw new Exception\RuntimeException('Header name is not set, use setFieldName()');
193        }
194        $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED);
195
196        return $name . ': ' . $value;
197    }
198}
199