Page MenuHomePhabricator

D15492.diff
No OneTemporary

D15492.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2501,6 +2501,7 @@
'PhabricatorInvalidConfigSetupCheck' => 'applications/config/check/PhabricatorInvalidConfigSetupCheck.php',
'PhabricatorIteratedMD5PasswordHasher' => 'infrastructure/util/password/PhabricatorIteratedMD5PasswordHasher.php',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'infrastructure/util/password/__tests__/PhabricatorIteratedMD5PasswordHasherTestCase.php',
+ 'PhabricatorIteratorFileUploadSource' => 'applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php',
'PhabricatorJIRAAuthProvider' => 'applications/auth/provider/PhabricatorJIRAAuthProvider.php',
'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php',
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
@@ -6947,6 +6948,7 @@
'PhabricatorInvalidConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorIteratedMD5PasswordHasher' => 'PhabricatorPasswordHasher',
'PhabricatorIteratedMD5PasswordHasherTestCase' => 'PhabricatorTestCase',
+ 'PhabricatorIteratorFileUploadSource' => 'PhabricatorFileUploadSource',
'PhabricatorJIRAAuthProvider' => 'PhabricatorOAuth1AuthProvider',
'PhabricatorJavelinLinter' => 'ArcanistLinter',
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php
--- a/src/applications/diffusion/controller/DiffusionServeController.php
+++ b/src/applications/diffusion/controller/DiffusionServeController.php
@@ -891,7 +891,12 @@
}
$path = $this->getGitLFSRequestPath($repository);
- if ($path == 'objects/batch') {
+ $matches = null;
+
+ if (preg_match('(^upload/(.*)\z)', $path, $matches)) {
+ $oid = $matches[1];
+ return $this->serveGitLFSUploadRequest($repository, $viewer, $oid);
+ } else if ($path == 'objects/batch') {
return $this->serveGitLFSBatchRequest($repository, $viewer);
} else {
return DiffusionGitLFSResponse::newErrorResponse(
@@ -947,7 +952,7 @@
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
- ->withPHIDs(array($file_phids))
+ ->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
@@ -960,6 +965,7 @@
$oid = idx($object, 'oid');
$size = idx($object, 'size');
$ref = idx($refs, $oid);
+ $error = null;
// NOTE: If we already have a ref for this object, we only emit a
// "download" action. The client should not upload the file again.
@@ -968,9 +974,26 @@
if ($ref) {
$file = idx($files, $ref->getFilePHID());
if ($file) {
+ // Git LFS may prompt users for authentication if the action does
+ // not provide an "Authorization" header and does not have a query
+ // parameter named "token". See here for discussion:
+ // <https://github.com/github/git-lfs/issues/1088>
+ $no_authorization = 'Basic '.base64_encode('none');
+
$get_uri = $file->getCDNURIWithToken();
$actions['download'] = array(
'href' => $get_uri,
+ 'header' => array(
+ 'Authorization' => $no_authorization,
+ ),
+ );
+ } else {
+ $error = array(
+ 'code' => 404,
+ 'message' => pht(
+ 'Object "%s" was previously uploaded, but no longer exists '.
+ 'on this server.',
+ $oid),
);
}
} else if ($want_upload) {
@@ -995,11 +1018,20 @@
);
}
- $output[] = array(
+ $object = array(
'oid' => $oid,
'size' => $size,
- 'actions' => $actions,
);
+
+ if ($actions) {
+ $object['actions'] = $actions;
+ }
+
+ if ($error) {
+ $object['error'] = $error;
+ }
+
+ $output[] = $object;
}
$output = array(
@@ -1010,6 +1042,69 @@
->setContent($output);
}
+ private function serveGitLFSUploadRequest(
+ PhabricatorRepository $repository,
+ PhabricatorUser $viewer,
+ $oid) {
+
+ $ref = id(new PhabricatorRepositoryGitLFSRefQuery())
+ ->setViewer($viewer)
+ ->withRepositoryPHIDs(array($repository->getPHID()))
+ ->withObjectHashes(array($oid))
+ ->executeOne();
+ if ($ref) {
+ return DiffusionGitLFSResponse::newErrorResponse(
+ 405,
+ pht(
+ 'Content for object "%s" is already known to this server. It can '.
+ 'not be uploaded again.',
+ $oid));
+ }
+
+ $request_stream = new AphrontRequestStream();
+ $request_iterator = $request_stream->getIterator();
+ $hashing_iterator = id(new PhutilHashingIterator($request_iterator))
+ ->setAlgorithm('sha256');
+
+ $source = id(new PhabricatorIteratorFileUploadSource())
+ ->setName('lfs-'.$oid)
+ ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE)
+ ->setIterator($hashing_iterator);
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ $file = $source->uploadFile();
+ unset($unguarded);
+
+ $hash = $hashing_iterator->getHash();
+ if ($hash !== $oid) {
+ return DiffusionGitLFSResponse::newErrorResponse(
+ 400,
+ pht(
+ 'Uploaded data is corrupt or invalid. Expected hash "%s", actual '.
+ 'hash "%s".',
+ $oid,
+ $hash));
+ }
+
+ $ref = id(new PhabricatorRepositoryGitLFSRef())
+ ->setRepositoryPHID($repository->getPHID())
+ ->setObjectHash($hash)
+ ->setByteSize($file->getByteSize())
+ ->setAuthorPHID($viewer->getPHID())
+ ->setFilePHID($file->getPHID());
+
+ $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+ // Attach the file to the repository to give users permission
+ // to access it.
+ $file->attachToObject($repository->getPHID());
+ $ref->save();
+ unset($unguarded);
+
+ // This is just a plain HTTP 200 with no content, which is what `git lfs`
+ // expects.
+ return new DiffusionGitLFSResponse();
+ }
+
private function newGitLFSHTTPAuthorization(
PhabricatorRepository $repository,
PhabricatorUser $viewer,
diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php
--- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php
+++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php
@@ -72,7 +72,9 @@
$data->rewind();
$this->didRewind = true;
} else {
- $data->next();
+ if ($data->valid()) {
+ $data->next();
+ }
}
if (!$data->valid()) {
diff --git a/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php
new file mode 100644
--- /dev/null
+++ b/src/applications/files/uploadsource/PhabricatorIteratorFileUploadSource.php
@@ -0,0 +1,25 @@
+<?php
+
+final class PhabricatorIteratorFileUploadSource
+ extends PhabricatorFileUploadSource {
+
+ private $iterator;
+
+ public function setIterator(Iterator $iterator) {
+ $this->iterator = $iterator;
+ return $this;
+ }
+
+ public function getIterator() {
+ return $this->iterator;
+ }
+
+ protected function newDataIterator() {
+ return $this->getIterator();
+ }
+
+ protected function getDataLength() {
+ return null;
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Fri, Dec 20, 4:59 AM (20 h, 44 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6909549
Default Alt Text
D15492.diff (7 KB)

Event Timeline