Completed
Push — master ( a71ff0...f66167 )
by Jan-Paul
02:15
created

HtaccessFirewall::readLinesWithPrefix()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 8
nop 1
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
    public function set403Message($message)
118
    {
119
        $message = preg_replace('/\s+/', ' ', $message);
120
        $message = trim($message);
121
        $message = str_replace('"', '', $message);
122
123
        $line = 'ErrorDocument 403 "' . $message . '"';
124
125
        $insertion = array_merge(
126
            array('order allow,deny'),
127
            array($line),
128
            $this->readLinesWithPrefix('deny from '),
129
            array('allow from all')
130
        );
131
132
        $this->writeLines($insertion);
133
    }
134
135
    /**
136
     * Remove 403 error message.
137
     */
138
    public function remove403Message()
139
    {
140
        $this->removeLine('ErrorDocument 403 ');
141
    }
142
143
    /**
144
     * Check whether Htaccess file exists.
145
     *
146
     * @return bool
147
     */
148
    public function exists()
149
    {
150
        return $this->fileSystem->exists($this->path);
151
    }
152
153
    /**
154
     * Check whether Htaccess file exists and is readable.
155
     *
156
     * @return bool
157
     */
158
    public function readable()
159
    {
160
        return $this->fileSystem->readable($this->path);
161
    }
162
163
    /**
164
     * Check whether Htaccess file exists and is writable.
165
     *
166
     * @return bool
167
     */
168
    public function writable()
169
    {
170
        return $this->fileSystem->writable($this->path);
171
    }
172
173
    /**
174
     * Add single line.
175
     *
176
     * @param string $line
177
     */
178
    private function addLine($line)
179
    {
180
        $insertion = array_merge(
181
            array('order allow,deny'),
182
            $this->readLinesWithPrefix(array('ErrorDocument 403 ', 'deny from ')),
183
            array($line),
184
            array('allow from all')
185
        );
186
187
        $this->writeLines(array_unique($insertion));
188
    }
189
190
    /**
191
     * Remove single line.
192
     *
193
     * @param string $content
194
     */
195
    private function removeLine($content)
196
    {
197
        $lines = $this->readLines();
198
199
        $insertion = array();
200
        foreach ($lines as $line) {
201
            if (strpos($line, $content) !== 0) {
202
                $insertion[] = $line;
203
            }
204
        }
205
206
        $this->writeLines($insertion);
207
    }
208
209
    /**
210
     * Get array of prefixed lines in section.
211
     *
212
     * @param string|string[] $prefixes
213
     *
214
     * @return string[]
215
     */
216
    private function readLinesWithPrefix($prefixes)
217
    {
218
        if (!is_array($prefixes)) {
219
            $prefixes = array($prefixes);
220
        }
221
222
        $lines = $this->readLines();
223
224
        $prefixedLines = array();
225
        foreach ($lines as $line) {
226
            foreach ($prefixes as $prefix) {
227
                if (strpos($line, $prefix) === 0) {
228
                    $prefixedLines[] = $line;
229
                }
230
            }
231
        }
232
233
        return $prefixedLines;
234
    }
235
236
    /**
237
     * Get array of all lines in section.
238
     *
239
     * @return string[]
240
     */
241
    private function readLines()
242
    {
243
        $lines = $this->fileSystem->read($this->path);
244
245
        $linesInSection = array();
246
        $inSection = false;
247
        foreach ($lines as $line) {
248
            if ($this->isEndOfSection($line)) {
249
                break;
250
            }
251
            if ($inSection) {
252
                $linesInSection[] = $line;
253
            }
254
            if ($this->isBeginOfSection($line)) {
255
                $inSection = true;
256
            }
257
        }
258
259
        return $linesInSection;
260
    }
261
262
    /**
263
     * Write array of lines to section.
264
     *
265
     * @param string[] $lines
266
     *
267
     * @throws FileException
268
     */
269
    private function writeLines($lines)
270
    {
271
        $oldLines = $this->fileSystem->read($this->path);
272
273
        $newLines = array();
274
        $sectionExists = false;
275
        $inSection = false;
276
        foreach ($oldLines as $oldLine) {
277
            if ($this->isBeginOfSection($oldLine)) {
278
                $inSection = true;
279
            }
280
            if (!$inSection) {
281
                $newLines[] = $oldLine;
282
            }
283
            if ($this->isEndOfSection($oldLine)) {
284
                $newLines = array_merge(
285
                    $newLines,
286
                    array('# BEGIN ' . self::$sectionLabel),
287
                    $lines,
288
                    array('# END ' . self::$sectionLabel)
289
                );
290
291
                $sectionExists = true;
292
                $inSection = false;
293
            }
294
        }
295
296
        if ($inSection && !$sectionExists) {
297
            throw new FileException('Missing END marker in Htaccess file.');
298
        }
299
300
        if (!$sectionExists) {
301
            $newLines = array_merge(
302
                $oldLines,
303
                array('# BEGIN ' . self::$sectionLabel),
304
                $lines,
305
                array('# END ' . self::$sectionLabel)
306
            );
307
        }
308
309
        $this->fileSystem->write($this->path, $newLines);
310
    }
311
312
    /**
313
     * Check whether line is the begin of the section.
314
     *
315
     * @param $line
316
     *
317
     * @return bool
318
     */
319
    private function isBeginOfSection($line)
320
    {
321
        return strpos($line, '# BEGIN ' . self::$sectionLabel) === 0;
322
    }
323
324
    /**
325
     * Check whether line is the end of the section.
326
     *
327
     * @param $line
328
     *
329
     * @return bool
330
     */
331
    private function isEndOfSection($line)
332
    {
333
        return strpos($line, '# END ' . self::$sectionLabel) === 0;
334
    }
335
}
336