Page MenuHomePhabricator

D7417.id16709.diff
No OneTemporary

D7417.id16709.diff

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',
@@ -1866,6 +1867,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',
@@ -2690,6 +2692,7 @@
'DiffusionRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'DiffusionRepositoryController' => 'DiffusionController',
'DiffusionRepositoryCreateController' => 'DiffusionRepositoryEditController',
+ 'DiffusionRepositoryDefaultController' => 'DiffusionController',
'DiffusionRepositoryEditActionsController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditActivateController' => 'DiffusionRepositoryEditController',
'DiffusionRepositoryEditBasicController' => 'DiffusionRepositoryEditController',
@@ -4200,6 +4203,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

Mime Type
text/plain
Expires
Thu, Jun 27, 10:33 PM (3 d, 12 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6295988
Default Alt Text
D7417.id16709.diff (14 KB)

Event Timeline