Files
templates/c/sd-file-browser/Src/sd_file_browser.c

178 lines
6.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "sd_file_browser.h"
#include <stddef.h>
#include <string.h>
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);
}