File Coverage

blib/lib/Net/MySQL.pm
Criterion Covered Total %
statement 226 526 42.9
branch 11 108 10.1
condition 3 15 20.0
subroutine 60 108 55.5
pod 11 13 84.6
total 311 770 40.3


line stmt bran cond sub pod time code
1             package Net::MySQL;
2              
3 2     2   15709 use 5.004;
  2         8  
  2         85  
4 2     2   2208 use IO::Socket;
  2         105197  
  2         10  
5 2     2   2483 use Carp;
  2         12  
  2         119  
6 2     2   10 use vars qw($VERSION $DEBUG);
  2         4  
  2         397  
7 2     2   10 use strict;
  2         2  
  2         120  
8             $VERSION = '0.11';
9              
10 2     2   11 use constant COMMAND_SLEEP => "\x00";
  2         3  
  2         409  
11 2     2   10 use constant COMMAND_QUIT => "\x01";
  2         4  
  2         106  
12 2     2   9 use constant COMMAND_INIT_DB => "\x02";
  2         4  
  2         128  
13 2     2   9 use constant COMMAND_QUERY => "\x03";
  2         4  
  2         96  
14 2     2   21 use constant COMMAND_FIELD_LIST => "\x04";
  2         2  
  2         81  
15 2     2   10 use constant COMMAND_CREATE_DB => "\x05";
  2         2  
  2         87  
16 2     2   9 use constant COMMAND_DROP_DB => "\x06";
  2         4  
  2         83  
17 2     2   9 use constant COMMAND_REFRESH => "\x07";
  2         3  
  2         85  
18 2     2   19 use constant COMMAND_SHUTDOWN => "\x08";
  2         4  
  2         205  
19 2     2   9 use constant COMMAND_STATISTICS => "\x09";
  2         4  
  2         89  
20 2     2   8 use constant COMMAND_PROCESS_INFO => "\x0A";
  2         2  
  2         79  
21 2     2   8 use constant COMMAND_CONNECT => "\x0B";
  2         2  
  2         182  
22 2     2   10 use constant COMMAND_PROCESS_KILL => "\x0C";
  2         2  
  2         82  
23 2     2   10 use constant COMMAND_DEBUG => "\x0D";
  2         2  
  2         79  
24 2     2   8 use constant COMMAND_PING => "\x0E";
  2         3  
  2         154  
25 2     2   9 use constant COMMAND_TIME => "\x0F";
  2         4  
  2         88  
26 2     2   8 use constant COMMAND_DELAYED_INSERT => "\x10";
  2         3  
  2         108  
27 2     2   9 use constant COMMAND_CHANGE_USER => "\x11";
  2         3  
  2         83  
28 2     2   9 use constant COMMAND_BINLOG_DUMP => "\x12";
  2         3  
  2         126  
29 2     2   10 use constant COMMAND_TABLE_DUMP => "\x13";
  2         4  
  2         85  
30 2     2   9 use constant COMMAND_CONNECT_OUT => "\x14";
  2         2  
  2         79  
31              
32 2     2   8 use constant DEFAULT_PORT_NUMBER => 3306;
  2         4  
  2         80  
33 2     2   10 use constant BUFFER_LENGTH => 1460;
  2         10  
  2         73  
34 2     2   10 use constant DEFAULT_UNIX_SOCKET => '/tmp/mysql.sock';
  2         3  
  2         8238  
