Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/controller/DiffusionServeController.php
| <?php | <?php | ||||
| final class DiffusionServeController extends DiffusionController { | final class DiffusionServeController extends DiffusionController { | ||||
| private $serviceViewer; | private $serviceViewer; | ||||
| private $serviceRepository; | private $serviceRepository; | ||||
| private $isGitLFSRequest; | private $isGitLFSRequest; | ||||
| private $gitLFSToken; | private $gitLFSToken; | ||||
| public function setServiceViewer(PhabricatorUser $viewer) { | public function setServiceViewer(PhabricatorUser $viewer) { | ||||
| $this->getRequest()->setUser($viewer); | |||||
| $this->serviceViewer = $viewer; | $this->serviceViewer = $viewer; | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function getServiceViewer() { | public function getServiceViewer() { | ||||
| return $this->serviceViewer; | return $this->serviceViewer; | ||||
| } | } | ||||
| Show All 17 Lines | final class DiffusionServeController extends DiffusionController { | ||||
| public function isVCSRequest(AphrontRequest $request) { | public function isVCSRequest(AphrontRequest $request) { | ||||
| $identifier = $this->getRepositoryIdentifierFromRequest($request); | $identifier = $this->getRepositoryIdentifierFromRequest($request); | ||||
| if ($identifier === null) { | if ($identifier === null) { | ||||
| return null; | return null; | ||||
| } | } | ||||
| $content_type = $request->getHTTPHeader('Content-Type'); | $content_type = $request->getHTTPHeader('Content-Type'); | ||||
| $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); | $user_agent = idx($_SERVER, 'HTTP_USER_AGENT'); | ||||
| $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); | |||||
| // This may have a "charset" suffix, so only match the prefix. | // This may have a "charset" suffix, so only match the prefix. | ||||
| $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; | $lfs_pattern = '(^application/vnd\\.git-lfs\\+json(;|\z))'; | ||||
| $vcs = null; | $vcs = null; | ||||
| if ($request->getExists('service')) { | if ($request->getExists('service')) { | ||||
| $service = $request->getStr('service'); | $service = $request->getStr('service'); | ||||
| // We get this initially for `info/refs`. | // We get this initially for `info/refs`. | ||||
| // Git also gives us a User-Agent like "git/1.8.2.3". | // Git also gives us a User-Agent like "git/1.8.2.3". | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | ||||
| } else if (strncmp($user_agent, 'git/', 4) === 0) { | } else if (strncmp($user_agent, 'git/', 4) === 0) { | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | ||||
| } else if ($content_type == 'application/x-git-upload-pack-request') { | } else if ($content_type == 'application/x-git-upload-pack-request') { | ||||
| // We get this for `git-upload-pack`. | // We get this for `git-upload-pack`. | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | ||||
| } else if ($content_type == 'application/x-git-receive-pack-request') { | } else if ($content_type == 'application/x-git-receive-pack-request') { | ||||
| // We get this for `git-receive-pack`. | // We get this for `git-receive-pack`. | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | ||||
| } else if (preg_match($lfs_pattern, $content_type)) { | } else if (preg_match($lfs_pattern, $content_type)) { | ||||
| // This is a Git LFS HTTP API request. | // This is a Git LFS HTTP API request. | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | ||||
| $this->isGitLFSRequest = true; | $this->isGitLFSRequest = true; | ||||
| } else if ($request_type == 'git-lfs') { | |||||
| // This is a Git LFS object content request. | |||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; | |||||
| $this->isGitLFSRequest = true; | |||||
| } else if ($request->getExists('cmd')) { | } else if ($request->getExists('cmd')) { | ||||
| // Mercurial also sends an Accept header like | // Mercurial also sends an Accept header like | ||||
| // "application/mercurial-0.1", and a User-Agent like | // "application/mercurial-0.1", and a User-Agent like | ||||
| // "mercurial/proto-1.0". | // "mercurial/proto-1.0". | ||||
| $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; | $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; | ||||
| } else { | } else { | ||||
| // Subversion also sends an initial OPTIONS request (vs GET/POST), and | // Subversion also sends an initial OPTIONS request (vs GET/POST), and | ||||
| // has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2) | // has a User-Agent like "SVN/1.8.3 (x86_64-apple-darwin11.4.2) | ||||
| ▲ Show 20 Lines • Show All 804 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') { | |||||
| return $this->serveGitLFSBatchRequest($repository, $viewer); | |||||
| } 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)); | ||||
| } | } | ||||
| } | |||||
| private function serveGitLFSBatchRequest( | |||||
| PhabricatorRepository $repository, | |||||
| PhabricatorUser $viewer) { | |||||
| $input = PhabricatorStartup::getRawInput(); | |||||
| $input = phutil_json_decode($input); | |||||
| $operation = idx($input, 'operation'); | |||||
| switch ($operation) { | |||||
| case 'upload': | |||||
| $want_upload = true; | |||||
| break; | |||||
| case 'download': | |||||
| $want_upload = false; | |||||
| break; | |||||
| default: | |||||
| return DiffusionGitLFSResponse::newErrorResponse( | |||||
| 404, | |||||
| pht( | |||||
| 'Git LFS batch operation "%s" is not supported by this server.', | |||||
| $operation)); | |||||
| } | |||||
| $objects = idx($input, 'objects', array()); | |||||
| $hashes = array(); | |||||
| foreach ($objects as $object) { | |||||
| $hashes[] = idx($object, 'oid'); | |||||
| } | |||||
| if ($hashes) { | |||||
| $refs = id(new PhabricatorRepositoryGitLFSRefQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withRepositoryPHIDs(array($repository->getPHID())) | |||||
| ->withObjectHashes($hashes) | |||||
| ->execute(); | |||||
| $refs = mpull($refs, null, 'getObjectHash'); | |||||
| } else { | |||||
| $refs = array(); | |||||
| } | |||||
| $file_phids = mpull($refs, 'getFilePHID'); | |||||
| if ($file_phids) { | |||||
| $files = id(new PhabricatorFileQuery()) | |||||
| ->setViewer($viewer) | |||||
| ->withPHIDs(array($file_phids)) | |||||
| ->execute(); | |||||
| $files = mpull($files, null, 'getPHID'); | |||||
| } else { | |||||
| $files = array(); | |||||
| } | |||||
| $authorization = null; | |||||
| $output = array(); | |||||
| foreach ($objects as $object) { | |||||
| $oid = idx($object, 'oid'); | |||||
| $size = idx($object, 'size'); | |||||
| $ref = idx($refs, $oid); | |||||
| // NOTE: If we already have a ref for this object, we only emit a | |||||
| // "download" action. The client should not upload the file again. | |||||
| $actions = array(); | |||||
| if ($ref) { | |||||
| $file = idx($files, $ref->getFilePHID()); | |||||
| if ($file) { | |||||
| $get_uri = $file->getCDNURIWithToken(); | |||||
| $actions['download'] = array( | |||||
| 'href' => $get_uri, | |||||
| ); | |||||
| } | |||||
| } else if ($want_upload) { | |||||
| if (!$authorization) { | |||||
| // Here, we could reuse the existing authorization if we have one, | |||||
| // but it's a little simpler to just generate a new one | |||||
| // unconditionally. | |||||
| $authorization = $this->newGitLFSHTTPAuthorization( | |||||
| $repository, | |||||
| $viewer, | |||||
| $operation); | |||||
| } | |||||
| $put_uri = $repository->getGitLFSURI("info/lfs/upload/{$oid}"); | |||||
| $actions['upload'] = array( | |||||
| 'href' => $put_uri, | |||||
| 'header' => array( | |||||
| 'Authorization' => $authorization, | |||||
| 'X-Phabricator-Request-Type' => 'git-lfs', | |||||
| ), | |||||
| ); | |||||
| } | |||||
| $output[] = array( | |||||
| 'oid' => $oid, | |||||
| 'size' => $size, | |||||
| 'actions' => $actions, | |||||
| ); | |||||
| } | |||||
| $output = array( | |||||
| 'objects' => $output, | |||||
| ); | |||||
| return id(new DiffusionGitLFSResponse()) | |||||
| ->setContent($output); | |||||
| } | |||||
| private function newGitLFSHTTPAuthorization( | |||||
| PhabricatorRepository $repository, | |||||
| PhabricatorUser $viewer, | |||||
| $operation) { | |||||
| $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); | |||||
| $authorization = DiffusionGitLFSTemporaryTokenType::newHTTPAuthorization( | |||||
| $repository, | |||||
| $viewer, | |||||
| $operation); | |||||
| unset($unguarded); | |||||
| return $authorization; | |||||
| } | |||||
| private function getGitLFSRequestPath(PhabricatorRepository $repository) { | private function getGitLFSRequestPath(PhabricatorRepository $repository) { | ||||
| $request_path = $this->getRequestDirectoryPath($repository); | $request_path = $this->getRequestDirectoryPath($repository); | ||||
| $matches = null; | $matches = null; | ||||
| if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) { | if (preg_match('(^/info/lfs(?:\z|/)(.*))', $request_path, $matches)) { | ||||
| return $matches[1]; | return $matches[1]; | ||||
| } | } | ||||
| return null; | return null; | ||||
| } | } | ||||
| } | } | ||||