Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php b/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php
index 934969f792..e5645859a4 100644
--- a/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php
+++ b/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php
@@ -1,188 +1,192 @@
<?php
final class DiffusionSubversionWireProtocol extends Phobject {
private $buffer = '';
private $state = 'item';
private $expectBytes = 0;
private $byteBuffer = '';
private $stack = array();
private $list = array();
private $raw = '';
private function pushList() {
$this->stack[] = $this->list;
$this->list = array();
}
private function popList() {
$list = $this->list;
$this->list = array_pop($this->stack);
return $list;
}
private function pushItem($item, $type) {
$this->list[] = array(
'type' => $type,
'value' => $item,
);
}
public function writeData($data) {
$this->buffer .= $data;
$messages = array();
while (true) {
if ($this->state == 'item') {
$match = null;
$result = null;
$buf = $this->buffer;
if (preg_match('/^([a-z][a-z0-9-]*)\s/i', $buf, $match)) {
$this->pushItem($match[1], 'word');
} else if (preg_match('/^(\d+)\s/', $buf, $match)) {
$this->pushItem((int)$match[1], 'number');
} else if (preg_match('/^(\d+):/', $buf, $match)) {
// NOTE: The "+ 1" includes the space after the string.
$this->expectBytes = (int)$match[1] + 1;
$this->state = 'bytes';
} else if (preg_match('/^(\\()\s/', $buf, $match)) {
$this->pushList();
} else if (preg_match('/^(\\))\s/', $buf, $match)) {
$list = $this->popList();
if ($this->stack) {
$this->pushItem($list, 'list');
} else {
$result = $list;
}
} else {
$match = false;
}
if ($match !== false) {
$this->raw .= substr($this->buffer, 0, strlen($match[0]));
$this->buffer = substr($this->buffer, strlen($match[0]));
if ($result !== null) {
$messages[] = array(
'structure' => $list,
'raw' => $this->raw,
);
$this->raw = '';
}
} else {
// No matches yet, wait for more data.
break;
}
} else if ($this->state == 'bytes') {
$new_data = substr($this->buffer, 0, $this->expectBytes);
+ if (!strlen($new_data)) {
+ // No more bytes available yet, wait for more data.
+ break;
+ }
$this->buffer = substr($this->buffer, strlen($new_data));
$this->expectBytes -= strlen($new_data);
$this->raw .= $new_data;
$this->byteBuffer .= $new_data;
if (!$this->expectBytes) {
$this->state = 'byte-space';
// Strip off the terminal space.
$this->pushItem(substr($this->byteBuffer, 0, -1), 'string');
$this->byteBuffer = '';
$this->state = 'item';
}
} else {
throw new Exception("Invalid state '{$this->state}'!");
}
}
return $messages;
}
/**
* Convert a parsed command struct into a wire protocol string.
*/
public function serializeStruct(array $struct) {
$out = array();
$out[] = '( ';
foreach ($struct as $item) {
$value = $item['value'];
$type = $item['type'];
switch ($type) {
case 'word':
$out[] = $value;
break;
case 'number':
$out[] = $value;
break;
case 'string':
$out[] = strlen($value).':'.$value;
break;
case 'list':
$out[] = self::serializeStruct($value);
break;
default:
throw new Exception("Unknown SVN wire protocol structure '{$type}'!");
}
if ($type != 'list') {
$out[] = ' ';
}
}
$out[] = ') ';
return implode('', $out);
}
public function isReadOnlyCommand(array $struct) {
if (empty($struct[0]['type']) || ($struct[0]['type'] != 'word')) {
// This isn't what we expect; fail defensively.
throw new Exception(
pht("Unexpected command structure, expected '( word ... )'."));
}
switch ($struct[0]['value']) {
// Authentication command set.
case 'EXTERNAL':
// The "Main" command set. Some of the commands in this command set are
// mutation commands, and are omitted from this list.
case 'reparent':
case 'get-latest-rev':
case 'get-dated-rev':
case 'rev-proplist':
case 'rev-prop':
case 'get-file':
case 'get-dir':
case 'check-path':
case 'stat':
case 'update':
case 'get-mergeinfo':
case 'switch':
case 'status':
case 'diff':
case 'log':
case 'get-file-revs':
case 'get-locations':
// The "Report" command set. These are not actually mutation
// operations, they just define a request for information.
case 'set-path':
case 'delete-path':
case 'link-path':
case 'finish-report':
case 'abort-report':
// These are used to report command results.
case 'success':
case 'failure':
// If we get here, we've matched some known read-only command.
return true;
default:
// Anything else isn't a known read-only command, so require write
// access to use it.
break;
}
return false;
}
}
diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php b/src/applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php
index 28b6473eb8..088f7222ed 100644
--- a/src/applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php
+++ b/src/applications/diffusion/protocol/__tests__/DiffusionSubversionWireProtocolTestCase.php
@@ -1,80 +1,105 @@
<?php
final class DiffusionSubversionWireProtocolTestCase
extends PhabricatorTestCase {
public function testSubversionWireProtocolParser() {
$this->assertSameSubversionMessages(
'( ) ',
array(
array(
),
));
$this->assertSameSubversionMessages(
'( duck 5:quack 42 ( item1 item2 ) ) ',
array(
array(
array(
'type' => 'word',
'value' => 'duck',
),
array(
'type' => 'string',
'value' => 'quack',
),
array(
'type' => 'number',
'value' => 42,
),
array(
'type' => 'list',
'value' => array(
array(
'type' => 'word',
'value' => 'item1',
),
array(
'type' => 'word',
'value' => 'item2',
),
),
),
),
));
$this->assertSameSubversionMessages(
'( msg1 ) ( msg2 ) ',
array(
array(
array(
'type' => 'word',
'value' => 'msg1',
),
),
array(
array(
'type' => 'word',
'value' => 'msg2',
),
),
));
}
+ public function testSubversionWireProtocolPartialFrame() {
+ $proto = new DiffusionSubversionWireProtocol();
+
+ // This is primarily a test that we don't hang when we write() a frame
+ // which straddles a string boundary.
+ $msg1 = $proto->writeData('( duck 5:qu');
+ $msg2 = $proto->writeData('ack ) ');
+
+ $this->assertEqual(array(), ipull($msg1, 'structure'));
+ $this->assertEqual(
+ array(
+ array(
+ array(
+ 'type' => 'word',
+ 'value' => 'duck',
+ ),
+ array(
+ 'type'=> 'string',
+ 'value' => 'quack',
+ ),
+ ),
+ ),
+ ipull($msg2, 'structure'));
+ }
+
private function assertSameSubversionMessages($string, array $structs) {
$proto = new DiffusionSubversionWireProtocol();
// Verify that the wire message parses into the structs.
$messages = $proto->writeData($string);
$messages = ipull($messages, 'structure');
$this->assertEqual($structs, $messages, 'parse<'.$string.'>');
// Verify that the structs serialize into the wire message.
$serial = array();
foreach ($structs as $struct) {
$serial[] = $proto->serializeStruct($struct);
}
$serial = implode('', $serial);
$this->assertEqual($string, $serial, 'serialize<'.$string.'>');
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Dec 3, 7:16 AM (8 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
433204
Default Alt Text
(8 KB)

Event Timeline