Swift_Signers_DomainKeySigner::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 3
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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
 * DomainKey Signer used to apply DomainKeys Signature to a message.
13
 *
14
 * @author Xavier De Cock <[email protected]>
15
 */
16
class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner
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...
17
{
18
    /**
19
     * PrivateKey.
20
     *
21
     * @var string
22
     */
23
    protected $_privateKey;
24
25
    /**
26
     * DomainName.
27
     *
28
     * @var string
29
     */
30
    protected $_domainName;
31
32
    /**
33
     * Selector.
34
     *
35
     * @var string
36
     */
37
    protected $_selector;
38
39
    /**
40
     * Hash algorithm used.
41
     *
42
     * @var string
43
     */
44
    protected $_hashAlgorithm = 'rsa-sha1';
45
46
    /**
47
     * Canonisation method.
48
     *
49
     * @var string
50
     */
51
    protected $_canon = 'simple';
52
53
    /**
54
     * Headers not being signed.
55
     *
56
     * @var array
57
     */
58
    protected $_ignoredHeaders = array();
59
60
    /**
61
     * Signer identity.
62
     *
63
     * @var string
64
     */
65
    protected $_signerIdentity;
66
67
    /**
68
     * Must we embed signed headers?
69
     *
70
     * @var bool
71
     */
72
    protected $_debugHeaders = false;
73
74
    // work variables
75
76
    /**
77
     * Headers used to generate hash.
78
     *
79
     * @var array
80
     */
81
    private $_signedHeaders = array();
82
83
    /**
84
     * Stores the signature header.
85
     *
86
     * @var Swift_Mime_Headers_ParameterizedHeader
87
     */
88
    protected $_domainKeyHeader;
89
90
    /**
91
     * Hash Handler.
92
     *
93
     * @var resource|null
94
     */
95
    private $_hashHandler;
96
97
    /**
98
     * @var string|null
99
     */
100
    private $_hash;
101
102
    /**
103
     * @var string
104
     */
105
    private $_canonData = '';
106
107
    /**
108
     * @var int
109
     */
110
    private $_bodyCanonEmptyCounter = 0;
111
112
    /**
113
     * @var int
114
     */
115
    private $_bodyCanonIgnoreStart = 2;
116
117
    /**
118
     * @var bool
119
     */
120
    private $_bodyCanonSpace = false;
121
122
    /**
123
     * @var null|string
124
     */
125
    private $_bodyCanonLastChar = null;
126
127
    /**
128
     * @var string
129
     */
130
    private $_bodyCanonLine = '';
131
132
    /**
133
     * @var Swift_InputByteStream[]
134
     */
135
    private $_bound = array();
136
137
    /**
138
     * Constructor.
139
     *
140
     * @param string $privateKey
141
     * @param string $domainName
142
     * @param string $selector
143
     */
144
    public function __construct($privateKey, $domainName, $selector)
145
    {
146
        $this->_privateKey = $privateKey;
147
        $this->_domainName = $domainName;
148
        $this->_signerIdentity = '@' . $domainName;
149
        $this->_selector = $selector;
150
    }
151
152
    /**
153
     * Instanciate DomainKeySigner.
154
     *
155
     * @param string $privateKey
156
     * @param string $domainName
157
     * @param string $selector
158
     *
159
     * @return self
160
     */
161
    public static function newInstance($privateKey, $domainName, $selector)
162
    {
163
        return new static($privateKey, $domainName, $selector);
164
    }
165
166
    /**
167
     * Resets internal states.
168
     *
169
     * @return $this
170
     */
171
    public function reset()
172
    {
173
        $this->_hash = null;
174
        $this->_hashHandler = null;
175
        $this->_bodyCanonIgnoreStart = 2;
176
        $this->_bodyCanonEmptyCounter = 0;
177
        $this->_bodyCanonLastChar = null;
178
        $this->_bodyCanonSpace = false;
179
180
        return $this;
181
    }
182
183
    /**
184
     * Writes $bytes to the end of the stream.
185
     *
186
     * Writing may not happen immediately if the stream chooses to buffer.  If
187
     * you want to write these bytes with immediate effect, call {@link commit()}
188
     * after calling write().
189
     *
190
     * This method returns the sequence ID of the write (i.e. 1 for first, 2 for
191
     * second, etc etc).
192
     *
193
     * @param string $bytes
194
     *
195
     * @throws Swift_IoException
196
     *
197
     * @return $this
198
     */
199
    public function write($bytes)
200
    {
201
        $this->_canonicalizeBody($bytes);
202
        foreach ($this->_bound as $is) {
203
            $is->write($bytes);
204
        }
205
206
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Swift_Signers_DomainKeySigner) is incompatible with the return type declared by the interface Swift_InputByteStream::write of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
207
    }
208
209
    /**
210
     * For any bytes that are currently buffered inside the stream, force them
211
     * off the buffer.
212
     *
213
     * @throws Swift_IoException
214
     *
215
     * @return $this
216
     */
