Issues (273)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Command/ChangesCommand.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace BZIon\Command;
4
5
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
6
use Symfony\Component\Console\Input\InputInterface;
7
use Symfony\Component\Console\Input\InputOption;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Yaml\Yaml;
10
11
class ChangesCommand extends ContainerAwareCommand
12
{
13
    /**
14
     * The date when the latest changes were most recently shown
15
     *
16
     * @var null|\TimeDate
17
     */
18
    private $lastUpdateDate = null;
19
20
    /**
21
     * An array of the most recent changelog entries that were shown to the user
22
     * on the last update
23
     *
24
     * Used to prevent showing the same changelog entries if the user updated
25
     * two times in the same day
26
     *
27
     * @var array
28
     */
29
    private $alreadyListedChanges = array();
30
31
    /**
32
     * {@inheritdoc}
33
     */
34
    protected function configure()
35
    {
36
        $this
37
            ->setName('bzion:changes')
38
            ->setDescription('List new features and bug fixes since the last update')
39
            ->addOption(
40
                'changelog',
41
                'c',
42
                InputOption::VALUE_OPTIONAL,
43
                'The path to the changelog file',
44
                dirname(dirname(__DIR__)) . '/app/changelog.yml'
45
            )
46
            ->addOption(
47
                'lastupdate',
48
                'l',
49
                InputOption::VALUE_OPTIONAL,
50
                'The path to the file containing the date of the last update',
51
                dirname(dirname(__DIR__)) . '/app/lastupdate.yml'
52
            )
53
            ->addOption(
54
                'date',
55
                'd',
56
                InputOption::VALUE_OPTIONAL,
57
                'Show all the changes made since the given date, overrides the lastupdate file'
58
            )
59
            ->addOption(
60
                'read',
61
               null,
62
               InputOption::VALUE_NONE,
63
               'Mark all the changes made before the current date as read'
64
            );
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    protected function execute(InputInterface $input, OutputInterface $output)
71
    {
72
        $lastUpdatePath = $input->getOption('lastupdate');
73
        $date           = $input->getOption('date');
74
        $markRead       = $input->getOption('read');
75
        $changelog      = Yaml::parse($input->getOption('changelog'));
76
77
        $this->parseOptions($lastUpdatePath, $date, $output);
78
79
        // Make sure the changelog dates are properly sorted (more recent to older)
80
        LogCommand::sort($changelog);
81
        $listed = $this->parseChangelog($changelog);
82
83
        if (!$markRead) {
84
            if ($date) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $date of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
85
                $last = $date->isFuture() ? 'next' : 'last';
86
                $since = "in the $last " . $date->diffForHumans(null, true);
87
            } else {
88
                $since = 'since the last update';
89
            }
90
91
            if ($this->isEmpty($listed)) {
92
                $output->writeln("No significant changes $since.");
93
            } else {
94
                $output->writeln("Changes $since:");
95
                $this->renderChangeList($listed, $output);
96
            }
97
        }
98
99
        $this->storeLastUpdate($lastUpdatePath, $date);
100
101
        // Reset properties in case execute() is run again
102
        $this->lastUpdateDate = null;
103
        $this->alreadyListedChanges = array();
104
    }
105
106
    /**
107
     * Parse the command line options concerning the date of the last update
108
     *
109
     * @param  string          $lastUpdatePath The path to the last update file
110
     * @param  string|null     $date           The date command line argument
111
     * @param  OutputInterface $output         The command's output
112
     * @return void
113
     */
114
    private function parseOptions($lastUpdatePath, &$date, $output)
115
    {
116
        $message = null;
117
118
        if ($date) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $date of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
119
            $this->lastUpdateDate = $date = \TimeDate::from($date)->startOfDay();
120
        } elseif (!file_exists($lastUpdatePath)) {
121
            $message = "Last update file not found, a new one is going to be created";
122
        } else {
123
            $message = $this->parseLastUpdate($lastUpdatePath);
124
        }
125
126
        if ($output->isVeryVerbose()) {
127
            $output->writeln($message);
128
129
            if ($this->lastUpdateDate) {
130
                $formattedDate = $this->lastUpdateDate->toFormattedDateString();
131
                $output->writeln("Showing changes since <options=bold>$formattedDate</options=bold>");
132
            }
133
134
            $output->writeln("");
135
        }
136
    }
137
138
    /**
139
     * Parse the last update file
140
     *
141
     * @param  string $path The path to the last update file
142
     * @return string The message to show to the user
143
     */
