mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Introduce simple regex utils
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
#include "cliparser.h"
|
||||
#include "generator.h" // generate nuid
|
||||
#include "iso14b.h" // defines for ETU conversions
|
||||
#include "util.h" // regex utility
|
||||
|
||||
static int CmdHelp(const char *Cmd);
|
||||
|
||||
@@ -1168,6 +1169,91 @@ static int CmdAnalyseUnits(const char *Cmd) {
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdAnalyseRegex(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "analyse regex",
|
||||
"Regex utility (subset: ^ $ . * with \\\\ escape)",
|
||||
"analyse regex --pattern '^A000' --text A000000476D0000111\n"
|
||||
"analyse regex --pattern '.*500A416E64726F6964506179.*9000$' --text 6F8150500A416E64726F69645061799000 --insensitive\n"
|
||||
"analyse regex --test"
|
||||
);
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
arg_str0("p", "pattern", "<str>", "regex pattern"),
|
||||
arg_str0("d", "text", "<str>", "text to match"),
|
||||
arg_lit0("i", "insensitive", "case-insensitive match"),
|
||||
arg_lit0("t", "test", "run self tests"),
|
||||
arg_param_end
|
||||
};
|
||||
CLIExecWithReturn(ctx, Cmd, argtable, true);
|
||||
|
||||
struct arg_str *arg_pattern = arg_get_str(ctx, 1);
|
||||
struct arg_str *arg_text = arg_get_str(ctx, 2);
|
||||
bool insensitive = arg_get_lit(ctx, 3);
|
||||
bool selftest = arg_get_lit(ctx, 4);
|
||||
|
||||
if (selftest) {
|
||||
CLIParserFree(ctx);
|
||||
|
||||
typedef struct {
|
||||
const char *pattern;
|
||||
const char *text;
|
||||
bool case_insensitive;
|
||||
bool expect_match;
|
||||
} regex_test_case_t;
|
||||
|
||||
const regex_test_case_t tests[] = {
|
||||
{.pattern = "^A000", .text = "A000000476D0000111", .case_insensitive = false, .expect_match = true},
|
||||
{.pattern = "9000$", .text = "6F009000", .case_insensitive = false, .expect_match = true},
|
||||
{.pattern = ".*500A416E64726F6964506179.*9000$", .text = "6F8150500A416E64726F69645061799000", .case_insensitive = true, .expect_match = true},
|
||||
{.pattern = "^a0.*$", .text = "A0000000", .case_insensitive = true, .expect_match = true},
|
||||
{.pattern = "^a0.*$", .text = "B0000000", .case_insensitive = true, .expect_match = false},
|
||||
{.pattern = "A\\*B", .text = "ZZA*BZZ", .case_insensitive = false, .expect_match = true},
|
||||
{.pattern = "A+B", .text = "AAAB", .case_insensitive = false, .expect_match = false},
|
||||
{.pattern = "*ABC", .text = "ABC", .case_insensitive = false, .expect_match = false},
|
||||
{.pattern = "ABC\\", .text = "ABC", .case_insensitive = false, .expect_match = false},
|
||||
};
|
||||
|
||||
bool all_ok = true;
|
||||
for (size_t i = 0; i < ARRAYLEN(tests); i++) {
|
||||
bool matched = tests[i].case_insensitive
|
||||
? str_regex_match_case_insensitive(tests[i].pattern, tests[i].text)
|
||||
: str_regex_match(tests[i].pattern, tests[i].text);
|
||||
|
||||
bool ok = (matched == tests[i].expect_match);
|
||||
PrintAndLogEx(ok ? SUCCESS : FAILED, "%zu. pattern=`%s` valid=%s match=%s ( %s )",
|
||||
i + 1,
|
||||
tests[i].pattern,
|
||||
"true",
|
||||
matched ? "true" : "false",
|
||||
ok ? _GREEN_("ok") : _RED_("fail"));
|
||||
if (!ok) {
|
||||
all_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
PrintAndLogEx(all_ok ? SUCCESS : FAILED, "Tests ( %s )", all_ok ? _GREEN_("ok") : _RED_("fail"));
|
||||
return all_ok ? PM3_SUCCESS : PM3_ESOFT;
|
||||
}
|
||||
|
||||
if (arg_pattern->count == 0 || arg_text->count == 0) {
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(ERR, "pattern and text are required unless --test is used");
|
||||
return PM3_EINVARG;
|
||||
}
|
||||
|
||||
const char *pattern = arg_pattern->sval[0];
|
||||
const char *text = arg_text->sval[0];
|
||||
|
||||
bool matched = insensitive
|
||||
? str_regex_match_case_insensitive(pattern, text)
|
||||
: str_regex_match(pattern, text);
|
||||
CLIParserFree(ctx);
|
||||
PrintAndLogEx(matched ? SUCCESS : INFO, "Regex match: %s", matched ? _GREEN_("true") : _YELLOW_("false"));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static command_t CommandTable[] = {
|
||||
{"help", CmdHelp, AlwaysAvailable, "This help"},
|
||||
{"lrc", CmdAnalyseLRC, AlwaysAvailable, "Generate final byte for XOR LRC"},
|
||||
@@ -1180,6 +1266,7 @@ static command_t CommandTable[] = {
|
||||
{"demodbuff", CmdAnalyseDemodBuffer, AlwaysAvailable, "Load binary string to DemodBuffer"},
|
||||
{"freq", CmdAnalyseFreq, AlwaysAvailable, "Calc wave lengths"},
|
||||
{"foo", CmdAnalyseFoo, AlwaysAvailable, "muxer"},
|
||||
{"regex", CmdAnalyseRegex, AlwaysAvailable, "Regex utility (subset: ^ $ . * with \\\\ escape)"},
|
||||
{"units", CmdAnalyseUnits, AlwaysAvailable, "convert ETU <> US <> SSP_CLK (3.39MHz)"},
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -1499,6 +1499,90 @@ size_t str_nlen(const char *src, size_t maxlen) {
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool str_regex_atom_matches(char atom, bool escaped, char c) {
|
||||
if (!escaped && atom == '.') {
|
||||
return true;
|
||||
}
|
||||
return (atom == c);
|
||||
}
|
||||
|
||||
static bool str_regex_match_here(const char *regexp, const char *text);
|
||||
|
||||
static bool str_regex_match_star(char atom, bool escaped, const char *regexp, const char *text) {
|
||||
do {
|
||||
if (str_regex_match_here(regexp, text)) {
|
||||
return true;
|
||||
}
|
||||
} while (*text != '\0' && str_regex_atom_matches(atom, escaped, *text++));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool str_regex_match_here(const char *regexp, const char *text) {
|
||||
if (regexp[0] == '\0') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (regexp[0] == '$' && regexp[1] == '\0') {
|
||||
return (text[0] == '\0');
|
||||
}
|
||||
|
||||
bool escaped = false;
|
||||
char atom = regexp[0];
|
||||
size_t atom_len = 1;
|
||||
if (regexp[0] == '\\' && regexp[1] != '\0') {
|
||||
escaped = true;
|
||||
atom = regexp[1];
|
||||
atom_len = 2;
|
||||
}
|
||||
|
||||
if (regexp[atom_len] == '*') {
|
||||
return str_regex_match_star(atom, escaped, regexp + atom_len + 1, text);
|
||||
}
|
||||
|
||||
if (text[0] != '\0' && str_regex_atom_matches(atom, escaped, text[0])) {
|
||||
return str_regex_match_here(regexp + atom_len, text + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool str_regex_match(const char *regexp, const char *text) {
|
||||
if (regexp[0] == '^') {
|
||||
return str_regex_match_here(regexp + 1, text);
|
||||
}
|
||||
|
||||
do {
|
||||
if (str_regex_match_here(regexp, text)) {
|
||||
return true;
|
||||
}
|
||||
} while (*text++ != '\0');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool str_regex_match_case_insensitive(const char *regexp, const char *text) {
|
||||
if (regexp == NULL || text == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *pattern_lc = str_dup(regexp);
|
||||
char *text_lc = str_dup(text);
|
||||
if (pattern_lc == NULL || text_lc == NULL) {
|
||||
free(pattern_lc);
|
||||
free(text_lc);
|
||||
return false;
|
||||
}
|
||||
|
||||
str_lower(pattern_lc);
|
||||
str_lower(text_lc);
|
||||
bool matched = str_regex_match(pattern_lc, text_lc);
|
||||
|
||||
free(pattern_lc);
|
||||
free(text_lc);
|
||||
return matched;
|
||||
}
|
||||
|
||||
void str_reverse(char *buf, size_t len) {
|
||||
for (size_t i = 0; i < (len >> 1); i++) {
|
||||
char tmp = buf[i];
|
||||
|
||||
@@ -181,6 +181,12 @@ void str_trim(char *s);
|
||||
char *str_dup(const char *src);
|
||||
char *str_ndup(const char *src, size_t len);
|
||||
size_t str_nlen(const char *src, size_t maxlen);
|
||||
// Lightweight regex subset:
|
||||
// - supported metacharacters: '^' (start), '$' (end), '.' (any char), '*' (zero or more)
|
||||
// - escaping: '\\' to match the following char literally
|
||||
// - all other regex constructs are currently treated as literal characters
|
||||
bool str_regex_match(const char *regexp, const char *text);
|
||||
bool str_regex_match_case_insensitive(const char *regexp, const char *text);
|
||||
|
||||
int hexstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str);
|
||||
int binstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str);
|
||||
|
||||
@@ -465,6 +465,7 @@ while true; do
|
||||
if ! CheckExecute "mfu pwdgen test" "$CLIENTBIN -c 'hf mfu pwdgen --test'" "Selftest ok"; then break; fi
|
||||
if ! CheckExecute "mfu keygen test" "$CLIENTBIN -c 'hf mfu keygen --uid 11223344556677'" "80 B1 C2 71 D8 A0"; then break; fi
|
||||
if ! CheckExecute "jooki encode test" "$CLIENTBIN -c 'hf jooki encode --test'" "04 28 F4 DA F0 4A 81 \( ok \)"; then break; fi
|
||||
if ! CheckExecute "analyse regex selftest" "$CLIENTBIN -c 'analyse regex --test'" "Tests \( ok \)"; then break; fi
|
||||
if ! CheckExecute "trace load/list 14a" "$CLIENTBIN -c 'trace load -f traces/hf_14a_mfu.trace; trace list -1 -t 14a;'" "READBLOCK\(8\)"; then break; fi
|
||||
if ! CheckExecute "trace load/list x" "$CLIENTBIN -c 'trace load -f traces/hf_14a_mfu.trace; trace list -x1 -t 14a;'" "0.0101840425"; then break; fi
|
||||
if ! CheckExecute "nfc decode test - oob" "$CLIENTBIN -c 'nfc decode -d DA2010016170706C69636174696F6E2F766E642E626C7565746F6F74682E65702E6F6F62301000649201B96DFB0709466C65782032'" "Flex 2"; then break; fi
|
||||
|
||||
Reference in New Issue
Block a user