1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-12 18:02:39 +01:00
phorge-arcanist/src/upload/ArcanistFileDataRef.php
epriestley a59cfca5f1 Upgrade "arc upload" to use SHA256
Summary: Fixes T12464. Moves "arc upload" to SHA256 where applicable.

Test Plan: Ran `arc upload` against a server with D17620 twice, saw it skip the actual upload the second time.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12464

Differential Revision: https://secure.phabricator.com/D17622
2017-04-04 16:56:35 -07:00

368 lines
7.5 KiB
PHP

<?php
/**
* Reference to a file or block of file data which can be uploaded using
* @{class:ArcanistFileUploader}.
*
* You can either upload a file on disk by using @{method:setPath}, or upload
* a block of data in memory by using @{method:setData}.
*
* For usage examples, see @{class:ArcanistFileUploader}.
*
* After uploading, successful uploads will have @{method:getPHID} populated.
* Failed uploads will have @{method:getErrors} populated with a description
* of reasons for failure.
*
* @task config Configuring File References
* @task results Handling Upload Results
* @task uploader Uploader API
*/
final class ArcanistFileDataRef extends Phobject {
private $name;
private $data;
private $path;
private $hash;
private $size;
private $errors = array();
private $phid;
private $fileHandle;
private $deleteAfterEpoch;
private $viewPolicy;
/* -( Configuring File References )---------------------------------------- */
/**
* Set a human-readable display filename, like "file.jpg".
*
* This name does not correspond to a path on disk, and is purely for
* human consumption.
*
* @param string Filename.
* @return this
* @task config
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* @task config
*/
public function getName() {
return $this->name;
}
/**
* Set the data to upload as a single raw blob.
*
* You can specify file data by calling this method with a single blob of
* data, or by calling @{method:setPath} and providing a path to a file on
* disk.
*
* @param bytes Blob of file data.
* @task config
*/
public function setData($data) {
$this->data = $data;
return $this;
}
/**
* @task config
*/
public function getData() {
return $this->data;
}
/**
* Set the data to upload by pointing to a file on disk.
*
* You can specify file data by calling this method with a path, or by
* providing a blob of raw data to @{method:setData}.
*
* The path itself only provides data. If you want to name the file, you
* should also call @{method:setName}.
*
* @param string Path on disk to a file containing data to upload.
* @return this
* @task config
*/
public function setPath($path) {
$this->path = $path;
return $this;
}
/**
* @task config
*/
public function getPath() {
return $this->path;
}
/**
* @task config
*/
public function setViewPolicy($view_policy) {
$this->viewPolicy = $view_policy;
return $this;
}
/**
* @task config
*/
public function getViewPolicy() {
return $this->viewPolicy;
}
/**
* Configure a file to be temporary instead of permanent.
*
* By default, files are retained indefinitely until explicitly deleted. If
* you want to upload a temporary file instead, you can specify an epoch
* timestamp. The file will be deleted after this time.
*
* @param int Epoch timestamp to retain the file until.
* @return this
* @task config
*/
public function setDeleteAfterEpoch($epoch) {
$this->deleteAfterEpoch = $epoch;
return $this;
}
/**
* @task config
*/
public function getDeleteAfterEpoch() {
return $this->deleteAfterEpoch;
}
/* -( Handling Upload Results )-------------------------------------------- */
/**
* @task results
*/
public function getErrors() {
return $this->errors;
}
/**
* @task results
*/
public function getPHID() {
return $this->phid;
}
/* -( Uploader API )------------------------------------------------------- */
/**
* @task uploader
*/
public function willUpload() {
$have_data = ($this->data !== null);
$have_path = ($this->path !== null);
if (!$have_data && !$have_path) {
throw new Exception(
pht(
'Specify setData() or setPath() when building a file data '.
'reference.'));
}
if ($have_data && $have_path) {
throw new Exception(
pht(
'Specify either setData() or setPath() when building a file data '.
'reference, but not both.'));
}
if ($have_path) {
$path = $this->path;
if (!Filesystem::pathExists($path)) {
throw new Exception(
pht(
'Unable to upload file: path "%s" does not exist.',
$path));
}
try {
Filesystem::assertIsFile($path);
} catch (FilesystemException $ex) {
throw new Exception(
pht(
'Unable to upload file: path "%s" is not a file.',
$path));
}
try {
Filesystem::assertReadable($path);
} catch (FilesystemException $ex) {
throw new Exception(
pht(
'Unable to upload file: path "%s" is not readable.',
$path));
}
$size = @filesize($path);
if ($size === false) {
throw new Exception(
pht(
'Unable to upload file: failed to determine filesize of '.
'path "%s".',
$path));
}
$this->hash = $this->newFileHash($path);
$this->size = $size;
} else {
$data = $this->data;
$this->hash = $this->newDataHash($data);
$this->size = strlen($data);
}
}
/**
* @task uploader
*/
public function didFail($error) {
$this->errors[] = $error;
return $this;
}
/**
* @task uploader
*/
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
/**
* @task uploader
*/
public function getByteSize() {
if ($this->size === null) {
throw new PhutilInvalidStateException('willUpload');
}
return $this->size;
}
/**
* @task uploader
*/
public function getContentHash() {
if ($this->size === null) {
throw new PhutilInvalidStateException('willUpload');
}
return $this->hash;
}
/**
* @task uploader
*/
public function didUpload() {
if ($this->fileHandle) {
@fclose($this->fileHandle);
$this->fileHandle = null;
}
}
/**
* @task uploader
*/
public function readBytes($start, $end) {
if ($this->size === null) {
throw new PhutilInvalidStateException('willUpload');
}
$len = ($end - $start);
if ($this->data !== null) {
return substr($this->data, $start, $len);
}
$path = $this->path;
if ($this->fileHandle === null) {
$f = @fopen($path, 'rb');
if (!$f) {
throw new Exception(
pht(
'Unable to upload file: failed to open path "%s" for reading.',
$path));
}
$this->fileHandle = $f;
}
$f = $this->fileHandle;
$ok = @fseek($f, $start);
if ($ok !== 0) {
throw new Exception(
pht(
'Unable to upload file: failed to fseek() to offset %d in file '.
'at path "%s".',
$start,
$path));
}
$data = @fread($f, $len);
if ($data === false) {
throw new Exception(
pht(
'Unable to upload file: failed to read %d bytes after offset %d '.
'from file at path "%s".',
$len,
$start,
$path));
}
return $data;
}
private function newFileHash($path) {
$hash = hash_file('sha256', $path, $raw_output = false);
if ($hash === false) {
return null;
}
return $hash;
}
private function newDataHash($data) {
$hash = hash('sha256', $data, $raw_output = false);
if ($hash === false) {
return null;
}
return $hash;
}
}