1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 13:22:42 +01:00

Convert Phabricator to handle "%s" / "%B" properly

Summary:
Ref T1191. I believe we only have three meaningful binary fields across all applications:

  - The general cache may contain gzipped content.
  - The file storage blob may contain arbitrary binary content.
  - The Passphrase secret can store arbitrary binary data (although it currently never does).

This adds Lisk config for binary fields, and uses `%B` where necessary.

Test Plan:
  - Added and executed unit tests.
  - Forced file uploads to use MySQL, uploaded binaries.
  - Disabled the CONFIG_BINARY on the file storage blob and tried again, got an appropraite failure.
  - Tried to register with an account containing a G-Clef, and was stopped before the insert.

Reviewers: btrahan, arice

Reviewed By: arice

CC: arice, chad, aran

Maniphest Tasks: T1191

Differential Revision: https://secure.phabricator.com/D8316
This commit is contained in:
epriestley 2014-02-23 16:20:46 -08:00
parent 70b008d18d
commit a298a79bda
9 changed files with 88 additions and 39 deletions

View file

@ -74,9 +74,18 @@ final class DarkConsoleCore {
$cache = new PhutilKeyValueCacheProfiler($cache); $cache = new PhutilKeyValueCacheProfiler($cache);
$cache->setProfiler(PhutilServiceProfiler::getInstance()); $cache->setProfiler(PhutilServiceProfiler::getInstance());
// This encoding may fail if there are, e.g., database queries which
// include binary data. It would be a little cleaner to try to strip these,
// but just do something non-broken here if we end up with unrepresentable
// data.
$json = @json_encode($storage);
if (!$json) {
$json = '{}';
}
$cache->setKeys( $cache->setKeys(
array( array(
'darkconsole:'.$key => json_encode($storage), 'darkconsole:'.$key => $json,
), ),
$ttl = (60 * 60 * 6)); $ttl = (60 * 60 * 6));

View file

@ -19,7 +19,7 @@ final class PhabricatorKeyValueDatabaseCache
$sql[] = qsprintf( $sql[] = qsprintf(
$conn_w, $conn_w,
'(%s, %s, %s, %s, %d, %nd)', '(%s, %s, %s, %B, %d, %nd)',
$hash, $hash,
$key, $key,
$format, $format,

View file

@ -2,46 +2,17 @@
/** /**
* Simple blob store DAO for @{class:PhabricatorMySQLFileStorageEngine}. * Simple blob store DAO for @{class:PhabricatorMySQLFileStorageEngine}.
*
* @group file
*/ */
final class PhabricatorFileStorageBlob extends PhabricatorFileDAO { final class PhabricatorFileStorageBlob extends PhabricatorFileDAO {
// max_allowed_packet defaults to 1 MiB, escaping can make the data twice
// longer, query fits in the rest.
const CHUNK_SIZE = 5e5;
protected $data; protected $data;
private $fullData; public function getConfiguration() {
return array(
protected function willWriteData(array &$data) { self::CONFIG_BINARY => array(
parent::willWriteData($data); 'data' => true,
),
$this->fullData = $data['data']; ) + parent::getConfiguration();
if (strlen($data['data']) > self::CHUNK_SIZE) {
$data['data'] = substr($data['data'], 0, self::CHUNK_SIZE);
$this->openTransaction();
}
}
protected function didWriteData() {
$size = self::CHUNK_SIZE;
$length = strlen($this->fullData);
if ($length > $size) {
$conn = $this->establishConnection('w');
for ($offset = $size; $offset < $length; $offset += $size) {
queryfx(
$conn,
'UPDATE %T SET data = CONCAT(data, %s) WHERE %C = %d',
$this->getTableName(),
substr($this->fullData, $offset, $size),
$this->getIDKeyForUse(),
$this->getID());
}
$this->saveTransaction();
}
parent::didWriteData();
} }
} }

View file

@ -7,6 +7,9 @@ final class PassphraseSecret extends PassphraseDAO {
public function getConfiguration() { public function getConfiguration() {
return array( return array(
self::CONFIG_TIMESTAMPS => false, self::CONFIG_TIMESTAMPS => false,
self::CONFIG_BINARY => array(
'secretData' => true,
),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -59,6 +59,9 @@ final class PhabricatorRepositoryPushLog
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
self::CONFIG_TIMESTAMPS => false, self::CONFIG_TIMESTAMPS => false,
self::CONFIG_BINARY => array(
'refNameRaw' => true,
),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -24,6 +24,9 @@ final class PhabricatorRepositoryRefCursor extends PhabricatorRepositoryDAO
public function getConfiguration() { public function getConfiguration() {
return array( return array(
self::CONFIG_TIMESTAMPS => false, self::CONFIG_TIMESTAMPS => false,
self::CONFIG_BINARY => array(
'refNameRaw' => true,
),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -77,5 +77,28 @@ final class PhabricatorInfrastructureTestCase
$this->assertEqual($buf, $read->getBigData()); $this->assertEqual($buf, $read->getBigData());
} }
public function testRejectMySQLBMPQueries() {
$table = new HarbormasterScratchTable();
$conn_r = $table->establishConnection('w');
$snowman = "\xE2\x98\x83";
$gclef = "\xF0\x9D\x84\x9E";
qsprintf($conn_r, 'SELECT %B', $snowman);
qsprintf($conn_r, 'SELECT %s', $snowman);
qsprintf($conn_r, 'SELECT %B', $gclef);
$caught = null;
try {
qsprintf($conn_r, 'SELECT %s', $gclef);
} catch (AphrontQueryCharacterSetException $ex) {
$caught = $ex;
}
$this->assertEqual(
true,
($caught instanceof AphrontQueryCharacterSetException));
}
} }

View file

@ -35,6 +35,23 @@ final class QueryFormattingTestCase extends PhabricatorTestCase {
$this->assertEqual( $this->assertEqual(
'NULL', 'NULL',
qsprintf($conn_r, '%ns', null)); qsprintf($conn_r, '%ns', null));
$this->assertEqual(
"'<S>', '<S>'",
qsprintf($conn_r, '%Ls', array('x', 'y')));
$this->assertEqual(
"'<B>'",
qsprintf($conn_r, '%B', null));
$this->assertEqual(
"NULL",
qsprintf($conn_r, '%nB', null));
$this->assertEqual(
"'<B>', '<B>'",
qsprintf($conn_r, '%LB', array('x', 'y')));
} }
} }

View file

@ -171,6 +171,7 @@ abstract class LiskDAO {
const CONFIG_AUX_PHID = 'auxiliary-phid'; const CONFIG_AUX_PHID = 'auxiliary-phid';
const CONFIG_SERIALIZATION = 'col-serialization'; const CONFIG_SERIALIZATION = 'col-serialization';
const CONFIG_PARTIAL_OBJECTS = 'partial-objects'; const CONFIG_PARTIAL_OBJECTS = 'partial-objects';
const CONFIG_BINARY = 'binary';
const SERIALIZATION_NONE = 'id'; const SERIALIZATION_NONE = 'id';
const SERIALIZATION_JSON = 'json'; const SERIALIZATION_JSON = 'json';
@ -356,6 +357,11 @@ abstract class LiskDAO {
* directly access or assign protected members of your class (use the getters * directly access or assign protected members of your class (use the getters
* and setters). * and setters).
* *
* CONFIG_BINARY
* You can optionally provide a map of columns to a flag indicating that
* they store binary data. These columns will not raise an error when
* handling binary writes.
*
* @return dictionary Map of configuration options to values. * @return dictionary Map of configuration options to values.
* *
* @task config * @task config
@ -1148,9 +1154,14 @@ abstract class LiskDAO {
} }
$conn = $this->establishConnection('w'); $conn = $this->establishConnection('w');
$binary = $this->getBinaryColumns();
foreach ($map as $key => $value) { foreach ($map as $key => $value) {
$map[$key] = qsprintf($conn, '%C = %ns', $key, $value); if (!empty($binary[$key])) {
$map[$key] = qsprintf($conn, '%C = %nB', $key, $value);
} else {
$map[$key] = qsprintf($conn, '%C = %ns', $key, $value);
}
} }
$map = implode(', ', $map); $map = implode(', ', $map);
@ -1242,10 +1253,15 @@ abstract class LiskDAO {
$this->willWriteData($data); $this->willWriteData($data);
$columns = array_keys($data); $columns = array_keys($data);
$binary = $this->getBinaryColumns();
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
try { try {
$data[$key] = qsprintf($conn, '%ns', $value); if (!empty($binary[$key])) {
$data[$key] = qsprintf($conn, '%nB', $value);
} else {
$data[$key] = qsprintf($conn, '%ns', $value);
}
} catch (AphrontQueryParameterException $parameter_exception) { } catch (AphrontQueryParameterException $parameter_exception) {
throw new PhutilProxyException( throw new PhutilProxyException(
pht( pht(
@ -1803,4 +1819,8 @@ abstract class LiskDAO {
return $conn_w->getInsertID(); return $conn_w->getInsertID();
} }
private function getBinaryColumns() {
return $this->getConfigOption(self::CONFIG_BINARY);
}
} }