Files
sm-menu-plugin/classes/links.go
Jakub Zych 08f99b8aad feat(17-04): menu plugin admin with registry types, URL checks and tree reorder
- golem15.menu plugin, tables golem15_menu_menus and golem15_menu_menu_entries with the PHP columns plus a nullable url_pattern (D-02)
- MenuEntry BeforeSave ports PHP beforeSave: nest columns 0, menu from the parent, sort_order after the menu's last
- entry type dropdown: url, divider and the signpost registry types; reference depends on type through signpost.ReferenceOptions
- URL scheme allowlist (errors.url_scheme), target _self or _blank, menu and parent cycle checks
- menu filter, toolbar reorder/create/delete and config_reorder.yaml partitioned by menu
- navigation Menus with Menus and Menu entries, permission golem15.menu.manage_menu, en and pl phrasebooks
- TestMenuAdminSmoke
2026-10-12 17:30:50 +02:00

47 lines
1.2 KiB
Go

// Package classes holds the menu plugin's link rules and the tree the public
// API serves.
package classes
import "strings"
// AllowedURL reports whether an entry URL may be stored and served: empty, a
// path starting with "/", a fragment starting with "#", or an absolute
// http, https, mailto or tel URL (case-insensitive). Anything else, such as
// javascript: or data:, is refused, and so is a URL with surrounding
// whitespace or control characters, which browsers strip before reading the
// scheme (RESEARCH Pitfall 15).
func AllowedURL(url string) bool {
if url == "" {
return true
}
for _, r := range url {
if r < 0x20 || r == 0x7f {
return false
}
}
if strings.TrimSpace(url) != url {
return false
}
if url[0] == '/' || url[0] == '#' {
return true
}
lower := strings.ToLower(url)
for _, prefix := range []string{"http://", "https://", "mailto:", "tel:"} {
if strings.HasPrefix(lower, prefix) {
return true
}
}
return false
}
// The link targets an entry may open in.
const (
TargetSelf = "_self"
TargetBlank = "_blank"
)
// AllowedTarget reports whether target is _self, _blank or empty.
func AllowedTarget(target string) bool {
return target == "" || target == TargetSelf || target == TargetBlank
}