Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/protocol/DiffusionSubversionWireProtocol.php
12242 views
1
<?php
2
3
final class DiffusionSubversionWireProtocol extends Phobject {
4
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 = '';
12
13
private function pushList() {
14
$this->stack[] = $this->list;
15
$this->list = array();
16
}
17
18
private function popList() {
19
$list = $this->list;
20
$this->list = array_pop($this->stack);
21
return $list;
22
}
23
24
private function pushItem($item, $type) {
25
$this->list[] = array(
26
'type' => $type,
27
'value' => $item,
28
);
29
}
30
31
public function writeData($data) {
32
$this->buffer .= $data;
33
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.
39
40
$matches = null;
41
if (!preg_match('/^(\s*)\S/', $this->buffer, $matches)) {
42
// Wait for more data.
43
break;
44
}
45
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]));
50
}
51
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;
73
}
74
} else {
75
$match = false;
76
}
77
78
if ($match !== false) {
79
$this->raw .= substr($this->buffer, 0, strlen($match[0]));
80
$this->buffer = substr($this->buffer, strlen($match[0]));
81
82
if ($result !== null) {
83
$messages[] = array(
84
'structure' => $list,
85
'raw' => $this->raw,
86
);
87
$this->raw = '';
88
}
89
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';
94
}
95
} else {
96
// No matches yet, wait for more data.
97
break;
98
}
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;
104
}
105
$this->buffer = substr($this->buffer, strlen($new_data));
106
107
$this->expectBytes -= strlen($new_data);
108
$this->raw .= $new_data;
109
$this->byteBuffer .= $new_data;
110
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';
117
}
118
} else {
119
throw new Exception(pht("Invalid state '%s'!", $this->state));
120
}
121
}
122
123
return $messages;
124
}
125
126
/**
127
* Convert a parsed command struct into a wire protocol string.
128
*/
129
public function serializeStruct(array $struct) {
130
$out = array();
131
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));
154
}
155
if ($type != 'list') {
156
$out[] = ' ';
157
}
158
}
159
$out[] = ') ';
160
161
return implode('', $out);
162
}
163
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 ... )'));
171
}
172
173
switch ($struct[0]['value']) {
174
// Authentication command set.
175
case 'EXTERNAL':
176
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':
196
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':
204
205
// These are used to report command results.
206
case 'success':
207
case 'failure':
208
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;
215
}
216
217
return false;
218
}
219
220
}
221
222