Skip to content

Commit 4124bec

Browse files
committed
support OPT_LOAD_DATA_LOCAL_DIR
Incompatible change: raise error if the server requires a local file whiout client's OPT_LOCAL_INFILE option or if the filename specified by the server does not match OPT_LOAD_DATA_LOCAL_DIR.
1 parent 5b47f75 commit 4124bec

File tree

4 files changed

+61
-11
lines changed

4 files changed

+61
-11
lines changed

README.rdoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ MySQL connector for Ruby.
4242
* Mysql#options でサポートしているオプションは次のものだけです:
4343
Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT,
4444
Mysql::OPT_LOCAL_INFILE, Mysql::OPT_READ_TIMEOUT,
45-
Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME.
45+
Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME,
46+
Mysql::OPT_LOAD_DATA_LOCAL_DIR.
4647
これら以外を指定すると "option not implementted" という warning が標準エラー出力に出力されます。
4748

4849
* Mysql#use_result は Mysql#store_result と同じです。つまりサーバーから一気に結果セットを読み込みます。
@@ -64,5 +65,5 @@ MySQL connector for Ruby.
6465
== Copyright
6566

6667
Author :: TOMITA Masahiro <[email protected]>
67-
Copyright :: Copyright (c) 2009-2012 TOMITA Masahiro
68+
Copyright :: Copyright (c) 2008 TOMITA Masahiro
6869
License :: Ruby's

lib/mysql.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# coding: ascii-8bit
2-
# Copyright (C) 2008-2012 TOMITA Masahiro
2+
# Copyright (C) 2008 TOMITA Masahiro
33
44

55
# MySQL connection class.
@@ -112,8 +112,8 @@ def connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=0
112112
warn 'unsupported flag: CLIENT_COMPRESS' if $VERBOSE
113113
flag &= ~CLIENT_COMPRESS
114114
end
115-
@protocol = Protocol.new host, port, socket, @connect_timeout, @read_timeout, @write_timeout
116-
@protocol.authenticate user, passwd, db, (@local_infile ? CLIENT_LOCAL_FILES : 0) | flag, @charset
115+
@protocol = Protocol.new host, port, socket, @connect_timeout, @read_timeout, @write_timeout, @local_infile
116+
@protocol.authenticate user, passwd, db, flag, @charset
117117
@charset ||= @protocol.charset
118118
@host_info = (host.nil? || host == "localhost") ? 'Localhost via UNIX socket' : "#{host} via TCP/IP"
119119
query @init_command if @init_command
@@ -158,6 +158,8 @@ def options(opt, value=nil)
158158
@connect_timeout = value
159159
# when Mysql::GUESS_CONNECTION
160160
when Mysql::OPT_LOCAL_INFILE
161+
@local_infile = value ? '' : nil
162+
when Mysql::OPT_LOAD_DATA_LOCAL_DIR
161163
@local_infile = value
162164
# when Mysql::OPT_NAMED_PIPE
163165
# when Mysql::OPT_PROTOCOL

lib/mysql/protocol.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# coding: ascii-8bit
2-
# Copyright (C) 2008-2012 TOMITA Masahiro
2+
# Copyright (C) 2008 TOMITA Masahiro
33
44

55
require "socket"
@@ -134,15 +134,17 @@ def self.value2net(v)
134134
# conn_timeout :: [Integer] connect timeout (sec).
135135
# read_timeout :: [Integer] read timeout (sec).
136136
# write_timeout :: [Integer] write timeout (sec).
137+
# local_infile :: [String] local infile path
137138
# === Exception
138139
# [ClientError] :: connection timeout
139-
def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout)
140+
def initialize(host, port, socket, conn_timeout, read_timeout, write_timeout, local_infile)
140141
@insert_id = 0
141142
@warning_count = 0
142143
@gc_stmt_queue = [] # stmt id list which GC destroy.
143144
set_state :INIT
144145
@read_timeout = read_timeout
145146
@write_timeout = write_timeout
147+
@local_infile = local_infile
146148
begin
147149
Timeout.timeout conn_timeout do
148150
if host.nil? or host.empty? or host == "localhost"
@@ -180,6 +182,7 @@ def authenticate(user, passwd, db, flag, charset)
180182
@server_version = init_packet.server_version.split(/\D/)[0,3].inject{|a,b|a.to_i*100+b.to_i}
181183
@thread_id = init_packet.thread_id
182184
client_flags = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
185+
client_flags |= CLIENT_LOCAL_FILES if @local_infile
183186
client_flags |= CLIENT_CONNECT_WITH_DB if db
184187
client_flags |= flag
185188
@charset = charset
@@ -230,10 +233,7 @@ def get_result
230233
return res_packet.field_count
231234
end
232235
if res_packet.field_count.nil? # LOAD DATA LOCAL INFILE
233-
filename = res_packet.message
234-
File.open(filename){|f| write f}
235-
write nil # EOF mark
236-
read
236+
send_local_file(res_packet.message)
237237
end
238238
@affected_rows, @insert_id, @server_status, @warning_count, @message =
239239
res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count, res_packet.message
@@ -245,6 +245,19 @@ def get_result
245245
end
246246
end
247247

