fileutils: honor a filename extension the user supplied

'-f card.mfd' looked for card.mfd.bin and saved to card.mfd.bin,
because the caller's suffix was appended unless the name already
ended in that exact suffix.

searchFile() now tries the name as typed before falling back to the
suffixed one.  newfilenamemcopyEx() keeps an extension that denotes
the same kind of file it is about to write, and swaps any other for
its own, so 'hf mf dump -f card.mfd' gives card.mfd + card.json
rather than card.mfd + card.mfd.json.  Both classify with
get_filetype() so load and save cannot drift apart.

Also drops the size_t underflow in newfilenamemcopyEx(), where a long
path plus a configured save path made the snprintf bound wrap past
the 1000 byte buffer.

Co-Authored-By: Claude Opus 5 (1M context)
This commit is contained in:
iceman1001
2026-09-13 12:19:53 +02:00
co-authored by Claude Opus 5 (1M context)
parent 196a467bf8
commit 72f48c8a02
2 changed files with 82 additions and 16 deletions
+3
View File
@@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Fixed `-f <fn>` - a filename given with its own extension is now used as-is on both load and save, instead of always getting `.bin` / `.json` appended (@iceman1001)
- Fixed `hf mf view` - VIGIK signature verification was a heuristic that counted zero bytes; it now recovers the ISO 9796-1 message and checks it against the card, catching a modified UID that the old check passed (@iceman1001)
- Added `hf mf view --selftest` - recovers and checks the signature of a known good expired VIGIK card (@iceman1001)
- Changed `hf mf view` / `hf mf dump --ns` - identify VIGIK family systems (@iceman1001)
- Added `parsehid.c` - HID PACS decoding for MIFARE Classic dumps
- Added `parsevigik.c` - VIGIK PACS decoding for MIFARE Classic dumps (@iceman1001)
+79 -16
View File
@@ -261,6 +261,35 @@ void path_basename_without_ext(const char *path, char *out, size_t out_len) {
}
}
// the extension of the last path component, leading dot included, or NULL when
// there is none. "dumps/vg20180106.mfd" -> ".mfd", "dumps/vg20180106" -> NULL
static const char *path_extension(const char *path) {
const char *base = path_basename(path);
const char *dot = strrchr(base, '.');
if ((dot == NULL) || (dot == base) || (dot[1] == '\0')) {
return NULL;
}
return dot;
}
static bool path_has_extension(const char *path) {
return (path_extension(path) != NULL);
}
// true when two extensions denote the same kind of file, ie ".mfd" and ".bin"
// are both binary. Classified with get_filetype() so the save path and the
// load path can never drift apart.
static bool same_filetype(const char *ext_a, const char *ext_b) {
if (ext_a == NULL || ext_b == NULL) {
return false;
}
char a[64] = {0};
char b[64] = {0};
snprintf(a, sizeof(a), "dump%s", ext_a);
snprintf(b, sizeof(b), "dump%s", ext_b);
return (get_filetype(a) == get_filetype(b));
}
static int qsort_path_cmp(const void *a, const void *b) {
const char *pa = (const char *)a;
const char *pb = (const char *)b;
@@ -462,26 +491,30 @@ char *newfilenamemcopyEx(const char *preferredName, const char *suffix, savePath
}
}
// remove file extension if exist in name
// strip the extension the name came with, if any, and decide which one to write.
// An extension of the same kind as the file we are about to write is the user's
// choice, keep it, `-f card.mfd` saves the binary as card.mfd.
// Any other is swapped for ours, so the same `-f card.mfd` saves the json as
// card.json instead of card.mfd.json
size_t p_namelen = strlen(expanded);
if (str_endswith(expanded, suffix)) {
p_namelen -= strlen(suffix);
const char *ext = suffix;
const char *p_ext = path_extension(expanded);
if (p_ext != NULL) {
if (same_filetype(p_ext, suffix)) {
ext = p_ext;
}
p_namelen -= strlen(p_ext);
}
len -= strlen(suffix) + 1;
len -= p_namelen;
// modify filename
snprintf(pfn, len, "%.*s%s", (int)p_namelen, expanded, suffix);
// "-001"
len -= 4;
// modify filename. len is what is left of fileName from pfn onwards
snprintf(pfn, len, "%.*s%s", (int)p_namelen, expanded, ext);
int num = 1;
// check complete path/filename if exists
while (fileExists(fileName)) {
// modify filename
snprintf(pfn, len, "%.*s-%03d%s", (int)p_namelen, expanded, num, suffix);
snprintf(pfn, len, "%.*s-%03d%s", (int)p_namelen, expanded, num, ext);
num++;
}
@@ -3941,12 +3974,42 @@ int searchFile(char **foundpath, const char *pm3dir, const char *searchname, con
return PM3_EINVARG;
}
int res = searchFinalFile(foundpath, pm3dir, filename, silent);
if (res != PM3_SUCCESS) {
if ((res == PM3_EFILE) && (!silent)) {
PrintAndLogEx(FAILED, "Error - can't find `" _YELLOW_("%s") "`", filename);
// the given name already carries an extension of its own, ie `-f dump.mfd`
// while the caller asks for ".bin". Honour what the user typed before
// falling back to dump.mfd.bin
int res = PM3_EFILE;
char *asis = NULL;
if (path_has_extension(searchname) && (str_endswith(searchname, suffix) == false)) {
asis = filenamemcopy(searchname, "");
if (asis == NULL) {
free(filename);
return PM3_EMALLOC;
}
if ((strlen(asis) == 0) || path_is_directory(asis)) {
free(asis);
asis = NULL;
} else {
res = searchFinalFile(foundpath, pm3dir, asis, silent);
}
}
if (res != PM3_SUCCESS) {
res = searchFinalFile(foundpath, pm3dir, filename, silent);
}
if (res != PM3_SUCCESS) {
if ((res == PM3_EFILE) && (!silent)) {
if (asis != NULL) {
PrintAndLogEx(FAILED, "Error - can't find `" _YELLOW_("%s") "` nor `" _YELLOW_("%s") "`", asis, filename);
} else {
PrintAndLogEx(FAILED, "Error - can't find `" _YELLOW_("%s") "`", filename);
}
}
}
free(asis);
free(filename);
return res;
}