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;
 }