Задание 2.
Реализация функции для взаимодействия с базой данных
Реализуйте любой из SQL запросов, который вы выполняли в одной из предыдущих работ в виде функции на языке С, взаимодействующей с базой данных. Предусмотрите защиту от SQL инъекции любым из приведенных выше способов.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libpq-fe.h>
/* Функция выхода из программы с сообщением об ошибке. */
void err_exit(PGconn *conn) {
PQfinish(conn);
exit(1);
}
/* Функция печати результата запроса на экран */
void print_query(PGresult *res) {
int rows = PQntuples(res);
int cols = PQnfields(res);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
}
/*
* Функция поиска самого младшего студента в указанной группе
*/
void find_youngest_student_in_group(PGconn *conn, const char* group_number) {
const char *param_values[1] = { group_number };
PGresult *res = PQexecParams(
conn,
"SELECT last_name, first_name, birthday "
"FROM students "
"WHERE students_group_number = $1 "
"ORDER BY birthday DESC "
"LIMIT 1;",
1, // Количество параметров
NULL, // Массив типов параметров (NULL для автоматического определения)
param_values, // Массив значений параметров
NULL, // Длины параметров (NULL для текстовых параметров)
NULL, // Формат параметров (NULL для текста)
0 // Формат результата (0 для текстового)
);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "No data retrieved\n");
PQclear(res);
err_exit(conn);
}
printf("Самый младший студент в группе %s:\n", group_number);
print_query(res);
PQclear(res);
}
int main() {
// Подключение к серверу базы данных
PGconn *conn = PQconnectdb("user=SAB password=123456 dbname=postgres");
// Проверка статуса подключения
if (PQstatus(conn) == CONNECTION_BAD) {
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
err_exit(conn);
}
// Вызов функции поиска самого младшего студента в группе "ИВТ-43"
find_youngest_student_in_group(conn, "ИВТ-43");
PQfinish(conn);
return 0;
}
Защита от SQL-инъекций:
Используем PQexecParams, где значение параметра group_name передается как отдельный параметр $1. Это позволяет избежать SQL-инъекций, поскольку PostgreSQL автоматически экранирует переданное значение.
Запрос для нахождения самого младшего студента:
В запросе ORDER BY birthday DESC LIMIT 1 сортирует записи по дате рождения в порядке убывания и возвращает только первую запись, которая соответствует самому младшему студенту.
Функция print_query:
Выводит результат запроса с информацией о фамилии, имени и дате рождения студента, позволяя просмотреть результат без необходимости повторной разработки кода вывода.
Этот код найдет и отобразит самого младшего студента в группе «ИВТ-43» на основе даты рождения.
Предупреждение об инъекции
Добавьте функцию, срабатывающую в случае попытки внедрения SQL инъекции и выводящее значение, что текущему пользователю вынесено предупреждение.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libpq-fe.h>
/* Функция выхода из программы с сообщением об ошибке. */
void err_exit(PGconn *conn) {
PQfinish(conn);
exit(1);
}
/* Функция печати результата запроса на экран */
void print_query(PGresult *res) {
int rows = PQntuples(res);
int cols = PQnfields(res);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
}
/*
* Функция проверки на SQL-инъекцию.
* Если найдены подозрительные символы, возвращает 1, иначе — 0.
*/
int detect_sql_injection(const char* input) {
// Символы, часто используемые в SQL-инъекциях
const char* blacklist[] = {"'", ";", "--", "/*", "*/", "#", "\""};
int n = sizeof(blacklist) / sizeof(blacklist[0]);
for (int i = 0; i < n; i++) {
if (strstr(input, blacklist[i]) != NULL) {
return 1; // Обнаружена потенциальная инъекция
}
}
return 0;
}
/*
* Функция поиска самого младшего студента в указанной группе.
* Выводит предупреждение при попытке SQL-инъекции.
*/
void find_youngest_student_in_group(PGconn *conn, const char* group_number) {
// Проверка на SQL-инъекцию
if (detect_sql_injection(group_number)) {
printf("Предупреждение: Попытка SQL-инъекции обнаружена. Доступ для текущего пользователя ограничен.\n");
return;
}
const char *param_values[1] = { group_number };
PGresult *res = PQexecParams(
conn,
"SELECT last_name, first_name, birthday "
"FROM students "
"WHERE students_group_number = $1 "
"ORDER BY birthday DESC "
"LIMIT 1;",
1, // Количество параметров
NULL, // Массив типов параметров (NULL для автоматического определения)
param_values, // Массив значений параметров
NULL, // Длины параметров (NULL для текстовых параметров)
NULL, // Формат параметров (NULL для текста)
0 // Формат результата (0 для текстового)
);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "No data retrieved\n");
PQclear(res);
err_exit(conn);
}
printf("Самый младший студент в группе %s:\n", group_number);
print_query(res);
PQclear(res);
}
int main() {
// Подключение к серверу базы данных
PGconn *conn = PQconnectdb("user=SAB password=123456 dbname=postgres");
// Проверка статуса подключения
if (PQstatus(conn) == CONNECTION_BAD) {
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
err_exit(conn);
}
// Ввод названия группы пользователем
char group_number[256];
printf("Введите название группы (например, ИТД-43): ");
fgets(group_number, sizeof(group_number), stdin);
// Удаление символа новой строки, если он есть
size_t len = strlen(group_number);
if (len > 0 && group_number[len - 1] == '\n') {
group_number[len - 1] = '\0';
}
// Вызов функции поиска самого младшего студента в указанной группе
find_youngest_student_in_group(conn, group_number);
PQfinish(conn);
return 0;
}
Функция detect_sql_injection:
Принимает строку input и проверяет её на наличие символов и последовательностей, часто встречающихся в SQL-инъекциях.
Если находят такие символы, возвращает 1 (обнаружена инъекция), иначе — 0.
Функция find_youngest_student_in_group:
Проверяет, содержит ли ввод пользователя потенциально опасные символы.
Если detect_sql_injection возвращает 1, выводит сообщение предупреждения и завершает функцию без выполнения SQL-запроса.
Использование представлений
Замените вызов SQL запроса из программного кода на вызов представления. Объясните преимущества и недостатки такого подхода.
В pgADMIN – cоздать виртуальную таблицу
CREATE OR REPLACE VIEW youngest_student_in_group AS
SELECT last_name, first_name, birthday, students_group_number
FROM students
ORDER BY students_group_number, birthday DESC;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libpq-fe.h>
/* Функция выхода из программы с сообщением об ошибке. */
void err_exit(PGconn *conn) {
PQfinish(conn);
exit(1);
}
/* Функция печати результата запроса на экран */
void print_query(PGresult *res) {
int rows = PQntuples(res);
int cols = PQnfields(res);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%s ", PQgetvalue(res, i, j));
}
printf("\n");
}
}
/*
* Функция проверки на SQL-инъекцию.
* Если найдены подозрительные символы, возвращает 1, иначе — 0.
*/
int detect_sql_injection(const char* input) {
// Символы, часто используемые в SQL-инъекциях
const char* blacklist[] = {"'", ";", "--", "/*", "*/", "#", "\""};
int n = sizeof(blacklist) / sizeof(blacklist[0]);
for (int i = 0; i < n; i++) {
if (strstr(input, blacklist[i]) != NULL) {
return 1; // Обнаружена потенциальная инъекция
}
}
return 0;
}
/*
* Функция поиска самого младшего студента в указанной группе.
* Выводит предупреждение при попытке SQL-инъекции.
*/
void find_youngest_student_in_group(PGconn *conn, const char* group_number) {
if (detect_sql_injection(group_number)) {
printf("Предупреждение: Попытка SQL-инъекции обнаружена. Доступ для текущего пользователя ограничен.\n");
return;
}
const char *param_values[1] = { group_number };
PGresult *res = PQexecParams(
conn,
"SELECT last_name, first_name, birthday "
"FROM youngest_student_in_group "
"WHERE students_group_number = $1 "
"LIMIT 1;",
1, // Количество параметров
NULL, // Массив типов параметров (NULL для автоматического определения)
param_values, // Массив значений параметров
NULL, // Длины параметров (NULL для текстовых параметров)
NULL, // Формат параметров (NULL для текста)
0 // Формат результата (0 для текстового)
);
if (PQresultStatus(res) != PGRES_TUPLES_OK) {
fprintf(stderr, "No data retrieved\n");
PQclear(res);
err_exit(conn);
}
printf("Самый младший студент в группе %s:\n", group_number);
print_query(res);
PQclear(res);
}
int main() {
// Подключение к серверу базы данных
PGconn *conn = PQconnectdb("user=SAB password=123456 dbname=postgres");
// Проверка статуса подключения
if (PQstatus(conn) == CONNECTION_BAD) {
fprintf(stderr, "Connection to database failed: %s\n", PQerrorMessage(conn));
err_exit(conn);
}
// Ввод названия группы пользователем
char group_number[256];
printf("Введите название группы (например, ИТД-43): ");
fgets(group_number, sizeof(group_number), stdin);
// Удаление символа новой строки, если он есть
size_t len = strlen(group_number);
if (len > 0 && group_number[len - 1] == '\n') {
group_number[len - 1] = '\0';
}
// Вызов функции поиска самого младшего студента в указанной группе
find_youngest_student_in_group(conn, group_number);
PQfinish(conn);
return 0;
}
Преимущества:
Повышенная читаемость и упрощение кода: В коде больше не нужно писать длинный SQL-запрос. Достаточно простого запроса к представлению.
Переиспользование и модульность: Представление можно использовать в разных частях приложения или другими приложениями, что делает код более гибким.
Безопасность и защита от SQL-инъекций: Представления помогают централизовать доступ к данным и могут ограничить доступ к подмножествам данных, снижая риск утечек.
Оптимизация запросов: В некоторых СУБД представления кэшируются, что может ускорить выполнение запросов. PostgreSQL, например, может оптимизировать запросы, учитывая представления, хотя это зависит от сложности запроса.
Недостатки:
Производительность: Если представление не является материализованным, каждый раз при его вызове выполняется запрос на обновление данных, что может замедлить выполнение, особенно если данные большого объема.
Сложность обновления: Любое изменение в представлении может потребовать обновления кода, если в нем учитываются структура и поля представления.
Ограниченная гибкость: Представления фиксируют логику запроса, и если она часто изменяется, придется пересоздавать представления.
