Completed
Push — master ( e3a6ad...a80db0 )
by Krishnaprasad
04:55
created

src/ClamavValidator/ClamavValidator.php (1 issue)

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 Sunspikes\ClamavValidator;
4
5
use Illuminate\Contracts\Translation\Translator;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\Config;
8
use Illuminate\Validation\Validator;
9
use Xenolope\Quahog\Client as QuahogClient;
10
use Socket\Raw\Factory as SocketFactory;
11
use Symfony\Component\HttpFoundation\File\UploadedFile;
12
13
class ClamavValidator extends Validator
14
{
15
    /**
16
     * Creates a new instance of ClamavValidator.
17
     *
18
     * ClamavValidator constructor.
19
     * @param Translator $translator
20
     * @param array      $data
21
     * @param array      $rules
22
     * @param array      $messages
23
     * @param array      $customAttributes
24
     */
25 4
    public function __construct(
26
        Translator $translator,
27
        array $data,
28
        array $rules,
29
        array $messages = [],
30
        array $customAttributes = []
31
    ) {
32 4
        parent::__construct($translator, $data, $rules, $messages, $customAttributes);
33 4
    }
34
35
    /**
36
     * Validate the uploaded file for virus/malware with ClamAV.
37
     *
38
     * @param  $attribute   string
39
     * @param  $value       mixed
40
     * @param  $parameters  array
41
     *
42
     * @return boolean
43
     * @throws ClamavValidatorException
44
     */
45 3
    public function validateClamav($attribute, $value, $parameters)
46
    {
47 3
        if (true === Config::get('clamav.skip_validation')) {
48
            return true;
49
        }
50
51 3
        $file = $this->getFilePath($value);
52 3
        if (! is_readable($file)) {
53 1
            throw ClamavValidatorException::forNonReadableFile($file);
54
        }
55
56
        try {
57 2
            $socket  = $this->getClamavSocket();
58 2
            $scanner = $this->createQuahogScannerClient($socket);
59 2
            $result  = $scanner->scanResourceStream(fopen($file, 'rb'));
60 2
        } catch (\Exception $exception) {
61
            throw ClamavValidatorException::forClientException($exception);
62
        }
63
64 2
        if (QuahogClient::RESULT_ERROR === $result['status']) {
65
            throw ClamavValidatorException::forScanResult($result);
66
        }
67
68
        // Check if scan result is clean
69 2
        return QuahogClient::RESULT_OK === $result['status'];
70
    }
71
72
    /**
73
     * Guess the ClamAV socket.
74
     *
75
     * @return string
76
     */
77 2
    protected function getClamavSocket()
78
    {
79 2
        $preferredSocket = Config::get('clamav.preferred_socket');
80
81 2
        if ($preferredSocket === 'unix_socket') {
82 2
            $unixSocket = Config::get('clamav.unix_socket');
83 2
            if (file_exists($unixSocket)) {
84 2
                return 'unix://' . $unixSocket;
85
            }
86
        }
87
88
        // We use the tcp_socket as fallback as well
89
        return Config::get('clamav.tcp_socket');
90
    }
91
92
    /**
93
     * Return the file path from the passed object.
94
     *
95
     * @param mixed $file
96
     * @return string
97
     */
98 3
    protected function getFilePath($file)
99
    {
100
        // if were passed an instance of UploadedFile, return the path
101 3
        if ($file instanceof UploadedFile) {
102
            return $file->getPathname();
103
        }
104
105
        // if we're passed a PHP file upload array, return the "tmp_name"
106 3
        if (is_array($file) && null !== Arr::get($file, 'tmp_name')) {
107
            return $file['tmp_name'];
108
        }
109
110
        // fallback: we were likely passed a path already
111 3
        return $file;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $file; (object|integer|double|string|null|boolean|array) is incompatible with the return type documented by Sunspikes\ClamavValidato...vValidator::getFilePath of type string.

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...
112
    }
113
114
    /**
115
     * Create a new quahog ClamAV scanner client.
116
     *
117
     * @param string $socket
118
     * @return QuahogClient
119
     */
120 2
    protected function createQuahogScannerClient($socket)
121
    {
122
        // Create a new client socket instance
123 2
        $client = (new SocketFactory())->createClient($socket);
124
125 2
        return new QuahogClient($client, Config::get('clamav.socket_read_timeout'), PHP_NORMAL_READ);
126
    }
127
}
128