Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/controller/DiffusionServeController.php
Show First 20 Lines • Show All 885 Lines • ▼ Show 20 Lines | private function serveGitLFSRequest( | ||||
PhabricatorRepository $repository, | PhabricatorRepository $repository, | ||||
PhabricatorUser $viewer) { | PhabricatorUser $viewer) { | ||||
if (!$this->getIsGitLFSRequest()) { | if (!$this->getIsGitLFSRequest()) { | ||||
throw new Exception(pht('This is not a Git LFS request!')); | throw new Exception(pht('This is not a Git LFS request!')); | ||||
} | } | ||||
$path = $this->getGitLFSRequestPath($repository); | $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); | return $this->serveGitLFSBatchRequest($repository, $viewer); | ||||
} else { | } else { | ||||
return DiffusionGitLFSResponse::newErrorResponse( | return DiffusionGitLFSResponse::newErrorResponse( | ||||
404, | 404, | ||||
pht( | pht( | ||||
'Git LFS operation "%s" is not supported by this server.', | 'Git LFS operation "%s" is not supported by this server.', | ||||
$path)); | $path)); | ||||
} | } | ||||
Show All 39 Lines | private function serveGitLFSBatchRequest( | ||||
} else { | } else { | ||||
$refs = array(); | $refs = array(); | ||||
} | } | ||||
$file_phids = mpull($refs, 'getFilePHID'); | $file_phids = mpull($refs, 'getFilePHID'); | ||||
if ($file_phids) { | if ($file_phids) { | ||||
$files = id(new PhabricatorFileQuery()) | $files = id(new PhabricatorFileQuery()) | ||||
->setViewer($viewer) | ->setViewer($viewer) | ||||
->withPHIDs(array($file_phids)) | ->withPHIDs($file_phids) | ||||
->execute(); | ->execute(); | ||||
$files = mpull($files, null, 'getPHID'); | $files = mpull($files, null, 'getPHID'); | ||||
} else { | } else { | ||||
$files = array(); | $files = array(); | ||||
} | } | ||||
$authorization = null; | $authorization = null; | ||||
$output = array(); | $output = array(); | ||||
foreach ($objects as $object) { | foreach ($objects as $object) { | ||||
$oid = idx($object, 'oid'); | $oid = idx($object, 'oid'); | ||||
$size = idx($object, 'size'); | $size = idx($object, 'size'); | ||||
$ref = idx($refs, $oid); | $ref = idx($refs, $oid); | ||||
$error = null; | |||||
// NOTE: If we already have a ref for this object, we only emit a | // NOTE: If we already have a ref for this object, we only emit a | ||||
// "download" action. The client should not upload the file again. | // "download" action. The client should not upload the file again. | ||||
$actions = array(); | $actions = array(); | ||||
if ($ref) { | if ($ref) { | ||||
$file = idx($files, $ref->getFilePHID()); | $file = idx($files, $ref->getFilePHID()); | ||||
if ($file) { | 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(); | $get_uri = $file->getCDNURIWithToken(); | ||||
$actions['download'] = array( | $actions['download'] = array( | ||||
'href' => $get_uri, | '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) { | } else if ($want_upload) { | ||||
if (!$authorization) { | if (!$authorization) { | ||||
// Here, we could reuse the existing authorization if we have one, | // Here, we could reuse the existing authorization if we have one, | ||||
// but it's a little simpler to just generate a new one | // but it's a little simpler to just generate a new one | ||||
// unconditionally. | // unconditionally. | ||||
$authorization = $this->newGitLFSHTTPAuthorization( | $authorization = $this->newGitLFSHTTPAuthorization( | ||||
$repository, | $repository, | ||||
$viewer, | $viewer, | ||||
$operation); | $operation); | ||||
} | } | ||||
$put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); | $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); | ||||
$actions['upload'] = array( | $actions['upload'] = array( | ||||
'href' => $put_uri, | 'href' => $put_uri, | ||||
'header' => array( | 'header' => array( | ||||
'Authorization' => $authorization, | 'Authorization' => $authorization, | ||||
'X-Phabricator-Request-Type' => 'git-lfs', | 'X-Phabricator-Request-Type' => 'git-lfs', | ||||
), | ), | ||||
); | ); | ||||
} | } | ||||
$output[] = array( | $object = array( | ||||
'oid' => $oid, | 'oid' => $oid, | ||||
'size' => $size, | 'size' => $size, | ||||
'actions' => $actions, | |||||
); | ); | ||||
if ($actions) { | |||||
$object['actions'] = $actions; | |||||
} | |||||
if ($error) { | |||||
$object['error'] = $error; | |||||
} | |||||
$output[] = $object; | |||||
} | } | ||||
$output = array( | $output = array( | ||||
'objects' => $output, | 'objects' => $output, | ||||
); | ); | ||||
return id(new DiffusionGitLFSResponse()) | return id(new DiffusionGitLFSResponse()) | ||||
->setContent($output); | ->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( | private function newGitLFSHTTPAuthorization( | ||||
PhabricatorRepository $repository, | PhabricatorRepository $repository, | ||||
PhabricatorUser $viewer, | PhabricatorUser $viewer, | ||||
$operation) { | $operation) { | ||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | ||||
$authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( | $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( | ||||
Show All 21 Lines |