Skip to content

Commit af23187

Browse files
authored
Merge pull request #5270 from sysown/v3.0_restapi_improvement
Use parameterized prepared statements in REST API for safer SQL execution
2 parents 461f5d5 + 79df693 commit af23187

3 files changed

Lines changed: 91 additions & 9 deletions

File tree

include/sqlite3db.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ class SQLite3DB {
206206
bool execute_statement(const char *, char **, int *, int *, SQLite3_result **);
207207
SQLite3_result* execute_statement(const char *, char **_error=NULL, int *_cols=NULL, int *_affected_rows=NULL);
208208
bool execute_statement_raw(const char *, char **, int *, int *, sqlite3_stmt **);
209+
bool execute_prepared(sqlite3_stmt* statement, char** error, int* cols, int* affected_rows, SQLite3_result** resultset);
210+
SQLite3_result* execute_prepared(sqlite3_stmt* statement, char** _error, int* cols, int* affected_rows);
209211
int return_one_int(const char *);
210212
int check_table_structure(char *table_name, char *table_def);
211213
bool build_table(char *table_name, char *table_def, bool dropit);

lib/ProxySQL_RESTAPI_Server.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,22 @@ class sync_resource : public http_resource {
3131
}
3232

3333
const std::shared_ptr<http_response> find_script(const http_request& req, std::string& script, int &interval_ms) {
34-
char *error=NULL;
3534
const string req_uri { req.get_path_piece(1) };
3635
const string req_path { req.get_path() };
37-
const string select_query {
38-
"SELECT * FROM runtime_restapi_routes WHERE uri='" + req_uri + "' and"
39-
" method='" + req.get_method() + "' and active=1"
40-
};
41-
42-
std::unique_ptr<SQLite3_result> resultset {
43-
std::unique_ptr<SQLite3_result>(GloAdmin->admindb->execute_statement(select_query.c_str(), &error))
44-
};
36+
const string select_query { "SELECT * FROM runtime_restapi_routes WHERE uri=?1 AND method=?2 AND active=1" };
37+
38+
std::unique_ptr<SQLite3_result> resultset = nullptr;
39+
char* error = NULL;
40+
int cols = 0;
41+
int affected_rows = 0;
42+
43+
auto [rc, statement1] = GloAdmin->admindb->prepare_v2(select_query.c_str());
44+
ASSERT_SQLITE_OK(rc, GloAdmin->admindb);
45+
rc = (*proxy_sqlite3_bind_text)(statement1.get(), 1, req_uri.c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, GloAdmin->admindb);
46+
rc = (*proxy_sqlite3_bind_text)(statement1.get(), 2, req.get_method().c_str(), -1, SQLITE_TRANSIENT); ASSERT_SQLITE_OK(rc, GloAdmin->admindb);
47+
resultset = std::unique_ptr<SQLite3_result>(GloAdmin->admindb->execute_prepared(statement1.get(), &error, &cols, &affected_rows));
48+
rc = (*proxy_sqlite3_clear_bindings)(statement1.get()); ASSERT_SQLITE_OK(rc, GloAdmin->admindb);
49+
rc = (*proxy_sqlite3_reset)(statement1.get()); ASSERT_SQLITE_OK(rc, GloAdmin->admindb);
4550

4651
if (!resultset) {
4752
proxy_error(

lib/sqlite3db.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,81 @@ bool SQLite3DB::execute_statement_raw(const char *str, char **error, int *cols,
425425
return ret;
426426
}
427427

428+
/**
429+
* @brief Executes a prepared SQL statement and returns the result set.
430+
*
431+
* @param statement The prepared SQL statement to execute.
432+
* @param _error Pointer to a variable to store the error message.
433+
* @param _cols Pointer to a variable to store the number of columns.
434+
* @param _affected_rows Pointer to a variable to store the number of affected rows.
435+
* @return A pointer to the SQLite3_result object representing the result set.
436+
*/
437+
SQLite3_result* SQLite3DB::execute_prepared(sqlite3_stmt* statement, char** error, int* cols, int* affected_rows) {
438+
SQLite3_result* resultset;
439+
440+
char* myerror;
441+
char** _error = (error == NULL ? &myerror : error);
442+
443+
int mycols;
444+
int* _cols = (cols == NULL ? &mycols : cols);
445+
446+
int my_affected_rows;
447+
int* _affected_rows = (affected_rows == NULL ? &my_affected_rows : affected_rows);
448+
449+
if (execute_prepared(statement, _error, _cols, _affected_rows, &resultset))
450+
return resultset;
451+
452+
return NULL;
453+
}
454+
455+
/**
456+
* @brief Executes a prepared SQL statement and returns the result set.
457+
*
458+
* @param statement The prepared SQLite statement to execute.
459+
* @param error Pointer to a variable to store the error message.
460+
* @param cols Pointer to a variable to store the number of columns.
461+
* @param affected_rows Pointer to a variable to store the number of affected rows.
462+
* @param resultset Pointer to a pointer to a SQLite3_result object representing the result set.
463+
* @return True if the execution was successful, false otherwise.
464+
*/
465+
bool SQLite3DB::execute_prepared(sqlite3_stmt* statement, char** error, int* cols, int* affected_rows, SQLite3_result** resultset) {
466+
int rc;
467+
*error = NULL;
468+
bool ret = false;
469+
*cols = (*proxy_sqlite3_column_count)(statement);
470+
if (*cols == 0) { // not a SELECT
471+
*resultset = NULL;
472+
// Get total changes before executing the statement
473+
long long total_changes_before = (*proxy_sqlite3_total_changes64)(db);
474+
do {
475+
rc = (*proxy_sqlite3_step)(statement);
476+
if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { // the execution of the prepared statement failed because locked
477+
if ((*proxy_sqlite3_get_autocommit)(db) == 0) {
478+
*error = strdup((*proxy_sqlite3_errmsg)(db));
479+
goto __exit_execute_prepared;
480+
}
481+
usleep(USLEEP_SQLITE_LOCKED);
482+
}
483+
} while (rc == SQLITE_LOCKED || rc == SQLITE_BUSY);
484+
if (rc == SQLITE_DONE) {
485+
// Calculate affected rows as the difference in total changes
486+
long long total_changes_after = (*proxy_sqlite3_total_changes64)(db);
487+
*affected_rows = (int)(total_changes_after - total_changes_before);
488+
ret = true;
489+
} else {
490+
*error = strdup((*proxy_sqlite3_errmsg)(db));
491+
goto __exit_execute_prepared;
492+
}
493+
} else {
494+
*affected_rows = 0;
495+
*resultset = new SQLite3_result(statement);
496+
ret = true;
497+
}
498+
__exit_execute_prepared:
499+
//(*proxy_sqlite3_reset)(statement);
500+
return ret;
501+
}
502+
428503
/**
429504
* @brief Executes a SQL statement and returns a single integer result.
430505
*

0 commit comments

Comments
 (0)