Completed
Push — master ( bf6d6b...0724eb )
by Jan-Paul
02:49
created

HtaccessFirewall::readLinesWithPrefix()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 19
rs 8.8571
cc 5
eloc 10
nc 8
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
     * Add single line.
82
     *
83
     * @param string $line
84
     */
85
    private function addLine($line)
86
    {
87
        $insertion = array_merge(
88
            ['order allow,deny'],
89
            $this->readLinesWithPrefix('deny from '),
90
            [$line],
91
            ['allow from all']
92
        );
93
94
        $this->writeLines(array_unique($insertion));
95
    }
96
97
    /**
98
     * Remove single line.
99
     *
100
     * @param string $line
101
     */
102
    private function removeLine($line)
103
    {
104
        $insertion = $this->readLines();
105
106
        $lineToRemove = array_search($line, $insertion);
107
        if ($lineToRemove === false) {
108
            return;
109
        }
110
111
        unset($insertion[$lineToRemove]);
112
113
        $this->writeLines($insertion);
114
    }
115
116
    /**
117
     * Get array of prefixed lines in section.
118
     *
119
     * @param string|string[] $prefixes
120
     *
121
     * @return string[]
122
     */
123
    private function readLinesWithPrefix($prefixes)
124
    {
125
        if (!is_array($prefixes)) {
126
            $prefixes = [$prefixes];
127
        }
128
129
        $lines = $this->readLines();
130
131
        $prefixedLines = [];
132
        foreach ($lines as $line) {
133
            foreach ($prefixes as $prefix) {
134
                if (strpos($line, $prefix) === 0) {
135
                    $prefixedLines[] = $line;
136
                }
137
            }
138
        }
139
140
        return $prefixedLines;
141
    }
142
143
    /**
144
     * Get array of all lines in section.
145
     *
146
     * @return string[]
147
     */
148
    private function readLines()
149
    {
150
        $lines = $this->fileSystem->read($this->path);
151
152
        $linesInSection = [];
153
        $inSection = false;
154
        foreach ($lines as $line) {
155
            if ($this->isEndOfSection($line)) {
156
                break;
157
            }
158
            if ($inSection) {
159
                $linesInSection[] = $line;
160
            }
161
            if ($this->isBeginOfSection($line)) {
162
                $inSection = true;
163
            }
164
        }
165
166
        return $linesInSection;
167
    }
168
169
    /**
170
     * @param $lines
171
     *
172
     * @throws FileException
173
     */
174
    private function writeLines($lines)
175
    {
176
        $oldLines = $this->fileSystem->read($this->path);
177
178
        $newLines = [];
179
        $sectionExists = false;
180
        $inSection = false;
181
        foreach ($oldLines as $oldLine) {
182
            if ($this->isBeginOfSection($oldLine)) {
183
                $inSection = true;
184
            }
185
            if (!$inSection) {
186
                $newLines[] = $oldLine;
187
            }
188
            if ($this->isEndOfSection($oldLine)) {
189
                $newLines = array_merge(
190
                    $newLines,
191
                    ['# BEGIN ' . self::$sectionLabel],
192
                    $lines,
193
                    ['# END ' . self::$sectionLabel]
194
                );
195
196
                $sectionExists = true;
197
                $inSection = false;
198
            }
199
        }
200
201
        if ($inSection && !$sectionExists) {
202
            throw new FileException('Missing END marker in Htaccess file.');
203
        }
204
205
        if (!$sectionExists) {
206
            $newLines = array_merge(
207
                $oldLines,
208
                ['# BEGIN ' . self::$sectionLabel],
209
                $lines,
210
                ['# END ' . self::$sectionLabel]
211
            );
212
        }
213
214
        $this->fileSystem->write($this->path, $newLines);
215
    }
216
217
    /**
218
     * Check whether line is the begin of the section.
219
     *
220
     * @param $line
221
     *
222
     * @return bool
223
     */
224
    private function isBeginOfSection($line)
225
    {
226
        return strpos($line, '# BEGIN ' . self::$sectionLabel) === 0;
227
    }
228
229
    /**
230
     * Check whether line is the end of the section.
231
     *
232
     * @param $line
233
     *
234
     * @return bool
235
     */
236
    private function isEndOfSection($line)
237
    {
238
        return strpos($line, '# END ' . self::$sectionLabel) === 0;
239
    }
240
}
241