avoid overwriting on filename conflicts

This commit is contained in:
Aleksejs Popovs 2026-01-20 20:12:56 -05:00
parent 687b0b5ac0
commit 6473689e3e
4 changed files with 40 additions and 21 deletions

View File

@ -4,6 +4,7 @@
#include "MappedInputManager.h"
#include "fontIds.h"
#include "util/StringUtils.h"
#define DEVICE_NAME "EPaper"
#define SERVICE_UUID "4ae29d01-499a-480a-8c41-a82192105125"
@ -11,6 +12,7 @@
#define RESPONSE_CHARACTERISTIC_UUID "0c656023-dee6-47c5-9afb-e601dfbdaa1d"
#define OUTPUT_DIRECTORY "/bt"
#define MAX_FILENAME_LENGTH 200
#define PROTOCOL_ASSERT(cond, fmt, ...) \
do { \
@ -181,7 +183,7 @@ void BluetoothActivity::render() const {
renderer.drawCenteredText(UI_10_FONT_ID, 75, stateText.c_str());
if (state == STATE_OFFERED || state == STATE_RECEIVING || state == STATE_DONE) {
renderer.drawCenteredText(UI_12_FONT_ID, 110, filename);
renderer.drawCenteredText(UI_12_FONT_ID, 110, filename.c_str());
} else if (state == STATE_ERROR) {
renderer.drawCenteredText(UI_10_FONT_ID, 110, errorMessage);
}
@ -249,25 +251,30 @@ void BluetoothActivity::onRequest(lfbt_message* msg, size_t msg_len) {
totalBytes = msg->body.clientOffer.bodyLength;
size_t filenameLen = msg_len - 8 - sizeof(lfbt_msg_client_offer);
if (filenameLen > MAX_FILENAME) {
filenameLen = MAX_FILENAME;
}
memcpy(filename, msg->body.clientOffer.name, filenameLen);
filename[filenameLen] = 0;
// sanitize filename
for (char *p = filename; *p; p++) {
if (*p == '/' || *p == '\\' || *p == ':') {
*p = '_';
}
}
size_t filenameLength = msg_len - 8 - sizeof(lfbt_msg_client_offer);
std::string originalFilename = StringUtils::sanitizeFilename(
std::string(msg->body.clientOffer.name, filenameLength),
MAX_FILENAME_LENGTH
);
PROTOCOL_ASSERT(SdMan.ensureDirectoryExists(OUTPUT_DIRECTORY), "Couldn't create output directory %s", OUTPUT_DIRECTORY);
char filepath[MAX_FILENAME + strlen(OUTPUT_DIRECTORY) + 2];
snprintf(filepath, sizeof(filepath), "%s/%s", OUTPUT_DIRECTORY, filename);
// TODO: we could check if file already exists and append a number to filename to avoid overwriting
// generate unique filepath
auto splitName = StringUtils::splitFileName(originalFilename);
filename = originalFilename;
std::string filepath = OUTPUT_DIRECTORY "/" + filename;
uint32_t duplicateIndex = 0;
while (SdMan.exists(filepath.c_str())) {
duplicateIndex++;
if (splitName.second.empty()) {
// no extension
filename = splitName.first + "-" + std::to_string(duplicateIndex);
} else {
filename = splitName.first + "-" + std::to_string(duplicateIndex) + splitName.second;
}
filepath = OUTPUT_DIRECTORY "/" + filename;
}
PROTOCOL_ASSERT(SdMan.openFileForWrite("BT", filepath, file), "Couldn't open file %s for writing", filepath);
// TODO: would be neat to check if we have enough space, but SDCardManager doesn't seem to expose that info currently

View File

@ -13,8 +13,6 @@
#include "../Activity.h"
#define MAX_FILENAME 200
typedef struct __attribute__((packed)) {
uint32_t version;
uint32_t bodyLength;
@ -104,7 +102,7 @@ class BluetoothActivity final : public Activity {
} State;
State state = STATE_INITIALIZING;
char filename[MAX_FILENAME + 1];
std::string filename;
FsFile file;
size_t receivedBytes = 0;
size_t totalBytes = 0;

View File

@ -61,6 +61,14 @@ bool checkFileExtension(const String& fileName, const char* extension) {
return localFile.endsWith(localExtension);
}
std::pair<std::string, std::string> splitFileName(const std::string& name) {
size_t lastDot = name.find_last_of('.');
if (lastDot == std::string::npos) {
return std::make_pair(name, "");
}
return std::make_pair(name.substr(0, lastDot), name.substr(lastDot));
}
size_t utf8RemoveLastChar(std::string& str) {
if (str.empty()) return 0;
size_t pos = str.size() - 1;

View File

@ -19,6 +19,12 @@ std::string sanitizeFilename(const std::string& name, size_t maxLength = 100);
bool checkFileExtension(const std::string& fileName, const char* extension);
bool checkFileExtension(const String& fileName, const char* extension);
/**
* Split a filename into base name and extension.
* If there is no extension, the second element of the pair will be an empty string.
*/
std::pair<std::string, std::string> splitFileName(const std::string& name);
// UTF-8 safe string truncation - removes one character from the end
// Returns the new size after removing one UTF-8 character
size_t utf8RemoveLastChar(std::string& str);