Path: blob/master/src/applications/diffusion/protocol/DiffusionGitUploadPackWireProtocol.php
12242 views
<?php12final class DiffusionGitUploadPackWireProtocol3extends DiffusionGitWireProtocol {45private $readMode = 'length';6private $readBuffer;7private $readFrameLength;8private $readFrames = array();910private $readFrameMode = 'refs';11private $refFrames = array();1213private $readMessages = array();1415public function willReadBytes($bytes) {16if ($this->readBuffer === null) {17$this->readBuffer = new PhutilRope();18}19$buffer = $this->readBuffer;2021$buffer->append($bytes);2223while (true) {24$len = $buffer->getByteLength();25switch ($this->readMode) {26case 'length':27// We're expecting 4 bytes containing the length of the protocol28// frame as hexadecimal in ASCII text, like "01ab". Wait until we29// see at least 4 bytes on the wire.30if ($len < 4) {31if ($len > 0) {32$bytes = $this->peekBytes($len);33if (!preg_match('/^[0-9a-f]+\z/', $bytes)) {34throw new Exception(35pht(36'Bad frame length character in Git protocol ("%s"), '.37'expected a 4-digit hexadecimal value encoded as ASCII '.38'text.',39$bytes));40}41}4243// We can't make any more progress until we get enough bytes, so44// we're done with state processing.45break 2;46}4748$frame_length = $this->readBytes(4);49$frame_length = hexdec($frame_length);5051// Note that the frame length includes the 4 header bytes, so we52// usually expect a length of 5 or larger. Frames with length 053// are boundaries.54if ($frame_length === 0) {55$this->readFrames[] = $this->newProtocolFrame('null', '');56} else if ($frame_length >= 1 && $frame_length <= 3) {57throw new Exception(58pht(59'Encountered Git protocol frame with unexpected frame '.60'length (%s)!',61$frame_length));62} else {63$this->readFrameLength = $frame_length - 4;64$this->readMode = 'frame';65}6667break;68case 'frame':69// We're expecting a protocol frame of a specified length. Note that70// it is possible for a frame to have length 0.7172// We don't have enough bytes yet, so wait for more.73if ($len < $this->readFrameLength) {74break 2;75}7677if ($this->readFrameLength > 0) {78$bytes = $this->readBytes($this->readFrameLength);79} else {80$bytes = '';81}8283// Emit a protocol frame.84$this->readFrames[] = $this->newProtocolFrame('data', $bytes);85$this->readMode = 'length';86break;87}88}8990while (true) {91switch ($this->readFrameMode) {92case 'refs':93if (!$this->readFrames) {94break 2;95}9697foreach ($this->readFrames as $key => $frame) {98unset($this->readFrames[$key]);99100if ($frame['type'] === 'null') {101$ref_frames = $this->refFrames;102$this->refFrames = array();103104$ref_frames[] = $frame;105106$this->readMessages[] = $this->newProtocolRefMessage($ref_frames);107$this->readFrameMode = 'passthru';108break;109} else {110$this->refFrames[] = $frame;111}112}113114break;115case 'passthru':116if (!$this->readFrames) {117break 2;118}119120$this->readMessages[] = $this->newProtocolDataMessage(121$this->readFrames);122$this->readFrames = array();123124break;125}126}127128$wire = array();129foreach ($this->readMessages as $key => $message) {130$wire[] = $message;131unset($this->readMessages[$key]);132}133$wire = implode('', $wire);134135return $wire;136}137138public function willWriteBytes($bytes) {139return $bytes;140}141142private function readBytes($count) {143$buffer = $this->readBuffer;144145$bytes = $buffer->getPrefixBytes($count);146$buffer->removeBytesFromHead($count);147148return $bytes;149}150151private function peekBytes($count) {152$buffer = $this->readBuffer;153return $buffer->getPrefixBytes($count);154}155156private function newProtocolFrame($type, $bytes) {157return array(158'type' => $type,159'length' => strlen($bytes),160'bytes' => $bytes,161);162}163164private function newProtocolRefMessage(array $frames) {165$head_key = head_key($frames);166$last_key = last_key($frames);167168$capabilities = null;169$last_frame = null;170171$refs = array();172foreach ($frames as $key => $frame) {173$is_last = ($key === $last_key);174if ($is_last) {175// This is a "0000" frame at the end of the list of refs, so we pass176// it through unmodified after we figure out what the rest of the177// frames should look like, below.178$last_frame = $frame;179continue;180}181182$is_first = ($key === $head_key);183184// Otherwise, we expect a list of:185//186// <hash> <ref-name>\0<capabilities>187// <hash> <ref-name>188// ...189//190// See T13309. The end of this list (which may be empty if a repository191// does not have any refs) has a list of zero or more of these:192//193// shallow <hash>194//195// These entries are present if the repository is a shallow clone196// which was made with the "--depth" flag.197//198// Note that "shallow" frames do not advertise capabilities, and if199// a repository has only "shallow" frames, capabilities are never200// advertised.201202$bytes = $frame['bytes'];203$matches = array();204if ($is_first) {205$capabilities_pattern = '\0(?P<capabilities>[^\n]+)';206} else {207$capabilities_pattern = '';208}209210$ok = preg_match(211'('.212'^'.213'(?:'.214'(?P<hash>[0-9a-f]{40}) (?P<name>[^\0\n]+)'.$capabilities_pattern.215'|'.216'shallow (?P<shallow>[0-9a-f]{40})'.217')'.218'\n'.219'\z'.220')',221$bytes,222$matches);223224if (!$ok) {225if ($is_first) {226throw new Exception(227pht(228'Unexpected "git upload-pack" initial protocol frame: expected '.229'"<hash> <name>\0<capabilities>\n", or '.230'"shallow <hash>\n", got "%s".',231$bytes));232} else {233throw new Exception(234pht(235'Unexpected "git upload-pack" protocol frame: expected '.236'"<hash> <name>\n", or "shallow <hash>\n", got "%s".',237$bytes));238}239}240241if (isset($matches['shallow'])) {242$name = null;243$hash = $matches['shallow'];244$is_shallow = true;245} else {246$name = $matches['name'];247$hash = $matches['hash'];248$is_shallow = false;249}250251if (isset($matches['capabilities'])) {252$capabilities = $matches['capabilities'];253}254255$refs[] = array(256'hash' => $hash,257'name' => $name,258'shallow' => $is_shallow,259);260}261262$capabilities = DiffusionGitWireProtocolCapabilities::newFromWireFormat(263$capabilities);264265$ref_list = id(new DiffusionGitWireProtocolRefList())266->setCapabilities($capabilities);267268foreach ($refs as $ref) {269$wire_ref = id(new DiffusionGitWireProtocolRef())270->setHash($ref['hash']);271272if ($ref['shallow']) {273$wire_ref->setIsShallow(true);274} else {275$wire_ref->setName($ref['name']);276}277278$ref_list->addRef($wire_ref);279}280281// TODO: Here, we have a structured list of refs. In a future change,282// we are free to mutate the structure before flattening it back into283// wire format.284285$refs = $ref_list->getRefs();286287// Before we write the ref list, sort it for consistency with native288// Git output. We may have added, removed, or renamed refs and ended up289// with an out-of-order list.290291$refs = msortv($refs, 'newSortVector');292293// The first ref we send back includes the capabilities data. Note that if294// we send back no refs, we also don't send back capabilities! This is295// a little surprising, but is consistent with the native behavior of the296// protocol.297298// Likewise, we don't send back any capabilities if we're sending only299// "shallow" frames.300301$output = array();302$is_first = true;303foreach ($refs as $ref) {304$is_shallow = $ref->getIsShallow();305306if ($is_shallow) {307$result = sprintf(308"shallow %s\n",309$ref->getHash());310} else if ($is_first) {311$result = sprintf(312"%s %s\0%s\n",313$ref->getHash(),314$ref->getName(),315$ref_list->getCapabilities()->toWireFormat());316} else {317$result = sprintf(318"%s %s\n",319$ref->getHash(),320$ref->getName());321}322323$output[] = $this->newProtocolFrame('data', $result);324$is_first = false;325}326327$output[] = $last_frame;328329return $this->newProtocolDataMessage($output);330}331332private function newProtocolDataMessage(array $frames) {333$message = array();334335foreach ($frames as $frame) {336switch ($frame['type']) {337case 'null':338$message[] = '0000';339break;340case 'data':341$message[] = sprintf(342'%04x%s',343$frame['length'] + 4,344$frame['bytes']);345break;346}347}348349$message = implode('', $message);350351return $message;352}353354}355356357