HtaccessFirewall::readable()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace HtaccessFirewall;
4
5
use HtaccessFirewall\Filesystem\Filesystem;
6
use HtaccessFirewall\Filesystem\BuiltInFilesystem;
7
use HtaccessFirewall\Filesystem\Exception\FileException;
8
use HtaccessFirewall\Host\Host;
9
10
/**
11
 * Firewall using Htaccess files.
12
 */
13
class HtaccessFirewall
14
{
15
    /**
16
     * @var string
17
     */
18
    public static $sectionLabel = 'Firewall';
19
20
    /**
21
     * @var string
22
     */
23
    private $path;
24
25
    /**
26
     * @var Filesystem
27
     */
28
    private $fileSystem;
29
30
    /**
31
     * Initialize HtaccessFirewall.
32
     *
33
     * @param $path
34
     * @param Filesystem $fileSystem
35
     */
36
    public function __construct($path, Filesystem $fileSystem = null)
37
    {
38
        $this->fileSystem = $fileSystem ?: new BuiltInFilesystem();
39
40
        $this->path = $path;
41
    }
42
43
    /**
44
     * Deny host.
45
     *
46
     * @param Host $host
47
     */
48
    public function deny(Host $host)
49
    {
50
        $this->addLine('deny from ' . $host->toString());
51
    }
52
53
    /**
54
     * Undeny host.
55
     *
56
     * @param Host $host
57
     */
58
    public function undeny(Host $host)
59
    {
60
        $this->removeLine('deny from ' . $host->toString());
61
    }
62
63
    /**
64
     * Get all denied hosts.
65
     *
66
     * @return string[]
67
     */
68
    public function getDenied()
69
    {
70
        $lines = $this->readLinesWithPrefix('deny from ');
71
72
        foreach ($lines as $key => $line) {
73
            $host = substr($line, 10);
74
            $lines[$key] = $host;
75
        }
76
77
        return $lines;
78
    }
79
80
    /**
81
     * Deactivate all denials.
82
     */
83
    public function deactivate()
84
    {
85
        $lines = $this->readLinesWithPrefix(array('ErrorDocument 403 ', 'deny from '));
86
87
        $insertion = array();
88
        foreach ($lines as $line) {
89
            $insertion[] = '#' . $line;
90
        }
91
92
        $this->writeLines($insertion);
93
    }
94
95
    /**
96
     * Reactivate all deactivated denials.
97
     */
98
    public function reactivate()
99
    {
100
        $lines = $this->readLinesWithPrefix(array('#ErrorDocument 403 ', '#deny from '));
101
102
        $insertion = array();
103
        $insertion[] = 'order allow,deny';
104
        foreach ($lines as $line) {
105
            $insertion[] = substr($line, 1);
106
        }
107
        $insertion[] = 'allow from all';
108
109
        $this->writeLines($insertion);
110
    }
111
112
    /**
113
     * Set 403 error message.
114
     *
115
     * @param string $message
116
     *
117
     * @return string
118
     */
119
    public function set403Message($message)
120
    {
121
        if (empty($message)) {
122
            $this->remove403Message();
123
            return '';
124
        }
125
126
        $message = preg_replace('/\s+/', ' ', $message);
127
        $message = trim($message);
128
        $message = htmlentities($message);
129
130
        $line = 'ErrorDocument 403 "' . $message . '"';
131
132
        $insertion = array_merge(
133
            array('order allow,deny'),
134
            array($line),
135
            $this->readLinesWithPrefix('deny from '),
136
            array('allow from all')
137
        );
138
139
        $this->writeLines($insertion);
140
141
        return $message;
142
    }
143
144
    /**
145
     * Remove 403 error message.
146
     */
147
    public function remove403Message()
148
    {
149
        $this->removeLine('ErrorDocument 403 ');
150
    }
151
152
    /**
153
     * Check whether Htaccess file exists.
154
     *
155
     * @return bool
156
     */
157
    public function exists()
158
    {
159
        return $this->fileSystem->exists($this->path);
160
    }
161
162
    /**
163
     * Check whether Htaccess file exists and is readable.
164
     *
165
     * @return bool
166
     */
167
    public function readable()
168
    {
169
        return $this->fileSystem->readable($this->path);
170
    }
171
172
    /**
173
     * Check whether Htaccess file exists and is writable.
174
     *
175
     * @return bool
176
     */
177
    public function writable()
178
    {
179
        return $this->fileSystem->writable($this->path);
180
    }
181
182
    /**
183
     * Add single line.
184
     *
185
     * @param string $line
186
     */
187
    private function addLine($line)
188
    {
189
        $insertion = array_merge(
190
            array('order allow,deny'),
191
            $this->readLinesWithPrefix(array('ErrorDocument 403 ', 'deny from ')),
192
            array($line),
193
            array('allow from all')
194
        );
195
196
        $this->writeLines(array_unique($insertion));
197
    }
198
199
    /**
200
     * Remove single line.
201
     *
202
     * @param string $content
203
     */
204
    private function removeLine($content)
205
    {
206
        $lines = $this->readLines();
207
208
        $insertion = array();
209
        foreach ($lines as $line) {
210
            if (strpos($line, $content) !== 0) {
211
                $insertion[] = $line;
212
            }
213
        }
214
215
        $this->writeLines($insertion);
216
    }
217
218
    /**
219
     * Get array of prefixed lines in section.
220
     *
221
     * @param string|string[] $prefixes
222
     *
223
     * @return string[]
224
     */
225
    private function readLinesWithPrefix($prefixes)
226
    {
227
        if (!is_array($prefixes)) {
228
            $prefixes = array($prefixes);
229
        }
230
231
        $lines = $this->readLines();
232
233
        $prefixedLines = array();
234
        foreach ($lines as $line) {
235
            foreach ($prefixes as $prefix) {
236
                if (strpos($line, $prefix) === 0) {
237
                    $prefixedLines[] = $line;
238
                }
239
            }
240
        }
241
242
        return $prefixedLines;
243
    }
244
245
    /**
246
     * Get array of all lines in section.
247
     *
248
     * @return string[]
249
     */
250
    private function readLines()
251
    {
252
        $lines = $this->fileSystem->read($this->path);
253
254
        $linesInSection = array();
255
        $inSection = false;
256
        foreach ($lines as $line) {
257
            if ($this->isEndOfSection($line)) {
258
                break;
259
            }
260
            if ($inSection) {
261
                $linesInSection[] = $line;
262
            }
263
            if ($this->isBeginOfSection($line)) {
264
                $inSection = true;
265
            }
266
        }
267
268
        return $linesInSection;
269
    }
270
271
    /**
272
     * Write array of lines to section.
273
     *
274
     * @param string[] $lines
275
     *
276
     * @throws FileException
277
     */
278
    private function writeLines($lines)
279
    {
280
        $oldLines = $this->fileSystem->read($this->path);
281
282
        $newLines = array();
283
        $sectionExists = false;
284
        $inSection = false;
285
        foreach ($oldLines as $oldLine) {
286
            if ($this->isBeginOfSection($oldLine)) {
287
                $inSection = true;
288
            }
289
            if (!$inSection) {
290
                $newLines[] = $oldLine;
291
            }
292
            if ($this->isEndOfSection($oldLine)) {
293
                $newLines = array_merge(
294
                    $newLines,
295
                    array('# BEGIN ' . self::$sectionLabel),
296
                    $lines,
297
                    array('# END ' . self::$sectionLabel)
298
                );
299
300
                $sectionExists = true;
301
                $inSection = false;
302
            }
303
        }
304
305
        if ($inSection && !$sectionExists) {
306
            throw new FileException('Missing END marker in Htaccess file.');
307
        }
308
309
        if (!$sectionExists) {
310
            $newLines = array_merge(
311
                $oldLines,
312
                array('# BEGIN ' . self::$sectionLabel),
313
                $lines,
314
                array('# END ' . self::$sectionLabel)
315
            );
316
        }
317
318
        $this->fileSystem->write($this->path, $newLines);
319
    }
320
321
    /**
322
     * Check whether line is the begin of the section.
323
     *
324
     * @param $line
325
     *
326
     * @return bool
327
     */
328
    private function isBeginOfSection($line)
329
    {
330
        return strpos($line, '# BEGIN ' . self::$sectionLabel) === 0;
331
    }
332
333
    /**
334
     * Check whether line is the end of the section.
335
     *
336
     * @param $line
337
     *
338
     * @return bool
339
     */
340
    private function isEndOfSection($line)
341
    {
342
        return strpos($line, '# END ' . self::$sectionLabel) === 0;
343
    }
344
}
345