248+
# send local file to server
249+
def send_local_file(filename)
250+
filename = File.absolute_path(filename)
251+
if filename.start_with? @local_infile
252+
File.open(filename){|f| write f}
253+
else
254+
raise ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.'
255+
end
256+
ensure
257+
write nil # EOF mark
258+
read
259+
end
260+
248261
# Retrieve n fields
249262
# === Argument
250263
# n :: [Integer] number of fields

test/test_mysql.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,40 @@ class TestMysql < Test::Unit::TestCase
160160
@m.query("load data local infile '#{tmpf.path}' into table t")
161161
assert{ @m.query('select * from t').fetch_row == ['123','abc'] }
162162
end
163+
test 'OPT_LOAD_DATA_LOCAL_DIR: client can execute LOAD DATA LOCAL INFILE query with specified directory' do
164+
require 'tempfile'
165+
tmpf = Tempfile.new 'mysql_spec'
166+
tmpf.puts "123\tabc\n"
167+
tmpf.close
168+
assert{ @m.options(Mysql::OPT_LOAD_DATA_LOCAL_DIR, File.dirname(tmpf.path)) == @m }
169+
@m.connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, MYSQL_SOCKET)
170+
@m.query('create temporary table t (i int, c char(10))')
171+
@m.query("load data local infile '#{tmpf.path}' into table t")
172+
assert{ @m.query('select * from t').fetch_row == ['123','abc'] }
173+
end
174+
test 'OPT_LOAD_DATA_LOCAL_DIR: client cannot execute LOAD DATA LOCAL INFILE query without specified directory' do
175+
require 'tempfile'
176+
tmpf = Tempfile.new 'mysql_spec'
177+
tmpf.puts "123\tabc\n"
178+
tmpf.close
179+
assert{ @m.options(Mysql::OPT_LOAD_DATA_LOCAL_DIR, '/hoge') == @m }
180+
@m.connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, MYSQL_SOCKET)
181+
@m.query('create temporary table t (i int, c char(10))')
182+
assert_raise Mysql::ClientError::LoadDataLocalInfileRejected, 'LOAD DATA LOCAL INFILE file request rejected due to restrictions on access.' do
183+
@m.query("load data local infile '#{tmpf.path}' into table t")
184+
end
185+
end
186+
test 'without OPT_LOCAL_INFILE and OPT_LOAD_DATA_LOCAL_DIR: client cannot execute LOAD DATA LOCAL INFILE query' do
187+
require 'tempfile'
188+
tmpf = Tempfile.new 'mysql_spec'
189+
tmpf.puts "123\tabc\n"
190+
tmpf.close
191+
@m.connect(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_PORT, MYSQL_SOCKET)
192+
@m.query('create temporary table t (i int, c char(10))')
193+
assert_raise Mysql::ServerError::NotAllowedCommand, 'The used command is not allowed with this MySQL version' do
194+
@m.query("load data local infile '#{tmpf.path}' into table t")
195+
end
196+
end
163197
test 'OPT_READ_TIMEOUT: set timeout for reading packet' do
164198
assert{ @m.options(Mysql::OPT_READ_TIMEOUT, 10) == @m }
165199
end

0 commit comments

Comments
 (0)