1
0
Fork 0
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:
epriestley 2013-10-26 12:10:52 -07:00
parent bb4904553f
commit 7d9dfb561d
3 changed files with 119 additions and 6 deletions

View file

@ -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',

View file

@ -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);
}
}

View 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;
}
}