Additional fixes and expansions on field dependencies. Still a WIP.
This commit is contained in:
parent
023acfaafe
commit
515eb27fe0
7 changed files with 419 additions and 5 deletions
|
|
@ -19,6 +19,52 @@ _ALLOWED_ATTRS = {
|
|||
"id", "name", "value",
|
||||
}
|
||||
|
||||
_SAFE_CSS_PROPS = {
|
||||
# spacing / sizing
|
||||
"margin","margin-top","margin-right","margin-bottom","margin-left",
|
||||
"padding","padding-top","padding-right","padding-bottom","padding-left",
|
||||
"width","height","min-width","min-height","max-width","max-height", "resize",
|
||||
# layout
|
||||
"display","flex","flex-direction","flex-wrap","justify-content","align-items","gap",
|
||||
# text
|
||||
"font-size","font-weight","line-height","text-align","white-space",
|
||||
# colors / background
|
||||
"color","background-color",
|
||||
# borders / radius
|
||||
"border","border-top","border-right","border-bottom","border-left",
|
||||
"border-width","border-style","border-color","border-radius",
|
||||
# misc (safe-ish)
|
||||
"opacity","overflow","overflow-x","overflow-y",
|
||||
}
|
||||
|
||||
_num_unit = r"-?\d+(?:\.\d+)?"
|
||||
_len_unit = r"(?:px|em|rem|%)"
|
||||
P_LEN = re.compile(rf"^{_num_unit}(?:{_len_unit})?$") # 12, 12px, 1.2rem, 50%
|
||||
P_GAP = P_LEN
|
||||
P_INT = re.compile(r"^\d+$")
|
||||
P_COLOR = re.compile(
|
||||
r"^(#[0-9a-fA-F]{3,8}|"
|
||||
r"rgb\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*\)|"
|
||||
r"rgba\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*(?:0|1|0?\.\d+)\s*\)|"
|
||||
r"[a-zA-Z]+)$"
|
||||
)
|
||||
|
||||
_ENUMS = {
|
||||
"display": {"block","inline","inline-block","flex","grid","none"},
|
||||
"flex-direction": {"row","row-reverse","column","column-reverse"},
|
||||
"flex-wrap": {"nowrap","wrap","wrap-reverse"},
|
||||
"justify-content": {"flex-start","flex-end","center","space-between","space-around","space-evenly"},
|
||||
"align-items": {"stretch","flex-start","flex-end","center","baseline"},
|
||||
"text-align": {"left","right","center","justify","start","end"},
|
||||
"white-space": {"normal","nowrap","pre","pre-wrap","pre-line","break-spaces"},
|
||||
"border-style": {"none","solid","dashed","dotted","double","groove","ridge","inset","outset"},
|
||||
"overflow": {"visible","hidden","scroll","auto","clip"},
|
||||
"overflow-x": {"visible","hidden","scroll","auto","clip"},
|
||||
"overflow-y": {"visible","hidden","scroll","auto","clip"},
|
||||
"font-weight": {"normal","bold","bolder","lighter","100","200","300","400","500","600","700","800","900"},
|
||||
"resize": {"none", "both", "horizontal", "vertical"},
|
||||
}
|
||||
|
||||
def get_env():
|
||||
app = current_app
|
||||
default_path = os.path.join(os.path.dirname(__file__), 'templates')
|
||||
|
|
@ -28,6 +74,79 @@ def get_env():
|
|||
loader=ChoiceLoader([app.jinja_loader, fallback_loader])
|
||||
)
|
||||
|
||||
def expand_projection(model_cls, fields):
|
||||
req = getattr(model_cls, "__crudkit_field_requires__", {}) or {}
|
||||
out = set(fields)
|
||||
for f in list(fields):
|
||||
for dep in req.get(f, ()):
|
||||
out.add(dep)
|
||||
return list(out)
|
||||
|
||||
def _clean_css_value(prop: str, raw: str) -> str | None:
|
||||
v = raw.strip()
|
||||
|
||||
v = v.replace("!important", "")
|
||||
low = v.lower()
|
||||
if any(bad in low for bad in ("url(", "expression(", "javascript:", "var(")):
|
||||
return None
|
||||
|
||||
if prop in {"width","height","min-width","min-height","max-width","max-height",
|
||||
"margin","margin-top","margin-right","margin-bottom","margin-left",
|
||||
"padding","padding-top","padding-right","padding-bottom","padding-left",
|
||||
"border-width","border-top","border-right","border-bottom","border-left","border-radius",
|
||||
"line-height","font-size"}:
|
||||
return v if P_LEN.match(v) else None
|
||||
|
||||
if prop in {"gap"}:
|
||||
parts = [p.strip() for p in v.split()]
|
||||
if 1 <= len(parts) <= 2 and all(P_GAP.match(p) for p in parts):
|
||||
return " ".join(parts)
|
||||
return None
|
||||
|
||||
if prop in {"color", "background-color", "border-color"}:
|
||||
return v if P_COLOR.match(v) else None
|
||||
|
||||
if prop in _ENUMS:
|
||||
return v if v.lower() in _ENUMS[prop] else None
|
||||
|
||||
if prop == "flex":
|
||||
toks = v.split()
|
||||
if len(toks) == 1 and (toks[0].isdigit() or toks[0] in {"auto", "none"}):
|
||||
return v
|
||||
if len(toks) == 2 and toks[0].isdigit() and (toks[1].isdigit() or toks[1] == "auto"):
|
||||
return v
|
||||
if len(toks) == 3 and toks[0].isdigit() and toks[1].isdigit() and (P_LEN.match(toks[2]) or toks[2] == "auto"):
|
||||
return " ".join(toks)
|
||||
return None
|
||||
|
||||
if prop == "border":
|
||||
parts = v.split()
|
||||
bw = next((p for p in parts if P_LEN.match(p)), None)
|
||||
bs = next((p for p in parts if p in _ENUMS["border-style"]), None)
|
||||
bc = next((p for p in parts if P_COLOR.match(p)), None)
|
||||
chosen = [x for x in (bw, bs, bc) if x]
|
||||
return " ".join(chosen) if chosen else None
|
||||
|
||||
return None
|
||||
|
||||
def _sanitize_style(style: str | None) -> str | None:
|
||||
if not style or not isinstance(style, str):
|
||||
return None
|
||||
safe_decls = []
|
||||
for chunk in style.split(";"):
|
||||
if not chunk.strip():
|
||||
continue
|
||||
if ":" not in chunk:
|
||||
continue
|
||||
prop, val = chunk.split(":", 1)
|
||||
prop = prop.strip().lower()
|
||||
if prop not in _SAFE_CSS_PROPS:
|
||||
continue
|
||||
clean = _clean_css_value(prop, val)
|
||||
if clean is not None and clean != "":
|
||||
safe_decls.append(f"{prop}: {clean}")
|
||||
return "; ".join(safe_decls) if safe_decls else None
|
||||
|
||||
def _is_column_attr(attr) -> bool:
|
||||
try:
|
||||
return isinstance(attr, InstrumentedAttribute) and isinstance(attr.property, ColumnProperty)
|
||||
|
|
@ -217,6 +336,11 @@ def _sanitize_attrs(attrs: Any) -> dict[str, Any]:
|
|||
elif isinstance(v, str):
|
||||
if len(v) > 512:
|
||||
v = v[:512]
|
||||
if k == "style":
|
||||
sv = _sanitize_style(v)
|
||||
if sv:
|
||||
out["style"] = sv
|
||||
continue
|
||||
if k.startswith("data-") or k.startswith("aria-") or k in _ALLOWED_ATTRS:
|
||||
if isinstance(v, bool):
|
||||
if v:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue