mysql-pr 3.0.0 → 3.0.2
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
- data/.rubocop.yml +33 -17
- data/CHANGELOG.md +14 -1
- data/lib/mysql-pr/charset.rb +132 -134
- data/lib/mysql-pr/constants.rb +5 -5
- data/lib/mysql-pr/error.rb +10 -10
- data/lib/mysql-pr/packet.rb +21 -17
- data/lib/mysql-pr/protocol.rb +219 -199
- data/lib/mysql-pr/version.rb +1 -1
- data/lib/mysql-pr.rb +20 -30
- data/mysql-pr.gemspec +0 -1
- metadata +1 -15
data/lib/mysql-pr/protocol.rb
CHANGED
|
@@ -13,9 +13,8 @@ require "openssl"
|
|
|
13
13
|
class MysqlPR
|
|
14
14
|
# MySQL network protocol
|
|
15
15
|
class Protocol
|
|
16
|
-
|
|
17
16
|
VERSION = 10
|
|
18
|
-
MAX_PACKET_LENGTH = 2**24-1
|
|
17
|
+
MAX_PACKET_LENGTH = 2**24 - 1
|
|
19
18
|
|
|
20
19
|
# Convert netdata to Ruby value
|
|
21
20
|
# === Argument
|
|
@@ -27,42 +26,59 @@ class MysqlPR
|
|
|
27
26
|
def self.net2value(pkt, type, unsigned)
|
|
28
27
|
case type
|
|
29
28
|
when Field::TYPE_STRING, Field::TYPE_VAR_STRING, Field::TYPE_NEWDECIMAL, Field::TYPE_BLOB
|
|
30
|
-
|
|
29
|
+
pkt.lcs
|
|
31
30
|
when Field::TYPE_TINY
|
|
32
31
|
v = pkt.utiny
|
|
33
|
-
|
|
32
|
+
if unsigned
|
|
33
|
+
v
|
|
34
|
+
else
|
|
35
|
+
v < 128 ? v : v - 256
|
|
36
|
+
end
|
|
34
37
|
when Field::TYPE_SHORT
|
|
35
38
|
v = pkt.ushort
|
|
36
|
-
|
|
39
|
+
if unsigned
|
|
40
|
+
v
|
|
41
|
+
else
|
|
42
|
+
v < 32_768 ? v : v - 65_536
|
|
43
|
+
end
|
|
37
44
|
when Field::TYPE_INT24, Field::TYPE_LONG
|
|
38
45
|
v = pkt.ulong
|
|
39
|
-
|
|
46
|
+
if unsigned
|
|
47
|
+
v
|
|
48
|
+
else
|
|
49
|
+
v < 2**32 / 2 ? v : v - 2**32
|
|
50
|
+
end
|
|
40
51
|
when Field::TYPE_LONGLONG
|
|
41
|
-
n1
|
|
52
|
+
n1 = pkt.ulong
|
|
53
|
+
n2 = pkt.ulong
|
|
42
54
|
v = (n2 << 32) | n1
|
|
43
|
-
|
|
55
|
+
if unsigned
|
|
56
|
+
v
|
|
57
|
+
else
|
|
58
|
+
v < 2**64 / 2 ? v : v - 2**64
|
|
59
|
+
end
|
|
44
60
|
when Field::TYPE_FLOAT
|
|
45
|
-
|
|
61
|
+
pkt.read(4).unpack1("e")
|
|
46
62
|
when Field::TYPE_DOUBLE
|
|
47
|
-
|
|
63
|
+
pkt.read(8).unpack1("E")
|
|
48
64
|
when Field::TYPE_DATE
|
|
49
65
|
len = pkt.utiny
|
|
50
66
|
y, m, d = pkt.read(len).unpack("vCC")
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
MysqlPR::Time.new(y, m, d, nil, nil, nil)
|
|
68
|
+
|
|
53
69
|
when Field::TYPE_DATETIME, Field::TYPE_TIMESTAMP
|
|
54
70
|
len = pkt.utiny
|
|
55
71
|
y, m, d, h, mi, s, sp = pkt.read(len).unpack("vCCCCCV")
|
|
56
|
-
|
|
72
|
+
MysqlPR::Time.new(y, m, d, h, mi, s, false, sp)
|
|
57
73
|
when Field::TYPE_TIME
|
|
58
74
|
len = pkt.utiny
|
|
59
75
|
sign, d, h, mi, s, sp = pkt.read(len).unpack("CVCCCV")
|
|
60
76
|
h = d.to_i * 24 + h.to_i
|
|
61
|
-
|
|
77
|
+
MysqlPR::Time.new(0, 0, 0, h, mi, s, sign != 0, sp)
|
|
62
78
|
when Field::TYPE_YEAR
|
|
63
|
-
|
|
79
|
+
pkt.ushort
|
|
64
80
|
when Field::TYPE_BIT
|
|
65
|
-
|
|
81
|
+
pkt.lcs
|
|
66
82
|
else
|
|
67
83
|
raise "not implemented: type=#{type}"
|
|
68
84
|
end
|
|
@@ -94,26 +110,24 @@ class MysqlPR
|
|
|
94
110
|
val = [v].pack("V")
|
|
95
111
|
elsif v < 256**8
|
|
96
112
|
type = Field::TYPE_LONGLONG | 0x8000
|
|
97
|
-
val = [v&0xffffffff, v>>32].pack("VV")
|
|
113
|
+
val = [v & 0xffffffff, v >> 32].pack("VV")
|
|
98
114
|
else
|
|
99
115
|
raise ProtocolError, "value too large: #{v}"
|
|
100
116
|
end
|
|
117
|
+
elsif -v <= 256 / 2
|
|
118
|
+
type = Field::TYPE_TINY
|
|
119
|
+
val = [v].pack("C")
|
|
120
|
+
elsif -v <= 256**2 / 2
|
|
121
|
+
type = Field::TYPE_SHORT
|
|
122
|
+
val = [v].pack("v")
|
|
123
|
+
elsif -v <= 256**4 / 2
|
|
124
|
+
type = Field::TYPE_LONG
|
|
125
|
+
val = [v].pack("V")
|
|
126
|
+
elsif -v <= 256**8 / 2
|
|
127
|
+
type = Field::TYPE_LONGLONG
|
|
128
|
+
val = [v & 0xffffffff, v >> 32].pack("VV")
|
|
101
129
|
else
|
|
102
|
-
|
|
103
|
-
type = Field::TYPE_TINY
|
|
104
|
-
val = [v].pack("C")
|
|
105
|
-
elsif -v <= 256**2/2
|
|
106
|
-
type = Field::TYPE_SHORT
|
|
107
|
-
val = [v].pack("v")
|
|
108
|
-
elsif -v <= 256**4/2
|
|
109
|
-
type = Field::TYPE_LONG
|
|
110
|
-
val = [v].pack("V")
|
|
111
|
-
elsif -v <= 256**8/2
|
|
112
|
-
type = Field::TYPE_LONGLONG
|
|
113
|
-
val = [v&0xffffffff, v>>32].pack("VV")
|
|
114
|
-
else
|
|
115
|
-
raise ProtocolError, "value too large: #{v}"
|
|
116
|
-
end
|
|
130
|
+
raise ProtocolError, "value too large: #{v}"
|
|
117
131
|
end
|
|
118
132
|
when Float
|
|
119
133
|
type = Field::TYPE_DOUBLE
|
|
@@ -127,18 +141,11 @@ class MysqlPR
|
|
|
127
141
|
else
|
|
128
142
|
raise ProtocolError, "class #{v.class} is not supported"
|
|
129
143
|
end
|
|
130
|
-
|
|
144
|
+
[type, val]
|
|
131
145
|
end
|
|
132
146
|
|
|
133
|
-
attr_reader :server_info
|
|
134
|
-
|
|
135
|
-
attr_reader :thread_id
|
|
136
|
-
attr_reader :sqlstate
|
|
137
|
-
attr_reader :affected_rows
|
|
138
|
-
attr_reader :insert_id
|
|
139
|
-
attr_reader :server_status
|
|
140
|
-
attr_reader :warning_count
|
|
141
|
-
attr_reader :message
|
|
147
|
+
attr_reader :server_info, :server_version, :thread_id, :sqlstate, :affected_rows, :insert_id, :server_status,
|
|
148
|
+
:warning_count, :message
|
|
142
149
|
attr_accessor :charset
|
|
143
150
|
|
|
144
151
|
# @state variable keep state for connection.
|
|
@@ -161,7 +168,7 @@ class MysqlPR
|
|
|
161
168
|
def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout, ssl_options = nil)
|
|
162
169
|
@insert_id = 0
|
|
163
170
|
@warning_count = 0
|
|
164
|
-
@gc_stmt_queue = []
|
|
171
|
+
@gc_stmt_queue = [] # stmt id list which GC destroy.
|
|
165
172
|
set_state :INIT
|
|
166
173
|
@read_timeout = read_timeout
|
|
167
174
|
@write_timeout = write_timeout
|
|
@@ -169,11 +176,15 @@ class MysqlPR
|
|
|
169
176
|
@ssl_enabled = false
|
|
170
177
|
begin
|
|
171
178
|
Timeout.timeout conn_timeout do
|
|
172
|
-
if host.nil?
|
|
179
|
+
if host.nil? || host.empty? || (host == "localhost")
|
|
173
180
|
socket ||= ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_PORT
|
|
174
181
|
@sock = UNIXSocket.new socket
|
|
175
182
|
else
|
|
176
|
-
port ||= ENV["MYSQL_TCP_PORT"] ||
|
|
183
|
+
port ||= ENV["MYSQL_TCP_PORT"] || begin
|
|
184
|
+
Socket.getservbyname("mysql", "tcp")
|
|
185
|
+
rescue StandardError
|
|
186
|
+
MYSQL_TCP_PORT
|
|
187
|
+
end
|
|
177
188
|
@sock = TCPSocket.new host, port
|
|
178
189
|
end
|
|
179
190
|
end
|
|
@@ -213,16 +224,17 @@ class MysqlPR
|
|
|
213
224
|
reset
|
|
214
225
|
init_packet = InitialPacket.parse read
|
|
215
226
|
@server_info = init_packet.server_version
|
|
216
|
-
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
|
|
227
|
+
@server_version = init_packet.server_version.split(/\D/)[0, 3].inject { |a, b| a.to_i * 100 + b.to_i }
|
|
217
228
|
@thread_id = init_packet.thread_id
|
|
218
|
-
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS |
|
|
229
|
+
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS |
|
|
230
|
+
CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
|
|
219
231
|
client_flags |= CLIENT_PLUGIN_AUTH
|
|
220
232
|
client_flags |= CLIENT_CONNECT_WITH_DB if db
|
|
221
233
|
client_flags |= flag
|
|
222
234
|
@charset = charset
|
|
223
235
|
unless @charset
|
|
224
236
|
@charset = Charset.by_number(init_packet.server_charset)
|
|
225
|
-
@charset.encoding
|
|
237
|
+
@charset.encoding # raise error if unsupported charset
|
|
226
238
|
end
|
|
227
239
|
|
|
228
240
|
# SSL handshake if requested and server supports it
|
|
@@ -240,11 +252,11 @@ class MysqlPR
|
|
|
240
252
|
scramble = init_packet.scramble_buff
|
|
241
253
|
|
|
242
254
|
# Choose password encryption based on auth plugin
|
|
243
|
-
if auth_plugin == "caching_sha2_password"
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
255
|
+
netpw = if auth_plugin == "caching_sha2_password"
|
|
256
|
+
encrypt_password_sha256(passwd, scramble)
|
|
257
|
+
else
|
|
258
|
+
encrypt_password(passwd, scramble)
|
|
259
|
+
end
|
|
248
260
|
|
|
249
261
|
write AuthenticationPacket.serialize(client_flags, 1024**3, @charset.number, user, netpw, db, auth_plugin)
|
|
250
262
|
|
|
@@ -309,25 +321,22 @@ class MysqlPR
|
|
|
309
321
|
if @ssl_enabled
|
|
310
322
|
# Send plaintext password over SSL
|
|
311
323
|
write "#{passwd}\x00"
|
|
312
|
-
read # OK or error
|
|
313
|
-
set_state :READY
|
|
314
324
|
else
|
|
315
325
|
# Need RSA encryption - request public key
|
|
316
326
|
write "\x02" # Request public key
|
|
317
327
|
pubkey_response = read
|
|
318
328
|
pubkey_data = pubkey_response.to_s
|
|
319
329
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
else
|
|
328
|
-
raise ProtocolError, "Failed to get server public key"
|
|
329
|
-
end
|
|
330
|
+
raise ProtocolError, "Failed to get server public key" unless pubkey_data.getbyte(0) == 0x01
|
|
331
|
+
|
|
332
|
+
# Got public key
|
|
333
|
+
public_key = pubkey_data[1..]
|
|
334
|
+
encrypted_password = rsa_encrypt_password(passwd, scramble, public_key)
|
|
335
|
+
write encrypted_password
|
|
336
|
+
|
|
330
337
|
end
|
|
338
|
+
read
|
|
339
|
+
set_state :READY
|
|
331
340
|
else
|
|
332
341
|
raise ProtocolError, "Unknown caching_sha2_password status: #{status}"
|
|
333
342
|
end
|
|
@@ -352,30 +361,20 @@ class MysqlPR
|
|
|
352
361
|
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
353
362
|
|
|
354
363
|
# Configure SSL context based on options
|
|
355
|
-
if @ssl_options[:ca]
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if @ssl_options[:
|
|
359
|
-
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_options[:cert]))
|
|
360
|
-
end
|
|
361
|
-
if @ssl_options[:key]
|
|
362
|
-
ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_options[:key]))
|
|
363
|
-
end
|
|
364
|
-
if @ssl_options[:ca_path]
|
|
365
|
-
ssl_context.ca_path = @ssl_options[:ca_path]
|
|
366
|
-
end
|
|
364
|
+
ssl_context.ca_file = @ssl_options[:ca] if @ssl_options[:ca]
|
|
365
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(@ssl_options[:cert])) if @ssl_options[:cert]
|
|
366
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(File.read(@ssl_options[:key])) if @ssl_options[:key]
|
|
367
|
+
ssl_context.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
|
|
367
368
|
|
|
368
369
|
# Set verification mode
|
|
369
|
-
if @ssl_options[:verify] == false
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
ssl_context.verify_mode = if @ssl_options[:verify] == false
|
|
371
|
+
OpenSSL::SSL::VERIFY_NONE
|
|
372
|
+
else
|
|
373
|
+
OpenSSL::SSL::VERIFY_PEER
|
|
374
|
+
end
|
|
374
375
|
|
|
375
376
|
# Set minimum TLS version if specified
|
|
376
|
-
if @ssl_options[:min_version]
|
|
377
|
-
ssl_context.min_version = @ssl_options[:min_version]
|
|
378
|
-
end
|
|
377
|
+
ssl_context.min_version = @ssl_options[:min_version] if @ssl_options[:min_version]
|
|
379
378
|
|
|
380
379
|
# Wrap socket in SSL
|
|
381
380
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(@sock, ssl_context)
|
|
@@ -409,7 +408,7 @@ class MysqlPR
|
|
|
409
408
|
reset
|
|
410
409
|
write [COM_QUERY, @charset.convert(query)].pack("Ca*")
|
|
411
410
|
get_result
|
|
412
|
-
rescue
|
|
411
|
+
rescue StandardError
|
|
413
412
|
set_state :READY
|
|
414
413
|
raise
|
|
415
414
|
end
|
|
@@ -419,26 +418,27 @@ class MysqlPR
|
|
|
419
418
|
# === Return
|
|
420
419
|
# [integer / nil] number of fields of results. nil if no results.
|
|
421
420
|
def get_result
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
421
|
+
res_packet = ResultPacket.parse read
|
|
422
|
+
if res_packet.field_count.to_i.positive? # result data exists
|
|
423
|
+
set_state :FIELD
|
|
424
|
+
return res_packet.field_count
|
|
425
|
+
end
|
|
426
|
+
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
|
|
427
|
+
filename = res_packet.message
|
|
428
|
+
File.open(filename) { |f| write f }
|
|
429
|
+
write nil # EOF mark
|
|
430
|
+
read
|
|
431
|
+
end
|
|
432
|
+
@affected_rows = res_packet.affected_rows
|
|
433
|
+
@insert_id = res_packet.insert_id
|
|
434
|
+
@server_status = res_packet.server_status
|
|
435
|
+
@warning_count = res_packet.warning_count
|
|
436
|
+
@message = res_packet.message
|
|
437
|
+
set_state :READY
|
|
438
|
+
nil
|
|
439
|
+
rescue StandardError
|
|
440
|
+
set_state :READY
|
|
441
|
+
raise
|
|
442
442
|
end
|
|
443
443
|
|
|
444
444
|
# Retrieve n fields
|
|
@@ -449,11 +449,11 @@ class MysqlPR
|
|
|
449
449
|
def retr_fields(n)
|
|
450
450
|
check_state :FIELD
|
|
451
451
|
begin
|
|
452
|
-
fields = n.times.map{Field.new FieldPacket.parse(read)}
|
|
452
|
+
fields = n.times.map { Field.new FieldPacket.parse(read) }
|
|
453
453
|
read_eof_packet
|
|
454
454
|
set_state :RESULT
|
|
455
455
|
fields
|
|
456
|
-
rescue
|
|
456
|
+
rescue StandardError
|
|
457
457
|
set_state :READY
|
|
458
458
|
raise
|
|
459
459
|
end
|
|
@@ -507,11 +507,11 @@ class MysqlPR
|
|
|
507
507
|
reset
|
|
508
508
|
write [COM_PROCESS_INFO].pack("C")
|
|
509
509
|
field_count = read.lcb
|
|
510
|
-
fields = field_count.times.map{Field.new FieldPacket.parse(read)}
|
|
510
|
+
fields = field_count.times.map { Field.new FieldPacket.parse(read) }
|
|
511
511
|
read_eof_packet
|
|
512
512
|
set_state :RESULT
|
|
513
|
-
|
|
514
|
-
rescue
|
|
513
|
+
fields
|
|
514
|
+
rescue StandardError
|
|
515
515
|
set_state :READY
|
|
516
516
|
raise
|
|
517
517
|
end
|
|
@@ -559,12 +559,12 @@ class MysqlPR
|
|
|
559
559
|
reset
|
|
560
560
|
write [COM_STMT_PREPARE, charset.convert(stmt)].pack("Ca*")
|
|
561
561
|
res_packet = PrepareResultPacket.parse read
|
|
562
|
-
if res_packet.param_count
|
|
563
|
-
res_packet.param_count.times{read}
|
|
562
|
+
if res_packet.param_count.positive?
|
|
563
|
+
res_packet.param_count.times { read } # skip parameter packet
|
|
564
564
|
read_eof_packet
|
|
565
565
|
end
|
|
566
|
-
if res_packet.field_count
|
|
567
|
-
fields = res_packet.field_count.times.map{Field.new FieldPacket.parse(read)}
|
|
566
|
+
if res_packet.field_count.positive?
|
|
567
|
+
fields = res_packet.field_count.times.map { Field.new FieldPacket.parse(read) }
|
|
568
568
|
read_eof_packet
|
|
569
569
|
else
|
|
570
570
|
fields = []
|
|
@@ -585,7 +585,7 @@ class MysqlPR
|
|
|
585
585
|
reset
|
|
586
586
|
write ExecutePacket.serialize(stmt_id, MysqlPR::Stmt::CURSOR_TYPE_NO_CURSOR, values)
|
|
587
587
|
get_result
|
|
588
|
-
rescue
|
|
588
|
+
rescue StandardError
|
|
589
589
|
set_state :READY
|
|
590
590
|
raise
|
|
591
591
|
end
|
|
@@ -628,36 +628,34 @@ class MysqlPR
|
|
|
628
628
|
private
|
|
629
629
|
|
|
630
630
|
def check_state(st)
|
|
631
|
-
raise
|
|
631
|
+
raise "command out of sync" unless @state == st
|
|
632
632
|
end
|
|
633
633
|
|
|
634
634
|
def set_state(st)
|
|
635
635
|
@state = st
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
ensure
|
|
644
|
-
GC.enable unless gc_disabled unless RUBY_PLATFORM == 'java'
|
|
636
|
+
return unless st == :READY
|
|
637
|
+
|
|
638
|
+
gc_disabled = GC.disable unless RUBY_PLATFORM == "java"
|
|
639
|
+
begin
|
|
640
|
+
while (st = @gc_stmt_queue.shift)
|
|
641
|
+
reset
|
|
642
|
+
write [COM_STMT_CLOSE, st].pack("CV")
|
|
645
643
|
end
|
|
644
|
+
ensure
|
|
645
|
+
GC.enable if RUBY_PLATFORM != "java" && !gc_disabled
|
|
646
646
|
end
|
|
647
647
|
end
|
|
648
648
|
|
|
649
649
|
def synchronize
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
set_state :READY
|
|
655
|
-
end
|
|
650
|
+
check_state :READY
|
|
651
|
+
yield
|
|
652
|
+
ensure
|
|
653
|
+
set_state :READY
|
|
656
654
|
end
|
|
657
655
|
|
|
658
656
|
# Reset sequence number
|
|
659
657
|
def reset
|
|
660
|
-
@seq = 0
|
|
658
|
+
@seq = 0 # packet counter. reset by each command
|
|
661
659
|
end
|
|
662
660
|
|
|
663
661
|
# Read one packet data
|
|
@@ -668,35 +666,39 @@ class MysqlPR
|
|
|
668
666
|
def read
|
|
669
667
|
ret = ""
|
|
670
668
|
len = nil
|
|
671
|
-
|
|
669
|
+
loop do
|
|
672
670
|
Timeout.timeout @read_timeout do
|
|
673
671
|
header = @sock.read(4)
|
|
674
672
|
raise EOFError unless header && header.length == 4
|
|
673
|
+
|
|
675
674
|
len1, len2, seq = header.unpack("CvC")
|
|
676
675
|
len = (len2 << 8) + len1
|
|
677
676
|
raise ProtocolError, "invalid packet: sequence number mismatch(#{seq} != #{@seq}(expected))" if @seq != seq
|
|
677
|
+
|
|
678
678
|
@seq = (@seq + 1) % 256
|
|
679
679
|
ret = @sock.read(len)
|
|
680
680
|
raise EOFError unless ret && ret.length == len
|
|
681
681
|
end
|
|
682
682
|
rescue EOFError
|
|
683
|
-
raise ClientError::ServerGoneError,
|
|
683
|
+
raise ClientError::ServerGoneError, "The MySQL server has gone away"
|
|
684
684
|
rescue Timeout::Error
|
|
685
685
|
raise ClientError, "read timeout"
|
|
686
|
-
|
|
686
|
+
break unless len == MAX_PACKET_LENGTH
|
|
687
|
+
end
|
|
687
688
|
|
|
688
689
|
@sqlstate = "00000"
|
|
689
690
|
|
|
690
691
|
# Error packet (use getbyte for encoding-safe comparison)
|
|
691
692
|
if ret.getbyte(0) == 0xff
|
|
692
|
-
|
|
693
|
+
_, errno, marker, @sqlstate, message = ret.unpack("Cvaa5a*")
|
|
693
694
|
unless marker == "#"
|
|
694
|
-
|
|
695
|
+
_, errno, message = ret.unpack("Cva*") # Version 4.0 Error
|
|
695
696
|
@sqlstate = ""
|
|
696
697
|
end
|
|
697
698
|
if MysqlPR::ServerError::ERROR_MAP.key? errno
|
|
698
699
|
raise MysqlPR::ServerError::ERROR_MAP[errno].new(message, @sqlstate)
|
|
699
700
|
end
|
|
701
|
+
|
|
700
702
|
raise MysqlPR::ServerError.new(message, @sqlstate)
|
|
701
703
|
end
|
|
702
704
|
Packet.new(ret)
|
|
@@ -706,32 +708,30 @@ class MysqlPR
|
|
|
706
708
|
# === Argument
|
|
707
709
|
# data :: [String / IO] packet data. If data is nil, write empty packet.
|
|
708
710
|
def write(data)
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
711
|
+
@sock.sync = false
|
|
712
|
+
if data.nil?
|
|
713
|
+
Timeout.timeout @write_timeout do
|
|
714
|
+
@sock.write [0, 0, @seq].pack("CvC")
|
|
715
|
+
end
|
|
716
|
+
@seq = (@seq + 1) % 256
|
|
717
|
+
else
|
|
718
|
+
data = StringIO.new data if data.is_a? String
|
|
719
|
+
while (d = data.read(MAX_PACKET_LENGTH))
|
|
712
720
|
Timeout.timeout @write_timeout do
|
|
713
|
-
@sock.write [
|
|
721
|
+
@sock.write [d.length % 256, d.length / 256, @seq].pack("CvC")
|
|
722
|
+
@sock.write d
|
|
714
723
|
end
|
|
715
724
|
@seq = (@seq + 1) % 256
|
|
716
|
-
else
|
|
717
|
-
data = StringIO.new data if data.is_a? String
|
|
718
|
-
while d = data.read(MAX_PACKET_LENGTH)
|
|
719
|
-
Timeout.timeout @write_timeout do
|
|
720
|
-
@sock.write [d.length%256, d.length/256, @seq].pack("CvC")
|
|
721
|
-
@sock.write d
|
|
722
|
-
end
|
|
723
|
-
@seq = (@seq + 1) % 256
|
|
724
|
-
end
|
|
725
725
|
end
|
|
726
|
-
@sock.sync = true
|
|
727
|
-
Timeout.timeout @write_timeout do
|
|
728
|
-
@sock.flush
|
|
729
|
-
end
|
|
730
|
-
rescue Errno::EPIPE
|
|
731
|
-
raise ClientError::ServerGoneError, 'The MySQL server has gone away'
|
|
732
|
-
rescue Timeout::Error
|
|
733
|
-
raise ClientError, "write timeout"
|
|
734
726
|
end
|
|
727
|
+
@sock.sync = true
|
|
728
|
+
Timeout.timeout @write_timeout do
|
|
729
|
+
@sock.flush
|
|
730
|
+
end
|
|
731
|
+
rescue Errno::EPIPE
|
|
732
|
+
raise ClientError::ServerGoneError, "The MySQL server has gone away"
|
|
733
|
+
rescue Timeout::Error
|
|
734
|
+
raise ClientError, "write timeout"
|
|
735
735
|
end
|
|
736
736
|
|
|
737
737
|
# Read EOF packet
|
|
@@ -761,10 +761,13 @@ class MysqlPR
|
|
|
761
761
|
# === Return
|
|
762
762
|
# [String] encrypted password
|
|
763
763
|
def encrypt_password(plain, scramble)
|
|
764
|
-
return "" if plain.nil?
|
|
764
|
+
return "" if plain.nil? || plain.empty?
|
|
765
|
+
|
|
765
766
|
hash_stage1 = Digest::SHA1.digest plain
|
|
766
767
|
hash_stage2 = Digest::SHA1.digest hash_stage1
|
|
767
|
-
|
|
768
|
+
hash_stage1.unpack("C*").zip(Digest::SHA1.digest(scramble + hash_stage2).unpack("C*")).map do |a, b|
|
|
769
|
+
a ^ b
|
|
770
|
+
end.pack("C*")
|
|
768
771
|
end
|
|
769
772
|
|
|
770
773
|
# Encrypt password for caching_sha2_password (SHA256)
|
|
@@ -774,10 +777,13 @@ class MysqlPR
|
|
|
774
777
|
# === Return
|
|
775
778
|
# [String] encrypted password
|
|
776
779
|
def encrypt_password_sha256(plain, scramble)
|
|
777
|
-
return "" if plain.nil?
|
|
780
|
+
return "" if plain.nil? || plain.empty?
|
|
781
|
+
|
|
778
782
|
hash_stage1 = Digest::SHA256.digest(plain)
|
|
779
783
|
hash_stage2 = Digest::SHA256.digest(hash_stage1)
|
|
780
|
-
hash_stage1.unpack("C*").zip(Digest::SHA256.digest(hash_stage2 + scramble).unpack("C*")).map
|
|
784
|
+
hash_stage1.unpack("C*").zip(Digest::SHA256.digest(hash_stage2 + scramble).unpack("C*")).map do |a, b|
|
|
785
|
+
a ^ b
|
|
786
|
+
end.pack("C*")
|
|
781
787
|
end
|
|
782
788
|
|
|
783
789
|
# Initial packet
|
|
@@ -799,18 +805,26 @@ class MysqlPR
|
|
|
799
805
|
rest_scramble_buff = pkt.read(rest_scramble_len)
|
|
800
806
|
# Remove trailing null if present
|
|
801
807
|
rest_scramble_buff = rest_scramble_buff.sub(/\x00+\z/, "")
|
|
802
|
-
auth_plugin_name =
|
|
808
|
+
auth_plugin_name = begin
|
|
809
|
+
pkt.string
|
|
810
|
+
rescue StandardError
|
|
811
|
+
"mysql_native_password"
|
|
812
|
+
end
|
|
803
813
|
raise ProtocolError, "unsupported version: #{protocol_version}" unless protocol_version == VERSION
|
|
804
|
-
raise ProtocolError, "invalid packet: f0=#{f0}" unless f0
|
|
814
|
+
raise ProtocolError, "invalid packet: f0=#{f0}" unless f0.zero?
|
|
815
|
+
|
|
805
816
|
scramble_buff.concat rest_scramble_buff
|
|
806
817
|
server_capabilities |= (server_capabilities_upper << 16)
|
|
807
|
-
|
|
818
|
+
new protocol_version, server_version, thread_id, server_capabilities, server_charset, server_status,
|
|
819
|
+
scramble_buff, auth_plugin_name
|
|
808
820
|
end
|
|
809
821
|
|
|
810
|
-
attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset,
|
|
822
|
+
attr_reader :protocol_version, :server_version, :thread_id, :server_capabilities, :server_charset,
|
|
823
|
+
:server_status, :scramble_buff, :auth_plugin_name
|
|
811
824
|
|
|
812
825
|
def initialize(*args)
|
|
813
|
-
@protocol_version, @server_version, @thread_id, @server_capabilities,
|
|
826
|
+
@protocol_version, @server_version, @thread_id, @server_capabilities,
|
|
827
|
+
@server_charset, @server_status, @scramble_buff, @auth_plugin_name = args
|
|
814
828
|
end
|
|
815
829
|
end
|
|
816
830
|
|
|
@@ -818,17 +832,17 @@ class MysqlPR
|
|
|
818
832
|
class ResultPacket
|
|
819
833
|
def self.parse(pkt)
|
|
820
834
|
field_count = pkt.lcb
|
|
821
|
-
if field_count
|
|
835
|
+
if field_count.zero?
|
|
822
836
|
affected_rows = pkt.lcb
|
|
823
837
|
insert_id = pkt.lcb
|
|
824
838
|
server_status = pkt.ushort
|
|
825
839
|
warning_count = pkt.ushort
|
|
826
840
|
message = pkt.lcs
|
|
827
|
-
|
|
828
|
-
elsif field_count.nil?
|
|
829
|
-
|
|
841
|
+
new(field_count, affected_rows, insert_id, server_status, warning_count, message)
|
|
842
|
+
elsif field_count.nil? # LOAD DATA LOCAL INFILE
|
|
843
|
+
new(nil, nil, nil, nil, nil, pkt.to_s)
|
|
830
844
|
else
|
|
831
|
-
|
|
845
|
+
new(field_count)
|
|
832
846
|
end
|
|
833
847
|
end
|
|
834
848
|
|
|
@@ -842,13 +856,13 @@ class MysqlPR
|
|
|
842
856
|
# Field packet
|
|
843
857
|
class FieldPacket
|
|
844
858
|
def self.parse(pkt)
|
|
845
|
-
|
|
859
|
+
pkt.lcs
|
|
846
860
|
db = pkt.lcs
|
|
847
861
|
table = pkt.lcs
|
|
848
862
|
org_table = pkt.lcs
|
|
849
863
|
name = pkt.lcs
|
|
850
864
|
org_name = pkt.lcs
|
|
851
|
-
|
|
865
|
+
pkt.utiny
|
|
852
866
|
charsetnr = pkt.ushort
|
|
853
867
|
length = pkt.ulong
|
|
854
868
|
type = pkt.utiny
|
|
@@ -856,9 +870,10 @@ class MysqlPR
|
|
|
856
870
|
decimals = pkt.utiny
|
|
857
871
|
f1 = pkt.ushort
|
|
858
872
|
|
|
859
|
-
raise ProtocolError, "invalid packet: f1=#{f1}" unless f1
|
|
873
|
+
raise ProtocolError, "invalid packet: f1=#{f1}" unless f1.zero?
|
|
874
|
+
|
|
860
875
|
default = pkt.lcs
|
|
861
|
-
|
|
876
|
+
new(db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default)
|
|
862
877
|
end
|
|
863
878
|
|
|
864
879
|
attr_reader :db, :table, :org_table, :name, :org_name, :charsetnr, :length, :type, :flags, :decimals, :default
|
|
@@ -871,14 +886,16 @@ class MysqlPR
|
|
|
871
886
|
# Prepare result packet
|
|
872
887
|
class PrepareResultPacket
|
|
873
888
|
def self.parse(pkt)
|
|
874
|
-
raise ProtocolError, "invalid packet" unless pkt.utiny
|
|
889
|
+
raise ProtocolError, "invalid packet" unless pkt.utiny.zero?
|
|
890
|
+
|
|
875
891
|
statement_id = pkt.ulong
|
|
876
892
|
field_count = pkt.ushort
|
|
877
893
|
param_count = pkt.ushort
|
|
878
894
|
f = pkt.utiny
|
|
879
895
|
warning_count = pkt.ushort
|
|
880
|
-
raise ProtocolError, "invalid packet" unless f
|
|
881
|
-
|
|
896
|
+
raise ProtocolError, "invalid packet" unless f.zero?
|
|
897
|
+
|
|
898
|
+
new statement_id, field_count, param_count, warning_count
|
|
882
899
|
end
|
|
883
900
|
|
|
884
901
|
attr_reader :statement_id, :field_count, :param_count, :warning_count
|
|
@@ -895,19 +912,20 @@ class MysqlPR
|
|
|
895
912
|
client_flags,
|
|
896
913
|
max_packet_size,
|
|
897
914
|
charset_number,
|
|
898
|
-
""
|
|
915
|
+
"" # filler: 23 bytes of 0x00
|
|
899
916
|
].pack("VVCa23")
|
|
900
917
|
end
|
|
901
918
|
end
|
|
902
919
|
|
|
903
920
|
# Authentication packet
|
|
904
921
|
class AuthenticationPacket
|
|
905
|
-
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename,
|
|
922
|
+
def self.serialize(client_flags, max_packet_size, charset_number, username, scrambled_password, databasename,
|
|
923
|
+
auth_plugin_name = nil)
|
|
906
924
|
packet = [
|
|
907
925
|
client_flags,
|
|
908
926
|
max_packet_size,
|
|
909
927
|
charset_number,
|
|
910
|
-
""
|
|
928
|
+
"" # reserved 23 bytes
|
|
911
929
|
].pack("VVCa23")
|
|
912
930
|
|
|
913
931
|
packet << "#{username}\x00"
|
|
@@ -929,30 +947,32 @@ class MysqlPR
|
|
|
929
947
|
netvalues.concat n if v
|
|
930
948
|
t
|
|
931
949
|
end
|
|
932
|
-
[MysqlPR::COM_STMT_EXECUTE, statement_id, cursor_type, 1, nbm, 1, types.pack("v*"),
|
|
950
|
+
[MysqlPR::COM_STMT_EXECUTE, statement_id, cursor_type, 1, nbm, 1, types.pack("v*"),
|
|
951
|
+
netvalues].pack("CVCVa*Ca*a*")
|
|
933
952
|
end
|
|
934
953
|
|
|
935
954
|
# make null bitmap
|
|
936
955
|
#
|
|
937
956
|
# If values is [1, nil, 2, 3, nil] then returns "\x12"(0b10010).
|
|
938
957
|
def self.null_bitmap(values)
|
|
939
|
-
bitmap = values.enum_for(:each_slice,8).map do |vals|
|
|
940
|
-
vals.reverse.inject(0){|b, v|(b << 1 | (v ? 0 : 1))}
|
|
958
|
+
bitmap = values.enum_for(:each_slice, 8).map do |vals|
|
|
959
|
+
vals.reverse.inject(0) { |b, v| (b << 1 | (v ? 0 : 1)) }
|
|
941
960
|
end
|
|
942
|
-
|
|
961
|
+
bitmap.pack("C*")
|
|
943
962
|
end
|
|
944
|
-
|
|
945
963
|
end
|
|
946
964
|
end
|
|
947
965
|
|
|
948
966
|
class RawRecord
|
|
949
967
|
def initialize(packet, nfields, encoding)
|
|
950
|
-
@packet
|
|
968
|
+
@packet = packet
|
|
969
|
+
@nfields = nfields
|
|
970
|
+
@encoding = encoding
|
|
951
971
|
end
|
|
952
972
|
|
|
953
973
|
def to_a
|
|
954
974
|
@nfields.times.map do
|
|
955
|
-
if s = @packet.lcs
|
|
975
|
+
if (s = @packet.lcs)
|
|
956
976
|
s = Charset.convert_encoding(s, @encoding)
|
|
957
977
|
end
|
|
958
978
|
s
|
|
@@ -966,34 +986,34 @@ class MysqlPR
|
|
|
966
986
|
# fields :: [Array of Fields]
|
|
967
987
|
# encoding:: [Encoding]
|
|
968
988
|
def initialize(packet, fields, encoding)
|
|
969
|
-
@packet
|
|
989
|
+
@packet = packet
|
|
990
|
+
@fields = fields
|
|
991
|
+
@encoding = encoding
|
|
970
992
|
end
|
|
971
993
|
|
|
972
994
|
# Parse statement result packet
|
|
973
995
|
# === Return
|
|
974
996
|
# [Array of Object] one record
|
|
975
997
|
def parse_record_packet
|
|
976
|
-
@packet.utiny
|
|
977
|
-
null_bit_map = @packet.read((@fields.length+7+2)/8).
|
|
978
|
-
|
|
979
|
-
if null_bit_map[i+2] ==
|
|
998
|
+
@packet.utiny # skip first byte
|
|
999
|
+
null_bit_map = @packet.read((@fields.length + 7 + 2) / 8).unpack1("b*")
|
|
1000
|
+
@fields.each_with_index.map do |f, i|
|
|
1001
|
+
if null_bit_map[i + 2] == "1"
|
|
980
1002
|
nil
|
|
981
1003
|
else
|
|
982
1004
|
unsigned = f.flags & Field::UNSIGNED_FLAG != 0
|
|
983
1005
|
v = Protocol.net2value(@packet, f.type, unsigned)
|
|
984
|
-
if v.is_a?
|
|
1006
|
+
if v.is_a?(Numeric) || v.is_a?(MysqlPR::Time)
|
|
985
1007
|
v
|
|
986
|
-
elsif f.type == Field::TYPE_BIT
|
|
1008
|
+
elsif (f.type == Field::TYPE_BIT) || (f.charsetnr == Charset::BINARY_CHARSET_NUMBER)
|
|
987
1009
|
Charset.to_binary(v)
|
|
988
1010
|
else
|
|
989
1011
|
Charset.convert_encoding(v, @encoding)
|
|
990
1012
|
end
|
|
991
1013
|
end
|
|
992
1014
|
end
|
|
993
|
-
rec
|
|
994
1015
|
end
|
|
995
1016
|
|
|
996
1017
|
alias to_a parse_record_packet
|
|
997
|
-
|
|
998
1018
|
end
|
|
999
1019
|
end
|