diff --git a/scripts/ssh/ssh-auth.php b/scripts/ssh/ssh-auth.php index af6f7f7f43..b25905668b 100755 --- a/scripts/ssh/ssh-auth.php +++ b/scripts/ssh/ssh-auth.php @@ -1,77 +1,87 @@ #!/usr/bin/env php setViewer(PhabricatorUser::getOmnipotentUser()) - ->withIsActive(true) - ->execute(); - -if (!$keys) { - echo pht('No keys found.')."\n"; - exit(1); -} +$cache = PhabricatorCaches::getMutableCache(); +$authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY; +$authfile = $cache->getKey($authfile_key); + +if ($authfile === null) { + $keys = id(new PhabricatorAuthSSHKeyQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIsActive(true) + ->execute(); + + if (!$keys) { + echo pht('No keys found.')."\n"; + exit(1); + } -$bin = $root.'/bin/ssh-exec'; -foreach ($keys as $ssh_key) { - $key_argv = array(); - $object = $ssh_key->getObject(); - if ($object instanceof PhabricatorUser) { - $key_argv[] = '--phabricator-ssh-user'; - $key_argv[] = $object->getUsername(); - } else if ($object instanceof AlmanacDevice) { - if (!$ssh_key->getIsTrusted()) { - // If this key is not a trusted device key, don't allow SSH - // authentication. + $bin = $root.'/bin/ssh-exec'; + foreach ($keys as $ssh_key) { + $key_argv = array(); + $object = $ssh_key->getObject(); + if ($object instanceof PhabricatorUser) { + $key_argv[] = '--phabricator-ssh-user'; + $key_argv[] = $object->getUsername(); + } else if ($object instanceof AlmanacDevice) { + if (!$ssh_key->getIsTrusted()) { + // If this key is not a trusted device key, don't allow SSH + // authentication. + continue; + } + $key_argv[] = '--phabricator-ssh-device'; + $key_argv[] = $object->getName(); + } else { + // We don't know what sort of key this is; don't permit SSH auth. continue; } - $key_argv[] = '--phabricator-ssh-device'; - $key_argv[] = $object->getName(); - } else { - // We don't know what sort of key this is; don't permit SSH auth. - continue; - } - $key_argv[] = '--phabricator-ssh-key'; - $key_argv[] = $ssh_key->getID(); + $key_argv[] = '--phabricator-ssh-key'; + $key_argv[] = $ssh_key->getID(); - $cmd = csprintf('%s %Ls', $bin, $key_argv); + $cmd = csprintf('%s %Ls', $bin, $key_argv); - $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); - if (strlen($instance)) { - $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); - } + $instance = PhabricatorEnv::getEnvConfig('cluster.instance'); + if (strlen($instance)) { + $cmd = csprintf('PHABRICATOR_INSTANCE=%s %C', $instance, $cmd); + } - // This is additional escaping for the SSH 'command="..."' string. - $cmd = addcslashes($cmd, '"\\'); + // This is additional escaping for the SSH 'command="..."' string. + $cmd = addcslashes($cmd, '"\\'); - // Strip out newlines and other nonsense from the key type and key body. + // Strip out newlines and other nonsense from the key type and key body. - $type = $ssh_key->getKeyType(); - $type = preg_replace('@[\x00-\x20]+@', '', $type); - if (!strlen($type)) { - continue; - } + $type = $ssh_key->getKeyType(); + $type = preg_replace('@[\x00-\x20]+@', '', $type); + if (!strlen($type)) { + continue; + } - $key = $ssh_key->getKeyBody(); - $key = preg_replace('@[\x00-\x20]+@', '', $key); - if (!strlen($key)) { - continue; - } + $key = $ssh_key->getKeyBody(); + $key = preg_replace('@[\x00-\x20]+@', '', $key); + if (!strlen($key)) { + continue; + } + + $options = array( + 'command="'.$cmd.'"', + 'no-port-forwarding', + 'no-X11-forwarding', + 'no-agent-forwarding', + 'no-pty', + ); + $options = implode(',', $options); - $options = array( - 'command="'.$cmd.'"', - 'no-port-forwarding', - 'no-X11-forwarding', - 'no-agent-forwarding', - 'no-pty', - ); - $options = implode(',', $options); + $lines[] = $options.' '.$type.' '.$key."\n"; + } - $lines[] = $options.' '.$type.' '.$key."\n"; + $authfile = implode('', $lines); + $ttl = phutil_units('24 hours in seconds'); + $cache->setKey($authfile_key, $authfile, $ttl); } -echo implode('', $lines); +echo $authfile; exit(0); diff --git a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php index 1fc61ffb2c..4d04707598 100644 --- a/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php +++ b/src/applications/auth/editor/PhabricatorAuthSSHKeyEditor.php @@ -1,244 +1,258 @@ getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: return $object->getName(); case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: return $object->getEntireKey(); case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: return !$object->getIsActive(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: return $xaction->getNewValue(); case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: return (bool)$xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $value = $xaction->getNewValue(); switch ($xaction->getTransactionType()) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: $object->setName($value); return; case PhabricatorAuthSSHKeyTransaction::TYPE_KEY: $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($value); $type = $public_key->getType(); $body = $public_key->getBody(); $comment = $public_key->getComment(); $object->setKeyType($type); $object->setKeyBody($body); $object->setKeyComment($comment); return; case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: if ($value) { $new = null; } else { $new = 1; } $object->setIsActive($new); return; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { return; } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorAuthSSHKeyTransaction::TYPE_NAME: $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('SSH key name is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } break; case PhabricatorAuthSSHKeyTransaction::TYPE_KEY; $missing = $this->validateIsEmptyTextField( $object->getName(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('SSH key material is required.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; } else { foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); try { $public_key = PhabricatorAuthSSHPublicKey::newFromRawKey($new); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); } } } break; case PhabricatorAuthSSHKeyTransaction::TYPE_DEACTIVATE: foreach ($xactions as $xaction) { if (!$xaction->getNewValue()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('SSH keys can not be reactivated.'), $xaction); } } break; } return $errors; } protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) { $errors = array(); $errors[] = new PhabricatorApplicationTransactionValidationError( PhabricatorAuthSSHKeyTransaction::TYPE_KEY, pht('Duplicate'), pht( 'This public key is already associated with another user or device. '. 'Each key must unambiguously identify a single unique owner.'), null); throw new PhabricatorApplicationTransactionValidationException($errors); } protected function shouldSendMail( PhabricatorLiskDAO $object, array $xactions) { return true; } protected function getMailSubjectPrefix() { return pht('[SSH Key]'); } protected function getMailThreadID(PhabricatorLiskDAO $object) { return 'ssh-key-'.$object->getPHID(); } + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + // After making any change to an SSH key, drop the authfile cache so it + // is regenerated the next time anyone authenticates. + $cache = PhabricatorCaches::getMutableCache(); + $authfile_key = PhabricatorAuthSSHKeyQuery::AUTHFILE_CACHEKEY; + $cache->deleteKey($authfile_key); + + return $xactions; + } + + protected function getMailTo(PhabricatorLiskDAO $object) { return $object->getObject()->getSSHKeyNotifyPHIDs(); } protected function getMailCC(PhabricatorLiskDAO $object) { return array(); } protected function buildReplyHandler(PhabricatorLiskDAO $object) { return id(new PhabricatorAuthSSHKeyReplyHandler()) ->setMailReceiver($object); } protected function buildMailTemplate(PhabricatorLiskDAO $object) { $id = $object->getID(); $name = $object->getName(); $phid = $object->getPHID(); $mail = id(new PhabricatorMetaMTAMail()) ->setSubject(pht('SSH Key %d: %s', $id, $name)) ->addHeader('Thread-Topic', $phid); // The primary value of this mail is alerting users to account compromises, // so force delivery. In particular, this mail should still be delievered // even if "self mail" is disabled. $mail->setForceDelivery(true); return $mail; } protected function buildMailBody( PhabricatorLiskDAO $object, array $xactions) { $body = parent::buildMailBody($object, $xactions); $body->addTextSection( pht('SECURITY WARNING'), pht( 'If you do not recognize this change, it may indicate your account '. 'has been compromised.')); $detail_uri = $object->getURI(); $detail_uri = PhabricatorEnv::getProductionURI($detail_uri); $body->addLinkSection(pht('SSH KEY DETAIL'), $detail_uri); return $body; } } diff --git a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php index 4592d794fa..6ba047d100 100644 --- a/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php +++ b/src/applications/auth/query/PhabricatorAuthSSHKeyQuery.php @@ -1,130 +1,132 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withObjectPHIDs(array $object_phids) { $this->objectPHIDs = $object_phids; return $this; } public function withKeys(array $keys) { assert_instances_of($keys, 'PhabricatorAuthSSHPublicKey'); $this->keys = $keys; return $this; } public function withIsActive($active) { $this->isActive = $active; return $this; } public function newResultObject() { return new PhabricatorAuthSSHKey(); } protected function loadPage() { return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $keys) { $object_phids = mpull($keys, 'getObjectPHID'); $objects = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) ->setParentQuery($this) ->withPHIDs($object_phids) ->execute(); $objects = mpull($objects, null, 'getPHID'); foreach ($keys as $key => $ssh_key) { $object = idx($objects, $ssh_key->getObjectPHID()); // We must have an object, and that object must be a valid object for // SSH keys. if (!$object || !($object instanceof PhabricatorSSHPublicKeyInterface)) { $this->didRejectResult($ssh_key); unset($keys[$key]); continue; } $ssh_key->attachObject($object); } return $keys; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } if ($this->objectPHIDs !== null) { $where[] = qsprintf( $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } if ($this->keys !== null) { $sql = array(); foreach ($this->keys as $key) { $sql[] = qsprintf( $conn, '(keyType = %s AND keyIndex = %s)', $key->getType(), $key->getHash()); } $where[] = implode(' OR ', $sql); } if ($this->isActive !== null) { if ($this->isActive) { $where[] = qsprintf( $conn, 'isActive = %d', 1); } else { $where[] = qsprintf( $conn, 'isActive IS NULL'); } } return $where; } public function getQueryApplicationClass() { return 'PhabricatorAuthApplication'; } } diff --git a/src/applications/cache/PhabricatorCaches.php b/src/applications/cache/PhabricatorCaches.php index c9bd304ccf..a725238150 100644 --- a/src/applications/cache/PhabricatorCaches.php +++ b/src/applications/cache/PhabricatorCaches.php @@ -1,394 +1,411 @@ setCaches($caches); } /* -( Request Cache )------------------------------------------------------ */ /** * Get a request cache stack. * * This cache stack is destroyed after each logical request. In particular, * it is destroyed periodically by the daemons, while `static` caches are * not. * * @return PhutilKeyValueCacheStack Request cache stack. */ public static function getRequestCache() { if (!self::$requestCache) { self::$requestCache = new PhutilInRequestKeyValueCache(); } return self::$requestCache; } /** * Destroy the request cache. * * This is called at the beginning of each logical request. * * @return void */ public static function destroyRequestCache() { self::$requestCache = null; } /* -( Immutable Cache )---------------------------------------------------- */ /** * Gets an immutable cache stack. * * This stack trades mutability away for improved performance. Normally, it is * APC + DB. * * In the general case with multiple web frontends, this stack can not be * cleared, so it is only appropriate for use if the value of a given key is * permanent and immutable. * * @return PhutilKeyValueCacheStack Best immutable stack available. * @task immutable */ public static function getImmutableCache() { static $cache; if (!$cache) { $caches = self::buildImmutableCaches(); $cache = self::newStackFromCaches($caches); } return $cache; } /** * Build the immutable cache stack. * * @return list List of caches. * @task immutable */ private static function buildImmutableCaches() { $caches = array(); $apc = new PhutilAPCKeyValueCache(); if ($apc->isAvailable()) { $caches[] = $apc; } $caches[] = new PhabricatorKeyValueDatabaseCache(); return $caches; } + public static function getMutableCache() { + static $cache; + if (!$cache) { + $caches = self::buildMutableCaches(); + $cache = self::newStackFromCaches($caches); + } + return $cache; + } + + private static function buildMutableCaches() { + $caches = array(); + + $caches[] = new PhabricatorKeyValueDatabaseCache(); + + return $caches; + } + /* -( Repository Graph Cache )--------------------------------------------- */ public static function getRepositoryGraphL1Cache() { static $cache; if (!$cache) { $caches = self::buildRepositoryGraphL1Caches(); $cache = self::newStackFromCaches($caches); } return $cache; } private static function buildRepositoryGraphL1Caches() { $caches = array(); $request = new PhutilInRequestKeyValueCache(); $request->setLimit(32); $caches[] = $request; $apc = new PhutilAPCKeyValueCache(); if ($apc->isAvailable()) { $caches[] = $apc; } return $caches; } public static function getRepositoryGraphL2Cache() { static $cache; if (!$cache) { $caches = self::buildRepositoryGraphL2Caches(); $cache = self::newStackFromCaches($caches); } return $cache; } private static function buildRepositoryGraphL2Caches() { $caches = array(); $caches[] = new PhabricatorKeyValueDatabaseCache(); return $caches; } /* -( Server State Cache )------------------------------------------------- */ /** * Highly specialized cache for storing server process state. * * We use this cache to track initial steps in the setup phase, before * configuration is loaded. * * This cache does NOT use the cache namespace (it must be accessed before * we build configuration), and is global across all instances on the host. * * @return PhutilKeyValueCacheStack Best available server state cache stack. * @task setup */ public static function getServerStateCache() { static $cache; if (!$cache) { $caches = self::buildSetupCaches('phabricator-server'); // NOTE: We are NOT adding a cache namespace here! This cache is shared // across all instances on the host. $caches = self::addProfilerToCaches($caches); $cache = id(new PhutilKeyValueCacheStack()) ->setCaches($caches); } return $cache; } /* -( Setup Cache )-------------------------------------------------------- */ /** * Highly specialized cache for performing setup checks. We use this cache * to determine if we need to run expensive setup checks when the page * loads. Without it, we would need to run these checks every time. * * Normally, this cache is just APC. In the absence of APC, this cache * degrades into a slow, quirky on-disk cache. * * NOTE: Do not use this cache for anything else! It is not a general-purpose * cache! * * @return PhutilKeyValueCacheStack Most qualified available cache stack. * @task setup */ public static function getSetupCache() { static $cache; if (!$cache) { $caches = self::buildSetupCaches('phabricator-setup'); $cache = self::newStackFromCaches($caches); } return $cache; } /** * @task setup */ private static function buildSetupCaches($cache_name) { // If this is the CLI, just build a setup cache. if (php_sapi_name() == 'cli') { return array(); } // In most cases, we should have APC. This is an ideal cache for our // purposes -- it's fast and empties on server restart. $apc = new PhutilAPCKeyValueCache(); if ($apc->isAvailable()) { return array($apc); } // If we don't have APC, build a poor approximation on disk. This is still // much better than nothing; some setup steps are quite slow. $disk_path = self::getSetupCacheDiskCachePath($cache_name); if ($disk_path) { $disk = new PhutilOnDiskKeyValueCache(); $disk->setCacheFile($disk_path); $disk->setWait(0.1); if ($disk->isAvailable()) { return array($disk); } } return array(); } /** * @task setup */ private static function getSetupCacheDiskCachePath($name) { // The difficulty here is in choosing a path which will change on server // restart (we MUST have this property), but as rarely as possible // otherwise (we desire this property to give the cache the best hit rate // we can). // Unfortunately, we don't have a very good strategy for minimizing the // churn rate of the cache. We previously tried to use the parent process // PID in some cases, but this was not reliable. See T9599 for one case of // this. $pid_basis = getmypid(); // If possible, we also want to know when the process launched, so we can // drop the cache if a process restarts but gets the same PID an earlier // process had. "/proc" is not available everywhere (e.g., not on OSX), but // check if we have it. $epoch_basis = null; $stat = @stat("/proc/{$pid_basis}"); if ($stat !== false) { $epoch_basis = $stat['ctime']; } $tmp_dir = sys_get_temp_dir(); $tmp_path = $tmp_dir.DIRECTORY_SEPARATOR.$name; if (!file_exists($tmp_path)) { @mkdir($tmp_path); } $is_ok = self::testTemporaryDirectory($tmp_path); if (!$is_ok) { $tmp_path = $tmp_dir; $is_ok = self::testTemporaryDirectory($tmp_path); if (!$is_ok) { // We can't find anywhere to write the cache, so just bail. return null; } } $tmp_name = 'setup-'.$pid_basis; if ($epoch_basis) { $tmp_name .= '.'.$epoch_basis; } $tmp_name .= '.cache'; return $tmp_path.DIRECTORY_SEPARATOR.$tmp_name; } /** * @task setup */ private static function testTemporaryDirectory($dir) { if (!@file_exists($dir)) { return false; } if (!@is_dir($dir)) { return false; } if (!@is_writable($dir)) { return false; } return true; } private static function addProfilerToCaches(array $caches) { foreach ($caches as $key => $cache) { $pcache = new PhutilKeyValueCacheProfiler($cache); $pcache->setProfiler(PhutilServiceProfiler::getInstance()); $caches[$key] = $pcache; } return $caches; } private static function addNamespaceToCaches(array $caches) { $namespace = self::getNamespace(); if (!$namespace) { return $caches; } foreach ($caches as $key => $cache) { $ncache = new PhutilKeyValueCacheNamespace($cache); $ncache->setNamespace($namespace); $caches[$key] = $ncache; } return $caches; } /** * Deflate a value, if deflation is available and has an impact. * * If the value is larger than 1KB, we have `gzdeflate()`, we successfully * can deflate it, and it benefits from deflation, we deflate it. Otherwise * we leave it as-is. * * Data can later be inflated with @{method:inflateData}. * * @param string String to attempt to deflate. * @return string|null Deflated string, or null if it was not deflated. * @task compress */ public static function maybeDeflateData($value) { $len = strlen($value); if ($len <= 1024) { return null; } if (!function_exists('gzdeflate')) { return null; } $deflated = gzdeflate($value); if ($deflated === false) { return null; } $deflated_len = strlen($deflated); if ($deflated_len >= ($len / 2)) { return null; } return $deflated; } /** * Inflate data previously deflated by @{method:maybeDeflateData}. * * @param string Deflated data, from @{method:maybeDeflateData}. * @return string Original, uncompressed data. * @task compress */ public static function inflateData($value) { if (!function_exists('gzinflate')) { throw new Exception( pht( '%s is not available; unable to read deflated data!', 'gzinflate()')); } $value = gzinflate($value); if ($value === false) { throw new Exception(pht('Failed to inflate data!')); } return $value; } } diff --git a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php index 99060478c0..c6a52024fe 100644 --- a/src/applications/cache/PhabricatorKeyValueDatabaseCache.php +++ b/src/applications/cache/PhabricatorKeyValueDatabaseCache.php @@ -1,174 +1,174 @@ digestKeys(array_keys($keys)); $conn_w = $this->establishConnection('w'); $sql = array(); foreach ($map as $key => $hash) { $value = $keys[$key]; list($format, $storage_value) = $this->willWriteValue($key, $value); $sql[] = qsprintf( $conn_w, '(%s, %s, %s, %B, %d, %nd)', $hash, $key, $format, $storage_value, time(), $ttl ? (time() + $ttl) : null); } $guard = AphrontWriteGuard::beginScopedUnguardedWrites(); foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) { queryfx( $conn_w, 'INSERT INTO %T (cacheKeyHash, cacheKey, cacheFormat, cacheData, cacheCreated, cacheExpires) VALUES %Q ON DUPLICATE KEY UPDATE cacheKey = VALUES(cacheKey), cacheFormat = VALUES(cacheFormat), cacheData = VALUES(cacheData), cacheCreated = VALUES(cacheCreated), cacheExpires = VALUES(cacheExpires)', $this->getTableName(), $chunk); } unset($guard); } return $this; } public function getKeys(array $keys) { $results = array(); if ($keys) { $map = $this->digestKeys($keys); $rows = queryfx_all( $this->establishConnection('r'), 'SELECT * FROM %T WHERE cacheKeyHash IN (%Ls)', $this->getTableName(), $map); $rows = ipull($rows, null, 'cacheKey'); foreach ($keys as $key) { if (empty($rows[$key])) { continue; } $row = $rows[$key]; if ($row['cacheExpires'] && ($row['cacheExpires'] < time())) { continue; } try { $results[$key] = $this->didReadValue( $row['cacheFormat'], $row['cacheData']); } catch (Exception $ex) { // Treat this as a cache miss. phlog($ex); } } } return $results; } public function deleteKeys(array $keys) { if ($keys) { $map = $this->digestKeys($keys); queryfx( $this->establishConnection('w'), 'DELETE FROM %T WHERE cacheKeyHash IN (%Ls)', $this->getTableName(), - $keys); + $map); } return $this; } public function destroyCache() { queryfx( $this->establishConnection('w'), 'DELETE FROM %T', $this->getTableName()); return $this; } /* -( Raw Cache Access )--------------------------------------------------- */ public function establishConnection($mode) { // TODO: This is the only concrete table we have on the database right // now. return id(new PhabricatorMarkupCache())->establishConnection($mode); } public function getTableName() { return 'cache_general'; } /* -( Implementation )----------------------------------------------------- */ private function digestKeys(array $keys) { $map = array(); foreach ($keys as $key) { $map[$key] = PhabricatorHash::digestForIndex($key); } return $map; } private function willWriteValue($key, $value) { if (!is_string($value)) { throw new Exception(pht('Only strings may be written to the DB cache!')); } static $can_deflate; if ($can_deflate === null) { $can_deflate = function_exists('gzdeflate') && PhabricatorEnv::getEnvConfig('cache.enable-deflate'); } if ($can_deflate) { $deflated = PhabricatorCaches::maybeDeflateData($value); if ($deflated !== null) { return array(self::CACHE_FORMAT_DEFLATE, $deflated); } } return array(self::CACHE_FORMAT_RAW, $value); } private function didReadValue($format, $value) { switch ($format) { case self::CACHE_FORMAT_RAW: return $value; case self::CACHE_FORMAT_DEFLATE: return PhabricatorCaches::inflateData($value); default: throw new Exception(pht('Unknown cache format.')); } } }