217
    public function commit()
218
    {
219
        // Nothing to do
220
        return $this;
221
    }
222
223
    /**
224
     * Attach $is to this stream.
225
     * The stream acts as an observer, receiving all data that is written.
226
     * All {@link write()} and {@link flushBuffers()} operations will be mirrored.
227
     *
228
     * @param Swift_InputByteStream $is
229
     *
230
     * @return $this
231
     */
232
    public function bind(Swift_InputByteStream $is)
233
    {
234
        // Don't have to mirror anything
235
        $this->_bound[] = $is;
236
237
        return $this;
238
    }
239
240
    /**
241
     * Remove an already bound stream.
242
     * If $is is not bound, no errors will be raised.
243
     * If the stream currently has any buffered data it will be written to $is
244
     * before unbinding occurs.
245
     *
246
     * @param Swift_InputByteStream $is
247
     *
248
     * @return $this
249
     */
250 View Code Duplication
    public function unbind(Swift_InputByteStream $is)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
251
    {
252
        // Don't have to mirror anything
253
        foreach ($this->_bound as $k => $stream) {
254
            if ($stream === $is) {
255
                unset($this->_bound[$k]);
256
257
                break;
258
            }
259
        }
260
261
        return $this;
262
    }
263
264
    /**
265
     * Flush the contents of the stream (empty it) and set the internal pointer
266
     * to the beginning.
267
     *
268
     * @throws Swift_IoException
269
     *
270
     * @return $this
271
     */
272
    public function flushBuffers()
273
    {
274
        $this->reset();
275
276
        return $this;
277
    }
278
279
    /**
280
     * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256.
281
     *
282
     * @param string $hash WARNING: $hash not used, it's set fixed to "rsa-sha1"
283
     *
284
     * @return $this
285
     */
286
    public function setHashAlgorithm($hash)
0 ignored issues
show
Unused Code introduced by
The parameter $hash is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
287
    {
288
        $this->_hashAlgorithm = 'rsa-sha1';
289
290
        return $this;
291
    }
292
293
    /**
294
     * Set the canonicalization algorithm.
295
     *
296
     * @param string $canon simple | nofws defaults to simple
297
     *
298
     * @return $this
299
     */
300
    public function setCanon($canon)
301
    {
302
        if ($canon === 'nofws') {
303
            $this->_canon = 'nofws';
304
        } else {
305
            $this->_canon = 'simple';
306
        }
307
308
        return $this;
309
    }
310
311
    /**
312
     * Set the signer identity.
313
     *
314
     * @param string $identity
315
     *
316
     * @return $this
317
     */
318
    public function setSignerIdentity($identity)
319
    {
320
        $this->_signerIdentity = $identity;
321
322
        return $this;
323
    }
324
325
    /**
326
     * Enable / disable the DebugHeaders.
327
     *
328
     * @param bool $debug
329
     *
330
     * @return $this
331
     */
332
    public function setDebugHeaders($debug)
333
    {
334
        $this->_debugHeaders = (bool) $debug;
335
336
        return $this;
337
    }
338
339
    /**
340
     * Start Body.
341
     */
342
    public function startBody()
343
    {
344
    }
345
346
    /**
347
     * End Body.
348
     */
349
    public function endBody()
350
    {
351
        $this->_endOfBody();
352
    }
353
354
    /**
355
     * Returns the list of Headers Tampered by this plugin.
356
     *
357
     * @return string[]
358
     */
359
    public function getAlteredHeaders()
360
    {
361
        if ($this->_debugHeaders) {
362
            return array('DomainKey-Signature', 'X-DebugHash');
363
        }
364
365
        return array('DomainKey-Signature');
366
    }
367
368
    /**
369
     * Adds an ignored Header.
370
     *
371
     * @param string $header_name
372
     *
373
     * @return $this
374
     */
375
    public function ignoreHeader($header_name)
376
    {
377
        $this->_ignoredHeaders[Swift::strtolowerWithStaticCache($header_name)] = true;
378
379
        return $this;
380
    }
381
382
    /**
383
     * Set the headers to sign.
384
     *
385
     * @param Swift_Mime_HeaderSet $headers
386
     *
387
     * @return $this
388
     */
389
    public function setHeaders(Swift_Mime_HeaderSet $headers)
390
    {
391
        $this->_startHash();
392
        $this->_canonData = '';
393
        // Loop through Headers
394
        $listHeaders = $headers->listAll();
395 View Code Duplication
        foreach ($listHeaders as $hName) {
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...
396
            // Check if we need to ignore Header
397
            if (!isset($this->_ignoredHeaders[Swift::strtolowerWithStaticCache($hName)])) {
398
                if ($headers->has($hName)) {
399
                    $tmp = $headers->getAll($hName);
400
                    foreach ($tmp as $header) {
401
                        if ($header->getFieldBody() != '') {
402
                            $this->_addHeader($header->toString());
403
                            $this->_signedHeaders[] = $header->getFieldName();
404
                        }
405
                    }
406
                }
407
            }
408
        }
409
        $this->_endOfHeaders();
410
411
        return $this;
412
    }
