Issues (36)

Security Analysis    no request data  

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/Memcached.php (5 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
 *
4
 * This file is part of the Apix Project.
5
 *
6
 * (c) Franck Cassedanne <franck at ouarz.net>
7
 *
8
 * @license     http://opensource.org/licenses/BSD-3-Clause  New BSD License
9
 *
10
 */
11
12
namespace Apix\Cache;
13
14
/**
15
 * Memcached cache wrapper.
16
 *
17
 * @see http://code.google.com/p/memcached/wiki/NewProgrammingTricks
18
 * @see http://dustin.github.com/2011/02/17/memcached-set.html
19
 *
20
 * @package Apix\Cache
21
 * @author Franck Cassedanne <franck at ouarz.net>
22
 */
23
class Memcached extends AbstractCache
24
{
25
    /**
26
     * Holds an injected adapter.
27
     * @var \Memcached
28
     */
29
    protected $adapter = null;
30
31
    /**
32
     * Holds the array of TTLs.
33
     * @var array
34
     */
35
    protected $ttls = array();
36
37
    /**
38
     * Constructor.
39
     *
40
     * @param \Memcached $memcached A Memcached instance.
41
     * @param array      $options   Array of options.
42
     */
43 66
    public function __construct(\Memcached $memcached, array $options = null)
44
    {
45
        // default options
46 66
        $this->options['prefix_key'] = 'key_';  // prefix cache keys
47 66
        $this->options['prefix_tag'] = 'tag_';  // prefix cache tags
48 66
        $this->options['prefix_idx'] = 'idx_';  // prefix cache indexes
49 66
        $this->options['prefix_nsp'] = 'nsp_';  // prefix cache namespaces
50
51 66
        // 'auto' is igbinary or msgpack if available, php otherwise.
52
        $this->options['serializer'] = 'auto';   // auto, php, json, json_array
53 66
                                                 // igBinary and msgpack
54
55 66
        parent::__construct($memcached, $options);
56
57 66
        $memcached->setOption(\Memcached::OPT_COMPRESSION, false);
58 66
59 66
        if ($this->options['tag_enable']) {
60 66
            $memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, false);
61 66
            $this->setSerializer($this->options['serializer']);
62 66
            $this->setNamespace($this->options['prefix_nsp']);
63
        }
64
    }
65
66
    /**
67 18
     * {@inheritdoc}
68
     */
69 18
    public function loadKey($key)
70
    {
71
        return $this->get($this->mapKey($key));
72
    }
73
74
    /**
75 22
     * {@inheritdoc}
76
     */
77 22
    public function loadTag($tag)
78
    {
79
        return $this->getIndex($this->mapTag($tag))->load();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getIndex($...>mapTag($tag))->load(); (Apix\Cache\Indexer\Returns) is incompatible with the return type declared by the interface Apix\Cache\Adapter::loadTag of type array|null.

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...
80
    }
81
82
    /**
83 36
     * {@inheritdoc}
84
     */
85 36
    public function save($data, $key, array $tags = null, $ttl = null)
86
    {
87 36
        $ttl = $this->sanitiseTtl($ttl);
88
89 36
        $mKey = $this->mapKey($key);
90 36
91
        $data = array('data' => $data, 'ttl' => $ttl);
92
        $this->ttls[$mKey] = $ttl;
93 36
94
        // add the item
95 36
        $success = $this->adapter->set($mKey, $data, $ttl);
96
97
        if ($success && $this->options['tag_enable'] && !empty($tags)) {
98 20
99
            // add all the tags to the index key.
100
            $this->getIndex($this->mapIdx($key))->add($tags);
101 20
102 20
            // append the key to each tag.
103 20
            foreach ($tags as $tag) {
104 20
                $this->getIndex($this->mapTag($tag))->add($mKey);
105
            }
106 36
        }
107
108
        return $success;
109
    }
110
111
    /**
112
     * Alias to `Memcached::deleteMulti` or loop `Memcached::delete`.
113
     *
114
     * @param array $items The items to be deleted.
115
     *
116 8
     * @return bool Returns TRUE on success or FALSE on failure.
117
     */
118 8
    protected function deleteMulti($items)
119 8
    {
120
        if (method_exists($this->adapter, 'deleteMulti')) {
121 8
            $this->adapter->deleteMulti($items);
122
123
            return (boolean) $this->adapter->getResultCode() != \Memcached::RES_FAILURE;
124
        }
125
126
        // Fix environments (some HHVM versions) that don't handle deleteMulti.
127
        // @see https://github.com/facebook/hhvm/issues/4602
128
        // @codeCoverageIgnoreStart
129
        $success = true;
130
        foreach ($items as $item) {
131
            $success = $this->adapter->delete($item) && $success;
132
        }
133
134
        return $success;
135
        // @codeCoverageIgnoreEnd
136
    }
137 2
138
    /**
139 2
     * {@inheritdoc}
140 2
     */
141 2
    public function clean(array $tags)
142 2
    {
143 2
        $items = array();
144 2
        foreach ($tags as $tag) {
145
            $keys = $this->loadTag($tag);
146 2
            if (null !== $keys) {
147 2
                foreach ($keys as $key) {
148
                    $items[] = $key;
149 2
                    // $items[] = $this->mapIdx($key);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
150
                }
151
            }
152
            // add the tag to deletion
153 2
            $items[] = $this->mapTag($tag);
154
155 2
            // add the index key for deletion
156
            // $items[] = $this->mapTag($tag);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
157
        }
158
159
        return $this->deleteMulti($items);
160
    }
161 6
162
    /**
163 6
     * {@inheritdoc}
164 6
     */
165
    public function delete($key)
166 6
    {
167 4
        $_key = $this->mapKey($key);
168
        $items = array($_key);
169
170 4
        if ($this->options['tag_enable']) {
171
            $idx_key = $this->mapIdx($key);
172 4
173
            // load the tags from the index key
174 2
            $tags = $this->getIndex($idx_key)->load();
175 2
176 2
            if (is_array($tags)) {
177
                // mark the key as deleted in the tags.
178 2
                foreach ($tags as $tag) {
179 2
                    $this->getIndex($this->mapTag($tag))->remove($_key);
180 4
                }
181
                // delete that index key
182 6
                $items[] = $idx_key;
183
            }
184
        }
185
186
        return $this->deleteMulti($items);
187
    }
188 66
189
    /**
190 66
     * {@inheritdoc}
191 66
     */
192
    public function flush($all = false)
193 4
    {
194
        if (true === $all) {
195
            return $this->adapter->flush();
196 4
        }
197
        $nsKey = $this->options['prefix_nsp'];
198 4
199
        // set a new namespace
200
        $success = $this->setNamespace($nsKey, true);
201
202
        return (boolean) $success;
203
    }
204
205
    /**
206 66
     * {@inheritdoc}
207
     *
208
     * @param string $serializer
209
     */
210
    public function setSerializer($serializer)
211
    {
212
        switch ($serializer) {
213
214
            case 'php':
215
                $opt = \Memcached::SERIALIZER_PHP;
216
            break;
217
218
            // @codeCoverageIgnoreStart
219
            case 'igBinary':
220
                if (!\Memcached::HAVE_IGBINARY) {
221
                    continue;
222
                }
223
                $opt = \Memcached::SERIALIZER_IGBINARY;
224
            break;
225
226
            case 'json':
227
                if (!\Memcached::HAVE_JSON) {
228 66
                    continue;
229 66
                }
230 66
                $opt = \Memcached::SERIALIZER_JSON;
231 66
            break;
232
233 66
            case 'json_array':
234 66
                if (!\Memcached::HAVE_JSON_ARRAY) {
235 66
                    continue;
236 66
                }
237
                $opt = \Memcached::SERIALIZER_JSON_ARRAY;
238
            break;
239
240
            case 'msgpack':
241 4
                if (!\Memcached::HAVE_MSGPACK) {
242
                    continue;
243 4
                }
244
                $opt = \Memcached::SERIALIZER_MSGPACK;
245
            break;
246
            // @codeCoverageIgnoreEnd
247
248
            default:
249
        }
250
251
        if (isset($opt)) {
252
            $this->adapter->setOption(\Memcached::OPT_SERIALIZER, $opt);
253
        }
254 50
    }
255
256 50
    /**
257 50
     * {@inheritdoc}
258 36
     */
259
    public function getSerializer()
260 36
    {
261
        return $this->adapter->getOption(\Memcached::OPT_SERIALIZER);
262
    }
263 25
264
    /**
265
     * Retrieves the cache item for the given id.
266
     *
267
     * @param string $id        The cache id to retrieve.
268
     * @param float  $cas_token The variable to store the CAS token in.
269
     *
270
     * @return mixed|null Returns the cached data or null.
271
     */
272
    public function get($id, &$cas_token = null)
273
    {
274
        $data = $this->adapter->get($id, null, $cas_token);
275
        if ($this->adapter->getResultCode() == \Memcached::RES_SUCCESS) {
276
            $this->ttls[$id] = isset($data['ttl']) ? $data['ttl'] : 0;
277 36
278
            return isset($data['data']) ? $data['data'] : $data;
279 36
        }
280
281
        return;
282
    }
283
284
    /**
285
     * Returns the ttl sanitased for this cache adapter.
286
     *
287
     * The number of seconds may not exceed 60*60*24*30 = 2,592,000 (30 days).
288
     *
289 66
     * @see http://php.net/manual/en/memcached.expiration.php
290
     *
291 66
     * @param int|null $ttl The time-to-live in seconds.
292
     *
293
     * @return int
294
     */
295
    public function sanitiseTtl($ttl)
296
    {
297
        return $ttl > 2592000 ? time() + $ttl : $ttl;
298
    }
299
300
    /**
301
     * Returns the named indexer.
302
     *
303
     * @param string $name The name of the index.
304 66
     *
305
     * @return Indexer\Adapter
306
     */
307 66
    public function getIndex($name)
308
    {
309
        return new Indexer\MemcachedIndexer($name, $this);
310 66
    }
311
312 66
    /**
313
     * Sets the namespace prefix.
314 4
     * Specific to memcache; this sets as 'ns'+integer (incremented).
315 4
     *
316 66
     * @param string $ns
317 66
     * @param bool   $renew
318 66
     * @param string $suffix
319 66
     *
320 66
     * @return int
321
     */
322
    public function setNamespace($ns, $renew = false, $suffix = '_')
323 66
    {
324 66
        // temporally set the namespace to null
325
        $this->adapter->setOption(\Memcached::OPT_PREFIX_KEY, null);
326 66
327
        // mark the current namespace for future deletion
328
        $this->getIndex($this->mapIdx($ns))->remove($this->getNamespace());
329
330
        if ($renew) {
331
            // increment the namespace counter
332
            $counter = $this->increment($ns);
333
        } else {
334 66
            $counter = $this->adapter->get($ns);
335
            if (false === $counter) {
336 66
                $counter = 1;
337
                $this->adapter->set($ns, $counter);
338
            }
339
        }
340
341
        $ns .= $counter.$suffix;
342
        $this->adapter->setOption(\Memcached::OPT_PREFIX_KEY, $ns);
343
344
        return $counter;
345
    }
346 66
347
    /**
348 66
     * Returns the namespace.
349
     *
350
     * @return string
351
     */
352
    public function getNamespace()
353
    {
354
        return $this->adapter->getOption(\Memcached::OPT_PREFIX_KEY);
355
    }
356
357
    /**
358 6
     * Returns a prefixed and sanitased cache id.
359
     *
360
     * @param string $key The base key to prefix.
361
     *
362
     * @return string
363
     */
364
    public function mapIdx($key)
365 6
    {
366 6
        return $this->sanitise($this->options['prefix_idx'].$key);
367 2
    }
368 2
369 2
    /**
370 6
     * Increments the value of the given key.
371
     *
372
     * @param string $key The key to increment.
373 6
     *
374
     * @return int|bool Returns the new item's value on success or FALSE on failure.
375
     */
376
    public function increment($key)
377
    {
378
        // if (true === \Memcached::OPT_BINARY_PROTOCOL) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
379
        //     // Increment will initialize the value (if not available)
380
        //     // only when OPT_BINARY_PROTOCOL is set to true!
381
        //     return $this->adapter->increment($key, 1);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
382
        // }
383 4
        $counter = $this->adapter->get($key);
384
        if (false === $counter) {
385 4
            $counter = 1;
386
            $this->adapter->set($key, $counter);
387 4
        } else {
388 4
            $counter = $this->adapter->increment($key);
389 4
        }
390 4
391 4
        return $counter;
392 4
    }
393 4
394
    /**
395 4
     * {@inheritdoc}
396
     *
397
     * The number of seconds may not exceed 60*60*24*30 = 2,592,000 (30 days).
398 2
     *
399
     * @see http://php.net/manual/en/memcached.expiration.php
400
     */
401
    public function getTtl($key)
402
    {
403
        $mKey = $this->mapKey($key);
404
405
        if (!isset($this->ttls[$mKey])) {
406
            $data = $this->adapter->get($mKey, null, $cas_token);
407
            $this->ttls[$mKey] =
408
                 $this->adapter->getResultCode() == \Memcached::RES_SUCCESS
409
                    ? (isset($data['ttl']) ? $data['ttl'] : 0)
410
                    : false;
411
        }
412
413
        return $this->ttls[$mKey];
414
    }
415
}
416