StaticWebServer::getWebroot()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Jalle19\ReactHttpStatic;
4
5
use Dflydev\ApacheMimeTypes\Parser as ContentTypeParser;
6
use Dflydev\ApacheMimeTypes\PhpRepository;
7
use Jalle19\ReactHttpStatic\Authentication\Handler\HandlerInterface;
8
use Psr\Log\LoggerInterface;
9
use React\Http\Server;
10
use React\Http\Request;
11
use React\Http\Response;
12
13
/**
14
 * Class StaticWebServer
15
 * @package   Jalle19\ReactHttpStatic
16
 * @copyright Copyright &copy; Sam Stenvall 2016-
17
 * @license   @license https://opensource.org/licenses/MIT
18
 */
19
class StaticWebServer
20
{
21
22
    /**
23
     * @var Server
24
     */
25
    private $httpServer;
26
27
    /**
28
     * @var string the absolute path to the directory where files are served from
29
     */
30
    private $webroot;
31
32
    /**
33
     * @var HandlerInterface
34
     */
35
    private $authenticationHandler;
36
37
    /**
38
     * @var LoggerInterface
39
     */
40
    private $logger;
41
42
    /**
43
     * @var ContentTypeParser
44
     */
45
    private $contentTypeParser;
46
47
    /**
48
     * @var array
49
     */
50
    private $indexFiles = [
51
        'index.htm',
52
        'index.html',
53
    ];
54
55
56
    /**
57
     * StaticWebServer constructor.
58
     *
59
     * @param Server               $httpServer
60
     * @param string               $webroot
61
     * @param LoggerInterface|null $logger
62
     */
63
    public function __construct(Server $httpServer, $webroot, LoggerInterface $logger = null)
64
    {
65
        if (!file_exists($webroot)) {
66
            throw new \InvalidArgumentException('The specified webroot path does not exist');
67
        }
68
69
        $this->httpServer = $httpServer;
70
        $this->webroot    = $webroot;
71
        $this->logger     = $logger;
72
73
        // Attach the request handler
74
        $this->httpServer->on('request', [$this, 'handleRequest']);
75
76
        // Configure the content type parser
77
        $this->contentTypeParser = new ContentTypeParser();
78
    }
79
80
81
    /**
82
     * @return string
83
     */
84
    public function getWebroot()
85
    {
86
        return $this->webroot;
87
    }
88
89
90
    /**
91
     * @param string $webroot
92
     *
93
     * @return StaticWebServer
94
     */
95
    public function setWebroot($webroot)
96
    {
97
        $this->webroot = $webroot;
98
99
        return $this;
100
    }
101
102
103
    /**
104
     * @return LoggerInterface
105
     */
106
    public function getLogger()
107
    {
108
        return $this->logger;
109
    }
110
111
112
    /**
113
     * @param LoggerInterface $logger
114
     *
115
     * @return StaticWebServer
116
     */
117
    public function setLogger($logger)
118
    {
119
        $this->logger = $logger;
120
121
        return $this;
122
    }
123
124
125
    /**
126
     * @return array
127
     */
128
    public function getIndexFiles()
129
    {
130
        return $this->indexFiles;
131
    }
132
133
134
    /**
135
     * @param array $indexFiles
136
     *
137
     * @return StaticWebServer
138
     */
139
    public function setIndexFiles($indexFiles)
140
    {
141
        $this->indexFiles = $indexFiles;
142
143
        return $this;
144
    }
145
146
147
    /**
148
     * @param HandlerInterface|null $authenticationHandler
149
     *
150
     * @return StaticWebServer
151
     */
152
    public function setAuthenticationHandler($authenticationHandler)
153
    {
154
        $this->authenticationHandler = $authenticationHandler;
155
156
        return $this;
157
    }
158
159
160
    /**
161
     * @param Request  $request
162
     * @param Response $response
163
     */
164
    public function handleRequest(Request $request, Response $response)
165
    {
166
        $requestPath = $request->getPath();
167
        $filePath    = $this->resolvePath($requestPath);
168
169
        if ($this->logger !== null) {
170
            $this->logger->debug('Got HTTP request (request path: {requestPath}, resolved path: {resolvedPath})', [
171
                'requestPath'  => $requestPath,
172
                'resolvedPath' => $filePath,
173
            ]);
174
        }
175
176
        if ($this->authenticationHandler instanceof HandlerInterface) {
177
            if (!$this->authenticationHandler->handle($request)) {
178
                if ($this->logger !== null) {
179
                    $this->logger->warning('Client failed authentication');
180
                }
181
182
                $this->authenticationHandler->requireAuthentication($response);
183
184
                return;
185
            }
186
        }
187
188
        if (file_exists($filePath)) {
189
            if (is_readable($filePath)) {
190
                $response->writeHead(200, [
191
                    'Content-Type' => $this->getContentType($filePath),
0 ignored issues
show
Security Bug introduced by
It seems like $filePath defined by $this->resolvePath($requestPath) on line 167 can also be of type false; however, Jalle19\ReactHttpStatic\...erver::getContentType() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
192
                ]);
193
194
                $response->end(file_get_contents($filePath));
195 View Code Duplication
            } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
196
                if ($this->logger !== null) {
197
                    $this->logger->error('HTTP request failed, file unreadable ({filePath})', [
198
                        'filePath' => $filePath,
199
                    ]);    
200
                }
201
                
202
                $response->writeHead(403, ['Content-Type' => 'text/plain']);
203
                $response->end("Forbidden\n");
204
            }
205 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
            if ($this->logger !== null) {
207
                $this->logger->error('HTTP request failed, file not found ({filePath})', [
208
                    'filePath' => $filePath,
209
                ]);    
210
            }
211
            
212
            $response->writeHead(404, ['Content-Type' => 'text/plain']);
213
            $response->end("Not found\n");
214
        }
215
    }
216
217
218
    /**
219
     * @param string $requestPath
220
     *
221
     * @return bool|string
222
     */
223
    public function resolvePath($requestPath)
224
    {
225
        $filePath = $this->webroot . $requestPath;
226
227
        if ($requestPath === '/') {
228
            foreach ($this->indexFiles as $indexFile) {
229
                $indexPath = $filePath . $indexFile;
230
231
                if (file_exists($indexPath)) {
232
                    return $indexPath;
233
                }
234
            }
235
236
            return false;
237
        }
238
239
        return $filePath;
240
    }
241
242
243
    /**
244
     * @param string $filePath
245
     *
246
     * @return string
247
     */
248
    public function getContentType($filePath)
249
    {
250
        $pathInfo = pathinfo($filePath);
251
252
        if (!isset($pathInfo['extension'])) {
253
            $extension = '';
254
        } else {
255
            $extension = $pathInfo['extension'];
256
        }
257
258
        $repository = new PhpRepository();
259
        $type = $repository->findType($extension);
260
261
        if ($type === null) {
262
            $type = 'text/plain';
263
        }
264
265
        return $type;
266
    }
267
268
}
269