Files
Crosspoint/src/util/StringUtils.cpp
T
Zach Nelson 5b8787b2bc perf: Avoid creating strings for file extension checks (#1303)
## Summary

**What is the goal of this PR?**

This change avoids the pattern of creating a `std::string` using
`.substr` in order to compare against a file extension literal.
```c++
std::string path;
if (path.length() >= 4 && path.substr(path.length() - 4) == ".ext")
```

The `checkFileExtension` utility has moved from StringUtils to
FsHelpers, to be available to code in lib/. The signature now accepts a
`std::string_view` instead of `std::string`, which makes the single
implementation reusable for Arduino `String`.

Added utility functions for commonly repeated extensions.

These changes **save about 2 KB of flash (5,999,427 to 5,997,343)**.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-03-05 10:12:22 -06:00

47 lines
1.3 KiB
C++

#include "StringUtils.h"
#include <Utf8.h>
namespace StringUtils {
std::string sanitizeFilename(const std::string& name, size_t maxBytes) {
std::string result;
result.reserve(std::min(name.size(), maxBytes));
const auto* text = reinterpret_cast<const unsigned char*>(name.c_str());
// Skip leading spaces and dots so they don't consume the byte budget
while (*text == ' ' || *text == '.') {
text++;
}
// Process full UTF-8 codepoints to avoid trimming in the middle of a multibyte sequence
while (*text != 0) {
const auto* cpStart = text;
uint32_t cp = utf8NextCodepoint(&text);
if (cp == '/' || cp == '\\' || cp == ':' || cp == '*' || cp == '?' || cp == '"' || cp == '<' || cp == '>' ||
cp == '|') {
// Replace illegal and control characters with '_'
if (result.length() + 1 > maxBytes) break;
result += '_';
} else if (cp >= 128 || (cp >= 32 && cp < 127)) {
const size_t cpBytes = text - cpStart;
if (result.length() + cpBytes > maxBytes) break;
result.append(reinterpret_cast<const char*>(cpStart), cpBytes);
}
}
// Trim trailing spaces and dots
size_t end = result.find_last_not_of(" .");
if (end != std::string::npos) {
result.resize(end + 1);
} else {
result.clear();
}
return result.empty() ? "book" : result;
}
} // namespace StringUtils