mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Remove SHA1 file content hashing and make Files work without any hashing
Summary: Ref T12464. We currently use SHA1 to detect when two files have the same content so we don't have to store two copies of the data. Now that a SHA1 collision is known, this is theoretically dangerous. T12464 describes the shape of a possible attack. Before replacing this with something more robust, shore things up so things work correctly if we don't hash at all. This mechanism is entirely optional; it only helps us store less data if some files are duplicates. (This mechanism is also less important now than it once was, before we added temporary files.) Test Plan: Uploaded multiple identical files, saw the uploads work and the files store separate copies of the same data. Reviewers: chad Reviewed By: chad Maniphest Tasks: T12464 Differential Revision: https://secure.phabricator.com/D17619
This commit is contained in:
parent
1e181f0781
commit
440ef5b7a7
4 changed files with 68 additions and 48 deletions
|
@ -36,23 +36,26 @@ final class FileAllocateConduitAPIMethod
|
||||||
$properties = array(
|
$properties = array(
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'authorPHID' => $viewer->getPHID(),
|
'authorPHID' => $viewer->getPHID(),
|
||||||
'viewPolicy' => $view_policy,
|
|
||||||
'isExplicitUpload' => true,
|
'isExplicitUpload' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ($view_policy !== null) {
|
||||||
|
$properties['viewPolicy'] = $view_policy;
|
||||||
|
}
|
||||||
|
|
||||||
$ttl = $request->getValue('deleteAfterEpoch');
|
$ttl = $request->getValue('deleteAfterEpoch');
|
||||||
if ($ttl) {
|
if ($ttl) {
|
||||||
$properties['ttl.absolute'] = $ttl;
|
$properties['ttl.absolute'] = $ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = null;
|
$file = null;
|
||||||
if ($hash) {
|
if ($hash !== null) {
|
||||||
$file = PhabricatorFile::newFileFromContentHash(
|
$file = PhabricatorFile::newFileFromContentHash(
|
||||||
$hash,
|
$hash,
|
||||||
$properties);
|
$properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($hash && !$file) {
|
if ($hash !== null && !$file) {
|
||||||
$chunked_hash = PhabricatorChunkedFileStorageEngine::getChunkedHash(
|
$chunked_hash = PhabricatorChunkedFileStorageEngine::getChunkedHash(
|
||||||
$viewer,
|
$viewer,
|
||||||
$hash);
|
$hash);
|
||||||
|
@ -103,7 +106,7 @@ final class FileAllocateConduitAPIMethod
|
||||||
if ($chunk_engines) {
|
if ($chunk_engines) {
|
||||||
$chunk_properties = $properties;
|
$chunk_properties = $properties;
|
||||||
|
|
||||||
if ($hash) {
|
if ($hash !== null) {
|
||||||
$chunk_properties += array(
|
$chunk_properties += array(
|
||||||
'chunkedHash' => $chunked_hash,
|
'chunkedHash' => $chunked_hash,
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,21 +27,27 @@ final class FileUploadConduitAPIMethod extends FileConduitAPIMethod {
|
||||||
$viewer = $request->getUser();
|
$viewer = $request->getUser();
|
||||||
|
|
||||||
$name = $request->getValue('name');
|
$name = $request->getValue('name');
|
||||||
$can_cdn = $request->getValue('canCDN');
|
$can_cdn = (bool)$request->getValue('canCDN');
|
||||||
$view_policy = $request->getValue('viewPolicy');
|
$view_policy = $request->getValue('viewPolicy');
|
||||||
|
|
||||||
$data = $request->getValue('data_base64');
|
$data = $request->getValue('data_base64');
|
||||||
$data = $this->decodeBase64($data);
|
$data = $this->decodeBase64($data);
|
||||||
|
|
||||||
$file = PhabricatorFile::newFromFileData(
|
$params = array(
|
||||||
$data,
|
'authorPHID' => $viewer->getPHID(),
|
||||||
array(
|
'canCDN' => $can_cdn,
|
||||||
'name' => $name,
|
'isExplicitUpload' => true,
|
||||||
'authorPHID' => $viewer->getPHID(),
|
);
|
||||||
'viewPolicy' => $view_policy,
|
|
||||||
'canCDN' => $can_cdn,
|
if ($name !== null) {
|
||||||
'isExplicitUpload' => true,
|
$params['name'] = $name;
|
||||||
));
|
}
|
||||||
|
|
||||||
|
if ($view_policy !== null) {
|
||||||
|
$params['viewPolicy'] = $view_policy;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = PhabricatorFile::newFromFileData($data, $params);
|
||||||
|
|
||||||
return $file->getPHID();
|
return $file->getPHID();
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,39 +198,42 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
|
|
||||||
|
|
||||||
public static function newFileFromContentHash($hash, array $params) {
|
public static function newFileFromContentHash($hash, array $params) {
|
||||||
// Check to see if a file with same contentHash exist
|
if ($hash === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if a file with same hash already exists.
|
||||||
$file = id(new PhabricatorFile())->loadOneWhere(
|
$file = id(new PhabricatorFile())->loadOneWhere(
|
||||||
'contentHash = %s LIMIT 1',
|
'contentHash = %s LIMIT 1',
|
||||||
$hash);
|
$hash);
|
||||||
|
if (!$file) {
|
||||||
if ($file) {
|
return null;
|
||||||
$copy_of_storage_engine = $file->getStorageEngine();
|
|
||||||
$copy_of_storage_handle = $file->getStorageHandle();
|
|
||||||
$copy_of_storage_format = $file->getStorageFormat();
|
|
||||||
$copy_of_storage_properties = $file->getStorageProperties();
|
|
||||||
$copy_of_byte_size = $file->getByteSize();
|
|
||||||
$copy_of_mime_type = $file->getMimeType();
|
|
||||||
|
|
||||||
$new_file = self::initializeNewFile();
|
|
||||||
|
|
||||||
$new_file->setByteSize($copy_of_byte_size);
|
|
||||||
|
|
||||||
$new_file->setContentHash($hash);
|
|
||||||
$new_file->setStorageEngine($copy_of_storage_engine);
|
|
||||||
$new_file->setStorageHandle($copy_of_storage_handle);
|
|
||||||
$new_file->setStorageFormat($copy_of_storage_format);
|
|
||||||
$new_file->setStorageProperties($copy_of_storage_properties);
|
|
||||||
$new_file->setMimeType($copy_of_mime_type);
|
|
||||||
$new_file->copyDimensions($file);
|
|
||||||
|
|
||||||
$new_file->readPropertiesFromParameters($params);
|
|
||||||
|
|
||||||
$new_file->save();
|
|
||||||
|
|
||||||
return $new_file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $file;
|
$copy_of_storage_engine = $file->getStorageEngine();
|
||||||
|
$copy_of_storage_handle = $file->getStorageHandle();
|
||||||
|
$copy_of_storage_format = $file->getStorageFormat();
|
||||||
|
$copy_of_storage_properties = $file->getStorageProperties();
|
||||||
|
$copy_of_byte_size = $file->getByteSize();
|
||||||
|
$copy_of_mime_type = $file->getMimeType();
|
||||||
|
|
||||||
|
$new_file = self::initializeNewFile();
|
||||||
|
|
||||||
|
$new_file->setByteSize($copy_of_byte_size);
|
||||||
|
|
||||||
|
$new_file->setContentHash($hash);
|
||||||
|
$new_file->setStorageEngine($copy_of_storage_engine);
|
||||||
|
$new_file->setStorageHandle($copy_of_storage_handle);
|
||||||
|
$new_file->setStorageFormat($copy_of_storage_format);
|
||||||
|
$new_file->setStorageProperties($copy_of_storage_properties);
|
||||||
|
$new_file->setMimeType($copy_of_mime_type);
|
||||||
|
$new_file->copyDimensions($file);
|
||||||
|
|
||||||
|
$new_file->readPropertiesFromParameters($params);
|
||||||
|
|
||||||
|
$new_file->save();
|
||||||
|
|
||||||
|
return $new_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function newChunkedFile(
|
public static function newChunkedFile(
|
||||||
|
@ -353,7 +356,9 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
$file->setByteSize(strlen($data));
|
$file->setByteSize(strlen($data));
|
||||||
$file->setContentHash(self::hashFileContent($data));
|
|
||||||
|
$hash = self::hashFileContent($data);
|
||||||
|
$file->setContentHash($hash);
|
||||||
|
|
||||||
$file->setStorageEngine($engine_identifier);
|
$file->setStorageEngine($engine_identifier);
|
||||||
$file->setStorageHandle($data_handle);
|
$file->setStorageHandle($data_handle);
|
||||||
|
@ -379,10 +384,12 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
|
|
||||||
public static function newFromFileData($data, array $params = array()) {
|
public static function newFromFileData($data, array $params = array()) {
|
||||||
$hash = self::hashFileContent($data);
|
$hash = self::hashFileContent($data);
|
||||||
$file = self::newFileFromContentHash($hash, $params);
|
|
||||||
|
|
||||||
if ($file) {
|
if ($hash !== null) {
|
||||||
return $file;
|
$file = self::newFileFromContentHash($hash, $params);
|
||||||
|
if ($file) {
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::buildFromFileData($data, $params);
|
return self::buildFromFileData($data, $params);
|
||||||
|
@ -710,9 +717,8 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function hashFileContent($data) {
|
public static function hashFileContent($data) {
|
||||||
return sha1($data);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadFileData() {
|
public function loadFileData() {
|
||||||
|
|
|
@ -301,6 +301,11 @@ final class PhabricatorFileTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
$data = Filesystem::readRandomCharacters(64);
|
$data = Filesystem::readRandomCharacters(64);
|
||||||
|
|
||||||
|
$hash = PhabricatorFile::hashFileContent($data);
|
||||||
|
if ($hash === null) {
|
||||||
|
$this->assertSkipped(pht('File content hashing is not available.'));
|
||||||
|
}
|
||||||
|
|
||||||
$params = array(
|
$params = array(
|
||||||
'name' => 'test.dat',
|
'name' => 'test.dat',
|
||||||
'storageEngines' => array(
|
'storageEngines' => array(
|
||||||
|
|
Loading…
Reference in a new issue