private function assertSameSubversionMessages($string, array $structs) { $proto = new DiffusionSubversionWireProtocol(); // Verify that the wire message parses into the structs. $messages = $proto->writeData($string); $messages = ipull($messages, 'structure'); $this->assertEqual($structs, $messages, 'parse<' . $string . '>'); // Verify that the structs serialize into the wire message. $serial = array(); foreach ($structs as $struct) { $serial[] = $proto->serializeStruct($struct); } $serial = implode('', $serial); $this->assertEqual($string, $serial, 'serialize<' . $string . '>'); }
protected function identifyRepository() { // NOTE: In SVN, we need to read the first few protocol frames before we // can determine which repository the user is trying to access. We're // going to peek at the data on the wire to identify the repository. $io_channel = $this->getIOChannel(); // Before the client will send us the first protocol frame, we need to send // it a connection frame with server capabilities. To figure out the // correct frame we're going to start `svnserve`, read the frame from it, // send it to the client, then kill the subprocess. // TODO: This is pretty inelegant and the protocol frame will change very // rarely. We could cache it if we can find a reasonable way to dirty the // cache. $command = csprintf('svnserve -t'); $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command); $future = new ExecFuture('%C', $command); $exec_channel = new PhutilExecChannel($future); $exec_protocol = new DiffusionSubversionWireProtocol(); while (true) { PhutilChannel::waitForAny(array($exec_channel)); $exec_channel->update(); $exec_message = $exec_channel->read(); if ($exec_message !== null) { $messages = $exec_protocol->writeData($exec_message); if ($messages) { $message = head($messages); $raw = $message['raw']; // Write the greeting frame to the client. $io_channel->write($raw); // Kill the subprocess. $future->resolveKill(); break; } } if (!$exec_channel->isOpenForReading()) { throw new Exception(pht('%s subprocess exited before emitting a protocol frame.', 'svnserve')); } } $io_protocol = new DiffusionSubversionWireProtocol(); while (true) { PhutilChannel::waitForAny(array($io_channel)); $io_channel->update(); $in_message = $io_channel->read(); if ($in_message !== null) { $this->peekBuffer .= $in_message; if (strlen($this->peekBuffer) > 1024 * 1024) { throw new Exception(pht('Client transmitted more than 1MB of data without transmitting ' . 'a recognizable protocol frame.')); } $messages = $io_protocol->writeData($in_message); if ($messages) { $message = head($messages); $struct = $message['structure']; // This is the: // // ( version ( cap1 ... ) url ... ) // // The `url` allows us to identify the repository. $uri = $struct[2]['value']; $path = $this->getPathFromSubversionURI($uri); return $this->loadRepositoryWithPath($path, PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); } } if (!$io_channel->isOpenForReading()) { throw new Exception(pht('Client closed connection before sending a complete protocol ' . 'frame.')); } // If the client has disconnected, kill the subprocess and bail. if (!$io_channel->isOpenForWriting()) { throw new Exception(pht('Client closed connection before receiving response.')); } } }