Swift_FileSpool::start()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of SwiftMailer.
5
 * (c) 2009 Fabien Potencier <[email protected]>
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
 * Stores Messages on the filesystem.
13
 *
14
 * @author Fabien Potencier
15
 * @author Xavier De Cock <[email protected]>
16
 */
17
class Swift_FileSpool extends Swift_ConfigurableSpool
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...
18
{
19
    /** The spool directory */
20
    private $_path;
21
22
    /**
23
     * File WriteRetry Limit.
24
     *
25
     * @var int
26
     */
27
    private $_retryLimit = 10;
28
29
    /**
30
     * Create a new FileSpool.
31
     *
32
     * @param string $path
33
     *
34
     * @throws Swift_IoException
35
     */
36
    public function __construct($path)
37
    {
38
        $this->_path = $path;
39
40
      /** @noinspection PhpUsageOfSilenceOperatorInspection */
41
      if (
42
            !file_exists($this->_path)
43
            &&
44
            !@mkdir($this->_path, 0777, true)
45
            &&
46
            !is_dir($this->_path)
47
        ) {
48
            throw new Swift_IoException(sprintf('Unable to create path "%s".', $this->_path));
49
        }
50
    }
51
52
    /**
53
     * Tests if this Spool mechanism has started.
54
     *
55
     * @return bool
56
     */
57
    public function isStarted()
58
    {
59
        return true;
60
    }
61
62
    /**
63
     * Starts this Spool mechanism.
64
     */
65
    public function start()
66
    {
67
    }
68
69
    /**
70
     * Stops this Spool mechanism.
71
     */
72
    public function stop()
73
    {
74
    }
75
76
    /**
77
     * Allow to manage the enqueuing retry limit.
78
     *
79
     * Default, is ten and allows over 64^20 different fileNames
80
     *
81
     * @param int $limit
82
     */
83
    public function setRetryLimit($limit)
84
    {
85
        $this->_retryLimit = $limit;
86
    }
87
88
    /**
89
     * Queues a message.
90
     *
91
     * @param Swift_Mime_Message $message The message to store
92
     *
93
     * @throws Swift_IoException
94
     *
95
     * @return bool
96
     */
97
    public function queueMessage(Swift_Mime_Message $message)
98
    {
99
        $ser = serialize($message);
100
        $fileName = $this->_path . '/' . $this->getRandomString();
101
        for ($i = 0; $i < $this->_retryLimit; ++$i) {
102
            /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */
103
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
104
            $fp = @fopen($fileName . '.message', 'xb');
105
                if (false !== $fp) {
106
                    if (false === fwrite($fp, $ser)) {
107
                        return false;
108
                    }
109
                    return fclose($fp);
110
              }
111
112
            /* The file already exists, we try a longer fileName */
113
            $fileName .= $this->getRandomString(1);
114
        }
115
116
        throw new Swift_IoException(sprintf('Unable to create a file for enqueuing Message in "%s".', $this->_path));
117
    }
118
119
    /**
120
     * Execute a recovery if for any reason a process is sending for too long.
121
     *
122
     * @param int $timeout in second Defaults is for very slow smtp responses
123
     */
124
    public function recover($timeout = 900)
125
    {
126
        foreach (new DirectoryIterator($this->_path) as $file) {
127
            $file = $file->getRealPath();
128
129
            if (substr($file, -16) === '.message.sending') {
130
                $lockedtime = filectime($file);
131
                if ((time() - $lockedtime) > $timeout) {
132
                    rename($file, substr($file, 0, -8));
133
                }
134
            }
135
        }
136
    }
137
138
    /**
139
     * Sends messages using the given transport instance.
140
     *
141
     * @param Swift_Transport $transport        A transport instance
142
     * @param string[]        $failedRecipients An array of failures by-reference
143
     *
144
     * @return int The number of sent e-mail's
145
     */
146
    public function flushQueue(Swift_Transport $transport, &$failedRecipients = null)
147
    {
148
        $directoryIterator = new DirectoryIterator($this->_path);
149
150
        /* Start the transport only if there are queued files to send */
151
        if (!$transport->isStarted()) {
152
            foreach ($directoryIterator as $file) {
153
                if (substr($file->getRealPath(), -8) === '.message') {
154
                    $transport->start();
155
                    break;
156
                }
157
            }
158
        }
159
160
        $failedRecipients = (array)$failedRecipients;
161
        $count = 0;
162
        $time = time();
163
        foreach ($directoryIterator as $file) {
164
            $file = $file->getRealPath();
165
166
            if (substr($file, -8) !== '.message') {
167
                continue;
168
            }
169
170
            /* We try a rename, it's an atomic operation, and avoid locking the file */
171
            if (rename($file, $file . '.sending')) {
172
                // TODO: -> SECURITY | Perhaps it's possible to exploit the unserialize via: file_get_contents(...)
173
                $message = unserialize(file_get_contents($file . '.sending'));
174
175
                $count += $transport->send($message, $failedRecipients);
0 ignored issues
show
Bug introduced by
It seems like $failedRecipients defined by (array) $failedRecipients on line 160 can also be of type array; however, Swift_Transport::send() does only seem to accept array<integer,string>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
176
177
                unlink($file . '.sending');
178
            } else {
179
                /* This message has just been catched by another process */
180
                continue;
181
            }
182
183
            if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) {
184
                break;
185
            }
186
187
            if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) {
188
                break;
189
            }
190
        }
191
192
        return $count;
193
    }
194
195
    /**
196
     * Returns a random string needed to generate a fileName for the queue.
197
     *
198
     * @param int|null $count <strong>null</strong> use only "uniqid()"<br />
199
     *                        <strong>int</strong> use "random_int()" with a fixed- / fs-safe string
200
     *
201
     * @return string
202
     */
203
    protected function getRandomString($count = null)
204
    {
205
        if ($count === null) {
206
            return uniqid('', true);
207
        }
208
209
        // This string MUST stay FS safe, avoid special chars.
210
        $base = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-';
211
        $ret = '';
212
        $strlen = strlen($base) - 1;
213
        for ($i = 0; $i < $count; ++$i) {
214
            $ret .= $base[\random_int(0, $strlen - 1)];
215
        }
216
217
        return $ret;
218
    }
219
}
220