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 | |||||