use
5.012;
catch_run(
"[inflate-frames]"
);
my
$create_pair
=
sub
{
my
$configure
=
shift
;
my
$req
= {
ws_key
=>
"dGhlIHNhbXBsZSBub25jZQ=="
,
};
local
$Test::Builder::Level
=
$Test::Builder::Level
+ 1;
my
$client
= Protocol::WebSocket::Fast::ClientParser->new;
my
$server
= Protocol::WebSocket::Fast::ServerParser->new;
$configure
->(
$client
,
$server
)
if
$configure
;
my
$str
=
$client
->connect_request(
$req
);
my
$creq
=
$server
->
accept
(
$str
) or
die
"should not happen"
;
my
$res_str
=
$creq
->error ?
$server
->accept_error :
$server
->accept_response;
my
$server_deflate
=
$client
->deflate_config &&
$server
->deflate_config;
like
$str
,
qr/permessage-deflate/
if
(
$client
->deflate_config);
like
$res_str
,
qr/permessage-deflate/
if
(
$server
->deflate_config);
ok
$server
->is_deflate_active
if
(
$server_deflate
);
$client
->
connect
(
$res_str
);
ok
$client
->established;
ok
$client
->is_deflate_active
if
(
$server_deflate
);
return
(
$client
,
$server
);
};
subtest
'empty payload frame'
=>
sub
{
my
$payload
=
""
;
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(
$payload
);
my
(
$f
) =
$c
->get_frames(
$bin
);
ok !
$f
->payload;
};
subtest
'tiny payload'
=>
sub
{
my
$payload
=
"preved"
;
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(
$payload
);
my
(
$f
) =
$c
->get_frames(
$bin
);
is
$f
->payload,
$payload
;
};
subtest
'medium payload'
=>
sub
{
my
@payload
= (
'0'
) x (1923);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(
$payload
);
my
(
$f
) =
$c
->get_frames(
$bin
);
is
$f
->payload,
$payload
;
};
subtest
'medium payload (fragmented)'
=>
sub
{
my
@payload
= (
'0'
) x (1923);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(\
@payload
);
my
(
$f
) =
$c
->get_frames(
$bin
);
is
$f
->payload,
$payload
;
};
subtest
'large payload'
=>
sub
{
my
@payload
= (
'0'
) x (1024 * 1024);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(
$payload
);
my
(
$f
) =
$c
->get_frames(
$bin
);
is
$f
->payload,
$payload
;
};
subtest
'1-frame-message (tiny payload)'
=>
sub
{
my
$payload
=
"hello-world"
;
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
deflate
=> 1)->
send
(
$payload
, 1);
my
(
$m
) =
$c
->get_messages(
$bin
);
ok
$m
;
is
$m
->payload,
$payload
;
};
subtest
'message, 2 frames, context_takeover = true, tiny payload'
=>
sub
{
my
@payload
= (
'a'
,
'b'
);
my
$payload
=
join
(
''
,
@payload
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$builder
=
$s
->start_message(
deflate
=> 1);
my
$bin1
=
$builder
->
send
(\
@payload
);
my
$bin2
=
$builder
->
send
(\
@payload
, 1);
my
(
$m
) =
$c
->get_messages(
$bin1
.
$bin2
);
ok
$m
;
is
$m
->payload,
$payload
;
};
subtest
'message, 2 frames, context_takeover = true, medium payload'
=>
sub
{
my
@payload
= (
'0'
) x (1024);
my
$payload
=
join
(
''
,
@payload
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$builder
=
$s
->start_message(
deflate
=> 1);
my
$bin1
=
$builder
->
send
(\
@payload
);
my
$bin2
=
$builder
->
send
(\
@payload
, 1);
note
"l1 = "
,
length
(
$bin1
),
", l2 = "
,
length
(
$bin2
);
my
(
$m
) =
$c
->get_messages(
$bin1
.
$bin2
);
ok
$m
;
is
$m
->payload,
$payload
;
};
subtest
'2 messages, 2 frames, server_context_takeover = false, medium payload'
=>
sub
{
my
@payload
= (
'0'
) x (1024);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$c
->configure({
deflate
=> {
server_no_context_takeover
=> 1}});
});
my
$bin1
=
$s
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
my
$bin2
=
$s
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
note
"l1 = "
,
length
(
$bin1
),
", l2 = "
,
length
(
$bin2
);
is
length
(
$bin1
),
length
(
$bin2
),
"make sure there is no context takeover"
;
my
@m
=
$c
->get_messages(
$bin1
.
$bin2
);
is
scalar
(
@m
), 2;
my
(
$m1
,
$m2
) =
@m
;
is
$m1
->payload,
$payload
;
is
$m2
->payload,
$payload
;
};
subtest
'2 messages, 2 frames, client_context_takeover = false, medium payload'
=>
sub
{
my
@payload
= (
'0'
) x (1024);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$c
->configure({
deflate
=> {
client_no_context_takeover
=> 1}});
});
my
$bin1
=
$c
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
my
$bin2
=
$c
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
note
"l1 = "
,
length
(
$bin1
),
", l2 = "
,
length
(
$bin2
);
is
length
(
$bin1
),
length
(
$bin2
),
"make sure there is no context takeover"
;
my
@m
=
$s
->get_messages(
$bin1
.
$bin2
);
is
scalar
(
@m
), 2;
my
(
$m1
,
$m2
) =
@m
;
is
$m1
->payload,
$payload
;
is
$m2
->payload,
$payload
;
};
subtest
'2 messages, 2 frames, server_context_takeover = false = client_context_takeover = false, medium payload, custom windows'
=>
sub
{
my
@payload
= (
'0'
) x (1024 * 10);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$c
->configure({
deflate
=> {
client_no_context_takeover
=> 1,
server_no_context_takeover
=> 1,
client_max_window_bits
=> 10,
server_max_window_bits
=> 11,
compression_level
=> 1,
}});
});
my
$bin1
=
$c
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
my
$bin2
=
$c
->start_message(
deflate
=> 1)->
send
(\
@payload
, 1);
note
"l1 = "
,
length
(
$bin1
),
", l2 = "
,
length
(
$bin2
);
is
length
(
$bin1
),
length
(
$bin2
),
"make sure there is no context takeover"
;
my
@m
=
$s
->get_messages(
$bin1
.
$bin2
);
is
scalar
(
@m
), 2;
my
(
$m1
,
$m2
) =
@m
;
is
$m1
->payload,
$payload
;
is
$m2
->payload,
$payload
;
};
subtest
"multiframe message"
=>
sub
{
my
$payloads
= [
qw/first second third/
];
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$c
->send_message_multiframe(
deflate
=> 1,
payload
=>
$payloads
);
my
(
$m
) =
$s
->get_messages(
$bin
);
is
$m
->payload,
join
(
''
,
@$payloads
);
};
subtest
"multiframe message (with empty pieces)"
=>
sub
{
my
$payloads
= [
''
,
'hello'
,
''
];
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$c
->send_message_multiframe(
deflate
=> 1,
payload
=>
$payloads
);
my
(
$m
) =
$s
->get_messages(
$bin
);
is
$m
->payload,
join
(
''
,
@$payloads
);
};
subtest
"multiframe message (empty)"
=>
sub
{
my
$payloads
= [
''
,
''
,
''
];
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$c
->send_message_multiframe(
deflate
=> 1,
payload
=>
$payloads
);
my
(
$m
) =
$s
->get_messages(
$bin
);
ok
$m
;
ok !
$m
->payload;
};
subtest
'corrupted frame'
=>
sub
{
my
@payload
= (
'0'
) x (1923);
my
$payload
=
join
(
''
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$bin
=
$s
->start_message(
final
=> 1,
deflate
=> 1)->
send
(
$payload
);
my
$deflate_payload
=
substr
(
$bin
, 2);
my
$forged_bin
=
substr
(
$bin
, 0, 2) .
"xx"
.
substr
(
$deflate_payload
, 2);
my
(
$f
) =
$c
->get_frames(
$forged_bin
);
like
$f
->error,
qr/zlib::inflate error/
;
};
subtest
'corrupted 2nd frame from 3'
=>
sub
{
my
@payload
= (
'0'
) x (1923);
my
$payload
=
join
(
''
,
@payload
,
@payload
,
@payload
);
my
(
$c
,
$s
) =
$create_pair
->();
my
$builder
=
$s
->start_message(
deflate
=> 1);
my
$bin_1
=
$builder
->
send
(\
@payload
);
my
$bin_2
=
$builder
->
send
(\
@payload
);
my
$bin_3
=
$builder
->
send
(\
@payload
, 1);
my
$bin_2_payload
=
substr
(
$bin_2
, 2);
my
$bin_2_forged
=
substr
(
$bin_2
, 0, 2) .
substr
(
$bin_2_payload
, 0, 2) .
'xx'
.
substr
(
$bin_2_payload
, 4);
my
(
$m
) =
$c
->get_messages(
$bin_1
.
$bin_2_forged
.
$bin_3
);
ok
$m
;
like
$m
->error,
qr/zlib::inflate error/
;
};
subtest
"compression threshold"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$s
->configure({
deflate
=> {
compression_threshold
=> 5 }});
});
my
$payload_1
=
"1234"
;
my
$bin_1
=
$s
->send_message(
payload
=>
$payload_1
);
note
$bin_1
;
is(
substr
(
$bin_1
, 2),
$payload_1
);
my
$payload_2
=
"12345"
;
my
$bin_2
=
$s
->send_message(
payload
=>
$payload_2
,
opcode
=> OPCODE_BINARY);
note
$bin_2
;
is(
substr
(
$bin_2
, 2),
$payload_2
,
"binary payload isn't compressed by default"
);
my
$bin_3
=
$s
->send_message(
payload
=>
$payload_2
,
opcode
=> OPCODE_TEXT);
note
$bin_3
;
isnt(
substr
(
$bin_3
, 2),
$payload_2
,
"text payload is compressed by default"
);
};
subtest
"no_deflate"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$s
->no_deflate;
});
my
$payload
=
"1234"
;
my
$bin
=
$s
->send_message(
payload
=>
$payload
);
note
$bin
;
is(
substr
(
$bin
, 2),
$payload
);
};
subtest
"SRV-1236/12.3 inflate error"
=>
sub
{
my
$data
= read_file(__FILE__);
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$_
->configure({
client_no_context_takeover
=> 1})
for
(
$c
,
$s
);
});
for
(
my
$i
= 1;
$i
< 100; ++
$i
) {
my
$sample
=
substr
(
$data
, 1, 256);
my
$bin
=
$c
->send_message(
payload
=>
$sample
);
my
(
$m
) =
$c
->get_messages(
$bin
);
is
$m
->payload,
$sample
;
}
};
subtest
"zip-bomb prevention (check max_message_size)"
=>
sub
{
subtest
"single frame/message exceeds limit"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$_
->configure({
max_message_size
=> 100,
deflate
=> {
compression_threshold
=> 0 }})
for
(
$c
,
$s
);
});
my
$payload
=
join
(
""
, (
'0'
) x (101));
my
$bin
=
$s
->send_message(
payload
=>
$payload
,
opcode
=> OPCODE_TEXT);
note
"payload length: "
,
length
(
$bin
);
my
(
$m
) =
$c
->get_messages(
$bin
);
ok
$m
;
is
$m
->error, Protocol::WebSocket::Fast::Error::max_message_size;
};
subtest
"multi-frame/message exceeds limit"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$_
->configure({
max_message_size
=> 100,
deflate
=> {
compression_threshold
=> 0 }})
for
(
$c
,
$s
);
});
my
$payload_1
=
join
(
""
, (
'0'
) x (60));
my
$payload_2
=
$payload_1
;
my
$builder
=
$s
->start_message(
final
=> 0,
deflate
=> 1);
my
$bin_1
=
$builder
->
send
(
$payload_1
);
my
$bin_2
=
$builder
->
send
(
$payload_2
, 1);
my
$bin
=
$bin_1
.
$bin_2
;
note
"payload lengths: "
,
length
(
$bin_1
) ,
" and "
,
length
(
$bin_2
);
my
(
$m
) =
$c
->get_messages(
$bin
);
ok
$m
;
is
$m
->error, Protocol::WebSocket::Fast::Error::max_message_size;
};
subtest
"exact message size is allowed"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$_
->configure({
max_message_size
=> 100,
deflate
=> {
compression_threshold
=> 0 }})
for
(
$c
,
$s
);
});
my
$payload
=
join
(
""
, (
'0'
) x (100));
my
$bin
=
$s
->send_message(
payload
=>
$payload
,
opcode
=> OPCODE_TEXT);
note
"payload length: "
,
length
(
$bin
);
my
(
$m
) =
$c
->get_messages(
$bin
);
ok
$m
;
ok !
$m
->error;
ok
$m
->payload,
$payload
;
};
};
subtest
"windowBits == 8 zlib tests"
=>
sub
{
subtest
"8-bit config"
=>
sub
{
my
$req
= {
ws_key
=>
"dGhlIHNhbXBsZSBub25jZQ=="
,
};
my
$client
= Protocol::WebSocket::Fast::ClientParser->new;
my
$server
= Protocol::WebSocket::Fast::ServerParser->new;
my
$config
= {
deflate
=> {
compression_threshold
=> 0,
server_max_window_bits
=> 8,
client_max_window_bits
=> 8,
}};
$_
->configure(
$config
)
for
(
$client
,
$server
);
my
$str
=
$client
->connect_request(
$req
);
my
$creq
=
$server
->
accept
(
$str
) or
die
"should not happen"
;
my
$res_str
=
$creq
->error ?
$server
->accept_error :
$server
->accept_response;
my
$server_deflate
=
$client
->deflate_config &&
$server
->deflate_config;
like
$str
,
qr/permessage-deflate/
if
(
$client
->deflate_config);
unlike
$res_str
,
qr/permessage-deflate/
if
(
$server
->deflate_config);
ok !
$server
->is_deflate_active;
$client
->
connect
(
$res_str
);
ok
$client
->established;
ok !
$client
->is_deflate_active;
};
subtest
"9-bit config is allowed"
=>
sub
{
my
(
$c
,
$s
) =
$create_pair
->(
sub
{
my
(
$c
,
$s
) =
@_
;
$_
->configure({
deflate
=> {
compression_threshold
=> 0,
server_max_window_bits
=> 9,
client_max_window_bits
=> 9,
}})
for
(
$c
,
$s
);
});
ok
$c
;
ok
$s
;
};
};
sub
read_file {
my
$file
=
shift
;
open
my
$fh
,
'<'
,
$file
or
die
"cannot open $file: $!"
;
local
$/ =
undef
;
my
$ret
= <
$fh
>;
close
$fh
;
return
$ret
;
}
done_testing;