Path: blob/master/src/applications/files/engine/PhabricatorChunkedFileStorageEngine.php
12241 views
<?php12final class PhabricatorChunkedFileStorageEngine3extends PhabricatorFileStorageEngine {45public function getEngineIdentifier() {6return 'chunks';7}89public function getEnginePriority() {10return 60000;11}1213/**14* We can write chunks if we have at least one valid storage engine15* underneath us.16*/17public function canWriteFiles() {18return (bool)$this->getWritableEngine();19}2021public function hasFilesizeLimit() {22return false;23}2425public function isChunkEngine() {26return true;27}2829public function writeFile($data, array $params) {30// The chunk engine does not support direct writes.31throw new PhutilMethodNotImplementedException();32}3334public function readFile($handle) {35// This is inefficient, but makes the API work as expected.36$chunks = $this->loadAllChunks($handle, true);3738$buffer = '';39foreach ($chunks as $chunk) {40$data_file = $chunk->getDataFile();41if (!$data_file) {42throw new Exception(pht('This file data is incomplete!'));43}4445$buffer .= $chunk->getDataFile()->loadFileData();46}4748return $buffer;49}5051public function deleteFile($handle) {52$engine = new PhabricatorDestructionEngine();53$chunks = $this->loadAllChunks($handle, true);54foreach ($chunks as $chunk) {55$engine->destroyObject($chunk);56}57}5859private function loadAllChunks($handle, $need_files) {60$chunks = id(new PhabricatorFileChunkQuery())61->setViewer(PhabricatorUser::getOmnipotentUser())62->withChunkHandles(array($handle))63->needDataFiles($need_files)64->execute();6566$chunks = msort($chunks, 'getByteStart');6768return $chunks;69}7071/**72* Compute a chunked file hash for the viewer.73*74* We can not currently compute a real hash for chunked file uploads (because75* no process sees all of the file data).76*77* We also can not trust the hash that the user claims to have computed. If78* we trust the user, they can upload some `evil.exe` and claim it has the79* same file hash as `good.exe`. When another user later uploads the real80* `good.exe`, we'll just create a reference to the existing `evil.exe`. Users81* who download `good.exe` will then receive `evil.exe`.82*83* Instead, we rehash the user's claimed hash with account secrets. This84* allows users to resume file uploads, but not collide with other users.85*86* Ideally, we'd like to be able to verify hashes, but this is complicated87* and time consuming and gives us a fairly small benefit.88*89* @param PhabricatorUser Viewing user.90* @param string Claimed file hash.91* @return string Rehashed file hash.92*/93public static function getChunkedHash(PhabricatorUser $viewer, $hash) {94if (!$viewer->getPHID()) {95throw new Exception(96pht('Unable to compute chunked hash without real viewer!'));97}9899$input = $viewer->getAccountSecret().':'.$hash.':'.$viewer->getPHID();100return self::getChunkedHashForInput($input);101}102103public static function getChunkedHashForInput($input) {104$rehash = PhabricatorHash::weakDigest($input);105106// Add a suffix to identify this as a chunk hash.107$rehash = substr($rehash, 0, -2).'-C';108109return $rehash;110}111112public function allocateChunks($length, array $properties) {113$file = PhabricatorFile::newChunkedFile($this, $length, $properties);114115$chunk_size = $this->getChunkSize();116117$handle = $file->getStorageHandle();118119$chunks = array();120for ($ii = 0; $ii < $length; $ii += $chunk_size) {121$chunks[] = PhabricatorFileChunk::initializeNewChunk(122$handle,123$ii,124min($ii + $chunk_size, $length));125}126127$file->openTransaction();128foreach ($chunks as $chunk) {129$chunk->save();130}131$file->saveAndIndex();132$file->saveTransaction();133134return $file;135}136137/**138* Find a storage engine which is suitable for storing chunks.139*140* This engine must be a writable engine, have a filesize limit larger than141* the chunk limit, and must not be a chunk engine itself.142*/143private function getWritableEngine() {144// NOTE: We can't just load writable engines or we'll loop forever.145$engines = parent::loadAllEngines();146147foreach ($engines as $engine) {148if ($engine->isChunkEngine()) {149continue;150}151152if ($engine->isTestEngine()) {153continue;154}155156if (!$engine->canWriteFiles()) {157continue;158}159160if ($engine->hasFilesizeLimit()) {161if ($engine->getFilesizeLimit() < $this->getChunkSize()) {162continue;163}164}165166return true;167}168169return false;170}171172public function getChunkSize() {173return (4 * 1024 * 1024);174}175176public function getRawFileDataIterator(177PhabricatorFile $file,178$begin,179$end,180PhabricatorFileStorageFormat $format) {181182// NOTE: It is currently impossible for files stored with the chunk183// engine to have their own formatting (instead, the individual chunks184// are formatted), so we ignore the format object.185186$chunks = id(new PhabricatorFileChunkQuery())187->setViewer(PhabricatorUser::getOmnipotentUser())188->withChunkHandles(array($file->getStorageHandle()))189->withByteRange($begin, $end)190->needDataFiles(true)191->execute();192193return new PhabricatorFileChunkIterator($chunks, $begin, $end);194}195196}197198199