21#include <freerdp/log.h>
24#define TAG FREERDP_TAG("core.gateway.websocket")
26#define RESPONSE_SIZE_LIMIT (64ULL * 1024ULL * 1024ULL)
28struct s_websocket_context
35 BYTE fragmentOriginalOpcode;
36 BYTE lengthAndMaskPosition;
37 WEBSOCKET_STATE state;
42static BOOL Stream_Reset(
wStream* s)
44 Stream_ResetPosition(s);
45 return Stream_SetLength(s, Stream_Capacity(s));
48static int websocket_write_all(BIO* bio,
const BYTE* data,
size_t length);
50BOOL websocket_context_mask_and_send(BIO* bio,
wStream* sPacket,
wStream* sDataPacket,
53 const size_t len = Stream_Length(sDataPacket);
54 Stream_ResetPosition(sDataPacket);
56 if (!Stream_EnsureRemainingCapacity(sPacket, len))
61 for (; streamPos + 4 <= len; streamPos += 4)
63 const uint32_t data = Stream_Get_UINT32(sDataPacket);
64 Stream_Write_UINT32(sPacket, data ^ maskingKey);
68 for (; streamPos < len; streamPos++)
71 BYTE* partialMask = ((BYTE*)&maskingKey) + (streamPos % 4);
72 Stream_Read_UINT8(sDataPacket, data);
73 Stream_Write_UINT8(sPacket, data ^ *partialMask);
76 Stream_SealLength(sPacket);
79 const size_t size = Stream_Length(sPacket);
80 const int status = websocket_write_all(bio, Stream_Buffer(sPacket), size);
81 Stream_Free(sPacket, TRUE);
83 return !((status < 0) || ((
size_t)status != size));
86wStream* websocket_context_packet_new(
size_t len, WEBSOCKET_OPCODE opcode, UINT32* pMaskingKey)
88 WINPR_ASSERT(pMaskingKey);
95 else if (len < 0x10000)
100 UINT32 maskingKey = 0;
101 if (winpr_RAND(&maskingKey,
sizeof(maskingKey)) < 0)
104 wStream* sWS = Stream_New(
nullptr, fullLen);
108 Stream_Write_UINT8(sWS, (UINT8)(WEBSOCKET_FIN_BIT | opcode));
110 Stream_Write_UINT8(sWS, (UINT8)len | WEBSOCKET_MASK_BIT);
111 else if (len < 0x10000)
113 Stream_Write_UINT8(sWS, 126 | WEBSOCKET_MASK_BIT);
114 Stream_Write_UINT16_BE(sWS, (UINT16)len);
118 Stream_Write_UINT8(sWS, 127 | WEBSOCKET_MASK_BIT);
119 Stream_Write_UINT32_BE(sWS, 0);
120 Stream_Write_UINT32_BE(sWS, (UINT32)len);
122 Stream_Write_UINT32(sWS, maskingKey);
123 *pMaskingKey = maskingKey;
127BOOL websocket_context_write_wstream(websocket_context* context, BIO* bio,
wStream* sPacket,
128 WEBSOCKET_OPCODE opcode)
130 WINPR_ASSERT(context);
132 if (context->closeSent)
135 if (opcode == WebsocketCloseOpcode)
136 context->closeSent = TRUE;
139 WINPR_ASSERT(sPacket);
141 const size_t len = Stream_Length(sPacket);
142 uint32_t maskingKey = 0;
143 wStream* sWS = websocket_context_packet_new(len, opcode, &maskingKey);
147 return websocket_context_mask_and_send(bio, sWS, sPacket, maskingKey);
150int websocket_write_all(BIO* bio,
const BYTE* data,
size_t length)
156 if (length > INT32_MAX)
159 while (offset < length)
162 const size_t diff = length - offset;
163 int status = BIO_write(bio, &data[offset], (
int)diff);
166 offset += (size_t)status;
169 if (!BIO_should_retry(bio))
172 if (BIO_write_blocked(bio))
174 const long rstatus = BIO_wait_write(bio, 100);
178 else if (BIO_read_blocked(bio))
188int websocket_context_write(websocket_context* context, BIO* bio,
const BYTE* buf,
int isize,
189 WEBSOCKET_OPCODE opcode)
197 wStream sbuffer = WINPR_C_ARRAY_INIT;
198 wStream* s = Stream_StaticConstInit(&sbuffer, buf, (
size_t)isize);
199 if (!Stream_SetLength(s, Stream_Capacity(s)))
201 if (!websocket_context_write_wstream(context, bio, s, opcode))
206static int websocket_read_data(BIO* bio, BYTE* pBuffer,
size_t size,
207 websocket_context* encodingContext)
212 WINPR_ASSERT(pBuffer);
213 WINPR_ASSERT(encodingContext);
215 if (encodingContext->payloadLength == 0)
217 encodingContext->state = WebsocketStateOpcodeAndFin;
222 (encodingContext->payloadLength < size ? encodingContext->payloadLength : size);
223 if (rlen > INT32_MAX)
227 status = BIO_read(bio, pBuffer, (
int)rlen);
228 if ((status <= 0) || ((
size_t)status > encodingContext->payloadLength))
231 encodingContext->payloadLength -= (size_t)status;
233 if (encodingContext->payloadLength == 0)
234 encodingContext->state = WebsocketStateOpcodeAndFin;
239static int websocket_read_wstream(BIO* bio, websocket_context* encodingContext)
242 WINPR_ASSERT(encodingContext);
244 wStream* s = encodingContext->responseStreamBuffer;
247 if (encodingContext->payloadLength == 0)
249 encodingContext->state = WebsocketStateOpcodeAndFin;
253 if (!Stream_EnsureRemainingCapacity(s, encodingContext->payloadLength))
256 "wStream::capacity [%" PRIuz
"] != encodingContext::paylaodLangth [%" PRIuz
"]",
257 Stream_GetRemainingCapacity(s), encodingContext->payloadLength);
261 const int status = websocket_read_data(bio, Stream_Pointer(s), Stream_GetRemainingCapacity(s),
266 if (!Stream_SafeSeek(s, (
size_t)status))
272static BOOL websocket_reply_close(BIO* bio, websocket_context* context,
wStream* s)
276 return websocket_context_write_wstream(context, bio, s, WebsocketCloseOpcode);
279static BOOL websocket_reply_pong(BIO* bio, websocket_context* context,
wStream* s)
284 if (Stream_GetPosition(s) != 0)
285 return websocket_context_write_wstream(context, bio, s, WebsocketPongOpcode);
287 return websocket_reply_close(bio, context,
nullptr);
290static int websocket_handle_payload(BIO* bio, BYTE* pBuffer,
size_t size,
291 websocket_context* encodingContext)
296 WINPR_ASSERT(pBuffer);
297 WINPR_ASSERT(encodingContext);
299 const BYTE effectiveOpcode = ((encodingContext->opcode & 0xf) == WebsocketContinuationOpcode
300 ? encodingContext->fragmentOriginalOpcode & 0xf
301 : encodingContext->opcode & 0xf);
303 switch (effectiveOpcode)
305 case WebsocketBinaryOpcode:
307 status = websocket_read_data(bio, pBuffer, size, encodingContext);
313 case WebsocketPingOpcode:
315 status = websocket_read_wstream(bio, encodingContext);
319 if (encodingContext->payloadLength == 0)
321 websocket_reply_pong(bio, encodingContext, encodingContext->responseStreamBuffer);
322 if (!Stream_Reset(encodingContext->responseStreamBuffer))
327 case WebsocketPongOpcode:
329 status = websocket_read_wstream(bio, encodingContext);
333 if (!Stream_Reset(encodingContext->responseStreamBuffer))
337 case WebsocketCloseOpcode:
339 status = websocket_read_wstream(bio, encodingContext);
343 if (encodingContext->payloadLength == 0)
345 websocket_reply_close(bio, encodingContext, encodingContext->responseStreamBuffer);
346 encodingContext->closeSent = TRUE;
347 if (!Stream_Reset(encodingContext->responseStreamBuffer))
353 WLog_WARN(TAG,
"Unimplemented websocket opcode %" PRIx8
". Dropping", effectiveOpcode);
355 status = websocket_read_wstream(bio, encodingContext);
358 if (!Stream_Reset(encodingContext->responseStreamBuffer))
367int websocket_context_read(websocket_context* encodingContext, BIO* bio, BYTE* pBuffer,
size_t size)
370 size_t effectiveDataLen = 0;
373 WINPR_ASSERT(pBuffer);
374 WINPR_ASSERT(encodingContext);
378 switch (encodingContext->state)
380 case WebsocketStateOpcodeAndFin:
382 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
385 status = BIO_read(bio, (
char*)buffer,
sizeof(buffer));
387 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(
int, effectiveDataLen)
390 encodingContext->opcode = buffer[0];
391 if (((encodingContext->opcode & 0xf) != WebsocketContinuationOpcode) &&
392 (encodingContext->opcode & 0xf) < 0x08)
393 encodingContext->fragmentOriginalOpcode = encodingContext->opcode;
394 encodingContext->state = WebsocketStateLengthAndMasking;
397 case WebsocketStateLengthAndMasking:
399 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
402 status = BIO_read(bio, (
char*)buffer,
sizeof(buffer));
404 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(
int, effectiveDataLen)
407 encodingContext->masking = ((buffer[0] & WEBSOCKET_MASK_BIT) == WEBSOCKET_MASK_BIT);
408 encodingContext->lengthAndMaskPosition = 0;
409 encodingContext->payloadLength = 0;
410 const BYTE len = buffer[0] & 0x7f;
413 encodingContext->payloadLength = len;
414 encodingContext->state = (encodingContext->masking ? WebSocketStateMaskingKey
415 : WebSocketStatePayload);
418 encodingContext->state = WebsocketStateShortLength;
420 encodingContext->state = WebsocketStateLongLength;
423 case WebsocketStateShortLength:
424 case WebsocketStateLongLength:
426 BYTE buffer[1] = WINPR_C_ARRAY_INIT;
427 const BYTE lenLength =
428 (encodingContext->state == WebsocketStateShortLength ? 2 : 8);
429 while (encodingContext->lengthAndMaskPosition < lenLength)
432 status = BIO_read(bio, (
char*)buffer,
sizeof(buffer));
434 return (effectiveDataLen > 0
435 ? WINPR_ASSERTING_INT_CAST(
int, effectiveDataLen)
437 if (status > UINT8_MAX)
439 encodingContext->payloadLength =
440 (encodingContext->payloadLength) << 8 | buffer[0];
441 encodingContext->lengthAndMaskPosition +=
442 WINPR_ASSERTING_INT_CAST(BYTE, status);
444 if (encodingContext->payloadLength > RESPONSE_SIZE_LIMIT)
446 WLog_ERR(TAG,
"received excessive payload size %" PRIuz
", aborting",
447 encodingContext->payloadLength);
450 encodingContext->state =
451 (encodingContext->masking ? WebSocketStateMaskingKey : WebSocketStatePayload);
454 case WebSocketStateMaskingKey:
457 TAG,
"Websocket Server sends data with masking key. This is against RFC 6455.");
460 case WebSocketStatePayload:
462 status = websocket_handle_payload(bio, pBuffer, size, encodingContext);
464 return (effectiveDataLen > 0 ? WINPR_ASSERTING_INT_CAST(
int, effectiveDataLen)
467 effectiveDataLen += WINPR_ASSERTING_INT_CAST(
size_t, status);
469 if (WINPR_ASSERTING_INT_CAST(
size_t, status) >= size)
470 return WINPR_ASSERTING_INT_CAST(
int, effectiveDataLen);
472 size -= WINPR_ASSERTING_INT_CAST(
size_t, status);
482websocket_context* websocket_context_new(
void)
484 websocket_context* context = calloc(1,
sizeof(websocket_context));
488 context->responseStreamBuffer = Stream_New(
nullptr, 1024);
489 if (!context->responseStreamBuffer)
492 if (!websocket_context_reset(context))
497 websocket_context_free(context);
501void websocket_context_free(websocket_context* context)
506 Stream_Free(context->responseStreamBuffer, TRUE);
510BOOL websocket_context_reset(websocket_context* context)
512 WINPR_ASSERT(context);
514 context->state = WebsocketStateOpcodeAndFin;
515 return Stream_Reset(context->responseStreamBuffer);