init
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package adminapi
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"strings"
|
||||
|
||||
"github.com/iceBear67/simplepages/api"
|
||||
"github.com/iceBear67/simplepages/internal/config"
|
||||
)
|
||||
|
||||
// Field length caps. They exist to stop a client filling the database with a
|
||||
// megabyte of display name, not to express any semantic limit.
|
||||
const (
|
||||
maxDisplayNameLen = 200
|
||||
maxKeyNameLen = 200
|
||||
maxSitePathLen = 1024
|
||||
maxCacheControlLen = 256
|
||||
)
|
||||
|
||||
// checkProjectName validates a name before it becomes a row and, later, a
|
||||
// "~name" entry inside $WEBROOT. The pattern is the only thing standing between
|
||||
// a project name and the filesystem, so this is a hard reject rather than a
|
||||
// normalisation.
|
||||
func checkProjectName(name string) error {
|
||||
if name == "" {
|
||||
return api.Errorf(api.CodeInvalidProjectName, "project name is required")
|
||||
}
|
||||
if !config.ProjectNamePattern.MatchString(name) {
|
||||
return api.Errorf(api.CodeInvalidProjectName,
|
||||
"project name must match %s", config.ProjectNamePattern.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkText rejects control characters in free-text fields.
|
||||
//
|
||||
// These strings are printed to operator terminals by "pages project list" and
|
||||
// written into structured logs. An embedded ESC would let whoever set the field
|
||||
// move the cursor, recolour the output or rewrite the line the operator is
|
||||
// reading; a newline would forge a second log record.
|
||||
func checkText(field, v string, max int) error {
|
||||
if len(v) > max {
|
||||
return api.Errorf(api.CodeBadRequest, "%s must be at most %d bytes", field, max)
|
||||
}
|
||||
for i := 0; i < len(v); i++ {
|
||||
if c := v[i]; c < 0x20 || c == 0x7f {
|
||||
return api.Errorf(api.CodeBadRequest, "%s must not contain control characters", field)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSitePath validates a path that names a document inside a deployment,
|
||||
// such as index_file or not_found_file.
|
||||
//
|
||||
// It is checked here as well as at serve time because a value that cannot
|
||||
// possibly resolve is a configuration mistake worth reporting at the moment it
|
||||
// is made, rather than as a silent 404 on every request afterwards.
|
||||
func checkSitePath(field, v string) error {
|
||||
if v == "" {
|
||||
return api.Errorf(api.CodeInvalidPath, "%s must not be empty", field)
|
||||
}
|
||||
if len(v) > maxSitePathLen {
|
||||
return api.Errorf(api.CodeInvalidPath, "%s must be at most %d bytes", field, maxSitePathLen)
|
||||
}
|
||||
// fs.ValidPath rejects "..", absolute paths, empty segments and a trailing
|
||||
// slash. It accepts ".", which is a directory rather than a document.
|
||||
if !fs.ValidPath(v) || v == "." {
|
||||
return api.Errorf(api.CodeInvalidPath,
|
||||
"%s must be a relative slash-separated path with no . or .. segments", field)
|
||||
}
|
||||
if strings.ContainsRune(v, '\\') {
|
||||
return api.Errorf(api.CodeInvalidPath, "%s must use forward slashes", field)
|
||||
}
|
||||
for i := 0; i < len(v); i++ {
|
||||
if c := v[i]; c < 0x20 || c == 0x7f {
|
||||
return api.Errorf(api.CodeInvalidPath, "%s must not contain control characters", field)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkHeaderValue validates a string that is emitted verbatim as a response
|
||||
// header value.
|
||||
//
|
||||
// A CR or LF here would be response splitting: the project could append headers
|
||||
// of its own choosing to every response it serves, and under path routing those
|
||||
// responses share an origin with every other project. net/http replaces newlines
|
||||
// with spaces on write, so this is defence in depth — but it is the layer that
|
||||
// keeps the bad value out of the database in the first place, and it is the one
|
||||
// that tells the operator they typed something wrong.
|
||||
func checkHeaderValue(field, v string) error {
|
||||
if len(v) > maxCacheControlLen {
|
||||
return api.Errorf(api.CodeBadRequest, "%s must be at most %d bytes", field, maxCacheControlLen)
|
||||
}
|
||||
for i := 0; i < len(v); i++ {
|
||||
c := v[i]
|
||||
if c == '\t' {
|
||||
continue
|
||||
}
|
||||
if c < 0x20 || c > 0x7e {
|
||||
return api.Errorf(api.CodeBadRequest,
|
||||
"%s must contain only printable ASCII (no newlines)", field)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user