Completed
Push — master ( bc64e6...82952f )
by Jan-Paul
02:35
created

HtaccessFirewall::removeLine()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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