Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/ssh/DiffusionMercurialWireClientSSHProtocolChannel.php
12242 views
1
<?php
2
3
final class DiffusionMercurialWireClientSSHProtocolChannel
4
extends PhutilProtocolChannel {
5
6
private $buffer = '';
7
private $state = 'command';
8
private $expectArgumentCount;
9
private $argumentName;
10
private $expectBytes;
11
private $command;
12
private $arguments;
13
private $raw;
14
15
protected function encodeMessage($message) {
16
return $message;
17
}
18
19
private function initializeState($last_command = null) {
20
if ($last_command == 'unbundle') {
21
$this->command = '<raw-data>';
22
$this->state = 'data-length';
23
} else {
24
$this->state = 'command';
25
}
26
$this->expectArgumentCount = null;
27
$this->expectBytes = null;
28
$this->command = null;
29
$this->argumentName = null;
30
$this->arguments = array();
31
$this->raw = '';
32
}
33
34
private function readProtocolLine() {
35
$pos = strpos($this->buffer, "\n");
36
37
if ($pos === false) {
38
return null;
39
}
40
41
$line = substr($this->buffer, 0, $pos);
42
43
$this->raw .= $line."\n";
44
$this->buffer = substr($this->buffer, $pos + 1);
45
46
return $line;
47
}
48
49
private function readProtocolBytes() {
50
if (strlen($this->buffer) < $this->expectBytes) {
51
return null;
52
}
53
54
$bytes = substr($this->buffer, 0, $this->expectBytes);
55
$this->raw .= $bytes;
56
$this->buffer = substr($this->buffer, $this->expectBytes);
57
58
return $bytes;
59
}
60
61
private function newMessageAndResetState() {
62
$message = array(
63
'command' => $this->command,
64
'arguments' => $this->arguments,
65
'raw' => $this->raw,
66
);
67
$this->initializeState($this->command);
68
return $message;
69
}
70
71
private function newDataMessage($bytes) {
72
$message = array(
73
'command' => '<raw-data>',
74
'raw' => strlen($bytes)."\n".$bytes,
75
);
76
return $message;
77
}
78
79
protected function decodeStream($data) {
80
$this->buffer .= $data;
81
82
$out = array();
83
$messages = array();
84
85
while (true) {
86
if ($this->state == 'command') {
87
$this->initializeState();
88
89
// We're reading a command. It looks like:
90
//
91
// <command>
92
93
$line = $this->readProtocolLine();
94
if ($line === null) {
95
break;
96
}
97
98
$this->command = $line;
99
$this->state = 'arguments';
100
} else if ($this->state == 'arguments') {
101
102
// Check if we're still waiting for arguments.
103
$args = DiffusionMercurialWireProtocol::getCommandArgs($this->command);
104
$have = array_select_keys($this->arguments, $args);
105
if (count($have) == count($args)) {
106
// We have all the arguments. Emit a message and read the next
107
// command.
108
$messages[] = $this->newMessageAndResetState();
109
} else {
110
// We're still reading arguments. They can either look like:
111
//
112
// <name> <length(value)>
113
// <value>
114
// ...
115
//
116
// ...or like this:
117
//
118
// * <count>
119
// <name1> <length(value1)>
120
// <value1>
121
// ...
122
123
$line = $this->readProtocolLine();
124
if ($line === null) {
125
break;
126
}
127
128
list($arg, $size) = explode(' ', $line, 2);
129
$size = (int)$size;
130
131
if ($arg != '*') {
132
$this->expectBytes = $size;
133
$this->argumentName = $arg;
134
$this->state = 'value';
135
} else {
136
$this->arguments['*'] = array();
137
$this->expectArgumentCount = $size;
138
$this->state = 'argv';
139
}
140
}
141
} else if ($this->state == 'value' || $this->state == 'argv-value') {
142
143
// We're reading the value of an argument. We just need to wait for
144
// the right number of bytes to show up.
145
146
$bytes = $this->readProtocolBytes();
147
if ($bytes === null) {
148
break;
149
}
150
151
if ($this->state == 'argv-value') {
152
$this->arguments['*'][$this->argumentName] = $bytes;
153
$this->state = 'argv';
154
} else {
155
$this->arguments[$this->argumentName] = $bytes;
156
$this->state = 'arguments';
157
}
158
159
160
} else if ($this->state == 'argv') {
161
162
// We're reading a variable number of arguments. We need to wait for
163
// the arguments to arrive.
164
165
if ($this->expectArgumentCount) {
166
$line = $this->readProtocolLine();
167
if ($line === null) {
168
break;
169
}
170
171
list($arg, $size) = explode(' ', $line, 2);
172
$size = (int)$size;
173
174
$this->expectBytes = $size;
175
$this->argumentName = $arg;
176
$this->state = 'argv-value';
177
178
$this->expectArgumentCount--;
179
} else {
180
$this->state = 'arguments';
181
}
182
} else if ($this->state == 'data-length') {
183
184
// We're reading the length of a chunk of raw data. It looks like
185
// this:
186
//
187
// <length-in-bytes>\n
188
//
189
// The length is human-readable text (for example, "4096"), and
190
// may be 0.
191
192
$line = $this->readProtocolLine();
193
if ($line === null) {
194
break;
195
}
196
$this->expectBytes = (int)$line;
197
if (!$this->expectBytes) {
198
$messages[] = $this->newDataMessage('');
199
$this->initializeState();
200
} else {
201
$this->state = 'data-bytes';
202
}
203
} else if ($this->state == 'data-bytes') {
204
205
// We're reading some known, nonzero number of raw bytes of data.
206
207
// If we don't have any more bytes on the buffer yet, just bail:
208
// otherwise, we'll emit a pointless and possibly harmful 0-byte data
209
// frame. See T13036 for discussion.
210
if (!strlen($this->buffer)) {
211
break;
212
}
213
214
$bytes = substr($this->buffer, 0, $this->expectBytes);
215
$this->buffer = substr($this->buffer, strlen($bytes));
216
$this->expectBytes -= strlen($bytes);
217
218
// NOTE: We emit a data frame as soon as we read some data. This can
219
// cause us to repackage frames: for example, if we receive one large
220
// frame slowly, we may emit it as several smaller frames. In theory
221
// this is good; in practice, Mercurial never seems to select a frame
222
// size larger than 4096 bytes naturally and this may be more
223
// complexity and trouble than it is worth. See T13036.
224
225
$messages[] = $this->newDataMessage($bytes);
226
227
if (!$this->expectBytes) {
228
// We've finished reading this chunk, so go read the next chunk.
229
$this->state = 'data-length';
230
} else {
231
// We're waiting for more data, and have read everything available
232
// to us so far.
233
break;
234
}
235
} else {
236
throw new Exception(pht("Bad parser state '%s'!", $this->state));
237
}
238
}
239
240
return $messages;
241
}
242
243
}
244
245