Completed
Push — 5.x ( 4b3980...241dce )
by Lars
05:15
created

Swift_Encoder_QpEncoder::_encodeByteSequence()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 14
c 0
b 0
f 0
nc 5
nop 2
dl 0
loc 22
ccs 13
cts 13
cp 1
crap 4
rs 8.9197
1
<?php
2
3
/*
4
 * This file is part of SwiftMailer.
5
 * (c) 2004-2009 Chris Corbyn
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
/**
12
 * Handles Quoted Printable (QP) Encoding in Swift Mailer.
13
 *
14
 * Possibly the most accurate RFC 2045 QP implementation found in PHP.
15
 *
16
 * @author Chris Corbyn
17
 */
18
class Swift_Encoder_QpEncoder implements Swift_Encoder
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
19
{
20
    /**
21
     * The CharacterStream used for reading characters (as opposed to bytes).
22
     *
23
     * @var Swift_CharacterStream
24
     */
25
    protected $_charStream;
26
27
    /**
28
     * A filter used if input should be canonicalized.
29
     *
30
     * @var Swift_StreamFilter
31
     */
32
    protected $_filter;
33
34
    /**
35
     * Pre-computed QP for HUGE optimization.
36
     *
37
     * @var string[]
38
     */
39
    protected static $_qpMap = array(
40
        0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
41
        5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
42
        10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
43
        15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
44
        20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
45
        25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
46
        30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
47
        35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
48
        40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
49
        45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
50
        50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
51
        55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
52
        60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
53
        65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
54
        70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
55
        75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
56
        80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
57
        85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
58
        90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
59
        95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
60
        100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
61
        105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
62
        110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
63
        115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
64
        120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
65
        125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
66
        130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
67
        135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
68
        140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
69
        145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
70
        150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
71
        155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
72
        160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
73
        165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
74
        170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
75
        175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
76
        180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
77
        185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
78
        190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
79
        195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
80
        200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
81
        205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
82
        210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
83
        215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
84
        220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
85
        225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
86
        230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
87
        235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
88
        240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
89
        245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
90
        250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
91
        255 => '=FF',
92
    );
93
94
    protected static $_safeMapShare = array();
95
96
    /**
97
     * A map of non-encoded ascii characters.
98
     *
99
     * @var string[]
100
     */
101
    protected $_safeMap = array();
102
103
    /**
104
     * Creates a new QpEncoder for the given CharacterStream.
105
     *
106
     * @param Swift_CharacterStream $charStream to use for reading characters
107
     * @param Swift_StreamFilter    $filter     if input should be canonicalized
108
     */
109 189
    public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null)
110
    {
111 189
        $this->_charStream = $charStream;
112
113 189 View Code Duplication
        if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114 3
            $this->initSafeMap();
115 3
            self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
116
        } else {
117 189
            $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
118
        }
119
120 189
        $this->_filter = $filter;
121 189
    }
122
123
    public function __sleep()
124
    {
125
        return array('_charStream', '_filter');
126
    }
127
128
    public function __wakeup()
129
    {
130 View Code Duplication
        if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
            $this->initSafeMap();
132
            self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap;
133
        } else {
134
            $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()];
135
        }
136
    }
137
138 168
    protected function getSafeMapShareId()
139
    {
140 168
        return get_class($this);
141
    }
142
143 3
    protected function initSafeMap()
144
    {
145
        foreach (
146 3
            array_merge(
147 3
                array(0x09, 0x20),
148 3
                range(0x21, 0x3C),
149 3
                range(0x3E, 0x7E)
150
            ) as $byte
151
        ) {
152 3
            $this->_safeMap[$byte] = chr($byte);
153
        }
154 3
    }
155
156
    /**
157
     * Takes an unencoded string and produces a QP encoded string from it.
158
     *
159
     * QP encoded strings have a maximum line length of 76 characters.
160
     * If the first line needs to be shorter, indicate the difference with
161
     * $firstLineOffset.
162
     *
163
     * @param string $string          to encode
164
     * @param int    $firstLineOffset , optional
165
     * @param int    $maxLineLength   ,   optional 0 indicates the default of 76 chars
166
     *
167
     * @return string
168
     */
169 72
    public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0)
