Repository navigation
v0.1.45
Two validation gaps, both of which made a form look validated when it was
not.
Upload rules that actually run
Dimensions and MimeTypes assert their value to a string and os.Open it.
An uploaded file arrives as *multipart.FileHeader, which is the only shape
they ever meet in a request — so the assertion fails, the field is returned
unchanged, and the rule silently does nothing.
v.Field("avatar", input.Avatar).
Required().
MaxSize(2 << 20).
FileTypes("image/png", "image/jpeg").
ImageDimensions(128, 128, 2048, 2048)MaxSize, MinSize, FileTypes, Image, ImageDimensions.
FileTypes sniffs the first 512 bytes rather than trusting the filename
or the browser's Content-Type, both of which are client-supplied. A .png
extension on an HTML file is how a stored upload becomes an XSS vector once
it's served from the same origin.
Every rule rewinds, so a later rule and the handler still see the whole file.
Also registers the gif/jpeg/png decoders. image.DecodeConfig dispatches
on registered formats, so without them every call returns ErrFormat — the
existing Dimensions rule has had that latent the whole time, and a rule that
rejects every image is worse than no rule. WebP and AVIF are not in the
standard library; register golang.org/x/image yourself if you accept them.
vField.Check and database-backed rules
v.Field("handle", input.Handle).Required().Check(db.Unique(c.App(), "users", "handle"))Custom cannot express the difference between "this value is invalid" and
"I could not tell". For a database-backed rule that difference is the whole
thing: reporting a failed lookup as invalid tells someone their handle is
taken when the database is simply unreachable — and they will pick a different
handle, permanently, over a transient outage.
Check takes (ok, message, error), and Validator.Err() surfaces the
error, so a handler answers 503 rather than 422.
db.Unique and db.Exists live in api/db, not on the validator, because
api/db imports api/app and the dependency can only point one way. Table
and column names are interpolated — no dialect lets a placeholder stand in for
an identifier — so they are validated as bare identifiers and anything else is
refused.