Path: blob/master/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php
12242 views
<?php12final class DiffusionSubversionWireProtocol extends Phobject {34private $buffer = '';5private $state = 'item';6private $expectBytes = 0;7private $byteBuffer = '';8private $stack = array();9private $list = array();10private $raw = '';1112private function pushList() {13$this->stack[] = $this->list;14$this->list = array();15}1617private function popList() {18$list = $this->list;19$this->list = array_pop($this->stack);20return $list;21}2223private function pushItem($item, $type) {24$this->list[] = array(25'type' => $type,26'value' => $item,27);28}2930public function writeData($data) {31$this->buffer .= $data;3233$messages = array();34while (true) {35if ($this->state == 'space') {36// Consume zero or more extra spaces after matching an item. The37// protocol requires at least one space, but allows more than one.3839$matches = null;40if (!preg_match('/^(\s*)\S/', $this->buffer, $matches)) {41// Wait for more data.42break;43}4445// We have zero or more spaces and then some other character, so throw46// the spaces away and continue parsing frames.47if (strlen($matches[1])) {48$this->buffer = substr($this->buffer, strlen($matches[1]));49}5051$this->state = 'item';52} else if ($this->state == 'item') {53$match = null;54$result = null;55$buf = $this->buffer;56if (preg_match('/^([a-z][a-z0-9-]*)\s/i', $buf, $match)) {57$this->pushItem($match[1], 'word');58} else if (preg_match('/^(\d+)\s/', $buf, $match)) {59$this->pushItem((int)$match[1], 'number');60} else if (preg_match('/^(\d+):/', $buf, $match)) {61// NOTE: The "+ 1" includes the space after the string.62$this->expectBytes = (int)$match[1] + 1;63$this->state = 'bytes';64} else if (preg_match('/^(\\()\s/', $buf, $match)) {65$this->pushList();66} else if (preg_match('/^(\\))\s/', $buf, $match)) {67$list = $this->popList();68if ($this->stack) {69$this->pushItem($list, 'list');70} else {71$result = $list;72}73} else {74$match = false;75}7677if ($match !== false) {78$this->raw .= substr($this->buffer, 0, strlen($match[0]));79$this->buffer = substr($this->buffer, strlen($match[0]));8081if ($result !== null) {82$messages[] = array(83'structure' => $list,84'raw' => $this->raw,85);86$this->raw = '';87}8889// Consume any extra whitespace after an item. If we're in the90// "bytes" state, we aren't looking for whitespace.91if ($this->state == 'item') {92$this->state = 'space';93}94} else {95// No matches yet, wait for more data.96break;97}98} else if ($this->state == 'bytes') {99$new_data = substr($this->buffer, 0, $this->expectBytes);100if (!strlen($new_data)) {101// No more bytes available yet, wait for more data.102break;103}104$this->buffer = substr($this->buffer, strlen($new_data));105106$this->expectBytes -= strlen($new_data);107$this->raw .= $new_data;108$this->byteBuffer .= $new_data;109110if (!$this->expectBytes) {111$this->state = 'byte-space';112// Strip off the terminal space.113$this->pushItem(substr($this->byteBuffer, 0, -1), 'string');114$this->byteBuffer = '';115$this->state = 'space';116}117} else {118throw new Exception(pht("Invalid state '%s'!", $this->state));119}120}121122return $messages;123}124125/**126* Convert a parsed command struct into a wire protocol string.127*/128public function serializeStruct(array $struct) {129$out = array();130131$out[] = '( ';132foreach ($struct as $item) {133$value = $item['value'];134$type = $item['type'];135switch ($type) {136case 'word':137$out[] = $value;138break;139case 'number':140$out[] = $value;141break;142case 'string':143$out[] = strlen($value).':'.$value;144break;145case 'list':146$out[] = self::serializeStruct($value);147break;148default:149throw new Exception(150pht(151"Unknown SVN wire protocol structure '%s'!",152$type));153}154if ($type != 'list') {155$out[] = ' ';156}157}158$out[] = ') ';159160return implode('', $out);161}162163public function isReadOnlyCommand(array $struct) {164if (empty($struct[0]['type']) || ($struct[0]['type'] != 'word')) {165// This isn't what we expect; fail defensively.166throw new Exception(167pht(168"Unexpected command structure, expected '%s'.",169'( word ... )'));170}171172switch ($struct[0]['value']) {173// Authentication command set.174case 'EXTERNAL':175176// The "Main" command set. Some of the commands in this command set are177// mutation commands, and are omitted from this list.178case 'reparent':179case 'get-latest-rev':180case 'get-dated-rev':181case 'rev-proplist':182case 'rev-prop':183case 'get-file':184case 'get-dir':185case 'check-path':186case 'stat':187case 'update':188case 'get-mergeinfo':189case 'switch':190case 'status':191case 'diff':192case 'log':193case 'get-file-revs':194case 'get-locations':195196// The "Report" command set. These are not actually mutation197// operations, they just define a request for information.198case 'set-path':199case 'delete-path':200case 'link-path':201case 'finish-report':202case 'abort-report':203204// These are used to report command results.205case 'success':206case 'failure':207208// If we get here, we've matched some known read-only command.209return true;210default:211// Anything else isn't a known read-only command, so require write212// access to use it.213break;214}215216return false;217}218219}220221222