170
    {
171 72
        if ($maxLineLength > 76 || $maxLineLength <= 0) {
172 70
            $maxLineLength = 76;
173
        }
174
175 72
        $thisLineLength = $maxLineLength - $firstLineOffset;
176
177 72
        $lines = array();
178 72
        $lNo = 0;
179 72
        $lines[$lNo] = '';
180 72
        $currentLine = &$lines[$lNo++];
181 72
        $size = $lineLen = 0;
182
183 72
        $this->_charStream->flushContents();
184 72
        $this->_charStream->importString($string);
185
186 72
        while (false !== $bytes = $this->_nextSequence()) {
187
            // if we're filtering the input
188 72 View Code Duplication
            if (isset($this->_filter)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
                // if we can't filter because we need more bytes
190 54
                while ($this->_filter->shouldBuffer($bytes)) {
191
                    // then collect bytes into the buffer
192 2
                    if (false === $moreBytes = $this->_nextSequence(1)) {
193 2
                        break;
194
                    }
195
196
                    foreach ($moreBytes as $b) {
197
                        $bytes[] = $b;
198
                    }
199
                }
200
                // and filter them
201 54
                $bytes = $this->_filter->filter($bytes);
202
            }
203
204 72
            $enc = $this->_encodeByteSequence($bytes, $size);
205
206 72
            $i = strpos($enc, '=0D=0A');
207 72
            $newLineLength = $lineLen + ($i === false ? $size : $i);
208
209 72
            if ($currentLine && $newLineLength >= $thisLineLength) {
210 7
                $lines[$lNo] = '';
211 7
                $currentLine = &$lines[$lNo++];
212 7
                $thisLineLength = $maxLineLength;
213 7
                $lineLen = 0;
214
            }
215
216 72
            $currentLine .= $enc;
217
218 72 View Code Duplication
            if ($i === false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219 58
                $lineLen += $size;
220
            } else {
221
                // 6 is the length of '=0D=0A'.
222 18
                $lineLen = $size - strrpos($enc, '=0D=0A') - 6;
223
            }
224
        }
225
226 72
        return $this->_standardize(implode("=\r\n", $lines));
227
    }
228
229
    /**
230
     * Updates the charset used.
231
     *
232
     * @param string $charset
233
     */
234 151
    public function charsetChanged($charset)
235
    {
236 151
        $this->_charStream->setCharacterSet($charset);
237 151
    }
238
239
    /**
240
     * Encode the given byte array into a verbatim QP form.
241
     *
242
     * @param integer[] $bytes
243
     * @param int       $size
244
     *
245
     * @return string
246
     */
247 85
    protected function _encodeByteSequence(array $bytes, &$size)
248
    {
249 85
        $ret = '';
250 85
        $size = 0;
251 85
        foreach ($bytes as $b) {
252
253 85
            $tmpRet = '';
254 85
            if (isset($this->_safeMap[$b])) {
255 76
                $tmpRet .= $this->_safeMap[$b];
256 76
                ++$size;
257
            } else {
258 42
                $tmpRet .= self::$_qpMap[$b];
259 42
                $size += 3;
260
            }
261
262 85
            if ($tmpRet !== '=00') {
263 85
              $ret .= $tmpRet;
264
            }
265
        }
266
267 85
        return $ret;
268
    }
269
270
    /**
271
     * Get the next sequence of bytes to read from the char stream.
272
     *
273
     * Fetching more than 4 chars at one is slower, as is fetching fewer bytes
274
     * Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
275
     * bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes.
276
     *
277
     * @param int $size number of bytes to read
278
     *
279
     * @return int[]
280
     */
281 85
    protected function _nextSequence($size = 72)
282
    {
283 85
        return $this->_charStream->readBytes($size);
284
    }
285
286
    /**
287
     * Make sure CRLF is correct and HT/SPACE are in valid places.
288
     *
289
     * @param string $string
290
     *
291
     * @return string
292
     */
293 84
    protected function _standardize($string)
294
    {
295 84
        $string = str_replace(
296 84
            array("\t=0D=0A", ' =0D=0A', '=0D=0A'),
297 84
            array("=09\r\n", "=20\r\n", "\r\n"),
298
            $string
299
        );
300
301 84
        switch ($end = ord(substr($string, -1))) {
302 84
            case 0x09:
303 84
            case 0x20:
304 3
                $string = substr_replace($string, self::$_qpMap[$end], -1);
305
        }
306
307 84
        return $string;
308
    }
309
310
    /**
311
     * Make a deep copy of object.
312
     */
313 5
    public function __clone()
314
    {
315 5
        $this->_charStream = clone $this->_charStream;
316 5
    }
317
}
318