using System.Net.Sockets;\r
using System.IO;\r
using ICSharpCode.SharpZipLib.Zip.Compression;\r
+using ICSharpCode.SharpZipLib.Zip.Compression.Streams;\r
+using System.Security.Cryptography;\r
+using ByteFX.Data.Common;\r
+using System.Collections;\r
+using System.Text;\r
\r
-namespace ByteFX.Data.MySQLClient\r
+namespace ByteFX.Data.MySqlClient\r
{\r
/// <summary>\r
/// Summary description for Driver.\r
/// </summary>\r
- internal class Driver : IDisposable\r
+ internal class Driver\r
{\r
- protected const int COMPRESS_HEADER_LEN = 3;\r
protected const int HEADER_LEN = 4;\r
- protected const int MIN_COMPRESS_LEN = 50;\r
-\r
- public MemoryStream _packet;\r
- protected Stream _stream;\r
- protected Socket _socket;\r
- protected int m_Seq;\r
- protected int m_BufIndex;\r
- protected byte m_LastResult;\r
- protected byte[] m_Buffer;\r
- protected int m_Timeout;\r
- protected int _port;\r
-\r
- int m_Protocol;\r
- String m_ServerVersion;\r
- int m_ThreadID;\r
- String m_EncryptionSeed;\r
- int m_ServerCaps;\r
- bool m_UseCompression = false;\r
-\r
-\r
- public Driver(int ConnectionTimeout)\r
- {\r
- m_Seq = -1;\r
- m_LastResult = 0xff;\r
- m_Timeout = ConnectionTimeout;\r
- m_BufIndex = 0;\r
+ protected const int MIN_COMPRESS_LENGTH = 50;\r
+ protected const int MAX_PACKET_SIZE = 256*256*256-1;\r
\r
- ResetPacket();\r
- }\r
+ protected Stream stream;\r
+ protected BufferedStream writer;\r
+ protected Encoding encoding;\r
+ protected byte packetSeq;\r
+ protected long maxPacketSize;\r
+ protected DBVersion serverVersion;\r
+ protected bool isOpen;\r
+ protected string versionString;\r
+ protected Packet peekedPacket;\r
+\r
+ protected int protocol;\r
+ protected uint threadID;\r
+ protected String encryptionSeed;\r
+ protected int serverCaps;\r
+ protected bool useCompression = false;\r
\r
- ~Driver() \r
- {\r
- }\r
\r
- public byte LastResult \r
+ public Driver()\r
{\r
- get { return m_LastResult; }\r
+ packetSeq = 0;\r
+ encoding = System.Text.Encoding.Default;\r
+ isOpen = false;\r
}\r
\r
- public string ServerVersion \r
+ public Encoding Encoding \r
{\r
- get { return m_ServerVersion; }\r
+ get { return encoding; }\r
+ set { encoding = value; }\r
}\r
\r
- public void Dispose() \r
+ public long MaxPacketSize \r
{\r
+ get { return maxPacketSize; }\r
+ set { maxPacketSize = value; }\r
}\r
\r
-#if WINDOWS\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="host"></param>\r
- void CreatePipeStream( string host ) \r
+ public string VersionString \r
{\r
- string _pipename;\r
- if (host.ToLower().Equals("localhost"))\r
- _pipename = @"\\.\pipe\MySQL";\r
- else\r
- _pipename = String.Format(@"\\{0}\pipe\MySQL", host);\r
-\r
- _stream = new ByteFX.Data.Common.NamedPipeStream(_pipename, FileAccess.ReadWrite);\r
+ get { return versionString; }\r
}\r
-#endif\r
\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="host"></param>\r
- /// <param name="port"></param>\r
- void CreateSocketStream( string host, int port ) \r
+ public DBVersion Version \r
{\r
- _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
- IPHostEntry he = Dns.GetHostByName(host);\r
- IPEndPoint _serverAddr = new IPEndPoint(he.AddressList[0], port);\r
-\r
- _socket.Connect(_serverAddr);\r
- _stream = new NetworkStream(_socket);\r
+ get { return serverVersion; }\r
}\r
\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="host"></param>\r
- /// <param name="port"></param>\r
- /// <param name="userid"></param>\r
- /// <param name="password"></param>\r
- public void Open( String host, int port, String userid, String password, bool UseCompression ) \r
+ public void Open( MySqlConnectionString settings )\r
{\r
- _port = port;\r
-#if WINDOWS\r
- if (-1 == port) \r
+ // connect to one of our specified hosts\r
+ try \r
{\r
- CreatePipeStream(host);\r
+ StreamCreator sc = new StreamCreator( settings.Server, settings.Port, settings.PipeName );\r
+ stream = sc.GetStream( settings.ConnectionTimeout );\r
}\r
-#endif\r
- \r
- if (-1 != port)\r
+ catch (Exception ex)\r
{\r
- CreateSocketStream(host, port);\r
+ throw new MySqlException("Unable to connect to any of the specified MySQL hosts", ex);\r
}\r
\r
- ReadPacket();\r
+ if (stream == null) \r
+ throw new MySqlException("Unable to connect to any of the specified MySQL hosts");\r
\r
- // read off the protocol version\r
- m_Protocol = _packet.ReadByte();\r
- m_ServerVersion = ReadString();\r
- m_ThreadID = ReadInteger(4);\r
- m_EncryptionSeed = ReadString();\r
+ writer = new BufferedStream( stream );\r
+ // read off the welcome packet and parse out it's values\r
+ Packet packet = ReadPacket();\r
+ protocol = packet.ReadByte();\r
+ versionString = packet.ReadString();\r
+ serverVersion = DBVersion.Parse( versionString );\r
+ threadID = (uint)packet.ReadInteger(4);\r
+ encryptionSeed = packet.ReadString();\r
\r
// read in Server capabilities if they are provided\r
- m_ServerCaps = 0;\r
- if (_packet.CanRead)\r
- m_ServerCaps = ReadInteger(2);\r
+ serverCaps = 0;\r
+ if (packet.HasMoreData)\r
+ serverCaps = (int)packet.ReadInteger(2);\r
+\r
+ Authenticate( settings.UserId, settings.Password, settings.UseCompression );\r
+\r
+ // if we are using compression, then we use our CompressedStream class\r
+ // to hide the ugliness of managing the compression\r
+ if (settings.UseCompression)\r
+ {\r
+ stream = new CompressedStream( stream );\r
+ writer = new BufferedStream( stream );\r
+ }\r
\r
- Authenticate( userid, password, UseCompression );\r
+ isOpen = true;\r
+ }\r
+\r
+ private Packet CreatePacket( byte[] buf )\r
+ {\r
+ if (buf == null)\r
+ return new Packet( serverVersion.isAtLeast(3, 22, 5) );\r
+ return new Packet( buf, serverVersion.isAtLeast(3, 22, 5 ));\r
}\r
\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="userid"></param>\r
- /// <param name="password"></param>\r
private void Authenticate( String userid, String password, bool UseCompression )\r
{\r
ClientParam clientParam = ClientParam.CLIENT_FOUND_ROWS | ClientParam.CLIENT_LONG_FLAG;\r
\r
- if ((m_ServerCaps & (int)ClientParam.CLIENT_COMPRESS) != 0 && UseCompression)\r
+ if ((serverCaps & (int)ClientParam.CLIENT_COMPRESS) != 0 && UseCompression)\r
{\r
clientParam |= ClientParam.CLIENT_COMPRESS;\r
}\r
\r
clientParam |= ClientParam.CLIENT_LONG_PASSWORD;\r
+ clientParam |= ClientParam.CLIENT_LOCAL_FILES;\r
+// if (serverVersion.isAtLeast(4,1,0))\r
+// clientParam |= ClientParam.CLIENT_PROTOCOL_41;\r
+// if ( (serverCaps & (int)ClientParam.CLIENT_SECURE_CONNECTION ) != 0 && password.Length > 0 )\r
+// clientParam |= ClientParam.CLIENT_SECURE_CONNECTION;\r
\r
- password = EncryptPassword(password, m_EncryptionSeed, m_Protocol > 9);\r
- // header_length = 4\r
- //int headerLength = (userid.Length + 16) + 6 + 4; // Passwords can be 16 chars long\r
-\r
- ResetPacket();\r
- WriteInteger( (int)clientParam, 2 );\r
- WriteInteger( 0, 3 ); \r
- WriteString( userid );\r
- WriteString( password );\r
- WritePacket();\r
-\r
- CheckResult();\r
-\r
- if ((clientParam & ClientParam.CLIENT_COMPRESS) != 0)\r
- m_UseCompression = true;\r
- }\r
-\r
- public void ResetPacket()\r
- {\r
- _packet = new MemoryStream();\r
- _packet.SetLength(0);\r
-\r
- // hack for Mono 0.17 not handling length < position on MemoryStream\r
- _packet.Position = 0;\r
- WriteInteger(0, HEADER_LEN);\r
+ int packetLength = userid.Length + 16 + 6 + 4; // Passwords can be 16 chars long\r
\r
- if (m_UseCompression)\r
- _packet.Position += (COMPRESS_HEADER_LEN+HEADER_LEN);\r
- }\r
+ Packet packet = CreatePacket(null);\r
\r
- protected bool CanReadStream()\r
- {\r
-#if WINDOWS\r
- if (_port == -1)\r
+ if ((clientParam & ClientParam.CLIENT_PROTOCOL_41) != 0)\r
{\r
- return (_stream as ByteFX.Data.Common.NamedPipeStream).DataAvailable;\r
+ packet.WriteInteger( (int)clientParam, 4 );\r
+ packet.WriteInteger( (256*256*256)-1, 4 );\r
}\r
-#endif\r
- return (_stream as NetworkStream).DataAvailable;\r
- }\r
-\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <returns></returns>\r
- public byte ReadStreamByte()\r
- {\r
- long start = DateTime.Now.Ticks;\r
- long timeout = m_Timeout * TimeSpan.TicksPerSecond;\r
-\r
- while ((DateTime.Now.Ticks - start) < timeout)\r
+ else\r
{\r
- if (CanReadStream()) return (byte)_stream.ReadByte();\r
+ packet.WriteInteger( (int)clientParam, 2 );\r
+ packet.WriteInteger( 255*255*255, 3 );\r
}\r
- throw new MySQLException("Timeout waiting for response from server");\r
- }\r
\r
- /// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="buf"></param>\r
- /// <param name="offset"></param>\r
- /// <param name="count"></param>\r
- protected void ReadStreamBytes(byte[] buf, int offset, int count)\r
- {\r
- long start = DateTime.Now.Ticks;\r
- long timeout = m_Timeout * TimeSpan.TicksPerSecond;\r
- long curoffset = offset;\r
-\r
- while (count > 0 && ((DateTime.Now.Ticks - start) < timeout))\r
+ packet.WriteString( userid, encoding );\r
+ if ( (clientParam & ClientParam.CLIENT_SECURE_CONNECTION ) != 0 )\r
{\r
- if (CanReadStream()) \r
- {\r
- int cnt = _stream.Read(buf, (int)curoffset, count);\r
- count -= cnt;\r
- curoffset += cnt;\r
- }\r
+ // use the new authentication system\r
+ AuthenticateSecurely( packet, password );\r
}\r
- if (count > 0)\r
- throw new MySQLException("Timeout waiting for response from server");\r
+ else\r
+ {\r
+ // use old authentication system\r
+ packet.WriteString( EncryptPassword(password, encryptionSeed, protocol > 9), encoding );\r
+ // pad zeros out to packetLength for auth\r
+ for (int i=0; i < (packetLength-packet.Length); i++)\r
+ packet.WriteByte(0);\r
+ SendPacket(packet);\r
+ }\r
+\r
+ packet = ReadPacket();\r
+ if ((clientParam & ClientParam.CLIENT_COMPRESS) != 0)\r
+ useCompression = true;\r
}\r
\r
/// <summary>\r
- /// \r
+ /// AuthenticateSecurity implements the new 4.1 authentication scheme\r
/// </summary>\r
- private void ReadServerDataBlock()\r
+ /// <param name="packet">The in-progress packet we use to complete the authentication</param>\r
+ /// <param name="password">The password of the user to use</param>\r
+ private void AuthenticateSecurely( Packet packet, string password )\r
{\r
- int b0 = (int)ReadStreamByte();\r
- int b1 = (int)ReadStreamByte();\r
- int b2 = (int)ReadStreamByte();\r
+ packet.WriteString("xxxxxxxx", encoding );\r
+ SendPacket(packet);\r
\r
- if (b0 == -1 && b1 == -1 && b2 == -1) \r
- {\r
- //TODO: close?\r
- throw new IOException("Unexpected end of input stream");\r
- }\r
+ packet = ReadPacket();\r
\r
- int packetLength = (int)(b0+ (256*b1) + (256*256*b2));\r
- int comp_len = 0;\r
- byte Seq = (byte)ReadStreamByte();\r
- \r
- // handle the stupid field swapping does if compression is used\r
- // If the block is compressed, then the first length field is the compressed\r
- // length and the second is the uncompressed.\r
- // If the block is uncompressed, even if compression is selected, the first\r
- // length field is the uncompressed size and the second field is zero\r
- if (m_UseCompression) \r
- {\r
- int c0 = (int)ReadStreamByte();\r
- int c1 = (int)ReadStreamByte();\r
- int c2 = (int)ReadStreamByte();\r
- comp_len = (int)(c0 + (256*c1) + (256*256*c2));\r
- if (comp_len > 0) \r
- {\r
- int temp = packetLength;\r
- packetLength = comp_len;\r
- comp_len = temp;\r
- }\r
- }\r
+ // compute pass1 hash\r
+ string newPass = password.Replace(" ","").Replace("\t","");\r
+ SHA1 sha = new SHA1CryptoServiceProvider(); \r
+ byte[] firstPassBytes = sha.ComputeHash( System.Text.Encoding.Default.GetBytes(newPass));\r
\r
- if (m_UseCompression && comp_len > 0) \r
- {\r
- m_Buffer = new Byte[packetLength];\r
- byte[] comp = new Byte[comp_len];\r
- // read in the compressed data\r
- ReadStreamBytes(comp, 0, comp_len);\r
+ byte[] salt = packet.GetBuffer();\r
+ byte[] input = new byte[ firstPassBytes.Length + 4 ];\r
+ salt.CopyTo( input, 0 );\r
+ firstPassBytes.CopyTo( input, 4 );\r
+ byte[] outPass = new byte[100];\r
+ byte[] secondPassBytes = sha.ComputeHash( input );\r
\r
- Inflater i = new Inflater();\r
- i.SetInput( comp );\r
+ byte[] cryptSalt = new byte[20];\r
+ Security.ArrayCrypt( salt, 4, cryptSalt, 0, secondPassBytes, 20 );\r
\r
- i.Inflate(m_Buffer);\r
- return;\r
- }\r
+ Security.ArrayCrypt( cryptSalt, 0, firstPassBytes, 0, firstPassBytes, 20 );\r
\r
- if (!m_UseCompression) \r
- {\r
- m_Buffer = new Byte[packetLength+4];\r
- ReadStreamBytes(m_Buffer, 4, packetLength);\r
- m_Buffer[0] = (byte)b0; m_Buffer[1] = (byte)b1; m_Buffer[2] = (byte)b2;\r
- m_Buffer[3] = (byte)Seq;\r
- }\r
- else \r
- {\r
- m_Buffer = new Byte[packetLength];\r
- ReadStreamBytes(m_Buffer, 0, packetLength);\r
- }\r
+ // send the packet\r
+ packet = CreatePacket(null);\r
+ packet.Write( firstPassBytes, 0, 20 );\r
+ SendPacket(packet);\r
}\r
\r
+\r
/// <summary>\r
/// \r
/// </summary>\r
/// <returns></returns>\r
- public int ReadPacket()\r
+ public Packet PeekPacket()\r
{\r
- if (_packet == null || m_Buffer == null || m_BufIndex == m_Buffer.Length)\r
- {\r
- ReadServerDataBlock();\r
- m_BufIndex = 0;\r
- }\r
+ if (peekedPacket != null)\r
+ return peekedPacket;\r
\r
- _packet = new MemoryStream(m_Buffer, m_BufIndex, m_Buffer.Length - m_BufIndex);\r
- int len = ReadInteger(3);\r
- int seq = (int)ReadByte();\r
- _packet.SetLength(len+HEADER_LEN);\r
- m_BufIndex += (int)_packet.Length;\r
-\r
- // if the sequence doesn't match up, then there must be some orphaned\r
- // packets so we just read them off\r
- if (seq != (m_Seq+1)) return ReadPacket();\r
- \r
- m_Seq = seq;\r
- return len;\r
-}\r
+ peekedPacket = ReadPacket();\r
+ return peekedPacket;\r
+ }\r
\r
/// <summary>\r
- /// \r
+ /// ReadBuffer continuously loops until it has read the entire\r
+ /// requested data\r
/// </summary>\r
- /// <returns></returns>\r
- protected int CompressPacket()\r
+ /// <param name="buf">Buffer to read data into</param>\r
+ /// <param name="offset">Offset to place the data</param>\r
+ /// <param name="length">Number of bytes to read</param>\r
+ private void ReadBuffer( byte[] buf, int offset, int length )\r
{\r
- // compress the entire packet except the length\r
-\r
- // make sure we are using a packet prep'ed for compression\r
- // and that our packet is large enough to warrant compression\r
- // re: my_compress.c from mysql src\r
- int offset = HEADER_LEN + COMPRESS_HEADER_LEN;\r
- int original_len = (int)(_packet.Length - offset);\r
- if (original_len < MIN_COMPRESS_LEN) return 0;\r
-\r
- byte[] packetData = _packet.ToArray();\r
-\r
- byte[] output = new Byte[ original_len * 2 ];\r
- Deflater d = new Deflater();\r
- d.SetInput( packetData, offset, original_len );\r
- d.Finish();\r
- int comp_len = d.Deflate( output, offset, output.Length - offset );\r
-\r
- if (comp_len > original_len) return 0;\r
- _packet = new MemoryStream( output, 0, comp_len + offset );\r
- return (int)comp_len;\r
+ while (length > 0)\r
+ {\r
+ int amountRead = stream.Read( buf, offset, length );\r
+ if (amountRead == 0)\r
+ throw new MySqlException("Unexpected end of data encountered");\r
+ length -= amountRead;\r
+ offset += amountRead;\r
+ }\r
+ }\r
+\r
+ private Packet ReadPacketFromServer()\r
+ {\r
+ int len = stream.ReadByte() + (stream.ReadByte() << 8) +\r
+ (stream.ReadByte() << 16);\r
+ byte seq = (byte)stream.ReadByte();\r
+ byte[] buf = new byte[ len ];\r
+ ReadBuffer( buf, 0, len );\r
+\r
+ if (seq != packetSeq) \r
+ throw new MySqlException("Unknown transmission status: sequence out of order");\r
+ packetSeq++;\r
+\r
+ Packet p = CreatePacket(buf);\r
+ p.Encoding = this.Encoding;\r
+ if (p.Length == MAX_PACKET_SIZE && serverVersion.isAtLeast(4,0,0)) \r
+ p.Append( ReadPacketFromServer() );\r
+ return p;\r
}\r
\r
/// <summary>\r
- /// \r
+ /// Reads a single packet off the stream\r
/// </summary>\r
- /// <param name="useCompressionIfAvail"></param>\r
- protected void WritePacket()\r
+ /// <returns></returns>\r
+ public Packet ReadPacket()\r
{\r
- if (m_UseCompression)\r
+ // if we have peeked at a packet, then return it\r
+ if (peekedPacket != null)\r
{\r
- // store the length of the buffer we are going to compress\r
- long num_bytes = _packet.Length - (HEADER_LEN*2) - COMPRESS_HEADER_LEN;\r
- _packet.Position = HEADER_LEN + COMPRESS_HEADER_LEN;\r
- WriteInteger( (int) num_bytes, 3 );\r
- _packet.WriteByte(0); // internal packet has 0 as seq if compressing\r
+ Packet packet = peekedPacket;\r
+ peekedPacket = null;\r
+ return packet;\r
+ }\r
\r
- // now compress it\r
- int compressed_size = CompressPacket();\r
+ Packet p = ReadPacketFromServer();\r
\r
- _packet.Position = 0;\r
- if (compressed_size == 0) \r
- {\r
- WriteInteger( (int)num_bytes + HEADER_LEN, 3);\r
- _packet.WriteByte((byte)++m_Seq);\r
- WriteInteger( compressed_size, 3 );\r
- }\r
- else \r
- {\r
- WriteInteger( compressed_size, 3 );\r
- _packet.WriteByte((byte)++m_Seq);\r
- WriteInteger( (int)num_bytes + HEADER_LEN, 3);\r
- }\r
- }\r
- else \r
+ // if this is an error packet, then throw the exception\r
+ if (p[0] == 0xff)\r
{\r
- _packet.Position = 0;\r
- WriteInteger( (int)(_packet.Length - HEADER_LEN), 3 );\r
- _packet.WriteByte((byte)++m_Seq);\r
+ p.ReadByte();\r
+ int errorCode = (int)p.ReadInteger(2);\r
+ string msg = p.ReadString();\r
+ throw new MySqlException( msg, errorCode );\r
}\r
-\r
- _stream.Write( _packet.ToArray(), 0, (int)_packet.Length );\r
- _stream.Flush();\r
-\r
- // reset the writeStream to empty\r
- ResetPacket();\r
+ \r
+ return p;\r
}\r
\r
-\r
- protected void WriteString(string v)\r
+ protected MemoryStream CompressBuffer(byte[] buf, int index, int length)\r
{\r
- WriteStringNoNull(v);\r
- _packet.WriteByte(0);\r
- }\r
\r
- protected void WriteStringNoNull(string v)\r
- {\r
- byte[] bytes = System.Text.Encoding.ASCII.GetBytes(v);\r
- _packet.Write(bytes, 0, bytes.Length);\r
- }\r
+ if (length < MIN_COMPRESS_LENGTH) return null;\r
\r
- public void Close() \r
- {\r
- m_Seq = -1;\r
- _stream.Close();\r
- if (_socket != null)\r
- _socket.Close();\r
- }\r
+ MemoryStream ms = new MemoryStream(buf.Length);\r
+ DeflaterOutputStream dos = new DeflaterOutputStream(ms);\r
\r
- public string ReadString()\r
- {\r
- String str = new String('c',0);\r
+ dos.WriteByte( (byte)(length & 0xff ));\r
+ dos.WriteByte( (byte)((length >> 8) & 0xff ));\r
+ dos.WriteByte( (byte)((length >> 16) & 0xff ));\r
+ dos.WriteByte( 0 );\r
\r
- while (_packet.Position < _packet.Length) \r
- {\r
- byte b = (byte)_packet.ReadByte();\r
- if (b == 0) break;\r
- str += Convert.ToChar(b);\r
- }\r
- return str;\r
+ dos.Write( buf, index, length );\r
+ dos.Finish();\r
+ if (ms.Length > length+4) return null;\r
+ return ms;\r
}\r
\r
- protected void WriteInteger( int v, int numbytes )\r
+ private void WriteInteger( int v, int numbytes )\r
{\r
int val = v;\r
\r
if (numbytes < 1 || numbytes > 4) \r
- throw new Exception("Wrong byte count for WriteInteger");\r
+ throw new ArgumentOutOfRangeException("Wrong byte count for WriteInteger");\r
\r
for (int x=0; x < numbytes; x++)\r
{\r
- _packet.WriteByte( (byte)(val&0xff) );\r
+ writer.WriteByte( (byte)(val&0xff) );\r
val >>= 8;\r
}\r
}\r
\r
/// <summary>\r
- /// \r
- /// </summary>\r
- /// <param name="numbytes"></param>\r
- /// <returns></returns>\r
- public int ReadInteger(int numbytes)\r
- {\r
- int val = 0;\r
- int raise = 1;\r
- for (int x=0; x < numbytes; x++)\r
- {\r
- int b = (int)_packet.ReadByte();\r
- val += (b*raise);\r
- raise *= 256;\r
- }\r
- return val;\r
- }\r
-\r
- /// <summary>\r
- /// \r
+ /// Send a buffer to the server in a compressed form\r
/// </summary>\r
- /// <returns></returns>\r
- public int ReadLength()\r
- {\r
- byte c = (byte)_packet.ReadByte();\r
- switch(c) \r
+ /// <param name="buf">Byte buffer to send</param>\r
+ /// <param name="index">Location in buffer to start sending</param>\r
+ /// <param name="length">Amount of data to send</param>\r
+ protected void SendCompressedBuffer(byte[] buf, int index, int length)\r
+ {\r
+ MemoryStream compressed_bytes = CompressBuffer(buf, index, length);\r
+ int comp_len = compressed_bytes == null ? length+HEADER_LEN : (int)compressed_bytes.Length;\r
+ int ucomp_len = compressed_bytes == null ? 0 : length+HEADER_LEN;\r
+\r
+ WriteInteger( comp_len, 3 );\r
+ writer.WriteByte( packetSeq++ );\r
+ WriteInteger( ucomp_len, 3 );\r
+ if (compressed_bytes != null)\r
+ writer.Write( compressed_bytes.GetBuffer(), 0, (int)compressed_bytes.Length );\r
+ else \r
{\r
- case 251 : return (int) 0; \r
- case 252 : return ReadInteger(2);\r
- case 253 : return ReadInteger(3);\r
- case 254 : return ReadInteger(4);\r
- default : return (int) c;\r
+ WriteInteger( length, 3 ); \r
+ writer.WriteByte( 0 );\r
+ writer.Write( buf, index, length );\r
}\r
+ stream.Flush();\r
}\r
\r
- public byte ReadByte()\r
- {\r
- return (byte)_packet.ReadByte();\r
- }\r
-\r
- public int ReadNBytes()\r
- {\r
- byte c = (byte)_packet.ReadByte();\r
- if (c < 1 || c > 4) throw new MySQLException("Unexpected byte count received");\r
- return ReadInteger((int)c);\r
- }\r
-\r
- public string ReadLenString()\r
+ protected void SendBuffer( byte[] buf, int offset, int length )\r
{\r
- int len = ReadLength();\r
-\r
- byte[] buf = new Byte[len];\r
- _packet.Read(buf, 0, len);\r
-\r
- String s = new String('c', 0);\r
- for (int x=0; x < buf.Length; x++)\r
- s += Convert.ToChar(buf[x]);\r
- return s;\r
- }\r
-\r
-\r
- void CheckResult()\r
- {\r
- ReadPacket();\r
-\r
- m_LastResult = (byte)_packet.ReadByte();\r
-\r
- if (0xff == m_LastResult) \r
+ while (length > 0)\r
{\r
- int errno = ReadInteger(2);\r
- string msg = ReadString();\r
- throw new MySQLException(msg, errno);\r
+ int amount = Math.Min( 1024, length );\r
+ writer.Write( buf, offset, amount );\r
+ writer.Flush();\r
+ offset += amount;\r
+ length -= amount;\r
}\r
}\r
\r
/// <summary>\r
- /// \r
+ /// Send a single packet to the server.\r
/// </summary>\r
- /// <returns></returns>\r
- public bool IsLastPacketSignal() \r
- {\r
- byte b = (byte)_packet.ReadByte();\r
- _packet.Position--;\r
+ /// <param name="packet">Packet to send to the server</param>\r
+ /// <remarks>This method will send a single packet to the server\r
+ /// possibly breaking the packet up into smaller packets that are\r
+ /// smaller than max_allowed_packet. This method will always send at\r
+ /// least one packet to the server</remarks>\r
+ protected void SendPacket(Packet packet)\r
+ {\r
+ byte[] buf = packet.GetBuffer();\r
+ int len = packet.Length;\r
+ int index = 0;\r
+ bool oneSent = false;\r
+\r
+ // make sure we are not trying to send too much\r
+ if (packet.Length > maxPacketSize && maxPacketSize > 0)\r
+ throw new MySqlException("Packet size too large. This MySQL server cannot accept rows larger than " + maxPacketSize + " bytes.");\r
\r
- if ((_packet.Length - HEADER_LEN) == 1 && b == 0xfe)\r
+ try \r
{\r
- return true;\r
+ while (len > 0 || ! oneSent) \r
+ {\r
+ int lenToSend = Math.Min( len, MAX_PACKET_SIZE );\r
+\r
+ // send the data\r
+ if (useCompression)\r
+ SendCompressedBuffer( buf, index, lenToSend );\r
+ else \r
+ {\r
+ WriteInteger( lenToSend, 3 );\r
+ writer.WriteByte( packetSeq++ );\r
+ writer.Write( buf, index, lenToSend );\r
+ writer.Flush();\r
+ }\r
+\r
+ len -= lenToSend;\r
+ index += lenToSend;\r
+ oneSent = true;\r
+ }\r
+ writer.Flush();\r
}\r
-\r
- return false;\r
- }\r
-\r
- /// <summary>\r
- /// Read the byte data from the server for the next column\r
- /// </summary>\r
- /// <returns></returns>\r
- public byte[] ReadColumnData()\r
- {\r
- int len;\r
-\r
- byte c = (byte)_packet.ReadByte(); \r
-\r
- switch (c)\r
+ catch (Exception ex)\r
{\r
- case 251: return null; //new byte[1] { c }; \r
- case 252: len = ReadInteger(2); break;\r
- case 253: len = ReadInteger(3); break;\r
- case 254: len = ReadInteger(4); break;\r
- default: len = c; break;\r
+ Console.WriteLine( ex.Message );\r
}\r
+ }\r
+\r
\r
- byte[] buf = new Byte[len];\r
- _packet.Read(buf, 0, len);\r
- return buf;\r
+ public void Close() \r
+ {\r
+ if (stream != null)\r
+ stream.Close();\r
}\r
\r
\r
/// <param name="command">Command to execute</param>\r
/// <param name="text">Text attribute of command</param>\r
/// <returns>Result packet returned from database server</returns>\r
- public void SendCommand( DBCmd command, String text ) \r
+ public void Send( DBCmd command, String text ) \r
{\r
- m_Seq = -1;\r
- ResetPacket();\r
+ CommandResult result = Send( command, this.Encoding.GetBytes( text ) );\r
+ if (result.IsResultSet)\r
+ throw new MySqlException("SendCommand failed for command " + text );\r
+ }\r
\r
- _packet.WriteByte( (byte)command );\r
+ public CommandResult Send( DBCmd cmd, byte[] bytes )\r
+ {\r
+// string s = Encoding.GetString( bytes );\r
\r
- if (text != null && text.Length > 0)\r
- WriteStringNoNull(text);\r
+ Packet packet = CreatePacket(null);\r
+ packetSeq = 0;\r
+ packet.WriteByte( (byte)cmd );\r
+ if (bytes != null)\r
+ packet.Write( bytes, 0, bytes.Length );\r
\r
- try \r
- {\r
- WritePacket();\r
+ SendPacket( packet );\r
+ packet = ReadPacket();\r
\r
- if (command != DBCmd.QUIT)\r
- CheckResult();\r
- }\r
- catch (Exception e) \r
+ // first check to see if this is a LOAD DATA LOCAL callback\r
+ // if so, send the file and then read the results\r
+ long fieldcount = packet.ReadLenInteger();\r
+ if (fieldcount == Packet.NULL_LEN)\r
{\r
- throw e;\r
+ string filename = packet.ReadString();\r
+ SendFileToServer( filename );\r
+ packet = ReadPacket();\r
}\r
+ else\r
+ packet.Position = 0;\r
+\r
+ return new CommandResult(packet, this);\r
}\r
\r
- public void SendQuery( byte[] sql )\r
+ /// <summary>\r
+ /// Sends the specified file to the server. \r
+ /// This supports the LOAD DATA LOCAL INFILE\r
+ /// </summary>\r
+ /// <param name="filename"></param>\r
+ private void SendFileToServer( string filename )\r
{\r
+ Packet p = CreatePacket(null);\r
+ byte[] buffer = new byte[4092];\r
+ FileStream fs = null;\r
try \r
{\r
- m_Seq = -1;\r
- ResetPacket();\r
-\r
- _packet.WriteByte( (byte)DBCmd.QUERY );\r
- _packet.Write( sql, 0, sql.Length );\r
+ fs = new FileStream( filename, FileMode.Open );\r
+ int count = fs.Read( buffer, 0, buffer.Length );\r
+ while (count != 0) \r
+ {\r
+ if ((p.Length + count) > MAX_PACKET_SIZE)\r
+ {\r
+ SendPacket( p );\r
+ p.Clear();\r
+ }\r
+ p.Write( buffer, 0, count );\r
+ count = fs.Read( buffer, 0, buffer.Length );\r
+ }\r
+ fs.Close();\r
\r
- WritePacket();\r
- CheckResult();\r
+ // send any remaining data\r
+ if (p.Length > 0) \r
+ {\r
+ SendPacket(p);\r
+ p.Clear();\r
+ }\r
}\r
- catch (Exception e) \r
+ catch (Exception ex)\r
{\r
- throw e;\r
+ throw new MySqlException("Error during LOAD DATA LOCAL INFILE", ex);\r
+ }\r
+ finally \r
+ {\r
+ if (fs != null)\r
+ fs.Close();\r
+ // empty packet signals end of file\r
+ p.Clear();\r
+ SendPacket(p);\r
}\r
}\r
\r
}\r
\r
/// <summary>\r
- /// \r
+ /// Encrypts a password using the MySql encryption scheme\r
/// </summary>\r
- /// <param name="password"></param>\r
- /// <param name="seed"></param>\r
+ /// <param name="password">The password to encrypt</param>\r
+ /// <param name="message">The encryption seed the server gave us</param>\r
+ /// <param name="new_ver">Indicates if we should use the old or new encryption scheme</param>\r
/// <returns></returns>\r
public static String EncryptPassword(String password, String message, bool new_ver)\r
{\r