#include "sd_file_browser.h" #include #include static uint8_t has_temperature_name(const char *name) { const size_t length = strlen(name); const char prefix[] = "temperature_"; return (uint8_t)((length > (sizeof(prefix) - 1U + 3U)) && (strncmp(name, prefix, sizeof(prefix) - 1U) == 0) && (strcmp(name + length - 3U, ".md") == 0)); } /* Сравнение расширения без учёта регистра для короткого суффикса ASCII. */ static uint8_t has_suffix_ci(const char *name, const char *suffix) { const size_t name_length = strlen(name); const size_t suffix_length = strlen(suffix); size_t index; if (name_length <= suffix_length) { return 0U; } for (index = 0U; index < suffix_length; ++index) { char left = name[name_length - suffix_length + index]; char right = suffix[index]; if ((left >= 'A') && (left <= 'Z')) { left = (char)(left - 'A' + 'a'); } if ((right >= 'A') && (right <= 'Z')) { right = (char)(right - 'A' + 'a'); } if (left != right) { return 0U; } } return 1U; } /* Сырой образ прошивки: .bin или .fw. Intel HEX здесь не принимается — его * адреса лежат внутри файла и требуют разбора, недоступного на этом этапе. */ static uint8_t has_firmware_name(const char *name) { return (uint8_t)((has_suffix_ci(name, ".bin") != 0U) || (has_suffix_ci(name, ".fw") != 0U)); } /* Разрешены печатные ASCII-имена без разделителей ОС и управляющих байтов. */ static uint8_t path_character_is_safe(char character) { unsigned char value = (unsigned char)character; return (uint8_t)((value >= 0x20U) && (value <= 0x7EU) && (character != '\\') && (character != ':')); } SdFileBrowserStatus SdFileBrowser_ValidatePath(const char *path) { size_t length; size_t component_start; size_t index; /* strlen вызывается только после явной проверки внешнего указателя. */ if (path == NULL) { return SD_FILE_BROWSER_INVALID_PATH; } length = strlen(path); if ((length == 0U) || (length >= SD_FILE_BROWSER_MAX_PATH) || (path[0] != '/')) { /* Абсолютный путь накопителя и усечённый путь fail-closed отклоняются. */ return SD_FILE_BROWSER_INVALID_PATH; } if (length == 1U) { return SD_FILE_BROWSER_OK; } /* Компоненты анализируются за один проход, включая завершающий ноль. */ component_start = 1U; for (index = 1U; index <= length; ++index) { char character = path[index]; if ((character == '/') || (character == '\0')) { size_t component_length = index - component_start; if ((component_length == 0U) || (component_length > SD_FILE_BROWSER_MAX_NAME) || ((component_length == 1U) && (path[component_start] == '.')) || ((component_length == 2U) && (path[component_start] == '.') && (path[component_start + 1U] == '.'))) { /* Пустые, точечные и слишком длинные компоненты не нормализуются. */ return SD_FILE_BROWSER_INVALID_PATH; } component_start = index + 1U; } else if (path_character_is_safe(character) == 0U) { /* Кодировка текущего FatFs-порта ASCII; байты UTF-8 не угадываются. */ return SD_FILE_BROWSER_INVALID_PATH; } } return SD_FILE_BROWSER_OK; } SdFileBrowserStatus SdFileBrowser_ValidateLogFilePath(const char *path) { const char root[] = "/niceOne/mounth/"; const char *relative; const char *separator; const char *name; SdFileBrowserStatus status = SdFileBrowser_ValidatePath(path); if (status != SD_FILE_BROWSER_OK) { return status; } /* TEMPLOG.MD сохранён только для старых карт, где журнал лежал в корне. */ if (strcmp(path, "/TEMPLOG.MD") == 0) { return SD_FILE_BROWSER_OK; } if (strncmp(path, root, sizeof(root) - 1U) != 0) { return SD_FILE_BROWSER_NOT_LOG_FILE; } relative = path + sizeof(root) - 1U; separator = strchr(relative, '/'); /* Штатное дерево содержит ровно месяц и имя; обход дополнительных уровней запрещён. */ if ((separator == NULL) || (separator == relative) || (strchr(separator + 1U, '/') != NULL)) { return SD_FILE_BROWSER_NOT_LOG_FILE; } name = strrchr(path, '/'); if ((name == NULL) || (has_temperature_name(name + 1U) == 0U)) { return SD_FILE_BROWSER_NOT_LOG_FILE; } return SD_FILE_BROWSER_OK; } SdFileBrowserStatus SdFileBrowser_ValidateFirmwarePath(const char *path) { const char root[] = "/niceOne/firmware/"; const char *name; SdFileBrowserStatus status = SdFileBrowser_ValidatePath(path); if (status != SD_FILE_BROWSER_OK) { return status; } /* Образы лежат ровно в одном каталоге; вложенные уровни не обходятся. */ if (strncmp(path, root, sizeof(root) - 1U) != 0) { return SD_FILE_BROWSER_NOT_LOG_FILE; } name = path + sizeof(root) - 1U; if ((name[0] == '\0') || (strchr(name, '/') != NULL)) { return SD_FILE_BROWSER_NOT_LOG_FILE; } if (has_firmware_name(name) == 0U) { return SD_FILE_BROWSER_NOT_LOG_FILE; } return SD_FILE_BROWSER_OK; } SdFileBrowserStatus SdFileBrowser_ListPage( const SdFileBrowser *browser, const char *path, uint16_t offset, uint8_t limit, SdFileBrowserPage *page) { SdFileBrowserStatus status; /* limit проверяется до очистки, чтобы invalid call не трогал чужую память. */ if ((browser == NULL) || (browser->list_page == NULL) || (page == NULL) || (limit == 0U) || (limit > SD_FILE_BROWSER_MAX_PAGE_ITEMS)) { return SD_FILE_BROWSER_INVALID_CURSOR; } status = SdFileBrowser_ValidatePath(path); if (status != SD_FILE_BROWSER_OK) { return status; } /* Нулевой tail исключает утечку содержимого предыдущего Modbus-ответа. */ memset(page, 0, sizeof(*page)); /* Portable слой не знает FatFs/HAL и передаёт только проверенный запрос. */ return browser->list_page(browser->context, path, offset, limit, page); }