public function willWriteMessageCallback(PhabricatorSSHPassthruCommand $command, $message) { $command = $message['command']; // Check if this is a readonly command. $is_readonly = false; if ($command == 'batch') { $cmds = idx($message['arguments'], 'cmds'); if (DiffusionMercurialWireProtocol::isReadOnlyBatchCommand($cmds)) { $is_readonly = true; } } else { if (DiffusionMercurialWireProtocol::isReadOnlyCommand($command)) { $is_readonly = true; } } if (!$is_readonly) { $this->requireWriteAccess(); $this->didSeeWrite = true; } $raw_message = $message['raw']; if ($command == 'capabilities') { $raw_message = DiffusionMercurialWireProtocol::filterBundle2Capability($raw_message); } // If we're good, return the raw message data. return $raw_message; }
public function testFilteringBundle2Capability() { // this was the result of running 'capabilities' over // `hg serve --stdio` on my systems with Mercurial 3.5.1, 2.6.2 $capabilities_with_bundle2_hg_351 = 'lookup changegroupsubset branchmap pushkey ' . 'known getbundle unbundlehash batch stream ' . 'bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512' . '%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0A' . 'hgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps ' . 'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024'; $capabilities_without_bundle2_hg_351 = 'lookup changegroupsubset branchmap pushkey ' . 'known getbundle unbundlehash batch stream ' . 'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024'; $capabilities_hg_262 = 'lookup changegroupsubset branchmap pushkey ' . 'known getbundle unbundlehash batch stream ' . 'unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 largefiles=serve'; $cases = array(array('name' => pht('Filter bundle2 from Mercurial 3.5.1'), 'input' => $capabilities_with_bundle2_hg_351, 'expect' => $capabilities_without_bundle2_hg_351), array('name' => pht('Filter bundle does not affect Mercurial 2.6.2'), 'input' => $capabilities_hg_262, 'expect' => $capabilities_hg_262)); foreach ($cases as $case) { $actual = DiffusionMercurialWireProtocol::filterBundle2Capability($case['input']); $this->assertEqual($case['expect'], $actual, $case['name']); } }
private function formatMercurialArguments($command, array $arguments) { $spec = DiffusionMercurialWireProtocol::getCommandArgs($command); $out = array(); // Mercurial takes normal arguments like this: // // name <length(value)> // value $has_star = false; foreach ($spec as $arg_key) { if ($arg_key == '*') { $has_star = true; continue; } if (isset($arguments[$arg_key])) { $value = $arguments[$arg_key]; $size = strlen($value); $out[] = "{$arg_key} {$size}\n{$value}"; unset($arguments[$arg_key]); } } if ($has_star) { // Mercurial takes arguments for variable argument lists roughly like // this: // // * <count(args)> // argname1 <length(argvalue1)> // argvalue1 // argname2 <length(argvalue2)> // argvalue2 $count = count($arguments); $out[] = "* {$count}\n"; foreach ($arguments as $key => $value) { if (in_array($key, $spec)) { // We already added this argument above, so skip it. continue; } $size = strlen($value); $out[] = "{$key} {$size}\n{$value}"; } } return implode('', $out); }
protected function decodeStream($data) { $this->buffer .= $data; $out = array(); $messages = array(); while (true) { if ($this->state == 'command') { $this->initializeState(); // We're reading a command. It looks like: // // <command> $line = $this->readProtocolLine(); if ($line === null) { break; } $this->command = $line; $this->state = 'arguments'; } else { if ($this->state == 'arguments') { // Check if we're still waiting for arguments. $args = DiffusionMercurialWireProtocol::getCommandArgs($this->command); $have = array_select_keys($this->arguments, $args); if (count($have) == count($args)) { // We have all the arguments. Emit a message and read the next // command. $messages[] = $this->newMessageAndResetState(); } else { // We're still reading arguments. They can either look like: // // <name> <length(value)> // <value> // ... // // ...or like this: // // * <count> // <name1> <length(value1)> // <value1> // ... $line = $this->readProtocolLine(); if ($line === null) { break; } list($arg, $size) = explode(' ', $line, 2); $size = (int) $size; if ($arg != '*') { $this->expectBytes = $size; $this->argumentName = $arg; $this->state = 'value'; } else { $this->arguments['*'] = array(); $this->expectArgumentCount = $size; $this->state = 'argv'; } } } else { if ($this->state == 'value' || $this->state == 'argv-value') { // We're reading the value of an argument. We just need to wait for // the right number of bytes to show up. $bytes = $this->readProtocolBytes(); if ($bytes === null) { break; } if ($this->state == 'argv-value') { $this->arguments['*'][$this->argumentName] = $bytes; $this->state = 'argv'; } else { $this->arguments[$this->argumentName] = $bytes; $this->state = 'arguments'; } } else { if ($this->state == 'argv') { // We're reading a variable number of arguments. We need to wait for // the arguments to arrive. if ($this->expectArgumentCount) { $line = $this->readProtocolLine(); if ($line === null) { break; } list($arg, $size) = explode(' ', $line, 2); $size = (int) $size; $this->expectBytes = $size; $this->argumentName = $arg; $this->state = 'argv-value'; $this->expectArgumentCount--; } else { $this->state = 'arguments'; } } else { if ($this->state == 'data-length') { $line = $this->readProtocolLine(); if ($line === null) { break; } $this->expectBytes = (int) $line; if (!$this->expectBytes) { $messages[] = $this->newDataMessage(''); $this->initializeState(); } else { $this->state = 'data-bytes'; } } else { if ($this->state == 'data-bytes') { $bytes = substr($this->buffer, 0, $this->expectBytes); $this->buffer = substr($this->buffer, strlen($bytes)); $this->expectBytes -= strlen($bytes); $messages[] = $this->newDataMessage($bytes); if (!$this->expectBytes) { // We've finished reading this chunk, so go read the next chunk. $this->state = 'data-length'; } else { // We're waiting for more data, and have read everything available // to us so far. break; } } else { throw new Exception("Bad parser state '{$this->state}'!"); } } } } } } } return $messages; }