Patched to Drupal 8.4.8 level. See https://www.drupal.org/sa-core-2018-004 and patch...
[yaffs-website] / vendor / symfony / http-kernel / Profiler / FileProfilerStorage.php
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\HttpKernel\Profiler;
13
14 /**
15  * Storage for profiler using files.
16  *
17  * @author Alexandre Salomé <alexandre.salome@gmail.com>
18  */
19 class FileProfilerStorage implements ProfilerStorageInterface
20 {
21     /**
22      * Folder where profiler data are stored.
23      *
24      * @var string
25      */
26     private $folder;
27
28     /**
29      * Constructs the file storage using a "dsn-like" path.
30      *
31      * Example : "file:/path/to/the/storage/folder"
32      *
33      * @param string $dsn The DSN
34      *
35      * @throws \RuntimeException
36      */
37     public function __construct($dsn)
38     {
39         if (0 !== strpos($dsn, 'file:')) {
40             throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn));
41         }
42         $this->folder = substr($dsn, 5);
43
44         if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) {
45             throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
46         }
47     }
48
49     /**
50      * {@inheritdoc}
51      */
52     public function find($ip, $url, $limit, $method, $start = null, $end = null, $statusCode = null)
53     {
54         $file = $this->getIndexFilename();
55
56         if (!file_exists($file)) {
57             return array();
58         }
59
60         $file = fopen($file, 'r');
61         fseek($file, 0, SEEK_END);
62
63         $result = array();
64         while (count($result) < $limit && $line = $this->readLineFromFile($file)) {
65             $values = str_getcsv($line);
66             list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode) = $values;
67             $csvTime = (int) $csvTime;
68
69             if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method) || $statusCode && false === strpos($csvStatusCode, $statusCode)) {
70                 continue;
71             }
72
73             if (!empty($start) && $csvTime < $start) {
74                 continue;
75             }
76
77             if (!empty($end) && $csvTime > $end) {
78                 continue;
79             }
80
81             $result[$csvToken] = array(
82                 'token' => $csvToken,
83                 'ip' => $csvIp,
84                 'method' => $csvMethod,
85                 'url' => $csvUrl,
86                 'time' => $csvTime,
87                 'parent' => $csvParent,
88                 'status_code' => $csvStatusCode,
89             );
90         }
91
92         fclose($file);
93
94         return array_values($result);
95     }
96
97     /**
98      * {@inheritdoc}
99      */
100     public function purge()
101     {
102         $flags = \FilesystemIterator::SKIP_DOTS;
103         $iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
104         $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
105
106         foreach ($iterator as $file) {
107             if (is_file($file)) {
108                 unlink($file);
109             } else {
110                 rmdir($file);
111             }
112         }
113     }
114
115     /**
116      * {@inheritdoc}
117      */
118     public function read($token)
119     {
120         if (!$token || !file_exists($file = $this->getFilename($token))) {
121             return;
122         }
123
124         return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
125     }
126
127     /**
128      * {@inheritdoc}
129      *
130      * @throws \RuntimeException
131      */
132     public function write(Profile $profile)
133     {
134         $file = $this->getFilename($profile->getToken());
135
136         $profileIndexed = is_file($file);
137         if (!$profileIndexed) {
138             // Create directory
139             $dir = dirname($file);
140             if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
141                 throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
142             }
143         }
144
145         // Store profile
146         $data = array(
147             'token' => $profile->getToken(),
148             'parent' => $profile->getParentToken(),
149             'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()),
150             'data' => $profile->getCollectors(),
151             'ip' => $profile->getIp(),
152             'method' => $profile->getMethod(),
153             'url' => $profile->getUrl(),
154             'time' => $profile->getTime(),
155             'status_code' => $profile->getStatusCode(),
156         );
157
158         if (false === file_put_contents($file, serialize($data))) {
159             return false;
160         }
161
162         if (!$profileIndexed) {
163             // Add to index
164             if (false === $file = fopen($this->getIndexFilename(), 'a')) {
165                 return false;
166             }
167
168             fputcsv($file, array(
169                 $profile->getToken(),
170                 $profile->getIp(),
171                 $profile->getMethod(),
172                 $profile->getUrl(),
173                 $profile->getTime(),
174                 $profile->getParentToken(),
175                 $profile->getStatusCode(),
176             ));
177             fclose($file);
178         }
179
180         return true;
181     }
182
183     /**
184      * Gets filename to store data, associated to the token.
185      *
186      * @param string $token
187      *
188      * @return string The profile filename
189      */
190     protected function getFilename($token)
191     {
192         // Uses 4 last characters, because first are mostly the same.
193         $folderA = substr($token, -2, 2);
194         $folderB = substr($token, -4, 2);
195
196         return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token;
197     }
198
199     /**
200      * Gets the index filename.
201      *
202      * @return string The index filename
203      */
204     protected function getIndexFilename()
205     {
206         return $this->folder.'/index.csv';
207     }
208
209     /**
210      * Reads a line in the file, backward.
211      *
212      * This function automatically skips the empty lines and do not include the line return in result value.
213      *
214      * @param resource $file The file resource, with the pointer placed at the end of the line to read
215      *
216      * @return mixed A string representing the line or null if beginning of file is reached
217      */
218     protected function readLineFromFile($file)
219     {
220         $line = '';
221         $position = ftell($file);
222
223         if (0 === $position) {
224             return;
225         }
226
227         while (true) {
228             $chunkSize = min($position, 1024);
229             $position -= $chunkSize;
230             fseek($file, $position);
231
232             if (0 === $chunkSize) {
233                 // bof reached
234                 break;
235             }
236
237             $buffer = fread($file, $chunkSize);
238
239             if (false === ($upTo = strrpos($buffer, "\n"))) {
240                 $line = $buffer.$line;
241                 continue;
242             }
243
244             $position += $upTo;
245             $line = substr($buffer, $upTo + 1).$line;
246             fseek($file, max(0, $position), SEEK_SET);
247
248             if ('' !== $line) {
249                 break;
250             }
251         }
252
253         return '' === $line ? null : $line;
254     }
255
256     protected function createProfileFromData($token, $data, $parent = null)
257     {
258         $profile = new Profile($token);
259         $profile->setIp($data['ip']);
260         $profile->setMethod($data['method']);
261         $profile->setUrl($data['url']);
262         $profile->setTime($data['time']);
263         $profile->setStatusCode($data['status_code']);
264         $profile->setCollectors($data['data']);
265
266         if (!$parent && $data['parent']) {
267             $parent = $this->read($data['parent']);
268         }
269
270         if ($parent) {
271             $profile->setParent($parent);
272         }
273
274         foreach ($data['children'] as $token) {
275             if (!$token || !file_exists($file = $this->getFilename($token))) {
276                 continue;
277             }
278
279             $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
280         }
281
282         return $profile;
283     }
284 }