mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
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
This commit is contained in:
parent
bb4904553f
commit
7d9dfb561d
3 changed files with 119 additions and 6 deletions
|
@ -480,6 +480,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
|
||||
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
|
||||
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
|
||||
'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
|
||||
'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php',
|
||||
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
|
||||
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
|
||||
|
@ -2673,6 +2674,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
|
||||
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
|
||||
'DiffusionGitRequest' => 'DiffusionRequest',
|
||||
'DiffusionGitResponse' => 'AphrontResponse',
|
||||
'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery',
|
||||
'DiffusionHistoryController' => 'DiffusionController',
|
||||
'DiffusionHistoryTableView' => 'DiffusionView',
|
||||
|
|
|
@ -17,6 +17,8 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
if (preg_match($regex, (string)$uri, $matches)) {
|
||||
$vcs = null;
|
||||
|
||||
$content_type = $request->getHTTPHeader('Content-Type');
|
||||
|
||||
if ($request->getExists('__vcs__')) {
|
||||
// This is magic to make it easier for us to debug stuff by telling
|
||||
// users to run:
|
||||
|
@ -26,8 +28,13 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
// ...to get a human-readable error.
|
||||
$vcs = $request->getExists('__vcs__');
|
||||
} else if ($request->getExists('service')) {
|
||||
$service = $request->getStr('service');
|
||||
// We get this initially for `info/refs`.
|
||||
// Git also gives us a User-Agent like "git/1.8.2.3".
|
||||
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
} else if ($content_type == 'application/x-git-upload-pack-request') {
|
||||
// We get this for `git-upload-pack`.
|
||||
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
} else if ($request->getExists('cmd')) {
|
||||
// Mercurial also sends an Accept header like
|
||||
// "application/mercurial-0.1", and a User-Agent like
|
||||
|
@ -125,6 +132,13 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
pht('This repository is not available over HTTP.'));
|
||||
}
|
||||
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
return $this->serveGitRequest($repository);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return new PhabricatorVCSResponse(
|
||||
999,
|
||||
pht('TODO: Implement meaningful responses.'));
|
||||
|
@ -133,22 +147,28 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
private function isReadOnlyRequest(
|
||||
PhabricatorRepository $repository) {
|
||||
$request = $this->getRequest();
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
// TODO: This implementation is safe by default, but very incomplete.
|
||||
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
|
||||
$service = $request->getStr('service');
|
||||
$path = $this->getRequestDirectoryPath();
|
||||
// NOTE: Service names are the reverse of what you might expect, as they
|
||||
// are from the point of view of the server. The main read service is
|
||||
// "git-upload-pack", and the main write service is "git-receive-pack".
|
||||
switch ($service) {
|
||||
case 'git-upload-pack':
|
||||
return true;
|
||||
case 'git-receive-pack':
|
||||
default:
|
||||
return false;
|
||||
|
||||
if ($method == 'GET' &&
|
||||
$path == '/info/refs' &&
|
||||
$service == 'git-upload-pack') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($path == '/git-upload-pack') {
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
|
||||
$cmd = $request->getStr('cmd');
|
||||
|
@ -375,5 +395,52 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phutil-external-symbol class PhabricatorStartup
|
||||
*/
|
||||
private function serveGitRequest(PhabricatorRepository $repository) {
|
||||
$request = $this->getRequest();
|
||||
|
||||
$request_path = $this->getRequestDirectoryPath();
|
||||
$repository_root = $repository->getLocalPath();
|
||||
|
||||
// Rebuild the query string to strip `__magic__` parameters and prevent
|
||||
// issues where we might interpret inputs like "service=read&service=write"
|
||||
// differently than the server does and pass it an unsafe command.
|
||||
$query_data = $request->getPassthroughRequestParameters();
|
||||
$query_string = http_build_query($query_data, '', '&');
|
||||
|
||||
// We're about to wipe out PATH with the rest of the environment, so
|
||||
// resolve the binary first.
|
||||
$bin = Filesystem::resolveBinary('git-http-backend');
|
||||
if (!$bin) {
|
||||
throw new Exception("Unable to find `git-http-backend` in PATH!");
|
||||
}
|
||||
|
||||
$env = array(
|
||||
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
|
||||
'QUERY_STRING' => $query_string,
|
||||
'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
|
||||
'REMOTE_USER' => '',
|
||||
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
|
||||
'GIT_PROJECT_ROOT' => $repository_root,
|
||||
'GIT_HTTP_EXPORT_ALL' => '1',
|
||||
'PATH_INFO' => $request_path,
|
||||
);
|
||||
|
||||
list($stdout) = id(new ExecFuture('%s', $bin))
|
||||
->setEnv($env, true)
|
||||
->write(PhabricatorStartup::getRawInput())
|
||||
->resolvex();
|
||||
|
||||
return id(new DiffusionGitResponse())->setGitData($stdout);
|
||||
}
|
||||
|
||||
private function getRequestDirectoryPath() {
|
||||
$request = $this->getRequest();
|
||||
$request_path = $request->getRequestURI()->getPath();
|
||||
return preg_replace('@^/diffusion/[A-Z]+@', '', $request_path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
44
src/applications/diffusion/response/DiffusionGitResponse.php
Normal file
44
src/applications/diffusion/response/DiffusionGitResponse.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionGitResponse extends AphrontResponse {
|
||||
|
||||
private $httpCode;
|
||||
private $headers = array();
|
||||
private $response;
|
||||
|
||||
public function setGitData($data) {
|
||||
list($headers, $body) = explode("\r\n\r\n", $data, 2);
|
||||
$this->response = $body;
|
||||
$headers = explode("\r\n", $headers);
|
||||
|
||||
$matches = null;
|
||||
$this->httpCode = 200;
|
||||
$this->headers = array();
|
||||
foreach ($headers as $header) {
|
||||
if (preg_match('/^Status:\s*(\d+)/i', $header, $matches)) {
|
||||
$this->httpCode = (int)$matches[1];
|
||||
} else {
|
||||
$this->headers[] = explode(': ', $header, 2);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function buildResponseString() {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function getHeaders() {
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getCacheHeaders() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getHTTPResponseCode() {
|
||||
return $this->httpCode;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue