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