413
414
    /**
415
     * Add the signature to the given Headers.
416
     *
417
     * @param Swift_Mime_HeaderSet $headers
418
     *
419
     * @return $this
420
     */
421
    public function addSignature(Swift_Mime_HeaderSet $headers)
422
    {
423
        // Prepare the DomainKey-Signature Header
424
        $params = array('a' => $this->_hashAlgorithm, 'b' => chunk_split(base64_encode($this->_getEncryptedHash()), 73, ' '), 'c' => $this->_canon, 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'q' => 'dns', 's' => $this->_selector);
425
        $string = '';
426
        foreach ($params as $k => $v) {
427
            $string .= $k . '=' . $v . '; ';
428
        }
429
        $string = trim($string);
430
        $headers->addTextHeader('DomainKey-Signature', $string);
431
432
        return $this;
433
    }
434
435
    /* Private helpers */
436
437
    /**
438
     * @param string $header
439
     */
440 View Code Duplication
    protected function _addHeader($header)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
441
    {
442
        switch ($this->_canon) {
443
            case 'nofws':
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
444
                // Prepare Header and cascade
445
                $exploded = explode(':', $header, 2);
446
                $name = Swift::strtolowerWithStaticCache(trim($exploded[0]));
447
                $value = str_replace("\r\n", '', $exploded[1]);
448
                $value = preg_replace("/[ \t][ \t]+/", ' ', $value);
449
                $header = $name . ':' . trim($value) . "\r\n";
450
            case 'simple':
451
                // Nothing to do
452
        }
453
        $this->_addToHash($header);
454
    }
455
456
    protected function _endOfHeaders()
457
    {
458
        $this->_bodyCanonEmptyCounter = 1;
459
    }
460
461
    /**
462
     * @param string $string
463
     */
464
    protected function _canonicalizeBody($string)
465
    {
466
        $len = strlen($string);
467
        $canon = '';
468
        $nofws = ($this->_canon === 'nofws');
469
        for ($i = 0; $i < $len; ++$i) {
470
            if ($this->_bodyCanonIgnoreStart > 0) {
471
                --$this->_bodyCanonIgnoreStart;
472
                continue;
473
            }
474
            switch ($string[$i]) {
475
                case "\r":
476
                    $this->_bodyCanonLastChar = "\r";
477
                    break;
478 View Code Duplication
                case "\n":
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...
479
                    if ($this->_bodyCanonLastChar === "\r") {
480
                        if ($nofws) {
481
                            $this->_bodyCanonSpace = false;
482
                        }
483
                        if ($this->_bodyCanonLine === '') {
484
                            ++$this->_bodyCanonEmptyCounter;
485
                        } else {
486
                            $this->_bodyCanonLine = '';
487
                            $canon .= "\r\n";
488
                        }
489
                    } else {
490
                        // Wooops Error
491
                        throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r');
492
                    }
493
                    break;
494
                case ' ':
495
                case "\t":
496
                case "\x09": //HTAB
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
497
                    if ($nofws) {
498
                        $this->_bodyCanonSpace = true;
499
                        break;
500
                    }
501
                default:
502 View Code Duplication
                    if ($this->_bodyCanonEmptyCounter > 0) {
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...
503
                        $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter);
504
                        $this->_bodyCanonEmptyCounter = 0;
505
                    }
506
                    $this->_bodyCanonLine .= $string[$i];
507
                    $canon .= $string[$i];
508
            }
509
        }
510
        $this->_addToHash($canon);
511
    }
512
513
    protected function _endOfBody()
514
    {
515
        if ($this->_bodyCanonLine !== '') {
516
            $this->_addToHash("\r\n");
517
        }
518
519
        $this->_hash = hash_final($this->_hashHandler, true);
520
    }
521
522
    private function _addToHash($string)
523
    {
524
        $this->_canonData .= $string;
525
        hash_update($this->_hashHandler, $string);
526
    }
527
528
    private function _startHash()
529
    {
530
        // Init
531
        switch ($this->_hashAlgorithm) {
532
            case 'rsa-sha1':
533
                $this->_hashHandler = hash_init('sha1');
534
                break;
535
        }
536
        $this->_bodyCanonLine = '';
537
    }
538
539
    /**
540
     * @throws Swift_SwiftException
541
     *
542
     * @return string
543
     */
544
    private function _getEncryptedHash()
545
    {
546
        $signature = '';
547
        $pkeyId = openssl_get_privatekey($this->_privateKey);
548
        if (!$pkeyId) {
549
            throw new Swift_SwiftException('Unable to load DomainKey Private Key [' . openssl_error_string() . ']');
550
        }
551
        if (openssl_sign($this->_canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) {
552
            return $signature;
553
        }
554
        throw new Swift_SwiftException('Unable to sign DomainKey Hash  [' . openssl_error_string() . ']');
555
    }
556
}
557