Skip to content

Fix LOCAL INFILE options in mysql driver handshake #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/myxql.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule MyXQL do
| {:prepare, :force_named | :named | :unnamed}
| {:disconnect_on_error_codes, [atom()]}
| {:enable_cleartext_plugin, boolean()}
| {:local_infile, boolean()}
| DBConnection.start_option()

@type option() :: DBConnection.option()
Expand Down Expand Up @@ -103,6 +104,8 @@ defmodule MyXQL do

* `:enable_cleartext_plugin` - Set to `true` to send password as cleartext (default: `false`)

* `:local_infile` - Set to `true` to enable LOCAL INFILE capability (default: `false`)

The given options are passed down to DBConnection, some of the most commonly used ones are
documented below:

Expand Down
42 changes: 39 additions & 3 deletions lib/myxql/client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ defmodule MyXQL.Client do
:max_packet_size,
:charset,
:collation,
:enable_cleartext_plugin
:enable_cleartext_plugin,
:local_infile
]

@sock_opts [mode: :binary, packet: :raw, active: false]
Expand Down Expand Up @@ -67,7 +68,8 @@ defmodule MyXQL.Client do
socket_options: (opts[:socket_options] || []) ++ @sock_opts,
charset: Keyword.get(opts, :charset),
collation: Keyword.get(opts, :collation),
enable_cleartext_plugin: Keyword.get(opts, :enable_cleartext_plugin, false)
enable_cleartext_plugin: Keyword.get(opts, :enable_cleartext_plugin, false),
local_infile: Keyword.get(opts, :local_infile, false)
}
end

Expand Down Expand Up @@ -135,7 +137,32 @@ defmodule MyXQL.Client do

def com_query(client, statement, result_state \\ :single) do
with :ok <- send_com(client, {:com_query, statement}) do
recv_packets(client, &decode_com_query_response/3, :initial, result_state)
case recv_packets(client, &decode_com_query_response/3, :initial, result_state) do
{:ok, {:local_infile, filename}} ->
case handle_local_infile(client, filename) do
:ok ->
recv_packets(client, &decode_com_query_response/3, :initial, result_state)

error ->
error
end

other ->
other
end
end
end

def handle_local_infile(client, filename) do
case File.read(filename) do
{:ok, content} ->
with :ok <- send_packet(client, content, 2),
:ok <- send_packet(client, <<>>, 3) do
:ok
end

{:error, _reason} ->
send_packet(client, <<>>, 2)
end
end

Expand Down Expand Up @@ -265,6 +292,15 @@ defmodule MyXQL.Client do
{:many, results} -> {:ok, [result | results]}
end

{:local_infile, filename} ->
case handle_local_infile(client, filename) do
:ok ->
recv_packets(rest, decoder, decoder_state, result_state, timeout, client)

{:error, reason} ->
{:error, reason}
end

{:error, _} = error ->
error
end
Expand Down
12 changes: 12 additions & 0 deletions lib/myxql/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ defmodule MyXQL.Protocol do
])
|> maybe_put_capability_flag(:client_connect_with_db, !is_nil(config.database))
|> maybe_put_capability_flag(:client_ssl, is_list(config.ssl_opts))
|> maybe_put_capability_flag(:client_local_files, config.local_infile)

if config.ssl_opts && !has_capability_flag?(server_capability_flags, :client_ssl) do
{:error, :server_does_not_support_ssl}
Expand Down Expand Up @@ -331,6 +332,12 @@ defmodule MyXQL.Protocol do
{:halt, decode_ok_packet_body(rest)}
end

def decode_com_query_response(<<0xFB, rest::binary>>, "", :initial) do
{filename, ""} = take_string_nul(rest)

{:local_infile, filename}
end

def decode_com_query_response(<<0xFF, rest::binary>>, "", :initial) do
{:halt, decode_err_packet_body(rest)}
end
Expand Down Expand Up @@ -513,6 +520,11 @@ defmodule MyXQL.Protocol do
{:cont, :initial, {:many, [resultset | results]}}
end

defp decode_resultset(<<0xFB, rest::binary>>, _next_data, :initial, _row_decoder) do
{filename, ""} = take_string_nul(rest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base on the changes to take_string_nul, it seems you are not expecting a nul here? So maybe we should not call/change said function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we shouldn't change the function!

{:local_infile, filename}
end

defp decode_resultset(payload, _next_data, :initial, _row_decoder) do
{:cont, {:column_defs, decode_int_lenenc(payload), []}}
end
Expand Down
15 changes: 11 additions & 4 deletions lib/myxql/protocol/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ defmodule MyXQL.Protocol.Types do
def encode_int_lenenc(int) when int < 0xFFFFFFFFFFFFFFFF, do: <<0xFE, int::uint8()>>

def decode_int_lenenc(binary) do
{integer, ""} = take_int_lenenc(binary)
{integer, _rest} = take_int_lenenc(binary)
integer
end

def take_int_lenenc(<<int::uint1(), rest::binary>>) when int < 251, do: {int, rest}
def take_int_lenenc(<<0xFB, rest::binary>>), do: {nil, rest}
def take_int_lenenc(<<0xFC, int::uint2(), rest::binary>>), do: {int, rest}
def take_int_lenenc(<<0xFD, int::uint3(), rest::binary>>), do: {int, rest}
def take_int_lenenc(<<0xFE, int::uint8(), rest::binary>>), do: {int, rest}
def take_int_lenenc(<<0xFF, rest::binary>>), do: {:error, rest}

# https://dev.mysql.com/doc/internals/en/string.html#packet-Protocol::FixedLengthString
defmacro string(size) do
Expand Down Expand Up @@ -68,8 +70,13 @@ defmodule MyXQL.Protocol.Types do

def take_string_nul(""), do: {nil, ""}

def take_string_nul(binary) do
[string, rest] = :binary.split(binary, <<0>>)
{string, rest}
def take_string_nul(binary) when is_binary(binary) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change still necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I tested it without the changes to the method, an exception occurred when using the client. So its necessary

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why? We said the filename is always followed by a nul byte, right? That will always return a two element list:

iex(2)> :binary.split(<<"/", 0>>, [<<0>>])
["/", ""]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I change it I got this error:

stopped: ** (MatchError) no match of right hand side value: ["/Users/iagocavalcante/Workspaces/i9Amazon/i9_processador/25332012000225-pdv_caixa_lancamento-20250618_210733-T-110.txt"]
    (myxql 0.8.0-dev) lib/myxql/protocol/types.ex:74: MyXQL.Protocol.Types.take_string_nul/1

case :binary.split(binary, <<0>>) do
[string] ->
{string, ""}

[string, rest] ->
{string, rest}
end
end
end