dalli 4.3.10 → 5.0.0

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -161
  3. data/Gemfile +1 -1
  4. data/README.md +4 -55
  5. data/lib/dalli/client.rb +39 -98
  6. data/lib/dalli/compressor.rb +5 -53
  7. data/lib/dalli/instrumentation.rb +1 -1
  8. data/lib/dalli/key_manager.rb +2 -28
  9. data/lib/dalli/options.rb +2 -2
  10. data/lib/dalli/pipelined_getter.rb +32 -46
  11. data/lib/dalli/pipelined_setter.rb +8 -17
  12. data/lib/dalli/protocol/base.rb +31 -160
  13. data/lib/dalli/protocol/connection_manager.rb +20 -137
  14. data/lib/dalli/protocol/meta.rb +15 -54
  15. data/lib/dalli/protocol/{meta/request_formatter.rb → request_formatter.rb} +8 -21
  16. data/lib/dalli/protocol/response_buffer.rb +12 -27
  17. data/lib/dalli/protocol/{meta/response_processor.rb → response_processor.rb} +32 -64
  18. data/lib/dalli/protocol/server_config_parser.rb +1 -1
  19. data/lib/dalli/protocol/value_compressor.rb +7 -36
  20. data/lib/dalli/protocol/value_serializer.rb +1 -1
  21. data/lib/dalli/protocol.rb +9 -7
  22. data/lib/dalli/ring.rb +2 -2
  23. data/lib/dalli/servers_arg_normalizer.rb +1 -1
  24. data/lib/dalli/socket.rb +0 -29
  25. data/lib/dalli/version.rb +2 -2
  26. data/lib/dalli.rb +0 -3
  27. data/lib/rack/session/dalli.rb +2 -1
  28. metadata +7 -14
  29. data/lib/dalli/json_serializer.rb +0 -28
  30. data/lib/dalli/protocol/binary/request_formatter.rb +0 -116
  31. data/lib/dalli/protocol/binary/response_header.rb +0 -36
  32. data/lib/dalli/protocol/binary/response_processor.rb +0 -245
  33. data/lib/dalli/protocol/binary/sasl_authentication.rb +0 -60
  34. data/lib/dalli/protocol/binary.rb +0 -202
  35. data/lib/dalli/protocol_deprecations.rb +0 -45
  36. /data/lib/dalli/protocol/{meta/key_regularizer.rb → key_regularizer.rb} +0 -0
data/lib/dalli/socket.rb CHANGED
@@ -181,41 +181,12 @@ module Dalli
181
181
  [seconds, microseconds].pack(timeval_pack_format(sock))
182
182
  end
183
183
 
184
- # Closes the TCP socket if the handshake fails or times out, so a failed
185
- # attempt doesn't hold a file descriptor until GC.
186
184
  def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
187
- warn_if_unverified(ssl_context)
188
- connected = false
189
185
  ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
190
186
  ssl_socket.hostname = host
191
187
  ssl_socket.sync_close = true
192
188
  ssl_socket.connect
193
- connected = true
194
189
  ssl_socket
195
- ensure
196
- tcp_socket.close unless connected
197
- end
198
-
199
- # Class variable to track whether the TLS verification warning has been logged
200
- @@ssl_verification_warning_logged = false # rubocop:disable Style/ClassVars
201
-
202
- # Verification comes entirely from the caller's ssl_context, and a bare
203
- # SSLContext.new verifies nothing, so warn (once per process) when the
204
- # certificate or the hostname won't be checked.
205
- def self.warn_if_unverified(ssl_context)
206
- return if @@ssl_verification_warning_logged
207
-
208
- problem = if !ssl_context.verify_mode.to_i.anybits?(::OpenSSL::SSL::VERIFY_PEER)
209
- 'verify_mode is VERIFY_NONE, so the server certificate is not checked'
210
- elsif !ssl_context.verify_hostname
211
- 'verify_hostname is false, so the certificate is not checked against the hostname'
212
- end
213
- return unless problem
214
-
215
- Dalli.logger.warn "SECURITY WARNING: Dalli's ssl_context #{problem}. " \
216
- 'Use ssl_context.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER, ' \
217
- 'verify_hostname: true) to verify the server.'
218
- @@ssl_verification_warning_logged = true # rubocop:disable Style/ClassVars
219
190
  end
220
191
  end
221
192
 
data/lib/dalli/version.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dalli
4
- VERSION = '4.3.10'
4
+ VERSION = '5.0.0'
5
5
 
