cover/mysql_protocol.COVER.html

1 %% MySQL/OTP – MySQL client library for Erlang/OTP
2 %% Copyright (C) 2014-2021 Viktor Söderqvist
3 %% 2017 Piotr Nosek, Michal Slaski
4 %%
5 %% This file is part of MySQL/OTP.
6 %%
7 %% MySQL/OTP is free software: you can redistribute it and/or modify it under
8 %% the terms of the GNU Lesser General Public License as published by the Free
9 %% Software Foundation, either version 3 of the License, or (at your option)
10 %% any later version.
11 %%
12 %% This program is distributed in the hope that it will be useful, but WITHOUT
13 %% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 %% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 %% more details.
16 %%
17 %% You should have received a copy of the GNU Lesser General Public License
18 %% along with this program. If not, see <https://www.gnu.org/licenses/>.
19
20 %% @doc This module implements parts of the MySQL client/server protocol.
21 %%
22 %% The protocol is described in the document "MySQL Internals" which can be
23 %% found under "MySQL Documentation: Expert Guides" on http://dev.mysql.com/.
24 %%
25 %% TCP communication is not handled in this module. Most of the public functions
26 %% take funs for data communitaction as parameters.
27 %% @private
28 -module(mysql_protocol).
29
30 -export([handshake/8, change_user/8, quit/2, ping/2,
31 query/7, fetch_query_response/6, prepare/3, unprepare/3,
32 execute/8, fetch_execute_response/6, reset_connnection/2,
33 valid_params/1, valid_path/1]).
34
35 -type query_filtermap() :: no_filtermap_fun | mysql:query_filtermap_fun().
36
37 -type auth_more_data() :: fast_auth_completed
38 | full_auth_requested
39 | {public_key, term()}.
40
41 -type decode_decimal() :: mysql:decode_decimal().
42
43 %% How much data do we want per packet?
44 -define(MAX_BYTES_PER_PACKET, 16#1000000).
45
46 -include_lib("public_key/include/public_key.hrl").
47
48 -include("records.hrl").
49 -include("protocol.hrl").
50 -include("server_status.hrl").
51
52 %% Macros for pattern matching on packets.
53 -define(ok_pattern, <<?OK, _/binary>>).
54 -define(error_pattern, <<?ERROR, _/binary>>).
55 -define(eof_pattern, <<?EOF, _:4/binary>>).
56 -define(local_infile_pattern, <<?LOCAL_INFILE_REQUEST, _/binary>>).
57
58 %% Macros for auth methods.
59 -define(authmethod_none, <<>>).
60 -define(authmethod_mysql_native_password, <<"mysql_native_password">>).
61 -define(authmethod_sha256_password, <<"sha256_password">>).
62 -define(authmethod_caching_sha2_password, <<"caching_sha2_password">>).
63
64 %% @doc Performs a handshake using the supplied socket and socket module for
65 %% communication. Returns an ok or an error record. Raises errors when various
66 %% unimplemented features are requested.
67 -spec handshake(Host :: inet:socket_address() | inet:hostname(),
68 Username :: iodata(), Password :: iodata(),
69 Database :: iodata() | undefined,
70 SockModule :: module(), SSLOpts :: list() | undefined,
71 Socket :: term(),
72 SetFoundRows :: boolean()) ->
73 {ok, #handshake{}, SockModule :: module(), Socket :: term()} |
74 #error{}.
75
76 handshake(Host, Username, Password, Database, SockModule0, SSLOpts, Socket0,
77 SetFoundRows) ->
78 55 SeqNum0 = 0,
79 55 case recv_packet(SockModule0, Socket0, SeqNum0) of
80 {ok, HandshakePacket, SeqNum1} ->
81 54 handshake_process_initial_packet(HandshakePacket, Host, Username, Password,
82 Database, SockModule0, SSLOpts, Socket0,
83 SeqNum1, SetFoundRows);
84 {error, Reason} ->
85 1 #error{code = -3, msg = iolist_to_binary(io_lib:format("Error during handshake: ~p", [Reason]))}
86 end.
87
88 handshake_process_initial_packet(HandshakePacket, Host, Username, Password, Database,
89 SockModule0, SSLOpts, Socket0, SeqNum1, SetFoundRows) ->
90 54 case parse_handshake(HandshakePacket) of
91 #handshake{} = Handshake ->
92 52 handshake_upgrade_connection(Handshake, Host, Username, Password, Database,
93 SockModule0, SSLOpts, Socket0, SeqNum1, SetFoundRows);
94 #error{} = Error ->
95 1 Error
96 end.
97
98 handshake_upgrade_connection(Handshake, Host, Username, Password, Database,
99 SockModule0, SSLOpts, Socket0, SeqNum1, SetFoundRows) ->
100 52 {ok, SockModule, Socket, SeqNum2} =
101 maybe_do_ssl_upgrade(Host, SockModule0, Socket0, SeqNum1, Handshake,
102 SSLOpts, Database, SetFoundRows),
103 52 handshake_send_response(Handshake, Username, Password, Database, SockModule,
104 Socket, SeqNum2, SetFoundRows).
105
106 handshake_send_response(Handshake, Username, Password, Database, SockModule,
107 Socket, SeqNum2, SetFoundRows) ->
108 52 Response = build_handshake_response(Handshake, Username, Password,
109 Database, SetFoundRows),
110 52 {ok, SeqNum3} = send_packet(SockModule, Socket, Response, SeqNum2),
111 52 handshake_finish_or_switch_auth(Handshake, Password, SockModule, Socket,
112 SeqNum3).
113
114 handshake_finish_or_switch_auth(Handshake, Password, SockModule, Socket, SeqNum) ->
115 52 #handshake{auth_plugin_name = AuthPluginName,
116 auth_plugin_data = AuthPluginData,
117 server_version = ServerVersion,
118 status = Status} = Handshake,
119 52 AuthResult = auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
120 SockModule, Socket, ServerVersion, SeqNum),
121 52 case AuthResult of
122 #ok{status = OkStatus} ->
123 %% check status, ignoring bit 16#4000, SERVER_SESSION_STATE_CHANGED
124 %% and bit 16#0002, SERVER_STATUS_AUTOCOMMIT.
125 49 BitMask = bnot (?SERVER_SESSION_STATE_CHANGED bor ?SERVER_STATUS_AUTOCOMMIT),
126 49 StatusMasked = Status band BitMask,
127 49 StatusMasked = OkStatus band BitMask,
128 49 {ok, Handshake, SockModule, Socket};
129 Error ->
130 3 Error
131 end.
132
133 %% Finish the authentication, or switch to another auth method.
134 %%
135 %% An OK Packet signals authentication success.
136 %%
137 %% An Error Packet signals authentication failure.
138 %%
139 %% If the authentication process requires more data to be exchanged between
140 %% the server and client, this is done via More Data Packets. The formats and
141 %% meanings of the payloads in such packets depend on the auth method.
142 %%
143 %% An Auth Method Switch Packet signals a request for transition to another
144 %% auth method. The packet contains the name of the auth method to switch to,
145 %% and new auth plugin data.
146 auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
147 SockModule, Socket, ServerVersion, SeqNum0) ->
148 133 case recv_packet(SockModule, Socket, SeqNum0) of
149 {ok, ConfirmPacket, SeqNum1} ->
150 132 auth_process_confirm_packet(ConfirmPacket, AuthPluginName, AuthPluginData,
151 Password, SockModule, Socket, ServerVersion, SeqNum1);
152 {error, Reason} ->
153 1 #error{code = -4, msg = iolist_to_binary(io_lib:format("Error during authentication: ~p", [Reason]))}
154 end.
155
156 auth_process_confirm_packet(ConfirmPacket, AuthPluginName, AuthPluginData, Password,
157 SockModule, Socket, ServerVersion, SeqNum1) ->
158 132 case parse_handshake_confirm(ConfirmPacket) of
159 #ok{} = Ok ->
160 %% Authentication success.
161 57 Ok;
162 #auth_method_switch{auth_plugin_name = SwitchAuthPluginName,
163 auth_plugin_data = SwitchAuthPluginData} ->
164 %% Server wants to transition to a different auth method.
165 %% Send hash of password, calculated according to the requested auth method.
166 %% (NOTE: Sending the password hash as a response to an auth method switch
167 %% is the answer for both mysql_native_password and caching_sha2_password
168 %% methods. It may be different for future other auth methods.)
169 9 Hash = hash_password(SwitchAuthPluginName, Password, SwitchAuthPluginData),
170 9 {ok, SeqNum2} = send_packet(SockModule, Socket, Hash, SeqNum1),
171 9 auth_finish_or_switch(SwitchAuthPluginName, SwitchAuthPluginData, Password,
172 SockModule, Socket, ServerVersion, SeqNum2);
173 fast_auth_completed ->
174 %% Server signals success by fast authentication (probably specific to
175 %% the caching_sha2_password method). This will be followed by an OK Packet.
176 57 auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
177 Socket, ServerVersion, SeqNum1);
178 full_auth_requested when SockModule =:= ssl ->
179 %% Server wants full authentication (probably specific to the
180 %% caching_sha2_password method), and we are on a secure channel since
181 %% our connection is through SSL. We have to reply with the null-terminated
182 %% clear text password.
183
:-(
Password1 = case is_binary(Password) of
184
:-(
true -> Password;
185
:-(
false -> iolist_to_binary(Password)
186 end,
187
:-(
{ok, SeqNum2} = send_packet(SockModule, Socket, <<Password1/binary, 0>>, SeqNum1),
188
:-(
auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
189 Socket, ServerVersion, SeqNum2);
190 full_auth_requested ->
191 %% Server wants full authentication (probably specific to the
192 %% caching_sha2_password method), and we are not on a secure channel.
193 %% Since we are not implementing the client-side caching of the server's
194 %% public key, we must ask for it by sending a single byte "2".
195 3 {ok, SeqNum2} = send_packet(SockModule, Socket, <<2:8>>, SeqNum1),
196 3 auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
197 Socket, ServerVersion, SeqNum2);
198 {public_key, PubKey} ->
199 %% Server has sent its public key (certainly specific to the caching_sha2_password
200 %% method). We encrypt the password with the public key we received and send
201 %% it back to the server.
202 3 EncryptedPassword = encrypt_password(Password, AuthPluginData, PubKey,
203 ServerVersion),
204 3 {ok, SeqNum2} = send_packet(SockModule, Socket, EncryptedPassword, SeqNum1),
205 3 auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
206 Socket, ServerVersion, SeqNum2);
207 Error ->
208 %% Authentication failure.
209 3 Error
210 end.
211
212 -spec quit(module(), term()) -> ok.
213 quit(SockModule, Socket) ->
214 39 {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_QUIT>>, 0),
215 39 case recv_packet(SockModule, Socket, SeqNum1) of
216 39 {error, closed} -> ok; %% MySQL 5.5.40 and more
217
:-(
{ok, ?ok_pattern, _SeqNum2} -> ok %% Some older MySQL versions?
218 end.
219
220 -spec ping(module(), term()) -> #ok{}.
221 ping(SockModule, Socket) ->
222 4 {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_PING>>, 0),
223 3 {ok, OkPacket, _SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
224 3 parse_ok_packet(OkPacket).
225
226 -spec query(Query :: iodata(), module(), term(), [binary()],
227 decode_decimal(), query_filtermap(), timeout()) ->
228 {ok, [#ok{} | #resultset{} | #error{}]} | {error, term()}.
229 query(Query, SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout) ->
230 559 Req = <<?COM_QUERY, (iolist_to_binary(Query))/binary>>,
231 559 SeqNum0 = 0,
232 559 {ok, _SeqNum1} = send_packet(SockModule, Socket, Req, SeqNum0),
233 559 fetch_query_response(SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout).
234
235 %% @doc This is used by query/4. If query/4 returns {error, timeout}, this
236 %% function can be called to retry to fetch the results of the query.
237 fetch_query_response(SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout) ->
238 560 fetch_response(SockModule, Socket, Timeout, text, AllowedPaths, DecodeDecimal, FilterMap, []).
239
240 %% @doc Prepares a statement.
241 -spec prepare(iodata(), module(), term()) -> #error{} | #prepared{}.
242 prepare(Query, SockModule, Socket) ->
243 127 Req = <<?COM_STMT_PREPARE, (iolist_to_binary(Query))/binary>>,
244 127 {ok, SeqNum1} = send_packet(SockModule, Socket, Req, 0),
245 127 {ok, Resp, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
246 127 case Resp of
247 ?error_pattern ->
248 5 parse_error_packet(Resp);
249 <<?OK,
250 StmtId:32/little,
251 NumColumns:16/little,
252 NumParams:16/little,
253 0, %% reserved_1 -- [00] filler
254 WarningCount:16/little>> ->
255 %% This was the first packet.
256 %% Now: Parameter Definition Block. The parameter definitions don't
257 %% contain any useful data at all. They are always TYPE_VAR_STRING
258 %% with charset 'binary' so we have to select a type ourselves for
259 %% the parameters we have in execute/4.
260 122 {_ParamDefs, SeqNum3} =
261 fetch_column_definitions_if_any(NumParams, SockModule, Socket,
262 SeqNum2),
263 %% Column Definition Block. We get column definitions in execute
264 %% too, so we don't need them here. We *could* store them to be able
265 %% to provide the user with some info about a prepared statement.
266 122 {_ColDefs, _SeqNum4} =
267 fetch_column_definitions_if_any(NumColumns, SockModule, Socket,
268 SeqNum3),
269 122 #prepared{statement_id = StmtId,
270 orig_query = Query,
271 param_count = NumParams,
272 warning_count = WarningCount}
273 end.
274
275 %% @doc Deallocates a prepared statement.
276 -spec unprepare(#prepared{}, module(), term()) -> ok.
277 unprepare(#prepared{statement_id = Id}, SockModule, Socket) ->
278 93 {ok, _SeqNum} = send_packet(SockModule, Socket,
279 <<?COM_STMT_CLOSE, Id:32/little>>, 0),
280 93 ok.
281
282 %% @doc Executes a prepared statement.
283 -spec execute(#prepared{}, [term()], module(), term(), [binary()],
284 decode_decimal(), query_filtermap(), timeout()) ->
285 {ok, [#ok{} | #resultset{} | #error{}]} | {error, term()}.
286 execute(#prepared{statement_id = Id, param_count = ParamCount}, ParamValues,
287 SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout)
288 when ParamCount == length(ParamValues) ->
289 %% Flags Constant Name
290 %% 0x00 CURSOR_TYPE_NO_CURSOR
291 %% 0x01 CURSOR_TYPE_READ_ONLY
292 %% 0x02 CURSOR_TYPE_FOR_UPDATE
293 %% 0x04 CURSOR_TYPE_SCROLLABLE
294 223 Flags = 0,
295 223 Req0 = <<?COM_STMT_EXECUTE, Id:32/little, Flags, 1:32/little>>,
296 223 Req = case ParamCount of
297 0 ->
298 118 Req0;
299 _ ->
300 %% We can't use the parameter types returned by the prepare call.
301 %% They are all reported as ?TYPE_VAR_STRING with character
302 %% set 'binary'.
303 105 NullBitMap = build_null_bitmap(ParamValues),
304 %% What does it mean to *not* bind new params? To use the same
305 %% params as last time? Right now we always bind params each time.
306 105 NewParamsBoundFlag = 1,
307 105 Req1 = <<Req0/binary, NullBitMap/binary, NewParamsBoundFlag>>,
308 %% For each value, first append type and signedness (16#80 signed or
309 %% 00 unsigned) for all values and then the binary encoded values.
310 105 EncodedParams = lists:map(fun encode_param/1, ParamValues),
311 105 {TypesAndSigns, EncValues} = lists:unzip(EncodedParams),
312 105 iolist_to_binary([Req1, TypesAndSigns, EncValues])
313 end,
314 223 {ok, _SeqNum1} = send_packet(SockModule, Socket, Req, 0),
315 223 fetch_execute_response(SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout).
316
317 %% @doc This is used by execute/5. If execute/5 returns {error, timeout}, this
318 %% function can be called to retry to fetch the results of the query.
319 fetch_execute_response(SockModule, Socket, AllowedPaths, DecodeDecimal, FilterMap, Timeout) ->
320 225 fetch_response(SockModule, Socket, Timeout, binary, AllowedPaths, DecodeDecimal, FilterMap, []).
321
322 %% @doc Changes the user of the connection.
323 -spec change_user(module(), term(), iodata(), iodata(), binary(), binary(),
324 undefined | iodata(), [integer()]) -> #ok{} | #error{}.
325 change_user(SockModule, Socket, Username, Password, AuthPluginName, AuthPluginData,
326 Database, ServerVersion) ->
327 9 DbBin = case Database of
328 8 undefined -> <<>>;
329 1 _ -> iolist_to_binary(Database)
330 end,
331 9 Hash = hash_password(AuthPluginName, Password, AuthPluginData),
332 9 Req0 = <<?COM_CHANGE_USER, (iolist_to_binary(Username))/binary, 0,
333 (lenenc_str_encode(Hash))/binary,
334 DbBin/binary, 0, (character_set(ServerVersion)):16/little>>,
335 9 Req1 = case AuthPluginName of
336 <<>> ->
337
:-(
Req0;
338 _ ->
339 9 <<Req0/binary, AuthPluginName/binary, 0>>
340 end,
341 9 {ok, SeqNum1} = send_packet(SockModule, Socket, Req1, 0),
342 9 auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
343 SockModule, Socket, ServerVersion, SeqNum1).
344
345 -spec reset_connnection(module(), term()) -> #ok{}|#error{}.
346 reset_connnection(SockModule, Socket) ->
347 1 {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_RESET_CONNECTION>>, 0),
348 1 {ok, Packet, _SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
349 1 case Packet of
350 ?ok_pattern ->
351 1 parse_ok_packet(Packet);
352 ?error_pattern ->
353
:-(
parse_error_packet(Packet)
354 end.
355
356 %% --- internal ---
357
358 %% @doc Parses a handshake. This is the first thing that comes from the server
359 %% when connecting. If an unsupported version or variant of the protocol is used
360 %% an error is raised.
361 -spec parse_handshake(binary()) -> #handshake{} | #error{}.
362 parse_handshake(<<10, Rest/binary>>) ->
363 %% Protocol version 10.
364 52 {ServerVersion, Rest1} = nulterm_str(Rest),
365 52 <<ConnectionId:32/little,
366 AuthPluginDataPart1:8/binary-unit:8,
367 0, %% "filler" -- everything below is optional
368 CapabilitiesLower:16/little,
369 CharacterSet:8,
370 StatusFlags:16/little,
371 CapabilitiesUpper:16/little,
372 AuthPluginDataLength:8, %% if cabab & CLIENT_PLUGIN_AUTH, otherwise 0
373 _Reserved:10/binary-unit:8, %% 10 unused (reserved) bytes
374 Rest3/binary>> = Rest1,
375 52 Capabilities = CapabilitiesLower + 16#10000 * CapabilitiesUpper,
376 52 Len = case AuthPluginDataLength of
377
:-(
0 -> 13; %% Server has not CLIENT_PLUGIN_AUTH
378 52 K -> K - 8 %% Part 2 length = Total length minus the 8 bytes in part 1.
379 end,
380 52 <<AuthPluginDataPart2:Len/binary-unit:8, AuthPluginName/binary>> = Rest3,
381 52 AuthPluginData = <<AuthPluginDataPart1/binary, AuthPluginDataPart2/binary>>,
382 %% "Due to Bug#59453 the auth-plugin-name is missing the terminating
383 %% NUL-char in versions prior to 5.5.10 and 5.6.2."
384 %% Strip the final NUL byte if any.
385 %% This may also be <<>> in older versions.
386 52 L = byte_size(AuthPluginName) - 1,
387 52 AuthPluginName1 = case AuthPluginName of
388 52 <<AuthPluginNameTrimmed:L/binary, 0>> -> AuthPluginNameTrimmed;
389
:-(
_ -> AuthPluginName
390 end,
391 52 {Vendor, Version} = get_server_info(ServerVersion),
392 52 #handshake{server_vendor = Vendor,
393 server_version = Version,
394 connection_id = ConnectionId,
395 capabilities = Capabilities,
396 character_set = CharacterSet,
397 status = StatusFlags,
398 auth_plugin_data = AuthPluginData,
399 auth_plugin_name = AuthPluginName1};
400 parse_handshake(<<?ERROR, ErrNo:16/little, Msg/binary>>) ->
401 %% 'Too many connections' in MariaDB 10.1.21
402 %% (Error packet in pre-4.1 protocol)
403 1 #error{code = ErrNo, msg = Msg};
404 parse_handshake(<<Protocol:8, _/binary>>) when Protocol /= 10 ->
405 1 error(unknown_protocol).
406
407 %% @doc Detects server vendor and version from a version string.
408 -spec get_server_info(binary()) -> {mysql | mariadb, [integer()]}.
409 get_server_info(<<"5.5.5-", Rest/binary>>) ->
410 %% Iff the version string starts with "5.5.5-", it is MariaDB>=10.
411 %% The real version follows after the "5.5.5-" prefix.
412 %% https://mariadb.com/kb/en/connection/#initial-handshake-packet
413 4 {mariadb, server_version_to_list(Rest)};
414 get_server_info(ServerVersion) ->
415 88 Version = server_version_to_list(ServerVersion),
416 88 Vendor = case Version of
417 4 [5, 1|_] -> mysql; %% There is both MySQL and MariaDB 5.1; we assume it is MySQL
418 4 [5, 2|_] -> mariadb; %% There is no MySQL 5.2
419 4 [5, 3|_] -> mariadb; %% There is no MySQL 5.3
420 4 [5, 5|_] -> mysql; %% There is both MySQL and MariaDB 5.5; we assume it is MySQL
421 72 _ -> mysql %% Everything else is definitely MySQL
422 end,
423 88 {Vendor, Version}.
424
425 %% @doc Converts a version on the form `<<"5.6.21">' to a list `[5, 6, 21]'.
426 -spec server_version_to_list(binary()) -> [integer()].
427 server_version_to_list(ServerVersion) ->
428 %% This must work with e.g. "5.5.40-0ubuntu0.12.04.1-log" and "5.5.33a".
429 92 {match, Parts} = re:run(ServerVersion, <<"^(\\d+)\\.(\\d+)\\.(\\d+)">>,
430 [{capture, all_but_first, binary}]),
431 92 lists:map(fun binary_to_integer/1, Parts).
432
433 -spec maybe_do_ssl_upgrade(Host :: inet:socket_address() | inet:hostname(),
434 SockModule0 :: module(),
435 Socket0 :: term(),
436 SeqNum1 :: non_neg_integer(),
437 Handshake :: #handshake{},
438 SSLOpts :: undefined | list(),
439 Database :: iodata() | undefined,
440 SetFoundRows :: boolean()) ->
441 {ok, SockModule :: module(), Socket :: term(),
442 SeqNum2 :: non_neg_integer()}.
443 maybe_do_ssl_upgrade(_Host, SockModule0, Socket0, SeqNum1, _Handshake,
444 undefined, _Database, _SetFoundRows) ->
445 50 {ok, SockModule0, Socket0, SeqNum1};
446 maybe_do_ssl_upgrade(_Host, _SockModule0, _Socket0, _SeqNum1,
447 #handshake{capabilities=Caps},_SSLOpts, _Database,
448 _SetFoundRows) when Caps band ?CLIENT_SSL =/= ?CLIENT_SSL ->
449
:-(
exit({failed_to_upgrade_socket, ssl_not_supported});
450 maybe_do_ssl_upgrade(Host, gen_tcp, Socket0, SeqNum1, Handshake, SSLOpts,
451 Database, SetFoundRows) ->
452 2 Response = build_handshake_response(Handshake, Database, SetFoundRows),
453 2 {ok, SeqNum2} = send_packet(gen_tcp, Socket0, Response, SeqNum1),
454 2 case ssl_connect(Handshake, Host, Socket0, SSLOpts, 5000) of
455 {ok, SSLSocket} ->
456 2 {ok, ssl, SSLSocket, SeqNum2};
457 {error, Reason} ->
458
:-(
exit({failed_to_upgrade_socket, Reason})
459 end.
460
461 ssl_connect(Handshake, Host, Port, ConfigSSLOpts, Timeout) ->
462 2 SSLVersions = determine_ssl_versions(Handshake),
463 2 DefaultSSLOpts0 = [{versions, SSLVersions}, {verify, verify_peer}],
464 2 DefaultSSLOpts1 = case is_list(Host) andalso inet:parse_address(Host) of
465
:-(
false -> DefaultSSLOpts0;
466
:-(
{ok, _} -> DefaultSSLOpts0;
467 2 {error, einval} -> [{server_name_indication, Host} | DefaultSSLOpts0]
468 end,
469 2 MandatorySSLOpts = [{active, false}],
470 2 MergedSSLOpts = merge_ssl_options(DefaultSSLOpts1, MandatorySSLOpts, ConfigSSLOpts),
471 2 ssl:connect(Port, MergedSSLOpts, Timeout).
472
473
474 %% @doc Determines which ssl versions to use according to server vendor and version
475 %% since almostly, mysql &lt; 5.7.0 and mariadb &lt; 10.1.0 supports only tlsv1
476 -spec determine_ssl_versions(#handshake{}) -> list().
477 determine_ssl_versions(#handshake{server_vendor = mysql, server_version = Version}) when Version < [5, 7, 0] ->
478
:-(
[tlsv1];
479 determine_ssl_versions(#handshake{server_vendor = mariadb, server_version = Version}) when Version < [10, 1, 0] ->
480
:-(
[tlsv1];
481 determine_ssl_versions(_) ->
482 2 proplists:get_value(available, ssl:versions()).
483
484 -spec merge_ssl_options(list(), list(), list()) -> list().
485 merge_ssl_options(DefaultSSLOpts, MandatorySSLOpts, ConfigSSLOpts) ->
486 2 SSLOpts1 =
487 lists:foldl(fun({Key, _} = Opt, OptsAcc) ->
488 4 lists:keystore(Key, 1, OptsAcc, Opt)
489 end, DefaultSSLOpts, ConfigSSLOpts),
490 2 lists:foldl(fun({Key, _} = Opt, OptsAcc) ->
491 2 lists:keystore(Key, 1, OptsAcc, Opt)
492 end, SSLOpts1, MandatorySSLOpts).
493
494 %% @doc This function is used when upgrading to encrypted socket. In other,
495 %% cases, build_handshake_response/5 is used.
496 -spec build_handshake_response(#handshake{}, iodata() | undefined, boolean()) ->
497 binary().
498 build_handshake_response(Handshake, Database, SetFoundRows) ->
499 2 CapabilityFlags = basic_capabilities(Database /= undefined, SetFoundRows),
500 2 verify_server_capabilities(Handshake, CapabilityFlags),
501 2 ClientCapabilities = add_client_capabilities(CapabilityFlags),
502 2 ClientSSLCapabilities = ClientCapabilities bor ?CLIENT_SSL,
503 2 CharacterSet = character_set(Handshake#handshake.server_version),
504 2 <<ClientSSLCapabilities:32/little,
505 ?MAX_BYTES_PER_PACKET:32/little,
506 CharacterSet:8,
507 0:23/unit:8>>.
508
509 %% @doc The response sent by the client to the server after receiving the
510 %% initial handshake from the server
511 -spec build_handshake_response(#handshake{}, iodata(), iodata(),
512 iodata() | undefined, boolean()) ->
513 binary().
514 build_handshake_response(Handshake, Username, Password, Database,
515 SetFoundRows) ->
516 52 CapabilityFlags = basic_capabilities(Database /= undefined, SetFoundRows),
517 52 verify_server_capabilities(Handshake, CapabilityFlags),
518 %% Add some extra capability flags only for signalling to the server what
519 %% the client wants to do. The server doesn't say it handles them although
520 %% it does. (http://bugs.mysql.com/bug.php?id=42268)
521 52 ClientCapabilityFlags = add_client_capabilities(CapabilityFlags),
522 52 AuthPluginName = Handshake#handshake.auth_plugin_name,
523 52 AuthPluginData = Handshake#handshake.auth_plugin_data,
524 52 Hash = hash_password(AuthPluginName, Password, AuthPluginData),
525 52 HashLength = size(Hash),
526 52 CharacterSet = character_set(Handshake#handshake.server_version),
527 52 UsernameUtf8 = unicode:characters_to_binary(Username),
528 52 DbBin = case Database of
529 50 undefined -> <<>>;
530 2 _ -> <<(iolist_to_binary(Database))/binary, 0>>
531 end,
532 52 <<ClientCapabilityFlags:32/little,
533 ?MAX_BYTES_PER_PACKET:32/little,
534 CharacterSet:8,
535 0:23/unit:8, %% reserved
536 UsernameUtf8/binary,
537 0, %% NUL-terminator for the username
538 HashLength,
539 Hash/binary,
540 DbBin/binary,
541 AuthPluginName/binary,
542 0 %% NUL-terminator for the auth_plugin_name
543 >>.
544
545 -spec verify_server_capabilities(Handshake :: #handshake{},
546 CapabilityFlags :: integer()) ->
547 true | no_return().
548 verify_server_capabilities(Handshake, CapabilityFlags) ->
549 %% We require these capabilities. Make sure the server handles them.
550 54 Handshake#handshake.capabilities band CapabilityFlags == CapabilityFlags
551
:-(
orelse error(old_server_version).
552
553 -spec basic_capabilities(ConnectWithDB :: boolean(),
554 SetFoundRows :: boolean()) -> integer().
555 basic_capabilities(ConnectWithDB, SetFoundRows) ->
556 54 CapabilityFlags0 = ?CLIENT_PROTOCOL_41 bor
557 ?CLIENT_TRANSACTIONS bor
558 ?CLIENT_SECURE_CONNECTION,
559 54 CapabilityFlags1 = case ConnectWithDB of
560 2 true -> CapabilityFlags0 bor ?CLIENT_CONNECT_WITH_DB;
561 52 _ -> CapabilityFlags0
562 end,
563 54 case SetFoundRows of
564 1 true -> CapabilityFlags1 bor ?CLIENT_FOUND_ROWS;
565 53 _ -> CapabilityFlags1
566 end.
567
568 -spec add_client_capabilities(Caps :: integer()) -> integer().
569 add_client_capabilities(Caps) ->
570 Caps bor
571 ?CLIENT_MULTI_STATEMENTS bor
572 ?CLIENT_MULTI_RESULTS bor
573 ?CLIENT_PS_MULTI_RESULTS bor
574 ?CLIENT_PLUGIN_AUTH bor
575 54 ?CLIENT_LONG_PASSWORD bor
576 ?CLIENT_LOCAL_FILES.
577
578 -spec character_set([integer()]) -> integer().
579 character_set(ServerVersion) when ServerVersion >= [5, 5, 3] ->
580 %% https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-3.html
581 63 ?UTF8MB4;
582
583 character_set(_ServerVersion) ->
584
:-(
?UTF8MB3.
585
586 %% @doc Handles the second packet from the server, when we have replied to the
587 %% initial handshake. Returns an error if the server returns an error. Raises
588 %% an error if unimplemented features are required.
589 -spec parse_handshake_confirm(binary()) ->
590 #ok{} | #auth_method_switch{} | #error{} | auth_more_data().
591 parse_handshake_confirm(Packet = ?ok_pattern) ->
592 %% Connection complete.
593 57 parse_ok_packet(Packet);
594 parse_handshake_confirm(Packet = ?error_pattern) ->
595 %% Access denied, insufficient client capabilities, etc.
596 3 parse_error_packet(Packet);
597 parse_handshake_confirm(<<?EOF>>) ->
598 %% "Old Authentication Method Switch Request Packet consisting of a
599 %% single 0xfe byte. It is sent by server to request client to
600 %% switch to Old Password Authentication if CLIENT_PLUGIN_AUTH
601 %% capability is not supported (by either the client or the server)"
602
:-(
error(old_auth);
603 parse_handshake_confirm(<<?EOF, AuthMethodSwitch/binary>>) ->
604 %% "Authentication Method Switch Request Packet. If both server and
605 %% client support CLIENT_PLUGIN_AUTH capability, server can send
606 %% this packet to ask client to use another authentication method."
607 9 parse_auth_method_switch(AuthMethodSwitch);
608 parse_handshake_confirm(<<?MORE_DATA, MoreData/binary>>) ->
609 %% More Data Packet consisting of a 0x01 byte and a payload. This
610 %% kind of packet may be used in the authentication process to
611 %% provide more data to the client. It is usually followed by
612 %% either an OK Packet, an Error Packet, or another More Data
613 %% packet.
614 63 parse_auth_more_data(MoreData).
615
616 %% -- both text and binary protocol --
617
618 %% @doc Fetches one or more results and and parses the result set(s) using
619 %% either the text format (for plain queries) or the binary format (for
620 %% prepared statements).
621 -spec fetch_response(module(), term(), timeout(), text | binary, [binary()],
622 decode_decimal(), query_filtermap(), list()) ->
623 {ok, [#ok{} | #resultset{} | #error{}]} | {error, term()}.
624 fetch_response(SockModule, Socket, Timeout, Proto, AllowedPaths, DecodeDecimal, FilterMap, Acc) ->
625 805 case recv_packet(SockModule, Socket, Timeout, any) of
626 {ok, ?local_infile_pattern = Packet, SeqNum2} ->
627 4 Filename = parse_local_infile_packet(Packet),
628 4 Acc1 = case send_file(SockModule, Socket, Filename, AllowedPaths, SeqNum2) of
629 {ok, _SeqNum3} ->
630 2 Acc;
631 {{error, not_allowed}, _SeqNum3} ->
632 1 ErrorMsg = <<"The server requested a file not permitted by the client: ",
633 Filename/binary>>,
634 1 [#error{code = -1, msg = ErrorMsg}|Acc];
635 {{error, FileError}, _SeqNum3} ->
636 1 FileErrorMsg = list_to_binary(file:format_error(FileError)),
637 1 ErrorMsg = <<"The server requested a file which could not be opened "
638 "by the client: ", Filename/binary,
639 " (", FileErrorMsg/binary, ")">>,
640 1 [#error{code = -2, msg = ErrorMsg}|Acc]
641 end,
642 4 fetch_response(SockModule, Socket, Timeout, Proto, AllowedPaths,
643 DecodeDecimal, FilterMap, Acc1);
644 {ok, Packet, SeqNum2} ->
645 798 Result = case Packet of
646 ?ok_pattern ->
647 482 parse_ok_packet(Packet);
648 ?error_pattern ->
649 9 parse_error_packet(Packet);
650 ResultPacket ->
651 %% The first packet in a resultset is only the column count.
652 307 {ColCount, <<>>} = lenenc_int(ResultPacket),
653 307 fetch_resultset(SockModule, Socket, ColCount, Proto,
654 DecodeDecimal, FilterMap, SeqNum2)
655 end,
656 798 Acc1 = [Result | Acc],
657 798 case more_results_exists(Result) of
658 true ->
659 16 fetch_response(SockModule, Socket, Timeout, Proto,
660 AllowedPaths, DecodeDecimal, FilterMap, Acc1);
661 false ->
662 782 {ok, lists:reverse(Acc1)}
663 end;
664 {error, Reason} ->
665 3 {error, Reason}
666 end.
667
668 %% @doc Fetches a result set.
669 -spec fetch_resultset(module(), term(), integer(), text | binary,
670 decode_decimal(), query_filtermap(), integer()) ->
671 #resultset{} | #error{}.
672 fetch_resultset(SockModule, Socket, FieldCount, Proto, DecodeDecimal, FilterMap, SeqNum0) ->
673 307 {ok, ColDefs0, SeqNum1} = fetch_column_definitions(SockModule, Socket,
674 SeqNum0, FieldCount, []),
675 307 {ok, DelimPacket, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
676 307 #eof{} = parse_eof_packet(DelimPacket),
677 307 ColDefs1 = lists:map(fun(ColDef) ->
678 364 parse_column_definition(ColDef, DecodeDecimal)
679 end, ColDefs0),
680 307 case fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs1, Proto,
681 FilterMap, SeqNum2, []) of
682 {ok, Rows, _SeqNum3, #eof{status = S, warning_count = W}} ->
683 306 #resultset{cols = ColDefs1, rows = Rows, status = S,
684 warning_count = W};
685 #error{} = E ->
686 1 E
687 end.
688
689 %% @doc Fetches the rows for a result set and decodes them using either the text
690 %% format (for plain queries) or binary format (for prepared statements).
691 -spec fetch_resultset_rows(module(), term(), integer(), [#col{}], text | binary,
692 query_filtermap(), integer(), [[term()]]) ->
693 {ok, [[term()]], integer(), #eof{}} | #error{}.
694 fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs, Proto,
695 FilterMap, SeqNum0, Acc) ->
696 625 {ok, Packet, SeqNum1} = recv_packet(SockModule, Socket, SeqNum0),
697 625 case Packet of
698 ?error_pattern ->
699 1 parse_error_packet(Packet);
700 ?eof_pattern ->
701 306 Eof = parse_eof_packet(Packet),
702 306 {ok, lists:reverse(Acc), SeqNum1, Eof};
703 RowPacket ->
704 318 Row0 = decode_row(FieldCount, ColDefs, RowPacket, Proto),
705 318 Acc1 = case filtermap_resultset_row(FilterMap, ColDefs, Row0) of
706 false ->
707 6 Acc;
708 true ->
709 306 [Row0|Acc];
710 {true, Row1} ->
711 6 [Row1|Acc]
712 end,
713 318 fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs,
714 Proto, FilterMap, SeqNum1, Acc1)
715 end.
716
717 -spec filtermap_resultset_row(query_filtermap(), [#col{}], [term()]) ->
718 boolean() | {true, term()}.
719 filtermap_resultset_row(no_filtermap_fun, _, _) ->
720 300 true;
721 filtermap_resultset_row(Fun, _, Row) when is_function(Fun, 1) ->
722 9 Fun(Row);
723 filtermap_resultset_row(Fun, ColDefs, Row) when is_function(Fun, 2) ->
724 9 Fun([Col#col.name || Col <- ColDefs], Row).
725
726 more_results_exists(#ok{status = S}) ->
727 482 S band ?SERVER_MORE_RESULTS_EXISTS /= 0;
728 more_results_exists(#error{}) ->
729 10 false; %% No status bits for error
730 more_results_exists(#resultset{status = S}) ->
731 306 S band ?SERVER_MORE_RESULTS_EXISTS /= 0.
732
733 %% @doc Receives NumLeft column definition packets. They are not parsed.
734 %% @see parse_column_definition/1
735 -spec fetch_column_definitions(module(), term(), SeqNum :: integer(),
736 NumLeft :: integer(), Acc :: [binary()]) ->
737 {ok, ColDefPackets :: [binary()], NextSeqNum :: integer()}.
738 fetch_column_definitions(SockModule, Socket, SeqNum, NumLeft, Acc)
739 when NumLeft > 0 ->
740 521 {ok, Packet, SeqNum1} = recv_packet(SockModule, Socket, SeqNum),
741 521 fetch_column_definitions(SockModule, Socket, SeqNum1, NumLeft - 1,
742 [Packet | Acc]);
743 fetch_column_definitions(_SockModule, _Socket, SeqNum, 0, Acc) ->
744 437 {ok, lists:reverse(Acc), SeqNum}.
745
746 %% Parses a packet containing a column definition (part of a result set)
747 parse_column_definition(Data, DecodeDecimal) ->
748 364 {<<"def">>, Rest1} = lenenc_str(Data), %% catalog (always "def")
749 364 {_Schema, Rest2} = lenenc_str(Rest1), %% schema-name
750 364 {_Table, Rest3} = lenenc_str(Rest2), %% virtual table-name
751 364 {_OrgTable, Rest4} = lenenc_str(Rest3), %% physical table-name
752 364 {Name, Rest5} = lenenc_str(Rest4), %% virtual column name
753 364 {_OrgName, Rest6} = lenenc_str(Rest5), %% physical column name
754 364 {16#0c, Rest7} = lenenc_int(Rest6), %% length of the following fields
755 %% (always 0x0c)
756 364 <<Charset:16/little, %% column character set
757 Length:32/little, %% maximum length of the field
758 Type:8, %% type of the column as defined in Column Type
759 Flags:16/little, %% flags
760 Decimals:8, %% max shown decimal digits:
761 0, %% "filler" %% - 0x00 for integers and static strings
762 0, %% - 0x1f for dynamic strings, double, float
763 Rest8/binary>> = Rest7, %% - 0x00 to 0x51 for decimals
764 %% Here, if command was COM_FIELD_LIST {
765 %% default values: lenenc_str
766 %% }
767 364 <<>> = Rest8,
768 364 #col{name = Name, type = Type, charset = Charset, length = Length,
769 decimals = Decimals, flags = Flags, decode_decimal = DecodeDecimal}.
770
771 %% @doc Decodes a row using either the text or binary format.
772 -spec decode_row(integer(), [#col{}], binary(), text | binary) -> [term()].
773 decode_row(FieldCount, ColDefs, RowPacket, text) ->
774 189 decode_text_row(FieldCount, ColDefs, RowPacket);
775 decode_row(FieldCount, ColDefs, RowPacket, binary) ->
776 129 decode_binary_row(FieldCount, ColDefs, RowPacket).
777
778 %% -- text protocol --
779
780 -spec decode_text_row(NumColumns :: integer(),
781 ColumnDefinitions :: [#col{}],
782 Data :: binary()) -> [term()].
783 decode_text_row(_NumColumns, ColumnDefs, Data) ->
784 189 decode_text_row_acc(ColumnDefs, Data, []).
785
786 %% parses Data using ColDefs and builds the values Acc.
787 decode_text_row_acc([ColDef | ColDefs], Data, Acc) ->
788 230 case Data of
789 <<16#fb, Rest/binary>> ->
790 %% NULL
791 2 decode_text_row_acc(ColDefs, Rest, [null | Acc]);
792 _ ->
793 %% Every thing except NULL
794 228 {Text, Rest} = lenenc_str(Data),
795 228 Term = decode_text(ColDef, Text),
796 228 decode_text_row_acc(ColDefs, Rest, [Term | Acc])
797 end;
798 decode_text_row_acc([], <<>>, Acc) ->
799 189 lists:reverse(Acc).
800
801 %% @doc When receiving data in the text protocol, we get everything as binaries
802 %% (except NULL). This function is used to parse these string values.
803 decode_text(#col{type = T}, Text)
804 when T == ?TYPE_TINY; T == ?TYPE_SHORT; T == ?TYPE_LONG; T == ?TYPE_LONGLONG;
805 T == ?TYPE_INT24; T == ?TYPE_YEAR ->
806 97 binary_to_integer(Text);
807 decode_text(#col{type = T}, Text)
808 when T == ?TYPE_STRING; T == ?TYPE_VARCHAR; T == ?TYPE_VAR_STRING;
809 T == ?TYPE_ENUM; T == ?TYPE_SET; T == ?TYPE_LONG_BLOB;
810 T == ?TYPE_MEDIUM_BLOB; T == ?TYPE_BLOB; T == ?TYPE_TINY_BLOB;
811 T == ?TYPE_GEOMETRY; T == ?TYPE_JSON ->
812 %% As of MySQL 5.6.21 we receive SET and ENUM values as STRING, i.e. we
813 %% cannot convert them to atom() or sets:set(), etc.
814 61 Text;
815 decode_text(#col{type = ?TYPE_BIT, length = Length}, Text) ->
816 %% Convert to <<_:Length/bitstring>>
817 5 decode_bitstring(Text, Length);
818 decode_text(#col{type = T, decimals = S, length = L,
819 decode_decimal = DecodeDecimal}, Text)
820 when T == ?TYPE_DECIMAL; T == ?TYPE_NEWDECIMAL ->
821 %% Length is the max number of symbols incl. dot and minus sign, e.g. the
822 %% number of digits plus 2.
823 36 decode_decimal(DecodeDecimal, Text, L - 2, S);
824 decode_text(#col{type = ?TYPE_DATE},
825 <<Y:4/binary, "-", M:2/binary, "-", D:2/binary>>) ->
826 6 {binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)};
827 decode_text(#col{type = ?TYPE_TIME}, Text) ->
828 23 {match, [Sign, Hbin, Mbin, Sbin, Frac]} =
829 re:run(Text,
830 <<"^(-?)(\\d+):(\\d+):(\\d+)(\\.?\\d*)$">>,
831 [{capture, all_but_first, binary}]),
832 23 H = binary_to_integer(Hbin),
833 23 M = binary_to_integer(Mbin),
834 23 S = binary_to_integer(Sbin),
835 23 IsNeg = Sign == <<"-">>,
836 23 Fraction = case Frac of
837 19 <<>> -> 0;
838 2 _ when not IsNeg -> binary_to_float(<<"0", Frac/binary>>);
839 2 _ when IsNeg -> 1 - binary_to_float(<<"0", Frac/binary>>)
840 end,
841 23 Sec1 = H * 3600 + M * 60 + S,
842 23 Sec2 = if IsNeg -> -Sec1; true -> Sec1 end,
843 23 Sec3 = if IsNeg and (Fraction /= 0) -> Sec2 - 1;
844 21 true -> Sec2
845 end,
846 23 {Days, {Hours, Minutes, Seconds}} = calendar:seconds_to_daystime(Sec3),
847 23 {Days, {Hours, Minutes, Seconds + Fraction}};
848 decode_text(#col{type = T},
849 <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
850 H:2/binary, ":", Mi:2/binary, ":", S:2/binary>>)
851 when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
852 %% Without fractions.
853 9 {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
854 {binary_to_integer(H), binary_to_integer(Mi), binary_to_integer(S)}};
855 decode_text(#col{type = T},
856 <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
857 H:2/binary, ":", Mi:2/binary, ":", FloatS/binary>>)
858 when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
859 %% With fractions.
860 2 {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
861 {binary_to_integer(H), binary_to_integer(Mi), binary_to_float(FloatS)}};
862 decode_text(#col{type = T}, Text) when T == ?TYPE_FLOAT;
863 T == ?TYPE_DOUBLE ->
864 28 try binary_to_float(Text)
865 catch error:badarg ->
866 10 try binary_to_integer(Text) of
867 9 Int -> float(Int)
868 catch error:badarg ->
869 %% It is something like "4e75" that must be turned into "4.0e75"
870 1 binary_to_float(binary:replace(Text, <<"e">>, <<".0e">>))
871 end
872 end.
873
874 %% -- binary protocol --
875
876 %% @doc If NumColumns is non-zero, fetches this number of column definitions
877 %% and an EOF packet. Used by prepare/3.
878 fetch_column_definitions_if_any(0, _SockModule, _Socket, SeqNum) ->
879 114 {[], SeqNum};
880 fetch_column_definitions_if_any(N, SockModule, Socket, SeqNum) ->
881 130 {ok, Defs, SeqNum1} = fetch_column_definitions(SockModule, Socket, SeqNum,
882 N, []),
883 130 {ok, ?eof_pattern, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
884 130 {Defs, SeqNum2}.
885
886 %% @doc Decodes a packet representing a row in a binary result set.
887 %% It consists of a 0 byte, then a null bitmap, then the values.
888 %% Returns a list of length NumColumns with terms of appropriate types for each
889 %% MySQL type in ColumnTypes.
890 -spec decode_binary_row(NumColumns :: integer(),
891 ColumnDefs :: [#col{}],
892 Data :: binary()) -> [term()].
893 decode_binary_row(NumColumns, ColumnDefs, <<0, Data/binary>>) ->
894 129 {NullBitMap, Rest} = null_bitmap_decode(NumColumns, Data, 2),
895 129 decode_binary_row_acc(ColumnDefs, NullBitMap, Rest, []).
896
897 %% @doc Accumulating helper for decode_binary_row/3.
898 decode_binary_row_acc([_|ColDefs], <<1:1, NullBitMap/bitstring>>, Data, Acc) ->
899 %% NULL
900 1 decode_binary_row_acc(ColDefs, NullBitMap, Data, [null | Acc]);
901 decode_binary_row_acc([ColDef | ColDefs], <<0:1, NullBitMap/bitstring>>, Data,
902 Acc) ->
903 %% Not NULL
904 151 {Term, Rest} = decode_binary(ColDef, Data),
905 151 decode_binary_row_acc(ColDefs, NullBitMap, Rest, [Term | Acc]);
906 decode_binary_row_acc([], _NullBitMap, <<>>, Acc) ->
907 129 lists:reverse(Acc).
908
909 %% @doc Decodes a null bitmap as stored by MySQL and returns it in a strait
910 %% bitstring counting bits from left to right in a tuple with remaining data.
911 %%
912 %% In the MySQL null bitmap the bits are stored counting bytes from the left and
913 %% bits within each byte from the right. (Sort of little endian.)
914 -spec null_bitmap_decode(NumColumns :: integer(), Data :: binary(),
915 BitOffset :: integer()) ->
916 {NullBitstring :: bitstring(), Rest :: binary()}.
917 null_bitmap_decode(NumColumns, Data, BitOffset) ->
918 %% Binary shift right by 3 is equivallent to integer division by 8.
919 130 BitMapLength = (NumColumns + BitOffset + 7) bsr 3,
920 130 <<NullBitstring0:BitMapLength/binary, Rest/binary>> = Data,
921 130 <<_:BitOffset, NullBitstring:NumColumns/bitstring, _/bitstring>> =
922 132 << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= NullBitstring0 >>,
923 130 {NullBitstring, Rest}.
924
925 %% @doc The reverse of null_bitmap_decode/3. The number of columns is taken to
926 %% be the number of bits in NullBitstring. Returns the MySQL null bitmap as a
927 %% binary (i.e. full bytes). BitOffset is the number of unused bits that should
928 %% be inserted before the other bits.
929 -spec null_bitmap_encode(bitstring(), integer()) -> binary().
930 null_bitmap_encode(NullBitstring, BitOffset) ->
931 106 PayloadLength = bit_size(NullBitstring) + BitOffset,
932 %% Round up to a multiple of 8.
933 106 BitMapLength = (PayloadLength + 7) band bnot 7,
934 106 PadBitsLength = BitMapLength - PayloadLength,
935 106 PaddedBitstring = <<0:BitOffset, NullBitstring/bitstring, 0:PadBitsLength>>,
936 106 << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= PaddedBitstring >>.
937
938 %% Reverses the bits in a byte.
939 reverse_byte(<<A:1, B:1, C:1, D:1, E:1, F:1, G:1, H:1>>) ->
940 240 <<H:1, G:1, F:1, E:1, D:1, C:1, B:1, A:1>>.
941
942 %% @doc Used for executing prepared statements. The bit offset would be 0 in
943 %% this case.
944 -spec build_null_bitmap([any()]) -> binary().
945 build_null_bitmap(Values) ->
946 105 Bits = << <<(case V of null -> 1; _ -> 0 end):1>> || V <- Values >>,
947 105 null_bitmap_encode(Bits, 0).
948
949 %% Decodes a value as received in the 'binary protocol' result set.
950 %%
951 %% The types are type constants for the binary protocol, such as
952 %% ProtocolBinary::MYSQL_TYPE_STRING. In the guide "MySQL Internals" these are
953 %% not listed, but we assume that are the same as for the text protocol.
954 -spec decode_binary(ColDef :: #col{}, Data :: binary()) ->
955 {Term :: term(), Rest :: binary()}.
956 decode_binary(#col{type = T}, Data)
957 when T == ?TYPE_STRING; T == ?TYPE_VARCHAR; T == ?TYPE_VAR_STRING;
958 T == ?TYPE_ENUM; T == ?TYPE_SET; T == ?TYPE_LONG_BLOB;
959 T == ?TYPE_MEDIUM_BLOB; T == ?TYPE_BLOB; T == ?TYPE_TINY_BLOB;
960 T == ?TYPE_GEOMETRY; T == ?TYPE_JSON ->
961 %% As of MySQL 5.6.21 we receive SET and ENUM values as STRING, i.e. we
962 %% cannot convert them to atom() or sets:set(), etc.
963 20 lenenc_str(Data);
964 decode_binary(#col{type = ?TYPE_LONGLONG, flags = F},
965 <<Value:64/signed-little, Rest/binary>>)
966 when F band ?UNSIGNED_FLAG == 0 ->
967 7 {Value, Rest};
968 decode_binary(#col{type = ?TYPE_LONGLONG, flags = F},
969 <<Value:64/unsigned-little, Rest/binary>>)
970 when F band ?UNSIGNED_FLAG /= 0 ->
971 3 {Value, Rest};
972 decode_binary(#col{type = T, flags = F},
973 <<Value:32/signed-little, Rest/binary>>)
974 when (T == ?TYPE_LONG orelse T == ?TYPE_INT24) andalso
975 F band ?UNSIGNED_FLAG == 0 ->
976 25 {Value, Rest};
977 decode_binary(#col{type = T, flags = F},
978 <<Value:32/unsigned-little, Rest/binary>>)
979 when (T == ?TYPE_LONG orelse T == ?TYPE_INT24) andalso
980 F band ?UNSIGNED_FLAG /= 0 ->
981 2 {Value, Rest};
982 decode_binary(#col{type = ?TYPE_SHORT, flags = F},
983 <<Value:16/signed-little, Rest/binary>>)
984 when F band ?UNSIGNED_FLAG == 0 ->
985 4 {Value, Rest};
986 decode_binary(#col{type = T, flags = F},
987 <<Value:16/unsigned-little, Rest/binary>>)
988 when (T == ?TYPE_SHORT orelse T == ?TYPE_YEAR) andalso
989 F band ?UNSIGNED_FLAG /= 0 ->
990 3 {Value, Rest};
991 decode_binary(#col{type = ?TYPE_TINY, flags = F},
992 <<Value:8/unsigned, Rest/binary>>)
993 when F band ?UNSIGNED_FLAG /= 0 ->
994 2 {Value, Rest};
995 decode_binary(#col{type = ?TYPE_TINY, flags = F},
996 <<Value:8/signed, Rest/binary>>)
997 when F band ?UNSIGNED_FLAG == 0 ->
998 4 {Value, Rest};
999 decode_binary(#col{type = T, decimals = S, length = L,
1000 decode_decimal = DecodeDecimal}, Data)
1001 when T == ?TYPE_DECIMAL; T == ?TYPE_NEWDECIMAL ->
1002 %% Length is the max number of symbols incl. dot and minus sign, e.g. the
1003 %% number of digits plus 2.
1004 18 {Binary, Rest} = lenenc_str(Data),
1005 18 {decode_decimal(DecodeDecimal, Binary, L - 2, S), Rest};
1006 decode_binary(#col{type = ?TYPE_DOUBLE},
1007 <<Value:64/float-little, Rest/binary>>) ->
1008 1 {Value, Rest};
1009 decode_binary(#col{type = ?TYPE_FLOAT}, <<0.0:32/float-little, Rest/binary>>) ->
1010 %% TYPE_FLOAT conversation fails on math:log10(0.0)
1011 1 {0.0, Rest};
1012 decode_binary(#col{type = ?TYPE_FLOAT},
1013 <<Value:32/float-little, Rest/binary>>) ->
1014 %% There is a precision loss when storing and fetching a 32-bit float.
1015 %% In the text protocol, it is obviously rounded. Storing 3.14 in a FLOAT
1016 %% column and fetching it using the text protocol, we get "3.14" which we
1017 %% parse to the Erlang double as close as possible to 3.14. Fetching the
1018 %% same value as a binary 32-bit float, we get 3.140000104904175. To achieve
1019 %% the same rounding after receiving it as a 32-bit float, we try to do the
1020 %% same rounding here as MySQL does when sending it over the text protocol.
1021 %%
1022 %% This comment explains the idea:
1023 %%
1024 %% Posted by Geoffrey Downs on March 10 2011 10:26am
1025 %%
1026 %% Following up... I *think* this is correct for the default float
1027 %% columns in mysql:
1028 %%
1029 %% var yourNumber = some floating point value
1030 %% max decimal precision = 10 ^ (-5 + flooring(yourNumber log 10))
1031 %% So:
1032 %% 0 < x < 10 -> max precision is 0.00001
1033 %% 10 <= x < 100 -> max precision is 0.0001
1034 %% 100 <= x < 1000 -> max precision is 0.001
1035 %% etc.
1036 %%
1037 %% (From http://dev.mysql.com/doc/refman/5.7/en/problems-with-float.html
1038 %% fetched 10 Nov 2014)
1039 %%
1040 %% The above is almost correct, except for the example in the interval
1041 %% 0 < x < 1. There are 6 significant digits also for these numbers.
1042 %%
1043 %% Now, instead of P = 0.00001 we want the inverse 100000.0 but if we
1044 %% compute Factor = 1 / P we get a precision loss, so instead we do this:
1045 27 Factor = math:pow(10, flooring(6 - math:log10(abs(Value)))),
1046 27 RoundedValue = round(Value * Factor) / Factor,
1047 27 {RoundedValue, Rest};
1048 decode_binary(#col{type = ?TYPE_BIT, length = Length}, Data) ->
1049 4 {Binary, Rest} = lenenc_str(Data),
1050 %% Convert to <<_:Length/bitstring>>
1051 4 {decode_bitstring(Binary, Length), Rest};
1052 decode_binary(#col{type = ?TYPE_DATE}, Data) ->
1053 %% Coded in the same way as DATETIME and TIMESTAMP below, but returned in
1054 %% a simple triple.
1055 5 case lenenc_int(Data) of
1056 2 {0, Rest} -> {{0, 0, 0}, Rest};
1057 3 {4, <<Y:16/little, M, D, Rest/binary>>} -> {{Y, M, D}, Rest}
1058 end;
1059 decode_binary(#col{type = T}, Data)
1060 when T == ?TYPE_DATETIME; T == ?TYPE_TIMESTAMP ->
1061 %% length (1) -- number of bytes following (valid values: 0, 4, 7, 11)
1062 9 case lenenc_int(Data) of
1063 {0, Rest} ->
1064 2 {{{0, 0, 0}, {0, 0, 0}}, Rest};
1065 {4, <<Y:16/little, M, D, Rest/binary>>} ->
1066 2 {{{Y, M, D}, {0, 0, 0}}, Rest};
1067 {7, <<Y:16/little, M, D, H, Mi, S, Rest/binary>>} ->
1068 3 {{{Y, M, D}, {H, Mi, S}}, Rest};
1069 {11, <<Y:16/little, M, D, H, Mi, S, Micro:32/little, Rest/binary>>} ->
1070 2 {{{Y, M, D}, {H, Mi, S + 0.000001 * Micro}}, Rest}
1071 end;
1072 decode_binary(#col{type = ?TYPE_TIME}, Data) ->
1073 %% length (1) -- number of bytes following (valid values: 0, 8, 12)
1074 %% is_negative (1) -- (1 if minus, 0 for plus)
1075 %% days (4) -- days
1076 %% hours (1) -- hours
1077 %% minutes (1) -- minutes
1078 %% seconds (1) -- seconds
1079 %% micro_seconds (4) -- micro-seconds
1080 21 case lenenc_int(Data) of
1081 {0, Rest} ->
1082 2 {{0, {0, 0, 0}}, Rest};
1083 {8, <<0, D:32/little, H, M, S, Rest/binary>>} ->
1084 5 {{D, {H, M, S}}, Rest};
1085 {12, <<0, D:32/little, H, M, S, Micro:32/little, Rest/binary>>} ->
1086 2 {{D, {H, M, S + 0.000001 * Micro}}, Rest};
1087 {8, <<1, D:32/little, H, M, S, Rest/binary>>} ->
1088 %% Negative time. Example: '-00:00:01' --> {-1,{23,59,59}}
1089 10 Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
1090 %Seconds = D * 86400 + calendar:time_to_seconds({H, M, S}),
1091 10 {calendar:seconds_to_daystime(-Seconds), Rest};
1092 {12, <<1, D:32/little, H, M, S, Micro:32/little, Rest/binary>>}
1093 when Micro > 0 ->
1094 %% Negate and convert to seconds, excl fractions
1095 2 Seconds = -(((D * 24 + H) * 60 + M) * 60 + S),
1096 %Seconds = -D * 86400 - calendar:time_to_seconds({H, M, S}),
1097 %% Subtract 1 second for the fractions
1098 2 {Days, {Hours, Minutes, Sec}} =
1099 calendar:seconds_to_daystime(Seconds - 1),
1100 %% Adding the fractions to Sec again makes it a float
1101 2 {{Days, {Hours, Minutes, Sec + 1 - 0.000001 * Micro}}, Rest}
1102 end.
1103
1104 %% @doc Like trunc/1 but towards negative infinity instead of towards zero.
1105 flooring(Value) ->
1106 27 Trunc = trunc(Value),
1107 27 if
1108 21 Trunc =< Value -> Trunc;
1109 6 Trunc > Value -> Trunc - 1 %% for negative values
1110 end.
1111
1112 %% @doc Encodes a term reprenting av value as a binary for use in the binary
1113 %% protocol. As this is used to encode parameters for prepared statements, the
1114 %% encoding is in its required form, namely `<<Type:8, Sign:8, Value/binary>>'.
1115 -spec encode_param(term()) -> {TypeAndSign :: binary(), Data :: binary()}.
1116 encode_param(null) ->
1117 1 {<<?TYPE_NULL, 0>>, <<>>};
1118 encode_param(Value) when is_binary(Value) ->
1119 19 EncLength = lenenc_int_encode(byte_size(Value)),
1120 19 {<<?TYPE_VAR_STRING, 0>>, <<EncLength/binary, Value/binary>>};
1121 encode_param(Value) when is_list(Value) ->
1122 1 encode_param(unicode:characters_to_binary(Value));
1123 encode_param(Value) when is_integer(Value), Value >= 0 ->
1124 %% We send positive integers with the 'unsigned' flag set.
1125 44 if
1126 Value =< 16#ff ->
1127 35 {<<?TYPE_TINY, 16#80>>, <<Value:8>>};
1128 Value =< 16#ffff ->
1129 3 {<<?TYPE_SHORT, 16#80>>, <<Value:16/little>>};
1130 Value =< 16#ffffffff ->
1131 3 {<<?TYPE_LONG, 16#80>>, <<Value:32/little>>};
1132 Value =< 16#ffffffffffffffff ->
1133 2 {<<?TYPE_LONGLONG, 16#80>>, <<Value:64/little>>};
1134 true ->
1135 %% If larger than a 64-bit int we send it as a string. MySQL does
1136 %% silently cast strings in aithmetic expressions. Also, DECIMALs
1137 %% are always sent as strings.
1138 1 encode_param(integer_to_binary(Value))
1139 end;
1140 encode_param(Value) when is_integer(Value), Value < 0 ->
1141 7 if
1142 Value >= -16#80 ->
1143 2 {<<?TYPE_TINY, 0>>, <<Value:8>>};
1144 Value >= -16#8000 ->
1145 1 {<<?TYPE_SHORT, 0>>, <<Value:16/little>>};
1146 Value >= -16#80000000 ->
1147 2 {<<?TYPE_LONG, 0>>, <<Value:32/little>>};
1148 Value >= -16#8000000000000000 ->
1149 1 {<<?TYPE_LONGLONG, 0>>, <<Value:64/little>>};
1150 true ->
1151 1 encode_param(integer_to_binary(Value))
1152 end;
1153 encode_param(Value) when is_float(Value) ->
1154 33 {<<?TYPE_DOUBLE, 0>>, <<Value:64/float-little>>};
1155 encode_param({decimal, Value}) ->
1156 10 Bin = if is_binary(Value) -> Value;
1157 1 is_list(Value) -> list_to_binary(Value);
1158 1 is_integer(Value) -> integer_to_binary(Value);
1159 2 is_float(Value) -> list_to_binary(io_lib:format("~w", [Value]))
1160 end,
1161 10 EncLength = lenenc_int_encode(byte_size(Bin)),
1162 10 {<<?TYPE_DECIMAL, 0>>, <<EncLength/binary, Bin/binary>>};
1163 encode_param(Value) when is_bitstring(Value) ->
1164 2 Binary = encode_bitstring(Value),
1165 2 EncLength = lenenc_int_encode(byte_size(Binary)),
1166 2 {<<?TYPE_VAR_STRING, 0>>, <<EncLength/binary, Binary/binary>>};
1167 encode_param({Y, M, D}) ->
1168 %% calendar:date()
1169 3 {<<?TYPE_DATE, 0>>, <<4, Y:16/little, M, D>>};
1170 encode_param({{Y, M, D}, {0, 0, 0}}) ->
1171 %% Datetime at midnight
1172 2 {<<?TYPE_DATETIME, 0>>, <<4, Y:16/little, M, D>>};
1173 encode_param({{Y, M, D}, {H, Mi, S}}) when is_integer(S) ->
1174 %% calendar:datetime()
1175 2 {<<?TYPE_DATETIME, 0>>, <<7, Y:16/little, M, D, H, Mi, S>>};
1176 encode_param({{Y, M, D}, {H, Mi, S}}) when is_float(S) ->
1177 %% calendar:datetime() with a float for seconds. This way it looks very
1178 %% similar to a datetime. Microseconds in MySQL timestamps are possible but
1179 %% not very common.
1180 1 Sec = trunc(S),
1181 1 Micro = round(1000000 * (S - Sec)),
1182 1 {<<?TYPE_DATETIME, 0>>, <<11, Y:16/little, M, D, H, Mi, Sec,
1183 Micro:32/little>>};
1184 encode_param({D, {H, M, S}}) when is_integer(S), D >= 0 ->
1185 %% calendar:seconds_to_daystime()
1186 4 {<<?TYPE_TIME, 0>>, <<8, 0, D:32/little, H, M, S>>};
1187 encode_param({D, {H, M, S}}) when is_integer(S), D < 0 ->
1188 %% Convert to seconds, negate and convert back to daystime form.
1189 %% Then set the minus flag.
1190 6 Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
1191 6 {D1, {H1, M1, S1}} = calendar:seconds_to_daystime(-Seconds),
1192 6 {<<?TYPE_TIME, 0>>, <<8, 1, D1:32/little, H1, M1, S1>>};
1193 encode_param({D, {H, M, S}}) when is_float(S), D >= 0 ->
1194 1 S1 = trunc(S),
1195 1 Micro = round(1000000 * (S - S1)),
1196 1 {<<?TYPE_TIME, 0>>, <<12, 0, D:32/little, H, M, S1, Micro:32/little>>};
1197 encode_param({D, {H, M, S}}) when is_float(S), S > 0.0, D < 0 ->
1198 1 IntS = trunc(S),
1199 1 Micro = round(1000000 * (1 - S + IntS)),
1200 1 Seconds = (D * 24 + H) * 3600 + M * 60 + IntS + 1,
1201 1 {D1, {M1, H1, S1}} = calendar:seconds_to_daystime(-Seconds),
1202 1 {<<?TYPE_TIME, 0>>, <<12, 1, D1:32/little, H1, M1, S1, Micro:32/little>>};
1203 encode_param({D, {H, M, V}}) when abs(V) =:= +0.0 ->
1204 1 encode_param({D, {H, M, 0}}).
1205
1206 %% @doc Checks if the given Parameters can be encoded for use in the
1207 %% binary protocol. Returns `true' if all of the parameters can be
1208 %% encoded, `false' if any of them cannot be encoded.
1209 -spec valid_params([term()]) -> boolean().
1210 valid_params(Values) when is_list(Values) ->
1211 230 lists:all(fun is_valid_param/1, Values).
1212
1213 %% @doc Checks if the given parameter can be encoded for use in the
1214 %% binary protocol.
1215 -spec is_valid_param(term()) -> boolean().
1216 is_valid_param(null) ->
1217 4 true;
1218 is_valid_param(Value) when is_list(Value) ->
1219 12 try
1220 12 unicode:characters_to_binary(Value)
1221 of
1222 Value1 when is_binary(Value1) ->
1223 10 true;
1224 _ErrorOrIncomplete ->
1225 1 false
1226 catch
1227 error:badarg ->
1228 1 false
1229 end;
1230 is_valid_param(Value) when is_number(Value) ->
1231 96 true;
1232 is_valid_param({decimal, Value}) when is_binary(Value); is_list(Value);
1233 is_float(Value); is_integer(Value) ->
1234 5 true;
1235 is_valid_param(Value) when is_bitstring(Value) ->
1236 27 true;
1237 is_valid_param({Y, M, D}) ->
1238 9 is_integer(Y) andalso is_integer(M) andalso is_integer(D);
1239 is_valid_param({{Y, M, D}, {H, Mi, S}}) ->
1240 17 is_integer(Y) andalso is_integer(M) andalso is_integer(D) andalso
1241 14 is_integer(H) andalso is_integer(Mi) andalso is_number(S);
1242 is_valid_param({D, {H, M, S}}) ->
1243 22 is_integer(D) andalso
1244 21 is_integer(H) andalso is_integer(M) andalso is_number(S);
1245 is_valid_param(_) ->
1246 9 false.
1247
1248 %% -- Value representation in both the text and binary protocols --
1249
1250 %% @doc Convert to `<<_:Length/bitstring>>'
1251 decode_bitstring(Binary, Length) ->
1252 9 PaddingLength = bit_size(Binary) - Length,
1253 9 <<_:PaddingLength/bitstring, Bitstring:Length/bitstring>> = Binary,
1254 9 Bitstring.
1255
1256 encode_bitstring(Bitstring) ->
1257 2 Size = bit_size(Bitstring),
1258 2 PaddingSize = byte_size(Bitstring) * 8 - Size,
1259 2 <<0:PaddingSize, Bitstring:Size/bitstring>>.
1260
1261 decode_decimal(Decode, Bin, _P, 0) when Decode =:= number;
1262 Decode =:= auto ->
1263 8 binary_to_integer(Bin);
1264 decode_decimal(Decode, Bin, _P, 0) when Decode =:= float ->
1265 2 float(binary_to_integer(Bin));
1266 decode_decimal(Decode, Bin, P, S) when Decode =:= auto, P =< 15, S > 0;
1267 Decode =:= number;
1268 Decode =:= float ->
1269 24 binary_to_float(Bin);
1270 decode_decimal(_Decode, Bin, _P, _S) ->
1271 20 Bin.
1272
1273 %% -- Protocol basics: packets --
1274
1275 %% @doc Wraps Data in packet headers, sends it by calling SockModule:send/2 with
1276 %% Socket and returns {ok, SeqNum1} where SeqNum1 is the next sequence number.
1277 -spec send_packet(module(), term(), Data :: binary(), SeqNum :: integer()) ->
1278 {ok, NextSeqNum :: integer()}.
1279 send_packet(SockModule, Socket, Data, SeqNum) ->
1280 1130 {WithHeaders, SeqNum1} = add_packet_headers(Data, SeqNum),
1281 1130 ok = SockModule:send(Socket, WithHeaders),
1282 1129 {ok, SeqNum1}.
1283
1284 %% @see recv_packet/4
1285 recv_packet(SockModule, Socket, SeqNum) ->
1286 1941 recv_packet(SockModule, Socket, infinity, SeqNum).
1287
1288 %% @doc Receives data by calling SockModule:recv/2 and removes the packet
1289 %% headers. Returns the packet contents and the next packet sequence number.
1290 -spec recv_packet(module(), term(), timeout(), integer() | any) ->
1291 {ok, Data :: binary(), NextSeqNum :: integer()} | {error, term()}.
1292 recv_packet(SockModule, Socket, Timeout, SeqNum) ->
1293 2746 recv_packet(SockModule, Socket, Timeout, SeqNum, <<>>).
1294
1295 %% @doc Accumulating helper for recv_packet/4
1296 -spec recv_packet(module(), term(), timeout(), integer() | any, binary()) ->
1297 {ok, Data :: binary(), NextSeqNum :: integer()} | {error, term()}.
1298 recv_packet(SockModule, Socket, Timeout, ExpectSeqNum, Acc) ->
1299 2746 case SockModule:recv(Socket, 4, Timeout) of
1300 {ok, Header} ->
1301 2702 {Size, SeqNum, More} = parse_packet_header(Header),
1302 2702 true = SeqNum == ExpectSeqNum orelse ExpectSeqNum == any,
1303 2702 {ok, Body} = SockModule:recv(Socket, Size),
1304 2702 Acc1 = <<Acc/binary, Body/binary>>,
1305 2702 NextSeqNum = (SeqNum + 1) band 16#ff,
1306 2702 case More of
1307 2702 false -> {ok, Acc1, NextSeqNum};
1308
:-(
true -> recv_packet(SockModule, Socket, Timeout, NextSeqNum,
1309 Acc1)
1310 end;
1311 {error, Reason} ->
1312 44 {error, Reason}
1313 end.
1314
1315 -spec send_file(module(), term(), Filename :: binary(), AllowedPaths :: [binary()],
1316 SeqNum :: integer()) ->
1317 {ok | {error, Reason}, NextSeqNum :: integer()}
1318 when Reason :: not_allowed
1319 | file:posix()
1320 | badarg
1321 | system_limit.
1322 send_file(SockModule, Socket, Filename, AllowedPaths, SeqNum0) ->
1323 4 {Result, SeqNum1} = case allowed_path(Filename, AllowedPaths) andalso
1324 3 file:open(Filename, [read, raw, binary]) of
1325 false ->
1326 1 {{error, not_allowed}, SeqNum0};
1327 {ok, Handle} ->
1328 2 {ok, SeqNum2} = send_file_chunk(SockModule, Socket, Handle, SeqNum0),
1329 2 ok = file:close(Handle),
1330 2 {ok, SeqNum2};
1331 {error, _Reason} = E ->
1332 1 {E, SeqNum0}
1333 end,
1334 4 {ok, SeqNum3} = send_packet(SockModule, Socket, <<>>, SeqNum1),
1335 4 {Result, SeqNum3}.
1336
1337 -spec allowed_path(binary(), [binary()]) -> boolean().
1338 allowed_path(Path, AllowedPaths) ->
1339 15 valid_path(Path) andalso
1340 12 binary:last(Path) =/= $/ andalso
1341 11 lists:any(
1342 fun
1343 (AllowedPath) when Path =:= AllowedPath ->
1344 2 true;
1345 (AllowedPath) ->
1346 22 Size = byte_size(AllowedPath),
1347 22 HasSlash = binary:last(AllowedPath) =:= $/,
1348 22 case Path of
1349 4 <<AllowedPath:Size/binary, _/binary>> when HasSlash -> true;
1350 1 <<AllowedPath:Size/binary, $/, _/binary>> -> true;
1351 17 _ -> false
1352 end
1353 end,
1354 AllowedPaths
1355 ).
1356
1357 %% @doc Checks if the argument is a valid path.
1358 %%
1359 %% Returns `true' if the argument is an absolute path that does not contain
1360 %% any relative components like `..' or `.', otherwise `false'.
1361 -spec valid_path(term()) -> boolean().
1362 valid_path(Path) when is_binary(Path), byte_size(Path) > 0 ->
1363 26 case filename:pathtype(Path) of
1364 absolute ->
1365 22 valid_abspath(Path);
1366 volumerelative ->
1367
:-(
case Path of
1368 <<$/, _/binary>> ->
1369
:-(
false;
1370 _ ->
1371
:-(
valid_abspath(Path)
1372 end;
1373 relative ->
1374 4 false
1375 end;
1376 valid_path(_Path) ->
1377 2 false.
1378
1379 -spec valid_abspath(<<_:8, _:_*8>>) -> boolean().
1380 valid_abspath(Path) ->
1381 22 lists:all(
1382 fun
1383
:-(
(<<".">>) -> false;
1384 5 (<<"..">>) -> false;
1385 90 (_) -> true
1386 end,
1387 filename:split(Path)
1388 ).
1389
1390 -spec send_file_chunk(module(), term(), Handle :: file:io_device(), SeqNum :: integer()) ->
1391 {ok, NextSeqNum :: integer()}.
1392 send_file_chunk(SockModule, Socket, Handle, SeqNum0) ->
1393 4 case file:read(Handle, 16#ffffff) of
1394 eof ->
1395 2 {ok, SeqNum0};
1396 {ok, <<>>} ->
1397
:-(
send_file_chunk(SockModule, Socket, Handle, SeqNum0);
1398 {ok, Data} ->
1399 2 {ok, SeqNum1} = send_packet(SockModule, Socket, Data, SeqNum0),
1400 2 send_file_chunk(SockModule, Socket, Handle, SeqNum1)
1401 end.
1402
1403 %% @doc Parses a packet header (32 bits) and returns a tuple.
1404 %%
1405 %% The client should first read a header and parse it. Then read PacketLength
1406 %% bytes. If there are more packets, read another header and read a new packet
1407 %% length of payload until there are no more packets. The seq num should
1408 %% increment from 0 and may wrap around at 255 back to 0.
1409 %%
1410 %% When all packets are read and the payload of all packets are concatenated, it
1411 %% can be parsed using parse_response/1, etc. depending on what type of response
1412 %% is expected.
1413 -spec parse_packet_header(PackerHeader :: binary()) ->
1414 {PacketLength :: integer(),
1415 SeqNum :: integer(),
1416 MorePacketsExist :: boolean()}.
1417 parse_packet_header(<<PacketLength:24/little-integer, SeqNum:8/integer>>) ->
1418 2703 {PacketLength, SeqNum, PacketLength == 16#ffffff}.
1419
1420 %% @doc Splits a packet body into chunks and wraps them in headers. The
1421 %% resulting list is ready to be sent to the socket. The result is built as a
1422 %% list to avoid copying large binaries.
1423 -spec add_packet_headers(Data :: binary(), SeqNum :: integer()) ->
1424 {PacketsWithHeaders :: iodata(), NextSeqNum :: integer()}.
1425 add_packet_headers(<<Payload:16#ffffff/binary, Rest/binary>>, SeqNum) ->
1426 4 SeqNum1 = (SeqNum + 1) band 16#ff,
1427 4 {Packets, NextSeqNum} = add_packet_headers(Rest, SeqNum1),
1428 4 Header = <<16#ffffff:24/little, SeqNum:8>>,
1429 4 {[Header, Payload | Packets], NextSeqNum};
1430 add_packet_headers(Bin, SeqNum) when byte_size(Bin) < 16#ffffff ->
1431 1134 NextSeqNum = (SeqNum + 1) band 16#ff,
1432 1134 Header = <<(byte_size(Bin)):24/little, SeqNum:8>>,
1433 1134 {[Header, Bin], NextSeqNum}.
1434
1435 -spec parse_ok_packet(binary()) -> #ok{}.
1436 parse_ok_packet(<<?OK:8, Rest/binary>>) ->
1437 544 {AffectedRows, Rest1} = lenenc_int(Rest),
1438 544 {InsertId, Rest2} = lenenc_int(Rest1),
1439 544 <<StatusFlags:16/little, WarningCount:16/little, Msg/binary>> = Rest2,
1440 %% We have CLIENT_PROTOCOL_41 but not CLIENT_SESSION_TRACK enabled. The
1441 %% protocol is conditional. This is from the protocol documentation:
1442 %%
1443 %% if capabilities & CLIENT_PROTOCOL_41 {
1444 %% int<2> status_flags
1445 %% int<2> warning_count
1446 %% } elseif capabilities & CLIENT_TRANSACTIONS {
1447 %% int<2> status_flags
1448 %% }
1449 %% if capabilities & CLIENT_SESSION_TRACK {
1450 %% string<lenenc> info
1451 %% if status_flags & SERVER_SESSION_STATE_CHANGED {
1452 %% string<lenenc> session_state_changes
1453 %% }
1454 %% } else {
1455 %% string<EOF> info
1456 %% }
1457 544 #ok{affected_rows = AffectedRows,
1458 insert_id = InsertId,
1459 status = StatusFlags,
1460 warning_count = WarningCount,
1461 msg = Msg}.
1462
1463 -spec parse_error_packet(binary()) -> #error{}.
1464 parse_error_packet(<<?ERROR:8, ErrNo:16/little, "#", SQLState:5/binary-unit:8,
1465 Msg/binary>>) ->
1466 %% Error, 4.1 protocol.
1467 %% (Older protocol: <<?ERROR:8, ErrNo:16/little, Msg/binary>>)
1468 19 #error{code = ErrNo, state = SQLState, msg = Msg}.
1469
1470 -spec parse_eof_packet(binary()) -> #eof{}.
1471 parse_eof_packet(<<?EOF:8, NumWarnings:16/little, StatusFlags:16/little>>) ->
1472 %% EOF packet, 4.1 protocol.
1473 %% (Older protocol: <<?EOF:8>>)
1474 614 #eof{status = StatusFlags, warning_count = NumWarnings}.
1475
1476 parse_local_infile_packet(<<?LOCAL_INFILE_REQUEST:8, FileName/binary>>) ->
1477 4 FileName.
1478
1479 -spec parse_auth_method_switch(binary()) -> #auth_method_switch{}.
1480 parse_auth_method_switch(AMSData) ->
1481 9 {AuthPluginName, AuthPluginData} = get_null_terminated_binary(AMSData),
1482 9 #auth_method_switch{
1483 auth_plugin_name = AuthPluginName,
1484 auth_plugin_data = AuthPluginData
1485 }.
1486
1487 -spec parse_auth_more_data(binary()) -> auth_more_data().
1488 parse_auth_more_data(<<3>>) ->
1489 %% With caching_sha2_password authentication, a single 0x03
1490 %% byte signals Fast Auth Success.
1491 57 fast_auth_completed;
1492 parse_auth_more_data(<<4>>) ->
1493 %% With caching_sha2_password authentication, a single 0x04
1494 %% byte signals a Full Auth Request.
1495 3 full_auth_requested;
1496 parse_auth_more_data(Data) ->
1497 %% With caching_sha2_password authentication, anything
1498 %% other than the above should be the public key of the
1499 %% server.
1500 3 PubKey = case public_key:pem_decode(Data) of
1501 [PemEntry = #'SubjectPublicKeyInfo'{}] ->
1502 3 public_key:pem_entry_decode(PemEntry);
1503 [PemEntry = #'RSAPublicKey'{}] ->
1504
:-(
PemEntry
1505 end,
1506 3 {public_key, PubKey}.
1507
1508 -spec get_null_terminated_binary(binary()) -> {Binary :: binary(),
1509 Rest :: binary()}.
1510 get_null_terminated_binary(In) ->
1511 9 get_null_terminated_binary(In, <<>>).
1512
1513 get_null_terminated_binary(<<0, Rest/binary>>, Acc) ->
1514 9 {Acc, Rest};
1515 get_null_terminated_binary(<<Ch, Rest/binary>>, Acc) ->
1516 189 get_null_terminated_binary(Rest, <<Acc/binary, Ch>>).
1517
1518 -spec hash_password(AuthMethod, Password, Salt) -> Hash
1519 when AuthMethod :: binary(),
1520 Password :: iodata(),
1521 Salt :: binary(),
1522 Hash :: binary().
1523 hash_password(AuthMethod, Password, Salt) when not is_binary(Password) ->
1524 70 hash_password(AuthMethod, iolist_to_binary(Password), Salt);
1525 hash_password(?authmethod_none, Password, Salt) ->
1526
:-(
hash_password(?authmethod_mysql_native_password, Password, Salt);
1527 hash_password(?authmethod_mysql_native_password, <<>>, _Salt) ->
1528 1 <<>>;
1529 hash_password(?authmethod_mysql_native_password, Password, Salt) ->
1530 %% From the "MySQL Internals" manual:
1531 %% SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat>
1532 %% SHA1( SHA1( password ) ) )
1533 2 Salt1 = trim_salt(Salt),
1534 2 <<Hash1Num:160>> = Hash1 = crypto:hash(sha, Password),
1535 2 Hash2 = crypto:hash(sha, Hash1),
1536 2 <<Hash3Num:160>> = crypto:hash(sha, <<Salt1/binary, Hash2/binary>>),
1537 2 <<(Hash1Num bxor Hash3Num):160>>;
1538 hash_password(?authmethod_caching_sha2_password, <<>>, _Salt) ->
1539 1 <<>>;
1540 hash_password(?authmethod_caching_sha2_password, Password, Salt) ->
1541 %% From https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html
1542 %% (transcribed):
1543 %% SHA256( password ) XOR SHA256( SHA256( SHA256( password ) ) <concat>
1544 %% "20-bytes random data from server" )
1545 70 Salt1 = trim_salt(Salt),
1546 70 <<Hash1Num:256>> = Hash1 = crypto:hash(sha256, Password),
1547 70 Hash2 = crypto:hash(sha256, Hash1),
1548 70 <<Hash3Num:256>> = crypto:hash(sha256, <<Hash2/binary, Salt1/binary>>),
1549 70 <<(Hash1Num bxor Hash3Num):256>>;
1550 hash_password(?authmethod_sha256_password, Password, Salt) ->
1551 %% sha256_password authentication is superseded by
1552 %% caching_sha2_password.
1553
:-(
hash_password(?authmethod_caching_sha2_password, Password, Salt);
1554 hash_password(UnknownAuthMethod, _, _) ->
1555
:-(
error({auth_method, UnknownAuthMethod}).
1556
1557 encrypt_password(Password, Salt, PubKey, ServerVersion)
1558 when is_binary(Password) ->
1559 %% From http://www.dataarchitect.cloud/preparing-your-community-connector-for-mysql-8-part-2-sha256/:
1560 %% "The password is "obfuscated" first by employing a rotating "xor" against
1561 %% the seed bytes that were given to the authentication plugin upon initial
1562 %% handshake [the auth plugin data].
1563 %% [...]
1564 %% Buffer would then be encrypted using the RSA public key the server passed
1565 %% to the client. The resulting buffer would then be passed back to the
1566 %% server."
1567 3 Salt1 = trim_salt(Salt),
1568
1569 %% While the article does not mention it, the password must be null-terminated
1570 %% before obfuscation.
1571 3 Password1 = <<Password/binary, 0>>,
1572 3 Salt2 = case byte_size(Salt1)<byte_size(Password1) of
1573 true ->
1574
:-(
binary:copy(Salt1, (byte_size(Password1) div byte_size(Salt1)) + 1);
1575 false ->
1576 3 Salt1
1577 end,
1578 3 Size = bit_size(Password1),
1579 3 <<PasswordNum:Size>> = Password1,
1580 3 <<SaltNum:Size, _/bitstring>> = Salt2,
1581 3 Password2 = <<(PasswordNum bxor SaltNum):Size>>,
1582
1583 %% From http://www.dataarchitect.cloud/preparing-your-community-connector-for-mysql-8-part-2-sha256/:
1584 %% "It's important to note that a incompatible change happened in server 8.0.5.
1585 %% Prior to server 8.0.5 the encryption was done using RSA_PKCS1_PADDING.
1586 %% With 8.0.5 it is done with RSA_PKCS1_OAEP_PADDING."
1587 3 RsaPadding = case ServerVersion < [8, 0, 5] of
1588 true ->
1589
:-(
logger:warning("Using unsafe cipher algorithm padding rsa_pkcs1_padding"),
1590
:-(
rsa_pkcs1_padding;
1591 false ->
1592 3 rsa_pkcs1_oaep_padding
1593 end,
1594
1595 %% In OTP 27.0, public_key:encrypt_public was deprecated, and
1596 %% de-deprecated again in 27.1. We mitigate the possible deprecation
1597 %% warning by putting the module name in a variable and use it to
1598 %% perform the call.
1599 %% The option rsa_pad was renamed to rsa_padding in OTP/22, but rsa_pad
1600 %% is being kept for backwards compatibility.
1601 3 PublicKeyMod = public_key,
1602 3 PublicKeyMod:encrypt_public(Password2, PubKey, [{rsa_pad, RsaPadding}]);
1603 encrypt_password(Password, Salt, PubKey, ServerVersion) ->
1604 3 encrypt_password(iolist_to_binary(Password), Salt, PubKey, ServerVersion).
1605
1606 trim_salt(<<SaltNoNul:20/binary-unit:8, 0>>) ->
1607 73 SaltNoNul;
1608 trim_salt(Salt = <<_:20/binary-unit:8>>) ->
1609 2 Salt.
1610
1611 %% --- Lowlevel: variable length integers and strings ---
1612
1613 %% lenenc_int/1 decodes length-encoded-integer values
1614 -spec lenenc_int(Input :: binary()) -> {Value :: integer(), Rest :: binary()}.
1615 4250 lenenc_int(<<Value:8, Rest/bits>>) when Value < 251 -> {Value, Rest};
1616 1 lenenc_int(<<16#fc:8, Value:16/little, Rest/binary>>) -> {Value, Rest};
1617 1 lenenc_int(<<16#fd:8, Value:24/little, Rest/binary>>) -> {Value, Rest};
1618 1 lenenc_int(<<16#fe:8, Value:64/little, Rest/binary>>) -> {Value, Rest}.
1619
1620 %% Length-encoded-integer encode. Appends the encoded value to Acc.
1621 %% Values not representable in 64 bits are not accepted.
1622 -spec lenenc_int_encode(0..16#ffffffffffffffff) -> binary().
1623 lenenc_int_encode(Value) when Value >= 0 ->
1624 44 if Value < 251 -> <<Value>>;
1625 1 Value =< 16#ffff -> <<16#fc, Value:16/little>>;
1626 1 Value =< 16#ffffff -> <<16#fd, Value:24/little>>;
1627 1 Value =< 16#ffffffffffffffff -> <<16#fe, Value:64/little>>
1628 end.
1629
1630 %% lenenc_str/1 decodes length-encoded-string values
1631 -spec lenenc_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
1632 lenenc_str(Bin) ->
1633 2455 {Length, Rest} = lenenc_int(Bin),
1634 2455 <<String:Length/binary, Rest1/binary>> = Rest,
1635 2455 {String, Rest1}.
1636
1637 %% Length-encoded-string encode. Prefixes the value with a
1638 %% length-encoded-integer denoting its size.
1639 -spec lenenc_str_encode(Input :: binary()) -> binary().
1640 lenenc_str_encode(Bin) ->
1641 9 Length = byte_size(Bin),
1642 9 <<(lenenc_int_encode(Length))/binary, Bin:Length/binary>>.
1643
1644 %% nts/1 decodes a nul-terminated string
1645 -spec nulterm_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
1646 nulterm_str(Bin) ->
1647 53 [String, Rest] = binary:split(Bin, <<0>>),
1648 53 {String, Rest}.
1649
1650 -ifdef(TEST).
1651 -include_lib("eunit/include/eunit.hrl").
1652
1653 %% Testing some of the internal functions, mostly the cases we don't cover in
1654 %% other tests.
1655
1656 server_info_test() ->
1657 1 lists:foreach(
1658 fun ({Input, Exp}) ->
1659 10 ?assertEqual(Exp, get_server_info(Input)),
1660 10 ?assertEqual(Exp, get_server_info(<<Input/binary, $a>>)),
1661 10 ?assertEqual(Exp, get_server_info(<<Input/binary, $-, $a>>)),
1662 10 ?assertEqual(Exp, get_server_info(<<Input/binary, $-, $0>>))
1663 end,
1664 [
1665 {<<"4.1.0">>, {mysql, [4, 1, 0]}},
1666 {<<"5.0.0">>, {mysql, [5, 0, 0]}},
1667 {<<"5.1.0">>, {mysql, [5, 1, 0]}},
1668 {<<"5.2.0">>, {mariadb, [5, 2, 0]}},
1669 {<<"5.3.0">>, {mariadb, [5, 3, 0]}},
1670 {<<"5.5.0">>, {mysql, [5, 5, 0]}},
1671 {<<"5.6.0">>, {mysql, [5, 6, 0]}},
1672 {<<"5.7.0">>, {mysql, [5, 7, 0]}},
1673 {<<"8.0.0">>, {mysql, [8, 0, 0]}},
1674 {<<"5.5.5-10.0.0">>, {mariadb, [10, 0, 0]}}
1675 ]
1676 ).
1677
1678 decode_text_test() ->
1679 %% Int types
1680 1 lists:foreach(fun (T) ->
1681 6 ?assertEqual(1, decode_text(#col{type = T}, <<"1">>))
1682 end,
1683 [?TYPE_TINY, ?TYPE_SHORT, ?TYPE_LONG, ?TYPE_LONGLONG,
1684 ?TYPE_INT24, ?TYPE_YEAR]),
1685
1686 %% BIT
1687 1 <<217>> = decode_text(#col{type = ?TYPE_BIT, length = 8}, <<217>>),
1688
1689 %% Floating point and decimal numbers
1690 1 lists:foreach(fun (T) ->
1691 2 ?assertEqual(3.0, decode_text(#col{type = T}, <<"3.0">>))
1692 end,
1693 [?TYPE_FLOAT, ?TYPE_DOUBLE]),
1694 %% Decimal types
1695 1 lists:foreach(fun (T) ->
1696 2 ColDef = #col{type = T},
1697 2 ?assertMatch(3.0,
1698 decode_text(ColDef#col{decimals = 1, length = 4},
1699 <<"3.0">>)),
1700 %% Decimal Decode Options
1701 %% A small value like this would be returned as a float
1702 %% if decode_decimal=auto. We want to test forcing the
1703 %% binary return
1704 2 ?assertMatch(<<"3.0">>,
1705 decode_text(ColDef#col{decimals = 1, length = 4,
1706 decode_decimal = binary},
1707 <<"3.0">>)),
1708 %% When decode_decimal=number, we expect a float and accept the
1709 %% precision loss.
1710 2 ?assertMatch(123456789.45678912,
1711 decode_text(ColDef#col{decimals = 12, length = 23,
1712 decode_decimal = number},
1713 <<"123456789.456789123456789">>)),
1714 %% When with decode_decimal=auto, we expect a binary
1715 %% when the float value is large
1716 2 ?assertMatch(<<"12345678901234567890.12345">>,
1717 decode_text(ColDef#col{decimals = 5, length = 27,
1718 decode_decimal = auto},
1719 <<"12345678901234567890.12345">>)),
1720 %% When with decode_decimal=number, we expect a large float
1721 2 ?assertMatch(1.2345678901234567e19,
1722 decode_text(ColDef#col{decimals = 5, length = 27,
1723 decode_decimal = number},
1724 <<"12345678901234567890.12345">>)),
1725 %% When decode_decimal=float, even if the expected return value
1726 %% would be an integer, force the float
1727 2 ?assertMatch(3.0,
1728 decode_text(ColDef#col{decimals = 0, length = 2,
1729 decode_decimal = float},
1730 <<"3">>)),
1731 %% decimal_decode=auto will encode to binary to prevent
1732 %% the loss of precision when converting to float, so
1733 %% this is just testing to ensure that that happens.
1734 2 ?assertMatch(<<"123456789.456789123456789">>,
1735 decode_text(ColDef#col{decimals = 12, length = 23,
1736 decode_decimal = auto},
1737 <<"123456789.456789123456789">>))
1738 end,
1739 [?TYPE_DECIMAL, ?TYPE_NEWDECIMAL]),
1740
1741 1 ?assertEqual(3.0, decode_text(#col{type = ?TYPE_FLOAT}, <<"3">>)),
1742 1 ?assertEqual(30.0, decode_text(#col{type = ?TYPE_FLOAT}, <<"3e1">>)),
1743 1 ?assertEqual(3, decode_text(#col{type = ?TYPE_LONG}, <<"3">>)),
1744
1745 %% Date and time
1746 1 ?assertEqual({2014, 11, 01},
1747 1 decode_text(#col{type = ?TYPE_DATE}, <<"2014-11-01">>)),
1748 1 ?assertEqual({0, {23, 59, 01}},
1749 1 decode_text(#col{type = ?TYPE_TIME}, <<"23:59:01">>)),
1750 1 ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
1751 decode_text(#col{type = ?TYPE_DATETIME},
1752 1 <<"2014-11-01 23:59:01">>)),
1753 1 ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
1754 decode_text(#col{type = ?TYPE_TIMESTAMP},
1755 1 <<"2014-11-01 23:59:01">>)),
1756
1757 %% Strings and blobs
1758 1 lists:foreach(fun (T) ->
1759 9 ColDef = #col{type = T},
1760 9 ?assertEqual(<<"x">>, decode_text(ColDef, <<"x">>))
1761 end,
1762 [?TYPE_VARCHAR, ?TYPE_ENUM, ?TYPE_TINY_BLOB,
1763 ?TYPE_MEDIUM_BLOB, ?TYPE_LONG_BLOB, ?TYPE_BLOB,
1764 ?TYPE_VAR_STRING, ?TYPE_STRING, ?TYPE_GEOMETRY]),
1765 1 ok.
1766
1767 decode_binary_test() ->
1768 %% Test the special rounding we apply to (single precision) floats.
1769 1 ?assertEqual({1.0, <<>>},
1770 decode_binary(#col{type = ?TYPE_FLOAT},
1771 1 <<1.0:32/float-little>>)),
1772 1 ?assertEqual({0.2, <<>>},
1773 decode_binary(#col{type = ?TYPE_FLOAT},
1774 1 <<0.2:32/float-little>>)),
1775 1 ?assertEqual({-33.3333, <<>>},
1776 decode_binary(#col{type = ?TYPE_FLOAT},
1777 1 <<-33.333333:32/float-little>>)),
1778 1 ?assertEqual({0.000123457, <<>>},
1779 decode_binary(#col{type = ?TYPE_FLOAT},
1780 1 <<0.00012345678:32/float-little>>)),
1781 1 ?assertEqual({1234.57, <<>>},
1782 decode_binary(#col{type = ?TYPE_FLOAT},
1783 1 <<1234.56789:32/float-little>>)),
1784 1 ok.
1785
1786 null_bitmap_test() ->
1787 1 ?assertEqual({<<0, 1:1>>, <<>>}, null_bitmap_decode(9, <<0, 4>>, 2)),
1788 1 ?assertEqual(<<0, 4>>, null_bitmap_encode(<<0, 1:1>>, 2)),
1789 1 ok.
1790
1791 lenenc_int_test() ->
1792 %% decode
1793 1 ?assertEqual({40, <<>>}, lenenc_int(<<40>>)),
1794 1 ?assertEqual({16#ff, <<>>}, lenenc_int(<<16#fc, 255, 0>>)),
1795 1 ?assertEqual({16#33aaff, <<>>}, lenenc_int(<<16#fd, 16#ff, 16#aa, 16#33>>)),
1796 1 ?assertEqual({16#12345678, <<>>}, lenenc_int(<<16#fe, 16#78, 16#56, 16#34,
1797 1 16#12, 0, 0, 0, 0>>)),
1798 %% encode
1799 1 ?assertEqual(<<40>>, lenenc_int_encode(40)),
1800 1 ?assertEqual(<<16#fc, 255, 0>>, lenenc_int_encode(255)),
1801 1 ?assertEqual(<<16#fd, 16#ff, 16#aa, 16#33>>,
1802 1 lenenc_int_encode(16#33aaff)),
1803 1 ?assertEqual(<<16#fe, 16#78, 16#56, 16#34, 16#12, 0, 0, 0, 0>>,
1804 1 lenenc_int_encode(16#12345678)),
1805 1 ok.
1806
1807 lenenc_str_test() ->
1808 1 ?assertEqual({<<"Foo">>, <<"bar">>}, lenenc_str(<<3, "Foobar">>)).
1809
1810 nulterm_test() ->
1811 1 ?assertEqual({<<"Foo">>, <<"bar">>}, nulterm_str(<<"Foo", 0, "bar">>)).
1812
1813 parse_header_test() ->
1814 %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
1815 1 Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
1816 1 <<Header:4/binary-unit:8, Body/binary>> = Packet,
1817 %% Check header contents and body length
1818 1 ?assertEqual({size(Body), 5, false}, parse_packet_header(Header)),
1819 1 ok.
1820
1821 add_packet_headers_test() ->
1822 1 {Data, 43} = add_packet_headers(<<"foo">>, 42),
1823 1 ?assertEqual(<<3, 0, 0, 42, "foo">>, list_to_binary(Data)).
1824
1825 add_packet_headers_equal_to_0xffffff_test() ->
1826 1 BigBin = binary:copy(<<1>>, 16#ffffff),
1827 1 {Data, 44} = add_packet_headers(BigBin, 42),
1828 1 ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary,
1829 1 0, 0, 0, 43>>,
1830 1 list_to_binary(Data)).
1831
1832 add_packet_headers_greater_than_0xffffff_test() ->
1833 1 BigBin = binary:copy(<<1>>, 16#ffffff),
1834 1 {Data, 44} = add_packet_headers(<<BigBin/binary, "foo">>, 42),
1835 1 ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary, 3, 0, 0, 43, "foo">>,
1836 1 list_to_binary(Data)).
1837
1838 add_packet_headers_2_times_greater_than_0xffffff_test() ->
1839 1 BigBin = binary:copy(<<1>>, 16#ffffff),
1840 1 {Data, 45} = add_packet_headers(<<BigBin/binary, BigBin/binary, "foo">>, 42),
1841 1 ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary,
1842 16#ff, 16#ff, 16#ff, 43, BigBin/binary,
1843 1 3, 0, 0, 44, "foo">>,
1844 1 list_to_binary(Data)).
1845
1846 parse_ok_test() ->
1847 1 Body = <<0, 5, 1, 2, 0, 0, 0, "Foo">>,
1848 1 ?assertEqual(#ok{affected_rows = 5,
1849 insert_id = 1,
1850 status = ?SERVER_STATUS_AUTOCOMMIT,
1851 warning_count = 0,
1852 1 msg = <<"Foo">>},
1853 1 parse_ok_packet(Body)).
1854
1855 parse_error_test() ->
1856 %% Protocol 4.1
1857 1 Body = <<255, 42, 0, "#", "XYZxx", "Foo">>,
1858 1 ?assertEqual(#error{code = 42, state = <<"XYZxx">>, msg = <<"Foo">>},
1859 1 parse_error_packet(Body)),
1860 1 ok.
1861
1862 parse_eof_test() ->
1863 %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
1864 1 Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
1865 1 <<_Header:4/binary-unit:8, Body/binary>> = Packet,
1866 %% Ignore header. Parse body as an eof_packet.
1867 1 ?assertEqual(#eof{warning_count = 0,
1868 1 status = ?SERVER_STATUS_AUTOCOMMIT},
1869 1 parse_eof_packet(Body)),
1870 1 ok.
1871
1872 hash_password_test() ->
1873 1 ?assertEqual(<<222,207,222,139,41,181,202,13,191,241,
1874 1 234,234,73,127,244,101,205,3,28,251>>,
1875 hash_password(?authmethod_mysql_native_password,
1876 1 <<"foo">>, <<"abcdefghijklmnopqrst">>)),
1877 1 ?assertEqual(<<>>, hash_password(?authmethod_mysql_native_password,
1878 1 <<>>, <<"abcdefghijklmnopqrst">>)),
1879 1 ?assertEqual(<<125,155,142,2,20,139,6,254,65,126,239,
1880 146,107,77,17,8,120,55,247,33,87,16,76,
1881 1 63,128,131,60,188,58,81,171,242>>,
1882 hash_password(?authmethod_caching_sha2_password,
1883 1 <<"foo">>, <<"abcdefghijklmnopqrst">>)),
1884 1 ?assertEqual(<<>>, hash_password(?authmethod_caching_sha2_password,
1885 1 <<>>, <<"abcdefghijklmnopqrst">>)).
1886
1887 valid_params_test() ->
1888 1 ValidParams = [
1889 null,
1890 1,
1891 0.5,
1892 <<>>, <<$x>>, <<0:1>>,
1893
1894 %% valid unicode
1895 [], [$x], [16#E4],
1896
1897 %% valid date
1898 {1, 2, 3},
1899
1900 %% valid time
1901 {1, {2, 3, 4}}, {1, {2, 3, 4.5}},
1902
1903 %% valid datetime
1904 {{1, 2, 3}, {4, 5, 6}}, {{1, 2, 3}, {4, 5, 6.5}}
1905 ],
1906
1907 1 InvalidParams = [
1908 x,
1909 [x],
1910 {},
1911 self(),
1912 make_ref(),
1913
:-(
fun () -> ok end,
1914
1915 %% invalid unicode
1916 [16#FFFFFFFF],
1917
1918 %% invalid date
1919 {x, 1, 2}, {1, x, 2}, {1, 2, x},
1920
1921 %% invalid time
1922 {x, {1, 2, 3}}, {1, {x, 2, 3}},
1923 {1, {2, x, 3}}, {1, {2, 3, x}},
1924
1925 %% invalid datetime
1926 {{x, 1, 2}, {3, 4, 5}}, {{1, x, 2}, {3, 4, 5}},
1927 {{1, 2, x}, {3, 4, 5}}, {{1, 2, 3}, {x, 4, 5}},
1928 {{1, 2, 3}, {4, x, 5}}, {{1, 2, 3}, {4, 5, x}}
1929 ],
1930
1931 1 lists:foreach(
1932 fun (ValidParam) ->
1933 14 ?assert(is_valid_param(ValidParam))
1934 end,
1935 ValidParams),
1936 1 ?assert(valid_params(ValidParams)),
1937
1938 1 lists:foreach(
1939 fun (InvalidParam) ->
1940 20 ?assertNot(is_valid_param(InvalidParam))
1941 end,
1942 InvalidParams),
1943 1 ?assertNot(valid_params(InvalidParams)),
1944 1 ?assertNot(valid_params(ValidParams ++ InvalidParams)).
1945
1946 valid_path_test() ->
1947 1 ValidPaths = [
1948 <<"/">>,
1949 <<"/tmp">>,
1950 <<"/tmp/">>,
1951 <<"/tmp/foo">>
1952 ],
1953 1 InvalidPaths = [
1954 <<>>,
1955 <<"tmp">>,
1956 <<"tmp/">>,
1957 <<"tmp/foo">>,
1958 <<"../tmp">>,
1959 <<"/tmp/..">>,
1960 <<"/tmp/foo/../bar">>,
1961 "/tmp"
1962 ],
1963 1 lists:foreach(
1964 fun (ValidPath) ->
1965 4 ?assert(valid_path(ValidPath))
1966 end,
1967 ValidPaths
1968 ),
1969 1 lists:foreach(
1970 fun (InvalidPath) ->
1971 8 ?assertNot(valid_path(InvalidPath))
1972 end,
1973 InvalidPaths
1974 ).
1975
1976 allowed_path_test() ->
1977 1 AllowedPaths = [
1978 <<"/tmp/foo/file.csv">>,
1979 <<"/tmp/foo/bar/">>,
1980 <<"/tmp/foo/baz">>
1981 ],
1982 1 ValidPaths = [
1983 <<"/tmp/foo/file.csv">>,
1984 <<"/tmp/foo/bar/file.csv">>,
1985 <<"/tmp/foo/baz/file.csv">>,
1986 <<"/tmp/foo/baz">>
1987 ],
1988 1 InvalidPaths = [
1989 <<"/tmp/file.csv">>,
1990 <<"/tmp/foo/other_file.csv">>,
1991 <<"/tmp/foo/other_dir/file.csv">>,
1992 <<"/tmp/foo/../file.csv">>,
1993 <<"/tmp/foo/../bar/file.csv">>,
1994 <<"/tmp/foo/bar/">>,
1995 <<"/tmp/foo/barbaz">>
1996 ],
1997 1 lists:foreach(
1998 fun (ValidPath) ->
1999 4 ?assert(allowed_path(ValidPath, AllowedPaths))
2000 end,
2001 ValidPaths
2002 ),
2003 1 lists:foreach(
2004 fun (InvalidPath) ->
2005 7 ?assertNot(allowed_path(InvalidPath, AllowedPaths))
2006 end,
2007 InvalidPaths
2008 ).
2009
2010 -endif.
2011
Line Hits Source