@@ -44,12 +44,12 @@ discard block |
||
44 | 44 | |
45 | 45 | private function _list_complete_titles() { |
46 | 46 | $query = $this->db->select('tracker_titles.id, tracker_sites.site_class, tracker_titles.title, tracker_titles.title_url') |
47 | - ->from('tracker_chapters') |
|
48 | - ->join('tracker_titles', 'tracker_chapters.title_id = tracker_titles.id', 'left') |
|
49 | - ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
50 | - ->like('tracker_chapters.tags', 'complete') |
|
51 | - ->where('tracker_titles.status', 0) |
|
52 | - ->get(); |
|
47 | + ->from('tracker_chapters') |
|
48 | + ->join('tracker_titles', 'tracker_chapters.title_id = tracker_titles.id', 'left') |
|
49 | + ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
50 | + ->like('tracker_chapters.tags', 'complete') |
|
51 | + ->where('tracker_titles.status', 0) |
|
52 | + ->get(); |
|
53 | 53 | |
54 | 54 | $completeList = []; |
55 | 55 | if($query->num_rows() > 0) { |
@@ -68,10 +68,10 @@ discard block |
||
68 | 68 | |
69 | 69 | private function _update_mal_id() : void { |
70 | 70 | $query = $this->db->select('id, tags') |
71 | - ->from('tracker_chapters') |
|
72 | - ->where('tags REGEXP "[[:<:]]mal:([0-9]+|none)[[:>:]]"', NULL, FALSE) |
|
73 | - ->where('mal_id', NULL) |
|
74 | - ->get(); |
|
71 | + ->from('tracker_chapters') |
|
72 | + ->where('tags REGEXP "[[:<:]]mal:([0-9]+|none)[[:>:]]"', NULL, FALSE) |
|
73 | + ->where('mal_id', NULL) |
|
74 | + ->get(); |
|
75 | 75 | |
76 | 76 | |
77 | 77 | if($query->num_rows() > 0) { |
@@ -83,8 +83,8 @@ discard block |
||
83 | 83 | $new_tags = implode(',', array_diff( explode(',', $row->tags), [$matches[0]])); |
84 | 84 | |
85 | 85 | $this->db->set(['mal_id' => $malID, 'tags' => $new_tags, 'last_updated' => NULL]) |
86 | - ->where('id', $row->id) |
|
87 | - ->update('tracker_chapters'); |
|
86 | + ->where('id', $row->id) |
|
87 | + ->update('tracker_chapters'); |
|
88 | 88 | } |
89 | 89 | } |
90 | 90 | } |
@@ -53,9 +53,9 @@ |
||
53 | 53 | ]; |
54 | 54 | |
55 | 55 | $this->output |
56 | - ->set_status_header('200') |
|
57 | - ->set_content_type('application/json', 'utf-8') |
|
58 | - ->set_output(json_encode($json)); |
|
56 | + ->set_status_header('200') |
|
57 | + ->set_content_type('application/json', 'utf-8') |
|
58 | + ->set_output(json_encode($json)); |
|
59 | 59 | } else { |
60 | 60 | //TODO: We should probably try and have more verbose errors here. Return via JSON or something. |
61 | 61 | $this->output->set_status_header('400', 'Unable to update?'); |
@@ -15,11 +15,11 @@ discard block |
||
15 | 15 | */ |
16 | 16 | public function getID(string $titleURL, int $siteID, bool $create = TRUE, bool $returnData = FALSE) { |
17 | 17 | $query = $this->db->select('tracker_titles.id, tracker_titles.title, tracker_titles.title_url, tracker_titles.latest_chapter, tracker_titles.status, tracker_sites.site_class, (tracker_titles.last_checked > DATE_SUB(NOW(), INTERVAL 3 DAY)) AS active', FALSE) |
18 | - ->from('tracker_titles') |
|
19 | - ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
20 | - ->where('tracker_titles.title_url', $titleURL) |
|
21 | - ->where('tracker_titles.site_id', $siteID) |
|
22 | - ->get(); |
|
18 | + ->from('tracker_titles') |
|
19 | + ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
20 | + ->where('tracker_titles.title_url', $titleURL) |
|
21 | + ->where('tracker_titles.site_id', $siteID) |
|
22 | + ->get(); |
|
23 | 23 | |
24 | 24 | if($query->num_rows() > 0) { |
25 | 25 | $id = (int) $query->row('id'); |
@@ -32,8 +32,8 @@ discard block |
||
32 | 32 | //Make sure last_checked is always updated on successful run. |
33 | 33 | //CHECK: Is there a reason we aren't just doing this in updateTitleById? |
34 | 34 | $this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) |
35 | - ->where('id', $id) |
|
36 | - ->update('tracker_titles'); |
|
35 | + ->where('id', $id) |
|
36 | + ->update('tracker_titles'); |
|
37 | 37 | } |
38 | 38 | } else { |
39 | 39 | log_message('error', "{$query->row('title')} failed to update successfully"); |
@@ -73,9 +73,9 @@ discard block |
||
73 | 73 | */ |
74 | 74 | private function addTitle(string $titleURL, int $siteID) : int { |
75 | 75 | $query = $this->db->select('site, site_class') |
76 | - ->from('tracker_sites') |
|
77 | - ->where('id', $siteID) |
|
78 | - ->get(); |
|
76 | + ->from('tracker_sites') |
|
77 | + ->where('id', $siteID) |
|
78 | + ->get(); |
|
79 | 79 | |
80 | 80 | $titleData = $this->sites->{$query->row()->site_class}->getTitleData($titleURL, TRUE); |
81 | 81 | |
@@ -101,20 +101,20 @@ discard block |
||
101 | 101 | public function updateByID(int $titleID, string $latestChapter) : bool { |
102 | 102 | //FIXME: Really not too happy with how we're doing history stuff here, it just feels messy. |
103 | 103 | $query = $this->db->select('latest_chapter AS current_chapter') |
104 | - ->from('tracker_titles') |
|
105 | - ->where('id', $titleID) |
|
106 | - ->get(); |
|
104 | + ->from('tracker_titles') |
|
105 | + ->where('id', $titleID) |
|
106 | + ->get(); |
|
107 | 107 | $row = $query->row(); |
108 | 108 | |
109 | 109 | $success = $this->db->set(['latest_chapter' => $latestChapter, 'failed_checks' => 0]) //last_updated gets updated via a trigger if something changes |
110 | - ->where('id', $titleID) |
|
111 | - ->update('tracker_titles'); |
|
110 | + ->where('id', $titleID) |
|
111 | + ->update('tracker_titles'); |
|
112 | 112 | |
113 | 113 | if($this->db->affected_rows() > 0) { |
114 | 114 | //Clear hidden latest chapter |
115 | 115 | $this->db->set(['ignore_chapter' => 'NULL', 'last_updated' => 'last_updated'], NULL, FALSE) |
116 | - ->where('title_id', $titleID) |
|
117 | - ->update('tracker_chapters'); |
|
116 | + ->where('title_id', $titleID) |
|
117 | + ->update('tracker_chapters'); |
|
118 | 118 | } |
119 | 119 | |
120 | 120 | //Update History |
@@ -127,8 +127,8 @@ discard block |
||
127 | 127 | |
128 | 128 | public function updateFailedChecksByID(int $titleID) : bool { |
129 | 129 | $success = $this->db->set('failed_checks', 'failed_checks + 1', FALSE) |
130 | - ->where('id', $titleID) |
|
131 | - ->update('tracker_titles'); |
|
130 | + ->where('id', $titleID) |
|
131 | + ->update('tracker_titles'); |
|
132 | 132 | |
133 | 133 | return $success; |
134 | 134 | } |
@@ -140,9 +140,9 @@ discard block |
||
140 | 140 | */ |
141 | 141 | public function getSiteDataFromURL(string $site_url) { |
142 | 142 | $query = $this->db->select('*') |
143 | - ->from('tracker_sites') |
|
144 | - ->where('site', $site_url) |
|
145 | - ->get(); |
|
143 | + ->from('tracker_sites') |
|
144 | + ->where('site', $site_url) |
|
145 | + ->get(); |
|
146 | 146 | |
147 | 147 | return $query->row(); |
148 | 148 | } |
@@ -71,32 +71,32 @@ |
||
71 | 71 | |
72 | 72 | ////We need the series to be tracked |
73 | 73 | $idCQuery = $this->db->select('id') |
74 | - ->where('user_id', $userID) |
|
75 | - ->where('title_id', $titleID) |
|
76 | - ->get('tracker_chapters'); |
|
74 | + ->where('user_id', $userID) |
|
75 | + ->where('title_id', $titleID) |
|
76 | + ->get('tracker_chapters'); |
|
77 | 77 | if(!($idCQuery->num_rows() > 0)) { |
78 | 78 | //NOTE: This pretty much repeats a lot of what we already did above. Is there a better way to do this? |
79 | 79 | $this->Tracker->list->update($userID, $site, $title, $chapter, FALSE); |
80 | 80 | |
81 | 81 | $idCQuery = $this->db->select('id') |
82 | - ->where('user_id', $userID) |
|
83 | - ->where('title_id', $titleID) |
|
84 | - ->get('tracker_chapters'); |
|
82 | + ->where('user_id', $userID) |
|
83 | + ->where('title_id', $titleID) |
|
84 | + ->get('tracker_chapters'); |
|
85 | 85 | } |
86 | 86 | if($idCQuery->num_rows() > 0) { |
87 | 87 | $idCQueryRow = $idCQuery->row(); |
88 | 88 | |
89 | 89 | //Check if it is already favourited |
90 | 90 | $idFQuery = $this->db->select('id') |
91 | - ->where('chapter_id', $idCQueryRow->id) |
|
92 | - ->where('chapter', $chapter) |
|
93 | - ->get('tracker_favourites'); |
|
91 | + ->where('chapter_id', $idCQueryRow->id) |
|
92 | + ->where('chapter', $chapter) |
|
93 | + ->get('tracker_favourites'); |
|
94 | 94 | if($idFQuery->num_rows() > 0) { |
95 | 95 | //Chapter is already favourited, so remove it from DB |
96 | 96 | $idFQueryRow = $idFQuery->row(); |
97 | 97 | |
98 | 98 | $isSuccess = (bool) $this->db->where('id', $idFQueryRow->id) |
99 | - ->delete('tracker_favourites'); |
|
99 | + ->delete('tracker_favourites'); |
|
100 | 100 | |
101 | 101 | if($isSuccess) { |
102 | 102 | $success = array( |
@@ -74,8 +74,8 @@ discard block |
||
74 | 74 | //Make sure last_checked is always updated on successful run. |
75 | 75 | //CHECK: Is there a reason we aren't just doing this in updateByID? |
76 | 76 | $this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) |
77 | - ->where('id', $row->title_id) |
|
78 | - ->update('tracker_titles'); |
|
77 | + ->where('id', $row->title_id) |
|
78 | + ->update('tracker_titles'); |
|
79 | 79 | |
80 | 80 | print " - ({$titleData['latest_chapter']})\n"; |
81 | 81 | } else { |
@@ -152,8 +152,8 @@ discard block |
||
152 | 152 | //Make sure last_checked is always updated on successful run. |
153 | 153 | //CHECK: Is there a reason we aren't just doing this in updateByID? |
154 | 154 | $this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) |
155 | - ->where('id', $row->title_id) |
|
156 | - ->update('tracker_titles'); |
|
155 | + ->where('id', $row->title_id) |
|
156 | + ->update('tracker_titles'); |
|
157 | 157 | |
158 | 158 | print " - ({$titleData['latest_chapter']})\n"; |
159 | 159 | } else { |
@@ -177,10 +177,10 @@ discard block |
||
177 | 177 | */ |
178 | 178 | public function updateCustom() { |
179 | 179 | $query = $this->db->select('*') |
180 | - ->from('tracker_sites') |
|
181 | - ->where('status', 'enabled') |
|
182 | - ->where('tracker_sites.use_custom', 'Y') |
|
183 | - ->get(); |
|
180 | + ->from('tracker_sites') |
|
181 | + ->where('status', 'enabled') |
|
182 | + ->where('tracker_sites.use_custom', 'Y') |
|
183 | + ->get(); |
|
184 | 184 | |
185 | 185 | $sites = $query->result_array(); |
186 | 186 | foreach ($sites as $site) { |
@@ -196,8 +196,8 @@ discard block |
||
196 | 196 | //Make sure last_checked is always updated on successful run. |
197 | 197 | //CHECK: Is there a reason we aren't just doing this in updateByID? |
198 | 198 | $this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) |
199 | - ->where('id', $titleID) |
|
200 | - ->update('tracker_titles'); |
|
199 | + ->where('id', $titleID) |
|
200 | + ->update('tracker_titles'); |
|
201 | 201 | |
202 | 202 | print " - ({$titleData['latest_chapter']})\n"; |
203 | 203 | } else { |
@@ -224,13 +224,13 @@ discard block |
||
224 | 224 | |
225 | 225 | public function refollowCustom() { |
226 | 226 | $query = $this->db->select('tracker_titles.id, tracker_titles.title_url, tracker_sites.site_class') |
227 | - ->from('tracker_titles') |
|
228 | - ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
229 | - ->where('tracker_titles.followed','N') |
|
230 | - ->where('tracker_titles !=', '255') |
|
231 | - ->where('tracker_sites.status', 'enabled') |
|
232 | - ->where('tracker_sites.use_custom', 'Y') |
|
233 | - ->get(); |
|
227 | + ->from('tracker_titles') |
|
228 | + ->join('tracker_sites', 'tracker_sites.id = tracker_titles.site_id', 'left') |
|
229 | + ->where('tracker_titles.followed','N') |
|
230 | + ->where('tracker_titles !=', '255') |
|
231 | + ->where('tracker_sites.status', 'enabled') |
|
232 | + ->where('tracker_sites.use_custom', 'Y') |
|
233 | + ->get(); |
|
234 | 234 | |
235 | 235 | if($query->num_rows() > 0) { |
236 | 236 | foreach($query->result() as $row) { |
@@ -241,8 +241,8 @@ discard block |
||
241 | 241 | |
242 | 242 | if(!empty($titleData)) { |
243 | 243 | $this->db->set($titleData) |
244 | - ->where('id', $row->id) |
|
245 | - ->update('tracker_titles'); |
|
244 | + ->where('id', $row->id) |
|
245 | + ->update('tracker_titles'); |
|
246 | 246 | |
247 | 247 | print "> {$row->site_class}:{$row->id}:{$row->title_url} FOLLOWED\n"; |
248 | 248 | } else { |
@@ -290,8 +290,8 @@ discard block |
||
290 | 290 | if($titleData['title'] && is_array($titleData) && !is_null($titleData['latest_chapter'])) { |
291 | 291 | if($titleData['title'] !== $row->title) { |
292 | 292 | $this->db->set('title', $titleData['title']) |
293 | - ->where('id', $row->id) |
|
294 | - ->update('tracker_titles'); |
|
293 | + ->where('id', $row->id) |
|
294 | + ->update('tracker_titles'); |
|
295 | 295 | //TODO: Add to history somehow? |
296 | 296 | print " - NEW TITLE ({$titleData['title']})\n"; |
297 | 297 | } else { |
@@ -301,8 +301,8 @@ discard block |
||
301 | 301 | //We might as well try to update as well. |
302 | 302 | if($this->Tracker->title->updateByID((int) $row->id, $titleData['latest_chapter'])) { |
303 | 303 | $this->db->set('last_checked', 'CURRENT_TIMESTAMP', FALSE) |
304 | - ->where('id', $row->id) |
|
305 | - ->update('tracker_titles'); |
|
304 | + ->where('id', $row->id) |
|
305 | + ->update('tracker_titles'); |
|
306 | 306 | } |
307 | 307 | } else { |
308 | 308 | log_message('error', "{$row->title} failed to update title successfully"); |
@@ -21,11 +21,11 @@ |
||
21 | 21 | $this->ci->load->config('recaptcha', TRUE); |
22 | 22 | if ($this->ci->config->item('recaptcha_secretkey', 'recaptcha') == NULL || $this->ci->config->item('recaptcha_secretkey', 'recaptcha') == "") { |
23 | 23 | die("To use reCAPTCHA you must get an API key from <a href='" |
24 | - . $this->signup_url . "'>" . $this->signup_url . "</a>"); |
|
24 | + . $this->signup_url . "'>" . $this->signup_url . "</a>"); |
|
25 | 25 | } |
26 | 26 | if ($this->ci->config->item('recaptcha_sitekey', 'recaptcha') == NULL || $this->ci->config->item('recaptcha_sitekey', 'recaptcha') == "") { |
27 | 27 | die("To use reCAPTCHA you must get an API key from <a href='" |
28 | - . $this->signup_url . "'>" . $this->signup_url . "</a>"); |
|
28 | + . $this->signup_url . "'>" . $this->signup_url . "</a>"); |
|
29 | 29 | } |
30 | 30 | $this->_secret = $this->ci->config->item('recaptcha_secretkey', 'recaptcha'); |
31 | 31 | $this->_sitekey = $this->ci->config->item('recaptcha_sitekey', 'recaptcha'); |
@@ -12,223 +12,223 @@ |
||
12 | 12 | |
13 | 13 | class Limiter { |
14 | 14 | |
15 | - /** @type CI_Controller */ |
|
16 | - protected $CI; |
|
17 | - protected $table = 'rate_limit'; |
|
18 | - protected $base_limit = 0; // infinite |
|
19 | - protected $whitelist = array('127.0.0.1'); |
|
20 | - protected $header_show = TRUE; |
|
21 | - protected $checksum_algorithm = 'md4'; |
|
22 | - protected $header_prefix = 'X-RateLimit-'; |
|
23 | - protected $flush_on_abort = FALSE; |
|
24 | - |
|
25 | - protected $user_data = array(); |
|
26 | - protected $user_hash = FALSE; |
|
27 | - |
|
28 | - private $_truncated = FALSE; |
|
29 | - private $_info_cache = array(); |
|
30 | - |
|
31 | - private $_sql_truncate = 'DELETE FROM `RATE_TABLE` WHERE `start` < (NOW() - INTERVAL 1 HOUR)'; |
|
32 | - private $_sql_info = 'SELECT `count`, `start`, (`start` + INTERVAL (1 - TIMESTAMPDIFF(HOUR, UTC_TIMESTAMP(), NOW())) HOUR) \'reset_epoch\' FROM `RATE_TABLE` WHERE `client` = ? AND `target` = ?'; |
|
33 | - private $_sql_update = 'INSERT INTO `RATE_TABLE` (`client`, `target`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `count` = `count` + 1'; |
|
34 | - private $_config_fields = array( |
|
35 | - 'table', 'base_limit', 'checksum_algorithm', |
|
36 | - 'header', 'header_prefix', 'flush_on_abort' |
|
37 | - ); |
|
38 | - |
|
39 | - public function __construct($config = array()) { |
|
40 | - $this->CI = &get_instance(); |
|
41 | - |
|
42 | - if(!is_array($config)) { |
|
43 | - $config = array(); |
|
44 | - } |
|
45 | - |
|
46 | - foreach($this->_config_fields as $field) { |
|
47 | - if(array_key_exists($field, $config)) { |
|
48 | - $this->{$field} = $config[$field]; |
|
49 | - } |
|
50 | - } |
|
51 | - |
|
52 | - $sql = array('truncate', 'info', 'update'); |
|
53 | - foreach($sql as $s) { |
|
54 | - $this->{"_sql_$s"} = str_replace('RATE_TABLE', $this->table, $this->{"_sql_$s"}); |
|
55 | - } |
|
56 | - |
|
57 | - $this->add_user_data($this->CI->input->ip_address()); |
|
58 | - |
|
59 | - log_message('debug', 'Limiter Class Initialized'); |
|
60 | - } |
|
61 | - |
|
62 | - /** |
|
63 | - * Rate limits the amount of requests that can be sent by a client. |
|
64 | - * |
|
65 | - * @param string $target |
|
66 | - * @param int $req_per_hour Overrides base_limit setting if set |
|
67 | - * @param bool $flush_on_abort Overrides flush_on_abort setting if set |
|
68 | - * @param bool $show_header Overrides header_show setting if set |
|
69 | - * @return bool Should request be aborted |
|
70 | - */ |
|
71 | - public function limit($target = '_global', $req_per_hour = null, $flush_on_abort = null, $show_header = null) { |
|
72 | - $req_per_hour = $req_per_hour !== null ? $req_per_hour : $this->base_limit; |
|
73 | - $flush_on_abort = $flush_on_abort !== null ? $flush_on_abort : $this->flush_on_abort; |
|
74 | - $show_header = $show_header !== null ? $show_header : $this->header_show; |
|
75 | - |
|
76 | - $truncated = $this->_truncate(); |
|
77 | - if(!$truncated) { |
|
78 | - log_message('DEBUG', 'WARN: Could not truncate rate limit table'); |
|
79 | - } |
|
80 | - |
|
81 | - if($this->is_whitelisted()) { |
|
82 | - $req_per_hour = 0; |
|
83 | - } |
|
84 | - |
|
85 | - $abort = FALSE; |
|
86 | - if($req_per_hour > 0) { |
|
87 | - $info = $this->get_limit_info($target); |
|
88 | - |
|
89 | - if($info === FALSE) { |
|
90 | - $info = new stdClass(); |
|
91 | - $info->count = 0; |
|
92 | - $info->reset_epoch = gmdate('d M Y H:i:s', time() + (60 * 60)); |
|
93 | - $info->start = date('d M Y H:i:s'); |
|
94 | - } |
|
95 | - |
|
96 | - if($req_per_hour - $info->count > 0) { |
|
97 | - $data = array('client' => $this->get_hash(), 'target' => $target); |
|
98 | - $this->CI->db->query($this->_sql_update, $data); |
|
99 | - $info->count++; |
|
100 | - } else { |
|
101 | - $abort = TRUE; |
|
102 | - } |
|
103 | - |
|
104 | - if($show_header === TRUE) { |
|
105 | - $headers = array( |
|
106 | - 'Limit' => $req_per_hour, |
|
107 | - 'Remaining' => $req_per_hour - $info->count, |
|
108 | - 'Reset' => strtotime($info->reset_epoch), |
|
109 | - ); |
|
110 | - |
|
111 | - foreach(array_keys($headers) as $h) { |
|
112 | - $this->CI->output->set_header("$this->header_prefix$h: $headers[$h]"); |
|
113 | - } |
|
114 | - } |
|
115 | - |
|
116 | - $this->_info_cache[$target] = $info; |
|
117 | - |
|
118 | - |
|
119 | - if($abort) { |
|
120 | - $retry_seconds = strtotime($info->reset_epoch) - strtotime(gmdate('d M Y H:i:s')); |
|
121 | - $this->CI->output->set_header("Retry-After: $retry_seconds"); |
|
122 | - $this->CI->output->set_status_header(503, 'Rate limit reached'); |
|
123 | - |
|
124 | - if($flush_on_abort) { |
|
125 | - $this->CI->output->_display(); |
|
126 | - exit; |
|
127 | - } |
|
128 | - } |
|
129 | - } |
|
130 | - |
|
131 | - return $abort; |
|
132 | - } |
|
133 | - |
|
134 | - /** |
|
135 | - * Forget the client ever visited $target. |
|
136 | - * |
|
137 | - * @param string $target |
|
138 | - */ |
|
139 | - public function reset_rate($target = '_global') { |
|
140 | - $this->CI->db->delete($this->table, array( |
|
141 | - 'client' => $this->get_hash(), |
|
142 | - 'target' => $target |
|
143 | - )); |
|
144 | - } |
|
145 | - |
|
146 | - /** |
|
147 | - * Forgets all rate limits attached to the client. |
|
148 | - */ |
|
149 | - public function forget_client() { |
|
150 | - $this->CI->db->delete($this->table, array('client' => $this->get_hash())); |
|
151 | - } |
|
152 | - |
|
153 | - /** |
|
154 | - * Used to obtain the client hash. |
|
155 | - * |
|
156 | - * Returns false if hash generation failed |
|
157 | - * @return string |
|
158 | - */ |
|
159 | - public function get_hash() { |
|
160 | - if($this->user_hash === FALSE) { |
|
161 | - $this->user_hash = $this->_generate_hash(); |
|
162 | - } |
|
163 | - return $this->user_hash; |
|
164 | - } |
|
165 | - |
|
166 | - /** |
|
167 | - * Adds entropy to the client hash. Make |
|
168 | - * sure that this is some sort of static |
|
169 | - * data such as a username/id. |
|
170 | - * |
|
171 | - * @param string $data Any seeding data |
|
172 | - */ |
|
173 | - public function add_user_data($data) { |
|
174 | - array_push($this->user_data, (string) $data); |
|
175 | - |
|
176 | - if(count($this->_info_cache) !== 0) { |
|
177 | - log_message('DEBUG', 'WARN: Emptying info cache due to user data changing'); |
|
178 | - $this->_info_cache = array(); // Empty cache |
|
179 | - } |
|
180 | - |
|
181 | - if($this->user_hash !== FALSE) { |
|
182 | - log_message('DEBUG', 'WARN: Adding user data after hash was generated'); |
|
183 | - $this->user_hash = $this->_generate_hash(); |
|
184 | - } |
|
185 | - } |
|
186 | - |
|
187 | - /** |
|
188 | - * Get target rate info |
|
189 | - * |
|
190 | - * @param string $target |
|
191 | - * @return stdClass|false Info object, returns false if no info is present |
|
192 | - */ |
|
193 | - public function get_limit_info($target = '_global') { |
|
194 | - if(!array_key_exists($target, $this->_info_cache)) { |
|
195 | - $data = array('client' => $this->get_hash(), 'target' => $target); |
|
196 | - $info = $this->CI->db->query($this->_sql_info, $data)->row(); |
|
197 | - |
|
198 | - $this->_info_cache[$target] = $info; |
|
199 | - } else { |
|
200 | - $info = $this->_info_cache[$target]; |
|
201 | - } |
|
202 | - |
|
203 | - $valid_data = isset($info->count); |
|
204 | - return $valid_data ? $info : FALSE; |
|
205 | - } |
|
206 | - |
|
207 | - /** |
|
208 | - * @param $target |
|
209 | - * @return integer Amount of attempts |
|
210 | - */ |
|
211 | - public function get_attempts($target) { |
|
212 | - return $this->get_limit_info($target)->count; |
|
213 | - } |
|
214 | - |
|
215 | - /** |
|
216 | - * @param null $ip If null current IP |
|
217 | - * @return bool Is whitelisted |
|
218 | - */ |
|
219 | - public function is_whitelisted($ip = null) { |
|
220 | - $ip = $ip ?: $this->CI->input->ip_address(); |
|
221 | - return in_array($ip, $this->whitelist); |
|
222 | - } |
|
223 | - |
|
224 | - private function _truncate() { |
|
225 | - if(!$this->_truncated) { |
|
226 | - $this->_truncated = $this->CI->db->query($this->_sql_truncate); |
|
227 | - } |
|
228 | - return $this->_truncated; |
|
229 | - } |
|
230 | - |
|
231 | - private function _generate_hash() { |
|
232 | - return hash($this->checksum_algorithm, join('%', $this->user_data)); |
|
233 | - } |
|
15 | + /** @type CI_Controller */ |
|
16 | + protected $CI; |
|
17 | + protected $table = 'rate_limit'; |
|
18 | + protected $base_limit = 0; // infinite |
|
19 | + protected $whitelist = array('127.0.0.1'); |
|
20 | + protected $header_show = TRUE; |
|
21 | + protected $checksum_algorithm = 'md4'; |
|
22 | + protected $header_prefix = 'X-RateLimit-'; |
|
23 | + protected $flush_on_abort = FALSE; |
|
24 | + |
|
25 | + protected $user_data = array(); |
|
26 | + protected $user_hash = FALSE; |
|
27 | + |
|
28 | + private $_truncated = FALSE; |
|
29 | + private $_info_cache = array(); |
|
30 | + |
|
31 | + private $_sql_truncate = 'DELETE FROM `RATE_TABLE` WHERE `start` < (NOW() - INTERVAL 1 HOUR)'; |
|
32 | + private $_sql_info = 'SELECT `count`, `start`, (`start` + INTERVAL (1 - TIMESTAMPDIFF(HOUR, UTC_TIMESTAMP(), NOW())) HOUR) \'reset_epoch\' FROM `RATE_TABLE` WHERE `client` = ? AND `target` = ?'; |
|
33 | + private $_sql_update = 'INSERT INTO `RATE_TABLE` (`client`, `target`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `count` = `count` + 1'; |
|
34 | + private $_config_fields = array( |
|
35 | + 'table', 'base_limit', 'checksum_algorithm', |
|
36 | + 'header', 'header_prefix', 'flush_on_abort' |
|
37 | + ); |
|
38 | + |
|
39 | + public function __construct($config = array()) { |
|
40 | + $this->CI = &get_instance(); |
|
41 | + |
|
42 | + if(!is_array($config)) { |
|
43 | + $config = array(); |
|
44 | + } |
|
45 | + |
|
46 | + foreach($this->_config_fields as $field) { |
|
47 | + if(array_key_exists($field, $config)) { |
|
48 | + $this->{$field} = $config[$field]; |
|
49 | + } |
|
50 | + } |
|
51 | + |
|
52 | + $sql = array('truncate', 'info', 'update'); |
|
53 | + foreach($sql as $s) { |
|
54 | + $this->{"_sql_$s"} = str_replace('RATE_TABLE', $this->table, $this->{"_sql_$s"}); |
|
55 | + } |
|
56 | + |
|
57 | + $this->add_user_data($this->CI->input->ip_address()); |
|
58 | + |
|
59 | + log_message('debug', 'Limiter Class Initialized'); |
|
60 | + } |
|
61 | + |
|
62 | + /** |
|
63 | + * Rate limits the amount of requests that can be sent by a client. |
|
64 | + * |
|
65 | + * @param string $target |
|
66 | + * @param int $req_per_hour Overrides base_limit setting if set |
|
67 | + * @param bool $flush_on_abort Overrides flush_on_abort setting if set |
|
68 | + * @param bool $show_header Overrides header_show setting if set |
|
69 | + * @return bool Should request be aborted |
|
70 | + */ |
|
71 | + public function limit($target = '_global', $req_per_hour = null, $flush_on_abort = null, $show_header = null) { |
|
72 | + $req_per_hour = $req_per_hour !== null ? $req_per_hour : $this->base_limit; |
|
73 | + $flush_on_abort = $flush_on_abort !== null ? $flush_on_abort : $this->flush_on_abort; |
|
74 | + $show_header = $show_header !== null ? $show_header : $this->header_show; |
|
75 | + |
|
76 | + $truncated = $this->_truncate(); |
|
77 | + if(!$truncated) { |
|
78 | + log_message('DEBUG', 'WARN: Could not truncate rate limit table'); |
|
79 | + } |
|
80 | + |
|
81 | + if($this->is_whitelisted()) { |
|
82 | + $req_per_hour = 0; |
|
83 | + } |
|
84 | + |
|
85 | + $abort = FALSE; |
|
86 | + if($req_per_hour > 0) { |
|
87 | + $info = $this->get_limit_info($target); |
|
88 | + |
|
89 | + if($info === FALSE) { |
|
90 | + $info = new stdClass(); |
|
91 | + $info->count = 0; |
|
92 | + $info->reset_epoch = gmdate('d M Y H:i:s', time() + (60 * 60)); |
|
93 | + $info->start = date('d M Y H:i:s'); |
|
94 | + } |
|
95 | + |
|
96 | + if($req_per_hour - $info->count > 0) { |
|
97 | + $data = array('client' => $this->get_hash(), 'target' => $target); |
|
98 | + $this->CI->db->query($this->_sql_update, $data); |
|
99 | + $info->count++; |
|
100 | + } else { |
|
101 | + $abort = TRUE; |
|
102 | + } |
|
103 | + |
|
104 | + if($show_header === TRUE) { |
|
105 | + $headers = array( |
|
106 | + 'Limit' => $req_per_hour, |
|
107 | + 'Remaining' => $req_per_hour - $info->count, |
|
108 | + 'Reset' => strtotime($info->reset_epoch), |
|
109 | + ); |
|
110 | + |
|
111 | + foreach(array_keys($headers) as $h) { |
|
112 | + $this->CI->output->set_header("$this->header_prefix$h: $headers[$h]"); |
|
113 | + } |
|
114 | + } |
|
115 | + |
|
116 | + $this->_info_cache[$target] = $info; |
|
117 | + |
|
118 | + |
|
119 | + if($abort) { |
|
120 | + $retry_seconds = strtotime($info->reset_epoch) - strtotime(gmdate('d M Y H:i:s')); |
|
121 | + $this->CI->output->set_header("Retry-After: $retry_seconds"); |
|
122 | + $this->CI->output->set_status_header(503, 'Rate limit reached'); |
|
123 | + |
|
124 | + if($flush_on_abort) { |
|
125 | + $this->CI->output->_display(); |
|
126 | + exit; |
|
127 | + } |
|
128 | + } |
|
129 | + } |
|
130 | + |
|
131 | + return $abort; |
|
132 | + } |
|
133 | + |
|
134 | + /** |
|
135 | + * Forget the client ever visited $target. |
|
136 | + * |
|
137 | + * @param string $target |
|
138 | + */ |
|
139 | + public function reset_rate($target = '_global') { |
|
140 | + $this->CI->db->delete($this->table, array( |
|
141 | + 'client' => $this->get_hash(), |
|
142 | + 'target' => $target |
|
143 | + )); |
|
144 | + } |
|
145 | + |
|
146 | + /** |
|
147 | + * Forgets all rate limits attached to the client. |
|
148 | + */ |
|
149 | + public function forget_client() { |
|
150 | + $this->CI->db->delete($this->table, array('client' => $this->get_hash())); |
|
151 | + } |
|
152 | + |
|
153 | + /** |
|
154 | + * Used to obtain the client hash. |
|
155 | + * |
|
156 | + * Returns false if hash generation failed |
|
157 | + * @return string |
|
158 | + */ |
|
159 | + public function get_hash() { |
|
160 | + if($this->user_hash === FALSE) { |
|
161 | + $this->user_hash = $this->_generate_hash(); |
|
162 | + } |
|
163 | + return $this->user_hash; |
|
164 | + } |
|
165 | + |
|
166 | + /** |
|
167 | + * Adds entropy to the client hash. Make |
|
168 | + * sure that this is some sort of static |
|
169 | + * data such as a username/id. |
|
170 | + * |
|
171 | + * @param string $data Any seeding data |
|
172 | + */ |
|
173 | + public function add_user_data($data) { |
|
174 | + array_push($this->user_data, (string) $data); |
|
175 | + |
|
176 | + if(count($this->_info_cache) !== 0) { |
|
177 | + log_message('DEBUG', 'WARN: Emptying info cache due to user data changing'); |
|
178 | + $this->_info_cache = array(); // Empty cache |
|
179 | + } |
|
180 | + |
|
181 | + if($this->user_hash !== FALSE) { |
|
182 | + log_message('DEBUG', 'WARN: Adding user data after hash was generated'); |
|
183 | + $this->user_hash = $this->_generate_hash(); |
|
184 | + } |
|
185 | + } |
|
186 | + |
|
187 | + /** |
|
188 | + * Get target rate info |
|
189 | + * |
|
190 | + * @param string $target |
|
191 | + * @return stdClass|false Info object, returns false if no info is present |
|
192 | + */ |
|
193 | + public function get_limit_info($target = '_global') { |
|
194 | + if(!array_key_exists($target, $this->_info_cache)) { |
|
195 | + $data = array('client' => $this->get_hash(), 'target' => $target); |
|
196 | + $info = $this->CI->db->query($this->_sql_info, $data)->row(); |
|
197 | + |
|
198 | + $this->_info_cache[$target] = $info; |
|
199 | + } else { |
|
200 | + $info = $this->_info_cache[$target]; |
|
201 | + } |
|
202 | + |
|
203 | + $valid_data = isset($info->count); |
|
204 | + return $valid_data ? $info : FALSE; |
|
205 | + } |
|
206 | + |
|
207 | + /** |
|
208 | + * @param $target |
|
209 | + * @return integer Amount of attempts |
|
210 | + */ |
|
211 | + public function get_attempts($target) { |
|
212 | + return $this->get_limit_info($target)->count; |
|
213 | + } |
|
214 | + |
|
215 | + /** |
|
216 | + * @param null $ip If null current IP |
|
217 | + * @return bool Is whitelisted |
|
218 | + */ |
|
219 | + public function is_whitelisted($ip = null) { |
|
220 | + $ip = $ip ?: $this->CI->input->ip_address(); |
|
221 | + return in_array($ip, $this->whitelist); |
|
222 | + } |
|
223 | + |
|
224 | + private function _truncate() { |
|
225 | + if(!$this->_truncated) { |
|
226 | + $this->_truncated = $this->CI->db->query($this->_sql_truncate); |
|
227 | + } |
|
228 | + return $this->_truncated; |
|
229 | + } |
|
230 | + |
|
231 | + private function _generate_hash() { |
|
232 | + return hash($this->checksum_algorithm, join('%', $this->user_data)); |
|
233 | + } |
|
234 | 234 | } |
@@ -1,7 +1,7 @@ |
||
1 | 1 | <?php |
2 | 2 | |
3 | 3 | if (! defined('BASEPATH')) { |
4 | - exit('No direct script access allowed'); |
|
4 | + exit('No direct script access allowed'); |
|
5 | 5 | } |
6 | 6 | |
7 | 7 | /** |
@@ -5,8 +5,7 @@ |
||
5 | 5 | * Author: Balazs Bosternak |
6 | 6 | * [email protected] |
7 | 7 | * @bosternakbalazs |
8 | - |
|
9 | -* |
|
8 | + * |
|
10 | 9 | * Location: http://github.com/benedmunds/ion_auth/ |
11 | 10 | * |
12 | 11 | * Created: 07.19.2015 |