master
js 406 lines 13 KB
Raw
1 const type = require('../../core').type;
2 const EventEmitter = require('events').EventEmitter;
3 const caps = require('./caps');
4 const log = require('../../core').log;
5 const data = require('./data');
6
7 // RDP virtual channel constants (MS-RDPBCGR 3.1.5.2)
8 const CHANNEL_CHUNK_LENGTH = 1600;
9 const CHANNEL_FLAG_FIRST = 0x0001;
10 const CHANNEL_FLAG_LAST = 0x0002;
11 const CHANNEL_FLAG_SHOW_PROTOCOL = 0x0010;
12
13
14 /**
15 * Cliprdr channel for all clipboard
16 * capabilities exchange
17 */
18 class Cliprdr extends EventEmitter {
19
20 constructor(transport) {
21 super();
22 this.transport = transport;
23 // must be init via connect event
24 this.userId = 0;
25 this.serverCapabilities = [];
26 this.clientCapabilities = [];
27 }
28
29 }
30
31
32 /**
33 * Client side of Cliprdr channel automata
34 * @param transport
35 */
36 class Client extends Cliprdr {
37
38 constructor(transport, fastPathTransport) {
39
40 super(transport, fastPathTransport);
41
42 this.transport.once('connect', (gccCore, userId, channelId) => {
43 this.connect(gccCore, userId, channelId);
44 }).on('close', function () {
45 //this.emit('close');
46 }).on('error', function (err) {
47 //this.emit('error', err);
48 });
49
50 this.content = '';
51
52 }
53
54 /**
55 * connect function
56 * @param gccCore {type.Component(clientCoreData)}
57 */
58 connect(gccCore, userId, channelId) {
59 this.gccCore = gccCore;
60 this.userId = userId;
61 this.channelId = channelId;
62 this._fragmentBuffer = null;
63 this._fragmentMsgType = null;
64 this.transport.once('cliprdr', (s) => {
65 this.recv(s);
66 });
67 }
68
69
70 /**
71 * Send a CLIPRDR message, fragmenting into channel chunks if necessary.
72 * Per MS-RDPBCGR 3.1.5.2, each virtual channel chunk must be <= CHANNEL_CHUNK_LENGTH bytes,
73 * and the Channel PDU Header flags must reflect fragment position.
74 */
75 send(message) {
76 const msgBuf = message.toStream().buffer;
77 const totalLength = msgBuf.length;
78
79 let offset = 0;
80 while (offset < totalLength) {
81 const chunkSize = Math.min(CHANNEL_CHUNK_LENGTH, totalLength - offset);
82 const chunk = msgBuf.slice(offset, offset + chunkSize);
83
84 let flags = CHANNEL_FLAG_SHOW_PROTOCOL;
85 if (offset === 0) flags |= CHANNEL_FLAG_FIRST;
86 if (offset + chunkSize >= totalLength) flags |= CHANNEL_FLAG_LAST;
87
88 // Channel PDU Header: totalLength field is always the uncompressed total across all fragments
89 this.transport.send('cliprdr', new type.Component([
90 new type.UInt32Le(totalLength),
91 new type.UInt32Le(flags),
92 new type.BinaryString(chunk),
93 ]));
94
95 offset += chunkSize;
96 }
97 }
98
99 /**
100 * Receive a virtual channel PDU.
101 * Reads the Channel PDU Header at the current stream offset (not a hardcoded position),
102 * handles multi-fragment reassembly, then dispatches to the appropriate handler.
103 */
104 recv(s) {
105 // Read Channel PDU Header at the current stream position.
106 // Do NOT hardcode s.offset — the MCS per.readLength encoding is 1 byte for payloads
107 // < 128 bytes and 2 bytes for larger ones, so the stream offset varies by packet size.
108 const channelTotalLen = new type.UInt32Le().read(s).value; // eslint-disable-line no-unused-vars
109 const channelFlags = new type.UInt32Le().read(s).value;
110
111 const isFirst = !!(channelFlags & CHANNEL_FLAG_FIRST);
112 const isLast = !!(channelFlags & CHANNEL_FLAG_LAST);
113
114 if (!isFirst) {
115 // Middle or last fragment — accumulate payload data
116 if (this._fragmentBuffer) {
117 this._fragmentBuffer = Buffer.concat([this._fragmentBuffer, s.buffer.slice(s.offset)]);
118 }
119 if (isLast) {
120 this._dispatchFragment();
121 }
122 this.transport.once('cliprdr', (s) => { this.recv(s); });
123 return;
124 }
125
126 // First (or only) fragment — parse the CLIPRDR PDU header
127 const pdu = data.clipPDU().read(s);
128 const clipType = data.ClipPDUMsgType;
129 const msgType = pdu.obj.header.obj.msgType.value;
130
131 if (!isLast) {
132 // First of multiple fragments — begin reassembly; payload starts at current s.offset
133 this._fragmentMsgType = msgType;
134 this._fragmentBuffer = s.buffer.slice(s.offset);
135 this.transport.once('cliprdr', (s) => { this.recv(s); });
136 return;
137 }
138
139 // Single complete packet — dispatch directly
140 switch (msgType) {
141 case clipType.CB_MONITOR_READY:
142 this.recvMonitorReadyPDU(s);
143 break;
144 case clipType.CB_FORMAT_LIST:
145 this.recvFormatListPDU(s);
146 break;
147 case clipType.CB_FORMAT_LIST_RESPONSE:
148 this.recvFormatListResponsePDU(s);
149 break;
150 case clipType.CB_FORMAT_DATA_REQUEST:
151 this.recvFormatDataRequestPDU(s);
152 break;
153 case clipType.CB_FORMAT_DATA_RESPONSE:
154 this.recvFormatDataResponsePDU(s);
155 break;
156 case clipType.CB_TEMP_DIRECTORY:
157 break;
158 case clipType.CB_CLIP_CAPS:
159 this.recvClipboardCapsPDU(s);
160 break;
161 case clipType.CB_FILECONTENTS_REQUEST:
162 break;
163 }
164
165 this.transport.once('cliprdr', (s) => {
166 this.recv(s);
167 });
168 }
169
170 /**
171 * Dispatch a fully reassembled multi-fragment CLIPRDR message.
172 * this._fragmentBuffer contains the raw payload bytes (no CLIPRDR header).
173 */
174 _dispatchFragment() {
175 const buf = this._fragmentBuffer;
176 const clipType = data.ClipPDUMsgType;
177 this._fragmentBuffer = null;
178
179 if (this._fragmentMsgType === clipType.CB_FORMAT_DATA_RESPONSE) {
180 // buf is the UCS-2 encoded text with a null terminator; strip the terminator
181 const str = buf.toString('ucs2', 0, buf.length - 2);
182 this.content = str;
183 this.emit('clipboard', str);
184 }
185
186 this._fragmentMsgType = null;
187 }
188
189 /**
190 * Receive capabilities from server
191 * @param s {type.Stream}
192 */
193 recvClipboardCapsPDU(s) {
194 // const pdu = data.clipPDU().read(s);
195 // console.log('recvClipboardCapsPDU', s);
196 }
197
198
199 /**
200 * Receive monitor ready from server
201 * @param s {type.Stream}
202 */
203 recvMonitorReadyPDU(s) {
204 // const pdu = data.clipPDU().read(s);
205 // console.log('recvMonitorReadyPDU', s);
206
207 this.sendClipboardCapsPDU();
208 // this.sendClientTemporaryDirectoryPDU();
209 this.sendFormatListPDU();
210 }
211
212
213 /**
214 * Send clipboard capabilities PDU
215 */
216 sendClipboardCapsPDU() {
217 this.send(new type.Component({
218 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_CLIP_CAPS),
219 msgFlags: new type.UInt16Le(0x00),
220 dataLen: new type.UInt32Le(0x10),
221 cCapabilitiesSets: new type.UInt16Le(0x01),
222 pad1: new type.UInt16Le(0x00),
223 capabilitySetType: new type.UInt16Le(0x01),
224 lengthCapability: new type.UInt16Le(0x0c),
225 version: new type.UInt32Le(0x02),
226 capabilityFlags: new type.UInt32Le(0x02)
227 }));
228 }
229
230
231 /**
232 * Send client temporary directory PDU
233 */
234 sendClientTemporaryDirectoryPDU(path = '') {
235 // TODO
236 this.send(new type.Component({
237 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_TEMP_DIRECTORY),
238 msgFlags: new type.UInt16Le(0x00),
239 dataLen: new type.UInt32Le(0x0208),
240 wszTempDir: new type.BinaryString(Buffer.from('D:\\Vectors' + Array(251).join('\x00'), 'ucs2'), { readLength: new type.CallableValue(520) })
241 }));
242 }
243
244
245 /**
246 * Send format list PDU
247 */
248 sendFormatListPDU() {
249 this.send(new type.Component({
250 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_FORMAT_LIST),
251 msgFlags: new type.UInt16Le(0x00),
252
253 dataLen: new type.UInt32Le(0x24),
254
255 formatId6: new type.UInt32Le(0xc004),
256 formatName6: new type.BinaryString(Buffer.from('Native\x00', 'ucs2'), { readLength: new type.CallableValue(14) }),
257
258 formatId8: new type.UInt32Le(0x0d),
259 formatName8: new type.UInt16Le(0x00),
260
261 formatId9: new type.UInt32Le(0x10),
262 formatName9: new type.UInt16Le(0x00),
263
264 formatId0: new type.UInt32Le(0x01),
265 formatName0: new type.UInt16Le(0x00),
266
267 // dataLen: new type.UInt32Le(0xe0),
268
269 // formatId1: new type.UInt32Le(0xc08a),
270 // formatName1: new type.BinaryString(Buffer.from('Rich Text Format\x00' , 'ucs2'), { readLength : new type.CallableValue(34)}),
271
272 // formatId2: new type.UInt32Le(0xc145),
273 // formatName2: new type.BinaryString(Buffer.from('Rich Text Format Without Objects\x00' , 'ucs2'), { readLength : new type.CallableValue(66)}),
274
275 // formatId3: new type.UInt32Le(0xc143),
276 // formatName3: new type.BinaryString(Buffer.from('RTF As Text\x00' , 'ucs2'), { readLength : new type.CallableValue(24)}),
277
278 // formatId4: new type.UInt32Le(0x01),
279 // formatName4: new type.BinaryString(0x00),
280
281 formatId5: new type.UInt32Le(0x07),
282 formatName5: new type.UInt16Le(0x00),
283
284 // formatId6: new type.UInt32Le(0xc004),
285 // formatName6: new type.BinaryString(Buffer.from('Native\x00' , 'ucs2'), { readLength : new type.CallableValue(14)}),
286
287 // formatId7: new type.UInt32Le(0xc00e),
288 // formatName7: new type.BinaryString(Buffer.from('Object Descriptor\x00' , 'ucs2'), { readLength : new type.CallableValue(36)}),
289
290 // formatId8: new type.UInt32Le(0x03),
291 // formatName8: new type.UInt16Le(0x00),
292
293 // formatId9: new type.UInt32Le(0x10),
294 // formatName9: new type.UInt16Le(0x00),
295
296 // formatId0: new type.UInt32Le(0x07),
297 // formatName0: new type.UInt16Le(0x00),
298 }));
299
300 }
301
302 /**
303 * Recvie format list PDU from server
304 * @param {type.Stream} s
305 */
306 recvFormatListPDU(s) {
307 // const pdu = data.clipPDU().read(s);
308 // console.log('recvFormatListPDU', s);
309 this.sendFormatListResponsePDU();
310 }
311
312
313 /**
314 * Send format list reesponse
315 */
316 sendFormatListResponsePDU() {
317 this.send(new type.Component({
318 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_FORMAT_LIST_RESPONSE),
319 msgFlags: new type.UInt16Le(0x01),
320 dataLen: new type.UInt32Le(0x00),
321 }));
322
323 this.sendFormatDataRequestPDU();
324 }
325
326
327 /**
328 * Receive format list response from server
329 * @param s {type.Stream}
330 */
331 recvFormatListResponsePDU(s) {
332 // const pdu = data.clipPDU().read(s);
333 // console.log('recvFormatListResponsePDU', s);
334 // this.sendFormatDataRequestPDU();
335 }
336
337
338 /**
339 * Send format data request PDU
340 */
341 sendFormatDataRequestPDU(formartId = 0x0d) {
342 this.send(new type.Component({
343 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_FORMAT_DATA_REQUEST),
344 msgFlags: new type.UInt16Le(0x00),
345 dataLen: new type.UInt32Le(0x04),
346 requestedFormatId: new type.UInt32Le(formartId),
347 }));
348 }
349
350
351 /**
352 * Receive format data request PDU from server
353 * @param s {type.Stream}
354 */
355 recvFormatDataRequestPDU(s) {
356 // const pdu = data.clipPDU().read(s);
357 // console.log('recvFormatDataRequestPDU', s);
358 this.sendFormatDataResponsePDU();
359 }
360
361
362 /**
363 * Send format data reesponse PDU
364 */
365 sendFormatDataResponsePDU() {
366
367 const bufs = Buffer.from(this.content + '\x00', 'ucs2');
368
369 this.send(new type.Component({
370 msgType: new type.UInt16Le(data.ClipPDUMsgType.CB_FORMAT_DATA_RESPONSE),
371 msgFlags: new type.UInt16Le(0x01),
372 dataLen: new type.UInt32Le(bufs.length),
373 requestedFormatData: new type.BinaryString(bufs, { readLength: new type.CallableValue(bufs.length) })
374 }));
375
376 }
377
378
379 /**
380 * Receive format data response PDU from server.
381 * s.offset is positioned immediately after the CLIPRDR header (channel PDU header and
382 * CLIPRDR msgType/msgFlags/dataLen were already consumed in recv()), so the UCS-2
383 * text data starts exactly at s.offset.
384 * @param s {type.Stream}
385 */
386 recvFormatDataResponsePDU(s) {
387 // const pdu = data.clipPDU().read(s);
388 const str = s.buffer.toString('ucs2', s.offset, s.buffer.length - 2);
389 // console.log('recvFormatDataResponsePDU', str);
390 this.content = str;
391 this.emit('clipboard', str)
392 }
393
394
395 // =====================================================================================
396 setClipboardData(content) {
397 this.content = content;
398 this.sendFormatListPDU();
399 }
400
401 }
402
403
404 module.exports = {
405 Client
406 }