Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\Mime\Header;
13
 
14
use Symfony\Component\Mime\Encoder\QpMimeHeaderEncoder;
15
 
16
/**
17
 * An abstract base MIME Header.
18
 *
19
 * @author Chris Corbyn
20
 */
21
abstract class AbstractHeader implements HeaderInterface
22
{
23
    public 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])))?))+?)';
24
 
25
    private static QpMimeHeaderEncoder $encoder;
26
 
27
    private string $name;
28
    private int $lineLength = 76;
29
    private ?string $lang = null;
30
    private string $charset = 'utf-8';
31
 
32
    public function __construct(string $name)
33
    {
34
        $this->name = $name;
35
    }
36
 
37
    public function setCharset(string $charset)
38
    {
39
        $this->charset = $charset;
40
    }
41
 
42
    public function getCharset(): ?string
43
    {
44
        return $this->charset;
45
    }
46
 
47
    /**
48
     * Set the language used in this Header.
49
     *
50
     * For example, for US English, 'en-us'.
51
     */
52
    public function setLanguage(string $lang)
53
    {
54
        $this->lang = $lang;
55
    }
56
 
57
    public function getLanguage(): ?string
58
    {
59
        return $this->lang;
60
    }
61
 
62
    public function getName(): string
63
    {
64
        return $this->name;
65
    }
66
 
67
    public function setMaxLineLength(int $lineLength)
68
    {
69
        $this->lineLength = $lineLength;
70
    }
71
 
72
    public function getMaxLineLength(): int
73
    {
74
        return $this->lineLength;
75
    }
76
 
77
    public function toString(): string
78
    {
79
        return $this->tokensToString($this->toTokens());
80
    }
81
 
82
    /**
83
     * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
84
     *
85
     * @param string $string  as displayed
86
     * @param bool   $shorten the first line to make remove for header name
87
     */
88
    protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string
89
    {
90
        // Treat token as exactly what was given
91
        $phraseStr = $string;
92
 
93
        // If it's not valid
94
        if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) {
95
            // .. but it is just ascii text, try escaping some characters
96
            // and make it a quoted-string
97
            if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
98
                foreach (['\\', '"'] as $char) {
99
                    $phraseStr = str_replace($char, '\\'.$char, $phraseStr);
100
                }
101
                $phraseStr = '"'.$phraseStr.'"';
102
            } else {
103
                // ... otherwise it needs encoding
104
                // Determine space remaining on line if first line
105
                if ($shorten) {
106
                    $usedLength = \strlen($header->getName().': ');
107
                } else {
108
                    $usedLength = 0;
109
                }
110
                $phraseStr = $this->encodeWords($header, $string, $usedLength);
111
            }
112
        } elseif (str_contains($phraseStr, '(')) {
113
            foreach (['\\', '"'] as $char) {
114
                $phraseStr = str_replace($char, '\\'.$char, $phraseStr);
115
            }
116
            $phraseStr = '"'.$phraseStr.'"';
117
        }
118
 
119
        return $phraseStr;
120
    }
121
 
122
    /**
123
     * Encode needed word tokens within a string of input.
124
     */
125
    protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string
126
    {
127
        $value = '';
128
        $tokens = $this->getEncodableWordTokens($input);
129
        foreach ($tokens as $token) {
130
            // See RFC 2822, Sect 2.2 (really 2.2 ??)
131
            if ($this->tokenNeedsEncoding($token)) {
132
                // Don't encode starting WSP
133
                $firstChar = substr($token, 0, 1);
134
                switch ($firstChar) {
135
                    case ' ':
136
                    case "\t":
137
                        $value .= $firstChar;
138
                        $token = substr($token, 1);
139
                }
140
 
141
                if (-1 == $usedLength) {
142
                    $usedLength = \strlen($header->getName().': ') + \strlen($value);
143
                }
144
                $value .= $this->getTokenAsEncodedWord($token, $usedLength);
145
            } else {
146
                $value .= $token;
147
            }
148
        }
149
 
150
        return $value;
151
    }
152
 
153
    protected function tokenNeedsEncoding(string $token): bool
