Path: blob/master/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php
12242 views
<?php12final class DiffusionMercurialWireClientSSHProtocolChannel3extends PhutilProtocolChannel {45private $buffer = '';6private $state = 'command';7private $expectArgumentCount;8private $argumentName;9private $expectBytes;10private $command;11private $arguments;12private $raw;1314protected function encodeMessage($message) {15return $message;16}1718private function initializeState($last_command = null) {19if ($last_command == 'unbundle') {20$this->command = '<raw-data>';21$this->state = 'data-length';22} else {23$this->state = 'command';24}25$this->expectArgumentCount = null;26$this->expectBytes = null;27$this->command = null;28$this->argumentName = null;29$this->arguments = array();30$this->raw = '';31}3233private function readProtocolLine() {34$pos = strpos($this->buffer, "\n");3536if ($pos === false) {37return null;38}3940$line = substr($this->buffer, 0, $pos);4142$this->raw .= $line."\n";43$this->buffer = substr($this->buffer, $pos + 1);4445return $line;46}4748private function readProtocolBytes() {49if (strlen($this->buffer) < $this->expectBytes) {50return null;51}5253$bytes = substr($this->buffer, 0, $this->expectBytes);54$this->raw .= $bytes;55$this->buffer = substr($this->buffer, $this->expectBytes);5657return $bytes;58}5960private function newMessageAndResetState() {61$message = array(62'command' => $this->command,63'arguments' => $this->arguments,64'raw' => $this->raw,65);66$this->initializeState($this->command);67return $message;68}6970private function newDataMessage($bytes) {71$message = array(72'command' => '<raw-data>',73'raw' => strlen($bytes)."\n".$bytes,74);75return $message;76}7778protected function decodeStream($data) {79$this->buffer .= $data;8081$out = array();82$messages = array();8384while (true) {85if ($this->state == 'command') {86$this->initializeState();8788// We're reading a command. It looks like:89//90// <command>9192$line = $this->readProtocolLine();93if ($line === null) {94break;95}9697$this->command = $line;98$this->state = 'arguments';99} else if ($this->state == 'arguments') {100101// Check if we're still waiting for arguments.102$args = DiffusionMercurialWireProtocol::getCommandArgs($this->command);103$have = array_select_keys($this->arguments, $args);104if (count($have) == count($args)) {105// We have all the arguments. Emit a message and read the next106// command.107$messages[] = $this->newMessageAndResetState();108} else {109// We're still reading arguments. They can either look like:110//111// <name> <length(value)>112// <value>113// ...114//115// ...or like this:116//117// * <count>118// <name1> <length(value1)>119// <value1>120// ...121122$line = $this->readProtocolLine();123if ($line === null) {124break;125}126127list($arg, $size) = explode(' ', $line, 2);128$size = (int)$size;129130if ($arg != '*') {131$this->expectBytes = $size;132$this->argumentName = $arg;133$this->state = 'value';134} else {135$this->arguments['*'] = array();136$this->expectArgumentCount = $size;137$this->state = 'argv';138}139}140} else if ($this->state == 'value' || $this->state == 'argv-value') {141142// We're reading the value of an argument. We just need to wait for143// the right number of bytes to show up.144145$bytes = $this->readProtocolBytes();146if ($bytes === null) {147break;148}149150if ($this->state == 'argv-value') {151$this->arguments['*'][$this->argumentName] = $bytes;152$this->state = 'argv';153} else {154$this->arguments[$this->argumentName] = $bytes;155$this->state = 'arguments';156}157158159} else if ($this->state == 'argv') {160161// We're reading a variable number of arguments. We need to wait for162// the arguments to arrive.163164if ($this->expectArgumentCount) {165$line = $this->readProtocolLine();166if ($line === null) {167break;168}169170list($arg, $size) = explode(' ', $line, 2);171$size = (int)$size;172173$this->expectBytes = $size;174$this->argumentName = $arg;175$this->state = 'argv-value';176177$this->expectArgumentCount--;178} else {179$this->state = 'arguments';180}181} else if ($this->state == 'data-length') {182183// We're reading the length of a chunk of raw data. It looks like184// this:185//186// <length-in-bytes>\n187//188// The length is human-readable text (for example, "4096"), and189// may be 0.190191$line = $this->readProtocolLine();192if ($line === null) {193break;194}195$this->expectBytes = (int)$line;196if (!$this->expectBytes) {197$messages[] = $this->newDataMessage('');198$this->initializeState();199} else {200$this->state = 'data-bytes';201}202} else if ($this->state == 'data-bytes') {203204// We're reading some known, nonzero number of raw bytes of data.205206// If we don't have any more bytes on the buffer yet, just bail:207// otherwise, we'll emit a pointless and possibly harmful 0-byte data208// frame. See T13036 for discussion.209if (!strlen($this->buffer)) {210break;211}212213$bytes = substr($this->buffer, 0, $this->expectBytes);214$this->buffer = substr($this->buffer, strlen($bytes));215$this->expectBytes -= strlen($bytes);216217// NOTE: We emit a data frame as soon as we read some data. This can218// cause us to repackage frames: for example, if we receive one large219// frame slowly, we may emit it as several smaller frames. In theory220// this is good; in practice, Mercurial never seems to select a frame221// size larger than 4096 bytes naturally and this may be more222// complexity and trouble than it is worth. See T13036.223224$messages[] = $this->newDataMessage($bytes);225226if (!$this->expectBytes) {227// We've finished reading this chunk, so go read the next chunk.228$this->state = 'data-length';229} else {230// We're waiting for more data, and have read everything available231// to us so far.232break;233}234} else {235throw new Exception(pht("Bad parser state '%s'!", $this->state));236}237}238239return $messages;240}241242}243244245