Skip to content

Commit c30af76

Browse files
committed
Refactor FeatherBB cookie usage thanks to @capkokoon
1 parent 37f2d9c commit c30af76

3 files changed

Lines changed: 133 additions & 167 deletions

File tree

‎include/common.php‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,26 +69,6 @@
6969
set_magic_quotes_runtime(0);
7070
}
7171

72-
// Strip slashes from GET/POST/COOKIE/REQUEST/FILES (if magic_quotes_gpc is enabled)
73-
if (!defined('FORUM_DISABLE_STRIPSLASHES') && get_magic_quotes_gpc()) {
74-
function stripslashes_array($array)
75-
{
76-
return is_array($array) ? array_map('stripslashes_array', $array) : stripslashes($array);
77-
}
78-
79-
$_GET = stripslashes_array($_GET);
80-
$_POST = stripslashes_array($_POST);
81-
$_COOKIE = stripslashes_array($_COOKIE);
82-
$_REQUEST = stripslashes_array($_REQUEST);
83-
if (is_array($_FILES)) {
84-
// Don't strip valid slashes from tmp_name path on Windows
85-
foreach ($_FILES as $key => $value) {
86-
$_FILES[$key]['tmp_name'] = str_replace('\\', '\\\\', $value['tmp_name']);
87-
}
88-
$_FILES = stripslashes_array($_FILES);
89-
}
90-
}
91-
9272
// If a cookie name is not specified in config.php, we use the default (pun_cookie)
9373
if (empty($cookie_name)) {
9474
$cookie_name = 'feather_cookie';

‎include/functions.php‎

Lines changed: 130 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -23,130 +23,131 @@ function get_microtime()
2323
//
2424
function check_cookie()
2525
{
26-
global $db, $db_type, $feather_config, $cookie_name, $cookie_seed;
26+
global $cookie_name, $cookie_seed;
2727

28-
$now = time();
29-
3028
// Get Slim current session
3129
$feather = \Slim\Slim::getInstance();
30+
$now = time();
3231

33-
// If the cookie is set and it matches the correct pattern, then read the values from it
34-
if (isset($_COOKIE[$cookie_name]) && preg_match('%^(\d+)\|([0-9a-fA-F]+)\|(\d+)\|([0-9a-fA-F]+)$%', $_COOKIE[$cookie_name], $matches)) {
35-
$cookie = array(
36-
'user_id' => intval($matches[1]),
37-
'password_hash' => $matches[2],
38-
'expiration_time' => intval($matches[3]),
39-
'cookie_hash' => $matches[4],
40-
);
32+
// Get FeatherBB cookie
33+
$cookie_raw = $feather->getCookie($cookie_name);
34+
35+
// Check if cookie exists and is valid (getCookie method returns false if the data has been tampered locally so it can't decrypt the cookie);
36+
if (isset($cookie_raw)) {
37+
$cookie = json_decode($cookie_raw, true);
38+
$checksum = hash_hmac('sha1', $cookie['user_id'].$cookie['expires'], $cookie_seed . '_checksum');
39+
40+
// If cookie has a non-guest user, hasn't expired and is legit
41+
if ($cookie['user_id'] > 1 && $cookie['expires'] > $now && $checksum == $cookie['checksum']) {
42+
43+
// Get user info from db
44+
$select_check_cookie = array('u.*', 'g.*', 'o.logged', 'o.idle');
45+
$where_check_cookie = array('u.id' => intval($cookie['user_id']));
46+
47+
$result = ORM::for_table($feather->prefix.'users')
48+
->table_alias('u')
49+
->select_many($select_check_cookie)
50+
->inner_join($feather->prefix.'groups', array('u.group_id', '=', 'g.g_id'), 'g')
51+
->left_outer_join($feather->prefix.'online', array('o.user_id', '=', 'u.id'), 'o')
52+
->where($where_check_cookie)
53+
->find_result_set();
54+
55+
foreach ($result as $feather->user);
56+
57+
// Another security check, to prevent identity fraud by changing the user id in the cookie) (might be useless considering the strength of encryption)
58+
if (isset($feather->user->id) && hash_hmac('sha1', $feather->user->password, $cookie_seed.'_password_hash') === $cookie['password_hash']) {
59+
$expires = ($cookie['expires'] > $now + $feather->config['o_timeout_visit']) ? $now + 1209600 : $now + $feather->config['o_timeout_visit'];
60+
$feather->user->is_guest = false;
61+
$feather->user->is_admmod = $feather->user->g_id == FEATHER_ADMIN || $feather->user->g_moderator == '1';
62+
feather_setcookie($feather->user->id, $feather->user->password, $expires);
63+
set_preferences();
64+
return true;
65+
}
66+
}
4167
}
68+
// If there is no cookie, or cookie is guest or expired, let's reconnect.
69+
$expires = $now + 31536000; // The cookie expires after a year
70+
feather_setcookie(1, feather_hash(uniqid(rand(), true)), $expires);
71+
return set_default_user();
72+
}
4273

43-
// If it has a non-guest user, and hasn't expired
44-
if (isset($cookie) && $cookie['user_id'] > 1 && $cookie['expiration_time'] > $now) {
45-
// If the cookie has been tampered with
46-
if (hash_hmac('sha1', $cookie['user_id'].'|'.$cookie['expiration_time'], $cookie_seed.'_cookie_hash') !== $cookie['cookie_hash']) {
47-
$expire = $now + 31536000; // The cookie expires after a year
48-
feather_setcookie(1, feather_hash(uniqid(rand(), true)), $expire);
49-
set_default_user();
50-
51-
return;
52-
}
53-
54-
$select_check_cookie = array('u.*', 'g.*', 'o.logged', 'o.idle');
55-
$where_check_cookie = array('u.id' => intval($cookie['user_id']));
56-
57-
$result = ORM::for_table($feather->prefix.'users')
58-
->table_alias('u')
59-
->select_many($select_check_cookie)
60-
->inner_join($feather->prefix.'groups', array('u.group_id', '=', 'g.g_id'), 'g')
61-
->left_outer_join($feather->prefix.'online', array('o.user_id', '=', 'u.id'), 'o')
62-
->where($where_check_cookie)
63-
->find_result_set();
64-
65-
foreach ($result as $feather->user);
66-
67-
// If user authorisation failed
68-
if (!isset($feather->user->id) || hash_hmac('sha1', $feather->user->password, $cookie_seed.'_password_hash') !== $cookie['password_hash']) {
69-
$expire = $now + 31536000; // The cookie expires after a year
70-
feather_setcookie(1, feather_hash(uniqid(rand(), true)), $expire);
71-
set_default_user();
74+
//
75+
// Set preferences
76+
//
77+
function set_preferences()
78+
{
79+
global $db_type, $cookie_name;
7280

73-
return;
74-
}
81+
// Get Slim current session
82+
$feather = \Slim\Slim::getInstance();
83+
$now = time();
7584

76-
// Send a new, updated cookie with a new expiration timestamp
77-
$expire = ($cookie['expiration_time'] > $now + $feather_config['o_timeout_visit']) ? $now + 1209600 : $now + $feather_config['o_timeout_visit'];
78-
feather_setcookie($feather->user->id, $feather->user->password, $expire);
85+
// Set a default language if the user selected language no longer exists
86+
if (!file_exists(FEATHER_ROOT.'lang/'.$feather->user->language)) {
87+
$feather->user->language = $feather->config['o_default_lang'];
88+
}
7989

80-
// Set a default language if the user selected language no longer exists
81-
if (!file_exists(FEATHER_ROOT.'lang/'.$feather->user->language)) {
82-
$feather->user->language = $feather_config['o_default_lang'];
83-
}
90+
// Set a default style if the user selected style no longer exists
91+
if (!file_exists(FEATHER_ROOT.'style/'.$feather->user->style.'.css')) {
92+
$feather->user->style = $feather->config['o_default_style'];
93+
}
8494

85-
// Set a default style if the user selected style no longer exists
86-
if (!file_exists(FEATHER_ROOT.'style/'.$feather->user->style.'.css')) {
87-
$feather->user->style = $feather_config['o_default_style'];
88-
}
95+
if (!$feather->user->disp_topics) {
96+
$feather->user->disp_topics = $feather->config['o_disp_topics_default'];
97+
}
98+
if (!$feather->user->disp_posts) {
99+
$feather->user->disp_posts = $feather->config['o_disp_posts_default'];
100+
}
89101

90-
if (!$feather->user->disp_topics) {
91-
$feather->user->disp_topics = $feather_config['o_disp_topics_default'];
92-
}
93-
if (!$feather->user->disp_posts) {
94-
$feather->user->disp_posts = $feather_config['o_disp_posts_default'];
95-
}
102+
// Define this if you want this visit to affect the online list and the users last visit data
103+
if (!defined('FEATHER_QUIET_VISIT')) {
104+
// Update the online list
105+
if (!$feather->user->logged) {
106+
$feather->user->logged = $now;
96107

97-
// Define this if you want this visit to affect the online list and the users last visit data
98-
if (!defined('FEATHER_QUIET_VISIT')) {
99-
// Update the online list
100-
if (!$feather->user->logged) {
101-
$feather->user->logged = $now;
102-
103-
// With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
104-
switch ($db_type) {
105-
case 'mysql':
106-
case 'mysqli':
107-
case 'mysql_innodb':
108-
case 'mysqli_innodb':
109-
case 'sqlite':
110-
case 'sqlite3':
111-
\ORM::for_table($db->prefix.'online')->raw_execute('REPLACE INTO '.$db->prefix.'online (user_id, ident, logged) VALUES(:user_id, :ident, :logged)', array(':user_id' => $feather->user->id, ':ident' => $feather->user->username, ':logged' => $feather->user->logged));
112-
break;
113-
114-
default:
115-
\ORM::for_table($db->prefix.'online')->raw_execute('INSERT INTO '.$db->prefix.'online (user_id, ident, logged) SELECT :user_id, :ident, :logged WHERE NOT EXISTS (SELECT 1 FROM '.$db->prefix.'online WHERE user_id=:user_id)', array(':user_id' => $feather->user->id, ':ident' => $feather->user->username, ':logged' => $feather->user->logged));
116-
break;
117-
}
108+
// With MySQL/MySQLi/SQLite, REPLACE INTO avoids a user having two rows in the online table
109+
switch ($db_type) {
110+
case 'mysql':
111+
case 'mysqli':
112+
case 'mysql_innodb':
113+
case 'mysqli_innodb':
114+
case 'sqlite':
115+
case 'sqlite3':
116+
\ORM::for_table($feather->db->prefix.'online')->raw_execute('REPLACE INTO '.$feather->db->prefix.'online (user_id, ident, logged) VALUES(:user_id, :ident, :logged)', array(':user_id' => $feather->user->id, ':ident' => $feather->user->username, ':logged' => $feather->user->logged));
117+
break;
118118

119-
// Reset tracked topics
120-
set_tracked_topics(null);
121-
} else {
122-
// Special case: We've timed out, but no other user has browsed the forums since we timed out
123-
if ($feather->user->logged < ($now-$feather_config['o_timeout_visit'])) {
124-
\ORM::for_table($this->db->prefix.'users')->where('id', $feather->user->id)
125-
->find_one()
126-
->set('last_visit', $feather->user->logged)
127-
->save();
128-
$feather->user->last_visit = $feather->user->logged;
129-
}
119+
default:
120+
\ORM::for_table($feather->db->prefix.'online')->raw_execute('INSERT INTO '.$feather->db->prefix.'online (user_id, ident, logged) SELECT :user_id, :ident, :logged WHERE NOT EXISTS (SELECT 1 FROM '.$feather->db->prefix.'online WHERE user_id=:user_id)', array(':user_id' => $feather->user->id, ':ident' => $feather->user->username, ':logged' => $feather->user->logged));
121+
break;
122+
}
130123

131-
$idle_sql = ($feather->user->idle == '1') ? ', idle=0' : '';
132-
133-
\ORM::for_table($db->prefix.'online')->raw_execute('UPDATE '.$db->prefix.'online SET logged='.$now.$idle_sql.' WHERE user_id=:user_id', array(':user_id' => $feather->user->id));
124+
// Reset tracked topics
125+
set_tracked_topics(null);
134126

135-
// Update tracked topics with the current expire time
136-
if (isset($_COOKIE[$cookie_name.'_track'])) {
137-
forum_setcookie($cookie_name.'_track', $_COOKIE[$cookie_name.'_track'], $now + $feather_config['o_timeout_visit']);
138-
}
139-
}
140127
} else {
141-
if (!$feather->user->logged) {
142-
$feather->user->logged = $feather->user->last_visit;
128+
// Special case: We've timed out, but no other user has browsed the forums since we timed out
129+
if ($feather->user->logged < ($now-$feather->config['o_timeout_visit'])) {
130+
\ORM::for_table($feather->db->prefix.'users')->where('id', $feather->user->id)
131+
->find_one()
132+
->set('last_visit', $feather->user->logged)
133+
->save();
134+
$feather->user->last_visit = $feather->user->logged;
143135
}
144-
}
145136

146-
$feather->user->is_guest = false;
147-
$feather->user->is_admmod = $feather->user->g_id == FEATHER_ADMIN || $feather->user->g_moderator == '1';
137+
$idle_sql = ($feather->user->idle == '1') ? ', idle=0' : '';
138+
139+
\ORM::for_table($feather->db->prefix.'online')->raw_execute('UPDATE '.$feather->db->prefix.'online SET logged='.$now.$idle_sql.' WHERE user_id=:user_id', array(':user_id' => $feather->user->id));
140+
141+
// Update tracked topics with the current expire time
142+
$cookie_tracked_topics = $feather->getCookie($cookie_name.'_track');
143+
if (isset($cookie_tracked_topics)) {
144+
set_tracked_topics(json_decode($cookie_tracked_topics, true));
145+
}
146+
}
148147
} else {
149-
set_default_user();
148+
if (!$feather->user->logged) {
149+
$feather->user->logged = $feather->user->last_visit;
150+
}
150151
}
151152
}
152153

@@ -308,14 +309,19 @@ function set_default_user()
308309

309310

310311
//
311-
// Set a cookie, FluxBB style!
312-
// Wrapper for forum_setcookie
312+
// Wrapper for Slim setCookie method
313313
//
314-
function feather_setcookie($user_id, $password_hash, $expire)
314+
function feather_setcookie($user_id, $password, $expires)
315315
{
316316
global $cookie_name, $cookie_seed;
317317

318-
forum_setcookie($cookie_name, $user_id.'|'.hash_hmac('sha1', $password_hash, $cookie_seed.'_password_hash').'|'.$expire.'|'.hash_hmac('sha1', $user_id.'|'.$expire, $cookie_seed.'_cookie_hash'), $expire);
318+
// Get Slim current session
319+
$feather = \Slim\Slim::getInstance();
320+
$cookie_data = array('user_id' => $user_id,
321+
'password_hash' => hash_hmac('sha1', $password, $cookie_seed.'_password_hash'),
322+
'expires' => $expires,
323+
'checksum' => hash_hmac('sha1', $user_id.$expires, $cookie_seed.'_checksum'));
324+
$feather->setCookie($cookie_name, json_encode($cookie_data), $expires);
319325
}
320326

321327

@@ -557,33 +563,22 @@ function generate_page_title($page_title, $p = null)
557563
//
558564
// Save array of tracked topics in cookie
559565
//
560-
function set_tracked_topics($tracked_topics)
566+
function set_tracked_topics($tracked_topics = null)
561567
{
562-
global $cookie_name, $cookie_path, $cookie_domain, $cookie_secure, $feather_config;
568+
global $cookie_name;
569+
570+
// Get Slim current session
571+
$feather = \Slim\Slim::getInstance();
563572

564-
$cookie_data = '';
565573
if (!empty($tracked_topics)) {
566574
// Sort the arrays (latest read first)
567575
arsort($tracked_topics['topics'], SORT_NUMERIC);
568576
arsort($tracked_topics['forums'], SORT_NUMERIC);
569-
570-
// Homebrew serialization (to avoid having to run unserialize() on cookie data)
571-
foreach ($tracked_topics['topics'] as $id => $timestamp) {
572-
$cookie_data .= 't'.$id.'='.$timestamp.';';
573-
}
574-
foreach ($tracked_topics['forums'] as $id => $timestamp) {
575-
$cookie_data .= 'f'.$id.'='.$timestamp.';';
576-
}
577-
578-
// Enforce a byte size limit (4096 minus some space for the cookie name - defaults to 4048)
579-
if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE) {
580-
$cookie_data = substr($cookie_data, 0, FORUM_MAX_COOKIE_SIZE);
581-
$cookie_data = substr($cookie_data, 0, strrpos($cookie_data, ';')).';';
582-
}
577+
} else {
578+
$tracked_topics = array('topics' => array(), 'forums' => array());
583579
}
584580

585-
forum_setcookie($cookie_name.'_track', $cookie_data, time() + $feather_config['o_timeout_visit']);
586-
$_COOKIE[$cookie_name.'_track'] = $cookie_data; // Set it directly in $_COOKIE as well
581+
return $feather->setCookie($cookie_name . '_track', json_encode($tracked_topics), time() + $feather->config['o_timeout_visit']);
587582
}
588583

589584

@@ -594,28 +589,16 @@ function get_tracked_topics()
594589
{
595590
global $cookie_name;
596591

597-
$cookie_data = isset($_COOKIE[$cookie_name.'_track']) ? $_COOKIE[$cookie_name.'_track'] : false;
598-
if (!$cookie_data) {
599-
return array('topics' => array(), 'forums' => array());
600-
}
592+
// Get Slim current session
593+
$feather = \Slim\Slim::getInstance();
601594

602-
if (strlen($cookie_data) > FORUM_MAX_COOKIE_SIZE) {
603-
return array('topics' => array(), 'forums' => array());
604-
}
595+
$cookie_raw = $feather->getCookie($cookie_name.'_track');
605596

606-
// Unserialize data from cookie
607-
$tracked_topics = array('topics' => array(), 'forums' => array());
608-
$temp = explode(';', $cookie_data);
609-
foreach ($temp as $t) {
610-
$type = substr($t, 0, 1) == 'f' ? 'forums' : 'topics';
611-
$id = intval(substr($t, 1));
612-
$timestamp = intval(substr($t, strpos($t, '=') + 1));
613-
if ($id > 0 && $timestamp > 0) {
614-
$tracked_topics[$type][$id] = $timestamp;
615-
}
597+
if (isset($cookie_raw)) {
598+
$cookie_data = json_decode($cookie_raw, true);
599+
return $cookie_data;
616600
}
617-
618-
return $tracked_topics;
601+
return array('topics' => array(), 'forums' => array());
619602
}
620603

621604

‎index.php‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
// Load middlewares
2222
$feather->add(new \Slim\Extras\Middleware\CsrfGuard('featherbb_csrf')); // CSRF
2323

24+
// Cookie encryption
25+
$feather->config('cookies.encrypt', true);
26+
2427
// Load FeatherBB
2528
define('FEATHER_ROOT', dirname(__FILE__).'/');
2629
require FEATHER_ROOT.'include/common.php';

0 commit comments

Comments
 (0)