ruby_smb 2.0.3 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -2
- data/.github/workflows/verify.yml +56 -0
- data/README.md +0 -1
- data/examples/delete_file.rb +0 -0
- data/examples/net_share_enum_all.rb +0 -0
- data/examples/pipes.rb +0 -0
- data/examples/rename_file.rb +0 -0
- data/lib/ruby_smb.rb +3 -2
- data/lib/ruby_smb/client.rb +12 -8
- data/lib/ruby_smb/client/authentication.rb +5 -10
- data/lib/ruby_smb/client/echo.rb +2 -4
- data/lib/ruby_smb/client/negotiation.rb +4 -6
- data/lib/ruby_smb/client/tree_connect.rb +2 -4
- data/lib/ruby_smb/client/utils.rb +16 -10
- data/lib/ruby_smb/client/winreg.rb +1 -1
- data/lib/ruby_smb/compression.rb +7 -0
- data/lib/ruby_smb/compression/lznt1.rb +164 -0
- data/lib/ruby_smb/dcerpc.rb +3 -1
- data/lib/ruby_smb/dcerpc/ndr.rb +97 -0
- data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +37 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +32 -0
- data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
- data/lib/ruby_smb/dcerpc/request.rb +6 -0
- data/lib/ruby_smb/dcerpc/winreg.rb +2 -2
- data/lib/ruby_smb/dispatcher/socket.rb +1 -1
- data/lib/ruby_smb/error.rb +21 -5
- data/lib/ruby_smb/generic_packet.rb +11 -1
- data/lib/ruby_smb/smb1/file.rb +8 -15
- data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
- data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
- data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
- data/lib/ruby_smb/smb1/pipe.rb +8 -8
- data/lib/ruby_smb/smb1/tree.rb +13 -9
- data/lib/ruby_smb/smb2/file.rb +8 -16
- data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
- data/lib/ruby_smb/smb2/pipe.rb +8 -8
- data/lib/ruby_smb/smb2/tree.rb +12 -8
- data/lib/ruby_smb/version.rb +1 -1
- data/spec/lib/ruby_smb/client_spec.rb +17 -12
- data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
- data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
- data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +12 -0
- data/spec/lib/ruby_smb/error_spec.rb +34 -5
- data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
- data/spec/lib/ruby_smb/smb1/file_spec.rb +2 -4
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
- data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
- data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
- data/spec/lib/ruby_smb/smb1/pipe_spec.rb +23 -5
- data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
- data/spec/lib/ruby_smb/smb2/file_spec.rb +1 -3
- data/spec/lib/ruby_smb/smb2/pipe_spec.rb +2 -5
- data/spec/lib/ruby_smb/smb2/tree_spec.rb +23 -0
- data/spec/spec_helper.rb +1 -1
- metadata +42 -19
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3cfb7914736c49c84c366382e133ea0c2e9a5ecb6f0a3badceb498652bbaa76
|
4
|
+
data.tar.gz: 6f47a0ad156545a259d0496f9e1c59d50b6327889600b6acda0e93ec5c835963
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90c181090093eeb71d4ef508a2b1e7b75ccb31b7e40cdee973397f7554af1085e8b39a518f43e9e4e3eba5fd12d0677d1f059ed614eab8dfb5b0db74a956236a
|
7
|
+
data.tar.gz: 443e97e78383d44deb155c0d6e3ca8bc3923c49b2561fa41670e09d7916cdfaa7223143a29b24697699e7470cdcaf6f71a2e0e4167d8c19a7b80b2fc56b2a555
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
��� Hv�mFU�3�)���*q��qPμg�B�2�Д�0�ߤ����D�5#��f���b���5����05�f| ��0��7�o
|
1
|
+
�Ss�.�倮�����;�ͦ��c7-ȡ��.�84����R�Q�.]��^�_��w� =r��ܖhu9r(�9��0�D@�˫"c��z�N{-��F~U�3����@/��nD~,O���B��^tƯ�F>�Ż��4��tתp@ҫw�,B������j9��
|
3
2
|
��J�t��&kk��94��*��GTL9p�C
|
@@ -0,0 +1,56 @@
|
|
1
|
+
name: Verify
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- '*'
|
7
|
+
pull_request:
|
8
|
+
branches:
|
9
|
+
- '*'
|
10
|
+
|
11
|
+
jobs:
|
12
|
+
test:
|
13
|
+
runs-on: ubuntu-16.04
|
14
|
+
timeout-minutes: 40
|
15
|
+
|
16
|
+
strategy:
|
17
|
+
fail-fast: true
|
18
|
+
matrix:
|
19
|
+
ruby:
|
20
|
+
- 2.5
|
21
|
+
- 2.6
|
22
|
+
- 2.7
|
23
|
+
test_cmd:
|
24
|
+
- bundle exec rspec
|
25
|
+
|
26
|
+
env:
|
27
|
+
RAILS_ENV: test
|
28
|
+
|
29
|
+
name: Ruby ${{ matrix.ruby }} - ${{ matrix.test_cmd }}
|
30
|
+
steps:
|
31
|
+
- name: Checkout code
|
32
|
+
uses: actions/checkout@v2
|
33
|
+
|
34
|
+
- uses: actions/setup-ruby@v1
|
35
|
+
with:
|
36
|
+
ruby-version: ${{ matrix.ruby }}
|
37
|
+
|
38
|
+
- name: Setup bundler
|
39
|
+
run: |
|
40
|
+
gem install bundler
|
41
|
+
- uses: actions/cache@v2
|
42
|
+
with:
|
43
|
+
path: vendor/bundle
|
44
|
+
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
45
|
+
restore-keys: |
|
46
|
+
${{ runner.os }}-gems-
|
47
|
+
- name: Bundle install
|
48
|
+
run: |
|
49
|
+
bundle config path vendor/bundle
|
50
|
+
bundle install --jobs 4 --retry 3
|
51
|
+
- name: ${{ matrix.test_cmd }}
|
52
|
+
run: |
|
53
|
+
echo "${CMD}"
|
54
|
+
bash -c "${CMD}"
|
55
|
+
env:
|
56
|
+
CMD: ${{ matrix.test_cmd }}
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# RubySMB
|
2
2
|
|
3
|
-
[](https://travis-ci.org/rapid7/ruby_smb)
|
4
3
|
[](https://codeclimate.com/github/rapid7/ruby_smb)
|
5
4
|
[](https://coveralls.io/github/rapid7/ruby_smb?branch=master)
|
6
5
|
|
data/examples/delete_file.rb
CHANGED
File without changes
|
File without changes
|
data/examples/pipes.rb
CHANGED
File without changes
|
data/examples/rename_file.rb
CHANGED
File without changes
|
data/lib/ruby_smb.rb
CHANGED
@@ -8,8 +8,8 @@ require 'windows_error'
|
|
8
8
|
require 'windows_error/nt_status'
|
9
9
|
# A packet parsing and manipulation library for the SMB1 and SMB2 protocols
|
10
10
|
#
|
11
|
-
# [[MS-SMB] Server
|
12
|
-
# [[MS-SMB2] Server
|
11
|
+
# [[MS-SMB] Server Message Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
12
|
+
# [[MS-SMB2] Server Message Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
|
13
13
|
module RubySMB
|
14
14
|
require 'ruby_smb/error'
|
15
15
|
require 'ruby_smb/dispositions'
|
@@ -26,4 +26,5 @@ module RubySMB
|
|
26
26
|
require 'ruby_smb/smb1'
|
27
27
|
require 'ruby_smb/client'
|
28
28
|
require 'ruby_smb/crypto'
|
29
|
+
require 'ruby_smb/compression'
|
29
30
|
end
|
data/lib/ruby_smb/client.rb
CHANGED
@@ -296,7 +296,7 @@ module RubySMB
|
|
296
296
|
@smb3 = smb3
|
297
297
|
@username = username.encode('utf-8') || ''.encode('utf-8')
|
298
298
|
@max_buffer_size = MAX_BUFFER_SIZE
|
299
|
-
# These sizes will be
|
299
|
+
# These sizes will be modified during negotiation
|
300
300
|
@server_max_buffer_size = SERVER_MAX_BUFFER_SIZE
|
301
301
|
@server_max_read_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
302
302
|
@server_max_write_size = RubySMB::SMB2::File::MAX_PACKET_SIZE
|
@@ -411,8 +411,7 @@ module RubySMB
|
|
411
411
|
raise RubySMB::Error::InvalidPacket.new(
|
412
412
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
413
413
|
expected_cmd: RubySMB::SMB2::Packet::LogoffResponse::COMMAND,
|
414
|
-
|
415
|
-
received_cmd: response.smb2_header.command
|
414
|
+
packet: response
|
416
415
|
)
|
417
416
|
end
|
418
417
|
else
|
@@ -423,8 +422,7 @@ module RubySMB
|
|
423
422
|
raise RubySMB::Error::InvalidPacket.new(
|
424
423
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
425
424
|
expected_cmd: RubySMB::SMB1::Packet::LogoffResponse::COMMAND,
|
426
|
-
|
427
|
-
received_cmd: response.smb_header.command
|
425
|
+
packet: response
|
428
426
|
)
|
429
427
|
end
|
430
428
|
end
|
@@ -433,7 +431,7 @@ module RubySMB
|
|
433
431
|
end
|
434
432
|
|
435
433
|
# Sends a packet and receives the raw response through the Dispatcher.
|
436
|
-
# It will also sign the packet if
|
434
|
+
# It will also sign the packet if necessary.
|
437
435
|
#
|
438
436
|
# @param packet [RubySMB::GenericPacket] the request to be sent
|
439
437
|
# @param encrypt [Boolean] true if encryption has to be enabled for this transaction
|
@@ -473,11 +471,17 @@ module RubySMB
|
|
473
471
|
break unless is_status_pending?(smb2_header)
|
474
472
|
sleep 1
|
475
473
|
raw_response = recv_packet(encrypt: encrypt_data)
|
474
|
+
rescue IOError
|
475
|
+
# We're expecting an SMB2 packet, but the server sent an SMB1 packet
|
476
|
+
# instead. This behavior has been observed with older versions of Samba
|
477
|
+
# when something goes wrong on the server side. So, we just ignore it
|
478
|
+
# and expect the caller to handle this wrong response packet.
|
479
|
+
break
|
476
480
|
end unless version == 'SMB1'
|
477
481
|
|
478
482
|
self.sequence_counter += 1 if signing_required && !session_key.empty?
|
479
483
|
# update the SMB2 message ID according to the received Credit Charged
|
480
|
-
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.
|
484
|
+
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && self.server_supports_multi_credit
|
481
485
|
raw_response
|
482
486
|
end
|
483
487
|
|
@@ -579,7 +583,7 @@ module RubySMB
|
|
579
583
|
# @param [String] host
|
580
584
|
def net_share_enum_all(host)
|
581
585
|
tree = tree_connect("\\\\#{host}\\IPC$")
|
582
|
-
named_pipe = tree.
|
586
|
+
named_pipe = tree.open_pipe(filename: "srvsvc", write: true, read: true)
|
583
587
|
named_pipe.net_share_enum_all(host)
|
584
588
|
end
|
585
589
|
|
@@ -60,8 +60,7 @@ module RubySMB
|
|
60
60
|
raise RubySMB::Error::InvalidPacket.new(
|
61
61
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
62
62
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupLegacyResponse::COMMAND,
|
63
|
-
|
64
|
-
received_cmd: packet.smb_header.command
|
63
|
+
packet: packet
|
65
64
|
)
|
66
65
|
end
|
67
66
|
packet
|
@@ -154,8 +153,7 @@ module RubySMB
|
|
154
153
|
raise RubySMB::Error::InvalidPacket.new(
|
155
154
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
156
155
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
|
157
|
-
|
158
|
-
received_cmd: packet.smb_header.command
|
156
|
+
packet: packet
|
159
157
|
)
|
160
158
|
end
|
161
159
|
packet
|
@@ -168,8 +166,7 @@ module RubySMB
|
|
168
166
|
raise RubySMB::Error::InvalidPacket.new(
|
169
167
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
170
168
|
expected_cmd: RubySMB::SMB1::Packet::SessionSetupResponse::COMMAND,
|
171
|
-
|
172
|
-
received_cmd: packet.smb_header.command
|
169
|
+
packet: packet
|
173
170
|
)
|
174
171
|
end
|
175
172
|
|
@@ -236,8 +233,7 @@ module RubySMB
|
|
236
233
|
raise RubySMB::Error::InvalidPacket.new(
|
237
234
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
238
235
|
expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
|
239
|
-
|
240
|
-
received_cmd: packet.smb2_header.command
|
236
|
+
packet: packet
|
241
237
|
)
|
242
238
|
end
|
243
239
|
|
@@ -251,8 +247,7 @@ module RubySMB
|
|
251
247
|
raise RubySMB::Error::InvalidPacket.new(
|
252
248
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
253
249
|
expected_cmd: RubySMB::SMB2::Packet::SessionSetupResponse::COMMAND,
|
254
|
-
|
255
|
-
received_cmd: packet.smb2_header.command
|
250
|
+
packet: packet
|
256
251
|
)
|
257
252
|
end
|
258
253
|
|
data/lib/ruby_smb/client/echo.rb
CHANGED
@@ -21,8 +21,7 @@ module RubySMB
|
|
21
21
|
raise RubySMB::Error::InvalidPacket.new(
|
22
22
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
23
23
|
expected_cmd: RubySMB::SMB1::Packet::EchoResponse::COMMAND,
|
24
|
-
|
25
|
-
received_cmd: response.smb_header.command
|
24
|
+
packet: response
|
26
25
|
)
|
27
26
|
end
|
28
27
|
response
|
@@ -40,8 +39,7 @@ module RubySMB
|
|
40
39
|
raise RubySMB::Error::InvalidPacket.new(
|
41
40
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
42
41
|
expected_cmd: RubySMB::SMB2::Packet::EchoResponse::COMMAND,
|
43
|
-
|
44
|
-
received_cmd: response.smb2_header.command
|
42
|
+
packet: response
|
45
43
|
)
|
46
44
|
end
|
47
45
|
response
|
@@ -83,16 +83,14 @@ module RubySMB
|
|
83
83
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
84
84
|
expected_cmd: RubySMB::SMB1::Packet::NegotiateResponseExtended::COMMAND,
|
85
85
|
expected_custom: "extended_security=1",
|
86
|
-
|
87
|
-
received_cmd: packet.smb_header.command,
|
86
|
+
packet: packet,
|
88
87
|
received_custom: "extended_security=#{extended_security}"
|
89
88
|
)
|
90
89
|
elsif packet.packet_smb_version == 'SMB2'
|
91
90
|
raise RubySMB::Error::InvalidPacket.new(
|
92
91
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
93
92
|
expected_cmd: RubySMB::SMB2::Packet::NegotiateResponse::COMMAND,
|
94
|
-
|
95
|
-
received_cmd: packet.smb2_header.command
|
93
|
+
packet: packet
|
96
94
|
)
|
97
95
|
else
|
98
96
|
raise RubySMB::Error::InvalidPacket, 'Unknown SMB protocol version'
|
@@ -277,8 +275,8 @@ module RubySMB
|
|
277
275
|
context_type: RubySMB::SMB2::NegotiateContext::SMB2_COMPRESSION_CAPABILITIES
|
278
276
|
)
|
279
277
|
# Adding all possible compression algorithm even if we don't support
|
280
|
-
# them yet. This will force the server to disclose the
|
281
|
-
# algorithms in the
|
278
|
+
# them yet. This will force the server to disclose the supported
|
279
|
+
# algorithms in the response.
|
282
280
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZNT1
|
283
281
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77
|
284
282
|
nc.data.compression_algorithms << RubySMB::SMB2::CompressionCapabilities::LZ77_Huffman
|
@@ -33,8 +33,7 @@ module RubySMB
|
|
33
33
|
raise RubySMB::Error::InvalidPacket.new(
|
34
34
|
expected_proto: RubySMB::SMB1::SMB_PROTOCOL_ID,
|
35
35
|
expected_cmd: RubySMB::SMB1::Packet::TreeConnectResponse::COMMAND,
|
36
|
-
|
37
|
-
received_cmd: response.smb_header.command
|
36
|
+
packet: response
|
38
37
|
)
|
39
38
|
end
|
40
39
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
@@ -73,8 +72,7 @@ module RubySMB
|
|
73
72
|
raise RubySMB::Error::InvalidPacket.new(
|
74
73
|
expected_proto: RubySMB::SMB2::SMB2_PROTOCOL_ID,
|
75
74
|
expected_cmd: RubySMB::SMB2::Packet::TreeConnectResponse::COMMAND,
|
76
|
-
|
77
|
-
received_cmd: response.smb2_header.command
|
75
|
+
packet: response
|
78
76
|
)
|
79
77
|
end
|
80
78
|
unless response.status_code == WindowsError::NTStatus::STATUS_SUCCESS
|
@@ -24,19 +24,25 @@ module RubySMB
|
|
24
24
|
last_tree.id
|
25
25
|
end
|
26
26
|
|
27
|
-
def open(path, disposition=RubySMB::Dispositions::FILE_OPEN, write: false, read: true)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
27
|
+
def open(path, disposition=RubySMB::Dispositions::FILE_OPEN, write: false, read: true, pipe: false)
|
28
|
+
if pipe
|
29
|
+
file = last_tree.open_pipe(filename: path, write: write, read: read, disposition: disposition)
|
30
|
+
else
|
31
|
+
file = last_tree.open_file(filename: path, write: write, read: read, disposition: disposition)
|
32
|
+
end
|
33
|
+
@last_file_id = if file.respond_to?(:guid)
|
34
|
+
# SMB2 uses guid
|
35
|
+
file.guid.to_binary_s
|
36
|
+
elsif file.respond_to?(:fid)
|
37
|
+
# SMB1 uses fid
|
38
|
+
file.fid.to_binary_s
|
39
|
+
end
|
40
|
+
@open_files[@last_file_id] = file
|
41
|
+
@last_file_id
|
36
42
|
end
|
37
43
|
|
38
44
|
def create_pipe(path, disposition=RubySMB::Dispositions::FILE_OPEN_IF)
|
39
|
-
open(path
|
45
|
+
open(path, disposition, write: true, read: true, pipe: true)
|
40
46
|
end
|
41
47
|
|
42
48
|
#Writes data to an open file handle
|
@@ -6,7 +6,7 @@ module RubySMB
|
|
6
6
|
share = "\\\\#{host}\\IPC$"
|
7
7
|
tree = @tree_connects.find {|tree| tree.share == share}
|
8
8
|
tree = tree_connect(share) unless tree
|
9
|
-
named_pipe = tree.
|
9
|
+
named_pipe = tree.open_pipe(filename: "winreg", write: true, read: true)
|
10
10
|
if block_given?
|
11
11
|
res = yield named_pipe
|
12
12
|
named_pipe.close
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module RubySMB
|
2
|
+
module Compression
|
3
|
+
module LZNT1
|
4
|
+
def self.compress(buf, chunk_size: 0x1000)
|
5
|
+
out = ''
|
6
|
+
until buf.empty?
|
7
|
+
chunk = buf[0...chunk_size]
|
8
|
+
compressed = compress_chunk(chunk)
|
9
|
+
# chunk is compressed
|
10
|
+
if compressed.length < chunk.length
|
11
|
+
out << [0xb000 | (compressed.length - 1)].pack('v')
|
12
|
+
out << compressed
|
13
|
+
else
|
14
|
+
out << [0x3000 | (chunk.length - 1)].pack('v')
|
15
|
+
out << chunk
|
16
|
+
end
|
17
|
+
buf = buf[chunk_size..-1]
|
18
|
+
break if buf.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.compress_chunk(chunk)
|
25
|
+
blob = chunk
|
26
|
+
out = ''
|
27
|
+
pow2 = 0x10
|
28
|
+
l_mask3 = 0x1002
|
29
|
+
o_shift = 12
|
30
|
+
until blob.empty?
|
31
|
+
bits = 0
|
32
|
+
tmp = ''
|
33
|
+
i = -1
|
34
|
+
loop do
|
35
|
+
i += 1
|
36
|
+
bits >>= 1
|
37
|
+
while pow2 < (chunk.length - blob.length)
|
38
|
+
pow2 <<= 1
|
39
|
+
l_mask3 = (l_mask3 >> 1) + 1
|
40
|
+
o_shift -= 1
|
41
|
+
end
|
42
|
+
|
43
|
+
max_len = [blob.length, l_mask3].min
|
44
|
+
offset, length = find(chunk[0...(chunk.length - blob.length)], blob, max_len)
|
45
|
+
|
46
|
+
# try to find more compressed pattern
|
47
|
+
_offset2, length2 = find(chunk[0...chunk.length - blob.length + 1], blob[1..-1], max_len)
|
48
|
+
|
49
|
+
length = 0 if length < length2
|
50
|
+
|
51
|
+
if length > 0
|
52
|
+
symbol = ((offset - 1) << o_shift) | (length - 3)
|
53
|
+
tmp << [symbol].pack('v')
|
54
|
+
# set the highest bit
|
55
|
+
bits |= 0x80
|
56
|
+
blob = blob[length..-1]
|
57
|
+
else
|
58
|
+
tmp += blob[0]
|
59
|
+
blob = blob[1..-1]
|
60
|
+
end
|
61
|
+
|
62
|
+
break if blob.empty? || i == 7
|
63
|
+
end
|
64
|
+
|
65
|
+
out << [bits >> (7 - i)].pack('C')
|
66
|
+
out << tmp
|
67
|
+
end
|
68
|
+
|
69
|
+
out
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.decompress(buf, length_check: true)
|
73
|
+
out = ''
|
74
|
+
until buf.empty?
|
75
|
+
header = buf.unpack1('v')
|
76
|
+
length = (header & 0xfff) + 1
|
77
|
+
raise EncodingError, 'invalid chunk length' if length_check && length > (buf.length - 2)
|
78
|
+
|
79
|
+
chunk = buf[2...length + 2]
|
80
|
+
out << if header & 0x8000 == 0
|
81
|
+
chunk
|
82
|
+
else
|
83
|
+
decompress_chunk(chunk)
|
84
|
+
end
|
85
|
+
buf = buf[length + 2..-1]
|
86
|
+
end
|
87
|
+
|
88
|
+
out
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.decompress_chunk(chunk)
|
92
|
+
out = ''
|
93
|
+
until chunk.empty?
|
94
|
+
flags = chunk[0].unpack1('C')
|
95
|
+
chunk = chunk[1..-1]
|
96
|
+
8.times do |i|
|
97
|
+
if (flags >> i & 1) == 0
|
98
|
+
out << chunk[0]
|
99
|
+
chunk = chunk[1..-1]
|
100
|
+
else
|
101
|
+
flag = chunk.unpack1('v')
|
102
|
+
pos = out.length - 1
|
103
|
+
l_mask = 0xfff
|
104
|
+
o_shift = 12
|
105
|
+
while pos >= 0x10
|
106
|
+
l_mask >>= 1
|
107
|
+
o_shift -= 1
|
108
|
+
pos >>= 1
|
109
|
+
end
|
110
|
+
|
111
|
+
length = (flag & l_mask) + 3
|
112
|
+
offset = (flag >> o_shift) + 1
|
113
|
+
|
114
|
+
if length >= offset
|
115
|
+
out_offset = out[-offset..-1]
|
116
|
+
tmp = out_offset * (0xfff / out_offset.length + 1)
|
117
|
+
out << tmp[0...length]
|
118
|
+
else
|
119
|
+
out << out[-offset..-offset + length - 1]
|
120
|
+
end
|
121
|
+
chunk = chunk[2..-1]
|
122
|
+
end
|
123
|
+
break if chunk.empty?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
out
|
128
|
+
end
|
129
|
+
|
130
|
+
class << self
|
131
|
+
private
|
132
|
+
|
133
|
+
def find(src, target, max_len)
|
134
|
+
result_offset = 0
|
135
|
+
result_length = 0
|
136
|
+
1.upto(max_len - 1) do |i|
|
137
|
+
offset = src.rindex(target[0...i])
|
138
|
+
next if offset.nil?
|
139
|
+
|
140
|
+
tmp_offset = src.length - offset
|
141
|
+
tmp_length = i
|
142
|
+
if tmp_offset == tmp_length
|
143
|
+
src_offset = src[offset..-1]
|
144
|
+
tmp = src_offset * (0xfff / src_offset.length + 1)
|
145
|
+
i.upto(max_len) do |j|
|
146
|
+
offset = tmp.rindex(target[0...j])
|
147
|
+
break if offset.nil?
|
148
|
+
|
149
|
+
tmp_length = j
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if tmp_length > result_length
|
154
|
+
result_offset = tmp_offset
|
155
|
+
result_length = tmp_length
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
result_length < 3 ? [0, 0] : [result_offset, result_length]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|