144
    private function parseLastUpdate($path)
145
    {
146
        $lastUpdate = Yaml::parse($path);
147
        $this->lastUpdateDate = \TimeDate::from($lastUpdate['date']);
148
        $this->alreadyListedChanges = $lastUpdate['changes'];
149
150
        return "Using <options=bold>$path</options=bold>";
151
    }
152
153
    /**
154
     * Get a list of changes that will be shown to the user
155
     *
156
     * @param  array[] $changelog The parsed changelog.yml file
157
     * @return array[] The changes to show to the user
158
     */
159
    private function parseChangelog($changelog)
160
    {
161
        $listed = array();
162
        $firstEntry = true;
163
        $lastChangeDate = \TimeDate::now()->startOfDay();
164
        $lastChanges = array();
165
166
        foreach ($changelog as $date => $changes) {
167
            $date = \TimeDate::from($date);
168
169
            if ($firstEntry) {
170
                // The array has been sorted, the first entry represents the
171
                // most recent change. Store its date so that we don't show the
172
                // same entry many times
173
                $firstEntry = false;
174
175
                if ($lastChangeDate >= $date) {
176
                    $lastChangeDate = $date;
177
                    $lastChanges = $changes;
178
                }
179
            }
180
181
            // Don't list changes that we've listed before
182
            if ($date == $this->lastUpdateDate) {
183
                $this->filterAlreadyListedChanges($changes);
184
            } elseif ($this->lastUpdateDate && $date < $this->lastUpdateDate) {
185
                break;
186
            }
187
188
            $listed = array_merge_recursive($listed, $changes);
189
        }
190
191
        $this->alreadyListedChanges = $lastChanges;
192
        $this->lastUpdateDate = $lastChangeDate;
193
194
        return $listed;
195
    }
196
197
    /**
198
     * Filter out the changes made today that have already been shown to the
199
     * user
200
     * @param  array $changes Today's changes
201
     * @return void
202
     */
203
    private function filterAlreadyListedChanges(&$changes)
204
    {
205
        $alreadyListed = $this->alreadyListedChanges;
206
207
        foreach ($changes as $type => &$changelist) {
208
            $changelist = array_filter($changelist, function ($change) use ($type, $alreadyListed) {
209
                if (!isset($alreadyListed[$type])) {
210
                    return true;
211
                }
212
213
                return !in_array($change, $alreadyListed[$type]);
214
            });
215
        }
216
    }
217
218
    /**
219
     * Show a list of changes in a user-readable format
220
     *
221
     * @param  array[]         $listed The changes that should be listed
222
     * @param  OutputInterface $output The command's output
223
     * @return void
224
     */
225
    private function renderChangeList($listed, OutputInterface $output)
226
    {
227
        $types = array(
228
            'Features' => '<info>[+] %s</info>',
229
            'Bugfixes' => '<comment>[*] %s</comment>',
230
            'Notes'    => '<bg=red;options=bold>[!] %s</bg=red;options=bold>',
231
        );
232
233
        foreach ($types as $type => $format) {
234
            if (isset($listed[$type])) {
235
                foreach ($listed[$type] as $change) {
236
                    $output->writeln(sprintf($format, $change));
237
                }
238
            }
239
        }
240
    }
241
242
    /**
243
     * Store the newest entry's date into the last update file, so that the user
244
     * isn't shown the same changes in the future
245
     *
246
     * @param  string  $path The path to the last update file
247
     * @param  bool $date The date command line argument (used to determine
248
     *                       whether we should store the last update or not)
249
     * @return void
250
     */
251
    private function storeLastUpdate($path, $date)
252
    {
253
        if ($date !== null) {
254
            // The user probably run the command to see old changes, we don't
255
            // consider this a result of an update
256
            return;
257
        }
258
259
        $data = array(
260
            'date'    => $this->lastUpdateDate->toFormattedDateString(),
261
            'changes' => $this->alreadyListedChanges
262
        );
263
264
        file_put_contents($path, Yaml::dump($data, 3));
265
    }
266
267
    /**
268
     * Recursively find out if an array is empty
269
     *
270
     * @param  array   $array The array to test
271
     * @return bool|null
272
     */
273
    private function isEmpty(array $array)
274
    {
275
        if (empty($array)) {
276
            return true;
277
        }
278
279
        foreach ($array as $child) {
280
            if (!is_array($child)) {
281
                return false;
282
            }
283
284
            return self::isEmpty($child);
285
        }
286
287
        return null;
288
    }
289
}
290