/* +----------------------------------------------------------------------+ | Swoole | +----------------------------------------------------------------------+ | Copyright (c) 2012-2015 The Swoole Group | +----------------------------------------------------------------------+ | This source file is subject to version 2.0 of the Apache license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.apache.org/licenses/LICENSE-2.0.html | | If you did not receive a copy of the Apache2.0 license and are unable| | to obtain it through the world-wide-web, please send a note to | | license@swoole.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Tianfeng Han | +----------------------------------------------------------------------+ */ #include "php_swoole.h" #include "swoole_mysql.h" #include #include #ifdef SW_USE_MYSQLND #include "ext/mysqlnd/mysqlnd.h" #include "ext/mysqlnd/mysqlnd_charset.h" #endif #ifdef SW_MYSQL_RSA_SUPPORT #include #include #endif static PHP_METHOD(swoole_mysql, __construct); static PHP_METHOD(swoole_mysql, __destruct); static PHP_METHOD(swoole_mysql, connect); #ifdef SW_USE_MYSQLND static PHP_METHOD(swoole_mysql, escape); #endif static PHP_METHOD(swoole_mysql, query); static PHP_METHOD(swoole_mysql, begin); static PHP_METHOD(swoole_mysql, commit); static PHP_METHOD(swoole_mysql, rollback); static PHP_METHOD(swoole_mysql, getState); static PHP_METHOD(swoole_mysql, close); static PHP_METHOD(swoole_mysql, on); static zend_class_entry swoole_mysql_ce; static zend_class_entry *swoole_mysql_class_entry_ptr; static zend_class_entry swoole_mysql_exception_ce; static zend_class_entry *swoole_mysql_exception_class_entry_ptr; #define UTF8_MB4 "utf8mb4" #define UTF8_MB3 "utf8" typedef struct _mysql_charset { unsigned int nr; const char *name; const char *collation; } mysql_charset; static const mysql_charset swoole_mysql_charsets[] = { { 1, "big5", "big5_chinese_ci" }, { 3, "dec8", "dec8_swedish_ci" }, { 4, "cp850", "cp850_general_ci" }, { 6, "hp8", "hp8_english_ci" }, { 7, "koi8r", "koi8r_general_ci" }, { 8, "latin1", "latin1_swedish_ci" }, { 5, "latin1", "latin1_german1_ci" }, { 9, "latin2", "latin2_general_ci" }, { 2, "latin2", "latin2_czech_cs" }, { 10, "swe7", "swe7_swedish_ci" }, { 11, "ascii", "ascii_general_ci" }, { 12, "ujis", "ujis_japanese_ci" }, { 13, "sjis", "sjis_japanese_ci" }, { 16, "hebrew", "hebrew_general_ci" }, { 17, "filename", "filename" }, { 18, "tis620", "tis620_thai_ci" }, { 19, "euckr", "euckr_korean_ci" }, { 21, "latin2", "latin2_hungarian_ci" }, { 27, "latin2", "latin2_croatian_ci" }, { 22, "koi8u", "koi8u_general_ci" }, { 24, "gb2312", "gb2312_chinese_ci" }, { 25, "greek", "greek_general_ci" }, { 26, "cp1250", "cp1250_general_ci" }, { 28, "gbk", "gbk_chinese_ci" }, { 30, "latin5", "latin5_turkish_ci" }, { 31, "latin1", "latin1_german2_ci" }, { 15, "latin1", "latin1_danish_ci" }, { 32, "armscii8", "armscii8_general_ci" }, { 33, UTF8_MB3, UTF8_MB3"_general_ci" }, { 35, "ucs2", "ucs2_general_ci" }, { 36, "cp866", "cp866_general_ci" }, { 37, "keybcs2", "keybcs2_general_ci" }, { 38, "macce", "macce_general_ci" }, { 39, "macroman", "macroman_general_ci" }, { 40, "cp852", "cp852_general_ci" }, { 41, "latin7", "latin7_general_ci" }, { 20, "latin7", "latin7_estonian_cs" }, { 57, "cp1256", "cp1256_general_ci" }, { 59, "cp1257", "cp1257_general_ci" }, { 63, "binary", "binary" }, { 97, "eucjpms", "eucjpms_japanese_ci" }, { 29, "cp1257", "cp1257_lithuanian_ci" }, { 31, "latin1", "latin1_german2_ci" }, { 34, "cp1250", "cp1250_czech_cs" }, { 42, "latin7", "latin7_general_cs" }, { 43, "macce", "macce_bin" }, { 44, "cp1250", "cp1250_croatian_ci" }, { 45, UTF8_MB4, UTF8_MB4"_general_ci" }, { 46, UTF8_MB4, UTF8_MB4"_bin" }, { 47, "latin1", "latin1_bin" }, { 48, "latin1", "latin1_general_ci" }, { 49, "latin1", "latin1_general_cs" }, { 51, "cp1251", "cp1251_general_ci" }, { 14, "cp1251", "cp1251_bulgarian_ci" }, { 23, "cp1251", "cp1251_ukrainian_ci" }, { 50, "cp1251", "cp1251_bin" }, { 52, "cp1251", "cp1251_general_cs" }, { 53, "macroman", "macroman_bin" }, { 54, "utf16", "utf16_general_ci" }, { 55, "utf16", "utf16_bin" }, { 56, "utf16le", "utf16le_general_ci" }, { 58, "cp1257", "cp1257_bin" }, { 60, "utf32", "utf32_general_ci" }, { 61, "utf32", "utf32_bin" }, { 62, "utf16le", "utf16le_bin" }, { 64, "armscii8", "armscii8_bin" }, { 65, "ascii", "ascii_bin" }, { 66, "cp1250", "cp1250_bin" }, { 67, "cp1256", "cp1256_bin" }, { 68, "cp866", "cp866_bin" }, { 69, "dec8", "dec8_bin" }, { 70, "greek", "greek_bin" }, { 71, "hebrew", "hebrew_bin" }, { 72, "hp8", "hp8_bin" }, { 73, "keybcs2", "keybcs2_bin" }, { 74, "koi8r", "koi8r_bin" }, { 75, "koi8u", "koi8u_bin" }, { 77, "latin2", "latin2_bin" }, { 78, "latin5", "latin5_bin" }, { 79, "latin7", "latin7_bin" }, { 80, "cp850", "cp850_bin" }, { 81, "cp852", "cp852_bin" }, { 82, "swe7", "swe7_bin" }, { 83, UTF8_MB3, UTF8_MB3"_bin" }, { 84, "big5", "big5_bin" }, { 85, "euckr", "euckr_bin" }, { 86, "gb2312", "gb2312_bin" }, { 87, "gbk", "gbk_bin" }, { 88, "sjis", "sjis_bin" }, { 89, "tis620", "tis620_bin" }, { 90, "ucs2", "ucs2_bin" }, { 91, "ujis", "ujis_bin" }, { 92, "geostd8", "geostd8_general_ci" }, { 93, "geostd8", "geostd8_bin" }, { 94, "latin1", "latin1_spanish_ci" }, { 95, "cp932", "cp932_japanese_ci" }, { 96, "cp932", "cp932_bin" }, { 97, "eucjpms", "eucjpms_japanese_ci" }, { 98, "eucjpms", "eucjpms_bin" }, { 99, "cp1250", "cp1250_polish_ci" }, { 128, "ucs2", "ucs2_unicode_ci" }, { 129, "ucs2", "ucs2_icelandic_ci" }, { 130, "ucs2", "ucs2_latvian_ci" }, { 131, "ucs2", "ucs2_romanian_ci" }, { 132, "ucs2", "ucs2_slovenian_ci" }, { 133, "ucs2", "ucs2_polish_ci" }, { 134, "ucs2", "ucs2_estonian_ci" }, { 135, "ucs2", "ucs2_spanish_ci" }, { 136, "ucs2", "ucs2_swedish_ci" }, { 137, "ucs2", "ucs2_turkish_ci" }, { 138, "ucs2", "ucs2_czech_ci" }, { 139, "ucs2", "ucs2_danish_ci" }, { 140, "ucs2", "ucs2_lithuanian_ci" }, { 141, "ucs2", "ucs2_slovak_ci" }, { 142, "ucs2", "ucs2_spanish2_ci" }, { 143, "ucs2", "ucs2_roman_ci" }, { 144, "ucs2", "ucs2_persian_ci" }, { 145, "ucs2", "ucs2_esperanto_ci" }, { 146, "ucs2", "ucs2_hungarian_ci" }, { 147, "ucs2", "ucs2_sinhala_ci" }, { 148, "ucs2", "ucs2_german2_ci" }, { 149, "ucs2", "ucs2_croatian_ci" }, { 150, "ucs2", "ucs2_unicode_520_ci" }, { 151, "ucs2", "ucs2_vietnamese_ci" }, { 160, "utf32", "utf32_unicode_ci" }, { 161, "utf32", "utf32_icelandic_ci" }, { 162, "utf32", "utf32_latvian_ci" }, { 163, "utf32", "utf32_romanian_ci" }, { 164, "utf32", "utf32_slovenian_ci" }, { 165, "utf32", "utf32_polish_ci" }, { 166, "utf32", "utf32_estonian_ci" }, { 167, "utf32", "utf32_spanish_ci" }, { 168, "utf32", "utf32_swedish_ci" }, { 169, "utf32", "utf32_turkish_ci" }, { 170, "utf32", "utf32_czech_ci" }, { 171, "utf32", "utf32_danish_ci" }, { 172, "utf32", "utf32_lithuanian_ci" }, { 173, "utf32", "utf32_slovak_ci" }, { 174, "utf32", "utf32_spanish2_ci" }, { 175, "utf32", "utf32_roman_ci" }, { 176, "utf32", "utf32_persian_ci" }, { 177, "utf32", "utf32_esperanto_ci" }, { 178, "utf32", "utf32_hungarian_ci" }, { 179, "utf32", "utf32_sinhala_ci" }, { 180, "utf32", "utf32_german2_ci" }, { 181, "utf32", "utf32_croatian_ci" }, { 182, "utf32", "utf32_unicode_520_ci" }, { 183, "utf32", "utf32_vietnamese_ci" }, { 192, UTF8_MB3, UTF8_MB3"_unicode_ci" }, { 193, UTF8_MB3, UTF8_MB3"_icelandic_ci" }, { 194, UTF8_MB3, UTF8_MB3"_latvian_ci" }, { 195, UTF8_MB3, UTF8_MB3"_romanian_ci" }, { 196, UTF8_MB3, UTF8_MB3"_slovenian_ci" }, { 197, UTF8_MB3, UTF8_MB3"_polish_ci" }, { 198, UTF8_MB3, UTF8_MB3"_estonian_ci" }, { 199, UTF8_MB3, UTF8_MB3"_spanish_ci" }, { 200, UTF8_MB3, UTF8_MB3"_swedish_ci" }, { 201, UTF8_MB3, UTF8_MB3"_turkish_ci" }, { 202, UTF8_MB3, UTF8_MB3"_czech_ci" }, { 203, UTF8_MB3, UTF8_MB3"_danish_ci" }, { 204, UTF8_MB3, UTF8_MB3"_lithuanian_ci" }, { 205, UTF8_MB3, UTF8_MB3"_slovak_ci" }, { 206, UTF8_MB3, UTF8_MB3"_spanish2_ci" }, { 207, UTF8_MB3, UTF8_MB3"_roman_ci" }, { 208, UTF8_MB3, UTF8_MB3"_persian_ci" }, { 209, UTF8_MB3, UTF8_MB3"_esperanto_ci" }, { 210, UTF8_MB3, UTF8_MB3"_hungarian_ci" }, { 211, UTF8_MB3, UTF8_MB3"_sinhala_ci" }, { 212, UTF8_MB3, UTF8_MB3"_german2_ci" }, { 213, UTF8_MB3, UTF8_MB3"_croatian_ci" }, { 214, UTF8_MB3, UTF8_MB3"_unicode_520_ci" }, { 215, UTF8_MB3, UTF8_MB3"_vietnamese_ci" }, { 224, UTF8_MB4, UTF8_MB4"_unicode_ci" }, { 225, UTF8_MB4, UTF8_MB4"_icelandic_ci" }, { 226, UTF8_MB4, UTF8_MB4"_latvian_ci" }, { 227, UTF8_MB4, UTF8_MB4"_romanian_ci" }, { 228, UTF8_MB4, UTF8_MB4"_slovenian_ci" }, { 229, UTF8_MB4, UTF8_MB4"_polish_ci" }, { 230, UTF8_MB4, UTF8_MB4"_estonian_ci" }, { 231, UTF8_MB4, UTF8_MB4"_spanish_ci" }, { 232, UTF8_MB4, UTF8_MB4"_swedish_ci" }, { 233, UTF8_MB4, UTF8_MB4"_turkish_ci" }, { 234, UTF8_MB4, UTF8_MB4"_czech_ci" }, { 235, UTF8_MB4, UTF8_MB4"_danish_ci" }, { 236, UTF8_MB4, UTF8_MB4"_lithuanian_ci" }, { 237, UTF8_MB4, UTF8_MB4"_slovak_ci" }, { 238, UTF8_MB4, UTF8_MB4"_spanish2_ci" }, { 239, UTF8_MB4, UTF8_MB4"_roman_ci" }, { 240, UTF8_MB4, UTF8_MB4"_persian_ci" }, { 241, UTF8_MB4, UTF8_MB4"_esperanto_ci" }, { 242, UTF8_MB4, UTF8_MB4"_hungarian_ci" }, { 243, UTF8_MB4, UTF8_MB4"_sinhala_ci" }, { 244, UTF8_MB4, UTF8_MB4"_german2_ci" }, { 245, UTF8_MB4, UTF8_MB4"_croatian_ci" }, { 246, UTF8_MB4, UTF8_MB4"_unicode_520_ci" }, { 247, UTF8_MB4, UTF8_MB4"_vietnamese_ci" }, { 248, "gb18030", "gb18030_chinese_ci" }, { 249, "gb18030", "gb18030_bin" }, { 254, UTF8_MB3, UTF8_MB3"_general_cs" }, { 0, NULL, NULL}, }; ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_on, 0, 0, 2) ZEND_ARG_INFO(0, event_name) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_connect, 0, 0, 2) ZEND_ARG_ARRAY_INFO(0, server_config, 0) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_begin, 0, 0, 1) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_commit, 0, 0, 1) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_rollback, 0, 0, 1) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() #ifdef SW_USE_MYSQLND ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_escape, 0, 0, 1) ZEND_ARG_INFO(0, string) ZEND_ARG_INFO(0, flags) ZEND_END_ARG_INFO() #endif ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_mysql_query, 0, 0, 2) ZEND_ARG_INFO(0, sql) ZEND_ARG_INFO(0, callback) ZEND_END_ARG_INFO() static const zend_function_entry swoole_mysql_methods[] = { PHP_ME(swoole_mysql, __construct, arginfo_swoole_void, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) PHP_ME(swoole_mysql, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC | ZEND_ACC_DTOR) PHP_ME(swoole_mysql, connect, arginfo_swoole_mysql_connect, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, begin, arginfo_swoole_mysql_begin, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, commit, arginfo_swoole_mysql_commit, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, rollback, arginfo_swoole_mysql_rollback, ZEND_ACC_PUBLIC) #ifdef SW_USE_MYSQLND PHP_ME(swoole_mysql, escape, arginfo_swoole_mysql_escape, ZEND_ACC_PUBLIC) #endif PHP_ME(swoole_mysql, query, arginfo_swoole_mysql_query, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, close, arginfo_swoole_void, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, getState, arginfo_swoole_void, ZEND_ACC_PUBLIC) PHP_ME(swoole_mysql, on, arginfo_swoole_mysql_on, ZEND_ACC_PUBLIC) PHP_FE_END }; static void mysql_client_free(mysql_client *client, zval* zobject); static void mysql_columns_free(mysql_client *client); static void mysql_client_free(mysql_client *client, zval* zobject) { if (client->cli->timer) { swTimer_del(&SwooleG.timer, client->cli->timer); client->cli->timer = NULL; } //close the connection client->cli->close(client->cli); //release client object memory swClient_free(client->cli); efree(client->cli); client->cli = NULL; client->connected = 0; } static void mysql_columns_free(mysql_client *client) { int i; for (i = 0; i < client->response.num_column; i++) { if (client->response.columns[i].buffer) { efree(client->response.columns[i].buffer); client->response.columns[i].buffer = NULL; } } efree(client->response.columns); } #ifdef SW_MYSQL_DEBUG static void mysql_client_info(mysql_client *client); static void mysql_column_info(mysql_field *field); #endif static void swoole_mysql_onTimeout(swTimer *timer, swTimer_node *tnode); static int swoole_mysql_onRead(swReactor *reactor, swEvent *event); static int swoole_mysql_onWrite(swReactor *reactor, swEvent *event); static int swoole_mysql_onError(swReactor *reactor, swEvent *event); static void swoole_mysql_onConnect(mysql_client *client TSRMLS_DC); swString *mysql_request_buffer = NULL; void swoole_mysql_init(int module_number TSRMLS_DC) { SWOOLE_INIT_CLASS_ENTRY(swoole_mysql_ce, "swoole_mysql", "Swoole\\MySQL", swoole_mysql_methods); swoole_mysql_class_entry_ptr = zend_register_internal_class(&swoole_mysql_ce TSRMLS_CC); SWOOLE_CLASS_ALIAS(swoole_mysql, "Swoole\\MySQL"); SWOOLE_INIT_CLASS_ENTRY(swoole_mysql_exception_ce, "swoole_mysql_exception", "Swoole\\MySQL\\Exception", NULL); swoole_mysql_exception_class_entry_ptr = sw_zend_register_internal_class_ex(&swoole_mysql_exception_ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); SWOOLE_CLASS_ALIAS(swoole_mysql_exception, "Swoole\\MySQL\\Exception"); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("serverInfo"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("sock"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_bool(swoole_mysql_class_entry_ptr, ZEND_STRL("connected"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_long(swoole_mysql_class_entry_ptr, ZEND_STRL("errno"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_long(swoole_mysql_class_entry_ptr, ZEND_STRL("connect_errno"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("error"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("connect_error"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("insert_id"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("affected_rows"), ZEND_ACC_PUBLIC TSRMLS_CC); /** * event callback */ zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("onConnect"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_property_null(swoole_mysql_class_entry_ptr, ZEND_STRL("onClose"), ZEND_ACC_PUBLIC TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_QUERY")-1, SW_MYSQL_STATE_QUERY TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_READ_START")-1, SW_MYSQL_STATE_READ_START TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_READ_FIELD ")-1, SW_MYSQL_STATE_READ_FIELD TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_READ_ROW")-1, SW_MYSQL_STATE_READ_ROW TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_READ_END")-1, SW_MYSQL_STATE_READ_END TSRMLS_CC); zend_declare_class_constant_long(swoole_mysql_class_entry_ptr, SW_STRL("STATE_CLOSED")-1, SW_MYSQL_STATE_CLOSED TSRMLS_CC); } int mysql_request(swString *sql, swString *buffer) { bzero(buffer->str, 5); //length mysql_pack_length(sql->length + 1, buffer->str); //command buffer->str[4] = SW_MYSQL_COM_QUERY; buffer->length = 5; return swString_append(buffer, sql); } int mysql_prepare(swString *sql, swString *buffer) { bzero(buffer->str, 5); //length mysql_pack_length(sql->length + 1, buffer->str); //command buffer->str[4] = SW_MYSQL_COM_STMT_PREPARE; buffer->length = 5; return swString_append(buffer, sql); } int mysql_get_charset(char *name) { const mysql_charset *c = swoole_mysql_charsets; while (c[0].nr != 0) { if (!strcasecmp(c->name, name)) { return c->nr; } ++c; } return -1; } int mysql_get_result(mysql_connector *connector, char *buf, int len) { char *tmp = buf; int packet_length = mysql_uint3korr(tmp); if (len < packet_length + 4) { return 0; } //int packet_number = tmp[3]; tmp += 4; uint8_t opcode = *tmp; tmp += 1; //ERROR Packet if (opcode == 0xff) { connector->error_code = *(uint16_t *) tmp; connector->error_msg = tmp + 2; connector->error_length = packet_length - 3; return -1; } else { return 1; } } static void php_swoole_sha256(const char *str, int _len, unsigned char *digest) { PHP_SHA256_CTX context; PHP_SHA256Init(&context); PHP_SHA256Update(&context, (unsigned char *) str, _len); PHP_SHA256Final(digest, &context); } //sha256 static void mysql_sha2_password_with_nonce(char* ret, char* nonce, char* password, zend_size_t password_len) { // XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) char hashed[32], double_hashed[32]; php_swoole_sha256(password, password_len, (unsigned char *) hashed); php_swoole_sha256(hashed, 32, (unsigned char *) double_hashed); char combined[32 + SW_MYSQL_NONCE_LENGTH]; //double-hashed + nonce memcpy(combined, double_hashed, 32); memcpy(combined + 32, nonce, SW_MYSQL_NONCE_LENGTH); char xor_bytes[32]; php_swoole_sha256(combined, 32 + SW_MYSQL_NONCE_LENGTH, (unsigned char *) xor_bytes); int i; for (i = 0; i < 32; i++) { hashed[i] ^= xor_bytes[i]; } memcpy(ret, hashed, 32); } /** * Return: password length */ static int mysql_auth_encrypt_dispatch(char *buf, char *auth_plugin_name, char *password, zend_size_t password_len, char* nonce, int *next_state) { if (strcasecmp("mysql_native_password", auth_plugin_name) == 0) { // if not native, skip password and will trigger the auth switch // auth-response char hash_0[20]; bzero(hash_0, sizeof (hash_0)); php_swoole_sha1(password, password_len, (uchar *) hash_0); char hash_1[20]; bzero(hash_1, sizeof (hash_1)); php_swoole_sha1(hash_0, sizeof (hash_0), (uchar *) hash_1); char str[40]; memcpy(str, nonce, 20); memcpy(str + 20, hash_1, 20); char hash_2[20]; php_swoole_sha1(str, sizeof (str), (uchar *) hash_2); char hash_3[20]; int *a = (int *) hash_2; int *b = (int *) hash_0; int *c = (int *) hash_3; int i; for (i = 0; i < 5; i++) { c[i] = a[i] ^ b[i]; } memcpy(buf, hash_3, 20); return 20; } else if (strcasecmp("caching_sha2_password", auth_plugin_name) == 0) { char hashed[32]; mysql_sha2_password_with_nonce( (char *) hashed, (char *) nonce, password, password_len ); // copy hashed data to connector buf memcpy(buf, (char *) hashed, 32); *next_state = SW_MYSQL_HANDSHAKE_WAIT_SIGNATURE; return 32; } else { // unknown swWarn("Unknown auth plugin: %s", auth_plugin_name); return 0; } } /** 1 [0a] protocol version string[NUL] server version 4 connection id string[8] auth-plugin-data-part-1 1 [00] filler 2 capability flags (lower 2 bytes) if more data in the packet: 1 character set 2 status flags 2 capability flags (upper 2 bytes) if capabilities & CLIENT_PLUGIN_AUTH { 1 length of auth-plugin-data } else { 1 [00] } string[10] reserved (all [00]) if capabilities & CLIENT_SECURE_CONNECTION { string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8)) if capabilities & CLIENT_PLUGIN_AUTH { string[NUL] auth-plugin name } */ int mysql_handshake(mysql_connector *connector, char *buf, int len) { char *tmp = buf; int next_state = SW_MYSQL_HANDSHAKE_WAIT_RESULT; // ret is the next handshake state /** * handshake request */ mysql_handshake_request request; bzero(&request, sizeof(request)); request.packet_length = mysql_uint3korr(tmp); //continue to wait for data if (len < request.packet_length + 4) { return 0; } request.packet_number = tmp[3]; tmp += 4; request.protocol_version = *tmp; tmp += 1; //ERROR Packet if (request.protocol_version == 0xff) { connector->error_code = *(uint16_t *) tmp; connector->error_msg = tmp + 2; connector->error_length = request.packet_length - 3; return -1; } //1 [0a] protocol version request.server_version = tmp; tmp += (strlen(request.server_version) + 1); //4 connection id request.connection_id = *((int *) tmp); tmp += 4; //string[8] auth-plugin-data-part-1 memcpy(request.auth_plugin_data, tmp, 8); tmp += 8; //1 [00] filler request.filler = *tmp; tmp += 1; //2 capability flags (lower 2 bytes) memcpy(((char *) (&request.capability_flags)), tmp, 2); tmp += 2; if (tmp - tmp < len) { //1 character set request.character_set = *tmp; tmp += 1; //2 status flags memcpy(&request.status_flags, tmp, 2); tmp += 2; //2 capability flags (upper 2 bytes) memcpy(((char *) (&request.capability_flags) + 2), tmp, 2); tmp += 2; request.l_auth_plugin_data = *tmp; tmp += 1; memcpy(&request.reserved, tmp, sizeof(request.reserved)); tmp += sizeof(request.reserved); if (request.capability_flags & SW_MYSQL_CLIENT_SECURE_CONNECTION) { int len = MAX(13, request.l_auth_plugin_data - 8); memcpy(request.auth_plugin_data + 8, tmp, len); #ifdef SW_MYSQL_RSA_SUPPORT memcpy(connector->auth_plugin_data, request.auth_plugin_data, SW_MYSQL_NONCE_LENGTH); #endif tmp += len; } if (request.capability_flags & SW_MYSQL_CLIENT_PLUGIN_AUTH) { request.auth_plugin_name = tmp; request.l_auth_plugin_name = MIN(strlen(tmp), len - (tmp - buf)); swTraceLog(SW_TRACE_MYSQL_CLIENT, "use %s auth plugin", request.auth_plugin_name); } } int value; tmp = connector->buf + 4; //capability flags, CLIENT_PROTOCOL_41 always set value = SW_MYSQL_CLIENT_LONG_PASSWORD | SW_MYSQL_CLIENT_PROTOCOL_41 | SW_MYSQL_CLIENT_SECURE_CONNECTION | SW_MYSQL_CLIENT_CONNECT_WITH_DB | SW_MYSQL_CLIENT_PLUGIN_AUTH | SW_MYSQL_CLIENT_MULTI_RESULTS; memcpy(tmp, &value, sizeof(value)); tmp += 4; //max-packet size value = 300; memcpy(tmp, &value, sizeof(value)); tmp += 4; //use the server character_set when the character_set is not set. if (connector->character_set == 0) { connector->character_set = request.character_set; } //character set *tmp = connector->character_set; tmp += 1; //string[23] reserved (all [0]) tmp += 23; //string[NUL] username memcpy(tmp, connector->user, connector->user_len); tmp[connector->user_len] = '\0'; tmp += (connector->user_len + 1); if (connector->password_len > 0) { int length = 0; length = mysql_auth_encrypt_dispatch( tmp + 1, request.auth_plugin_name, connector->password, connector->password_len, request.auth_plugin_data, &next_state ); *tmp = length; tmp += length + 1; } else { *tmp = 0; tmp++; } //string[NUL] database memcpy(tmp, connector->database, connector->database_len); tmp[connector->database_len] = '\0'; tmp += (connector->database_len + 1); //string[NUL] auth plugin name memcpy(tmp, request.auth_plugin_name, request.l_auth_plugin_name); tmp[request.l_auth_plugin_name] = '\0'; tmp += (request.l_auth_plugin_name + 1); connector->packet_length = tmp - connector->buf - 4; mysql_pack_length(connector->packet_length, connector->buf); connector->buf[3] = 1; return next_state; } // we may need it one day but now // we can reply the every auth plugin requirement on the first handshake int mysql_auth_switch(mysql_connector *connector, char *buf, int len) { char *tmp = buf; if ((uint8_t) tmp[4] != 0xfe) { // out of the order package return SW_ERR; } int next_state = SW_MYSQL_HANDSHAKE_WAIT_RESULT; int packet_length = mysql_uint3korr(tmp); //continue to wait for data if (len < packet_length + 4) { return SW_AGAIN; } int packet_number = tmp[3]; tmp += 4; // type tmp += 1; // clear connector->packet_length = 0; memset(connector->buf, 0, 512); // string[NUL] plugin name char auth_plugin_name[32]; int auth_plugin_name_len = 0; int i; for (i = 0; i < packet_length; i++) { auth_plugin_name[auth_plugin_name_len] = tmp[auth_plugin_name_len]; auth_plugin_name_len++; if (tmp[auth_plugin_name_len] == 0x00) { break; } } auth_plugin_name[auth_plugin_name_len] = '\0'; swTraceLog(SW_TRACE_MYSQL_CLIENT, "auth switch plugin name=%s", auth_plugin_name); tmp += auth_plugin_name_len + 1; // name + 0x00 // if auth switch is triggered, password can't be empty // string auth plugin data char auth_plugin_data[20]; memcpy((char *)auth_plugin_data, tmp, 20); // create auth switch response package connector->packet_length += mysql_auth_encrypt_dispatch( (char *) (connector->buf + 4), auth_plugin_name, connector->password, connector->password_len, auth_plugin_data, &next_state ); // 3 for package length mysql_pack_length(connector->packet_length, connector->buf); // 1 package num connector->buf[3] = packet_number + 1; return next_state; } int mysql_parse_auth_signature(swString *buffer, mysql_connector *connector) { char *tmp = buffer->str; int packet_length = mysql_uint3korr(tmp); //continue to wait for data if (buffer->length < packet_length + 4) { return SW_AGAIN; } int packet_number = tmp[3]; tmp += 4; // signature if ((uint8_t) tmp[0] != SW_MYSQL_AUTH_SIGNATURE) { return SW_MYSQL_AUTH_SIGNATURE_ERROR; } // remaining length buffer->offset = 4 + packet_length; swTraceLog(SW_TRACE_MYSQL_CLIENT, "before signature remaining=%d", buffer->length - buffer->offset); if ((uint8_t)tmp[1] == SW_MYSQL_AUTH_SIGNATURE_FULL_AUTH_REQUIRED) { // create RSA prepared response connector->packet_length = 1; memset(connector->buf, 0, 512); // 3 for package length mysql_pack_length(connector->packet_length, connector->buf); // 1 packet number connector->buf[3] = packet_number + 1; // as I am OK connector->buf[4] = SW_MYSQL_AUTH_SIGNATURE_RSA_PREPARED; } // signature value return tmp[1]; } #ifdef SW_MYSQL_RSA_SUPPORT // Caching sha2 authentication. Public key request and send encrypted password // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse int mysql_parse_rsa(mysql_connector *connector, char *buf, int len) { // clear connector->packet_length = 0; memset(connector->buf, 0, 512); char *tmp = buf; int packet_length = mysql_uint3korr(tmp); //continue to wait for data if (len < packet_length + 4) { return SW_AGAIN; } int packet_number = tmp[3]; tmp += 4; int rsa_public_key_length = packet_length; while (tmp[0] != 0x2d) { tmp++; // ltrim rsa_public_key_length--; } char rsa_public_key[rsa_public_key_length + 1]; //rsa + '\0' memcpy((char *)rsa_public_key, tmp, rsa_public_key_length); rsa_public_key[rsa_public_key_length] = '\0'; swTraceLog(SW_TRACE_MYSQL_CLIENT, "rsa-length=%d;\nrsa-key=[%.*s]", rsa_public_key_length, rsa_public_key_length, rsa_public_key); int password_len = connector->password_len + 1; unsigned char password[password_len]; // copy to stack memcpy((char *)password, connector->password, password_len); // add NUL terminator to password password[password_len - 1] = '\0'; // XOR the password bytes with the challenge int i; for (i = 0; i < password_len; i++) { password[i] ^= connector->auth_plugin_data[i % SW_MYSQL_NONCE_LENGTH]; } // prepare RSA public key BIO *bio = NULL; RSA *public_rsa = NULL; if (unlikely((bio = BIO_new_mem_buf((void *)rsa_public_key, -1)) == NULL)) { swError("BIO_new_mem_buf publicKey error!"); return SW_ERR; } // PEM_read_bio_RSA_PUBKEY ERR_clear_error(); if (unlikely((public_rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL)) == NULL)) { ERR_load_crypto_strings(); char err_buf[512]; ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); swError("[PEM_read_bio_RSA_PUBKEY ERROR]: %s", err_buf); return SW_ERR; } BIO_free_all(bio); // encrypt with RSA public key int rsa_len = RSA_size(public_rsa); unsigned char encrypt_msg[rsa_len]; // RSA_public_encrypt ERR_clear_error(); int flen = rsa_len - 42; flen = password_len > flen ? flen : password_len; swDebug("rsa_len=%d", rsa_len); if (unlikely(RSA_public_encrypt(flen, (const unsigned char *)password, (unsigned char *)encrypt_msg, public_rsa, RSA_PKCS1_OAEP_PADDING) < 0)) { ERR_load_crypto_strings(); char err_buf[512]; ERR_error_string_n(ERR_get_error(), err_buf, sizeof(err_buf)); swError("[RSA_public_encrypt ERROR]: %s", err_buf); return SW_ERR; } RSA_free(public_rsa); memcpy((char *)connector->buf + 4, (char *)encrypt_msg, rsa_len); // copy rsa to buf connector->packet_length = rsa_len; // 3 for package length mysql_pack_length(connector->packet_length, connector->buf); // 1 packet number connector->buf[3] = packet_number + 1; return SW_OK; } #endif static int mysql_parse_prepare_result(mysql_client *client, char *buf, size_t n_buf) { if (n_buf < 11) { return SW_ERR; } mysql_statement *stmt = emalloc(sizeof(mysql_statement)); stmt->id = mysql_uint4korr(buf); buf += 4; stmt->field_count = mysql_uint2korr(buf); buf += 2; stmt->unreaded_param_count = stmt->param_count = mysql_uint2korr(buf); buf += 2; //skip 1 byte buf += 1; stmt->warning_count = mysql_uint2korr(buf); stmt->result = NULL; stmt->buffer = NULL; client->statement = stmt; stmt->client = client; swTraceLog(SW_TRACE_MYSQL_CLIENT, "id=%d, field_count=%d, param_count=%d, warning_count=%d.", stmt->id, stmt->field_count, stmt->param_count, stmt->warning_count); return 11; } static int mysql_decode_row(mysql_client *client, char *buf, int packet_len) { int read_n = 0, i; int tmp_len; ulong_t len; char nul; mysql_row row; char value_buffer[32]; bzero(&row, sizeof(row)); char *error; //unused //char mem; zval *result_array = client->response.result_array; zval *row_array = NULL; SW_ALLOC_INIT_ZVAL(row_array); array_init(row_array); swTraceLog(SW_TRACE_MYSQL_CLIENT, "mysql_decode_row begin, num_column=%d, packet_len=%d.", client->response.num_column, packet_len); for (i = 0; i < client->response.num_column; i++) { tmp_len = mysql_length_coded_binary(&buf[read_n], &len, &nul, packet_len - read_n); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } read_n += tmp_len; if (read_n + len > packet_len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } swTraceLog(SW_TRACE_MYSQL_CLIENT, "n=%d, fname=%s, name_length=%d", i, client->response.columns[i].name, client->response.columns[i].name_length); if (nul == 1) { add_assoc_null(row_array, client->response.columns[i].name); continue; } int type = client->response.columns[i].type; swTraceLog(SW_TRACE_MYSQL_CLIENT, "value: name=%s, type=%d, value=%s, len=%ld", client->response.columns[i].name, type, swoole_strndup(buf + read_n, len), len); switch (type) { case SW_MYSQL_TYPE_NULL: add_assoc_null(row_array, client->response.columns[i].name); break; /* String */ case SW_MYSQL_TYPE_TINY_BLOB: case SW_MYSQL_TYPE_MEDIUM_BLOB: case SW_MYSQL_TYPE_LONG_BLOB: case SW_MYSQL_TYPE_BLOB: case SW_MYSQL_TYPE_DECIMAL: case SW_MYSQL_TYPE_NEWDECIMAL: case SW_MYSQL_TYPE_BIT: case SW_MYSQL_TYPE_STRING: case SW_MYSQL_TYPE_VAR_STRING: case SW_MYSQL_TYPE_VARCHAR: case SW_MYSQL_TYPE_NEWDATE: /* Date Time */ case SW_MYSQL_TYPE_TIME: case SW_MYSQL_TYPE_YEAR: case SW_MYSQL_TYPE_TIMESTAMP: case SW_MYSQL_TYPE_DATETIME: case SW_MYSQL_TYPE_DATE: case SW_MYSQL_TYPE_JSON: sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); break; /* Integer */ case SW_MYSQL_TYPE_TINY: case SW_MYSQL_TYPE_SHORT: case SW_MYSQL_TYPE_INT24: case SW_MYSQL_TYPE_LONG: if(client->connector.strict_type) { memcpy(value_buffer, buf + read_n, len); value_buffer[len] = 0; row.sint = strtol(value_buffer, &error, 10); if (*error != '\0') { return -SW_MYSQL_ERR_CONVLONG; } add_assoc_long(row_array, client->response.columns[i].name, row.sint); } else { sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); } break; case SW_MYSQL_TYPE_LONGLONG: if(client->connector.strict_type) { memcpy(value_buffer, buf + read_n, len); value_buffer[len] = 0; row.sbigint = strtoll(value_buffer, &error, 10); if (*error != '\0') { return -SW_MYSQL_ERR_CONVLONG; } add_assoc_long(row_array, client->response.columns[i].name, row.sbigint); } else { sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); } break; case SW_MYSQL_TYPE_FLOAT: if(client->connector.strict_type) { memcpy(value_buffer, buf + read_n, len); value_buffer[len] = 0; row.mfloat = strtof(value_buffer, &error); if (*error != '\0') { return -SW_MYSQL_ERR_CONVFLOAT; } add_assoc_double(row_array, client->response.columns[i].name, row.mfloat); } else { sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); } break; case SW_MYSQL_TYPE_DOUBLE: if(client->connector.strict_type) { memcpy(value_buffer, buf + read_n, len); value_buffer[len] = 0; row.mdouble = strtod(value_buffer, &error); if (*error != '\0') { return -SW_MYSQL_ERR_CONVDOUBLE; } add_assoc_double(row_array, client->response.columns[i].name, row.mdouble); } else { sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); } break; default: swWarn("unknown field type[%d].", type); return -1; } read_n += len; } add_next_index_zval(result_array, row_array); #if PHP_MAJOR_VERSION > 5 if (row_array) { efree(row_array); } #endif return read_n; } #define DATETIME_MAX_SIZE 20 static int mysql_decode_datetime(char *buf, char *result) { uint16_t y = 0; uint8_t M = 0, d = 0, h = 0, m = 0, s = 0, n; n = *(uint8_t *) (buf); if (n != 0) { y = *(uint16_t *) (buf + 1); M = *(uint8_t *) (buf + 3); d = *(uint8_t *) (buf + 4); if (n > 4) { h = *(uint8_t *) (buf + 5); m = *(uint8_t *) (buf + 6); s = *(uint8_t *) (buf + 7); } } snprintf(result, DATETIME_MAX_SIZE, "%04d-%02d-%02d %02d:%02d:%02d", y, M, d, h, m, s); swTraceLog(SW_TRACE_MYSQL_CLIENT, "n=%d", n); return n; } static int mysql_decode_time(char *buf, char *result) { uint8_t h = 0, m = 0, s = 0; uint8_t n = *(uint8_t *) (buf); if (n != 0) { h = *(uint8_t *) (buf + 6); m = *(uint8_t *) (buf + 7); s = *(uint8_t *) (buf + 8); } snprintf(result, DATETIME_MAX_SIZE, "%02d:%02d:%02d", h, m, s); swTraceLog(SW_TRACE_MYSQL_CLIENT, "n=%d", n); return n; } static int mysql_decode_date(char *buf, char *result) { uint8_t M = 0, d = 0, n; uint16_t y = 0; n = *(uint8_t *) (buf); if (n != 0) { y = *(uint16_t *) (buf + 1); M = *(uint8_t *) (buf + 3); d = *(uint8_t *) (buf + 4); } snprintf(result, DATETIME_MAX_SIZE, "%04d-%02d-%02d", y, M, d); swTraceLog(SW_TRACE_MYSQL_CLIENT, "n=%d", n); return n; } static void mysql_decode_year(char *buf, char *result) { uint16_t y = *(uint16_t *) (buf); snprintf(result, DATETIME_MAX_SIZE, "%04d", y); } static int mysql_decode_row_prepare(mysql_client *client, char *buf, int packet_len) { int read_n = 0, i; int tmp_len; ulong_t len = 0; char nul; unsigned int null_count = ((client->response.num_column + 9) / 8) + 1; buf += null_count; packet_len -= null_count; swTraceLog(SW_TRACE_MYSQL_CLIENT, "null_count=%d", null_count); char datetime_buffer[DATETIME_MAX_SIZE]; mysql_row row; zval *result_array = client->response.result_array; zval *row_array = NULL; SW_ALLOC_INIT_ZVAL(row_array); array_init(row_array); swTraceLog(SW_TRACE_MYSQL_CLIENT, "mysql_decode_row begin, num_column=%d, packet_len=%d.", client->response.num_column, packet_len); for (i = 0; i < client->response.num_column; i++) { /* to check Null-Bitmap @see https://dev.mysql.com/doc/internals/en/null-bitmap.html */ if (((buf - null_count + 1)[((i + 2) / 8)] & (0x01 << ((i + 2) % 8))) != 0) { swTraceLog(SW_TRACE_MYSQL_CLIENT, "value: %s is null ,flag2", client->response.columns[i].name); add_assoc_null(row_array, client->response.columns[i].name); continue; } int type = client->response.columns[i].type; swTraceLog(SW_TRACE_MYSQL_CLIENT, "value: name=%s, type=%d", client->response.columns[i].name, type); switch (type) { /* Date Time */ case SW_MYSQL_TYPE_TIME: len = mysql_decode_time(buf + read_n, datetime_buffer) + 1; sw_add_assoc_stringl(row_array, client->response.columns[i].name, datetime_buffer, 8, 1); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%s", client->response.columns[i].name, datetime_buffer); break; case SW_MYSQL_TYPE_YEAR: mysql_decode_year(buf + read_n, datetime_buffer); sw_add_assoc_stringl(row_array, client->response.columns[i].name, datetime_buffer, 4, 1); len = 2; swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%s", client->response.columns[i].name, datetime_buffer); break; case SW_MYSQL_TYPE_DATE: len = mysql_decode_date(buf + read_n, datetime_buffer) + 1; sw_add_assoc_stringl(row_array, client->response.columns[i].name, datetime_buffer, 10, 1); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%s", client->response.columns[i].name, datetime_buffer); break; case SW_MYSQL_TYPE_TIMESTAMP: case SW_MYSQL_TYPE_DATETIME: len = mysql_decode_datetime(buf + read_n, datetime_buffer) + 1; sw_add_assoc_stringl(row_array, client->response.columns[i].name, datetime_buffer, 19, 1); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%s", client->response.columns[i].name, datetime_buffer); break; case SW_MYSQL_TYPE_NULL: add_assoc_null(row_array, client->response.columns[i].name); break; /* String */ case SW_MYSQL_TYPE_TINY_BLOB: case SW_MYSQL_TYPE_MEDIUM_BLOB: case SW_MYSQL_TYPE_LONG_BLOB: case SW_MYSQL_TYPE_BLOB: case SW_MYSQL_TYPE_DECIMAL: case SW_MYSQL_TYPE_NEWDECIMAL: case SW_MYSQL_TYPE_BIT: case SW_MYSQL_TYPE_JSON: case SW_MYSQL_TYPE_STRING: case SW_MYSQL_TYPE_VAR_STRING: case SW_MYSQL_TYPE_VARCHAR: case SW_MYSQL_TYPE_NEWDATE: tmp_len = mysql_length_coded_binary(&buf[read_n], &len, &nul, packet_len - read_n); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } read_n += tmp_len; sw_add_assoc_stringl(row_array, client->response.columns[i].name, buf + read_n, len, 1); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%s", client->response.columns[i].name, swoole_strndup(buf + read_n, len)); break; /* Integer */ case SW_MYSQL_TYPE_TINY: row.stiny = *(int8_t *) (buf + read_n); add_assoc_long(row_array, client->response.columns[i].name, row.stiny); len = sizeof(row.stiny); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%d", client->response.columns[i].name, row.stiny); break; case SW_MYSQL_TYPE_SHORT: row.ssmall = *(int16_t *) (buf + read_n); add_assoc_long(row_array, client->response.columns[i].name, row.ssmall); len = sizeof(row.ssmall); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%d", client->response.columns[i].name, row.ssmall); break; case SW_MYSQL_TYPE_INT24: case SW_MYSQL_TYPE_LONG: row.sint = *(int32_t *) (buf + read_n); add_assoc_long(row_array, client->response.columns[i].name, row.sint); len = sizeof(row.sint); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%d", client->response.columns[i].name, row.sint); break; case SW_MYSQL_TYPE_LONGLONG: row.sbigint = *(int64_t *) (buf + read_n); add_assoc_long(row_array, client->response.columns[i].name, row.sbigint); len = sizeof(row.sbigint); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%ld", client->response.columns[i].name, row.sbigint); break; case SW_MYSQL_TYPE_FLOAT: row.mfloat = *(float *) (buf + read_n); add_assoc_double(row_array, client->response.columns[i].name, row.mfloat); len = sizeof(row.mfloat); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%f", client->response.columns[i].name, row.mfloat); break; case SW_MYSQL_TYPE_DOUBLE: row.mdouble = *(double *) (buf + read_n); add_assoc_double(row_array, client->response.columns[i].name, row.mdouble); len = sizeof(row.mdouble); swTraceLog(SW_TRACE_MYSQL_CLIENT, "%s=%f", client->response.columns[i].name, row.mdouble); break; default: swWarn("unknown field type[%d].", type); return -1; } read_n += len; } add_next_index_zval(result_array, row_array); #if PHP_MAJOR_VERSION > 5 if (row_array) { efree(row_array); } #endif return read_n + null_count; } static sw_inline int mysql_read_eof(mysql_client *client, char *buffer, int n_buf) { //EOF, length (3byte) + id(1byte) + 0xFE + warning(2byte) + status(2byte) if (n_buf < 9) { client->response.wait_recv = 1; return SW_ERR; } client->response.packet_length = mysql_uint3korr(buffer); client->response.packet_number = buffer[3]; //not EOF packet uint8_t eof = buffer[4]; if (eof != 0xfe) { return SW_ERR; } client->response.warnings = mysql_uint2korr(buffer + 5); client->response.status_code = mysql_uint2korr(buffer + 7); MYSQL_RESPONSE_BUFFER->offset += client->response.packet_length + 4; return SW_OK; } static sw_inline int mysql_read_params(mysql_client *client) { while (1) { swString *buffer = MYSQL_RESPONSE_BUFFER; char *t_buffer = buffer->str + buffer->offset; uint32_t n_buf = buffer->length - buffer->offset; swTraceLog(SW_TRACE_MYSQL_CLIENT, "n_buf=%d, length=%d.", n_buf, client->response.packet_length); if (n_buf < 4) { swTraceLog(SW_TRACE_MYSQL_CLIENT, "read eof [1]"); return SW_ERR; } if (client->statement->unreaded_param_count > 0) { //no enough data if (n_buf - 4 < client->response.packet_length) { swTraceLog(SW_TRACE_MYSQL_CLIENT, "read eof 234234."); return SW_ERR; } // Read and ignore parameter field. Sentence from MySQL source: // skip parameters data: we don't support it yet client->response.packet_length = mysql_uint3korr(t_buffer); client->response.packet_number = t_buffer[3]; buffer->offset += (client->response.packet_length + 4); client->statement->unreaded_param_count--; swTraceLog(SW_TRACE_MYSQL_CLIENT, "read param, count=%d.", client->statement->unreaded_param_count); continue; } else { swTraceLog(SW_TRACE_MYSQL_CLIENT, "read eof [2]"); if (mysql_read_eof(client, t_buffer, n_buf) == 0) { return SW_OK; } else { return SW_ERR; } } } } static sw_inline int mysql_read_rows(mysql_client *client) { swString *buffer = MYSQL_RESPONSE_BUFFER; char *t_buffer = buffer->str + buffer->offset; uint32_t n_buf = buffer->length - buffer->offset; int ret; swTraceLog(SW_TRACE_MYSQL_CLIENT, "n_buf=%d", n_buf); //RecordSet parse while (n_buf > 0) { if (n_buf < 4) { client->response.wait_recv = 1; return SW_ERR; } //RecordSet end else if (mysql_read_eof(client, t_buffer, n_buf) == SW_OK) { if (client->response.columns) { mysql_columns_free(client); } return SW_OK; } client->response.packet_length = mysql_uint3korr(t_buffer); client->response.packet_number = t_buffer[3]; t_buffer += 4; n_buf -= 4; swTraceLog(SW_TRACE_MYSQL_CLIENT, "record size=%d", client->response.packet_length); //no enough data if (n_buf < client->response.packet_length) { client->response.wait_recv = 1; return SW_ERR; } if (client->cmd == SW_MYSQL_COM_STMT_EXECUTE) { ret = mysql_decode_row_prepare(client, t_buffer, client->response.packet_length); } else { //decode ret = mysql_decode_row(client, t_buffer, client->response.packet_length); } if (ret < 0) { break; } //next row client->response.num_row++; t_buffer += client->response.packet_length; n_buf -= client->response.packet_length; buffer->offset += client->response.packet_length + 4; } return SW_ERR; } static int mysql_decode_field(char *buf, int len, mysql_field *col) { int i; ulong_t size; char nul; char *wh; int tmp_len; /** * string buffer */ char *_buffer = (char*) emalloc(len); if (!_buffer) { return -SW_MYSQL_ERR_BAD_LCB; } col->buffer = _buffer; wh = buf; i = 0; tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->catalog_length = size; col->catalog = _buffer; _buffer += (size + 1); memcpy(col->catalog, &buf[i], size); col->catalog[size] = '\0'; wh += size + 1; i += size; /* n (Length Coded String) db */ tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->db_length = size; col->db = _buffer; _buffer += (size + 1); memcpy(col->db, &buf[i], size); col->db[size] = '\0'; wh += size + 1; i += size; /* n (Length Coded String) table */ tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->table_length = size; col->table = _buffer; _buffer += (size + 1); memcpy(col->table, &buf[i], size); col->table[size] = '\0'; wh += size + 1; i += size; /* n (Length Coded String) org_table */ tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->org_table_length = size; col->org_table = _buffer; _buffer += (size + 1); memcpy(col->org_table, &buf[i], size); col->org_table[size] = '\0'; wh += size + 1; i += size; /* n (Length Coded String) name */ tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->name_length = size; col->name = _buffer; _buffer += (size + 1); memcpy(col->name, &buf[i], size); col->name[size] = '\0'; wh += size + 1; i += size; /* n (Length Coded String) org_name */ tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->org_name_length = size; col->org_name = _buffer; _buffer += (size + 1); memcpy(col->org_name, &buf[i], size); col->org_name[size] = '\0'; wh += size + 1; i += size; /* check len */ if (i + 13 > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } /* (filler) */ i += 1; /* charset */ col->charsetnr = mysql_uint2korr(&buf[i]); i += 2; /* length */ col->length = mysql_uint4korr(&buf[i]); i += 4; /* type */ col->type = (enum mysql_field_types) (uchar)buf[i]; i += 1; /* flags */ col->flags = mysql_uint2korr(&buf[i]); i += 2; /* decimals */ col->decimals = buf[i]; i += 1; /* filler */ i += 2; /* default - a priori facultatif */ if (len - i > 0) { tmp_len = mysql_length_coded_binary(&buf[i], &size, &nul, len - i); if (tmp_len == -1) { return -SW_MYSQL_ERR_BAD_LCB; } i += tmp_len; if (i + size > len) { return -SW_MYSQL_ERR_LEN_OVER_BUFFER; } col->def_length = size; col->def = _buffer; //_buffer += (size + 1); memcpy(col->def, &buf[i], size); col->def[size] = '\0'; wh += size + 1; i += size; } else { col->def = NULL; col->def_length = 0; } /* set write pointer */ return wh - buf; } static int mysql_read_columns(mysql_client *client) { swString *buffer = MYSQL_RESPONSE_BUFFER; char *t_buffer = buffer->str + buffer->offset; uint32_t n_buf = buffer->length - buffer->offset; int ret; for (; client->response.index_column < client->response.num_column; client->response.index_column++) { swTraceLog(SW_TRACE_MYSQL_CLIENT, "index_index_column=%d, n_buf=%d.", client->response.index_column, n_buf); if (n_buf < 4) { return SW_ERR; } client->response.packet_length = mysql_uint3korr(t_buffer); //no enough data if (n_buf - 4 < client->response.packet_length) { return SW_ERR; } client->response.packet_number = t_buffer[3]; t_buffer += 4; n_buf -= 4; ret = mysql_decode_field(t_buffer, client->response.packet_length, &client->response.columns[client->response.index_column]); if (ret > 0) { t_buffer += client->response.packet_length; n_buf -= client->response.packet_length; buffer->offset += (client->response.packet_length + 4); } else { swWarn("mysql_decode_field failed, code=%d.", ret); break; } } if (mysql_read_eof(client, t_buffer, n_buf) < 0) { return SW_ERR; } t_buffer += 9; n_buf -= 9; if (client->cmd != SW_MYSQL_COM_STMT_PREPARE) { zval *result_array = client->response.result_array; if (!result_array) { SW_ALLOC_INIT_ZVAL(result_array); array_init(result_array); client->response.result_array = result_array; } } buffer->offset += t_buffer - (buffer->str + buffer->offset); return SW_OK; } // this function is used to check if multi responses has received over. int mysql_is_over(mysql_client *client) { swString *buffer = MYSQL_RESPONSE_BUFFER; char *p; if (client->check_offset == buffer->length) { // have already check all of the data goto again; } size_t n_buf = buffer->length - client->check_offset; // remaining buffer size uint32_t temp; while (1) { p = buffer->str + client->check_offset; // where to start checking now if (unlikely(buffer->length - buffer->offset < 5)) { break; } temp = mysql_uint3korr(p); //package length // add header p += 4; n_buf -= 4; if (unlikely(n_buf < temp)) //package is incomplete { break; } else { client->check_offset += 4; } client->check_offset += temp; // add package length if (client->check_offset >= buffer->length) // if false: more packages exist, skip the current one { switch ((uint8_t) p[0]) { case 0xfe: // eof { // +type +warning p += 3; swDebug("meet eof and flag=%d", mysql_uint2korr(p)); goto check_flag; } case 0x00: // ok { // if (temp < 7) // { // break; // } ulong_t val = 0; char nul; int retcode; int t_nbuf = n_buf; //+type p++; t_nbuf--; retcode = mysql_lcb_ll(p, &val, &nul, t_nbuf); //affecr rows t_nbuf -= retcode; p += retcode; retcode = mysql_lcb_ll(p, &val, &nul, t_nbuf); //insert id t_nbuf -= retcode; p += retcode; check_flag: if ((mysql_uint2korr(p) & SW_MYSQL_SERVER_MORE_RESULTS_EXISTS) == 0) { over: client->response.wait_recv = 0; client->check_offset = 0; return SW_OK; } break; } case 0xff: // response type = error { goto over; } } } n_buf -= temp; if (n_buf == 0) { break; } } again: client->response.wait_recv = 2; return SW_AGAIN; } int mysql_response(mysql_client *client) { swString *buffer = MYSQL_RESPONSE_BUFFER; char *p = buffer->str + buffer->offset; int ret; char nul; size_t n_buf = buffer->length - buffer->offset; while (n_buf > 0) { swTraceLog(SW_TRACE_MYSQL_CLIENT, "client->state=%d, n_buf=%d.", client->state, n_buf); switch (client->state) { case SW_MYSQL_STATE_READ_START: if (buffer->length - buffer->offset < 5) { client->response.wait_recv = 1; return SW_ERR; } client->response.packet_length = mysql_uint3korr(p); client->response.packet_number = p[3]; p += 4; n_buf -= 4; if (n_buf < client->response.packet_length) { client->response.wait_recv = 1; return SW_ERR; } client->response.response_type = p[0]; p ++; n_buf --; /* error */ if (client->response.response_type == 0xff) { client->response.error_code = mysql_uint2korr(p); /* status flag 1byte (#), skip.. */ memcpy(client->response.status_msg, p + 3, 5); client->response.server_msg = p + 8; /** * int<1> header [ff] header of the ERR packet * int<2> error_code error-code * if capabilities & CLIENT_PROTOCOL_41 { * string[1] sql_state_marker # marker of the SQL State * string[5] sql_state SQL State * } */ client->response.l_server_msg = client->response.packet_length - 9; client->state = SW_MYSQL_STATE_READ_END; return SW_OK; } /* eof */ else if (client->response.response_type == 0xfe) { client->response.warnings = mysql_uint2korr(p); client->response.status_code = mysql_uint2korr(p + 2); client->state = SW_MYSQL_STATE_READ_END; return SW_OK; } /* ok */ else if (client->response.response_type == 0) { if (client->cmd == SW_MYSQL_COM_STMT_PREPARE) { ret = mysql_parse_prepare_result(client, p, n_buf); if (ret < 0) { return SW_ERR; } else { p += ret; n_buf -= ret; buffer->offset += (5 + ret); client->response.num_column = client->statement->field_count; client->response.columns = ecalloc(client->response.num_column, sizeof(mysql_field)); if (client->statement->param_count > 0) { client->state = SW_MYSQL_STATE_READ_PARAM; } else { client->state = SW_MYSQL_STATE_READ_FIELD; } break; } } /* affected rows */ ret = mysql_length_coded_binary(p, &client->response.affected_rows, &nul, n_buf); n_buf -= ret; p += ret; /* insert id */ ret = mysql_length_coded_binary(p, &client->response.insert_id, &nul, n_buf); n_buf -= ret; p += ret; /* server status */ client->response.status_code = mysql_uint2korr(p); n_buf -= 2; p += 2; /* server warnings */ client->response.warnings = mysql_uint2korr(p); client->state = SW_MYSQL_STATE_READ_END; return SW_OK; } /* result set */ else { //Protocol::LengthEncodedInteger ret = mysql_length_coded_binary(p - 1, &client->response.num_column, &nul, n_buf + 1); if (ret < 0) { return SW_ERR; } buffer->offset += (4 + ret); client->response.columns = ecalloc(client->response.num_column, sizeof(mysql_field)); client->state = SW_MYSQL_STATE_READ_FIELD; break; } case SW_MYSQL_STATE_READ_FIELD: if (mysql_read_columns(client) < 0) { return SW_ERR; } else { if (client->cmd == SW_MYSQL_COM_STMT_PREPARE) { mysql_columns_free(client); return SW_OK; } client->state = SW_MYSQL_STATE_READ_ROW; break; } case SW_MYSQL_STATE_READ_ROW: if (mysql_read_rows(client) < 0) { return SW_ERR; } else { client->state = SW_MYSQL_STATE_READ_END; return SW_OK; } case SW_MYSQL_STATE_READ_PARAM: if (mysql_read_params(client) < 0) { return SW_ERR; } else if (client->statement->field_count > 0) { client->state = SW_MYSQL_STATE_READ_FIELD; continue; } else { mysql_columns_free(client); return SW_OK; } default: return SW_ERR; } } return SW_OK; } int mysql_query(zval *zobject, mysql_client *client, swString *sql, zval *callback TSRMLS_DC) { if (!client->cli) { SwooleG.error = SW_ERROR_CLIENT_NO_CONNECTION; swoole_php_fatal_error(E_WARNING, "mysql connection#%d is closed.", client->fd); return SW_ERR; } if (!client->connected) { SwooleG.error = SW_ERROR_CLIENT_NO_CONNECTION; swoole_php_error(E_WARNING, "mysql client is not connected to server."); return SW_ERR; } if (client->state != SW_MYSQL_STATE_QUERY) { swoole_php_fatal_error(E_WARNING, "mysql client is waiting response, cannot send new sql query."); return SW_ERR; } if (callback != NULL) { sw_zval_add_ref(&callback); client->callback = sw_zval_dup(callback); } client->cmd = SW_MYSQL_COM_QUERY; swString_clear(mysql_request_buffer); if (mysql_request(sql, mysql_request_buffer) < 0) { return SW_ERR; } //send query if (SwooleG.main_reactor->write(SwooleG.main_reactor, client->fd, mysql_request_buffer->str, mysql_request_buffer->length) < 0) { //connection is closed if (swConnection_error(errno) == SW_CLOSE) { zend_update_property_bool(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("connected"), 0 TSRMLS_CC); zend_update_property_long(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("errno"), 2013 TSRMLS_CC); zend_update_property_string(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("error"), "Lost connection to MySQL server during query" TSRMLS_CC); } return SW_ERR; } else { client->state = SW_MYSQL_STATE_READ_START; return SW_OK; } } #ifdef SW_MYSQL_DEBUG void mysql_client_info(mysql_client *client) { printf("\n"SW_START_LINE"\nmysql_client\nbuffer->offset=%ld\nbuffer->length=%ld\nstatus=%d\n" "packet_length=%d\npacket_number=%d\n" "insert_id=%d\naffected_rows=%d\n" "warnings=%d\n"SW_END_LINE, client->buffer->offset, client->buffer->length, client->response.status_code, client->response.packet_length, client->response.packet_number, client->response.insert_id, client->response.affected_rows, client->response.warnings); int i; if (client->response.num_column) { for (i = 0; i < client->response.num_column; i++) { mysql_column_info(&client->response.columns[i]); } } } void mysql_column_info(mysql_field *field) { printf("\n"SW_START_LINE"\nname=%s, table=%s, db=%s\n" "name_length=%d, table_length=%d, db_length=%d\n" "catalog=%s, default_value=%s\n" "length=%ld, type=%d\n"SW_END_LINE, field->name, field->table, field->db, field->name_length, field->table_length, field->db_length, field->catalog, field->def, field->length, field->type ); } #endif static PHP_METHOD(swoole_mysql, __construct) { if (!mysql_request_buffer) { mysql_request_buffer = swString_new(SW_MYSQL_QUERY_INIT_SIZE); if (!mysql_request_buffer) { swoole_php_fatal_error(E_ERROR, "[1] swString_new(%d) failed.", SW_HTTP_RESPONSE_INIT_SIZE); RETURN_FALSE; } } mysql_client *client = emalloc(sizeof(mysql_client)); bzero(client, sizeof(mysql_client)); swoole_set_object(getThis(), client); } static PHP_METHOD(swoole_mysql, connect) { zval *server_info; zval *callback; char buf[2048]; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "az", &server_info, &callback) == FAILURE) { RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (client->cli) { swoole_php_error(E_WARNING, "The mysql client is already connected server."); RETURN_FALSE; } php_swoole_array_separate(server_info); HashTable *_ht = Z_ARRVAL_P(server_info); zval *value; mysql_connector *connector = &client->connector; if (php_swoole_array_get_value(_ht, "host", value)) { convert_to_string(value); connector->host = Z_STRVAL_P(value); connector->host_len = Z_STRLEN_P(value); } else { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "HOST parameter is required.", 11 TSRMLS_CC); RETURN_FALSE; } if (php_swoole_array_get_value(_ht, "port", value)) { convert_to_long(value); connector->port = Z_LVAL_P(value); } else { connector->port = SW_MYSQL_DEFAULT_PORT; } if (php_swoole_array_get_value(_ht, "user", value)) { convert_to_string(value); connector->user = Z_STRVAL_P(value); connector->user_len = Z_STRLEN_P(value); } else { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "USER parameter is required.", 11 TSRMLS_CC); RETURN_FALSE; } if (php_swoole_array_get_value(_ht, "password", value)) { convert_to_string(value); connector->password = Z_STRVAL_P(value); connector->password_len = Z_STRLEN_P(value); } else { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "PASSWORD parameter is required.", 11 TSRMLS_CC); RETURN_FALSE; } if (php_swoole_array_get_value(_ht, "database", value)) { convert_to_string(value); connector->database = Z_STRVAL_P(value); connector->database_len = Z_STRLEN_P(value); } else { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "DATABASE parameter is required.", 11 TSRMLS_CC); RETURN_FALSE; } if (php_swoole_array_get_value(_ht, "timeout", value)) { convert_to_double(value); connector->timeout = Z_DVAL_P(value); } else { connector->timeout = SW_MYSQL_CONNECT_TIMEOUT; } if (php_swoole_array_get_value(_ht, "charset", value)) { convert_to_string(value); connector->character_set = mysql_get_charset(Z_STRVAL_P(value)); if (connector->character_set < 0) { snprintf(buf, sizeof(buf), "unknown charset [%s].", Z_STRVAL_P(value)); zend_throw_exception(swoole_mysql_exception_class_entry_ptr, buf, 11 TSRMLS_CC); RETURN_FALSE; } } //use the server default charset. else { connector->character_set = 0; } if (php_swoole_array_get_value(_ht, "strict_type", value)) { #if PHP_MAJOR_VERSION < 7 if (Z_TYPE_P(value) == IS_BOOL && Z_BVAL_P(value) == 1) #else if (Z_TYPE_P(value) == IS_TRUE) #endif { connector->strict_type = 1; } else { connector->strict_type = 0; } } else { connector->strict_type = 0; } if (php_swoole_array_get_value(_ht, "fetch_mode", value)) { #if PHP_MAJOR_VERSION < 7 if(Z_TYPE_P(value) == IS_BOOL && Z_BVAL_P(value) == 1) #else if (Z_TYPE_P(value) == IS_TRUE) #endif { connector->fetch_mode = 1; } else { connector->fetch_mode = 0; } } else { connector->fetch_mode = 0; } swClient *cli = emalloc(sizeof(swClient)); int type = SW_SOCK_TCP; if (strncasecmp(connector->host, ZEND_STRL("unix:/")) == 0) { connector->host = connector->host + 5; connector->host_len = connector->host_len - 5; type = SW_SOCK_UNIX_STREAM; } else if (strchr(connector->host, ':')) { type = SW_SOCK_TCP6; } php_swoole_check_reactor(); if (!swReactor_handle_isset(SwooleG.main_reactor, PHP_SWOOLE_FD_MYSQL)) { SwooleG.main_reactor->setHandle(SwooleG.main_reactor, PHP_SWOOLE_FD_MYSQL | SW_EVENT_READ, swoole_mysql_onRead); SwooleG.main_reactor->setHandle(SwooleG.main_reactor, PHP_SWOOLE_FD_MYSQL | SW_EVENT_WRITE, swoole_mysql_onWrite); SwooleG.main_reactor->setHandle(SwooleG.main_reactor, PHP_SWOOLE_FD_MYSQL | SW_EVENT_ERROR, swoole_mysql_onError); } //create socket if (swClient_create(cli, type, 0) < 0) { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "swClient_create failed.", 1 TSRMLS_CC); RETURN_FALSE; } //tcp nodelay if (type != SW_SOCK_UNIX_STREAM) { int tcp_nodelay = 1; if (setsockopt(cli->socket->fd, IPPROTO_TCP, TCP_NODELAY, (const void *) &tcp_nodelay, sizeof(int)) == -1) { swoole_php_sys_error(E_WARNING, "setsockopt(%d, IPPROTO_TCP, TCP_NODELAY) failed.", cli->socket->fd); } } //connect to mysql server int ret = cli->connect(cli, connector->host, connector->port, connector->timeout, 1); if ((ret < 0 && errno == EINPROGRESS) || ret == 0) { if (connector->timeout > 0) { php_swoole_check_timer((int) (connector->timeout * 1000)); cli->timer = SwooleG.timer.add(&SwooleG.timer, (int) (connector->timeout * 1000), 0, client, swoole_mysql_onTimeout); cli->timeout = connector->timeout; } if (SwooleG.main_reactor->add(SwooleG.main_reactor, cli->socket->fd, PHP_SWOOLE_FD_MYSQL | SW_EVENT_WRITE) < 0) { RETURN_FALSE; } } else { snprintf(buf, sizeof(buf), "connect to mysql server[%s:%d] failed.", connector->host, connector->port); zend_throw_exception(swoole_mysql_exception_class_entry_ptr, buf, 2 TSRMLS_CC); RETURN_FALSE; } zend_update_property(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("onConnect"), callback TSRMLS_CC); zend_update_property(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("serverInfo"), server_info TSRMLS_CC); zend_update_property_long(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("sock"), cli->socket->fd TSRMLS_CC); client->buffer = swString_new(SW_BUFFER_SIZE_BIG); client->fd = cli->socket->fd; client->object = getThis(); client->cli = cli; sw_copy_to_stack(client->object, client->_object); sw_zval_add_ref(&client->object); sw_zval_ptr_dtor(&server_info); swConnection *_socket = swReactor_get(SwooleG.main_reactor, cli->socket->fd); _socket->object = client; _socket->active = 0; RETURN_TRUE; } static PHP_METHOD(swoole_mysql, query) { zval *callback; swString sql; bzero(&sql, sizeof(sql)); if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &sql.str, &sql.length, &callback) == FAILURE) { return; } if (!php_swoole_is_callable(callback TSRMLS_CC)) { RETURN_FALSE; } if (sql.length <= 0) { swoole_php_fatal_error(E_WARNING, "Query is empty."); RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } SW_CHECK_RETURN(mysql_query(getThis(), client, &sql, callback TSRMLS_CC)); } static PHP_METHOD(swoole_mysql, begin) { zval *callback; if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "z", &callback) == FAILURE) { return; } if (!php_swoole_is_callable(callback TSRMLS_CC)) { RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (client->transaction) { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "There is already an active transaction.", 21 TSRMLS_CC); RETURN_FALSE; } swString sql; bzero(&sql, sizeof(sql)); swString_append_ptr(&sql, ZEND_STRL("START TRANSACTION")); if (mysql_query(getThis(), client, &sql, callback TSRMLS_CC) < 0) { RETURN_FALSE; } else { client->transaction = 1; RETURN_TRUE; } } static PHP_METHOD(swoole_mysql, commit) { zval *callback; if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "z", &callback) == FAILURE) { return; } if (!php_swoole_is_callable(callback TSRMLS_CC)) { RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (!client->transaction) { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "There is no active transaction.", 22 TSRMLS_CC); RETURN_FALSE; } swString sql; bzero(&sql, sizeof(sql)); swString_append_ptr(&sql, ZEND_STRL("COMMIT")); if (mysql_query(getThis(), client, &sql, callback TSRMLS_CC) < 0) { RETURN_FALSE; } else { client->transaction = 0; RETURN_TRUE; } } static PHP_METHOD(swoole_mysql, rollback) { zval *callback; if (zend_parse_parameters(ZEND_NUM_ARGS()TSRMLS_CC, "z", &callback) == FAILURE) { return; } if (!php_swoole_is_callable(callback TSRMLS_CC)) { RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (!client->transaction) { zend_throw_exception(swoole_mysql_exception_class_entry_ptr, "There is no active transaction.", 22 TSRMLS_CC); RETURN_FALSE; } swString sql; bzero(&sql, sizeof(sql)); swString_append_ptr(&sql, ZEND_STRL("ROLLBACK")); if (mysql_query(getThis(), client, &sql, callback TSRMLS_CC) < 0) { RETURN_FALSE; } else { client->transaction = 0; RETURN_TRUE; } } static PHP_METHOD(swoole_mysql, __destruct) { mysql_client *client = swoole_get_object(getThis()); if (!client) { return; } if (client->state != SW_MYSQL_STATE_CLOSED && client->cli) { zval *retval = NULL; zval *zobject = getThis(); client->cli->destroyed = 1; sw_zend_call_method_with_0_params(&zobject, swoole_mysql_class_entry_ptr, NULL, "close", &retval); if (retval) { sw_zval_ptr_dtor(&retval); } } //release buffer memory if (client->buffer) { swString_free(client->buffer); } efree(client); swoole_set_object(getThis(), NULL); } static PHP_METHOD(swoole_mysql, close) { mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (!client->cli) { RETURN_FALSE; } if (client->cli->socket->closing) { swoole_error_log(SW_LOG_NOTICE, SW_ERROR_SESSION_CLOSING, "The mysql connection[%d] is closing.", client->fd); RETURN_FALSE; } zend_update_property_bool(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("connected"), 0 TSRMLS_CC); SwooleG.main_reactor->del(SwooleG.main_reactor, client->fd); swConnection *socket = swReactor_get(SwooleG.main_reactor, client->fd); bzero(socket, sizeof(swConnection)); socket->removed = 1; zend_bool is_destroyed = client->cli->destroyed; zval *retval = NULL; zval **args[1]; zval *object = getThis(); if (client->onClose) { client->cli->socket->closing = 1; args[0] = &object; if (sw_call_user_function_ex(EG(function_table), NULL, client->onClose, &retval, 1, args, 0, NULL TSRMLS_CC) != SUCCESS) { swoole_php_fatal_error(E_WARNING, "swoole_mysql onClose callback error."); } if (EG(exception)) { zend_exception_error(EG(exception), E_ERROR TSRMLS_CC); } if (retval) { sw_zval_ptr_dtor(&retval); } } mysql_client_free(client, getThis()); if (!is_destroyed) { sw_zval_ptr_dtor(&object); } } static PHP_METHOD(swoole_mysql, on) { char *name; zend_size_t len; zval *cb; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &len, &cb) == FAILURE) { return; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (strncasecmp("close", name, len) == 0) { zend_update_property(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("onClose"), cb TSRMLS_CC); client->onClose = sw_zend_read_property(swoole_mysql_class_entry_ptr, getThis(), ZEND_STRL("onClose"), 0 TSRMLS_CC); sw_copy_to_stack(client->onClose, client->_onClose); } else { swoole_php_error(E_WARNING, "Unknown event type[%s]", name); RETURN_FALSE; } RETURN_TRUE; } static PHP_METHOD(swoole_mysql, getState) { mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } RETURN_LONG(client->state); } static void swoole_mysql_onTimeout(swTimer *timer, swTimer_node *tnode) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif mysql_client *client = tnode->data; client->connector.error_code = ETIMEDOUT; client->connector.error_msg = strerror(client->connector.error_code); client->connector.error_length = strlen(client->connector.error_msg); swoole_mysql_onConnect(client TSRMLS_CC); } static int swoole_mysql_onError(swReactor *reactor, swEvent *event) { swClient *cli = event->socket->object; if (cli && cli->socket && cli->socket->active) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif mysql_client *client = event->socket->object; if (!client) { close(event->fd); return SW_ERR; } zval *retval = NULL; zval *zobject = client->object; sw_zend_call_method_with_0_params(&zobject, swoole_mysql_class_entry_ptr, NULL, "close", &retval); if (retval) { sw_zval_ptr_dtor(&retval); } return SW_OK; } else { return swoole_mysql_onWrite(reactor, event); } } static void swoole_mysql_onConnect(mysql_client *client TSRMLS_DC) { zval *zobject = client->object; zval *callback = sw_zend_read_property(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("onConnect"), 0 TSRMLS_CC); zval *retval = NULL; zval *result; zval **args[2]; SW_MAKE_STD_ZVAL(result); if (client->cli->timer) { swTimer_del(&SwooleG.timer, client->cli->timer); client->cli->timer = NULL; } if (client->connector.error_code > 0) { zend_update_property_stringl(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("connect_error"), client->connector.error_msg, client->connector.error_length TSRMLS_CC); zend_update_property_long(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("connect_errno"), client->connector.error_code TSRMLS_CC); ZVAL_BOOL(result, 0); } else { zend_update_property_bool(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("connected"), 1 TSRMLS_CC); ZVAL_BOOL(result, 1); client->connected = 1; } args[0] = &zobject; args[1] = &result; if (sw_call_user_function_ex(EG(function_table), NULL, callback, &retval, 2, args, 0, NULL TSRMLS_CC) != SUCCESS) { swoole_php_fatal_error(E_WARNING, "swoole_mysql onConnect handler error."); } if (EG(exception)) { zend_exception_error(EG(exception), E_ERROR TSRMLS_CC); } if (retval != NULL) { sw_zval_ptr_dtor(&retval); } sw_zval_ptr_dtor(&result); if (client->connector.error_code > 0) { retval = NULL; //close sw_zend_call_method_with_0_params(&zobject, swoole_mysql_class_entry_ptr, NULL, "close", &retval); if (retval) { sw_zval_ptr_dtor(&retval); } } } static int swoole_mysql_onWrite(swReactor *reactor, swEvent *event) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif if (event->socket->active) { return swReactor_onWrite(SwooleG.main_reactor, event); } socklen_t len = sizeof(SwooleG.error); if (getsockopt(event->fd, SOL_SOCKET, SO_ERROR, &SwooleG.error, &len) < 0) { swWarn("getsockopt(%d) failed. Error: %s[%d]", event->fd, strerror(errno), errno); return SW_ERR; } mysql_client *client = event->socket->object; //success if (SwooleG.error == 0) { //listen read event SwooleG.main_reactor->set(SwooleG.main_reactor, event->fd, PHP_SWOOLE_FD_MYSQL | SW_EVENT_READ); //connected event->socket->active = 1; client->handshake = SW_MYSQL_HANDSHAKE_WAIT_REQUEST; } else { client->connector.error_code = SwooleG.error; client->connector.error_msg = strerror(SwooleG.error); client->connector.error_length = strlen(client->connector.error_msg); swoole_mysql_onConnect(client TSRMLS_CC); } return SW_OK; } static int swoole_mysql_onHandShake(mysql_client *client TSRMLS_DC) { swString *buffer = client->buffer; swClient *cli = client->cli; mysql_connector *connector = &client->connector; int n = cli->recv(cli, buffer->str + buffer->length, buffer->size - buffer->length, 0); if (n < 0) { switch (swConnection_error(errno)) { case SW_ERROR: swSysError("Read from socket[%d] failed.", cli->socket->fd); return SW_ERR; case SW_CLOSE: goto system_call_error; case SW_WAIT: return SW_OK; default: return SW_ERR; } } else if (n == 0) { errno = ECONNRESET; goto system_call_error; } buffer->length += n; int ret = 0; _again: swTraceLog(SW_TRACE_MYSQL_CLIENT, "handshake on %d", client->handshake); if (client->switch_check) { // after handshake we need check if server request us to switch auth type first goto _check_switch; } switch(client->handshake) { case SW_MYSQL_HANDSHAKE_WAIT_REQUEST: { client->switch_check = 1; ret = mysql_handshake(connector, buffer->str, buffer->length); if (ret < 0) { goto _error; } else if (ret > 0) { _send: if (cli->send(cli, connector->buf, connector->packet_length + 4, 0) < 0) { system_call_error: connector->error_code = errno; connector->error_msg = strerror(errno); connector->error_length = strlen(connector->error_msg); swoole_mysql_onConnect(client TSRMLS_CC); return SW_OK; } else { // clear for the new package swString_clear(buffer); // mysql_handshake will return the next state flag client->handshake = ret; } } break; } case SW_MYSQL_HANDSHAKE_WAIT_SWITCH: { _check_switch: client->switch_check = 0; int next_state; // handle auth switch request switch (next_state = mysql_auth_switch(connector, buffer->str, buffer->length)) { case SW_AGAIN: return SW_OK; case SW_ERR: // not the switch package, go to the next goto _again; default: ret = next_state; goto _send; } break; } case SW_MYSQL_HANDSHAKE_WAIT_SIGNATURE: { switch (mysql_parse_auth_signature(buffer, connector)) { case SW_MYSQL_AUTH_SIGNATURE_SUCCESS: { client->handshake = SW_MYSQL_HANDSHAKE_WAIT_RESULT; break; } case SW_MYSQL_AUTH_SIGNATURE_FULL_AUTH_REQUIRED: { // send response and wait RSA public key ret = SW_MYSQL_HANDSHAKE_WAIT_RSA; // handshake = ret goto _send; } default: { goto _error; } } // may be more packages if (buffer->offset < buffer->length) { goto _again; } else { swString_clear(buffer); } break; } case SW_MYSQL_HANDSHAKE_WAIT_RSA: { // encode by RSA #ifdef SW_MYSQL_RSA_SUPPORT switch (mysql_parse_rsa(connector, SWSTRING_CURRENT_VL(buffer))) { case SW_AGAIN: return SW_OK; case SW_OK: ret = SW_MYSQL_HANDSHAKE_WAIT_RESULT; // handshake = ret goto _send; default: goto _error; } #else connector->error_code = -1; connector->error_msg = "MySQL8 RSA-Auth need enable OpenSSL!"; connector->error_length = strlen(connector->error_msg); swoole_mysql_onConnect(client TSRMLS_CC); return SW_OK; #endif break; } default: { ret = mysql_get_result(connector, SWSTRING_CURRENT_VL(buffer)); if (ret < 0) { _error: swoole_mysql_onConnect(client TSRMLS_CC); } else if (ret > 0) { swString_clear(buffer); client->handshake = SW_MYSQL_HANDSHAKE_COMPLETED; swoole_mysql_onConnect(client TSRMLS_CC); } // else recv again } } return SW_OK; } static int swoole_mysql_onRead(swReactor *reactor, swEvent *event) { #if PHP_MAJOR_VERSION < 7 TSRMLS_FETCH_FROM_CTX(sw_thread_ctx ? sw_thread_ctx : NULL); #endif mysql_client *client = event->socket->object; if (client->handshake != SW_MYSQL_HANDSHAKE_COMPLETED) { return swoole_mysql_onHandShake(client TSRMLS_CC); } int sock = event->fd; int ret; zval *zobject = client->object; swString *buffer = client->buffer; zval **args[2]; zval *callback = NULL; zval *retval = NULL; zval *result = NULL; while(1) { ret = recv(sock, buffer->str + buffer->length, buffer->size - buffer->length, 0); if (ret < 0) { if (errno == EINTR) { continue; } else { switch (swConnection_error(errno)) { case SW_ERROR: swSysError("Read from socket[%d] failed.", event->fd); return SW_ERR; case SW_CLOSE: goto close_fd; case SW_WAIT: goto parse_response; default: return SW_ERR; } } } else if (ret == 0) { close_fd: if (client->state == SW_MYSQL_STATE_READ_END) { goto parse_response; } sw_zend_call_method_with_0_params(&zobject, swoole_mysql_class_entry_ptr, NULL, "close", &retval); if (retval) { sw_zval_ptr_dtor(&retval); } return SW_OK; } else { buffer->length += ret; //recv again if (buffer->length == buffer->size) { if (swString_extend(buffer, buffer->size * 2) < 0) { swoole_php_fatal_error(E_ERROR, "malloc failed."); reactor->del(SwooleG.main_reactor, event->fd); } continue; } parse_response: if (mysql_response(client) < 0) { return SW_OK; } zend_update_property_long(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("affected_rows"), client->response.affected_rows TSRMLS_CC); zend_update_property_long(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("insert_id"), client->response.insert_id TSRMLS_CC); client->state = SW_MYSQL_STATE_QUERY; args[0] = &zobject; //OK if (client->response.response_type == 0) { SW_ALLOC_INIT_ZVAL(result); ZVAL_BOOL(result, 1); } //ERROR else if (client->response.response_type == 255) { SW_ALLOC_INIT_ZVAL(result); ZVAL_BOOL(result, 0); zend_update_property_stringl(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("error"), client->response.server_msg, client->response.l_server_msg TSRMLS_CC); zend_update_property_long(swoole_mysql_class_entry_ptr, zobject, ZEND_STRL("errno"), client->response.error_code TSRMLS_CC); } //ResultSet else { result = client->response.result_array; } args[1] = &result; callback = client->callback; if (sw_call_user_function_ex(EG(function_table), NULL, callback, &retval, 2, args, 0, NULL TSRMLS_CC) != SUCCESS) { swoole_php_fatal_error(E_WARNING, "swoole_async_mysql callback[2] handler error."); reactor->del(SwooleG.main_reactor, event->fd); } if (EG(exception)) { zend_exception_error(EG(exception), E_ERROR TSRMLS_CC); } /* free memory */ if (retval) { sw_zval_ptr_dtor(&retval); } if (result) { sw_zval_free(result); } //free callback object sw_zval_free(callback); swConnection *_socket = swReactor_get(SwooleG.main_reactor, event->fd); if (_socket->object) { //clear buffer swString_clear(client->buffer); bzero(&client->response, sizeof(client->response)); } return SW_OK; } } return SW_OK; } #ifdef SW_USE_MYSQLND static PHP_METHOD(swoole_mysql, escape) { swString str; bzero(&str, sizeof(str)); long flags; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str.str, &str.length, &flags) == FAILURE) { return; } if (str.length <= 0) { swoole_php_fatal_error(E_WARNING, "String is empty."); RETURN_FALSE; } mysql_client *client = swoole_get_object(getThis()); if (!client) { swoole_php_fatal_error(E_WARNING, "object is not instanceof swoole_mysql."); RETURN_FALSE; } if (!client->cli) { swoole_php_fatal_error(E_WARNING, "mysql connection#%d is closed.", client->fd); RETURN_FALSE; } char *newstr = safe_emalloc(2, str.length + 1, 1); if (newstr == NULL) { swoole_php_fatal_error(E_ERROR, "emalloc(%ld) failed.", str.length + 1); RETURN_FALSE; } const MYSQLND_CHARSET* cset = mysqlnd_find_charset_nr(client->connector.character_set); if (cset == NULL) { swoole_php_fatal_error(E_ERROR, "unknown mysql charset[%s].", client->connector.character_set); RETURN_FALSE; } int newstr_len = mysqlnd_cset_escape_slashes(cset, newstr, str.str, str.length TSRMLS_CC); if (newstr_len < 0) { swoole_php_fatal_error(E_ERROR, "mysqlnd_cset_escape_slashes() failed."); RETURN_FALSE; } SW_RETURN_STRINGL(newstr, newstr_len, 0); } #endif