154
    {
155
        return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
156
    }
157
 
158
    /**
159
     * Splits a string into tokens in blocks of words which can be encoded quickly.
160
     *
161
     * @return string[]
162
     */
163
    protected function getEncodableWordTokens(string $string): array
164
    {
165
        $tokens = [];
166
        $encodedToken = '';
167
        // Split at all whitespace boundaries
168
        foreach (preg_split('~(?=[\t ])~', $string) as $token) {
169
            if ($this->tokenNeedsEncoding($token)) {
170
                $encodedToken .= $token;
171
            } else {
172
                if ('' !== $encodedToken) {
173
                    $tokens[] = $encodedToken;
174
                    $encodedToken = '';
175
                }
176
                $tokens[] = $token;
177
            }
178
        }
179
        if ('' !== $encodedToken) {
180
            $tokens[] = $encodedToken;
181
        }
182
 
183
        return $tokens;
184
    }
185
 
186
    /**
187
     * Get a token as an encoded word for safe insertion into headers.
188
     */
189
    protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string
190
    {
191
        self::$encoder ??= new QpMimeHeaderEncoder();
192
 
193
        // Adjust $firstLineOffset to account for space needed for syntax
194
        $charsetDecl = $this->charset;
195
        if (null !== $this->lang) {
196
            $charsetDecl .= '*'.$this->lang;
197
        }
198
        $encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??=');
199
 
200
        if ($firstLineOffset >= 75) {
201
            // Does this logic need to be here?
202
            $firstLineOffset = 0;
203
        }
204
 
205
        $encodedTextLines = explode("\r\n",
206
            self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength)
207
        );
208
 
209
        if ('iso-2022-jp' !== strtolower($this->charset)) {
210
            // special encoding for iso-2022-jp using mb_encode_mimeheader
211
            foreach ($encodedTextLines as $lineNum => $line) {
212
                $encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?=';
213
            }
214
        }
215
 
216
        return implode("\r\n ", $encodedTextLines);
217
    }
218
 
219
    /**
220
     * Generates tokens from the given string which include CRLF as individual tokens.
221
     *
222
     * @return string[]
223
     */
224
    protected function generateTokenLines(string $token): array
225
    {
226
        return preg_split('~(\r\n)~', $token, -1, \PREG_SPLIT_DELIM_CAPTURE);
227
    }
228
 
229
    /**
230
     * Generate a list of all tokens in the final header.
231
     */
232
    protected function toTokens(string $string = null): array
233
    {
234
        $string ??= $this->getBodyAsString();
235
 
236
        $tokens = [];
237
        // Generate atoms; split at all invisible boundaries followed by WSP
238
        foreach (preg_split('~(?=[ \t])~', $string) as $token) {
239
            $newTokens = $this->generateTokenLines($token);
240
            foreach ($newTokens as $newToken) {
241
                $tokens[] = $newToken;
242
            }
243
        }
244
 
245
        return $tokens;
246
    }
247
 
248
    /**
249
     * Takes an array of tokens which appear in the header and turns them into
250
     * an RFC 2822 compliant string, adding FWSP where needed.
251
     *
252
     * @param string[] $tokens
253
     */
254
    private function tokensToString(array $tokens): string
255
    {
256
        $lineCount = 0;
257
        $headerLines = [];
258
        $headerLines[] = $this->name.': ';
259
        $currentLine = &$headerLines[$lineCount++];
260
 
261
        // Build all tokens back into compliant header
262
        foreach ($tokens as $i => $token) {
263
            // Line longer than specified maximum or token was just a new line
264
            if (("\r\n" === $token) ||
265
                ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
266
                && '' !== $currentLine) {
267
                $headerLines[] = '';
268
                $currentLine = &$headerLines[$lineCount++];
269
            }
270
 
271
            // Append token to the line
272
            if ("\r\n" !== $token) {
273
                $currentLine .= $token;
274
            }
275
        }
276
 
277
        // Implode with FWS (RFC 2822, 2.2.3)
278
        return implode("\r\n", $headerLines);
279
    }
280
}