Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14054794
D7417.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
D7417.diff
View Options
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -509,6 +509,7 @@
'DiffusionRenameHistoryQuery' => 'applications/diffusion/query/DiffusionRenameHistoryQuery.php',
'DiffusionRepositoryController' => 'applications/diffusion/controller/DiffusionRepositoryController.php',
'DiffusionRepositoryCreateController' => 'applications/diffusion/controller/DiffusionRepositoryCreateController.php',
+ 'DiffusionRepositoryDefaultController' => 'applications/diffusion/controller/DiffusionRepositoryDefaultController.php',
'DiffusionRepositoryEditActionsController' => 'applications/diffusion/controller/DiffusionRepositoryEditActionsController.php',
'DiffusionRepositoryEditActivateController' => 'applications/diffusion/controller/DiffusionRepositoryEditActivateController.php',
'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
@@ -1867,6 +1868,7 @@
'PhabricatorUserTestCase' => 'applications/people/storage/__tests__/PhabricatorUserTestCase.php',
'PhabricatorUserTitleField' => 'applications/people/customfield/PhabricatorUserTitleField.php',
'PhabricatorUserTransaction' => 'applications/people/storage/PhabricatorUserTransaction.php',
+ 'PhabricatorVCSResponse' => 'applications/repository/response/PhabricatorVCSResponse.php',
'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php',
'PhabricatorWorkerActiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerActiveTask.php',
'PhabricatorWorkerArchiveTask' => 'infrastructure/daemon/workers/storage/PhabricatorWorkerArchiveTask.php',
@@ -2691,6 +2693,7 @@
'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
+ 'DiffusionRepositoryDefaultController' => 'DiffusionController',
'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
@@ -4202,6 +4205,7 @@
'PhabricatorUserTestCase' => 'PhabricatorTestCase',
'PhabricatorUserTitleField' => 'PhabricatorUserCustomField',
'PhabricatorUserTransaction' => 'PhabricatorApplicationTransaction',
+ 'PhabricatorVCSResponse' => 'AphrontResponse',
'PhabricatorWorkerActiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerArchiveTask' => 'PhabricatorWorkerTask',
'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
Index: src/aphront/response/AphrontResponse.php
===================================================================
--- src/aphront/response/AphrontResponse.php
+++ src/aphront/response/AphrontResponse.php
@@ -49,6 +49,10 @@
return $this->responseCode;
}
+ public function getHTTPResponseMessage() {
+ return '';
+ }
+
public function setFrameable($frameable) {
$this->frameable = $frameable;
return $this;
Index: src/aphront/sink/AphrontHTTPSink.php
===================================================================
--- src/aphront/sink/AphrontHTTPSink.php
+++ src/aphront/sink/AphrontHTTPSink.php
@@ -25,13 +25,13 @@
* @param int Numeric HTTP status code.
* @return void
*/
- final public function writeHTTPStatus($code) {
+ final public function writeHTTPStatus($code, $message = '') {
if (!preg_match('/^\d{3}$/', $code)) {
throw new Exception("Malformed HTTP status code '{$code}'!");
}
$code = (int)$code;
- $this->emitHTTPStatus($code);
+ $this->emitHTTPStatus($code, $message);
}
@@ -103,7 +103,9 @@
$response->getHeaders(),
$response->getCacheHeaders());
- $this->writeHTTPStatus($response->getHTTPResponseCode());
+ $this->writeHTTPStatus(
+ $response->getHTTPResponseCode(),
+ $response->getHTTPResponseMessage());
$this->writeHeaders($all_headers);
$this->writeData($response_string);
}
@@ -112,7 +114,7 @@
/* -( Emitting the Response )---------------------------------------------- */
- abstract protected function emitHTTPStatus($code);
+ abstract protected function emitHTTPStatus($code, $message = '');
abstract protected function emitHeader($name, $value);
abstract protected function emitData($data);
}
Index: src/aphront/sink/AphrontIsolatedHTTPSink.php
===================================================================
--- src/aphront/sink/AphrontIsolatedHTTPSink.php
+++ src/aphront/sink/AphrontIsolatedHTTPSink.php
@@ -11,7 +11,7 @@
private $headers;
private $data;
- protected function emitHTTPStatus($code) {
+ protected function emitHTTPStatus($code, $message = '') {
$this->status = $code;
}
Index: src/aphront/sink/AphrontPHPHTTPSink.php
===================================================================
--- src/aphront/sink/AphrontPHPHTTPSink.php
+++ src/aphront/sink/AphrontPHPHTTPSink.php
@@ -7,9 +7,13 @@
*/
final class AphrontPHPHTTPSink extends AphrontHTTPSink {
- protected function emitHTTPStatus($code) {
+ protected function emitHTTPStatus($code, $message = '') {
if ($code != 200) {
- header("HTTP/1.0 {$code}");
+ $header = "HTTP/1.0 {$code}";
+ if (strlen($message)) {
+ $header .= " {$message}";
+ }
+ header($header);
}
}
Index: src/applications/base/controller/PhabricatorController.php
===================================================================
--- src/applications/base/controller/PhabricatorController.php
+++ src/applications/base/controller/PhabricatorController.php
@@ -24,7 +24,7 @@
return PhabricatorUserEmail::isEmailVerificationRequired();
}
- final public function willBeginExecution() {
+ public function willBeginExecution() {
$request = $this->getRequest();
if ($request->getUser()) {
Index: src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
===================================================================
--- src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
+++ src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
@@ -79,6 +79,13 @@
'(?P<serve>serve)/' => 'DiffusionRepositoryEditHostingController',
),
),
+
+ // NOTE: This must come after the rule above; it just gives us a
+ // catch-all for serving repositories over HTTP. We must accept
+ // requests without the trailing "/" because SVN commands don't
+ // necessarily include it.
+ '(?P<callsign>[A-Z]+)(/|$).*' => 'DiffusionRepositoryDefaultController',
+
'inline/' => array(
'edit/(?P<phid>[^/]+)/' => 'DiffusionInlineCommentController',
'preview/(?P<phid>[^/]+)/' =>
Index: src/applications/diffusion/controller/DiffusionController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionController.php
+++ src/applications/diffusion/controller/DiffusionController.php
@@ -4,6 +4,168 @@
protected $diffusionRequest;
+ public function willBeginExecution() {
+ $request = $this->getRequest();
+ $uri = $request->getRequestURI();
+
+ // Check if this is a VCS request, e.g. from "git clone", "hg clone", or
+ // "svn checkout". If it is, we jump off into repository serving code to
+ // process the request.
+
+ $regex = '@^/diffusion/(?P<callsign>[A-Z]+)(/|$)@';
+ $matches = null;
+ if (preg_match($regex, (string)$uri, $matches)) {
+ $vcs = null;
+
+ if ($request->getExists('__vcs__')) {
+ // This is magic to make it easier for us to debug stuff by telling
+ // users to run:
+ //
+ // curl http://example.phabricator.com/diffusion/X/?__vcs__=1
+ //
+ // ...to get a human-readable error.
+ $vcs = $request->getExists('__vcs__');
+ } else if ($request->getExists('service')) {
+ // Git also gives us a User-Agent like "git/1.8.2.3".
+ $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
+ // "mercurial/proto-1.0".
+ $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
+ } else {
+ // 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)
+ // serf/1.3.2".
+ $dav = $request->getHTTPHeader('DAV');
+ $dav = new PhutilURI($dav);
+ if ($dav->getDomain() === 'subversion.tigris.org') {
+ $vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN;
+ }
+ }
+
+ if ($vcs) {
+ return $this->processVCSRequest($matches['callsign']);
+ }
+ }
+
+ parent::willBeginExecution();
+ }
+
+ private function processVCSRequest($callsign) {
+
+ // TODO: Authenticate user.
+
+ $viewer = new PhabricatorUser();
+
+ $allow_public = PhabricatorEnv::getEnvConfig('policy.allow-public');
+ if (!$allow_public) {
+ if (!$viewer->isLoggedIn()) {
+ return new PhabricatorVCSResponse(
+ 403,
+ pht('You must log in to access repositories.'));
+ }
+ }
+
+ try {
+ $repository = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withCallsigns(array($callsign))
+ ->executeOne();
+ if (!$repository) {
+ return new PhabricatorVCSResponse(
+ 404,
+ pht('No such repository exists.'));
+ }
+ } catch (PhabricatorPolicyException $ex) {
+ if ($viewer->isLoggedIn()) {
+ return new PhabricatorVCSResponse(
+ 403,
+ pht('You do not have permission to access this repository.'));
+ } else {
+ return new PhabricatorVCSResponse(
+ 401,
+ pht('You must log in to access this repository.'));
+ }
+ }
+
+ $is_push = !$this->isReadOnlyRequest($repository);
+
+ switch ($repository->getServeOverHTTP()) {
+ case PhabricatorRepository::SERVE_READONLY:
+ if ($is_push) {
+ return new PhabricatorVCSResponse(
+ 403,
+ pht('This repository is read-only over HTTP.'));
+ }
+ break;
+ case PhabricatorRepository::SERVE_READWRITE:
+ if ($is_push) {
+ $can_push = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $repository,
+ DiffusionCapabilityPush::CAPABILITY);
+ if (!$can_push) {
+ if ($viewer->isLoggedIn()) {
+ return new PhabricatorVCSResponse(
+ 403,
+ pht('You do not have permission to push to this repository.'));
+ } else {
+ return new PhabricatorVCSResponse(
+ 401,
+ pht('You must log in to push to this repository.'));
+ }
+ }
+ }
+ break;
+ case PhabricatorRepository::SERVE_OFF:
+ default:
+ return new PhabricatorVCSResponse(
+ 403,
+ pht('This repository is not available over HTTP.'));
+ }
+
+ return new PhabricatorVCSResponse(
+ 999,
+ pht('TODO: Implement meaningful responses.'));
+ }
+
+ private function isReadOnlyRequest(
+ PhabricatorRepository $repository) {
+ $request = $this->getRequest();
+
+ // TODO: This implementation is safe by default, but very incomplete.
+
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $service = $request->getStr('service');
+ // 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;
+ }
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+ $cmd = $request->getStr('cmd');
+ switch ($cmd) {
+ case 'capabilities':
+ return true;
+ default:
+ return false;
+ }
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SUBVERSION:
+ break;
+ }
+
+ return false;
+ }
+
public function willProcessRequest(array $data) {
if (isset($data['callsign'])) {
$drequest = DiffusionRequest::newFromAphrontRequestDictionary(
Index: src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php
===================================================================
--- /dev/null
+++ src/applications/diffusion/controller/DiffusionRepositoryDefaultController.php
@@ -0,0 +1,11 @@
+<?php
+
+final class DiffusionRepositoryDefaultController extends DiffusionController {
+
+ public function processRequest() {
+ // NOTE: This controller is just here to make sure we call
+ // willBeginExecution() on any /diffusion/X/ URI, so we can intercept
+ // `git`, `hg` and `svn` HTTP protocol requests.
+ return new Aphront404Response();
+ }
+}
Index: src/applications/repository/response/PhabricatorVCSResponse.php
===================================================================
--- /dev/null
+++ src/applications/repository/response/PhabricatorVCSResponse.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * In Git, there appears to be no way to send a message which will be output
+ * by `git clone http://...`, although the response code is visible.
+ *
+ * In Mercurial, the HTTP status response message is printed to the console, so
+ * we send human-readable text there.
+ *
+ * In Subversion, we can get it to print a custom message if we send an
+ * invalid/unknown response code, although the output is ugly and difficult
+ * to read. For known codes like 404, it prints a canned message.
+ *
+ * All VCS binaries ignore the response body; we include it only for
+ * completeness.
+ */
+final class PhabricatorVCSResponse extends AphrontResponse {
+
+ private $code;
+ private $message;
+
+ public function __construct($code, $message) {
+ $this->code = $code;
+
+ $message = head(phutil_split_lines($message));
+ $this->message = $message;
+ }
+
+ public function getMessage() {
+ return $this->message;
+ }
+
+ public function buildResponseString() {
+ return $this->code.' '.$this->message;
+ }
+
+ public function getHeaders() {
+ $headers = array();
+
+ if ($this->getHTTPResponseCode() == 401) {
+ $headers[] = array(
+ 'WWW-Authenticate',
+ 'Basic realm="Phabricator Repositories"',
+ );
+ }
+
+ return $headers;
+ }
+
+ public function getCacheHeaders() {
+ return array();
+ }
+
+ public function getHTTPResponseCode() {
+ return $this->code;
+ }
+
+ public function getHTTPResponseMessage() {
+ return $this->message;
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Nov 17, 4:57 AM (1 d, 56 m ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6720183
Default Alt Text
D7417.diff (14 KB)
Attached To
Mode
D7417: Accept and route VCS HTTP requests
Attached
Detach File
Event Timeline
Log In to Comment