35              
36              
37             sub new
38             {
39 0     0 1 0 my $class = shift;
40 0         0 my %args = @_;
41              
42 0   0     0 my $self = bless {
      0        
      0        
43             hostname => $args{hostname},
44             unixsocket => $args{unixsocket} || DEFAULT_UNIX_SOCKET,
45             port => $args{port} || DEFAULT_PORT_NUMBER,
46             database => $args{database},
47             user => $args{user},
48             password => $args{password},
49             timeout => $args{timeout} || 60,
50             'socket' => undef,
51             salt => '',
52             protocol_version => undef,
53             client_capabilities => 0,
54             affected_rows_length => 0,
55             }, $class;
56 0         0 $self->debug($args{debug});
57 0         0 $self->_initialize;
58 0         0 return $self;
59             }
60              
61              
62             sub query
63             {
64 0     0 1 0 my $self = shift;
65 0         0 my $sql = join '', @_;
66 0         0 my $mysql = $self->{socket};
67              
68 0         0 return $self->_execute_command(COMMAND_QUERY, $sql);
69             }
70              
71              
72             sub create_database
73             {
74 0     0 1 0 my $self = shift;
75 0         0 my $db_name = shift;
76 0         0 my $mysql = $self->{socket};
77              
78 0         0 return $self->_execute_command(COMMAND_CREATE_DB, $db_name);
79             }
80              
81              
82             sub drop_database
83             {
84 0     0 1 0 my $self = shift;
85 0         0 my $db_name = shift;
86 0         0 my $mysql = $self->{socket};
87              
88 0         0 return $self->_execute_command(COMMAND_DROP_DB, $db_name);
89             }
90              
91              
92             sub close
93             {
94 0     0 1 0 my $self = shift;
95 0         0 my $mysql = $self->{socket};
96 0 0       0 return unless $mysql->can('send');
97              
98 0         0 my $quit_message =
99             chr(length(COMMAND_QUIT)). "\x00\x00\x00". COMMAND_QUIT;
100 0         0 $mysql->send($quit_message, 0);
101 0 0       0 $self->_dump_packet($quit_message) if Net::MySQL->debug;
102 0         0 $mysql->close;
103             }
104              
105              
106             sub get_affected_rows_length
107             {
108 0     0 1 0 my $self = shift;
109 0         0 $self->{affected_rows_length};
110             }
111              
112              
113             sub get_insert_id
114             {
115 0     0 1 0 my $self = shift;
116 0         0 $self->{insert_id};
117             }
118              
119              
120             sub create_record_iterator
121             {
122 0     0 1 0 my $self = shift;
123 0 0       0 return undef unless $self->has_selected_record;
124              
125 0         0 my $record = Net::MySQL::RecordIterator->new(
126             $self->{selected_record}
127             );
128 0         0 $self->{selected_record} = undef;
129 0         0 $record->parse;
130 0         0 return $record;
131             }
132              
133              
134             sub has_selected_record
135             {
136 0     0 1 0 my $self = shift;
137 0 0       0 $self->{selected_record} ? 1 : undef;
138             }
139              
140              
141             sub is_error
142             {
143 0     0 1 0 my $self = shift;
144 0 0       0 $self->{error_code} ? 1 : undef;
145             }
146              
147              
148             sub get_error_code
149             {
150 0     0 0 0 my $self = shift;
151 0         0 $self->{error_code};
152             }
153              
154              
155             sub get_error_message
156             {
157 0     0 0 0 my $self = shift;
158 0         0 $self->{server_message};
159             }
160              
161              
162             sub debug
163             {
164 0     0 1 0 my $class = shift;
165 0 0       0 $DEBUG = shift if @_;
166 0         0 $DEBUG;
167             }
168              
169              
170             sub _connect
171             {
172 0     0   0 my $self = shift;
173              
174 0         0 my $mysql;
175 0 0       0 if ($self->{hostname}) {
176 0 0       0 printf "Use INET Socket: %s %d/tcp\n", $self->{hostname}, $self->{port}
177             if $self->debug;
178 0 0 0     0 $mysql = IO::Socket::INET->new(
179             PeerAddr => $self->{hostname},
180             PeerPort => $self->{port},
181             Proto => 'tcp',
182             Timeout => $self->{timeout} || 60,
183             ) or croak "Couldn't connect to $self->{hostname}:$self->{port}/tcp: $@";
184             }
185             else {
186 0 0       0 printf "Use UNIX Socket: %s\n", $self->{unixsocket} if $self->debug;
187 0 0       0 $mysql = IO::Socket::UNIX->new(
188             Type => SOCK_STREAM,
189             Peer => $self->{unixsocket},
190             ) or croak "Couldn't connect to $self->{unixsocket}: $@";
191             }
192 0         0 $mysql->autoflush(1);
193 0         0 $self->{socket} = $mysql;
194             }
195              
196              
197             sub _get_server_information
198             {
199 0     0   0 my $self = shift;
200 0         0 my $mysql = $self->{socket};
201              
202 0         0 my $message;
203 0         0 $mysql->recv($message, BUFFER_LENGTH, 0);
204 0 0       0 $self->_dump_packet($message)
205             if Net::MySQL->debug;
206 0         0 my $i = 0;
207 0         0 my $packet_length = ord substr $message, $i, 1;
208 0         0 $i += 4;
209 0         0 $self->{protocol_version} = ord substr $message, $i, 1;
210 0 0       0 printf "Protocol Version: %d\n", $self->{protocol_version}
211             if Net::MySQL->debug;
212 0 0       0 if ($self->{protocol_version} == 10) {
213 0         0 $self->{client_capabilities} = 1;
214             }
215              
216 0         0 ++$i;
217 0         0 my $string_end = index($message, "\0", $i) - $i;
218 0         0 $self->{server_version} = substr $message, $i, $string_end;
219 0 0       0 printf "Server Version: %s\n", $self->{server_version}
220             if Net::MySQL->debug;
221              
222 0         0 $i += $string_end + 1;
223 0         0 $self->{server_thread_id} = unpack 'v', substr $message, $i, 2;
224 0         0 $i += 4;
225 0         0 $self->{salt} = substr $message, $i, 8;
226             #
227 0         0 $i += 8+1;
228 0 0       0 if (length $message >= $i + 1) {
229 0         0 $i += 1;
230             }
231 0 0       0 if (length $message >= $i + 18) {
232             # get server_language
233             # get server_status
234             }
235 0         0 $i += 18 - 1;
236 0 0       0 if (length $message >= $i + 12 - 1) {
237 0         0 $self->{salt} .= substr $message, $i, 12;
238             }
239 0 0       0 printf "Salt: %s\n", $self->{salt} if Net::MySQL->debug;
240              
241             }
242              
243              
244             sub _request_authentication
245             {
246 0     0   0 my $self = shift;
247 0         0 my $mysql = $self->{socket};
248 0         0 $self->_send_login_message();
249              
250 0         0 my $auth_result;
251 0         0 $mysql->recv($auth_result, BUFFER_LENGTH, 0);
252 0 0       0 $self->_dump_packet($auth_result) if Net::MySQL->debug;
253 0 0       0 if ($self->_is_error($auth_result)) {
254 0         0 $mysql->close;
255 0 0       0 if (length $auth_result < 7) {
256 0         0 croak "Timeout of authentication";
257             }
258 0         0 croak substr $auth_result, 7;
259             }
260 0 0       0 print "connect database\n" if Net::MySQL->debug;
261             }
262              
263              
264             sub _send_login_message
265             {
266 0     0   0 my $self = shift;
267 0         0 my $mysql = $self->{socket};
268 0         0 my $body = "\0\0\x01\x0d\xa6\03\0\0\0\0\x01".
269             "\x21\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".
270             join "\0",
271             $self->{user},
272             "\x14".
273             Net::MySQL::Password->scramble(
274             $self->{password}, $self->{salt}, $self->{client_capabilities}
275             );
276 0         0 $body .= $self->{database};
277 0         0 $body .= "\0";
278 0         0 my $login_message = chr(length($body)-3). $body;
279 0         0 $mysql->send($login_message, 0);
280 0 0       0 $self->_dump_packet($login_message) if Net::MySQL->debug;
281             }
282              
283              
284              
285             sub _execute_command
286             {
287 0     0   0 my $self = shift;
288 0         0 my $command = shift;
289 0         0 my $sql = shift;
290 0         0 my $mysql = $self->{socket};
291              
292 0         0 my $message = pack('V', length($sql) + 1). $command. $sql;
293 0         0 $mysql->send($message, 0);
294 0 0       0 $self->_dump_packet($message) if Net::MySQL->debug;
295              
296 0         0 my $result;
297 0         0 $mysql->recv($result, BUFFER_LENGTH, 0);
298 0 0       0 $self->_dump_packet($result) if Net::MySQL->debug;
299 0         0 $self->_reset_status;
300              
301 0 0       0 if ($self->_is_error($result)) {
    0          
    0          
302 0         0 return $self->_set_error_by_packet($result);
303             }
304             elsif ($self->_is_select_query_result($result)) {
305 0         0 return $self->_get_record_by_server($result);
306             }
307             elsif ($self->_is_update_query_result($result)){
308 0         0 return $self->_get_affected_rows_information_by_packet($result);
309             }
310             else {
311 0         0 croak 'Unknown Result: '. $self->_get_result_length($result). 'byte';
312             }
313             }
314              
315              
316             sub _initialize
317             {
318 0     0   0 my $self = shift;
319 0         0 $self->_connect;
320 0         0 $self->_get_server_information;
321 0         0 $self->_request_authentication;
322             }
323              
324              
325             sub _set_error_by_packet
326             {
327 0     0   0 my $self = shift;
328 0         0 my $packet = shift;
329              
330 0         0 my $error_message = $self->_get_server_message($packet);
331 0         0 $self->{server_message} = $error_message;
332 0         0 $self->{error_code} = $self->_get_error_code($packet);
333 0         0 return undef;
334             }
335              
336              
337             sub _get_record_by_server
338             {
339 0     0   0 my $self = shift;
340 0         0 my $packet = shift;
341 0         0 my $mysql = $self->{socket};
342 0         0 $self->_get_column_length($packet);
343 0         0 while ($self->_has_next_packet($packet)) {
344 0         0 my $next_result;
345 0         0 $mysql->recv($next_result, BUFFER_LENGTH, 0);
346 0         0 $packet .= $next_result;
347 0 0       0 $self->_dump_packet($next_result) if Net::MySQL->debug;
348             }
349 0         0 $self->{selected_record} = $packet;
350             }
351              
352              
353             sub _get_affected_rows_information_by_packet
354             {
355 0     0   0 my $self = shift;
356 0         0 my $packet = shift;
357              
358 0         0 $self->{affected_rows_length} = $self->_get_affected_rows_length($packet);
359 0         0 $self->{insert_id} = $self->_get_insert_id($packet);
360 0         0 $self->{server_message} = $self->_get_server_message($packet);
361 0         0 return $self->{affected_rows_length};
362             }
363              
364              
365             sub _is_error
366             {
367 0     0   0 my $self = shift;
368 0         0 my $packet = shift;
369 0 0       0 return 1 if length $packet < 4;
370 0         0 ord(substr $packet, 4) == 255;
371             }
372              
373              
374             sub _is_select_query_result
375             {
376 0     0   0 my $self = shift;
377 0         0 my $packet = shift;
378 0 0       0 return undef if $self->_is_error($packet);
379 0         0 ord(substr $packet, 4) >= 1;
380             }
381              
382              
383             sub _is_update_query_result
384             {
385 0     0   0 my $self = shift;
386 0         0 my $packet = shift;
387 0 0       0 return undef if $self->_is_error($packet);
388 0         0 ord(substr $packet, 4) == 0;
389             }
390              
391              
392             sub _get_result_length
393             {
394 0     0   0 my $self = shift;
395 0         0 my $packet = shift;
396 0         0 ord(substr $packet, 0, 1)
397             }
398              
399              
400             sub _get_column_length
401             {
402 0     0   0 my $self = shift;
403 0         0 my $packet = shift;
404 0         0 ord(substr $packet, 4);
405             }
406              
407              
408             sub _get_affected_rows_length
409             {
410 0     0   0 my $self = shift;
411 0         0 my $packet = shift;
412 0         0 my $pos = 5;
413 0         0 return Net::MySQL::Util::get_field_length($packet, \$pos);
414             }
415              
416              
417             sub _get_insert_id
418             {
419 0     0   0 my $self = shift;
420 0         0 my $packet = shift;
421 0 0       0 return ord(substr $packet, 6, 1) if ord(substr $packet, 6, 1) != 0xfc;
422 0         0 unpack 'v', substr $packet, 7, 2;
423             }
424              
425              
426             sub _get_server_message
427             {
428 0     0   0 my $self = shift;
429 0         0 my $packet = shift;
430 0 0       0 return '' if length $packet < 7;
431 0         0 substr $packet, 7;
432             }
433              
434              
435             sub _get_error_code
436             {
437 0     0   0 my $self = shift;
438 0         0 my $packet = shift;
439 0 0       0 $self->_is_error($packet)
440             or croak "_get_error_code(): Is not error packet";
441 0         0 unpack 'v', substr $packet, 5, 2;
442             }
443              
444              
445             sub _reset_status
446             {
447 0     0   0 my $self = shift;
448 0         0 $self->{insert_id} = 0;
449 0         0 $self->{server_message} = '';
450 0         0 $self->{error_code} = undef;
451 0         0 $self->{selected_record} = undef;
452             }
453              
454              
455             sub _has_next_packet
456             {
457 0     0   0 my $self = shift;
458             #substr($_[0], -1) ne "\xfe";
459             #$self->_dump_packet(substr($_[0], -5));
460 0         0 return substr($_[0], -5, 1) ne "\xfe";
461             }
462              
463              
464             sub _dump_packet {
465 0     0   0 my $self = shift;
466 0         0 my $packet = shift;
467 0         0 my ($method_name) = (caller(1))[3];
468 0         0 my $str = sprintf "%s():\n", $method_name;
469 0         0 while ($packet =~ /(.{1,16})/sg) {
470 0         0 my $line = $1;
471 0         0 $str .= join ' ', map {sprintf '%02X', ord $_} split //, $line;
  0         0  
472 0         0 $str .= ' ' x (16 - length $line);
473 0         0 $str .= ' ';
474 0 0       0 $str .= join '', map {
475 0         0 sprintf '%s', (/[\w\d\*\,\?\%\=\'\;\(\)\.-]/) ? $_ : '.'
476             } split //, $line;
477 0         0 $str .= "\n";
478             }
479 0         0 print $str;
480             #warn $str;
481             }
482              
483              
484              
485             package Net::MySQL::RecordIterator;
486 2     2   22 use strict;
  2         4  
  2         144  
487              
488 2     2   10 use constant NULL_COLUMN => 251;
  2         19  
  2         212  
489 2     2   10 use constant UNSIGNED_CHAR_COLUMN => 251;
  2         3  
  2         170  
490 2     2   9 use constant UNSIGNED_SHORT_COLUMN => 252;
  2         5  
  2         85  
491 2     2   11 use constant UNSIGNED_INT24_COLUMN => 253;
  2         3  
  2         71  
492 2     2   10 use constant UNSIGNED_INT32_COLUMN => 254;
  2         23  
  2         75  
493 2     2   10 use constant UNSIGNED_CHAR_LENGTH => 1;
  2         3  
  2         90  
494 2     2   9 use constant UNSIGNED_SHORT_LENGTH => 2;
  2         3  
  2         120  
495 2     2   10 use constant UNSIGNED_INT24_LENGTH => 3;
  2         3  
  2         87  
496 2     2   47 use constant UNSIGNED_INT32_LENGTH => 4;
  2         12  
  2         80  
497 2     2   8 use constant UNSIGNED_INT32_PAD_LENGTH => 4;
  2         3  
  2         3313  
498              
499              
500             sub new
501             {
502 0     0   0 my $class = shift;
503 0         0 my $packet = shift;
504 0         0 bless {
505             packet => $packet,
506             position => 0,
507             column => [],
508             }, $class;
509             }
510              
511              
512             sub parse
513             {
514 0     0   0 my $self = shift;
515 0         0 $self->_get_column_length;
516 0         0 $self->_get_column_name;
517             }
518              
519              
520             sub each
521             {
522 0     0   0 my $self = shift;
523 0         0 my @result;
524 0 0       0 return undef if $self->is_end_of_packet;
525              
526 0         0 for (1..$self->{column_length}) {
527 0         0 push @result, $self->_get_string_and_seek_position;
528             }
529 0         0 $self->{position} += 4;
530              
531 0         0 return \@result;
532             }
533              
534              
535             sub is_end_of_packet
536             {
537 0     0   0 my $self = shift;
538 0         0 return substr($self->{packet}, $self->{position}, 1) eq "\xFE";
539             }
540              
541              
542             sub get_field_length
543             {
544 0     0   0 my $self = shift;
545 0         0 $self->{column_length};
546             }
547              
548              
549             sub get_field_names
550             {
551 0     0   0 my $self = shift;
552 0         0 map { $_->{column} } @{$self->{column}};
  0         0  
  0         0  
553             }
554              
555              
556             sub _get_column_length
557             {
558 0     0   0 my $self = shift;
559 0         0 $self->{position} += 4;
560 0         0 $self->{column_length} = ord substr $self->{packet}, $self->{position}, 1;
561 0         0 $self->{position} += 5;
562 0 0       0 printf "Column Length: %d\n", $self->{column_length}
563             if Net::MySQL->debug;
564             }
565              
566              
567             sub _get_column_name
568             {
569 0     0   0 my $self = shift;
570              
571 0         0 for my $i (1.. $self->{column_length}) {
572 0         0 $self->_get_string_and_seek_position;
573 0         0 $self->_get_string_and_seek_position;
574 0         0 my $table = $self->_get_string_and_seek_position;
575 0         0 $self->_get_string_and_seek_position;
576 0         0 my $column = $self->_get_string_and_seek_position;
577 0         0 $self->_get_string_and_seek_position;
578 0         0 push @{$self->{column}}, {
  0         0  
579             table => $table,
580             column => $column,
581             };
582 0         0 $self->_get_string_and_seek_position;
583 0         0 $self->{position} += 4;
584             }
585 0         0 $self->{position} += 9;
586 0         0 printf "Column name: '%s'\n",
587 0 0       0 join ", ", map { $_->{column} } @{$self->{column}}
  0         0  
588             if Net::MySQL->debug;
589             }
590              
591              
592             sub _get_string_and_seek_position
593             {
594 0     0   0 my $self = shift;
595              
596 0         0 my $length = $self->_get_field_length();
597              
598 0 0       0 return undef unless defined $length;
599              
600 0         0 my $string = substr $self->{packet}, $self->{position}, $length;
601 0         0 $self->{position} += $length;
602 0         0 return $string;
603             }
604              
605              
606             sub _get_field_length
607             {
608 0     0   0 my $self = shift;
609 0         0 return Net::MySQL::Util::get_field_length($self->{packet}, \$self->{position});
610             }
611              
612              
613             package Net::MySQL::Util;
614 2     2   15 use strict;
  2         4  
  2         66  
615              
616 2     2   10 use constant NULL_COLUMN => 251;
  2         3  
  2         100  
617 2     2   9 use constant UNSIGNED_CHAR_COLUMN => 251;
  2         3  
  2         74  
618 2     2   10 use constant UNSIGNED_SHORT_COLUMN => 252;
  2         4  
  2         76  
619 2     2   7 use constant UNSIGNED_INT24_COLUMN => 253;
  2         3  
  2         90  
620 2     2   10 use constant UNSIGNED_INT32_COLUMN => 254;
  2         9  
  2         260  
621 2     2   10 use constant UNSIGNED_CHAR_LENGTH => 1;
  2         3  
  2         288  
622 2     2   13 use constant UNSIGNED_SHORT_LENGTH => 2;
  2         2  
  2         88  
623 2     2   10 use constant UNSIGNED_INT24_LENGTH => 3;
  2         2  
  2         76  
624 2     2   9 use constant UNSIGNED_INT32_LENGTH => 4;
  2         4  
  2         83  
625 2     2   11 use constant UNSIGNED_INT32_PAD_LENGTH => 4;
  2         3  
  2         959  
626              
627              
628             sub get_field_length
629             {
630 0     0   0 my $packet = shift;
631 0         0 my $pos = shift;
632              
633 0         0 my $head = ord substr(
634             $packet,
635             $$pos,
636             UNSIGNED_CHAR_LENGTH
637             );
638 0         0 $$pos += UNSIGNED_CHAR_LENGTH;
639              
640 0 0       0 return undef if $head == NULL_COLUMN;
641 0 0       0 if ($head < UNSIGNED_CHAR_COLUMN) {
    0          
    0          
642 0         0 return $head;
643             }
644             elsif ($head == UNSIGNED_SHORT_COLUMN) {
645 0         0 my $length = unpack 'v', substr(
646             $packet,
647             $$pos,
648             UNSIGNED_SHORT_LENGTH
649             );
650 0         0 $$pos += UNSIGNED_SHORT_LENGTH;
651 0         0 return $length;
652             }
653             elsif ($head == UNSIGNED_INT24_COLUMN) {
654 0         0 my $int24 = substr(
655             $packet, $$pos,
656             UNSIGNED_INT24_LENGTH
657             );
658 0         0 my $length = unpack('C', substr($int24, 0, 1))
659             + (unpack('C', substr($int24, 1, 1)) << 8)
660             + (unpack('C', substr($int24, 2, 1)) << 16);
661 0         0 $$pos += UNSIGNED_INT24_LENGTH;
662 0         0 return $length;
663             }
664             else {
665 0         0 my $int32 = substr(
666             $packet, $$pos,
667             UNSIGNED_INT32_LENGTH
668             );
669 0         0 my $length = unpack('C', substr($int32, 0, 1))
670             + (unpack('C', substr($int32, 1, 1)) << 8)
671             + (unpack('C', substr($int32, 2, 1)) << 16)
672             + (unpack('C', substr($int32, 3, 1)) << 24);
673 0         0 $$pos += UNSIGNED_INT32_LENGTH;
674 0         0 $$pos += UNSIGNED_INT32_PAD_LENGTH;
675 0         0 return $length;
676             }
677             }
678              
679              
680              
681             package Net::MySQL::Password;
682 2     2   12 use strict;
  2         2  
  2         55  
683 2     2   2276 use Digest::SHA1;
  2         2334  
  2         940  
684              
685             sub scramble {
686 0     0   0 my $class = shift;
687 0         0 my $password = shift;
688 0         0 my $hash_seed = shift;
689 0 0       0 return '' unless $password;
690 0 0       0 return '' if length $password == 0;
691 0         0 return _make_scrambled_password($hash_seed, $password);
692             }
693              
694              
695             sub _make_scrambled_password {
696 0     0   0 my $message = shift;
697 0         0 my $password = shift;
698              
699 0         0 my $ctx = Digest::SHA1->new;
700 0         0 $ctx->reset;
701 0         0 $ctx->add($password);
702 0         0 my $stage1 = $ctx->digest;
703              
704 0         0 $ctx->reset;
705 0         0 $ctx->add($stage1);
706 0         0 my $stage2 = $ctx->digest;
707              
708 0         0 $ctx->reset;
709 0         0 $ctx->add($message);
710 0         0 $ctx->add($stage2);
711 0         0 my $result = $ctx->digest;
712 0         0 return _my_crypt($result, $stage1);
713             }
714              
715             sub _my_crypt {
716 0     0   0 my $s1 = shift;
717 0         0 my $s2 = shift;
718 0         0 my $l = length($s1) - 1;
719 0         0 my $result = '';
720 0         0 for my $i (0..$l) {
721 0         0 $result .= pack 'C', (unpack('C', substr($s1, $i, 1)) ^ unpack('C', substr($s2, $i, 1)));
722             }
723 0         0 return $result;
724             }
725              
726             package Net::MySQL::Password32;
727 2     2   14 use strict;
  2         4  
  2         1624  
728              
729             sub scramble
730             {
731 4     4   16 my $class = shift;
732 4         7 my $password = shift;
733 4         5 my $hash_seed = shift;
734 4         5 my $client_capabilities = shift;
735              
736 4 100       18 return '' unless $password;
737 2 50       6 return '' if length $password == 0;
738              
739 2         3 my $hsl = length $hash_seed;
740 2         3 my @out;
741 2         5 my @hash_pass = _get_hash($password);
742 2         3 my @hash_mess = _get_hash($hash_seed);
743              
744 2         3 my ($max_value, $seed, $seed2);
745 0         0 my ($dRes, $dSeed, $dMax);
746 2 100       6 if ($client_capabilities < 1) {
747 1         1 $max_value = 0x01FFFFFF;
748 1         3 $seed = _xor_by_long($hash_pass[0], $hash_mess[0]) % $max_value;
749 1         2 $seed2 = int($seed / 2);
750             } else {
751 1         1 $max_value= 0x3FFFFFFF;
752 1         3 $seed = _xor_by_long($hash_pass[0], $hash_mess[0]) % $max_value;
753 1         4 $seed2 = _xor_by_long($hash_pass[1], $hash_mess[1]) % $max_value;
754             }
755 2         3 $dMax = $max_value;
756              
757 2         6 for (my $i=0; $i < $hsl; $i++) {
758 16         16 $seed = int(($seed * 3 + $seed2) % $max_value);
759 16         15 $seed2 = int(($seed + $seed2 + 33) % $max_value);
760 16         15 $dSeed = $seed;
761 16         22 $dRes = $dSeed / $dMax;
762 16         35 push @out, int($dRes * 31) + 64;
763             }
764              
765 2 100       5 if ($client_capabilities == 1) {
766             # Make it harder to break
767 1         3 $seed = ($seed * 3 + $seed2 ) % $max_value;
768 1         2 $seed2 = ($seed + $seed2 + 33 ) % $max_value;
769 1         2 $dSeed = $seed;
770              
771 1         2 $dRes = $dSeed / $dMax;
772 1         2 my $e = int($dRes * 31);
773 1         4 for (my $i=0; $i < $hsl ; $i++) {
774 8         14 $out[$i] ^= $e;
775             }
776             }
777 2         5 return join '', map { chr $_ } @out;
  16         36  
778             }
779              
780              
781             sub _get_hash
782             {
783 4     4   5 my $password = shift;
784              
785 4         4 my $nr = 1345345333;
786 4         15 my $add = 7;
787 4         4 my $nr2 = 0x12345671;
788 4         5 my $tmp;
789 4         3 my $pwlen = length $password;
790 4         4 my $c;
791              
792 4         12 for (my $i=0; $i < $pwlen; $i++) {
793 36         45 my $c = substr $password, $i, 1;
794 36 50 33     123 next if $c eq ' ' || $c eq "\t";
795 36         36 my $tmp = ord $c;
796 36         97 my $value = ((_and_by_char($nr, 63) + $add) * $tmp) + $nr * 256;
797 36         46 $nr = _xor_by_long($nr, $value);
798 36         59 $nr2 += _xor_by_long(($nr2 * 256), $nr);
799 36         71 $add += $tmp;
800             }
801 4         6 return (_and_by_long($nr, 0x7fffffff), _and_by_long($nr2, 0x7fffffff));
802             }
803              
804              
805             sub _and_by_char
806             {
807 36     36   32 my $source = shift;
808 36         30 my $mask = shift;
809              
810 36         60 return $source & $mask;
811             }
812              
813              
814             sub _and_by_long
815             {
816 8     8   9 my $source = shift;
817 8   50     14 my $mask = shift || 0xFFFFFFFF;
818              
819 8         9 return _cut_off_to_long($source) & _cut_off_to_long($mask);
820             }
821              
822              
823             sub _xor_by_long
824             {
825 75     75   66 my $source = shift;
826 75   50     126 my $mask = shift || 0;
827              
828 75         86 return _cut_off_to_long($source) ^ _cut_off_to_long($mask);
829             }
830              
831              
832             sub _cut_off_to_long
833             {
834 166     166   134 my $source = shift;
835              
836 166 50       242 if ($] >= 5.006) {
837 166 100       236 $source = $source % (0xFFFFFFFF + 1) if $source > 0xFFFFFFFF;
838 166         251 return $source;
839             }
840 0           while ($source > 0xFFFFFFFF) {
841 0           $source -= 0xFFFFFFFF + 1;
842             }
843 0           return $source;
844             }
845              
846              
847             1;
848             __END__