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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -2
  4. data/.github/workflows/verify.yml +56 -0
  5. data/README.md +0 -1
  6. data/examples/delete_file.rb +0 -0
  7. data/examples/net_share_enum_all.rb +0 -0
  8. data/examples/pipes.rb +0 -0
  9. data/examples/rename_file.rb +0 -0
  10. data/lib/ruby_smb.rb +3 -2
  11. data/lib/ruby_smb/client.rb +12 -8
  12. data/lib/ruby_smb/client/authentication.rb +5 -10
  13. data/lib/ruby_smb/client/echo.rb +2 -4
  14. data/lib/ruby_smb/client/negotiation.rb +4 -6
  15. data/lib/ruby_smb/client/tree_connect.rb +2 -4
  16. data/lib/ruby_smb/client/utils.rb +16 -10
  17. data/lib/ruby_smb/client/winreg.rb +1 -1
  18. data/lib/ruby_smb/compression.rb +7 -0
  19. data/lib/ruby_smb/compression/lznt1.rb +164 -0
  20. data/lib/ruby_smb/dcerpc.rb +3 -1
  21. data/lib/ruby_smb/dcerpc/ndr.rb +97 -0
  22. data/lib/ruby_smb/dcerpc/netlogon.rb +101 -0
  23. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request.rb +37 -0
  24. data/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response.rb +26 -0
  25. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request.rb +37 -0
  26. data/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response.rb +23 -0
  27. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request.rb +32 -0
  28. data/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response.rb +24 -0
  29. data/lib/ruby_smb/dcerpc/request.rb +6 -0
  30. data/lib/ruby_smb/dcerpc/winreg.rb +2 -2
  31. data/lib/ruby_smb/dispatcher/socket.rb +1 -1
  32. data/lib/ruby_smb/error.rb +21 -5
  33. data/lib/ruby_smb/generic_packet.rb +11 -1
  34. data/lib/ruby_smb/smb1/file.rb +8 -15
  35. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +0 -1
  36. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +0 -1
  37. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +1 -2
  38. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +1 -13
  39. data/lib/ruby_smb/smb1/pipe.rb +8 -8
  40. data/lib/ruby_smb/smb1/tree.rb +13 -9
  41. data/lib/ruby_smb/smb2/file.rb +8 -16
  42. data/lib/ruby_smb/smb2/packet/compression_transform_header.rb +4 -0
  43. data/lib/ruby_smb/smb2/pipe.rb +8 -8
  44. data/lib/ruby_smb/smb2/tree.rb +12 -8
  45. data/lib/ruby_smb/version.rb +1 -1
  46. data/spec/lib/ruby_smb/client_spec.rb +17 -12
  47. data/spec/lib/ruby_smb/compression/lznt1_spec.rb +32 -0
  48. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_request_spec.rb +69 -0
  49. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_authenticate3_response_spec.rb +53 -0
  50. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_request_spec.rb +69 -0
  51. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_password_set2_response_spec.rb +37 -0
  52. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_request_spec.rb +45 -0
  53. data/spec/lib/ruby_smb/dcerpc/netlogon/netr_server_req_challenge_response_spec.rb +37 -0
  54. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +12 -0
  55. data/spec/lib/ruby_smb/error_spec.rb +34 -5
  56. data/spec/lib/ruby_smb/generic_packet_spec.rb +7 -0
  57. data/spec/lib/ruby_smb/smb1/file_spec.rb +2 -4
  58. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +0 -1
  59. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +0 -1
  60. data/spec/lib/ruby_smb/smb1/packet/trans2/open2_response_spec.rb +0 -5
  61. data/spec/lib/ruby_smb/smb1/packet/trans2/set_file_information_response_spec.rb +0 -6
  62. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +23 -5
  63. data/spec/lib/ruby_smb/smb1/tree_spec.rb +22 -0
  64. data/spec/lib/ruby_smb/smb2/file_spec.rb +1 -3
  65. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +2 -5
  66. data/spec/lib/ruby_smb/smb2/tree_spec.rb +23 -0
  67. data/spec/spec_helper.rb +1 -1
  68. metadata +42 -19
  69. metadata.gz.sig +0 -0
  70. data/.travis.yml +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cce717ed3175b8027db1e414fae576e984ff4c88a158568014b4c4eb06406f7
