summaryrefslogblamecommitdiffstats
path: root/inc/session.inc.php
blob: ccb878cd69646caf20c9914ee913160ad28f32a0 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

     

                        

                           
             
 

                                     

                                            
                                                      
        
                                                                     
         
                                         
                                                                                                          

                                             


                                                 
                                           
                                                     
                                           



                                           
 
                                                                                          
         
                                               







                                                                                                  
                                                   
         
 
                                           

                                                     

                                           
                                                                                      

                                            
                                              
                                   
                             
         
 





                                               
         

                                                                              
                                            
         
 




                                                                                 
                                                                                 
         
                                          
                                                                                                     


                                                 
                                                                                                                    
                 
                                          

         
                                                     
         
                                         
                                                                                                      

                                           
                                                                          

                                     



                                 
                                             
         



                                                                      
                                     


                                    
 




                                                                              
                                                          






                                                                                         
                                                   
         
                                         
         
 
                                                       
         
                                          
                                                                                          

                                                                                                                           
                              






                                                                                    

                                                                                                                           
                                                    



                                                               


                                                                                            
                                                          

                         

                            
 
                                                                          



                                                                                            
                                                                                                       
                                  
                                                                                                                          

                                                        
                                               

                   
 
                                                      
         
                              



                                                                          


                                                                 


                                      
                                                    






                                                                     
                                                           
         




                                                                                          
         
 
 
<?php

declare(strict_types=1);

require_once('config.php');

class Session
{
	private static $sid = false;
	private static $data = false;
	private static $dataChanged = false;
	private static $userId = 0;
	private static $updateSessionDateline = false;
	
	private static function generateSessionId(string $salt): void
	{
		if (self::$sid !== false)
			ErrorHandler::traceError('Error: Asked to generate session id when already set.');
		self::$sid = sha1($salt . ','
			. mt_rand(0, 65535)
			. $_SERVER['REMOTE_ADDR']
			. mt_rand(0, 65535)
			. $_SERVER['REMOTE_PORT']
			. mt_rand(0, 65535)
			. $_SERVER['HTTP_USER_AGENT']
			. mt_rand(0, 65535)
			. microtime(true)
			. mt_rand(0, 65535)
		);
	}

	public static function create(string $salt, int $userId, bool $fixedAddress): void
	{
		self::generateSessionId($salt);
		self::$data = [];
		self::$userId = $userId;
		Database::exec("INSERT INTO session (sid, userid, dateline, lastip, fixedip, data)
				VALUES (:sid, :userid, 0, '', :fixedip, '')", [
					'sid' => self::$sid,
					'userid' => $userId,
					'fixedip' => $fixedAddress ? 1 : 0,
		]);
		self::setupSessionAccounting(true);
	}

	public static function load(): bool
	{
		// Try to load session id from cookie
		if (!self::loadSessionId())
			return false;
		// Succeeded, now try to load session data. If successful, job is done
		if (self::readSessionData())
			return true;
		// Loading session data failed
		self::$sid = false;
		return false;
	}

	public static function getUserId(): int
	{
		return self::$userId;
	}

	public static function get(string $key)
	{
		if (!isset(self::$data[$key]) || !is_array(self::$data[$key]))
			return false;
		return self::$data[$key][0];
	}

	/**
	 * @param string $key key of entry
	 * @param mixed $value data to store for key, false = delete
	 * @param int|false $validMinutes validity in minutes, or false = forever
	 */
	public static function set(string $key, $value, $validMinutes = 60): void
	{
		if (self::$data === false)
			ErrorHandler::traceError('Tried to set session data with no active session');
		if ($value === false) {
			unset(self::$data[$key]);
		} else {
			self::$data[$key] = [$value, $validMinutes === false ? false : time() + $validMinutes * 60];
		}
		self::$dataChanged = true;
	}
	
	private static function loadSessionId(): bool
	{
		if (self::$sid !== false)
			ErrorHandler::traceError('Error: Asked to load session id when already set.');
		if (empty($_COOKIE['sid']))
			return false;
		$id = preg_replace('/[^a-zA-Z0-9]/', '', $_COOKIE['sid']);
		if (empty($id))
			return false;
		self::$sid = $id;
		return true;
	}
	
	public static function delete(): void
	{
		if (self::$sid === false)
			return;
		Database::exec("DELETE FROM session WHERE sid = :sid",
			['sid' => self::$sid]);
		self::deleteCookie();
		self::$sid = false;
		self::$data = false;
	}

	/**
	 * Kill all sessions of currently logged-in user. This can be used as
	 * a security measure if the user suspects that a session left open on
	 * another device could be/is being abused.
	 */
	public static function deleteAllButCurrent(): void
	{
		if (self::$sid === false)
			return;
		Database::exec("DELETE FROM session WHERE sid <> :sid AND userid = :uid",
			['sid' => self::$sid, 'uid' => self::$userId]);
	}

	public static function deleteCookie(): void
	{
		Util::clearCookie('sid');
	}

	private static function readSessionData(): bool
	{
		if (self::$data !== false)
			ErrorHandler::traceError('Tried to call read session data twice');
		$row = Database::queryFirst("SELECT userid, dateline, lastip, fixedip, data FROM session WHERE sid = :sid",
			['sid' => self::$sid]);
		$now = time();
		if ($row === false || $row['dateline'] < $now) {
			self::delete();
			return false;
		}
		if ($row['fixedip'] && $row['lastip'] !== $_SERVER['REMOTE_ADDR']) {
			return false; // Ignore but don't invalidate
		}
		// Refresh cookie if appropriate
		self::setupSessionAccounting(Request::isGet() && $row['dateline'] + 86400 < $now + CONFIG_SESSION_TIMEOUT);
		self::$userId = (int)$row['userid'];
		self::$data = @json_decode($row['data'], true);
		if (!is_array(self::$data)) {
			self::$data = [];
		}
		foreach (array_keys(self::$data) as $key) {
			if (self::$data[$key][1] !== false && self::$data[$key][1] < $now) {
				unset(self::$data[$key]);
				self::$dataChanged = true;
			}
		}
		return true;
	}

	private static function setupSessionAccounting(bool $cookie): void
	{
		if ($cookie) {
			self::$updateSessionDateline = true;
			$ret = setcookie('sid', self::$sid, time() + CONFIG_SESSION_TIMEOUT,
				'', '', !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
			if (!$ret)
				ErrorHandler::traceError('Error: Could not set Cookie for Client (headers already sent)');
		}
		register_shutdown_function(function () {
			self::saveOnShutdown();
		});
	}

	private static function saveOnShutdown(): void
	{
		$now = time();
		$args = ['lastip' => $_SERVER['REMOTE_ADDR']];
		if (self::$updateSessionDateline) {
			$args['dateline'] = $now + CONFIG_SESSION_TIMEOUT;
		}
		if (self::$dataChanged) {
			$args['data'] = json_encode(self::$data);
		}
		self::saveData($args);
	}

	public static function saveExtraData(): void
	{
		if (!self::$dataChanged)
			return;
		self::saveData(['data' => json_encode(self::$data)]);
		self::$dataChanged = false;
	}

	private static function saveData(array $args): void
	{
		$query = "UPDATE session SET " . implode(', ', array_map(function ($key) {
				return "$key = :$key";
			}, array_keys($args))) . " WHERE sid = :sid";
		$args['sid'] = self::$sid;
		Database::exec($query, $args);
	}

}