package models import ( "strings" "time" "unicode" "unicode/utf8" "git.golem15.com/golem15/summercms/modules/lagoon" ) // ExtraTypeKey is the type_key of the settings' additional addresses in // golem15_sitemap_urls; their record_id is the hex SHA-256 of the address. const ExtraTypeKey = "extra" // ExtraGroup is the file group of the additional addresses. const ExtraGroup = "pages" // The excluded reasons the sitemap sets itself. A Filter's reasons // (contract.ReasonNoindex and the others) are stored as it gives them. const ( // ReasonDeleted is an address whose record or locale is gone. ReasonDeleted = "deleted" // ReasonUnpublished is an address whose record is not published. ReasonUnpublished = "unpublished" ) // Alternate is one hreflang alternate of an address: Hreflang the locale // code or x-default, Href the alternate's path or absolute URL. type Alternate struct { Hreflang string `json:"hreflang"` Href string `json:"href"` } // Ref points at a record: Type is its content type key and ID the record's // ID within it (pact.ContentRef as stored in parent_refs). type Ref struct { Type string `json:"type"` ID string `json:"id"` } // URL is one row of the slot table golem15_sitemap_urls (D-30): an address // of a content type's record in one locale, or an additional address. Grp // and FileNo place it in the file sitemap--.xml; FileNo is // nil until the address is numbered. ExcludedReason is "" for a listed // address; a row left out keeps its FileNo, so its file keeps a hole. // Images are the page's image addresses (D-31), ImageFileNo the number of // its sitemap-images file (nil while it has none). Kind is the content // type's kind (pact.ContentKindArticle and the others), PublishedAt the // record's first publication and ParentRefs its parents, for the News // sitemap and the HTML sitemap. type URL struct { ID uint64 `gorm:"column:id;primaryKey"` Grp string `gorm:"column:grp"` TypeKey string `gorm:"column:type_key"` RecordID string `gorm:"column:record_id"` Locale string `gorm:"column:locale"` URL string `gorm:"column:url"` Title string `gorm:"column:title"` FileNo *int `gorm:"column:file_no"` FirstSeen time.Time `gorm:"column:first_seen"` LastMod *time.Time `gorm:"column:lastmod"` Alternates lagoon.Jsonable[[]Alternate] `gorm:"column:alternates"` Images lagoon.Jsonable[[]string] `gorm:"column:images"` ImageFileNo *int `gorm:"column:image_file_no"` Kind string `gorm:"column:kind"` PublishedAt *time.Time `gorm:"column:published_at"` ParentRefs lagoon.Jsonable[[]Ref] `gorm:"column:parent_refs"` ExcludedReason string `gorm:"column:excluded_reason"` UpdatedAt time.Time `gorm:"column:updated_at"` } func (URL) TableName() string { return "golem15_sitemap_urls" } func init() { Register(URL{}) } // AllowedAddress reports whether u may be a stored address: a path that // starts with a single / (no whitespace, control characters, quotes, < or // >), or an absolute http(s) URL that AllowedFrontendURL accepts. func AllowedAddress(u string) bool { if u == "" || !utf8.ValidString(u) { return false } if strings.HasPrefix(u, "/") && !strings.HasPrefix(u, "//") { return !strings.ContainsFunc(u, func(r rune) bool { return unicode.IsControl(r) || unicode.IsSpace(r) || r == '"' || r == '<' || r == '>' || r == '\\' }) } return AllowedFrontendURL(u) }