@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Serve Git reads over HTTP

Summary: Mostly ripped from D7391. No writes yet.

Test Plan: Ran `git clone` against a local over HTTP, got a clone.

Reviewers: btrahan, hach-que

Reviewed By: hach-que

CC: aran

Maniphest Tasks: T2230

Differential Revision: https://secure.phabricator.com/D7423

+119 -6
+2
src/__phutil_library_map__.php
··· 480 480 'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php', 481 481 'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php', 482 482 'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php', 483 + 'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php', 483 484 'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php', 484 485 'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php', 485 486 'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php', ··· 2673 2674 'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery', 2674 2675 'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery', 2675 2676 'DiffusionGitRequest' => 'DiffusionRequest', 2677 + 'DiffusionGitResponse' => 'AphrontResponse', 2676 2678 'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery', 2677 2679 'DiffusionHistoryController' => 'DiffusionController', 2678 2680 'DiffusionHistoryTableView' => 'DiffusionView',
+73 -6
src/applications/diffusion/controller/DiffusionController.php
··· 17 17 if (preg_match($regex, (string)$uri, $matches)) { 18 18 $vcs = null; 19 19 20 + $content_type = $request->getHTTPHeader('Content-Type'); 21 + 20 22 if ($request->getExists('__vcs__')) { 21 23 // This is magic to make it easier for us to debug stuff by telling 22 24 // users to run: ··· 26 28 // ...to get a human-readable error. 27 29 $vcs = $request->getExists('__vcs__'); 28 30 } else if ($request->getExists('service')) { 31 + $service = $request->getStr('service'); 32 + // We get this initially for `info/refs`. 29 33 // Git also gives us a User-Agent like "git/1.8.2.3". 34 + $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; 35 + } else if ($content_type == 'application/x-git-upload-pack-request') { 36 + // We get this for `git-upload-pack`. 30 37 $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; 31 38 } else if ($request->getExists('cmd')) { 32 39 // Mercurial also sends an Accept header like ··· 125 132 pht('This repository is not available over HTTP.')); 126 133 } 127 134 135 + switch ($repository->getVersionControlSystem()) { 136 + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 137 + return $this->serveGitRequest($repository); 138 + default: 139 + break; 140 + } 141 + 128 142 return new PhabricatorVCSResponse( 129 143 999, 130 144 pht('TODO: Implement meaningful responses.')); ··· 133 147 private function isReadOnlyRequest( 134 148 PhabricatorRepository $repository) { 135 149 $request = $this->getRequest(); 150 + $method = $_SERVER['REQUEST_METHOD']; 136 151 137 152 // TODO: This implementation is safe by default, but very incomplete. 138 153 139 154 switch ($repository->getVersionControlSystem()) { 140 155 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 141 156 $service = $request->getStr('service'); 157 + $path = $this->getRequestDirectoryPath(); 142 158 // NOTE: Service names are the reverse of what you might expect, as they 143 159 // are from the point of view of the server. The main read service is 144 160 // "git-upload-pack", and the main write service is "git-receive-pack". 145 - switch ($service) { 146 - case 'git-upload-pack': 147 - return true; 148 - case 'git-receive-pack': 149 - default: 150 - return false; 161 + 162 + if ($method == 'GET' && 163 + $path == '/info/refs' && 164 + $service == 'git-upload-pack') { 165 + return true; 151 166 } 167 + 168 + if ($path == '/git-upload-pack') { 169 + return true; 170 + } 171 + 152 172 break; 153 173 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: 154 174 $cmd = $request->getStr('cmd'); ··· 373 393 } 374 394 375 395 return $links; 396 + } 397 + 398 + /** 399 + * @phutil-external-symbol class PhabricatorStartup 400 + */ 401 + private function serveGitRequest(PhabricatorRepository $repository) { 402 + $request = $this->getRequest(); 403 + 404 + $request_path = $this->getRequestDirectoryPath(); 405 + $repository_root = $repository->getLocalPath(); 406 + 407 + // Rebuild the query string to strip `__magic__` parameters and prevent 408 + // issues where we might interpret inputs like "service=read&service=write" 409 + // differently than the server does and pass it an unsafe command. 410 + $query_data = $request->getPassthroughRequestParameters(); 411 + $query_string = http_build_query($query_data, '', '&'); 412 + 413 + // We're about to wipe out PATH with the rest of the environment, so 414 + // resolve the binary first. 415 + $bin = Filesystem::resolveBinary('git-http-backend'); 416 + if (!$bin) { 417 + throw new Exception("Unable to find `git-http-backend` in PATH!"); 418 + } 419 + 420 + $env = array( 421 + 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'], 422 + 'QUERY_STRING' => $query_string, 423 + 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'], 424 + 'REMOTE_USER' => '', 425 + 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'], 426 + 'GIT_PROJECT_ROOT' => $repository_root, 427 + 'GIT_HTTP_EXPORT_ALL' => '1', 428 + 'PATH_INFO' => $request_path, 429 + ); 430 + 431 + list($stdout) = id(new ExecFuture('%s', $bin)) 432 + ->setEnv($env, true) 433 + ->write(PhabricatorStartup::getRawInput()) 434 + ->resolvex(); 435 + 436 + return id(new DiffusionGitResponse())->setGitData($stdout); 437 + } 438 + 439 + private function getRequestDirectoryPath() { 440 + $request = $this->getRequest(); 441 + $request_path = $request->getRequestURI()->getPath(); 442 + return preg_replace('@^/diffusion/[A-Z]+@', '', $request_path); 376 443 } 377 444 378 445 }
+44
src/applications/diffusion/response/DiffusionGitResponse.php
··· 1 + <?php 2 + 3 + final class DiffusionGitResponse extends AphrontResponse { 4 + 5 + private $httpCode; 6 + private $headers = array(); 7 + private $response; 8 + 9 + public function setGitData($data) { 10 + list($headers, $body) = explode("\r\n\r\n", $data, 2); 11 + $this->response = $body; 12 + $headers = explode("\r\n", $headers); 13 + 14 + $matches = null; 15 + $this->httpCode = 200; 16 + $this->headers = array(); 17 + foreach ($headers as $header) { 18 + if (preg_match('/^Status:\s*(\d+)/i', $header, $matches)) { 19 + $this->httpCode = (int)$matches[1]; 20 + } else { 21 + $this->headers[] = explode(': ', $header, 2); 22 + } 23 + } 24 + 25 + return $this; 26 + } 27 + 28 + public function buildResponseString() { 29 + return $this->response; 30 + } 31 + 32 + public function getHeaders() { 33 + return $this->headers; 34 + } 35 + 36 + public function getCacheHeaders() { 37 + return array(); 38 + } 39 + 40 + public function getHTTPResponseCode() { 41 + return $this->httpCode; 42 + } 43 + 44 + }