Cybersecurity-Projects/PROJECTS/beginner/base64-tool/src/base64_tool/encoders.py

108 lines
2.4 KiB
Python

"""
©AngelaMos | 2026
encoders.py
"""
import base64 as b64
import binascii
from collections.abc import Callable
from urllib.parse import (
quote,
quote_plus,
unquote,
unquote_plus,
)
from base64_tool.constants import EncodingFormat
type EncoderFn = Callable[[bytes], str]
type DecoderFn = Callable[[str], bytes]
def encode_base64(data: bytes) -> str:
return b64.b64encode(data).decode("ascii")
def decode_base64(data: str) -> bytes:
cleaned = "".join(data.split())
return b64.b64decode(cleaned, validate=True)
def encode_base64url(data: bytes) -> str:
return b64.urlsafe_b64encode(data).decode("ascii")
def decode_base64url(data: str) -> bytes:
cleaned = "".join(data.split())
return b64.urlsafe_b64decode(cleaned)
def encode_base32(data: bytes) -> str:
return b64.b32encode(data).decode("ascii")
def decode_base32(data: str) -> bytes:
cleaned = "".join(data.split()).upper()
return b64.b32decode(cleaned)
def encode_hex(data: bytes) -> str:
return data.hex()
def decode_hex(data: str) -> bytes:
cleaned = data.strip()
for sep in (" ", ":", "-", "."):
cleaned = cleaned.replace(sep, "")
return bytes.fromhex(cleaned)
def encode_url(data: bytes, *, form: bool = False) -> str:
text = data.decode("utf-8")
if form:
return quote_plus(text)
return quote(text, safe="")
def decode_url(data: str, *, form: bool = False) -> bytes:
if form:
return unquote_plus(data).encode("utf-8")
return unquote(data).encode("utf-8")
ENCODER_REGISTRY: dict[
EncodingFormat,
tuple[EncoderFn, DecoderFn],
] = {
EncodingFormat.BASE64: (encode_base64, decode_base64),
EncodingFormat.BASE64URL: (encode_base64url, decode_base64url),
EncodingFormat.BASE32: (encode_base32, decode_base32),
EncodingFormat.HEX: (encode_hex, decode_hex),
EncodingFormat.URL: (
lambda data: encode_url(data),
lambda data: decode_url(data),
),
}
def encode(data: bytes, fmt: EncodingFormat) -> str:
encoder_fn, _ = ENCODER_REGISTRY[fmt]
return encoder_fn(data)
def decode(data: str, fmt: EncodingFormat) -> bytes:
_, decoder_fn = ENCODER_REGISTRY[fmt]
return decoder_fn(data)
def try_decode(data: str, fmt: EncodingFormat) -> bytes | None:
try:
return decode(data, fmt)
except (
ValueError,
binascii.Error,
UnicodeDecodeError,
UnicodeEncodeError,
):
return None