Skip to content

Commit 6564ea9

Browse files
feat(core): add -i file signature detector for extension mismatch recovery
1 parent 22757f2 commit 6564ea9

6 files changed

Lines changed: 405 additions & 235 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(SOURCES
1818
src/core/Stream.cpp
1919
src/core/StreamWriter.cpp
2020
src/core/BufferView.cpp
21+
src/core/FileTypeDetector.cpp
2122
src/cli/ArgumentParser.cpp
2223
)
2324

include/xary/cli/ArgumentParser.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ enum class Mode {
99
Help,
1010
Version,
1111
Encode,
12-
Decode
12+
Decode,
13+
Inspect
1314
};
1415

1516
struct Options {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include "../core/BufferView.hpp"
4+
#include <string>
5+
6+
namespace xary::core {
7+
8+
struct FileTypeInfo {
9+
std::string expectedExtension;
10+
std::string mimeType;
11+
std::string description;
12+
bool isKnown{false};
13+
};
14+
15+
class FileTypeDetector {
16+
public:
17+
static FileTypeInfo detect(BufferView headerView);
18+
};
19+
20+
}

src/cli/ArgumentParser.cpp

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,77 +4,89 @@
44

55
namespace xary::cli {
66

7-
Options ArgumentParser::parse(int argc, char* argv[]) {
8-
Options options;
9-
if (argc <= 1) {
10-
options.mode = Mode::Help;
11-
return options;
12-
}
7+
Options ArgumentParser::parse(int argc, char *argv[]) {
8+
Options options;
9+
if (argc <= 1) {
10+
options.mode = Mode::Help;
11+
return options;
12+
}
1313

14-
std::vector<std::string> args(argv + 1, argv + argc);
14+
std::vector<std::string> args(argv + 1, argv + argc);
1515

16-
for (std::size_t i = 0; i < args.size(); ++i) {
17-
const auto& arg = args[i];
16+
for (std::size_t i = 0; i < args.size(); ++i) {
17+
const auto &arg = args[i];
1818

19-
if (arg == "-h" || arg == "--help") {
20-
options.mode = Mode::Help;
21-
return options;
22-
} else if (arg == "-v" || arg == "--version") {
23-
options.mode = Mode::Version;
24-
return options;
25-
} else if (arg == "-e" || arg == "--encode") {
26-
if (i + 1 < args.size()) {
27-
options.mode = Mode::Encode;
28-
options.inputFile = args[++i];
29-
} else {
30-
options.isValid = false;
31-
options.errorMessage = "Missing input file path for --encode flag.";
32-
return options;
33-
}
34-
} else if (arg == "-d" || arg == "--decode") {
35-
if (i + 1 < args.size()) {
36-
options.mode = Mode::Decode;
37-
options.inputFile = args[++i];
38-
} else {
39-
options.isValid = false;
40-
options.errorMessage = "Missing input file path for --decode flag.";
41-
return options;
42-
}
43-
} else if (arg == "-o" || arg == "--output") {
44-
if (i + 1 < args.size()) {
45-
options.outputFile = args[++i];
46-
} else {
47-
options.isValid = false;
48-
options.errorMessage = "Missing output file path for --output flag.";
49-
return options;
50-
}
51-
} else {
52-
options.isValid = false;
53-
options.errorMessage = "Unknown argument or flag: " + arg;
54-
return options;
55-
}
19+
if (arg == "-h" || arg == "--help") {
20+
options.mode = Mode::Help;
21+
return options;
22+
} else if (arg == "-v" || arg == "--version") {
23+
options.mode = Mode::Version;
24+
return options;
25+
} else if (arg == "-e" || arg == "--encode") {
26+
if (i + 1 < args.size()) {
27+
options.mode = Mode::Encode;
28+
options.inputFile = args[++i];
29+
} else {
30+
options.isValid = false;
31+
options.errorMessage = "Missing input file path for --encode flag.";
32+
return options;
33+
}
34+
} else if (arg == "-d" || arg == "--decode") {
35+
if (i + 1 < args.size()) {
36+
options.mode = Mode::Decode;
37+
options.inputFile = args[++i];
38+
} else {
39+
options.isValid = false;
40+
options.errorMessage = "Missing input file path for --decode flag.";
41+
return options;
42+
}
43+
} else if (arg == "-o" || arg == "--output") {
44+
if (i + 1 < args.size()) {
45+
options.outputFile = args[++i];
46+
} else {
47+
options.isValid = false;
48+
options.errorMessage = "Missing output file path for --output flag.";
49+
return options;
50+
}
51+
} else if (arg == "-i" || arg == "--inspect") {
52+
if (i + 1 < args.size()) {
53+
options.mode = Mode::Inspect;
54+
options.inputFile = args[++i];
55+
} else {
56+
options.isValid = false;
57+
options.errorMessage = "Missing target file path for --inspect flag.";
58+
return options;
59+
}
60+
} else {
61+
options.isValid = false;
62+
options.errorMessage = "Unknown argument or flag: " + arg;
63+
return options;
5664
}
65+
}
5766

58-
return options;
67+
return options;
5968
}
6069

6170
void ArgumentParser::printHelp() {
62-
std::cout << "Usage: xary [OPTIONS]\n\n"
63-
<< "Options:\n"
64-
<< " -h, --help Display this help menu and exit\n"
65-
<< " -v, --version Display engine version\n"
66-
<< " -e, --encode <file> Encode binary file in 64 KB chunks\n"
67-
<< " -d, --decode <file> Decode binary file in 64 KB chunks\n"
68-
<< " -o, --output <file> Specify custom output file path\n\n"
69-
<< "Examples:\n"
70-
<< " xary --version\n"
71-
<< " xary -e data.bin\n"
72-
<< " xary -e data.bin -o encoded.xary\n"
73-
<< " xary -d encoded.xary -o restored.bin\n";
71+
std::cout
72+
<< "Usage: xary [OPTIONS]\n\n"
73+
<< "Options:\n"
74+
<< " -h, --help Display this help menu and exit\n"
75+
<< " -v, --version Display engine version\n"
76+
<< " -e, --encode <file> Encode binary file in 64 KB chunks\n"
77+
<< " -d, --decode <file> Decode binary file in 64 KB chunks\n"
78+
<< " -i, --inspect <file> Inspect file magic signature & detect real extension\n"
79+
<< " -o, --output <file> Specify custom output file path\n\n"
80+
<< "Examples:\n"
81+
<< " xary --version\n"
82+
<< " xary -i corrupted_file.png\n"
83+
<< " xary -e data.bin\n"
84+
<< " xary -e data.bin -o encoded.xary\n"
85+
<< " xary -d encoded.xary -o restored.bin\n";
7486
}
7587

7688
void ArgumentParser::printVersion() {
77-
std::cout << "Xary Binary Engine v0.1.0 (C++20)\n";
89+
std::cout << "Xary Binary Engine v0.1.0 (C++20)\n";
7890
}
7991

80-
}
92+
} // namespace xary::cli

src/core/FileTypeDetector.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "../../include/xary/core/FileTypeDetector.hpp"
2+
#include <array>
3+
4+
namespace xary::core {
5+
6+
// Xary Proprietary Header
7+
constexpr std::array<uint8_t, 8> XARY_MAGIC = {0x8F, 0x1E, 0xAA, 0x4D, 0x9C, 0x05, 0xF3, 0x72};
8+
9+
FileTypeInfo FileTypeDetector::detect(BufferView view) {
10+
if (view.size() < 4) {
11+
return {".bin", "application/octet-stream", "Unknown / File too small", false};
12+
}
13+
14+
// 1. Check Xary Encrypted Binary
15+
if (view.size() >= 8) {
16+
bool isXary = true;
17+
for (std::size_t i = 0; i < 8; ++i) {
18+
if (view[i] != XARY_MAGIC[i]) {
19+
isXary = false;
20+
break;
21+
}
22+
}
23+
if (isXary) {
24+
return {".xary", "application/x-xary-encrypted", "Xary Encrypted Binary Stream", true};
25+
}
26+
}
27+
28+
// 2. Standard Binary Magic Signatures
29+
30+
// PNG: 89 50 4E 47 0D 0A 1A 0A
31+
if (view.size() >= 8 && view[0] == 0x89 && view[1] == 0x50 && view[2] == 0x4E && view[3] == 0x47) {
32+
return {".png", "image/png", "PNG Image", true};
33+
}
34+
35+
// JPEG: FF D8 FF
36+
if (view[0] == 0xFF && view[1] == 0xD8 && view[2] == 0xFF) {
37+
return {".jpg", "image/jpeg", "JPEG Image", true};
38+
}
39+
40+
// PDF: %PDF (25 50 44 46)
41+
if (view[0] == 0x25 && view[1] == 0x50 && view[2] == 0x44 && view[3] == 0x46) {
42+
return {".pdf", "application/pdf", "PDF Document", true};
43+
}
44+
45+
// ZIP / Office Docs (DOCX, XLSX, PPTX): PK.. (50 4B 03 04)
46+
if (view[0] == 0x50 && view[1] == 0x4B && view[2] == 0x03 && view[3] == 0x04) {
47+
return {".zip", "application/zip", "ZIP Archive / MS Office Document", true};
48+
}
49+
50+
// Windows Executable / DLL: MZ (4D 5A)
51+
if (view[0] == 0x4D && view[1] == 0x5A) {
52+
return {".exe", "application/x-msdownload", "Windows Executable / DLL", true};
53+
}
54+
55+
// Linux Executable: ELF (7F 45 4C 46)
56+
if (view[0] == 0x7F && view[1] == 0x45 && view[2] == 0x4C && view[3] == 0x46) {
57+
return {".elf", "application/x-executable", "Linux ELF Executable", true};
58+
}
59+
60+
// GIF Image: GIF8 (47 49 46 38)
61+
if (view[0] == 0x47 && view[1] == 0x49 && view[2] == 0x46 && view[3] == 0x38) {
62+
return {".gif", "image/gif", "GIF Animated Image", true};
63+
}
64+
65+
// GZIP Archive: 1F 8B
66+
if (view[0] == 0x1F && view[1] == 0x8B) {
67+
return {".gz", "application/gzip", "GZIP Compressed Archive", true};
68+
}
69+
70+
// MP4 Video: ftyp box at offset 4
71+
if (view.size() >= 12 && view[4] == 'f' && view[5] == 't' && view[6] == 'y' && view[7] == 'p') {
72+
return {".mp4", "video/mp4", "MP4 Video Container", true};
73+
}
74+
75+
return {".bin", "application/octet-stream", "Generic Binary Data", false};
76+
}
77+
78+
}

0 commit comments

Comments
 (0)