coming allong nicely. adding more commands, tests, and
artifacts for testing. basic framework down just adding features now
This commit is contained in:
183
src/ctf/utils.py
183
src/ctf/utils.py
@@ -37,17 +37,14 @@ def active_competitions(dir: str) -> dict:
|
||||
comps[item] = active_categories(item)
|
||||
print(item.name)
|
||||
print(comps[item])
|
||||
return comps
|
||||
|
||||
# {{{ suggest_patterns
|
||||
def suggest_patterns(s: str) -> list[str]:
|
||||
"""Suggests a list of regex patterns from a sample flag string."""
|
||||
def suggest_patterns(s: str, comp_name: str = "") -> list[str]:
|
||||
"""Suggests a list of regex patterns from a sample flag string, optionally incorporating the competition name."""
|
||||
if not s or not s.strip():
|
||||
raise ValueError("Input string cannot be empty or whitespace only.")
|
||||
|
||||
# Group characters into types
|
||||
groups = []
|
||||
current_type = None
|
||||
current_chars = []
|
||||
|
||||
def get_type(c):
|
||||
if c.isupper():
|
||||
return 'U'
|
||||
@@ -57,19 +54,67 @@ def suggest_patterns(s: str) -> list[str]:
|
||||
return 'D'
|
||||
else:
|
||||
return 'S'
|
||||
|
||||
for c in s:
|
||||
ctype = get_type(c)
|
||||
if ctype != current_type:
|
||||
|
||||
groups = []
|
||||
comp_idx = -1
|
||||
matched_prefix = ""
|
||||
if comp_name and comp_name.strip():
|
||||
comp_idx = s.lower().find(comp_name.lower())
|
||||
|
||||
if comp_idx != -1:
|
||||
matched_prefix = s[comp_idx : comp_idx + len(comp_name)]
|
||||
# Parse prefix before competition name
|
||||
prefix_part = s[:comp_idx]
|
||||
if prefix_part:
|
||||
current_type = None
|
||||
current_chars = []
|
||||
for c in prefix_part:
|
||||
ctype = get_type(c)
|
||||
if ctype != current_type:
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
current_type = ctype
|
||||
current_chars = [c]
|
||||
else:
|
||||
current_chars.append(c)
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
current_type = ctype
|
||||
current_chars = [c]
|
||||
else:
|
||||
current_chars.append(c)
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
|
||||
# Insert competition name group
|
||||
groups.append(('C', matched_prefix))
|
||||
|
||||
# Parse suffix after competition name
|
||||
suffix_part = s[comp_idx + len(comp_name):]
|
||||
if suffix_part:
|
||||
current_type = None
|
||||
current_chars = []
|
||||
for c in suffix_part:
|
||||
ctype = get_type(c)
|
||||
if ctype != current_type:
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
current_type = ctype
|
||||
current_chars = [c]
|
||||
else:
|
||||
current_chars.append(c)
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
else:
|
||||
# Normal grouping without competition name
|
||||
current_type = None
|
||||
current_chars = []
|
||||
for c in s:
|
||||
ctype = get_type(c)
|
||||
if ctype != current_type:
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
current_type = ctype
|
||||
current_chars = [c]
|
||||
else:
|
||||
current_chars.append(c)
|
||||
if current_type is not None:
|
||||
groups.append((current_type, "".join(current_chars)))
|
||||
|
||||
def escape_special(chars):
|
||||
res = []
|
||||
for c in chars:
|
||||
@@ -79,55 +124,36 @@ def suggest_patterns(s: str) -> list[str]:
|
||||
res.append(c)
|
||||
return "".join(res)
|
||||
|
||||
# 1. Exact counts for group types
|
||||
opt1_parts = []
|
||||
for gtype, gchars in groups:
|
||||
if gtype == 'U':
|
||||
opt1_parts.append(f"[A-Z]{{{len(gchars)}}}")
|
||||
elif gtype == 'L':
|
||||
opt1_parts.append(f"[a-z]{{{len(gchars)}}}")
|
||||
elif gtype == 'D':
|
||||
opt1_parts.append(f"\\d{{{len(gchars)}}}")
|
||||
else:
|
||||
opt1_parts.append(escape_special(gchars))
|
||||
opt1 = "^" + "".join(opt1_parts) + "$"
|
||||
|
||||
# 2. Variable counts for group types
|
||||
opt2_parts = []
|
||||
for gtype, gchars in groups:
|
||||
if gtype == 'U':
|
||||
opt2_parts.append("[A-Z]+")
|
||||
elif gtype == 'L':
|
||||
opt2_parts.append("[a-z]+")
|
||||
elif gtype == 'D':
|
||||
opt2_parts.append("\\d+")
|
||||
else:
|
||||
opt2_parts.append(escape_special(gchars))
|
||||
opt2 = "^" + "".join(opt2_parts) + "$"
|
||||
def build_pat(use_counts=True, merge_case=False):
|
||||
parts = []
|
||||
for gtype, gchars in groups:
|
||||
if gtype == 'C':
|
||||
parts.append(escape_special(gchars))
|
||||
elif gtype == 'U':
|
||||
if merge_case:
|
||||
parts.append(f"[A-Za-z]{{{len(gchars)}}}" if use_counts else "[A-Za-z]+")
|
||||
else:
|
||||
parts.append(f"[A-Z]{{{len(gchars)}}}" if use_counts else "[A-Z]+")
|
||||
elif gtype == 'L':
|
||||
if merge_case:
|
||||
parts.append(f"[A-Za-z]{{{len(gchars)}}}" if use_counts else "[A-Za-z]+")
|
||||
else:
|
||||
parts.append(f"[a-z]{{{len(gchars)}}}" if use_counts else "[a-z]+")
|
||||
elif gtype == 'D':
|
||||
parts.append(f"\\d{{{len(gchars)}}}" if use_counts else "\\d+")
|
||||
else:
|
||||
parts.append(escape_special(gchars))
|
||||
return "".join(parts)
|
||||
|
||||
# 3. Case-insensitive / merged letters with exact counts
|
||||
opt3_parts = []
|
||||
for gtype, gchars in groups:
|
||||
if gtype in ('U', 'L'):
|
||||
opt3_parts.append(f"[A-Za-z]{{{len(gchars)}}}")
|
||||
elif gtype == 'D':
|
||||
opt3_parts.append(f"\\d{{{len(gchars)}}}")
|
||||
else:
|
||||
opt3_parts.append(escape_special(gchars))
|
||||
opt3 = "^" + "".join(opt3_parts) + "$"
|
||||
candidates = []
|
||||
|
||||
# 4. Case-insensitive / merged letters with variable counts
|
||||
opt4_parts = []
|
||||
for gtype, gchars in groups:
|
||||
if gtype in ('U', 'L'):
|
||||
opt4_parts.append("[A-Za-z]+")
|
||||
elif gtype == 'D':
|
||||
opt4_parts.append("\\d+")
|
||||
else:
|
||||
opt4_parts.append(escape_special(gchars))
|
||||
opt4 = "^" + "".join(opt4_parts) + "$"
|
||||
# 1. Unanchored patterns (ideal for searching/scanning)
|
||||
candidates.append(build_pat(use_counts=True, merge_case=False))
|
||||
candidates.append(build_pat(use_counts=False, merge_case=False))
|
||||
candidates.append(build_pat(use_counts=True, merge_case=True))
|
||||
candidates.append(build_pat(use_counts=False, merge_case=True))
|
||||
|
||||
# 5. General alphanumeric character class plus unique special characters
|
||||
# Custom character class
|
||||
unique_specials = set()
|
||||
has_alpha = False
|
||||
has_digit = False
|
||||
@@ -139,7 +165,6 @@ def suggest_patterns(s: str) -> list[str]:
|
||||
has_digit = True
|
||||
else:
|
||||
unique_specials.add(c)
|
||||
|
||||
char_class_parts = []
|
||||
if has_alpha:
|
||||
char_class_parts.append("A-Za-z")
|
||||
@@ -150,13 +175,35 @@ def suggest_patterns(s: str) -> list[str]:
|
||||
char_class_parts.append("\\" + spec)
|
||||
else:
|
||||
char_class_parts.append(spec)
|
||||
opt5 = f"^[{''.join(char_class_parts)}]+$"
|
||||
candidates.append(f"[{''.join(char_class_parts)}]+")
|
||||
candidates.append(escape_special(s))
|
||||
|
||||
# 6. Literal exact escaped match
|
||||
opt6 = "^" + escape_special(s) + "$"
|
||||
# Wildcard patterns with .* and .*? (unanchored)
|
||||
if comp_idx == 0:
|
||||
suffix_part = s[len(comp_name):]
|
||||
esc_prefix = escape_special(matched_prefix)
|
||||
if suffix_part.startswith("{") and suffix_part.endswith("}"):
|
||||
candidates.append(f"{esc_prefix}\\{{.*\\}}")
|
||||
candidates.append(f"{esc_prefix}\\{{.*?\\}}")
|
||||
candidates.append(f"{esc_prefix}\\{{[^}}]*\\}}")
|
||||
candidates.append(f"{esc_prefix}\\{{[A-Za-z0-9_\\-]+\\}}")
|
||||
inner_content = suffix_part[1:-1]
|
||||
if all(c.islower() or c.isdigit() or c == '_' for c in inner_content):
|
||||
candidates.append(f"{esc_prefix}\\{{[a-z0-9_]+\\}}")
|
||||
else:
|
||||
candidates.append(f"{esc_prefix}.*")
|
||||
candidates.append(f"{esc_prefix}.*?")
|
||||
else:
|
||||
candidates.append(".*")
|
||||
candidates.append(".*?")
|
||||
|
||||
# 2. Anchored versions of the above (ideal for strict validation)
|
||||
anchored_candidates = []
|
||||
for cand in candidates:
|
||||
anchored_candidates.append(f"^{cand}$")
|
||||
candidates.extend(anchored_candidates)
|
||||
|
||||
# Deduplicate while preserving order
|
||||
candidates = [opt1, opt2, opt3, opt4, opt5, opt6]
|
||||
seen = set()
|
||||
result = []
|
||||
for c in candidates:
|
||||
|
||||
Reference in New Issue
Block a user