Skip to content

Commit 7d9dfb5

Browse files
author
epriestley
committed
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
1 parent bb49045 commit 7d9dfb5

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

src/__phutil_library_map__.php

+2
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@
480480
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
481481
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
482482
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
483+
'DiffusionGitResponse' => 'applications/diffusion/response/DiffusionGitResponse.php',
483484
'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php',
484485
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
485486
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
@@ -2673,6 +2674,7 @@
26732674
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
26742675
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
26752676
'DiffusionGitRequest' => 'DiffusionRequest',
2677+
'DiffusionGitResponse' => 'AphrontResponse',
26762678
'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery',
26772679
'DiffusionHistoryController' => 'DiffusionController',
26782680
'DiffusionHistoryTableView' => 'DiffusionView',

src/applications/diffusion/controller/DiffusionController.php

+73-6
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public function willBeginExecution() {
1717
if (preg_match($regex, (string)$uri, $matches)) {
1818
$vcs = null;
1919

20+
$content_type = $request->getHTTPHeader('Content-Type');
21+
2022
if ($request->getExists('__vcs__')) {
2123
// This is magic to make it easier for us to debug stuff by telling
2224
// users to run:
@@ -26,8 +28,13 @@ public function willBeginExecution() {
2628
// ...to get a human-readable error.
2729
$vcs = $request->getExists('__vcs__');
2830
} else if ($request->getExists('service')) {
31+
$service = $request->getStr('service');
32+
// We get this initially for `info/refs`.
2933
// Git also gives us a User-Agent like "git/1.8.2.3".
3034
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
35+
} else if ($content_type == 'application/x-git-upload-pack-request') {
36+
// We get this for `git-upload-pack`.
37+
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
3138
} else if ($request->getExists('cmd')) {
3239
// Mercurial also sends an Accept header like
3340
// "application/mercurial-0.1", and a User-Agent like
@@ -125,6 +132,13 @@ private function processVCSRequest($callsign) {
125132
pht('This repository is not available over HTTP.'));
126133
}
127134

135+
switch ($repository->getVersionControlSystem()) {
136+
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
137+
return $this->serveGitRequest($repository);
138+
default:
139+
break;
140+
}
141+
128142
return new PhabricatorVCSResponse(
129143
999,
130144
pht('TODO: Implement meaningful responses.'));
@@ -133,22 +147,28 @@ private function processVCSRequest($callsign) {
133147
private function isReadOnlyRequest(
134148
PhabricatorRepository $repository) {
135149
$request = $this->getRequest();
150+
$method = $_SERVER['REQUEST_METHOD'];
136151

137152
// TODO: This implementation is safe by default, but very incomplete.
138153

139154
switch ($repository->getVersionControlSystem()) {
140155
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
141156
$service = $request->getStr('service');
157+
$path = $this->getRequestDirectoryPath();
142158
// NOTE: Service names are the reverse of what you might expect, as they
143159
// are from the point of view of the server. The main read service is
144160
// "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;
166+
}
167+
168+
if ($path == '/git-upload-pack') {
169+
return true;
151170
}
171+
152172
break;
153173
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
154174
$cmd = $request->getStr('cmd');
@@ -375,5 +395,52 @@ protected function renderPathLinks(DiffusionRequest $drequest, $action) {
375395
return $links;
376396
}
377397

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);
443+
}
444+
378445
}
379446

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
}

0 commit comments

Comments
 (0)