1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Hongliang\Defender; |
4
|
|
|
|
5
|
|
|
use Hongliang\Defender\Voter\VoterInterface; |
6
|
|
|
use Hongliang\Defender\Voter\IpRangeVoter; |
7
|
|
|
use Hongliang\Defender\Voter\UriKeywordVoter; |
8
|
|
|
use Hongliang\Defender\Voter\SpiderVoter; |
9
|
|
|
|
10
|
|
|
class Defender |
11
|
|
|
{ |
12
|
|
|
const NORMAL = 0; |
13
|
|
|
const NOTICE = 1; |
14
|
|
|
const FORBIDDEN = 2; |
15
|
|
|
const DENY = 3; |
16
|
|
|
const REVENGE = 4; |
17
|
|
|
const REVENGE_PERMANENT = 5; |
18
|
|
|
|
19
|
|
|
private $voters = null; |
20
|
|
|
private $redirectUrl = 'http://localhost'; |
21
|
|
|
|
22
|
|
|
public static function defend() |
23
|
|
|
{ |
24
|
|
|
$defender = new self(); |
25
|
|
|
$defender->addVoter(new IpRangeVoter(), self::DENY) |
26
|
|
|
->addVoter(new UriKeywordVoter(), self::FORBIDDEN) |
27
|
|
|
->addVoter(new SpiderVoter(), self::DENY) |
28
|
|
|
->react(); |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
public function addVoter(VoterInterface $voter, $level = self::FORBIDDEN) |
32
|
|
|
{ |
33
|
|
|
if (null === $this->voters) { |
34
|
|
|
$this->voters = []; |
35
|
|
|
} |
36
|
|
|
$this->voters [] = ['voter' => $voter, 'level' => $level]; |
37
|
|
|
|
38
|
|
|
return $this; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
public function setRedirectUrl($url) |
42
|
|
|
{ |
43
|
|
|
$this->redirectUrl = $url; |
44
|
|
|
|
45
|
|
|
return $this; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
public function getRedirectUrl() |
49
|
|
|
{ |
50
|
|
|
return $this->redirectUrl; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
public function exam() |
54
|
|
|
{ |
55
|
|
|
$level = self::NORMAL; |
56
|
|
|
if (null === $this->voters) { |
57
|
|
|
return $level; |
58
|
|
|
} |
59
|
|
|
$this->sortVoters(); |
60
|
|
|
foreach ($this->voters as $voter) { |
61
|
|
|
if ($voter['voter']->vote()) { |
62
|
|
|
return $voter['level']; |
63
|
|
|
} |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
return $level; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
private function sortVoters() |
70
|
|
|
{ |
71
|
|
|
if (is_array($this->voters)) { |
72
|
|
|
usort($this->voters, array($this, 'sortVotersFunc')); |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
private function sortVotersFunc($a, $b) |
77
|
|
|
{ |
78
|
|
|
return $a['level'] < $b['level']; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
public function react($level = null) |
82
|
|
|
{ |
83
|
|
|
if (null === $level) { |
84
|
|
|
$level = $this->exam(); |
85
|
|
|
} |
86
|
|
|
$exit = false; |
87
|
|
|
switch ($level) { |
88
|
|
View Code Duplication |
case self::REVENGE_PERMANENT: |
|
|
|
|
89
|
|
|
header('HTTP/1.1 301 Moved Permanently'); |
90
|
|
|
header('Location: '.$this->getRedirectUrl()); |
91
|
|
|
$exit = true; |
92
|
|
|
break; |
93
|
|
View Code Duplication |
case self::REVENGE: |
|
|
|
|
94
|
|
|
header('HTTP/1.1 302 Moved Temporarily'); |
95
|
|
|
header('Location: '.$this->getRedirectUrl()); |
96
|
|
|
$exit = true; |
97
|
|
|
break; |
98
|
|
|
case self::DENY: |
99
|
|
|
header('HTTP/1.1 500 Internal Server Error'); |
100
|
|
|
$exit = true; |
101
|
|
|
break; |
102
|
|
|
case self::FORBIDDEN: |
103
|
|
|
header('HTTP/1.0 403 Forbidden'); |
104
|
|
|
$exit = true; |
105
|
|
|
break; |
106
|
|
|
case self::NOTICE: |
107
|
|
|
default: |
108
|
|
|
break; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
if ($exit) { |
112
|
|
|
exit; |
|
|
|
|
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.