6
- MIN_SUPPORTED_MEMCACHED_VERSION = '1.4'
6
+ MIN_SUPPORTED_MEMCACHED_VERSION = '1.6'
7
7
  end
data/lib/dalli.rb CHANGED
@@ -62,8 +62,6 @@ require_relative 'dalli/version'
62
62
  require_relative 'dalli/instrumentation'
63
63
 
64
64
  require_relative 'dalli/compressor'
65
- require_relative 'dalli/json_serializer'
66
- require_relative 'dalli/protocol_deprecations'
67
65
  require_relative 'dalli/client'
68
66
  require_relative 'dalli/key_manager'
69
67
  require_relative 'dalli/pipelined_getter'
@@ -72,7 +70,6 @@ require_relative 'dalli/pipelined_deleter'
72
70
  require_relative 'dalli/ring'
73
71
  require_relative 'dalli/protocol'
74
72
  require_relative 'dalli/protocol/base'
75
- require_relative 'dalli/protocol/binary'
76
73
  require_relative 'dalli/protocol/connection_manager'
77
74
  require_relative 'dalli/protocol/meta'
78
75
  require_relative 'dalli/protocol/response_buffer'
@@ -16,10 +16,11 @@ module Rack
16
16
  attr_reader :data
17
17
 
18
18
  # Don't freeze this until we fix the specs/implementation
19
- # rubocop:disable-next Style/MutableConstant
19
+ # rubocop:disable Style/MutableConstant
20
20
  DEFAULT_DALLI_OPTIONS = {
21
21
  namespace: 'rack:session'
22
22
  }
23
+ # rubocop:enable Style/MutableConstant
23
24
 
24
25
  # Brings in a new Rack::Session::Dalli middleware with the given
25
26
  # `:memcache_server`. The server is either a hostname, or a
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dalli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.10
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter M. Goldstein
@@ -41,7 +41,6 @@ files:
41
41
  - lib/dalli/client.rb
42
42
  - lib/dalli/compressor.rb
43
43
  - lib/dalli/instrumentation.rb
44
- - lib/dalli/json_serializer.rb
45
44
  - lib/dalli/key_manager.rb
46
45
  - lib/dalli/options.rb
47
46
  - lib/dalli/pid_cache.rb
@@ -50,24 +49,18 @@ files:
50
49
  - lib/dalli/pipelined_setter.rb
51
50
  - lib/dalli/protocol.rb
52
51
  - lib/dalli/protocol/base.rb
53
- - lib/dalli/protocol/binary.rb
54
- - lib/dalli/protocol/binary/request_formatter.rb
55
- - lib/dalli/protocol/binary/response_header.rb
56
- - lib/dalli/protocol/binary/response_processor.rb
57
- - lib/dalli/protocol/binary/sasl_authentication.rb
58
52
  - lib/dalli/protocol/connection_manager.rb
53
+ - lib/dalli/protocol/key_regularizer.rb
59
54
  - lib/dalli/protocol/meta.rb
60
- - lib/dalli/protocol/meta/key_regularizer.rb
61
- - lib/dalli/protocol/meta/request_formatter.rb
62
- - lib/dalli/protocol/meta/response_processor.rb
55
+ - lib/dalli/protocol/request_formatter.rb
63
56
  - lib/dalli/protocol/response_buffer.rb
57
+ - lib/dalli/protocol/response_processor.rb
64
58
  - lib/dalli/protocol/server_config_parser.rb
65
59
  - lib/dalli/protocol/string_marshaller.rb
66
60
  - lib/dalli/protocol/ttl_sanitizer.rb
67
61
  - lib/dalli/protocol/value_compressor.rb
68
62
  - lib/dalli/protocol/value_marshaller.rb
69
63
  - lib/dalli/protocol/value_serializer.rb
70
- - lib/dalli/protocol_deprecations.rb
71
64
  - lib/dalli/ring.rb
72
65
  - lib/dalli/servers_arg_normalizer.rb
73
66
  - lib/dalli/socket.rb
@@ -78,7 +71,7 @@ licenses:
78
71
  - MIT
79
72
  metadata:
80
73
  bug_tracker_uri: https://github.com/petergoldstein/dalli/issues
81
- changelog_uri: https://github.com/petergoldstein/dalli/blob/v4.3/CHANGELOG.md
74
+ changelog_uri: https://github.com/petergoldstein/dalli/blob/main/CHANGELOG.md
82
75
  rubygems_mfa_required: 'true'
83
76
  rdoc_options: []
84
77
  require_paths:
