mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-12 18:02:39 +01:00
a59cfca5f1
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
368 lines
7.5 KiB
PHP
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;
|
|
}
|
|
|
|
}
|