-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathMySQLPlugin.cpp
542 lines (493 loc) · 18.2 KB
/
MySQLPlugin.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
#include <cstring>
#include <cstdio>
#include "MySQLPlugin.hpp"
#include "ScriptFunctions.hpp"
CMySQLPlugin* CMySQLPlugin::g_MySQLPlugin = NULL;
void InitPlugin()
{
CMySQLPlugin::g_MySQLPlugin = new CMySQLPlugin();
}
CMySQLPlugin *const GetPlugin()
{
return CMySQLPlugin::g_MySQLPlugin;
}
void FreePlugin()
{
if (CMySQLPlugin::g_MySQLPlugin)
delete CMySQLPlugin::g_MySQLPlugin;
}
/////////////////////////////////////////////////////////////////////////////////
CMySQLPlugin::CMySQLPlugin()
{
for (int i = 0; i < MYSQL_CONNECTION_COUNT; ++i)
{
memset(&m_MySQL[i], 0, sizeof(MYSQL));
m_MySQLResults[i] = NULL;
m_MySQLInUse[i] = false;
m_MYSQLErrNo[i] = -1;
}
}
/////////////////////////////////////////////////////////////////////////////////
CMySQLPlugin::~CMySQLPlugin()
{
Clear();
}
/////////////////////////////////////////////////////////////////////////////////
int CMySQLPlugin::OnInit()
{
/* MySQL-documented */
Plugin_ScrAddFunction("mysql_real_connect", Scr_MySQL_Real_Connect_f);
Plugin_ScrAddFunction("mysql_close", Scr_MySQL_Close_f);
Plugin_ScrAddFunction("mysql_affected_rows", Scr_MySQL_Affected_Rows_f);
Plugin_ScrAddFunction("mysql_query", Scr_MySQL_Query_f);
Plugin_ScrAddFunction("mysql_num_rows", Scr_MySQL_Num_Rows_f);
Plugin_ScrAddFunction("mysql_num_fields", Scr_MySQL_Num_Fields_f);
Plugin_ScrAddFunction("mysql_fetch_row", Scr_MySQL_Fetch_Row_f);
/* MySQL-custom */
Plugin_ScrAddFunction("mysql_fetch_rows", Scr_MySQL_Fetch_Rows_f);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////
void CMySQLPlugin::Clear()
{
for (int i = 0; i < MYSQL_CONNECTION_COUNT; ++i)
{
if (!m_MySQLInUse[i])
continue;
// Free query result.
if (m_MySQLResults[i])
mysql_free_result(m_MySQLResults[i]);
// Close opened connection.
mysql_close(&m_MySQL[i]);
// Clean up data.
m_MySQLResults[i] = NULL;
m_MySQLInUse[i] = false;
m_MYSQLErrNo[i] = -1;
}
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html
* Description
mysql_real_connect() attempts to establish a connection to a
MySQL database engine running on host. mysql_real_connect()
must complete successfully before you can execute any other
API functions that require a valid MYSQL connection handle structure.
* Return Value(s)
A MYSQL* connection handle if the connection was successful, NULL
if the connection was unsuccessful. For a successful connection,
the return value is the same as the value of the first parameter.
================================================================= */
void CMySQLPlugin::OnScript_Real_Connect()
{
int argc = Plugin_Scr_GetNumParam();
if (argc != 4 && argc != 5)
{
Plugin_Scr_Error("Usage: handle = mysql_real_connect(<str host>, "
"<str user>, <str passwd>, <str db or "
">, "
"[int port=3306]);");
return;
}
const char *host = Plugin_Scr_GetString(0);
char *user = Plugin_Scr_GetString(1);
char *pass = Plugin_Scr_GetString(2);
char *db = Plugin_Scr_GetString(3);
int port = 3306;
if (argc == 5)
{
port = Plugin_Scr_GetInt(4);
if (port < 0 || port > 65535)
{
Plugin_Scr_ParamError(4, "Incorrect port: must be any integer "
"from 0 to 65535");
return;
}
}
if (strcmp(host, "localhost") == 0)
host = "127.0.0.1";
if (db[0] == '\0')
db = 0;
int i = 0; // todo: for
for (i = 0; i < MYSQL_CONNECTION_COUNT; ++i)
{
if (m_MySQLInUse[i] == false) /* Will be reserved a bit later */
break;
}
if (i == MYSQL_CONNECTION_COUNT - 1) /* Whoops */
{
pluginError("MySQL connect error: max connections exceeded "
"(%d)",
MYSQL_CONNECTION_COUNT);
return;
}
MYSQL *pMysql = &m_MySQL[i];
// Init connections.
if (mysql_init(&m_MySQL[i]) == NULL)
{
Plugin_PrintError("MySQL init[%d] failed: (%d) %s", i,
mysql_errno(&m_MySQL[i]), mysql_error(&m_MySQL[i]));
return;
}
/* Would you like to reconnect if connection is dropped? */
/* Allows the database to reconnect on a new query etc. */
qboolean reconnect = qtrue;
/* Check to see if the mySQL server connection has dropped */
mysql_options(pMysql, MYSQL_OPT_RECONNECT, &reconnect);
MYSQL *result = mysql_real_connect(pMysql, host, user, pass,
db, port, NULL, 0);
/* We don't want to crash the server, so we have a check to return nothing to prevent that */
if (result == NULL)
{
pluginError("MySQL connect error: (%d) %s", mysql_errno(pMysql),
mysql_error(pMysql));
return;
}
m_MySQLInUse[i] = true;
m_MYSQLErrNo[i] = -1;
Plugin_Scr_AddInt(i);
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-close.html
* Description
Closes a previously opened connection. mysql_close()
also deallocates the connection handle pointed to by
mysql if the handle was allocated automatically by
mysql_init() or mysql_connect().
* Return Value(s)
None.
================================================================= */
void CMySQLPlugin::OnScript_Close()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: mysql_close(<handle>);");
return;
}
unsigned int idx = getHandleIndexForScriptArg(0);
/* Closes the MySQL Handle Connection and frees its query result */
Plugin_DPrintf("Closing MySQL connection: %d\n", idx);
if (m_MySQLResults[idx] != NULL)
{
mysql_free_result(m_MySQLResults[idx]);
m_MySQLResults[idx] = NULL;
}
mysql_close(&m_MySQL[idx]);
m_MySQLInUse[idx] = false;
m_MYSQLErrNo[idx] = -1;
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-affected-rows.html
* Description
mysql_affected_rows() may be called immediately after executing
a statement with mysql_query() or mysql_real_query(). It
returns the number of rows changed, deleted, or inserted by
the last statement if it was an UPDATE, DELETE, or INSERT.
For SELECT statements, mysql_affected_rows() works like
mysql_num_rows().
* Return Value(s)
An integer greater than zero indicates the number of rows
affected or retrieved. Zero indicates that no records
were updated for an UPDATE statement, no rows matched the
WHERE clause in the query or that no query has yet been
executed. -1 indicates that the query returned an error
or that, for a SELECT query, mysql_affected_rows()
was called prior to calling mysql_store_result().
================================================================= */
void CMySQLPlugin::OnScript_Affected_Rows()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: rows = mysql_affected_rows(<handle>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
checkConnection(idx);
checkQuery(idx);
Plugin_Scr_AddInt(mysql_affected_rows(&m_MySQL[idx]));
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-query.html
* Description
Executes the SQL statement pointed to by the null-terminated
string stmt_str. Normally, the string must consist of a single
SQL statement without a terminating semicolon (;) or \g.
If multiple-statement execution has been enabled, the string
can contain several statements separated by semicolons.
* Return Value(s)
Zero for success. Nonzero if an error occurred.
================================================================= */
/* What about SQL-injections? Should prevent? Or they already handled? */
/* Combine result output here? */
void CMySQLPlugin::OnScript_Query()
{
if (Plugin_Scr_GetNumParam() != 2)
{
Plugin_Scr_Error("Usage: mysql_query(<handle>, <string query>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
char *query = Plugin_Scr_GetString(1);
checkConnection(idx);
if (mysql_query(&m_MySQL[idx], query) == 0)
{
if (m_MySQLResults[idx])
{
mysql_free_result(m_MySQLResults[idx]);
m_MySQLResults[idx] = NULL;
}
m_MySQLResults[idx] = mysql_store_result(&m_MySQL[idx]);
/* Result may be NULL, with errno == 0. */
/* For example, try to create database. */
/* Try fix something weird and call mysql_errno once. */
m_MYSQLErrNo[idx] = mysql_errno(&m_MySQL[idx]);
if (m_MySQLResults[idx] == NULL && m_MYSQLErrNo[idx] != 0)
{
pluginError("MySQL store error: (%d) %s",
m_MYSQLErrNo[idx],
mysql_error(&m_MySQL[idx]));
}
}
else
{
pluginError("MySQL query error: (%d) %s",
mysql_errno(&m_MySQL[idx]),
mysql_error(&m_MySQL[idx]));
}
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-num-rows.html
* Description
Returns the number of rows in the result set.
* Return Value(s)
The number of rows in the result set.
================================================================= */
void CMySQLPlugin::OnScript_Num_Rows()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: mysql_num_rows(<handle>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
checkConnection(idx);
checkQuery(idx);
Plugin_Scr_AddInt(mysql_num_rows(m_MySQLResults[idx]));
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-num-fields.html
* Description
Returns the number of columns in a result set.
* Return Value(s)
An unsigned integer representing the number of columns in a result set.
================================================================= */
void CMySQLPlugin::OnScript_Num_Fields()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: mysql_num_fields(<handle>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
checkConnection(idx);
checkQuery(idx);
Plugin_Scr_AddInt(mysql_num_fields(m_MySQLResults[idx]));
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* URL
http://dev.mysql.com/doc/refman/5.7/en/mysql-fetch-row.html
* Description
Retrieves the next row of a result set.
* Return Value(s)
A MYSQL_ROW structure for the next row. NULL if there are no more rows to retrieve or if an error occurred.
================================================================= */
/* Todo: must be tweaked. I think, key for arrays will be great. */
/* Todo: double check that. I didn't worked with mysql before */
void CMySQLPlugin::OnScript_Fetch_Row()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: mysql_fetch_row(<handle>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
checkConnection(idx);
checkQuery(idx);
unsigned int col_count = mysql_num_fields(m_MySQLResults[idx]);
MYSQL_ROW row = mysql_fetch_row(m_MySQLResults[idx]);
if (row != NULL)
{
Plugin_Scr_MakeArray();
mysql_field_seek(m_MySQLResults[idx], 0);
for (unsigned int i = 0; i < col_count; ++i)
{
/* A little help here? I don't actually understand data
* representation. Integer must be integer, string - string,
* float - float */
MYSQL_FIELD *field = mysql_fetch_field(m_MySQLResults[idx]);
if (field == NULL)
{
pluginError("Houston, we got a problem: unnamed column!");
return;
}
if(row[i] == NULL)
Plugin_Scr_AddUndefined();
else
Plugin_Scr_AddString(row[i]);
Plugin_Scr_AddArrayKey(Plugin_Scr_AllocString(field->name));
}
}
}
/////////////////////////////////////////////////////////////////////////////////
/* =================================================================
* Description
Retrieves all rows from a query .
* Return Value(s)
A MYSQL_ROW structure from a query. It will return a different
array depending on the amount of rows. one row and it will return
a single dimensioned array using the column names as the array
keys. If it is more than one row then it will return a 2D array
the first dimension will be a numerical iterator through the rows
and the second dimension will be the columns names as array keys.
================================================================= */
void CMySQLPlugin::OnScript_Fetch_Rows()
{
if (Plugin_Scr_GetNumParam() != 1)
{
Plugin_Scr_Error("Usage: mysql_fetch_rows(<handle>);");
return;
}
int idx = getHandleIndexForScriptArg(0);
checkConnection(idx);
checkQuery(idx);
MYSQL_RES *mysqlResult = m_MySQLResults[idx];
/* Do this no matter what */
Plugin_Scr_MakeArray();
if (mysql_num_rows(mysqlResult) == 0) /* Rows are exist */
return;
unsigned int i = 0;
unsigned int col_count = mysql_num_fields(mysqlResult);
int *keyArrayIndex = reinterpret_cast<int *>(Plugin_Malloc(col_count * sizeof(int)));
MYSQL_FIELD *field;
while ((field = mysql_fetch_field(mysqlResult)) != NULL)
{
keyArrayIndex[i] = Plugin_Scr_AllocString(field->name);
++i;
}
MYSQL_ROW rows;
while ((rows = mysql_fetch_row(mysqlResult)) != NULL)
{
Plugin_Scr_MakeArray();
for (unsigned int i = 0; i < col_count; ++i)
{
if(rows[i] == NULL)
Plugin_Scr_AddUndefined();
else
Plugin_Scr_AddString(rows[i]);
Plugin_Scr_AddArrayKey(keyArrayIndex[i]);
}
Plugin_Scr_AddArray();
}
Plugin_Free(keyArrayIndex);
}
/////////////////////////////////////////////////////////////////////////////////
void CMySQLPlugin::OnInfoRequest(pluginInfo_t *Info_)
{
Info_->handlerVersion.major = PLUGIN_HANDLER_VERSION_MAJOR;
Info_->handlerVersion.minor = PLUGIN_HANDLER_VERSION_MINOR;
Info_->pluginVersion.major = getMajorVersion();
Info_->pluginVersion.minor = getMinorVersion();
const char *const name = getName();
strncpy(Info_->fullName, name, strlen(name));
const char *const shortDescription = getShortDescription();
strncpy(Info_->shortDescription, shortDescription, strlen(shortDescription));
char longDescription[1024] = {'\0'};
sprintf(longDescription, getDescription(), mysql_get_client_version());
longDescription[sizeof(longDescription) - 1] = '\0';
strncpy(Info_->longDescription, longDescription, sizeof(longDescription));
}
/////////////////////////////////////////////////////////////////////////////////
const char *const CMySQLPlugin::getName()
{
return "CoD4X MySQL Plugin";
}
/////////////////////////////////////////////////////////////////////////////////
const char *const CMySQLPlugin::getDescription()
{
return "CoD4X MySQL Plugin allows you to query information from "
"mysql database. MySQL version: %lu";
}
/////////////////////////////////////////////////////////////////////////////////
const char *const CMySQLPlugin::getShortDescription()
{
return "CoD4X MySQL Plugin by Sharpienero, MichaelHillcox, T-Max";
}
/////////////////////////////////////////////////////////////////////////////////
unsigned int CMySQLPlugin::getMajorVersion()
{
return 2;
}
/////////////////////////////////////////////////////////////////////////////////
unsigned int CMySQLPlugin::getMinorVersion()
{
return 0;
}
/////////////////////////////////////////////////////////////////////////////////
int CMySQLPlugin::getHandleIndexForScriptArg(const int ArgNum_) const
{
int idx = Plugin_Scr_GetInt(ArgNum_);
if (idx < 0 || idx >= MYSQL_CONNECTION_COUNT)
Plugin_Scr_ParamError(ArgNum_, "Incorrect connection handle");
return idx;
}
/////////////////////////////////////////////////////////////////////////////////
void CMySQLPlugin::checkConnection(const int HandleIndex_) const
{
if (HandleIndex_ < 0 || HandleIndex_ >= MYSQL_CONNECTION_COUNT)
{
Plugin_Scr_Error("Incorrect connection handle");
return;
}
// Attempt to call without connection
if (m_MySQLInUse[HandleIndex_] == false)
Plugin_Scr_Error("'mysql_real_connection' must be called before.");
}
/////////////////////////////////////////////////////////////////////////////////
void CMySQLPlugin::checkQuery(const int HandleIndex_) const
{
if (HandleIndex_ < 0 || HandleIndex_ >= MYSQL_CONNECTION_COUNT)
{
Plugin_Scr_Error("Incorrect connection handle");
return;
}
// Attempt to call without query
if (m_MySQLResults[HandleIndex_] == NULL && m_MYSQLErrNo[HandleIndex_] != 0)
Plugin_Scr_Error("'mysql_query' must be called before.");
}
/////////////////////////////////////////////////////////////////////////////////
void CMySQLPlugin::pluginError(const char *const Format_, ...) const
{
char buffer[1024] = {'\0'};
va_list va;
va_start(va, Format_);
#ifdef _WIN32
_vsnprintf(buffer, sizeof(buffer), Format_, va);
#else
vsnprintf(buffer, sizeof(buffer), Format_, va);
#endif
buffer[sizeof(buffer) - 1] = '\0';
va_end(va);
Plugin_Scr_Error(buffer);
}