4
- data.tar.gz: fb9223d4f15a3ed99aca553523dd957ceb3173cef87b4ba3d29a409ae4baffda
3
+ metadata.gz: e3cfb7914736c49c84c366382e133ea0c2e9a5ecb6f0a3badceb498652bbaa76
4
+ data.tar.gz: 6f47a0ad156545a259d0496f9e1c59d50b6327889600b6acda0e93ec5c835963
5
5
  SHA512:
6
- metadata.gz: 94319bee008719b00933ab82140fafd65c13344858f6677ecb26502cf22d087de0200c0feab9304959ac38df17fcac6540707e2b55457d8d92370484fbfbcd06
7
- data.tar.gz: bf55746b294b17d9a4633e1344d976bfdc13df89e96c1ea5861c55999fdd0e1367d25b0e7e1ec455d4666a9fe0d41bfc7b943c84bc00606e2705f952319f4cb2
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
- ����Zjݽ{1E��������β�Ф�H˟�?�iaDl���t�s:�Db�*�h����F �6KH�?���ga���U>to5�Ki_V�����_�C���?�oʃN������B���h9�"�#
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~U3����@/��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
- [![Build Status](https://travis-ci.org/rapid7/ruby_smb.svg?branch=master)](https://travis-ci.org/rapid7/ruby_smb)
4
3
  [![Code Climate](https://codeclimate.com/github/rapid7/ruby_smb.png)](https://codeclimate.com/github/rapid7/ruby_smb)
5
4
  [![Coverage Status](https://coveralls.io/repos/github/rapid7/ruby_smb/badge.svg?branch=master)](https://coveralls.io/github/rapid7/ruby_smb?branch=master)
6
5
 
File without changes
File without changes
data/examples/pipes.rb CHANGED
File without changes
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 Mesage Block (SMB) Protocol Version 1](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
12
- # [[MS-SMB2] Server Mesage Block (SMB) Protocol Versions 2 and 3](https://msdn.microsoft.com/en-us/library/cc246482.aspx)
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
@@ -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 modifed during negotiation
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
- received_proto: response.smb2_header.protocol,
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
- received_proto: response.smb_header.protocol,
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 neccessary.
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.dialect != '0x0202'
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.open_file(filename: "srvsvc", write: true, read: true)
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
- received_proto: packet.smb_header.protocol,
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
- received_proto: packet.smb_header.protocol,
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
- received_proto: packet.smb_header.protocol,
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
- received_proto: packet.smb2_header.protocol,
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
- received_proto: packet.smb2_header.protocol,
255
- received_cmd: packet.smb2_header.command
250
+ packet: packet
256
251
  )
257
252
  end
258
253
 
@@ -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
- received_proto: response.smb_header.protocol,
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
- received_proto: response.smb2_header.protocol,
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
- received_proto: packet.smb_header.protocol,
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
- received_proto: packet.smb2_header.protocol,
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 support
281
- # algorithms in the repsonse.
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
- received_proto: response.smb_header.protocol,
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
- received_proto: response.smb2_header.protocol,
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
- file = last_tree.open_file(filename: path.sub(/^\\/, ''), write: write, read: read, disposition: disposition)
29
- @last_file_id = if file.respond_to?(:guid)
30
- file.guid.to_binary_s
31
- elsif file.respond_to?(:fid)
32
- file.fid.to_binary_s
33
- end
34
- @open_files[@last_file_id] = file
35
- @last_file_id
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.gsub(/\\/, ''), disposition, write: true, read: true)
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.open_file(filename: "winreg", write: true, read: true)
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,7 @@
1
+ module RubySMB
2
+ module Compression
3
+
4
+ require 'ruby_smb/compression/lznt1'
5
+
6
+ end
7
+ end
@@ -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