Completed
Push — master ( 8b874d...bc64e6 )
by Jan-Paul
03:00
created

HtaccessFirewall::deactivate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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