Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/protocol/DiffusionGitWireProtocol.php
- This file was added.
| <?php | |||||
| final class DiffusionGitWireProtocol extends Phobject { | |||||
| private $buffer = ''; | |||||
| private $state = 'head'; | |||||
| private $expectBytes; | |||||
| private $expectPacks; | |||||
| private $packType; | |||||
| private $packSize; | |||||
| private $packShift; | |||||
| public function writeData($data) { | |||||
| $this->buffer .= $data; | |||||
| while (true) { | |||||
| if ($this->state == 'head') { | |||||
| if (strlen($this->buffer) < 4) { | |||||
| break; | |||||
| } | |||||
| $head = substr($this->buffer, 0, 4); | |||||
| $this->buffer = substr($this->buffer, 4); | |||||
| if ($head == 'PACK') { | |||||
| $this->state = 'pack-version'; | |||||
| continue; | |||||
| } | |||||
| if (!preg_match('/^[a-f0-9]{4}\z/', $head)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Expected Git wire protocol frame length header (four hex '. | |||||
| 'characters, like "02af") but got "%s".', | |||||
| $head)); | |||||
| } | |||||
| $bytes = hexdec($head); | |||||
| // NOTE: This header is documented as containing the length of the | |||||
| // "rest of the line", but in practice it appears to include its own | |||||
| // length. | |||||
| if ($bytes >= 4) { | |||||
| $this->expectBytes = ($bytes - 4); | |||||
| } else if (!$bytes) { | |||||
| $this->expectBytes = $bytes; | |||||
| } else { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Expected Git wire protocol frame length header with valid '. | |||||
| 'value (0, or >= 4 bytes) but got invalid value ("%s").', | |||||
| $bytes)); | |||||
| } | |||||
| $this->state = 'body'; | |||||
| } else if ($this->state == 'body') { | |||||
| if (strlen($this->buffer) < $this->expectBytes) { | |||||
| break; | |||||
| } | |||||
| $body = substr($this->buffer, 0, $this->expectBytes); | |||||
| $this->buffer = substr($this->buffer, $this->expectBytes); | |||||
| $this->state = 'head'; | |||||
| } else if ($this->state == 'pack-version') { | |||||
| if (strlen($this->buffer) < 4) { | |||||
| break; | |||||
| } | |||||
| $version = substr($this->buffer, 0, 4); | |||||
| $this->buffer = substr($this->buffer, 4); | |||||
| $expect = "\x00\x00\x00\x02"; | |||||
| if ($version !== $expect) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Expected Git wire protocol packfile version 2 (four binary '. | |||||
| 'bytes, "%s") but got "%s".', | |||||
| bin2hex($expect), | |||||
| bin2hex($version))); | |||||
| } | |||||
| $this->state = 'pack-length'; | |||||
| } else if ($this->state == 'pack-length') { | |||||
| if (strlen($this->buffer) < 4) { | |||||
| break; | |||||
| } | |||||
| $length = substr($this->buffer, 0, 4); | |||||
| $this->buffer = substr($this->buffer, 4); | |||||
| $value = head(unpack('N', $length)); | |||||
| if (!$value) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Expected Git wire protocol pack count (four binary '. | |||||
| 'bytes) but got "%s".', | |||||
| bin2hex($length))); | |||||
| } | |||||
| $this->expectPacks = $value; | |||||
| $this->state = 'pack-head'; | |||||
| } else if ($this->state == 'pack-head') { | |||||
| if (!$this->expectPacks) { | |||||
| $this->state = 'pack-tail'; | |||||
| continue; | |||||
| } | |||||
| if (!strlen($this->buffer)) { | |||||
| break; | |||||
| } | |||||
| $this->expectPacks--; | |||||
| $byte = substr($this->buffer, 0, 1); | |||||
| $this->buffer = substr($this->buffer, 1); | |||||
| $byte = ord($byte); | |||||
| $this->packType = ($byte >> 4) & 7; | |||||
| $this->packSize = ($byte & 15); | |||||
| $this->packShift = 4; | |||||
| if ($byte & 0x80) { | |||||
| $this->state = 'pack-head-more'; | |||||
| } else { | |||||
| $this->state = 'pack-body'; | |||||
| } | |||||
| } else if ($this->state == 'pack-head-more') { | |||||
| if (!strlen($this->buffer)) { | |||||
| break; | |||||
| } | |||||
| $byte = substr($this->buffer, 0, 1); | |||||
| $this->buffer = substr($this->buffer, 1); | |||||
| $byte = ord($byte); | |||||
| $this->packSize += ($byte & 0x7F) << $this->packShift; | |||||
| $this->packShift += 7; | |||||
| if ($byte & 0x80) { | |||||
| $this->state = 'pack-head-more'; | |||||
| } else { | |||||
| $this->state = 'pack-body'; | |||||
| } | |||||
| } else if ($this->state == 'pack-body') { | |||||
| $available = strlen($this->buffer); | |||||
| $expect = $this->packSize; | |||||
| $read_bytes = min($available, $expect); | |||||
| if ($read_bytes) { | |||||
| $pack_data = substr($this->buffer, 0, $read_bytes); | |||||
| $this->buffer = substr($this->buffer, $read_bytes); | |||||
| $this->packSize -= $read_bytes; | |||||
| } | |||||
| if (!$this->expectBytes) { | |||||
| $this->state = 'pack-head'; | |||||
| } | |||||
| } else if ($this->state == 'pack-tail') { | |||||
| if (strlen($this->buffer) < 20) { | |||||
| break; | |||||
| } | |||||
| $sha1 = substr($this->buffer, 0, 20); | |||||
| $this->buffer = substr($this->buffer, 20); | |||||
| $this->state = 'head'; | |||||
| } | |||||
| } | |||||
| return $data; | |||||
| } | |||||
| } | |||||