Sindbad~EG File Manager
<?php
/*
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2007 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* Container for session related data.
* @package GalleryCore
* @subpackage Classes
* @author Bharat Mediratta <bharat@menalto.com>
* @version $Revision: 15641 $
*/
/**
* Define gallery session key for this install.
*/
define('SESSION_ID_PARAMETER', 'GALLERYSID');
/**
* Define a temporary session id for new guest user sessions. If the guest needs a session, all
* HTML already generated will be scanned to replace this temporary id with the correct id.
*/
define('SESSION_TEMP_ID', 'TMP_SESSION_ID_DI_NOISSES_PMT');
/**
* Container for session related data.
*/
class GallerySession {
/**
* The time this session was created.
* @var int
* @access private
*/
var $_creationTime;
/**
* The time this session was last modified.
* @var int
* @access private
*/
var $_modificationTime;
/**
* The id of this session.
* @var string
* @access private
*/
var $_sessionId;
/**
* Is it OK to rely on cookies for this session?
* @var boolean
* @access private
*/
var $_isUsingCookies = false;
/**
* The id of the session's user.
* @var int
* @access private
*/
var $_userId;
/**
* The serialized session data as loaded from database.
* @var string
* @access private
*/
var $_loadedSessionData;
/**
* The session data.
* @var array
* @access private
*/
var $_sessionData;
/**
* The domain for our cookie.
* @var string
* @access private
*/
var $_cookieDomain;
/**
* A set of identifying values that we can use to verify that the session is coming from the
* same browser as it used to (to prevent session hijacking).
* @var array
* @access private
*/
var $_remoteIdentifier;
/**
* Whether the session has been saved in the persistent store during the current request
* handling. Used to determine whether we need to "touch" the session to prevent it from
* expiring in case the session data hasn't changed anyway.
* @var boolean
* @access private
*/
var $_isSessionSaved;
/**
* Whether this is a session for a search engine.
* @var boolean
* @access private
*/
var $_isSearchEngineSession;
/**
* Whether a persistent session has been created (not updated) in this request.
* @var boolean
* @access private
*/
var $_isPersistentSessionNew;
/**
* Whether no pseudo/temporary session id should be returned on getId() if there is no real
* session id yet.
* @var boolean
* @access private
*/
var $_doNotUseTempId;
/**
* Whether this is a persistent session or just a session for this single request.
* @var boolean
* @access private
*/
var $_isPersistent;
/**
* Whether a persistent session is allowed to be created in this request.
* @var boolean
* @access private
*/
var $_isPersistentSessionAllowedForRequest;
/**
* Whether a persistent session should be forced to be created.
* @var boolean
* @access private
*/
var $_forceSaveSession;
/**
* How many sessions to delete per expireSessions() call.
* @var int
* @access private
*/
var $_expirationLimit = 500;
/**
* Authentication token to verify genuine requests.
* @var string
* @access private
*/
var $_authToken = '';
/**
* Either create a new session, or attach to an existing one.
* @return object GalleryStatus a status code
*/
function init() {
global $gallery;
/* Check to see if we have an existing session */
$this->_sessionId = '';
$this->_isSearchEngineSession = $this->_isSessionSaved = $this->_isPersistent = false;
$this->_isPersistentSessionNew = false;
if (!empty($_COOKIE[SESSION_ID_PARAMETER])) {
/* Fix PHP HTTP_COOKIE header bug http://bugs.php.net/bug.php?id=32802 */
GalleryUtilities::fixCookieVars();
/* If we get id parameter as a cookie, then it also means cookies are functioning */
$this->_sessionId = $_COOKIE[SESSION_ID_PARAMETER];
$this->_isUsingCookies = true;
/* Allow the URL to override the cookie, in rare occasions */
$sessionId = GalleryUtilities::getRequestVariables(SESSION_ID_PARAMETER);
if ($sessionId) {
$this->_sessionId = $sessionId;
}
} else {
/*
* Many search engine crawlers don't use cookies. Normally this leads to us putting the
* session id in the URL. But doing so causes the search engine to do a lot of extra
* work to weed out the session id, which they may not do very well. So if we detect
* that this is a search engine, don't create a session under all circumstances and
* don't send cookies / don't append the sessionId to URLs.
*/
$searchEngineId = GalleryUtilities::identifySearchEngine();
if (isset($searchEngineId)) {
$this->_isUsingCookies = true;
$this->doNotUseTempId();
$this->_isSearchEngineSession = true;
} else {
/* When logging out (resetting the session), we already know if cookies are used */
if (!$this->isUsingCookies()) {
$this->_isUsingCookies = false;
}
$this->_sessionId = GalleryUtilities::getRequestVariables(SESSION_ID_PARAMETER);
}
}
/* Sanitize the session id */
$this->_sessionId = is_string($this->_sessionId) ? $this->_sessionId : '';
$this->_sessionId = preg_replace('/[^a-fA-F0-9]/', '', $this->_sessionId);
/* Prevent from querying the DB for sessionIds that are incorrect anyway */
if (strlen($this->_sessionId) != 32) {
$this->_sessionId = '';
}
$this->_sessionId = GalleryUtilities::strToLower($this->_sessionId);
/* Load session data if a session with that id exists and expire the session if necessary */
$ret = $this->_loadSessionData();
if ($ret) {
return $ret;
}
$this->_forceSaveSession = false;
/* Only need to check for session hijacking if the session is not new */
if ($this->_isPersistent) {
/* Verify the remote address to avoid casual session hijacking */
$currentRemoteIdentifier = $this->getRemoteIdentifier();
if (!isset($this->_remoteIdentifier)) {
/*
* Initialize remoteIdentifier if not yet set (via initEmpty(true) from a previous
* request when creating a session for a 3rd party)
*/
$this->_remoteIdentifier = $currentRemoteIdentifier;
$this->_forceSaveSession = true;
} else if ($this->compareIdentifiers($this->_remoteIdentifier,
$currentRemoteIdentifier) == 0) {
/* If we upgrade, allowSessionAccess could be missing */
$allowFrom = @$gallery->getConfig('allowSessionAccess');
if (!$allowFrom || $currentRemoteIdentifier[0] != $allowFrom) {
if ($gallery->getDebug()) {
$gallery->debug('Session hijack detected: saved vs. current below');
$gallery->debug_r($this->_remoteIdentifier);
$gallery->debug_r($currentRemoteIdentifier);
}
/*
* The session was not created from this browser address, so reset our data to
* prevent hijacking
*/
$this->_sessionId = '';
$ret = $this->_emptySessionData();
if ($ret) {
return $ret;
}
}
}
} /* End for existing persistent sessions */
return null;
}
/**
* Start session by ensuring we've got a valid, unique sessionId and send cookie if necessary.
* @return object GalleryStatus a status code
*/
function start() {
if (!$this->_isPersistentSessionAllowedForRequest() && !$this->_forceSaveSession) {
/* No need to send a cookie or to get a new sessionId */
return null;
}
/* If session hasn't any important data/attributes, we don't need a persistent session */
list ($ret, $isRequired) = $this->_isPersistentSessionRequired();
if ($ret) {
return $ret;
}
if (!$isRequired) {
return null;
}
/* For new sessions (no sessionId in DB yet), first get a new, collision-free sessionId */
if (!$this->_isPersistent) {
/*
* Since getting a new collision-free sessionId requires a DB query anyway, we save the
* whole session in this case. In all typical cases (main.php), there won't be any
* session changes after start() was called, thus we actually save the session only once
* per request.
*/
$ret = $this->_acquireNewPersistentSession();
if ($ret) {
return $ret;
}
} /* Else: not a new session */
/*
* Send a cookie to the browser, if necessry ( this must be done before we start outputting
* HTML because the DownloadItem requests might come in before we reach $session->save() )
*/
/* Don't save session / send cookie for DownloadItem, CSS, migrate.Redirect requests */
if (!isset($_COOKIE[SESSION_ID_PARAMETER])
|| $this->_forceSaveSession
|| $_COOKIE[SESSION_ID_PARAMETER] != $this->_sessionId) {
$ret = $this->_setCookie();
if ($ret) {
return $ret;
}
}
return null;
}
/**
* Save any session changes to the store. Does not save sessions that don't have a sessionId
* yet. Triggers the expiration of existing persistent sessions in 2% of all calls.
* @return object GalleryStatus a status code
*/
function save() {
global $gallery;
$phpVm = $gallery->getPhpVm();
$dieRoll = $phpVm->rand(1, 100);
if (!empty($this->_sessionId)
&& ($this->_isPersistentSessionAllowedForRequest() || $this->_forceSaveSession)) {
$this->_sessionId = GalleryUtilities::strtolower($this->_sessionId);
if (empty($this->_userId)) {
return GalleryCoreApi::error(ERROR_MISSING_VALUE);
}
/* Only bother saving if we've been modified at all */
$serialized = $this->_getSerializedSession();
if ($serialized != $this->_loadedSessionData) {
if (!$this->_isPersistent) {
$ret = $this->_acquireNewPersistentSession();
} else {
$this->_modificationTime = $phpVm->time();
$ret = GalleryCoreApi::updateMapEntry('GallerySessionMap',
array('id' => $this->_sessionId), array('userId' => $this->_userId,
'remoteIdentifier' => serialize($this->_remoteIdentifier),
'creationTimestamp' => $this->_creationTime,
'modificationTimestamp' => $this->_modificationTime,
'data' => serialize($this->_sessionData)));
}
if ($ret) {
return $ret;
}
$this->_isSessionSaved = true;
} else if (!$this->_isSessionSaved) {
/*
* 5% of the time touch the session file so that it doesn't get expired. We can't
* count on the atime being set, since you can disable that on some operating
* systems to get performance gains.
*/
if ($dieRoll <= 5) {
$this->_modificationTime = $phpVm->time();
$ret = GalleryCoreApi::updateMapEntry('GallerySessionMap',
array('id' => $this->_sessionId),
array('modificationTimestamp' => $this->_modificationTime));
if ($ret) {
return $ret;
}
$this->_isSessionSaved = true;
}
}
$this->_loadedSessionData = $this->_getSerializedSession();
$this->_isPersistent = true;
}
/* Perform garbage collection 2% of the time when a new session was created */
if ($this->_isPersistentSessionNew && $dieRoll <= 2 ) {
$ret = $this->_expireSessions();
if ($ret) {
return $ret;
}
}
return null;
}
/**
* Set a new/unused sessionid.
* @param boolean $emptyRemoteId (optional) if true don't initialize remoteIdentifier
* @param int $userId (optional) user for session, defaults to anonymous
* @return object GalleryStatus a status code
*/
function initEmpty($emptyRemoteId=false, $userId=null) {
$this->_emptySessionData();
$this->_sessionId = '';
if ($emptyRemoteId) {
$this->_remoteIdentifier = null;
}
if (empty($userId)) {
list ($ret, $userId) = GalleryCoreApi::getAnonymousUserId();
if ($ret) {
return $ret;
}
}
$this->_userId = (int)$userId;
/* Get a sessionId, don't send cookies */
$ret = $this->_acquireNewPersistentSession();
if ($ret) {
return $ret;
}
return null;
}
/**
* Clean/remove and reinitialize a session.
* @return object GalleryStatus a status code
*/
function reset() {
global $gallery;
if (!empty($this->_sessionId)) {
$this->_sessionId = GalleryUtilities::strToLower($this->_sessionId);
$ret = GalleryCoreApi::removeMapEntry('GallerySessionMap',
array('id' => $this->_sessionId));
if ($ret) {
return $ret;
}
}
$this->_sessionId = '';
$this->_userId = null;
/* Unset the cookie and any request variables so that we'll regenerate a new id in init() */
GalleryUtilities::removeRequestVariable(SESSION_ID_PARAMETER);
unset($_COOKIE[SESSION_ID_PARAMETER]);
/* Reset 'cached' variables */
$this->_cookieDomain = null;
/* Delete the cookie on the browser */
$ret = $this->_setCookie(true);
if ($ret) {
return $ret;
}
$ret = $this->init();
if ($ret) {
return $ret;
}
return null;
}
/**
* Regenerate the session id to prevent a session fixation attack by a hostile website.
* @return object GalleryStatus a status code
*/
function regenerate() {
/* Store the current session data */
$localSessionData = $this->_sessionData;
$localLoadedSessionData = $this->_loadedSessionData;
$userId = $this->getUserId();
/* Reset the session data to create a new session id */
$ret = $this->reset();
if ($ret) {
return $ret;
}
/* Restore the stored session data */
$this->_sessionData = $localSessionData;
$this->_loadedSessionData = $localLoadedSessionData;
$this->setUserId($userId);
/* Start the session again (create a session in the DB, ...) */
$ret = $this->start();
if ($ret) {
return $ret;
}
/* Replace old session id with new one in any return or navigation URLs */
$key = GalleryUtilities::prefixFormVariable($this->getKey()) . '=';
$match = '/' . $key . '[a-fA-F0-9]+/';
$replace = $key . $this->getId();
foreach (array('return', 'formUrl') as $key) {
if (GalleryUtilities::hasRequestVariable($key)) {
GalleryUtilities::putRequestVariable($key,
preg_replace($match, $replace, GalleryUtilities::getRequestVariables($key)));
}
}
if ($this->exists('core.navigation')) {
$navigation = $this->get('core.navigation');
foreach (array_keys($navigation) as $navId) {
if (isset($navigation[$navId]['data']['returnUrl'])) {
$navigation[$navId]['data']['returnUrl'] =
preg_replace($match, $replace, $navigation[$navId]['data']['returnUrl']);
}
}
$this->put('core.navigation', $navigation);
}
return null;
}
/**
* Send back a cookie to the browser.
* @param boolean $delete (optional) whether to delete the cookie
* @return object GalleryStatus a status code
* @access private
*/
function _setCookie($delete=false) {
global $gallery;
$phpVm = $gallery->getPhpVm();
/*
* Send back a cookie
*
* TODO: Need to be able to decide for certain that the browser isn't accepting cookies so
* that we can stop sending them. We can do this by recording how many times we've sent a
* cookie, and how many times that we've received one back in return. Leave that for later.
*/
if (!$delete) {
$cookie = 'Set-Cookie: ' . SESSION_ID_PARAMETER . '=' . $this->_sessionId;
} else {
$cookie = 'Set-Cookie: ' . SESSION_ID_PARAMETER . '=';
}
/*
* As part of the session/cookie management, we are forced to append the SID to all
* DownloadItem URLs in embedded Gallery if cookie path/domain are not configured
*/
list ($ret, $this->_cookieDomain) = $this->getCookieDomain();
if ($ret) {
return $ret;
}
$urlGenerator =& $gallery->getUrlGenerator();
list ($ret, $cookiePath) = $urlGenerator->getCookiePath();
if ($ret) {
return $ret;
}
list ($ret, $sessionLifetime) =
GalleryCoreApi::getPluginParameter('module', 'core', 'session.lifetime');
if ($ret) {
if ($ret->getErrorCode() & ERROR_STORAGE_FAILURE) {
/* During installation it's possible the database isn't around yet. Keep going. */
$sessionLifetime = 0;
} else {
return $ret;
}
}
if ($delete) {
/* Expires in the past instructs the browser to delete the cookie */
$expirationDate = GalleryUtilities::getHttpDate($phpVm->time() - (365 * 24 * 3600));
$cookie .= '; expires=' . $expirationDate;
} else if ($sessionLifetime > 0) {
$expirationDate = GalleryUtilities::getHttpDate($phpVm->time() + $sessionLifetime);
$cookie .= '; expires=' . $expirationDate;
}
/* Because of short URLs, the cookie path must always be set explicitly */
$cookie .= '; path=' . $cookiePath;
/*
* Set the cookie domain only if needed, ie. embedded multi-subdomain installs that is when
* Gallery is installed on a different subdomain than the embedding application.
*
* Q: Why not set the cookie domain to .example.com (omitting the subdomains) and the cookie
* path to /?
* A: This is actually a perfect fix (we had it in cvs between beta 3 and beta 4), because
* the case where a browser sends back multiple cookies is completely avoided. But it has a
* major flaw: security! When people share a common domain name, eg. by
* example.com/~accountName/ or by accountName.example.com, they will all have cookies with
* .example.com and /. To differentiate the cookies, we introduced the cookieId, ie. each
* Gallery install had its own unique cookie name. But when a user accessed multiple
* accounts on this shared domain, the Gallery cookie is sent to all accounts which opens
* the door for session hijacking. This single reason, security, made us not choose this
* approach.
*
* Q: Why not set the cookie domain to the actual host string (ie. .www.example.com when
* Gallery is accessed like that or .example.com in other requests, ...)?
* A: Because in RFC 2965, there is no rule in what order the browser should send back the
* cookies. And thus, PHP/Gallery wouldn't know which is the right cookie.
*
* Q: Why not just omit the cookie domain in the set cookie calls?
* A: Actually, this is a good solution. Because if no cookie domain was set, the browser
* sends only cookies back that match the requested domain exactly. So it won't return a
* example.com cookie for www.example.com and the other way around. But, and this is a big
* but, Internet Explorer doesn't conform to the RFC 2965. IE sends back example.com and
* www.example.com cookies when it shouldn't. Together with the php bug (least, most
* specific cookie match in HTTP_COOKIE), this results in an unpredictable behavior for
* various php version / IE scenarios. Luckily we can fix this manually with
* fixCookieVars(). That's why we chose this approach.
*
* Q: Why append the session id in embedded Gallery to all DownloadItem URLs?
* A: In embedded Gallery, all DownloadItem requests still go directly to Gallery and not
* through the emApp for performance reasons. If we set the cookie path in embedded Gallery
* to a path that matches embedded and standalone Gallery, then the standalone Gallery
* cookies always have precendence over the cookies from embedded Gallery. This leads to
* cookie conflicts, if the two cookies correspond to different sessions. That's why we are
* forced to append the session id to embedded URLs that require session management and go
* directly to standalone Gallery. DownloadItem is the only request that falls into this
* category.
*
* Q: Why force the Gallery base (standalone) path for Java applet cookies?
* A: Because the applets talk to Gallery directly. If the cookie path was set to the
* embedded Gallery path, then it would not be selected for the HTTP requests of the applet
* to Gallery, because it wouldn't path-match.
*
* Therefore we don't set the cookie domain by default and offer the option to set it to a
* configured value if it is required (embedded multi-subdomain G2). In embedded Gallery,
* we have to append the session id to all DownloadItem unless the cookie path is configured
* such that standalone and embedded Gallery set the same cookie path.
*/
if (!empty($this->_cookieDomain)) {
$cookie .= '; domain=' . $this->_cookieDomain;
}
/*
* Tag on the HttpOnly modifier. IE 6.0 SP1 will prevent any cookies with this in it from
* being visible to JavaScript, which mitigates XSS attacks.
*/
$cookie .= '; HttpOnly=1';
/*
* Init may be called multiple times (from unit tests) but don't send headers more than
* once. Use our PhpVm for testability.
*/
$phpVm = $gallery->getPhpVm();
if (!$phpVm->headers_sent()) {
GalleryUtilities::setResponseHeader($cookie);
}
return null;
}
/**
* Acquire a new persistent session and guarantee we've got a valid, unqiue sessionId.
* @return object GalleryStatus a status code
*/
function _acquireNewPersistentSession() {
global $gallery;
$phpVm = $gallery->getPhpVm();
$storage =& $gallery->getStorage();
/* Assemble the data */
$this->_modificationTime = $phpVm->time();
$data = array('userId' => $this->_userId,
'remoteIdentifier' => serialize($this->_remoteIdentifier),
'creationTimestamp' => $this->_creationTime,
'modificationTimestamp' => $this->_modificationTime,
'data' => serialize($this->_sessionData));
/* Get new sessionId, there's a 1:2^128 probability of collision (md5), try it 5 times */
$remoteHost = $this->_remoteIdentifier[0];
$attempt = 0;
$success = false;
do {
/* If there's sessionId given, first try it with this, else generate a new one */
if ($attempt != 0 || empty($this->_sessionId)) {
$this->_sessionId =
$phpVm->md5(uniqid(substr($remoteHost . microtime() . rand(1, 32767), 0, 114)));
}
$this->_sessionId = $data['id'] = GalleryUtilities::strToLower($this->_sessionId);
$ret = @GalleryCoreApi::addMapEntry('GallerySessionMap', $data);
if ($ret) {
if (!($ret->getErrorCode() & ERROR_STORAGE_FAILURE)) {
/* No luck after x attempts, give up, stop hitting the server with DB queries */
return $ret;
}
} else {
$success = true;
}
/*
* Make sure the session exists before other requests (DownloadItem, printing shops,
* ...) arrive that rely on it
*/
$ret = $storage->checkPoint();
if ($ret) {
return $ret;
}
} while (!$success && $attempt++ < 4);
if (!$success) {
return GalleryCoreApi::error(ERROR_COLLISION);
}
$this->_isPersistent = true;
/* Make sure we don't save the session a 2nd time for vain */
$this->_loadedSessionData = $this->_getSerializedSession();
/* Also prevent from doing a "touch" */
$this->_isSessionSaved = true;
/* To remember to replace SESSION_TEMP_ID with the real id in the generated HTML */
$this->_isPersistentSessionNew = true;
return null;
}
/**
* Check whether this session should be persistent or not.
*
* For guest users, we don't create sessions, unless their session has non-default data. Also,
* the session based permission cache and the navigation isn't regarded important enough to
* create a session.
*
* @return array object GalleryStatus a status code, boolean session is necessary
* @access private
*/
function _isPersistentSessionRequired() {
/* For existing sessions, the session is necessary */
if ($this->_isPersistent || $this->_forceSaveSession) {
return array(null, true);
}
if (!empty($this->_isSearchEngineSession) || empty($this->_userId)) {
return array(null, false);
}
list ($ret, $isAnonymous) = GalleryCoreApi::isAnonymousUser($this->_userId);
if ($ret) {
return array($ret, null);
}
if ($isAnonymous) {
$sessionDataCopy = $this->_sessionData;
/*
* - lastViewed: We don't care about viewed count, we can check that less strict with
* HTTP modified since headers
* - permissionCache: we don't care about the permission cache (session based
* permissions are stored as ACLs in another session data entry)
* - navigation: no, we don't need navigation
* - language: only useful if it's different from what we'd have set anyway
* - embed.id.externalUser: not important if mapped to the anonymousUser, else the
* userId is not == anonymousUserId
* - authToken: we only check the authToken for persistent sessions
*/
unset($sessionDataCopy['core.lastViewed']);
unset($sessionDataCopy['permissionCache']);
unset($sessionDataCopy['core.navigation']);
unset($sessionDataCopy['embed.id.externalUser']);
unset($sessionDataCopy['core.authToken']);
list ($ret, $detectedLanguageCode) = GalleryTranslator::getDefaultLanguageCode();
if ($ret) {
return array($ret, null);
}
if (isset($sessionDataCopy['core.language']) &&
$sessionDataCopy['core.language'] == $detectedLanguageCode) {
unset($sessionDataCopy['core.language']);
}
/* If there's anything left in the session data, we should probably create a session */
return array(null, !empty($sessionDataCopy));
} else {
return array(null, true);
}
}
/**
* Whether this controller/view request generally allows creating a session.
*
* Don't save session in core.DownloadItem, migrate.Redirect, ... requests
* Reason: In these requests we don't need to save the session or create a new one because
* a) the session is not modified (DownloadItem, CSS)
* b) we return an image / css and not a HTML page (DownloadItem, CSS)
* c) there will be either a DownloadItem / ShowItem request anyway (migrate.Redirect)
* d) in migrate.Redirect requests, the cookie path we would set would most certainly
* be wrong, because the internal mod_rewrite redirect doesn't change all PHP SERVER
* variables
*
* And if we stored the session, it would result in *a lot* unneeded sessions,
* eg. for migrate redirects or hotlinked images.
*
* @return boolean true if a persistent session can be created in this request
* @access private
*/
function _isPersistentSessionAllowedForRequest() {
if (!isset($this->_isPersistentSessionAllowedForRequest)) {
$flag = true; /* Default to true */
list ($view, $controller) = GalleryUtilities::getRequestVariables('view', 'controller');
if (!empty($controller)) {
GalleryCoreApi::requireOnce('modules/core/classes/GalleryController.class');
list ($ret, $controller) = GalleryController::loadController($controller);
if (!$ret && !$controller->shouldSaveSession()) {
$flag = false;
}
} else if (!empty($view)) {
GalleryCoreApi::requireOnce('modules/core/classes/GalleryView.class');
list ($ret, $view) = GalleryView::loadView($view);
if (!$ret && !$view->shouldSaveSession()) {
$flag = false;
}
}
$this->_isPersistentSessionAllowedForRequest = $flag;
}
return $this->_isPersistentSessionAllowedForRequest;
}
/**
* Load the session data or generate a new session with new data. Also sets
* $this->_isPersistent to true if loaded from persistent store.
* @return object GalleryStatus a status code
* @access private
*/
function _loadSessionData() {
global $gallery;
if (!empty($this->_sessionId)) {
$this->_sessionId = GalleryUtilities::strToLower($this->_sessionId);
/* Check if the session has expired */
list ($ret, $lifetime) =
GalleryCoreApi::getPluginParameter('module', 'core', 'session.lifetime');
if ($ret) {
return $ret;
}
list ($ret, $inactivityTimeout) =
GalleryCoreApi::getPluginParameter('module', 'core',
'session.inactivityTimeout');
if ($ret) {
return $ret;
}
$phpVm = $gallery->getPhpVm();
$lifetimeCutoff = $phpVm->time() - $lifetime;
$inactiveCutoff = $phpVm->time() - $inactivityTimeout;
list ($ret, $results) = GalleryCoreApi::getMapEntry('GallerySessionMap',
array('userId', 'remoteIdentifier', 'creationTimestamp',
'modificationTimestamp', 'data'),
array('id' => $this->_sessionId));
if ($ret) {
/* When upgrading from versions before 1.0.22, there's no DB table yet */
list ($ret2, $module) = GalleryCoreApi::loadPlugin('module', 'core');
if ($ret2) {
return $ret;
}
$instVersions = $module->getInstalledVersions();
if (!empty($instVersions['core']) &&
version_compare($instVersions['core'], '1.0.22', '<')) {
$this->_emptySessionData();
return null;
}
return $ret;
}
if ($results->resultCount()) {
$pSession = $results->nextResult();
if ($pSession[3] > $inactiveCutoff && $pSession[2] > $lifetimeCutoff) {
/* A session exists and it's valid */
$this->_userId = (int)$pSession[0];
$this->_remoteIdentifier = unserialize($pSession[1]);
$this->_creationTime = (int)$pSession[2];
$this->_modificationTime = (int)$pSession[3];
$this->_sessionData = unserialize($pSession[4]);
$this->_loadedSessionData = $this->_getSerializedSession();
$this->_isPersistent = true;
} else {
/* The session has timed out, remove it */
$ret = GalleryCoreApi::removeMapEntry('GallerySessionMap',
array('id' => $this->_sessionId));
if ($ret) {
return $ret;
}
/* Get a new sessionId + session meta data (later) */
$this->_sessionId = '';
}
} else { /* There's no session with this sessionId in the database */
$this->_sessionId = '';
}
if (!$this->_isPersistent) {
/*
* The sessionId was invalid. If we got the sessionId from the cookie, delete the
* cookie or we'll try to load the session on each request again.
*/
if (isset($_COOKIE[SESSION_ID_PARAMETER])) {
unset($_COOKIE[SESSION_ID_PARAMETER]);
$ret = $this->_setCookie(true);
if ($ret) {
return $ret;
}
}
}
} /* Else: no sessionId specified, thus we've no session yet */
if (!$this->_isPersistent) {
$this->_emptySessionData();
}
return null;
}
/**
* Get rid of all session data.
* @access private
*/
function _emptySessionData() {
/* Don't (re-)set sessionId since we can't ensure a collision-free id without DB queries */
global $gallery;
$phpVm = $gallery->getPhpVm();
$this->_sessionData = array();
$this->_loadedSessionData = '';
$this->_creationTime = $phpVm->time();
$this->_userId = null;
$this->_modificationTime = $phpVm->time();
$this->_remoteIdentifier = $this->getRemoteIdentifier();
/* Don't change userId or isUsingCookies */
$this->_isPersistentSessionNew = false;
$this->_isSessionSaved = false;
}
/**
* If we started this request without a sessionId, then we used SESSION_TEMP_ID in all generated
* URLs etc as a placeholder. If we still have no sessionId, remove
* g2_GALLERYSID=SESSION_TEMP_SID from all generated URLs and remove SESSION_TEMP_ID from the
* HTML. If a session was created (saved in the persistent store) during the request, replace
* the SESSION_TEMP_ID with the new/real session id.
* @param string $html HTML
* @return string same HTML with replaced or removed sessionId
*/
function replaceTempSessionIdIfNecessary($html) {
global $gallery;
if ($this->_isPersistentSessionNew) {
/*
* Session was created during request, probably need to replace temporary session id
* with real session id
*/
/* Replace temp session id with real/new one */
$html = str_replace(SESSION_TEMP_ID, $this->_sessionId, $html);
} else if (empty($this->_sessionId)) {
/* Remove sessionId from URLs for guests that have no session (normal case) */
$sessionString =
GalleryUtilities::prefixFormVariable($this->getKey()) . '=' . SESSION_TEMP_ID;
/*
* Handling only most cases here, still leaving &&, ?& but handling trailing ? and &
* Experimented a lot with str_replace and preg_replace. A perfect solution would be
* again 40% slower (e.g. 12 instead of 8.5ms on a slow box for a lot of data).
*/
/* sessionString normal and URL encoded */
$regexp = GalleryUtilities::prefixFormVariable($this->getKey()) . '(?:=|%3D)' .
SESSION_TEMP_ID;
/*
* Remove trailing & and ? from URLs, also handle JavaScript (no HTML entities) and
* URL encoded (return URL) versions of & and ?
* This preg_replace takes about the same time as the str_replace that follows
*/
$html = preg_replace('/(?:\\?|%3F|&|&|%26amp%3B|%26)' . $regexp . '(["\'])/S',
'\\1', $html);
/* Remove sessionStrings that are not at the end of URLs and sessionIds in the HTML */
$html = str_replace(array($sessionString,
urlencode($sessionString),
SESSION_TEMP_ID),
'', $html);
}
return $html;
}
/**
* Replaces the session id in all string members of an object or in all elements of an array.
*
* Applies replaceTempSessionIdIfNecessary to all strings if $search and $replace are omitted.
* Else it applies str_replace($search, $replace, $subject) on all strings.
*
* Examples:
* $themeData = $session->replaceSessionIdInData($themeData, $sessionId, SESSION_TEMP_ID);
*
* $themeData = $session->replaceSessionIdInData($themeData);
*
* @param mixed $subject array, object or string that should be modified
* @param string $search (optional) string to be replaced
* @param string $replace (optional) replacement string
* @return mixed converted subject
*/
function replaceSessionIdInData($subject, $search=null, $replace=null) {
if (($isArray = is_array($subject)) || is_object($subject)) {
foreach ($subject as $key => $value) {
$value = $this->replaceSessionIdInData($value, $search, $replace);
if ($isArray) {
$subject[$key] = $value;
} else {
$subject->$key = $value;
}
}
} else if (is_string($subject)) {
if ($search !== null) {
return str_replace($search, $replace, $subject);
} else {
return $this->replaceTempSessionIdIfNecessary($subject);
}
} else {
return $subject;
}
return $subject;
}
/**
* Get rid of any sessions that have not been accessed within our inactivity timeout or have
* exceeded the max lifetime.
* @return object GalleryStatus a status code
* @access private
*/
function _expireSessions() {
global $gallery;
$storage =& $gallery->getStorage();
list ($ret, $sessionInactivityTimeout) =
GalleryCoreApi::getPluginParameter('module', 'core', 'session.inactivityTimeout');
if ($ret) {
return $ret;
}
list ($ret, $lifetime) =
GalleryCoreApi::getPluginParameter('module', 'core', 'session.lifetime');
if ($ret) {
return $ret;
}
$phpVm = $gallery->getPhpVm();
$inactiveCutoff = $phpVm->time() - $sessionInactivityTimeout;
$lifetimeCutoff = $phpVm->time() - $lifetime;
$lastWeek = $phpVm->time() - 86400 * 7;
/* Only delete in small chunks, else we may lock the whole Gallery for too long */
$where = '
WHERE [GallerySessionMap::creationTimestamp] < ?
OR [GallerySessionMap::modificationTimestamp] < ?';
$data[] = (int)$lifetimeCutoff;
$data[] = (int)$inactiveCutoff;
if ($lastWeek > $lifetimeCutoff) {
/* Delete guest user sessions more aggressively than other sessions */
list ($ret, $anonymousUserId) = GalleryCoreApi::getAnonymousUserId();
if ($ret) {
return $ret;
}
/* Delete all sessions of guest users that are older than a week*/
$where .= ' OR
([GallerySessionMap::userId] = ? AND [GallerySessionMap::creationTimestamp] < ?)';
$data[] = (int)$anonymousUserId;
$data[] = (int)$lastWeek;
}
/* TODO: Make this more OO, eg. by adding function canLimitDelete() to GalleryStorage */
if ($storage->getType() == 'mysql') {
/*
* MySQL supports the LIMIT clause in DELETE statements, other DBMS' don't Since SELECT
* 500 sessionIds + DELETE those sessionIds is more expensive we optimize for MySQL by
* using DELETE ... LIMIT 500
*/
$query = '
DELETE FROM [GallerySessionMap]
' . $where . '
LIMIT ' . (int)$this->_expirationLimit;
list ($ret, $results) = $storage->execute($query, $data);
if ($ret) {
return $ret;
}
} else {
/*
* For other DBMS first SELECT the sessionIds with a LIMIT clause then DELETE
* ADOdb can't implement the LIMIT clause in subqueries DB independently!
*/
$query = '
SELECT [GallerySessionMap::id]
FROM [GallerySessionMap]' . $where;
$option['limit']['count'] = $this->_expirationLimit;
list ($ret, $results) = $gallery->search($query, $data, $option);
if ($ret) {
return $ret;
}
if ($results->resultCount()) {
$ids = array();
while ($row = $results->nextResult()) {
$ids[] = $row[0];
}
/* Delete the selected sessions */
$query = sprintf('
DELETE FROM [GallerySessionMap]
WHERE [GallerySessionMap::id] IN (%s)',
GalleryUtilities::makeMarkers(count($ids)));
list ($ret, $results) = $storage->execute($query, $ids);
if ($ret) {
return $ret;
}
}
}
return null;
}
/**
* The session key parameter used in URLs and the cookie.
* @return string
*/
function getKey() {
return SESSION_ID_PARAMETER;
}
/**
* The session id.
* @return string an id (like "A124DFE7A90")
*/
function getId() {
if (empty($this->_sessionId) && empty($this->_doNotUseTempId)) {
return SESSION_TEMP_ID;
} else {
return $this->_sessionId;
}
}
/**
* Instruct the session to not return a pseudo temporary session id on getId() calls Makes sure
* that the URL generator and other componennts don't use a pseudo session id for guest users
* without a real session. Call this method before starting to output immediate views the
* progress bar.
*/
function doNotUseTempId() {
$this->_doNotUseTempId = true;
}
/**
* Return the user id of the active user of this sesison.
* @return int the user id
*/
function getUserId() {
return $this->_userId;
}
/**
* Set the active user id for this session.
* @param int $userId
*/
function setUserId($userId) {
return $this->_userId = $userId;
}
/*
* Returns the cookie domain.
*
* By default, don't set the cookie domain. Only set it, if Gallery is configured to set it
* (eg. because it is a) embedded AND b) different subdomains are involved)
*
* @return array (object GalleryStatus a status code,
* string the cookie domain, or '' if no cookie domain should be set)
*/
function getCookieDomain() {
if (!isset($this->_cookieDomain)) {
list ($ret, $this->_cookieDomain) = GalleryCoreApi::getPluginParameter('module', 'core',
'cookie.domain');
if ($ret) {
return array($ret, null);
}
if (!isset($this->_cookieDomain)) {
$this->_cookieDomain = '';
}
}
return array(null, $this->_cookieDomain);
}
/**
* Is this transaction known to be using cookies?
* @return boolean
*/
function isUsingCookies() {
return $this->_isUsingCookies;
}
/**
* Get a value from the session data.
* @param string $key
* @return string the value or null if it doesn't exist
*/
function &get($key) {
if (isset($this->_sessionData[$key])) {
return $this->_sessionData[$key];
}
$null = null;
return $null;
}
/**
* Store a value in the session.
* @param string $key
* @param string $value
*/
function put($key, $value) {
$this->_sessionData[$key] = $value;
}
/**
* Remove a value from the session.
* @param string $key
*/
function remove($key) {
unset($this->_sessionData[$key]);
}
/**
* Check to see if a value exists in the session.
* @param string $key
*/
function exists($key) {
return isset($this->_sessionData[$key]);
}
/**
* Return a value that we can use to identify the client. We can't tie it to the IP address
* because that changes too frequently (dialup users, users behind proxies) so we have to be
* creative. Changing this algorithm will cause all existing sessions to be discarded.
* @return array
* @static
*/
function getRemoteIdentifier() {
$httpUserAgent = GalleryUtilities::getServerVar('HTTP_USER_AGENT');
return array(GalleryUtilities::getRemoteHostAddress(),
isset($httpUserAgent) ? md5($httpUserAgent) : null);
}
/**
* Get the serialized session for comparing purposes.
* @return string serialized session
* @access private
*/
function _getSerializedSession() {
return serialize(array($this->_sessionId, $this->_userId,
serialize($this->_remoteIdentifier), $this->_creationTime, $this->_modificationTime,
serialize($this->_sessionData)));
}
/**
* Compare two arrays and return score consisting of 1 point for each matching element.
* Example input:
* $a = array(0, 'x', 2);
* $b = array(0, 'y', 2);
* Example output:
* 2
* (Indexes 0 and 2 match, index 1 does not)
*
* @return int a score
*/
function compareIdentifiers($a, $b) {
$score = 0;
if (is_array($a) && is_array($b)) {
for ($i = 0; $i < sizeof($a); $i++) {
if (sizeof($b) > $i && $a[$i] == $b[$i]) {
$score++;
}
}
}
return $score;
}
/**
* Store a status message.
* @param array $statusData
* @return string the status id
*/
function putStatus($statusData) {
$tod = gettimeofday();
/*
* Prefix the status id with a character so that it doesn't wind up being entirely numeric
* because PHP will renumber numeric keys in associative arrays when you run it through
* functions like array_splice()
*/
$statusId = 'x' . substr(md5($tod['usec'] + rand(1, 1000)), 0, 8);
$status =& $this->get('core.status');
if (!isset($status)) {
$status = array();
}
$status[$statusId] = $statusData;
/* Prune extra status messages */
$maxStatusMessages = 5;
if (sizeof($status) > $maxStatusMessages) {
$status = array_splice($status, -$maxStatusMessages);
}
$this->put('core.status', $status);
return $statusId;
}
/**
* Get a status message.
* @param string $statusId
* @param boolean $remove (optional)
* @return array the status message
*/
function getStatus($statusId, $remove=true) {
$status = $this->get('core.status');
$statusData = null;
if (isset($status) && isset($status[$statusId])) {
$statusData = $status[$statusId];
if ($remove) {
unset($status[$statusId]);
$this->put('core.status', $status);
}
}
return $statusData;
}
/**
* Return the session id.
* @return string the session id
* @deprecated
* @todo will be removed in the next API branch
*/
function getSessionId() {
return $this->getId();
}
/**
* Start new navigation.
* @param array $navigationData data for this new navigation:
* array('returnName' => ...
* 'returnUrl' => ...
* ['returnNavId' => ...])
* @return string the navigation id
*/
function addToNavigation($navigationData) {
$tod = gettimeofday();
$navId = 'x' . substr(md5($tod['usec'] + rand(1, 1000)), 0, 8);
$navigation =& $this->get('core.navigation');
if (!isset($navigation)) {
$navigation = array();
}
$navigation[$navId] = array();
$navigation[$navId]['data'] = $navigationData;
$navigation[$navId]['nextIds'] = array();
/* Tell our predecessor that he's got a new successor */
if (isset($navigationData['returnNavId'])) {
$returnNavId = $navigationData['returnNavId'];
$navigation[$returnNavId]['nextIds'][$navId] = true;
}
/* Prune oldest navigation branches */
$maxNavBranches = 10;
if (sizeof($navigation) > $maxNavBranches) {
$navigation = array_splice($navigation, -$maxNavBranches);
}
$this->put('core.navigation', $navigation);
return $navId;
}
/**
* Get data for a specific navigation id.
* @param string $navId the navigation id
* @return array the navigation data
*/
function getNavigation($navId) {
$navigation = $this->get('core.navigation');
$navigationData = array();
if (isset($navigation[$navId]['data'])) {
$navigationData[] = $navigation[$navId]['data'];
/* Add data from our predecessors, if available */
while (isset($navigation[$navId]['data']['returnNavId'])
&& isset($navigation[$navigation[$navId]['data']['returnNavId']]['data'])) {
$navId = $navigation[$navId]['data']['returnNavId'];
$navigationData[] = $navigation[$navId]['data'];
}
}
return $navigationData;
}
/**
* Jump back from one navigation point to one of its predecessors.
* @param string $fromNavId the source navigation id
* @param string $destNavId the destination navigation id. If empty, go back to root.
*/
function jumpNavigation($fromNavId, $destNavId = '') {
global $gallery;
$gallery->debug("navigation: Jumping back from $fromNavId to $destNavId");
$navigation = $this->get('core.navigation');
$currentId = $fromNavId;
/*
* Iterate back to root, deleting everything, until we reach destNavId or an navId that has
* other successors
*/
while (true) {
$gallery->debug("navigation: deleting $currentId");
$returnNavId = null;
if (isset($navigation[$currentId]['data']['returnNavId'])) {
$returnNavId = $navigation[$currentId]['data']['returnNavId'];
}
unset($navigation[$currentId]);
if ($returnNavId == null) {
break;
}
unset($navigation[$returnNavId]['nextIds'][$currentId]);
if (count($navigation[$returnNavId]['nextIds']) > 0) {
break;
}
if ($returnNavId == $destNavId) {
break;
}
$currentId = $returnNavId;
}
$this->put('core.navigation', $navigation);
}
/**
* Return the Unix timestamp from when this session was created.
* @return int the creation time
*/
function getCreationTime() {
return $this->_creationTime;
}
/**
* Return the Unix timestamp from when this session was last modified.
* @return int the modification time
*/
function getModificationTime() {
return $this->_modificationTime;
}
/**
* Whether this session is a persistent session (= stored on the server) or just a session for
* this single request. Note that a non-persistent session can become persistent at the end of
* the request when we evaluate the conditions whether to create a persistent session or not.
* @return boolean true if the session is persistent, else false
*/
function isPersistent() {
return $this->_isPersistent;
}
/**
* Return true if this session is identified as one coming from a search engine.
* @return bool true if this is a search engine session
*/
function isSearchEngineSession() {
return $this->_isSearchEngineSession;
}
/**
* Returns the authentication token associated with this session.
* @return string the authentication token
*/
function getAuthToken() {
$authToken = $this->get('core.authToken');
if (empty($authToken)) {
global $gallery;
$phpVm = $gallery->getPhpVm();
$authToken = substr($phpVm->md5(uniqid(microtime() . mt_rand())), 0, 12);
$this->put('core.authToken', $authToken);
}
return $authToken;
}
/**
* Checks the given authentication token and resets the internal token on failure.
* @param string $authToken Authentication token to be verified
* @return bool true if the given
*/
function isCorrectAuthToken($authToken) {
$internalAuthToken = $this->get('core.authToken');
if (empty($authToken) || empty($internalAuthToken)
|| strcmp($internalAuthToken, $authToken)) {
$this->put('core.authToken', null);
return false;
} else {
return true;
}
}
}
/**
* Get the active user from the session's user id.
* @package GalleryCore
* @subpackage Classes
*/
class SessionAuthPlugin /* extends GalleryAuthPlugin */ {
/**
* @see GalleryAuthPlugin::getUser
*/
function getUser() {
global $gallery;
$session =& $gallery->getSession();
$userId = $session->getUserId();
if (!empty($userId)) {
list ($ret, $user) = GalleryCoreApi::loadEntitiesById($userId);
/* ERROR_MISSING_OBJECT check to suppress error if user id doesn't exist */
if ($ret && !($ret->getErrorCode() & ERROR_MISSING_OBJECT)) {
return array($ret, null);
}
return array(null, $user);
}
}
}
?>
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists