Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F122138989
receiver.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Jul 16, 01:57
Size
14 KB
Mime Type
text/x-c++
Expires
Fri, Jul 18, 01:57 (2 d)
Engine
blob
Format
Raw Data
Handle
27439763
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
receiver.js
View Options
'use strict'
;
const
{
Writable
}
=
require
(
'stream'
);
const
PerMessageDeflate
=
require
(
'./permessage-deflate'
);
const
{
BINARY_TYPES
,
EMPTY_BUFFER
,
kStatusCode
,
kWebSocket
}
=
require
(
'./constants'
);
const
{
concat
,
toArrayBuffer
,
unmask
}
=
require
(
'./buffer-util'
);
const
{
isValidStatusCode
,
isValidUTF8
}
=
require
(
'./validation'
);
const
FastBuffer
=
Buffer
[
Symbol
.
species
];
const
GET_INFO
=
0
;
const
GET_PAYLOAD_LENGTH_16
=
1
;
const
GET_PAYLOAD_LENGTH_64
=
2
;
const
GET_MASK
=
3
;
const
GET_DATA
=
4
;
const
INFLATING
=
5
;
/**
* HyBi Receiver implementation.
*
* @extends Writable
*/
class
Receiver
extends
Writable
{
/**
* Creates a Receiver instance.
*
* @param {Object} [options] Options object
* @param {String} [options.binaryType=nodebuffer] The type for binary data
* @param {Object} [options.extensions] An object containing the negotiated
* extensions
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
* client or server mode
* @param {Number} [options.maxPayload=0] The maximum allowed message length
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
* not to skip UTF-8 validation for text and close messages
*/
constructor
(
options
=
{})
{
super
();
this
.
_binaryType
=
options
.
binaryType
||
BINARY_TYPES
[
0
];
this
.
_extensions
=
options
.
extensions
||
{};
this
.
_isServer
=
!!
options
.
isServer
;
this
.
_maxPayload
=
options
.
maxPayload
|
0
;
this
.
_skipUTF8Validation
=
!!
options
.
skipUTF8Validation
;
this
[
kWebSocket
]
=
undefined
;
this
.
_bufferedBytes
=
0
;
this
.
_buffers
=
[];
this
.
_compressed
=
false
;
this
.
_payloadLength
=
0
;
this
.
_mask
=
undefined
;
this
.
_fragmented
=
0
;
this
.
_masked
=
false
;
this
.
_fin
=
false
;
this
.
_opcode
=
0
;
this
.
_totalPayloadLength
=
0
;
this
.
_messageLength
=
0
;
this
.
_fragments
=
[];
this
.
_state
=
GET_INFO
;
this
.
_loop
=
false
;
}
/**
* Implements `Writable.prototype._write()`.
*
* @param {Buffer} chunk The chunk of data to write
* @param {String} encoding The character encoding of `chunk`
* @param {Function} cb Callback
* @private
*/
_write
(
chunk
,
encoding
,
cb
)
{
if
(
this
.
_opcode
===
0x08
&&
this
.
_state
==
GET_INFO
)
return
cb
();
this
.
_bufferedBytes
+=
chunk
.
length
;
this
.
_buffers
.
push
(
chunk
);
this
.
startLoop
(
cb
);
}
/**
* Consumes `n` bytes from the buffered data.
*
* @param {Number} n The number of bytes to consume
* @return {Buffer} The consumed bytes
* @private
*/
consume
(
n
)
{
this
.
_bufferedBytes
-=
n
;
if
(
n
===
this
.
_buffers
[
0
].
length
)
return
this
.
_buffers
.
shift
();
if
(
n
<
this
.
_buffers
[
0
].
length
)
{
const
buf
=
this
.
_buffers
[
0
];
this
.
_buffers
[
0
]
=
new
FastBuffer
(
buf
.
buffer
,
buf
.
byteOffset
+
n
,
buf
.
length
-
n
);
return
new
FastBuffer
(
buf
.
buffer
,
buf
.
byteOffset
,
n
);
}
const
dst
=
Buffer
.
allocUnsafe
(
n
);
do
{
const
buf
=
this
.
_buffers
[
0
];
const
offset
=
dst
.
length
-
n
;
if
(
n
>=
buf
.
length
)
{
dst
.
set
(
this
.
_buffers
.
shift
(),
offset
);
}
else
{
dst
.
set
(
new
Uint8Array
(
buf
.
buffer
,
buf
.
byteOffset
,
n
),
offset
);
this
.
_buffers
[
0
]
=
new
FastBuffer
(
buf
.
buffer
,
buf
.
byteOffset
+
n
,
buf
.
length
-
n
);
}
n
-=
buf
.
length
;
}
while
(
n
>
0
);
return
dst
;
}
/**
* Starts the parsing loop.
*
* @param {Function} cb Callback
* @private
*/
startLoop
(
cb
)
{
let
err
;
this
.
_loop
=
true
;
do
{
switch
(
this
.
_state
)
{
case
GET_INFO
:
err
=
this
.
getInfo
();
break
;
case
GET_PAYLOAD_LENGTH_16
:
err
=
this
.
getPayloadLength16
();
break
;
case
GET_PAYLOAD_LENGTH_64
:
err
=
this
.
getPayloadLength64
();
break
;
case
GET_MASK
:
this
.
getMask
();
break
;
case
GET_DATA
:
err
=
this
.
getData
(
cb
);
break
;
default
:
// `INFLATING`
this
.
_loop
=
false
;
return
;
}
}
while
(
this
.
_loop
);
cb
(
err
);
}
/**
* Reads the first two bytes of a frame.
*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getInfo
()
{
if
(
this
.
_bufferedBytes
<
2
)
{
this
.
_loop
=
false
;
return
;
}
const
buf
=
this
.
consume
(
2
);
if
((
buf
[
0
]
&
0x30
)
!==
0x00
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'RSV2 and RSV3 must be clear'
,
true
,
1002
,
'WS_ERR_UNEXPECTED_RSV_2_3'
);
}
const
compressed
=
(
buf
[
0
]
&
0x40
)
===
0x40
;
if
(
compressed
&&
!
this
.
_extensions
[
PerMessageDeflate
.
extensionName
])
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'RSV1 must be clear'
,
true
,
1002
,
'WS_ERR_UNEXPECTED_RSV_1'
);
}
this
.
_fin
=
(
buf
[
0
]
&
0x80
)
===
0x80
;
this
.
_opcode
=
buf
[
0
]
&
0x0f
;
this
.
_payloadLength
=
buf
[
1
]
&
0x7f
;
if
(
this
.
_opcode
===
0x00
)
{
if
(
compressed
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'RSV1 must be clear'
,
true
,
1002
,
'WS_ERR_UNEXPECTED_RSV_1'
);
}
if
(
!
this
.
_fragmented
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'invalid opcode 0'
,
true
,
1002
,
'WS_ERR_INVALID_OPCODE'
);
}
this
.
_opcode
=
this
.
_fragmented
;
}
else
if
(
this
.
_opcode
===
0x01
||
this
.
_opcode
===
0x02
)
{
if
(
this
.
_fragmented
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
`
invalid
opcode
$
{
this
.
_opcode
}
`
,
true
,
1002
,
'WS_ERR_INVALID_OPCODE'
);
}
this
.
_compressed
=
compressed
;
}
else
if
(
this
.
_opcode
>
0x07
&&
this
.
_opcode
<
0x0b
)
{
if
(
!
this
.
_fin
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'FIN must be set'
,
true
,
1002
,
'WS_ERR_EXPECTED_FIN'
);
}
if
(
compressed
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'RSV1 must be clear'
,
true
,
1002
,
'WS_ERR_UNEXPECTED_RSV_1'
);
}
if
(
this
.
_payloadLength
>
0x7d
||
(
this
.
_opcode
===
0x08
&&
this
.
_payloadLength
===
1
)
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
`
invalid
payload
length
$
{
this
.
_payloadLength
}
`
,
true
,
1002
,
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
);
}
}
else
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
`
invalid
opcode
$
{
this
.
_opcode
}
`
,
true
,
1002
,
'WS_ERR_INVALID_OPCODE'
);
}
if
(
!
this
.
_fin
&&
!
this
.
_fragmented
)
this
.
_fragmented
=
this
.
_opcode
;
this
.
_masked
=
(
buf
[
1
]
&
0x80
)
===
0x80
;
if
(
this
.
_isServer
)
{
if
(
!
this
.
_masked
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'MASK must be set'
,
true
,
1002
,
'WS_ERR_EXPECTED_MASK'
);
}
}
else
if
(
this
.
_masked
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'MASK must be clear'
,
true
,
1002
,
'WS_ERR_UNEXPECTED_MASK'
);
}
if
(
this
.
_payloadLength
===
126
)
this
.
_state
=
GET_PAYLOAD_LENGTH_16
;
else
if
(
this
.
_payloadLength
===
127
)
this
.
_state
=
GET_PAYLOAD_LENGTH_64
;
else
return
this
.
haveLength
();
}
/**
* Gets extended payload length (7+16).
*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength16
()
{
if
(
this
.
_bufferedBytes
<
2
)
{
this
.
_loop
=
false
;
return
;
}
this
.
_payloadLength
=
this
.
consume
(
2
).
readUInt16BE
(
0
);
return
this
.
haveLength
();
}
/**
* Gets extended payload length (7+64).
*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength64
()
{
if
(
this
.
_bufferedBytes
<
8
)
{
this
.
_loop
=
false
;
return
;
}
const
buf
=
this
.
consume
(
8
);
const
num
=
buf
.
readUInt32BE
(
0
);
//
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
// if payload length is greater than this number.
//
if
(
num
>
Math
.
pow
(
2
,
53
-
32
)
-
1
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'Unsupported WebSocket frame: payload length > 2^53 - 1'
,
false
,
1009
,
'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
);
}
this
.
_payloadLength
=
num
*
Math
.
pow
(
2
,
32
)
+
buf
.
readUInt32BE
(
4
);
return
this
.
haveLength
();
}
/**
* Payload length has been read.
*
* @return {(RangeError|undefined)} A possible error
* @private
*/
haveLength
()
{
if
(
this
.
_payloadLength
&&
this
.
_opcode
<
0x08
)
{
this
.
_totalPayloadLength
+=
this
.
_payloadLength
;
if
(
this
.
_totalPayloadLength
>
this
.
_maxPayload
&&
this
.
_maxPayload
>
0
)
{
this
.
_loop
=
false
;
return
error
(
RangeError
,
'Max payload size exceeded'
,
false
,
1009
,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
);
}
}
if
(
this
.
_masked
)
this
.
_state
=
GET_MASK
;
else
this
.
_state
=
GET_DATA
;
}
/**
* Reads mask bytes.
*
* @private
*/
getMask
()
{
if
(
this
.
_bufferedBytes
<
4
)
{
this
.
_loop
=
false
;
return
;
}
this
.
_mask
=
this
.
consume
(
4
);
this
.
_state
=
GET_DATA
;
}
/**
* Reads data bytes.
*
* @param {Function} cb Callback
* @return {(Error|RangeError|undefined)} A possible error
* @private
*/
getData
(
cb
)
{
let
data
=
EMPTY_BUFFER
;
if
(
this
.
_payloadLength
)
{
if
(
this
.
_bufferedBytes
<
this
.
_payloadLength
)
{
this
.
_loop
=
false
;
return
;
}
data
=
this
.
consume
(
this
.
_payloadLength
);
if
(
this
.
_masked
&&
(
this
.
_mask
[
0
]
|
this
.
_mask
[
1
]
|
this
.
_mask
[
2
]
|
this
.
_mask
[
3
])
!==
0
)
{
unmask
(
data
,
this
.
_mask
);
}
}
if
(
this
.
_opcode
>
0x07
)
return
this
.
controlMessage
(
data
);
if
(
this
.
_compressed
)
{
this
.
_state
=
INFLATING
;
this
.
decompress
(
data
,
cb
);
return
;
}
if
(
data
.
length
)
{
//
// This message is not compressed so its length is the sum of the payload
// length of all fragments.
//
this
.
_messageLength
=
this
.
_totalPayloadLength
;
this
.
_fragments
.
push
(
data
);
}
return
this
.
dataMessage
();
}
/**
* Decompresses data.
*
* @param {Buffer} data Compressed data
* @param {Function} cb Callback
* @private
*/
decompress
(
data
,
cb
)
{
const
perMessageDeflate
=
this
.
_extensions
[
PerMessageDeflate
.
extensionName
];
perMessageDeflate
.
decompress
(
data
,
this
.
_fin
,
(
err
,
buf
)
=>
{
if
(
err
)
return
cb
(
err
);
if
(
buf
.
length
)
{
this
.
_messageLength
+=
buf
.
length
;
if
(
this
.
_messageLength
>
this
.
_maxPayload
&&
this
.
_maxPayload
>
0
)
{
return
cb
(
error
(
RangeError
,
'Max payload size exceeded'
,
false
,
1009
,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
)
);
}
this
.
_fragments
.
push
(
buf
);
}
const
er
=
this
.
dataMessage
();
if
(
er
)
return
cb
(
er
);
this
.
startLoop
(
cb
);
});
}
/**
* Handles a data message.
*
* @return {(Error|undefined)} A possible error
* @private
*/
dataMessage
()
{
if
(
this
.
_fin
)
{
const
messageLength
=
this
.
_messageLength
;
const
fragments
=
this
.
_fragments
;
this
.
_totalPayloadLength
=
0
;
this
.
_messageLength
=
0
;
this
.
_fragmented
=
0
;
this
.
_fragments
=
[];
if
(
this
.
_opcode
===
2
)
{
let
data
;
if
(
this
.
_binaryType
===
'nodebuffer'
)
{
data
=
concat
(
fragments
,
messageLength
);
}
else
if
(
this
.
_binaryType
===
'arraybuffer'
)
{
data
=
toArrayBuffer
(
concat
(
fragments
,
messageLength
));
}
else
{
data
=
fragments
;
}
this
.
emit
(
'message'
,
data
,
true
);
}
else
{
const
buf
=
concat
(
fragments
,
messageLength
);
if
(
!
this
.
_skipUTF8Validation
&&
!
isValidUTF8
(
buf
))
{
this
.
_loop
=
false
;
return
error
(
Error
,
'invalid UTF-8 sequence'
,
true
,
1007
,
'WS_ERR_INVALID_UTF8'
);
}
this
.
emit
(
'message'
,
buf
,
false
);
}
}
this
.
_state
=
GET_INFO
;
}
/**
* Handles a control message.
*
* @param {Buffer} data Data to handle
* @return {(Error|RangeError|undefined)} A possible error
* @private
*/
controlMessage
(
data
)
{
if
(
this
.
_opcode
===
0x08
)
{
this
.
_loop
=
false
;
if
(
data
.
length
===
0
)
{
this
.
emit
(
'conclude'
,
1005
,
EMPTY_BUFFER
);
this
.
end
();
}
else
{
const
code
=
data
.
readUInt16BE
(
0
);
if
(
!
isValidStatusCode
(
code
))
{
return
error
(
RangeError
,
`
invalid
status
code
$
{
code
}
`
,
true
,
1002
,
'WS_ERR_INVALID_CLOSE_CODE'
);
}
const
buf
=
new
FastBuffer
(
data
.
buffer
,
data
.
byteOffset
+
2
,
data
.
length
-
2
);
if
(
!
this
.
_skipUTF8Validation
&&
!
isValidUTF8
(
buf
))
{
return
error
(
Error
,
'invalid UTF-8 sequence'
,
true
,
1007
,
'WS_ERR_INVALID_UTF8'
);
}
this
.
emit
(
'conclude'
,
code
,
buf
);
this
.
end
();
}
}
else
if
(
this
.
_opcode
===
0x09
)
{
this
.
emit
(
'ping'
,
data
);
}
else
{
this
.
emit
(
'pong'
,
data
);
}
this
.
_state
=
GET_INFO
;
}
}
module
.
exports
=
Receiver
;
/**
* Builds an error object.
*
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
* @param {String} message The error message
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
* `message`
* @param {Number} statusCode The status code
* @param {String} errorCode The exposed error code
* @return {(Error|RangeError)} The error
* @private
*/
function
error
(
ErrorCtor
,
message
,
prefix
,
statusCode
,
errorCode
)
{
const
err
=
new
ErrorCtor
(
prefix
?
`
Invalid
WebSocket
frame
:
$
{
message
}
`
:
message
);
Error
.
captureStackTrace
(
err
,
error
);
err
.
code
=
errorCode
;
err
[
kStatusCode
]
=
statusCode
;
return
err
;
}
Event Timeline
Log In to Comment