@@ -87,14 +80,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
80
  requirements:
88
81
  - - ">="
89
82
  - !ruby/object:Gem::Version
90
- version: '3.1'
83
+ version: '3.3'
91
84
  required_rubygems_version: !ruby/object:Gem::Requirement
92
85
  requirements:
93
86
  - - ">="
94
87
  - !ruby/object:Gem::Version
95
88
  version: '0'
96
89
  requirements: []
97
- rubygems_version: 4.0.23
90
+ rubygems_version: 4.0.6
98
91
  specification_version: 4
99
92
  summary: High performance memcached client for Ruby
100
93
  test_files: []
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Dalli
6
- ##
7
- # A serializer that stores values as JSON and never creates Ruby objects
8
- # from what it reads.
9
- #
10
- # Passing the JSON module itself as the serializer (serializer: JSON) reads
11
- # values back with JSON.load, which on json gem versions before 3.0 honors
12
- # "json_class" and instantiates other classes when the json gem's additions
13
- # are loaded. This serializer reads with JSON.parse, so a value written to
14
- # memcached by someone else can only come back as plain hashes, arrays,
15
- # strings, numbers, booleans and nil.
16
- #
17
- # Dalli::Client.new(servers, serializer: Dalli::JSONSerializer)
18
- ##
19
- module JSONSerializer
20
- def self.dump(value)
21
- JSON.generate(value)
22
- end
23
-
24
- def self.load(data)
25
- JSON.parse(data)
26
- end
27
- end
28
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dalli
4
- module Protocol
5
- class Binary
6
- ##
7
- # Class that encapsulates logic for formatting binary protocol requests
8
- # to memcached.
9
- ##
10
- class RequestFormatter
11
- REQUEST = 0x80
12
-
13
- OPCODES = {
14
- get: 0x00,
15
- set: 0x01,
16
- add: 0x02,
17
- replace: 0x03,
18
- delete: 0x04,
19
- incr: 0x05,
20
- decr: 0x06,
21
- flush: 0x08,
22
- noop: 0x0A,
23
- version: 0x0B,
24
- getkq: 0x0D,
25
- append: 0x0E,
26
- prepend: 0x0F,
27
- stat: 0x10,
28
- setq: 0x11,
29
- addq: 0x12,
30
- replaceq: 0x13,
31
- deleteq: 0x14,
32
- incrq: 0x15,
33
- decrq: 0x16,
34
- flushq: 0x18,
35
- appendq: 0x19,
36
- prependq: 0x1A,
37
- touch: 0x1C,
38
- gat: 0x1D,
39
- auth_negotiation: 0x20,
40
- auth_request: 0x21,
41
- auth_continue: 0x22
42
- }.freeze
43
-
44
- REQ_HEADER_FORMAT = 'CCnCCnNNQ'
45
-
46
- KEY_ONLY = 'a*'
47
- TTL_AND_KEY = 'Na*'
48
- KEY_AND_VALUE = 'a*a*'
49
- INCR_DECR = 'NNNNNa*'
50
- TTL_ONLY = 'N'
51
- NO_BODY = ''
52
-
53
- BODY_FORMATS = {
54
- get: KEY_ONLY,
55
- getkq: KEY_ONLY,
56
- delete: KEY_ONLY,
57
- deleteq: KEY_ONLY,
58
- stat: KEY_ONLY,
59
-
60
- append: KEY_AND_VALUE,
61
- prepend: KEY_AND_VALUE,
62
- appendq: KEY_AND_VALUE,
63
- prependq: KEY_AND_VALUE,
64
- auth_request: KEY_AND_VALUE,
65
- auth_continue: KEY_AND_VALUE,
66
-
67
- set: 'NNa*a*',
68
- setq: 'NNa*a*',
69
- add: 'NNa*a*',
70
- addq: 'NNa*a*',
71
- replace: 'NNa*a*',
72
- replaceq: 'NNa*a*',
73
-
74
- incr: INCR_DECR,
75
- decr: INCR_DECR,
76
- incrq: INCR_DECR,
77
- decrq: INCR_DECR,
78
-
79
- flush: TTL_ONLY,
80
- flushq: TTL_ONLY,
81
-
82
- noop: NO_BODY,
83
- auth_negotiation: NO_BODY,
84
- version: NO_BODY,
85
-
86
- touch: TTL_AND_KEY,
87
- gat: TTL_AND_KEY
88
- }.freeze
89
- FORMAT = BODY_FORMATS.transform_values { |v| REQ_HEADER_FORMAT + v }
90
-
91
- # rubocop:disable-next Metrics/ParameterLists
92
- def self.standard_request(opkey:, key: nil, value: nil, opaque: 0, cas: 0, bitflags: nil, ttl: nil)
93
- extra_len = (bitflags.nil? ? 0 : 4) + (ttl.nil? ? 0 : 4)
94
- key_len = key.nil? ? 0 : key.bytesize
95
- value_len = value.nil? ? 0 : value.bytesize
96
- header = [REQUEST, OPCODES[opkey], key_len, extra_len, 0, 0, extra_len + key_len + value_len, opaque, cas]
97
- body = [bitflags, ttl, key, value].compact
98
- (header + body).pack(FORMAT[opkey])
99
- end
100
-
101
- def self.decr_incr_request(opkey:, key: nil, count: nil, initial: nil, expiry: nil)
102
- extra_len = 20
103
- (h, l) = as_8byte_uint(count)
104
- (dh, dl) = as_8byte_uint(initial)
105
- header = [REQUEST, OPCODES[opkey], key.bytesize, extra_len, 0, 0, key.bytesize + extra_len, 0, 0]
106
- body = [h, l, dh, dl, expiry, key]
107
- (header + body).pack(FORMAT[opkey])
108
- end
109
-
110
- def self.as_8byte_uint(val)
111
- [val >> 32, val & 0xFFFFFFFF]
112
- end
113
- end
114
- end
115
- end
116
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dalli
4
- module Protocol
5
- class Binary
6
- ##
7
- # Class that encapsulates data parsed from a memcached response header.
8
- ##
9
- class ResponseHeader
10
- SIZE = 24
11
- FMT = '@2nCCnNNQ'
12
-
13
- attr_reader :key_len, :extra_len, :data_type, :status, :body_len, :opaque, :cas
14
-
15
- def initialize(buf)
16
- raise ArgumentError, "Response buffer must be at least #{SIZE} bytes" unless buf.bytesize >= SIZE
17
-
18
- @key_len, @extra_len, @data_type, @status, @body_len, @opaque, @cas = buf.unpack(FMT)
19
- end
20
-
21
- def ok?
22
- status.zero?
23
- end
24
-
25
- def not_found?
26
- status == 1
27
- end
28
-
29
- NOT_STORED_STATUSES = [2, 5].freeze
30
- def not_stored?
31
- NOT_STORED_STATUSES.include?(status)
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,245 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dalli
4
- module Protocol
5
- class Binary
6
- ##
7
- # Class that encapsulates logic for processing binary protocol responses
8
- # from memcached. Includes logic for pulling data from an IO source
9
- # and parsing into local values. Handles errors on unexpected values.
10
- ##
11
- class ResponseProcessor
12
- # Response codes taken from:
13
- # https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped#response-status
14
- RESPONSE_CODES = {
15
- 0 => 'No error',
16
- 1 => 'Key not found',
17
- 2 => 'Key exists',
18
- 3 => 'Value too large',
19
- 4 => 'Invalid arguments',
20
- 5 => 'Item not stored',
21
- 6 => 'Incr/decr on a non-numeric value',
22
- 7 => 'The vbucket belongs to another server',
23
- 8 => 'Authentication error',
24
- 9 => 'Authentication continue',
25
- 0x20 => 'Authentication required',
26
- 0x81 => 'Unknown command',
27
- 0x82 => 'Out of memory',
28
- 0x83 => 'Not supported',
29
- 0x84 => 'Internal error',
30
- 0x85 => 'Busy',
31
- 0x86 => 'Temporary failure'
32
- }.freeze
33
-
34
- def initialize(io_source, value_marshaller)
35
- @io_source = io_source
36
- @value_marshaller = value_marshaller
37
- end
38
-
39
- def read(num_bytes)
40
- @io_source.read(num_bytes)
41
- end
42
-
43
- def read_response
44
- resp_header = ResponseHeader.new(read_header)
45
- body = read(resp_header.body_len) if resp_header.body_len.positive?
46
- [resp_header, body]
47
- end
48
-
49
- def unpack_response_body(resp_header, body, parse_as_stored_value)
50
- extra_len = resp_header.extra_len
51
- key_len = resp_header.key_len
52
- bitflags = extra_len.positive? ? body.unpack1('N') : 0x0
53
- key = body.byteslice(extra_len, key_len).force_encoding(Encoding::UTF_8) if key_len.positive?
54
- value = body.byteslice((extra_len + key_len)..-1)
55
- value = @value_marshaller.retrieve(value, bitflags) if parse_as_stored_value
56
- [key, value]
57
- end
58
-
59
- def read_header
60
- read(ResponseHeader::SIZE) || raise(Dalli::NetworkError, 'No response')
61
- end
62
-
63
- def raise_on_not_ok!(resp_header)
64
- return if resp_header.ok?
65
-
66
- raise Dalli::DalliError, "Response error #{resp_header.status}: #{RESPONSE_CODES[resp_header.status]}"
67
- end
68
-
69
- # raw: true returns the value as stored, without deserializing or
70
- # decompressing it, whatever flags it was stored with
71
- def get(cache_nils: false, raw: false)
72
- resp_header, body = read_response
73
-
74
- return false if resp_header.not_stored? # Not stored, normal status for add operation
75
- return cache_nils ? ::Dalli::NOT_FOUND : nil if resp_header.not_found?
76
-
77
- raise_on_not_ok!(resp_header)
78
- return true unless body
79
-
80
- unpack_response_body(resp_header, body, !raw).last
81
- end
82
-
83
- ##
84
- # Response for a storage operation. Returns the cas on success. False
85
- # if the value wasn't stored. And raises an error on all other error
86
- # codes from memcached.
87
- ##
88
- def storage_response
89
- resp_header, = read_response
90
- return nil if resp_header.not_found?
91
- return false if resp_header.not_stored? # Not stored, normal status for add operation
92
-
93
- raise_on_not_ok!(resp_header)
94
- resp_header.cas
95
- end
96
-
97
- def delete
98
- resp_header, = read_response
99
- return false if resp_header.not_found? || resp_header.not_stored?
100
-
101
- raise_on_not_ok!(resp_header)
102
- true
103
- end
104
-
105
- # raw: true returns the value as stored; see #get
106
- def data_cas_response(raw: false)
107
- resp_header, body = read_response
108
- return [nil, resp_header.cas] if resp_header.not_found?
109
- return [nil, false] if resp_header.not_stored?
110
-
111
- raise_on_not_ok!(resp_header)
112
- return [nil, resp_header.cas] unless body
113
-
114
- [unpack_response_body(resp_header, body, !raw).last, resp_header.cas]
115
- end
116
-
117
- # Returns the new value for the key, if found and updated
118
- def decr_incr
119
- body = generic_response
120
- body ? body.unpack1('Q>') : body
121
- end
122
-
123
- def stats
124
- hash = {}
125
- loop do
126
- resp_header, body = read_response
127
- # This is the response to the terminating noop / end of stat
128
- return hash if resp_header.ok? && resp_header.key_len.zero?
129
-
130
- # Ignore any responses with non-zero status codes,
131
- # such as errors from set operations. That allows
132
- # this code to be used at the end of a multi
133
- # block to clear any error responses from inside the multi.
134
- next unless resp_header.ok?
135
-
136
- key, value = unpack_response_body(resp_header, body, true)
137
- hash[key] = value
138
- end
139
- end
140
-
141
- def flush
142
- no_body_response
143
- end
144
-
145
- def reset
146
- generic_response
147
- end
148
-
149
- def version
150
- generic_response
151
- end
152
-
153
- def consume_all_responses_until_noop
154
- loop do
155
- resp_header, = read_response
156
- # This is the response to the terminating noop / end of stat
157
- return true if resp_header.ok? && resp_header.key_len.zero?
158
- end
159
- end
160
-
161
- def generic_response
162
- resp_header, body = read_response
163
-
164
- return false if resp_header.not_stored? # Not stored, normal status for add operation
165
- return nil if resp_header.not_found?
166
-
167
- raise_on_not_ok!(resp_header)
168
- return true unless body
169
-
170
- unpack_response_body(resp_header, body, false).last
171
- end
172
-
173
- def no_body_response
174
- resp_header, = read_response
175
- return false if resp_header.not_stored? # Not stored, possible status for append/prepend/delete
176
-
177
- raise_on_not_ok!(resp_header)
178
- true
179
- end
180
-
181
- def validate_auth_format(extra_len, count)
182
- return if extra_len.zero?
183
-
184
- raise Dalli::NetworkError, "Unexpected message format: #{extra_len} #{count}"
185
- end
186
-
187
- def auth_response(buf = read_header)
188
- resp_header = ResponseHeader.new(buf)
189
- body_len = resp_header.body_len
190
- validate_auth_format(resp_header.extra_len, body_len)
191
- content = read(body_len) if body_len.positive?
192
- [resp_header.status, content]
193
- end
194
-
195
- ##
196
- # This method returns an array of values used in a pipelined
197
- # getk process. The first value is the number of bytes by
198
- # which to advance the pointer in the buffer. If the
199
- # complete response is found in the buffer, this will
200
- # be the response size. Otherwise it is zero.
201
- #
202
- # The remaining three values in the array are the ResponseHeader,
203
- # key, and value.
204
- ##
205
- # memcached can't store an item larger than 1 GiB (its -I maximum). A
206
- # pipelined reply claiming an impossible size would otherwise have the
207
- # buffer wait for (and accumulate) that many bytes.
208
- MAX_BODY_BYTES = 1024 * 1024 * 1024
209
-
210
- def check_body_size!(size)
211
- return if size <= MAX_BODY_BYTES
212
-
213
- raise Dalli::DalliError, "Reply body size #{size} is out of range"
214
- end
215
-
216
- def getk_response_from_buffer(buf, offset = 0)
217
- # There's no header in the buffer, so don't advance
218
- return [0, nil, nil, nil, nil] unless buf && buf.bytesize >= offset + ResponseHeader::SIZE
219
-
220
- resp_header = ResponseHeader.new(buf.byteslice(offset, ResponseHeader::SIZE))
221
- body_len = resp_header.body_len
222
-
223
- # We have a complete response that has no body.
224
- # This is either the response to the terminating
225
- # noop or, if the status is not zero, an intermediate
226
- # error response that needs to be discarded.
227
- return [ResponseHeader::SIZE, resp_header.ok?, resp_header.cas, nil, nil] if body_len.zero?
228
-
229
- check_body_size!(body_len)
230
-
231
- resp_size = ResponseHeader::SIZE + body_len
232
- # The header is in the buffer, but the body is not. As we don't have
233
- # a complete response, don't advance the buffer
234
- return [0, nil, nil, nil, nil] unless buf.bytesize >= offset + resp_size
235
-
236
- # The full response is in our buffer, so parse it and return
237
- # the values
238
- body = buf.byteslice(offset + ResponseHeader::SIZE, body_len)
239
- key, value = unpack_response_body(resp_header, body, true)
240
- [resp_size, resp_header.ok?, resp_header.cas, key, value]
241
- end
242
- end
243
- end
244
- end
245
- end
@@ -1,60 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dalli
4
- module Protocol
5
- class Binary
6
- ##
7
- # Code to support SASL authentication
8
- ##
9
- module SaslAuthentication
10
- def perform_auth_negotiation
11
- write(RequestFormatter.standard_request(opkey: :auth_negotiation))
12
-
13
- status, content = response_processor.auth_response
14
- return [status, []] if content.nil?
15
-
16
- # Substitute spaces for the \x00 returned by
17
- # memcached as a separator for easier
18
- content&.tr!("\u0000", ' ')
19
- mechanisms = content&.split
20
- [status, mechanisms]
21
- end
22
-
23
- PLAIN_AUTH = 'PLAIN'
24
-
25
- def supported_mechanisms!(mechanisms)
26
- unless mechanisms.include?(PLAIN_AUTH)
27
- raise NotImplementedError,
28
- 'Dalli only supports the PLAIN authentication mechanism'
29
- end
30
- [PLAIN_AUTH]
31
- end
32
-
33
- def authenticate_with_plain
34
- write(RequestFormatter.standard_request(opkey: :auth_request,
35
- key: PLAIN_AUTH,
36
- value: "\x0#{username}\x0#{password}"))
37
- @response_processor.auth_response
38
- end
39
-
40
- def authenticate_connection
41
- Dalli.logger.info { "Dalli/SASL authenticating as #{username}" }
42
-
43
- status, mechanisms = perform_auth_negotiation
44
- return Dalli.logger.debug('Authentication not required/supported by server') if status == 0x81
45
-
46
- supported_mechanisms!(mechanisms)
47
- status, content = authenticate_with_plain
48
-
49
- return Dalli.logger.info("Dalli/SASL: #{content}") if status.zero?
50
-
51
- raise Dalli::DalliError, "Error authenticating: 0x#{status.to_s(16)}" unless status == 0x21
52
-
53
- raise NotImplementedError, 'No two-step authentication mechanisms supported'
54
- # (step, msg) = sasl.receive('challenge', content)
55
- # raise Dalli::NetworkError, "Authentication failed" if sasl.failed? || step != 'response'
56
- end
57
- end
58
- end
59
- end
60
- end