23 const PHRASE_PATTERN =
'(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
25 private static $encoder;
28 private $lineLength = 76;
30 private $charset =
'utf-8';
39 $this->charset = $charset;
44 return $this->charset;
69 $this->lineLength = $lineLength;
74 return $this->lineLength;
79 return $this->tokensToString($this->
toTokens());
94 if (!preg_match(
'/^'.self::PHRASE_PATTERN.
'$/D', $phraseStr)) {
97 if (preg_match(
'/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
98 foreach ([
'\\',
'"'] as $char) {
99 $phraseStr = str_replace($char,
'\\'.$char, $phraseStr);
101 $phraseStr =
'"'.$phraseStr.
'"';
106 $usedLength = \strlen($header->
getName().
': ');
110 $phraseStr = $this->
encodeWords($header, $string, $usedLength);
124 foreach ($tokens as $token) {
128 $firstChar = substr($token, 0, 1);
129 switch ($firstChar) {
132 $value .= $firstChar;
133 $token = substr($token, 1);
136 if (-1 == $usedLength) {
137 $usedLength = \strlen($header->
getName().
': ') + \strlen($value);
150 return (
bool) preg_match(
'~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
163 foreach (preg_split(
'~(?=[\t ])~', $string) as $token) {
165 $encodedToken .= $token;
167 if (\strlen($encodedToken) > 0) {
168 $tokens[] = $encodedToken;
174 if (\strlen($encodedToken)) {
175 $tokens[] = $encodedToken;
186 if (
null === self::$encoder) {
191 $charsetDecl = $this->charset;
192 if (
null !== $this->lang) {
193 $charsetDecl .=
'*'.$this->lang;
195 $encodingWrapperLength = \strlen(
'=?'.$charsetDecl.
'?'.self::$encoder->getName().
'??=');
197 if ($firstLineOffset >= 75) {
199 $firstLineOffset = 0;
202 $encodedTextLines = explode(
"\r\n",
203 self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength)
206 if (
'iso-2022-jp' !== strtolower($this->charset)) {
208 foreach ($encodedTextLines as $lineNum => $line) {
209 $encodedTextLines[$lineNum] =
'=?'.$charsetDecl.
'?'.self::$encoder->getName().
'?'.$line.
'?=';
213 return implode(
"\r\n ", $encodedTextLines);
223 return preg_split(
'~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
229 protected function toTokens(
string $string =
null): array
231 if (
null === $string) {
237 foreach (preg_split(
'~(?=[ \t])~', $string) as $token) {
239 foreach ($newTokens as $newToken) {
240 $tokens[] = $newToken;
253 private function tokensToString(array $tokens): string
257 $headerLines[] = $this->name.
': ';
258 $currentLine = &$headerLines[$lineCount++];
261 foreach ($tokens as $i => $token) {
263 if ((
"\r\n" === $token) ||
264 ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
265 && 0 < \strlen($currentLine)) {
267 $currentLine = &$headerLines[$lineCount++];
271 if (
"\r\n" !== $token) {
272 $currentLine .= $token;
277 return implode(
"\r\n", $headerLines);