mirror of
https://github.com/miniflux/v2.git
synced 2025-08-11 17:51:01 +00:00
refactor: unexport symbols
This commit is contained in:
parent
a4d51b5586
commit
566670cc06
36 changed files with 369 additions and 376 deletions
|
@ -1,7 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package config // import "miniflux.app/v2/internal/config"
|
||||
|
||||
// Opts holds parsed configuration options.
|
||||
var Opts *options
|
|
@ -100,6 +100,9 @@ type option struct {
|
|||
Value any
|
||||
}
|
||||
|
||||
// Opts holds parsed configuration options.
|
||||
var Opts *options
|
||||
|
||||
// options contains configuration options.
|
||||
type options struct {
|
||||
HTTPS bool
|
||||
|
|
|
@ -135,14 +135,14 @@ var (
|
|||
)
|
||||
)
|
||||
|
||||
// Collector represents a metric collector.
|
||||
type Collector struct {
|
||||
// collector represents a metric collector.
|
||||
type collector struct {
|
||||
store *storage.Storage
|
||||
refreshInterval int
|
||||
}
|
||||
|
||||
// NewCollector initializes a new metric collector.
|
||||
func NewCollector(store *storage.Storage, refreshInterval int) *Collector {
|
||||
func NewCollector(store *storage.Storage, refreshInterval int) *collector {
|
||||
prometheus.MustRegister(BackgroundFeedRefreshDuration)
|
||||
prometheus.MustRegister(ScraperRequestDuration)
|
||||
prometheus.MustRegister(ArchiveEntriesDuration)
|
||||
|
@ -158,11 +158,11 @@ func NewCollector(store *storage.Storage, refreshInterval int) *Collector {
|
|||
prometheus.MustRegister(dbConnectionsMaxIdleTimeClosedGauge)
|
||||
prometheus.MustRegister(dbConnectionsMaxLifetimeClosedGauge)
|
||||
|
||||
return &Collector{store, refreshInterval}
|
||||
return &collector{store, refreshInterval}
|
||||
}
|
||||
|
||||
// GatherStorageMetrics polls the database to fetch metrics.
|
||||
func (c *Collector) GatherStorageMetrics() {
|
||||
func (c *collector) GatherStorageMetrics() {
|
||||
for range time.Tick(time.Duration(c.refreshInterval) * time.Second) {
|
||||
slog.Debug("Collecting metrics from the database")
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
// Specs: http://web.archive.org/web/20060811235523/http://www.mnot.net/drafts/draft-nottingham-atom-format-02.html
|
||||
type Atom03Feed struct {
|
||||
type atom03Feed struct {
|
||||
Version string `xml:"version,attr"`
|
||||
|
||||
// The "atom:id" element's content conveys a permanent, globally unique identifier for the feed.
|
||||
|
@ -21,14 +21,14 @@ type Atom03Feed struct {
|
|||
// The "atom:title" element is a Content construct that conveys a human-readable title for the feed.
|
||||
// atom:feed elements MUST contain exactly one atom:title element.
|
||||
// If the feed describes a Web resource, its content SHOULD be the same as that resource's title.
|
||||
Title Atom03Content `xml:"http://purl.org/atom/ns# title"`
|
||||
Title atom03Content `xml:"http://purl.org/atom/ns# title"`
|
||||
|
||||
// The "atom:link" element is a Link construct that conveys a URI associated with the feed.
|
||||
// The nature of the relationship as well as the link itself is determined by the element's content.
|
||||
// atom:feed elements MUST contain at least one atom:link element with a rel attribute value of "alternate".
|
||||
// atom:feed elements MUST NOT contain more than one atom:link element with a rel attribute value of "alternate" that has the same type attribute value.
|
||||
// atom:feed elements MAY contain additional atom:link elements beyond those described above.
|
||||
Links AtomLinks `xml:"http://purl.org/atom/ns# link"`
|
||||
Links atomLinks `xml:"http://purl.org/atom/ns# link"`
|
||||
|
||||
// The "atom:author" element is a Person construct that indicates the default author of the feed.
|
||||
// atom:feed elements MUST contain exactly one atom:author element,
|
||||
|
@ -38,10 +38,10 @@ type Atom03Feed struct {
|
|||
|
||||
// The "atom:entry" element's represents an individual entry that is contained by the feed.
|
||||
// atom:feed elements MAY contain one or more atom:entry elements.
|
||||
Entries []Atom03Entry `xml:"http://purl.org/atom/ns# entry"`
|
||||
Entries []atom03Entry `xml:"http://purl.org/atom/ns# entry"`
|
||||
}
|
||||
|
||||
type Atom03Entry struct {
|
||||
type atom03Entry struct {
|
||||
// The "atom:id" element's content conveys a permanent, globally unique identifier for the entry.
|
||||
// It MUST NOT change over time, even if other representations of the entry (such as a web representation pointed to by the entry's atom:link element) are relocated.
|
||||
// If the same entry is syndicated in two atom:feeds published by the same entity, the entry's atom:id MUST be the same in both feeds.
|
||||
|
@ -50,7 +50,7 @@ type Atom03Entry struct {
|
|||
// The "atom:title" element is a Content construct that conveys a human-readable title for the entry.
|
||||
// atom:entry elements MUST have exactly one "atom:title" element.
|
||||
// If an entry describes a Web resource, its content SHOULD be the same as that resource's title.
|
||||
Title Atom03Content `xml:"title"`
|
||||
Title atom03Content `xml:"title"`
|
||||
|
||||
// The "atom:modified" element is a Date construct that indicates the time that the entry was last modified.
|
||||
// atom:entry elements MUST contain an atom:modified element, but MUST NOT contain more than one.
|
||||
|
@ -73,15 +73,15 @@ type Atom03Entry struct {
|
|||
// atom:entry elements MUST contain at least one atom:link element with a rel attribute value of "alternate".
|
||||
// atom:entry elements MUST NOT contain more than one atom:link element with a rel attribute value of "alternate" that has the same type attribute value.
|
||||
// atom:entry elements MAY contain additional atom:link elements beyond those described above.
|
||||
Links AtomLinks `xml:"link"`
|
||||
Links atomLinks `xml:"link"`
|
||||
|
||||
// The "atom:summary" element is a Content construct that conveys a short summary, abstract or excerpt of the entry.
|
||||
// atom:entry elements MAY contain an atom:created element, but MUST NOT contain more than one.
|
||||
Summary Atom03Content `xml:"summary"`
|
||||
Summary atom03Content `xml:"summary"`
|
||||
|
||||
// The "atom:content" element is a Content construct that conveys the content of the entry.
|
||||
// atom:entry elements MAY contain one or more atom:content elements.
|
||||
Content Atom03Content `xml:"content"`
|
||||
Content atom03Content `xml:"content"`
|
||||
|
||||
// The "atom:author" element is a Person construct that indicates the default author of the entry.
|
||||
// atom:entry elements MUST contain exactly one atom:author element,
|
||||
|
@ -90,7 +90,7 @@ type Atom03Entry struct {
|
|||
Author AtomPerson `xml:"author"`
|
||||
}
|
||||
|
||||
type Atom03Content struct {
|
||||
type atom03Content struct {
|
||||
// Content constructs MAY have a "type" attribute, whose value indicates the media type of the content.
|
||||
// When present, this attribute's value MUST be a registered media type [RFC2045].
|
||||
// If not present, its value MUST be considered to be "text/plain".
|
||||
|
@ -113,7 +113,7 @@ type Atom03Content struct {
|
|||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
func (a *Atom03Content) Content() string {
|
||||
func (a *atom03Content) content() string {
|
||||
content := ""
|
||||
|
||||
switch a.Mode {
|
||||
|
|
|
@ -14,15 +14,16 @@ import (
|
|||
"miniflux.app/v2/internal/urllib"
|
||||
)
|
||||
|
||||
type Atom03Adapter struct {
|
||||
atomFeed *Atom03Feed
|
||||
type atom03Adapter struct {
|
||||
atomFeed *atom03Feed
|
||||
}
|
||||
|
||||
func NewAtom03Adapter(atomFeed *Atom03Feed) *Atom03Adapter {
|
||||
return &Atom03Adapter{atomFeed}
|
||||
// TODO No need for a constructor, as it's only used in this package
|
||||
func NewAtom03Adapter(atomFeed *atom03Feed) *atom03Adapter {
|
||||
return &atom03Adapter{atomFeed}
|
||||
}
|
||||
|
||||
func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
||||
func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
|
||||
feed := new(model.Feed)
|
||||
|
||||
// Populate the feed URL.
|
||||
|
@ -36,7 +37,7 @@ func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Populate the site URL.
|
||||
siteURL := a.atomFeed.Links.OriginalLink()
|
||||
siteURL := a.atomFeed.Links.originalLink()
|
||||
if siteURL != "" {
|
||||
if absoluteSiteURL, err := urllib.AbsoluteURL(baseURL, siteURL); err == nil {
|
||||
feed.SiteURL = absoluteSiteURL
|
||||
|
@ -46,7 +47,7 @@ func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Populate the feed title.
|
||||
feed.Title = a.atomFeed.Title.Content()
|
||||
feed.Title = a.atomFeed.Title.content()
|
||||
if feed.Title == "" {
|
||||
feed.Title = feed.SiteURL
|
||||
}
|
||||
|
@ -55,7 +56,7 @@ func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
entry := model.NewEntry()
|
||||
|
||||
// Populate the entry URL.
|
||||
entry.URL = atomEntry.Links.OriginalLink()
|
||||
entry.URL = atomEntry.Links.originalLink()
|
||||
if entry.URL != "" {
|
||||
if absoluteEntryURL, err := urllib.AbsoluteURL(feed.SiteURL, entry.URL); err == nil {
|
||||
entry.URL = absoluteEntryURL
|
||||
|
@ -63,13 +64,13 @@ func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Populate the entry content.
|
||||
entry.Content = atomEntry.Content.Content()
|
||||
entry.Content = atomEntry.Content.content()
|
||||
if entry.Content == "" {
|
||||
entry.Content = atomEntry.Summary.Content()
|
||||
entry.Content = atomEntry.Summary.content()
|
||||
}
|
||||
|
||||
// Populate the entry title.
|
||||
entry.Title = atomEntry.Title.Content()
|
||||
entry.Title = atomEntry.Title.content()
|
||||
if entry.Title == "" {
|
||||
entry.Title = sanitizer.TruncateHTML(entry.Content, 100)
|
||||
}
|
||||
|
@ -101,7 +102,7 @@ func (a *Atom03Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Generate the entry hash.
|
||||
for _, value := range []string{atomEntry.ID, atomEntry.Links.OriginalLink()} {
|
||||
for _, value := range []string{atomEntry.ID, atomEntry.Links.originalLink()} {
|
||||
if value != "" {
|
||||
entry.Hash = crypto.SHA256(value)
|
||||
break
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
// Specs:
|
||||
// https://tools.ietf.org/html/rfc4287
|
||||
// https://validator.w3.org/feed/docs/atom.html
|
||||
type Atom10Feed struct {
|
||||
type atom10Feed struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"`
|
||||
|
||||
// The "atom:id" element conveys a permanent, universally unique
|
||||
|
@ -37,11 +37,11 @@ type Atom10Feed struct {
|
|||
// readable title for an entry or feed.
|
||||
//
|
||||
// atom:feed elements MUST contain exactly one atom:title element.
|
||||
Title Atom10Text `xml:"http://www.w3.org/2005/Atom title"`
|
||||
Title atom10Text `xml:"http://www.w3.org/2005/Atom title"`
|
||||
|
||||
// The "atom:subtitle" element is a Text construct that
|
||||
// contains a human-readable description or subtitle for the feed.
|
||||
Subtitle Atom10Text `xml:"http://www.w3.org/2005/Atom subtitle"`
|
||||
Subtitle atom10Text `xml:"http://www.w3.org/2005/Atom subtitle"`
|
||||
|
||||
// The "atom:author" element is a Person construct that indicates the
|
||||
// author of the entry or feed.
|
||||
|
@ -49,7 +49,7 @@ type Atom10Feed struct {
|
|||
// atom:feed elements MUST contain one or more atom:author elements,
|
||||
// unless all of the atom:feed element's child atom:entry elements
|
||||
// contain at least one atom:author element.
|
||||
Authors AtomPersons `xml:"http://www.w3.org/2005/Atom author"`
|
||||
Authors atomPersons `xml:"http://www.w3.org/2005/Atom author"`
|
||||
|
||||
// The "atom:icon" element's content is an IRI reference [RFC3987] that
|
||||
// identifies an image that provides iconic visual identification for a
|
||||
|
@ -71,7 +71,7 @@ type Atom10Feed struct {
|
|||
// atom:feed elements MUST NOT contain more than one atom:link
|
||||
// element with a rel attribute value of "alternate" that has the
|
||||
// same combination of type and hreflang attribute values.
|
||||
Links AtomLinks `xml:"http://www.w3.org/2005/Atom link"`
|
||||
Links atomLinks `xml:"http://www.w3.org/2005/Atom link"`
|
||||
|
||||
// The "atom:category" element conveys information about a category
|
||||
// associated with an entry or feed. This specification assigns no
|
||||
|
@ -79,12 +79,12 @@ type Atom10Feed struct {
|
|||
//
|
||||
// atom:feed elements MAY contain any number of atom:category
|
||||
// elements.
|
||||
Categories AtomCategories `xml:"http://www.w3.org/2005/Atom category"`
|
||||
Categories atomCategories `xml:"http://www.w3.org/2005/Atom category"`
|
||||
|
||||
Entries []Atom10Entry `xml:"http://www.w3.org/2005/Atom entry"`
|
||||
Entries []atom10Entry `xml:"http://www.w3.org/2005/Atom entry"`
|
||||
}
|
||||
|
||||
type Atom10Entry struct {
|
||||
type atom10Entry struct {
|
||||
// The "atom:id" element conveys a permanent, universally unique
|
||||
// identifier for an entry or feed.
|
||||
//
|
||||
|
@ -100,7 +100,7 @@ type Atom10Entry struct {
|
|||
// readable title for an entry or feed.
|
||||
//
|
||||
// atom:entry elements MUST contain exactly one atom:title element.
|
||||
Title Atom10Text `xml:"http://www.w3.org/2005/Atom title"`
|
||||
Title atom10Text `xml:"http://www.w3.org/2005/Atom title"`
|
||||
|
||||
// The "atom:published" element is a Date construct indicating an
|
||||
// instant in time associated with an event early in the life cycle of
|
||||
|
@ -118,7 +118,7 @@ type Atom10Entry struct {
|
|||
// atom:entry elements MUST NOT contain more than one atom:link
|
||||
// element with a rel attribute value of "alternate" that has the
|
||||
// same combination of type and hreflang attribute values.
|
||||
Links AtomLinks `xml:"http://www.w3.org/2005/Atom link"`
|
||||
Links atomLinks `xml:"http://www.w3.org/2005/Atom link"`
|
||||
|
||||
// atom:entry elements MUST contain an atom:summary element in either
|
||||
// of the following cases:
|
||||
|
@ -131,17 +131,17 @@ type Atom10Entry struct {
|
|||
//
|
||||
// atom:entry elements MUST NOT contain more than one atom:summary
|
||||
// element.
|
||||
Summary Atom10Text `xml:"http://www.w3.org/2005/Atom summary"`
|
||||
Summary atom10Text `xml:"http://www.w3.org/2005/Atom summary"`
|
||||
|
||||
// atom:entry elements MUST NOT contain more than one atom:content
|
||||
// element.
|
||||
Content Atom10Text `xml:"http://www.w3.org/2005/Atom content"`
|
||||
Content atom10Text `xml:"http://www.w3.org/2005/Atom content"`
|
||||
|
||||
// The "atom:author" element is a Person construct that indicates the
|
||||
// author of the entry or feed.
|
||||
//
|
||||
// atom:entry elements MUST contain one or more atom:author elements
|
||||
Authors AtomPersons `xml:"http://www.w3.org/2005/Atom author"`
|
||||
Authors atomPersons `xml:"http://www.w3.org/2005/Atom author"`
|
||||
|
||||
// The "atom:category" element conveys information about a category
|
||||
// associated with an entry or feed. This specification assigns no
|
||||
|
@ -149,7 +149,7 @@ type Atom10Entry struct {
|
|||
//
|
||||
// atom:entry elements MAY contain any number of atom:category
|
||||
// elements.
|
||||
Categories AtomCategories `xml:"http://www.w3.org/2005/Atom category"`
|
||||
Categories atomCategories `xml:"http://www.w3.org/2005/Atom category"`
|
||||
|
||||
media.MediaItemElement
|
||||
}
|
||||
|
@ -160,14 +160,14 @@ type Atom10Entry struct {
|
|||
// Text: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.1
|
||||
// HTML: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.2
|
||||
// XHTML: https://datatracker.ietf.org/doc/html/rfc4287#section-3.1.1.3
|
||||
type Atom10Text struct {
|
||||
type atom10Text struct {
|
||||
Type string `xml:"type,attr"`
|
||||
CharData string `xml:",chardata"`
|
||||
InnerXML string `xml:",innerxml"`
|
||||
XHTMLRootElement AtomXHTMLRootElement `xml:"http://www.w3.org/1999/xhtml div"`
|
||||
XHTMLRootElement atomXHTMLRootElement `xml:"http://www.w3.org/1999/xhtml div"`
|
||||
}
|
||||
|
||||
func (a *Atom10Text) Body() string {
|
||||
func (a *atom10Text) body() string {
|
||||
var content string
|
||||
|
||||
if strings.EqualFold(a.Type, "xhtml") {
|
||||
|
@ -179,7 +179,7 @@ func (a *Atom10Text) Body() string {
|
|||
return strings.TrimSpace(content)
|
||||
}
|
||||
|
||||
func (a *Atom10Text) Title() string {
|
||||
func (a *atom10Text) title() string {
|
||||
var content string
|
||||
|
||||
switch {
|
||||
|
@ -194,14 +194,14 @@ func (a *Atom10Text) Title() string {
|
|||
return strings.TrimSpace(content)
|
||||
}
|
||||
|
||||
func (a *Atom10Text) xhtmlContent() string {
|
||||
func (a *atom10Text) xhtmlContent() string {
|
||||
if a.XHTMLRootElement.XMLName.Local == "div" {
|
||||
return a.XHTMLRootElement.InnerXML
|
||||
}
|
||||
return a.InnerXML
|
||||
}
|
||||
|
||||
type AtomXHTMLRootElement struct {
|
||||
type atomXHTMLRootElement struct {
|
||||
XMLName xml.Name `xml:"div"`
|
||||
InnerXML string `xml:",innerxml"`
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ import (
|
|||
)
|
||||
|
||||
type Atom10Adapter struct {
|
||||
atomFeed *Atom10Feed
|
||||
atomFeed *atom10Feed
|
||||
}
|
||||
|
||||
func NewAtom10Adapter(atomFeed *Atom10Feed) *Atom10Adapter {
|
||||
func NewAtom10Adapter(atomFeed *atom10Feed) *Atom10Adapter {
|
||||
return &Atom10Adapter{atomFeed}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ func (a *Atom10Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Populate the site URL.
|
||||
siteURL := a.atomFeed.Links.OriginalLink()
|
||||
siteURL := a.atomFeed.Links.originalLink()
|
||||
if siteURL != "" {
|
||||
if absoluteSiteURL, err := urllib.AbsoluteURL(baseURL, siteURL); err == nil {
|
||||
feed.SiteURL = absoluteSiteURL
|
||||
|
@ -50,13 +50,13 @@ func (a *Atom10Adapter) BuildFeed(baseURL string) *model.Feed {
|
|||
}
|
||||
|
||||
// Populate the feed title.
|
||||
feed.Title = a.atomFeed.Title.Body()
|
||||
feed.Title = a.atomFeed.Title.body()
|
||||
if feed.Title == "" {
|
||||
feed.Title = feed.SiteURL
|
||||
}
|
||||
|
||||
// Populate the feed description.
|
||||
feed.Description = a.atomFeed.Subtitle.Body()
|
||||
feed.Description = a.atomFeed.Subtitle.body()
|
||||
|
||||
// Populate the feed icon.
|
||||
if a.atomFeed.Icon != "" {
|
||||
|
@ -79,7 +79,7 @@ func (a *Atom10Adapter) populateEntries(siteURL string) model.Entries {
|
|||
entry := model.NewEntry()
|
||||
|
||||
// Populate the entry URL.
|
||||
entry.URL = atomEntry.Links.OriginalLink()
|
||||
entry.URL = atomEntry.Links.originalLink()
|
||||
if entry.URL != "" {
|
||||
if absoluteEntryURL, err := urllib.AbsoluteURL(siteURL, entry.URL); err == nil {
|
||||
entry.URL = absoluteEntryURL
|
||||
|
@ -87,16 +87,16 @@ func (a *Atom10Adapter) populateEntries(siteURL string) model.Entries {
|
|||
}
|
||||
|
||||
// Populate the entry content.
|
||||
entry.Content = atomEntry.Content.Body()
|
||||
entry.Content = atomEntry.Content.body()
|
||||
if entry.Content == "" {
|
||||
entry.Content = atomEntry.Summary.Body()
|
||||
entry.Content = atomEntry.Summary.body()
|
||||
if entry.Content == "" {
|
||||
entry.Content = atomEntry.FirstMediaDescription()
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the entry title.
|
||||
entry.Title = atomEntry.Title.Title()
|
||||
entry.Title = atomEntry.Title.title()
|
||||
if entry.Title == "" {
|
||||
entry.Title = sanitizer.TruncateHTML(entry.Content, 100)
|
||||
if entry.Title == "" {
|
||||
|
@ -105,9 +105,9 @@ func (a *Atom10Adapter) populateEntries(siteURL string) model.Entries {
|
|||
}
|
||||
|
||||
// Populate the entry author.
|
||||
authors := atomEntry.Authors.PersonNames()
|
||||
authors := atomEntry.Authors.personNames()
|
||||
if len(authors) == 0 {
|
||||
authors = a.atomFeed.Authors.PersonNames()
|
||||
authors = a.atomFeed.Authors.personNames()
|
||||
}
|
||||
sort.Strings(authors)
|
||||
authors = slices.Compact(authors)
|
||||
|
@ -152,7 +152,7 @@ func (a *Atom10Adapter) populateEntries(siteURL string) model.Entries {
|
|||
}
|
||||
|
||||
// Generate the entry hash.
|
||||
for _, value := range []string{atomEntry.ID, atomEntry.Links.OriginalLink()} {
|
||||
for _, value := range []string{atomEntry.ID, atomEntry.Links.originalLink()} {
|
||||
if value != "" {
|
||||
entry.Hash = crypto.SHA256(value)
|
||||
break
|
||||
|
|
|
@ -30,9 +30,9 @@ func (a *AtomPerson) PersonName() string {
|
|||
return strings.TrimSpace(a.Email)
|
||||
}
|
||||
|
||||
type AtomPersons []*AtomPerson
|
||||
type atomPersons []*AtomPerson
|
||||
|
||||
func (a AtomPersons) PersonNames() []string {
|
||||
func (a atomPersons) personNames() []string {
|
||||
var names []string
|
||||
authorNamesMap := make(map[string]bool)
|
||||
|
||||
|
@ -56,9 +56,9 @@ type AtomLink struct {
|
|||
Title string `xml:"title,attr"`
|
||||
}
|
||||
|
||||
type AtomLinks []*AtomLink
|
||||
type atomLinks []*AtomLink
|
||||
|
||||
func (a AtomLinks) OriginalLink() string {
|
||||
func (a atomLinks) originalLink() string {
|
||||
for _, link := range a {
|
||||
if strings.EqualFold(link.Rel, "alternate") {
|
||||
return strings.TrimSpace(link.Href)
|
||||
|
@ -72,7 +72,7 @@ func (a AtomLinks) OriginalLink() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (a AtomLinks) firstLinkWithRelation(relation string) string {
|
||||
func (a atomLinks) firstLinkWithRelation(relation string) string {
|
||||
for _, link := range a {
|
||||
if strings.EqualFold(link.Rel, relation) {
|
||||
return strings.TrimSpace(link.Href)
|
||||
|
@ -82,7 +82,7 @@ func (a AtomLinks) firstLinkWithRelation(relation string) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (a AtomLinks) firstLinkWithRelationAndType(relation string, contentTypes ...string) string {
|
||||
func (a atomLinks) firstLinkWithRelationAndType(relation string, contentTypes ...string) string {
|
||||
for _, link := range a {
|
||||
if strings.EqualFold(link.Rel, relation) {
|
||||
for _, contentType := range contentTypes {
|
||||
|
@ -96,7 +96,7 @@ func (a AtomLinks) firstLinkWithRelationAndType(relation string, contentTypes ..
|
|||
return ""
|
||||
}
|
||||
|
||||
func (a AtomLinks) findAllLinksWithRelation(relation string) []*AtomLink {
|
||||
func (a atomLinks) findAllLinksWithRelation(relation string) []*AtomLink {
|
||||
var links []*AtomLink
|
||||
|
||||
for _, link := range a {
|
||||
|
@ -116,7 +116,7 @@ func (a AtomLinks) findAllLinksWithRelation(relation string) []*AtomLink {
|
|||
// meaning to the content (if any) of this element.
|
||||
//
|
||||
// Specs: https://datatracker.ietf.org/doc/html/rfc4287#section-4.2.2
|
||||
type AtomCategory struct {
|
||||
type atomCategory struct {
|
||||
// The "term" attribute is a string that identifies the category to
|
||||
// which the entry or feed belongs. Category elements MUST have a
|
||||
// "term" attribute.
|
||||
|
@ -134,9 +134,9 @@ type AtomCategory struct {
|
|||
Label string `xml:"label,attr"`
|
||||
}
|
||||
|
||||
type AtomCategories []AtomCategory
|
||||
type atomCategories []atomCategory
|
||||
|
||||
func (ac AtomCategories) CategoryNames() []string {
|
||||
func (ac atomCategories) CategoryNames() []string {
|
||||
var categories []string
|
||||
|
||||
for _, category := range ac {
|
||||
|
|
|
@ -15,13 +15,13 @@ import (
|
|||
func Parse(baseURL string, r io.ReadSeeker, version string) (*model.Feed, error) {
|
||||
switch version {
|
||||
case "0.3":
|
||||
atomFeed := new(Atom03Feed)
|
||||
atomFeed := new(atom03Feed)
|
||||
if err := xml_decoder.NewXMLDecoder(r).Decode(atomFeed); err != nil {
|
||||
return nil, fmt.Errorf("atom: unable to parse Atom 0.3 feed: %w", err)
|
||||
}
|
||||
return NewAtom03Adapter(atomFeed).BuildFeed(baseURL), nil
|
||||
return NewAtom03Adapter(atomFeed).buildFeed(baseURL), nil
|
||||
default:
|
||||
atomFeed := new(Atom10Feed)
|
||||
atomFeed := new(atom10Feed)
|
||||
if err := xml_decoder.NewXMLDecoder(r).Decode(atomFeed); err != nil {
|
||||
return nil, fmt.Errorf("atom: unable to parse Atom 1.0 feed: %w", err)
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ func (h *Handler) Export(userID int64) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
subscriptions := make(SubcriptionList, 0, len(feeds))
|
||||
subscriptions := make(subcriptionList, 0, len(feeds))
|
||||
for _, feed := range feeds {
|
||||
subscriptions = append(subscriptions, &Subcription{
|
||||
subscriptions = append(subscriptions, &subcription{
|
||||
Title: feed.Title,
|
||||
FeedURL: feed.FeedURL,
|
||||
SiteURL: feed.SiteURL,
|
||||
|
@ -34,12 +34,12 @@ func (h *Handler) Export(userID int64) (string, error) {
|
|||
})
|
||||
}
|
||||
|
||||
return Serialize(subscriptions), nil
|
||||
return serialize(subscriptions), nil
|
||||
}
|
||||
|
||||
// Import parses and create feeds from an OPML import.
|
||||
func (h *Handler) Import(userID int64, data io.Reader) error {
|
||||
subscriptions, err := Parse(data)
|
||||
subscriptions, err := parse(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ type opmlDocument struct {
|
|||
Outlines opmlOutlineCollection `xml:"body>outline"`
|
||||
}
|
||||
|
||||
// TODO remove as this is only used in the opml package
|
||||
func NewOPMLDocument() *opmlDocument {
|
||||
return &opmlDocument{}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
"miniflux.app/v2/internal/reader/encoding"
|
||||
)
|
||||
|
||||
// Parse reads an OPML file and returns a SubcriptionList.
|
||||
func Parse(data io.Reader) (SubcriptionList, error) {
|
||||
// parse reads an OPML file and returns a SubcriptionList.
|
||||
func parse(data io.Reader) (subcriptionList, error) {
|
||||
opmlDocument := NewOPMLDocument()
|
||||
decoder := xml.NewDecoder(data)
|
||||
decoder.Entity = xml.HTMLEntity
|
||||
|
@ -27,10 +27,10 @@ func Parse(data io.Reader) (SubcriptionList, error) {
|
|||
return getSubscriptionsFromOutlines(opmlDocument.Outlines, ""), nil
|
||||
}
|
||||
|
||||
func getSubscriptionsFromOutlines(outlines opmlOutlineCollection, category string) (subscriptions SubcriptionList) {
|
||||
func getSubscriptionsFromOutlines(outlines opmlOutlineCollection, category string) (subscriptions subcriptionList) {
|
||||
for _, outline := range outlines {
|
||||
if outline.IsSubscription() {
|
||||
subscriptions = append(subscriptions, &Subcription{
|
||||
subscriptions = append(subscriptions, &subcription{
|
||||
Title: outline.GetTitle(),
|
||||
FeedURL: outline.FeedURL,
|
||||
SiteURL: outline.GetSiteURL(),
|
||||
|
|
|
@ -8,6 +8,13 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
// equals compare two subscriptions.
|
||||
func (s subcription) equals(subscription *subcription) bool {
|
||||
return s.Title == subscription.Title && s.SiteURL == subscription.SiteURL &&
|
||||
s.FeedURL == subscription.FeedURL && s.CategoryName == subscription.CategoryName &&
|
||||
s.Description == subscription.Description
|
||||
}
|
||||
|
||||
func TestParseOpmlWithoutCategories(t *testing.T) {
|
||||
data := `<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<opml version="2.0">
|
||||
|
@ -32,10 +39,10 @@ func TestParseOpmlWithoutCategories(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "CNET News.com", FeedURL: "http://news.com.com/2547-1_3-0-5.xml", SiteURL: "http://news.com.com/", Description: "Tech news and business reports by CNET News.com. Focused on information technology, core topics include computers, hardware, software, networking, and Internet media."})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "CNET News.com", FeedURL: "http://news.com.com/2547-1_3-0-5.xml", SiteURL: "http://news.com.com/", Description: "Tech news and business reports by CNET News.com. Focused on information technology, core topics include computers, hardware, software, networking, and Internet media."})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -44,7 +51,7 @@ func TestParseOpmlWithoutCategories(t *testing.T) {
|
|||
t.Fatalf("Wrong number of subscriptions: %d instead of %d", len(subscriptions), 13)
|
||||
}
|
||||
|
||||
if !subscriptions[0].Equals(expected[0]) {
|
||||
if !subscriptions[0].equals(expected[0]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[0], expected[0])
|
||||
}
|
||||
}
|
||||
|
@ -67,12 +74,12 @@ func TestParseOpmlWithCategories(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "My Category 1"})
|
||||
expected = append(expected, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "My Category 1"})
|
||||
expected = append(expected, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed3/", SiteURL: "http://example.org/3", CategoryName: "My Category 2"})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "My Category 1"})
|
||||
expected = append(expected, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "My Category 1"})
|
||||
expected = append(expected, &subcription{Title: "Feed 3", FeedURL: "http://example.org/feed3/", SiteURL: "http://example.org/3", CategoryName: "My Category 2"})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -82,7 +89,7 @@ func TestParseOpmlWithCategories(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -101,11 +108,11 @@ func TestParseOpmlWithEmptyTitleAndEmptySiteURL(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "http://example.org/1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: ""})
|
||||
expected = append(expected, &Subcription{Title: "http://example.org/feed2/", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/feed2/", CategoryName: ""})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "http://example.org/1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: ""})
|
||||
expected = append(expected, &subcription{Title: "http://example.org/feed2/", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/feed2/", CategoryName: ""})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -115,7 +122,7 @@ func TestParseOpmlWithEmptyTitleAndEmptySiteURL(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -139,11 +146,11 @@ func TestParseOpmlVersion1(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "Category 1"})
|
||||
expected = append(expected, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "Category 2"})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "Category 1"})
|
||||
expected = append(expected, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "Category 2"})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -153,7 +160,7 @@ func TestParseOpmlVersion1(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -173,11 +180,11 @@ func TestParseOpmlVersion1WithoutOuterOutline(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: ""})
|
||||
expected = append(expected, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: ""})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: ""})
|
||||
expected = append(expected, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: ""})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -187,7 +194,7 @@ func TestParseOpmlVersion1WithoutOuterOutline(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -214,12 +221,12 @@ func TestParseOpmlVersion1WithSeveralNestedOutlines(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "Some Category"})
|
||||
expected = append(expected, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "Some Category"})
|
||||
expected = append(expected, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed3/", SiteURL: "http://example.org/3", CategoryName: "Another Category"})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/", SiteURL: "http://example.org/1", CategoryName: "Some Category"})
|
||||
expected = append(expected, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed2/", SiteURL: "http://example.org/2", CategoryName: "Some Category"})
|
||||
expected = append(expected, &subcription{Title: "Feed 3", FeedURL: "http://example.org/feed3/", SiteURL: "http://example.org/3", CategoryName: "Another Category"})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -229,7 +236,7 @@ func TestParseOpmlVersion1WithSeveralNestedOutlines(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -249,10 +256,10 @@ func TestParseOpmlWithInvalidCharacterEntity(t *testing.T) {
|
|||
</opml>
|
||||
`
|
||||
|
||||
var expected SubcriptionList
|
||||
expected = append(expected, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/a&b", SiteURL: "http://example.org/c&d", CategoryName: "Feed 1"})
|
||||
var expected subcriptionList
|
||||
expected = append(expected, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed1/a&b", SiteURL: "http://example.org/c&d", CategoryName: "Feed 1"})
|
||||
|
||||
subscriptions, err := Parse(bytes.NewBufferString(data))
|
||||
subscriptions, err := parse(bytes.NewBufferString(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -262,7 +269,7 @@ func TestParseOpmlWithInvalidCharacterEntity(t *testing.T) {
|
|||
}
|
||||
|
||||
for i := range len(subscriptions) {
|
||||
if !subscriptions[i].Equals(expected[i]) {
|
||||
if !subscriptions[i].equals(expected[i]) {
|
||||
t.Errorf(`Subscription is different: "%v" vs "%v"`, subscriptions[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
@ -270,7 +277,7 @@ func TestParseOpmlWithInvalidCharacterEntity(t *testing.T) {
|
|||
|
||||
func TestParseInvalidXML(t *testing.T) {
|
||||
data := `garbage`
|
||||
_, err := Parse(bytes.NewBufferString(data))
|
||||
_, err := parse(bytes.NewBufferString(data))
|
||||
if err == nil {
|
||||
t.Error("Parse should generate an error")
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Serialize returns a SubcriptionList in OPML format.
|
||||
func Serialize(subscriptions SubcriptionList) string {
|
||||
// serialize returns a SubcriptionList in OPML format.
|
||||
func serialize(subscriptions subcriptionList) string {
|
||||
var b bytes.Buffer
|
||||
writer := bufio.NewWriter(&b)
|
||||
writer.WriteString(xml.Header)
|
||||
|
@ -31,7 +31,7 @@ func Serialize(subscriptions SubcriptionList) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func convertSubscriptionsToOPML(subscriptions SubcriptionList) *opmlDocument {
|
||||
func convertSubscriptionsToOPML(subscriptions subcriptionList) *opmlDocument {
|
||||
opmlDocument := NewOPMLDocument()
|
||||
opmlDocument.Version = "2.0"
|
||||
opmlDocument.Header.Title = "Miniflux"
|
||||
|
@ -62,8 +62,8 @@ func convertSubscriptionsToOPML(subscriptions SubcriptionList) *opmlDocument {
|
|||
return opmlDocument
|
||||
}
|
||||
|
||||
func groupSubscriptionsByFeed(subscriptions SubcriptionList) map[string]SubcriptionList {
|
||||
groups := make(map[string]SubcriptionList)
|
||||
func groupSubscriptionsByFeed(subscriptions subcriptionList) map[string]subcriptionList {
|
||||
groups := make(map[string]subcriptionList)
|
||||
|
||||
for _, subscription := range subscriptions {
|
||||
groups[subscription.CategoryName] = append(groups[subscription.CategoryName], subscription)
|
||||
|
|
|
@ -9,13 +9,13 @@ import (
|
|||
)
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
var subscriptions SubcriptionList
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed/1", SiteURL: "http://example.org/1", CategoryName: "Category 1"})
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: "Category 1"})
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: "Category 2"})
|
||||
var subscriptions subcriptionList
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed/1", SiteURL: "http://example.org/1", CategoryName: "Category 1"})
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: "Category 1"})
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: "Category 2"})
|
||||
|
||||
output := Serialize(subscriptions)
|
||||
feeds, err := Parse(bytes.NewBufferString(output))
|
||||
output := serialize(subscriptions)
|
||||
feeds, err := parse(bytes.NewBufferString(output))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -48,10 +48,10 @@ func TestNormalizedCategoriesOrder(t *testing.T) {
|
|||
{"Category 1", "Category 3"},
|
||||
}
|
||||
|
||||
var subscriptions SubcriptionList
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 1", FeedURL: "http://example.org/feed/1", SiteURL: "http://example.org/1", CategoryName: orderTests[0].naturalOrderName})
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: orderTests[1].naturalOrderName})
|
||||
subscriptions = append(subscriptions, &Subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: orderTests[2].naturalOrderName})
|
||||
var subscriptions subcriptionList
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 1", FeedURL: "http://example.org/feed/1", SiteURL: "http://example.org/1", CategoryName: orderTests[0].naturalOrderName})
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 2", FeedURL: "http://example.org/feed/2", SiteURL: "http://example.org/2", CategoryName: orderTests[1].naturalOrderName})
|
||||
subscriptions = append(subscriptions, &subcription{Title: "Feed 3", FeedURL: "http://example.org/feed/3", SiteURL: "http://example.org/3", CategoryName: orderTests[2].naturalOrderName})
|
||||
|
||||
feeds := convertSubscriptionsToOPML(subscriptions)
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
package opml // import "miniflux.app/v2/internal/reader/opml"
|
||||
|
||||
// Subcription represents a feed that will be imported or exported.
|
||||
type Subcription struct {
|
||||
// subcription represents a feed that will be imported or exported.
|
||||
type subcription struct {
|
||||
Title string
|
||||
SiteURL string
|
||||
FeedURL string
|
||||
|
@ -12,12 +12,5 @@ type Subcription struct {
|
|||
Description string
|
||||
}
|
||||
|
||||
// Equals compare two subscriptions.
|
||||
func (s Subcription) Equals(subscription *Subcription) bool {
|
||||
return s.Title == subscription.Title && s.SiteURL == subscription.SiteURL &&
|
||||
s.FeedURL == subscription.FeedURL && s.CategoryName == subscription.CategoryName &&
|
||||
s.Description == subscription.Description
|
||||
}
|
||||
|
||||
// SubcriptionList is a list of subscriptions.
|
||||
type SubcriptionList []*Subcription
|
||||
// subcriptionList is a list of subscriptions.
|
||||
type subcriptionList []*subcription
|
||||
|
|
|
@ -16,15 +16,11 @@ import (
|
|||
"miniflux.app/v2/internal/urllib"
|
||||
)
|
||||
|
||||
type RDFAdapter struct {
|
||||
rdf *RDF
|
||||
type rdfAdapter struct {
|
||||
rdf *rdf
|
||||
}
|
||||
|
||||
func NewRDFAdapter(rdf *RDF) *RDFAdapter {
|
||||
return &RDFAdapter{rdf}
|
||||
}
|
||||
|
||||
func (r *RDFAdapter) BuildFeed(baseURL string) *model.Feed {
|
||||
func (r *rdfAdapter) buildFeed(baseURL string) *model.Feed {
|
||||
feed := &model.Feed{
|
||||
Title: stripTags(r.rdf.Channel.Title),
|
||||
FeedURL: strings.TrimSpace(baseURL),
|
||||
|
|
|
@ -13,10 +13,11 @@ import (
|
|||
|
||||
// Parse returns a normalized feed struct from a RDF feed.
|
||||
func Parse(baseURL string, data io.ReadSeeker) (*model.Feed, error) {
|
||||
xmlFeed := new(RDF)
|
||||
xmlFeed := new(rdf)
|
||||
if err := xml.NewXMLDecoder(data).Decode(xmlFeed); err != nil {
|
||||
return nil, fmt.Errorf("rdf: unable to parse feed: %w", err)
|
||||
}
|
||||
|
||||
return NewRDFAdapter(xmlFeed).BuildFeed(baseURL), nil
|
||||
adapter := &rdfAdapter{xmlFeed}
|
||||
return adapter.buildFeed(baseURL), nil
|
||||
}
|
||||
|
|
|
@ -9,21 +9,21 @@ import (
|
|||
"miniflux.app/v2/internal/reader/dublincore"
|
||||
)
|
||||
|
||||
// RDF sepcs: https://web.resource.org/rss/1.0/spec
|
||||
type RDF struct {
|
||||
// rdf sepcs: https://web.resource.org/rss/1.0/spec
|
||||
type rdf struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/1999/02/22-rdf-syntax-ns# RDF"`
|
||||
Channel RDFChannel `xml:"channel"`
|
||||
Items []RDFItem `xml:"item"`
|
||||
Channel rdfChannel `xml:"channel"`
|
||||
Items []rdfItem `xml:"item"`
|
||||
}
|
||||
|
||||
type RDFChannel struct {
|
||||
type rdfChannel struct {
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
dublincore.DublinCoreChannelElement
|
||||
}
|
||||
|
||||
type RDFItem struct {
|
||||
type rdfItem struct {
|
||||
Title string `xml:"http://purl.org/rss/1.0/ title"`
|
||||
Link string `xml:"link"`
|
||||
Description string `xml:"description"`
|
||||
|
|
|
@ -19,15 +19,11 @@ import (
|
|||
"miniflux.app/v2/internal/urllib"
|
||||
)
|
||||
|
||||
type RSSAdapter struct {
|
||||
rss *RSS
|
||||
type rssAdapter struct {
|
||||
rss *rss
|
||||
}
|
||||
|
||||
func NewRSSAdapter(rss *RSS) *RSSAdapter {
|
||||
return &RSSAdapter{rss}
|
||||
}
|
||||
|
||||
func (r *RSSAdapter) BuildFeed(baseURL string) *model.Feed {
|
||||
func (r *rssAdapter) buildFeed(baseURL string) *model.Feed {
|
||||
feed := &model.Feed{
|
||||
Title: html.UnescapeString(strings.TrimSpace(r.rss.Channel.Title)),
|
||||
FeedURL: strings.TrimSpace(baseURL),
|
||||
|
@ -145,7 +141,7 @@ func (r *RSSAdapter) BuildFeed(baseURL string) *model.Feed {
|
|||
return feed
|
||||
}
|
||||
|
||||
func findFeedAuthor(rssChannel *RSSChannel) string {
|
||||
func findFeedAuthor(rssChannel *rssChannel) string {
|
||||
var author string
|
||||
switch {
|
||||
case rssChannel.ItunesAuthor != "":
|
||||
|
@ -165,7 +161,7 @@ func findFeedAuthor(rssChannel *RSSChannel) string {
|
|||
return strings.TrimSpace(sanitizer.StripTags(author))
|
||||
}
|
||||
|
||||
func findFeedTags(rssChannel *RSSChannel) []string {
|
||||
func findFeedTags(rssChannel *rssChannel) []string {
|
||||
tags := make([]string, 0)
|
||||
|
||||
for _, tag := range rssChannel.Categories {
|
||||
|
@ -189,7 +185,7 @@ func findFeedTags(rssChannel *RSSChannel) []string {
|
|||
return tags
|
||||
}
|
||||
|
||||
func findEntryTitle(rssItem *RSSItem) string {
|
||||
func findEntryTitle(rssItem *rssItem) string {
|
||||
title := rssItem.Title.Content
|
||||
|
||||
if rssItem.DublinCoreTitle != "" {
|
||||
|
@ -199,7 +195,7 @@ func findEntryTitle(rssItem *RSSItem) string {
|
|||
return html.UnescapeString(html.UnescapeString(strings.TrimSpace(title)))
|
||||
}
|
||||
|
||||
func findEntryURL(rssItem *RSSItem) string {
|
||||
func findEntryURL(rssItem *rssItem) string {
|
||||
for _, link := range []string{rssItem.FeedBurnerLink, rssItem.Link} {
|
||||
if link != "" {
|
||||
return strings.TrimSpace(link)
|
||||
|
@ -222,7 +218,7 @@ func findEntryURL(rssItem *RSSItem) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func findEntryContent(rssItem *RSSItem) string {
|
||||
func findEntryContent(rssItem *rssItem) string {
|
||||
for _, value := range []string{
|
||||
rssItem.DublinCoreContent,
|
||||
rssItem.Description,
|
||||
|
@ -237,7 +233,7 @@ func findEntryContent(rssItem *RSSItem) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func findEntryDate(rssItem *RSSItem) time.Time {
|
||||
func findEntryDate(rssItem *rssItem) time.Time {
|
||||
value := rssItem.PubDate
|
||||
if rssItem.DublinCoreDate != "" {
|
||||
value = rssItem.DublinCoreDate
|
||||
|
@ -260,7 +256,7 @@ func findEntryDate(rssItem *RSSItem) time.Time {
|
|||
return time.Now()
|
||||
}
|
||||
|
||||
func findEntryAuthor(rssItem *RSSItem) string {
|
||||
func findEntryAuthor(rssItem *rssItem) string {
|
||||
var author string
|
||||
|
||||
switch {
|
||||
|
@ -283,7 +279,7 @@ func findEntryAuthor(rssItem *RSSItem) string {
|
|||
return strings.TrimSpace(sanitizer.StripTags(author))
|
||||
}
|
||||
|
||||
func findEntryTags(rssItem *RSSItem) []string {
|
||||
func findEntryTags(rssItem *rssItem) []string {
|
||||
tags := make([]string, 0)
|
||||
|
||||
for _, tag := range rssItem.Categories {
|
||||
|
@ -303,7 +299,7 @@ func findEntryTags(rssItem *RSSItem) []string {
|
|||
return tags
|
||||
}
|
||||
|
||||
func findEntryEnclosures(rssItem *RSSItem, siteURL string) model.EnclosureList {
|
||||
func findEntryEnclosures(rssItem *rssItem, siteURL string) model.EnclosureList {
|
||||
enclosures := make(model.EnclosureList, 0)
|
||||
duplicates := make(map[string]bool)
|
||||
|
||||
|
|
|
@ -7,14 +7,14 @@ import (
|
|||
"miniflux.app/v2/internal/reader/atom"
|
||||
)
|
||||
|
||||
type AtomAuthor struct {
|
||||
type atomAuthor struct {
|
||||
Author atom.AtomPerson `xml:"http://www.w3.org/2005/Atom author"`
|
||||
}
|
||||
|
||||
func (a *AtomAuthor) PersonName() string {
|
||||
func (a *atomAuthor) PersonName() string {
|
||||
return a.Author.PersonName()
|
||||
}
|
||||
|
||||
type AtomLinks struct {
|
||||
type atomLinks struct {
|
||||
Links []*atom.AtomLink `xml:"http://www.w3.org/2005/Atom link"`
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
package rss // import "miniflux.app/v2/internal/reader/rss"
|
||||
|
||||
// FeedBurnerItemElement represents FeedBurner XML elements.
|
||||
type FeedBurnerItemElement struct {
|
||||
// feedBurnerItemElement represents FeedBurner XML elements.
|
||||
type feedBurnerItemElement struct {
|
||||
FeedBurnerLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origLink"`
|
||||
FeedBurnerEnclosureLink string `xml:"http://rssnamespace.org/feedburner/ext/1.0 origEnclosureLink"`
|
||||
}
|
||||
|
|
|
@ -13,11 +13,12 @@ import (
|
|||
|
||||
// Parse returns a normalized feed struct from a RSS feed.
|
||||
func Parse(baseURL string, data io.ReadSeeker) (*model.Feed, error) {
|
||||
rssFeed := new(RSS)
|
||||
rssFeed := new(rss)
|
||||
decoder := xml.NewXMLDecoder(data)
|
||||
decoder.DefaultSpace = "rss"
|
||||
if err := decoder.Decode(rssFeed); err != nil {
|
||||
return nil, fmt.Errorf("rss: unable to parse feed: %w", err)
|
||||
}
|
||||
return NewRSSAdapter(rssFeed).BuildFeed(baseURL), nil
|
||||
adapter := &rssAdapter{rssFeed}
|
||||
return adapter.buildFeed(baseURL), nil
|
||||
}
|
||||
|
|
|
@ -10,20 +10,20 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var ErrInvalidDurationFormat = errors.New("rss: invalid duration format")
|
||||
var errInvalidDurationFormat = errors.New("rss: invalid duration format")
|
||||
|
||||
func getDurationInMinutes(rawDuration string) (int, error) {
|
||||
var sumSeconds int
|
||||
|
||||
durationParts := strings.Split(rawDuration, ":")
|
||||
if len(durationParts) > 3 {
|
||||
return 0, ErrInvalidDurationFormat
|
||||
return 0, errInvalidDurationFormat
|
||||
}
|
||||
|
||||
for i, durationPart := range durationParts {
|
||||
durationPartValue, err := strconv.Atoi(durationPart)
|
||||
if err != nil {
|
||||
return 0, ErrInvalidDurationFormat
|
||||
return 0, errInvalidDurationFormat
|
||||
}
|
||||
|
||||
sumSeconds += int(math.Pow(60, float64(len(durationParts)-i-1))) * durationPartValue
|
||||
|
|
|
@ -15,15 +15,15 @@ import (
|
|||
)
|
||||
|
||||
// Specs: https://www.rssboard.org/rss-specification
|
||||
type RSS struct {
|
||||
type rss struct {
|
||||
// Version is the version of the RSS specification.
|
||||
Version string `xml:"rss version,attr"`
|
||||
|
||||
// Channel is the main container for the RSS feed.
|
||||
Channel RSSChannel `xml:"rss channel"`
|
||||
Channel rssChannel `xml:"rss channel"`
|
||||
}
|
||||
|
||||
type RSSChannel struct {
|
||||
type rssChannel struct {
|
||||
// Title is the name of the channel.
|
||||
Title string `xml:"rss title"`
|
||||
|
||||
|
@ -64,10 +64,10 @@ type RSSChannel struct {
|
|||
DocumentationURL string `xml:"rss docs"`
|
||||
|
||||
// Cloud is a web service that supports the rssCloud interface which can be implemented in HTTP-POST, XML-RPC or SOAP 1.1.
|
||||
Cloud *RSSCloud `xml:"rss cloud"`
|
||||
Cloud *rssCloud `xml:"rss cloud"`
|
||||
|
||||
// Image specifies a GIF, JPEG or PNG image that can be displayed with the channel.
|
||||
Image *RSSImage `xml:"rss image"`
|
||||
Image *rssImage `xml:"rss image"`
|
||||
|
||||
// TTL is a number of minutes that indicates how long a channel can be cached before refreshing from the source.
|
||||
TTL string `xml:"rss ttl"`
|
||||
|
@ -83,14 +83,14 @@ type RSSChannel struct {
|
|||
SkipDays []string `xml:"rss skipDays>day"`
|
||||
|
||||
// Items is a collection of items.
|
||||
Items []RSSItem `xml:"rss item"`
|
||||
Items []rssItem `xml:"rss item"`
|
||||
|
||||
AtomLinks
|
||||
atomLinks
|
||||
itunes.ItunesChannelElement
|
||||
googleplay.GooglePlayChannelElement
|
||||
}
|
||||
|
||||
type RSSCloud struct {
|
||||
type rssCloud struct {
|
||||
Domain string `xml:"domain,attr"`
|
||||
Port string `xml:"port,attr"`
|
||||
Path string `xml:"path,attr"`
|
||||
|
@ -98,7 +98,7 @@ type RSSCloud struct {
|
|||
Protocol string `xml:"protocol,attr"`
|
||||
}
|
||||
|
||||
type RSSImage struct {
|
||||
type rssImage struct {
|
||||
// URL is the URL of a GIF, JPEG or PNG image that represents the channel.
|
||||
URL string `xml:"url"`
|
||||
|
||||
|
@ -109,9 +109,9 @@ type RSSImage struct {
|
|||
Link string `xml:"link"`
|
||||
}
|
||||
|
||||
type RSSItem struct {
|
||||
type rssItem struct {
|
||||
// Title is the title of the item.
|
||||
Title InnerContent `xml:"rss title"`
|
||||
Title innerContent `xml:"rss title"`
|
||||
|
||||
// Link is the URL of the item.
|
||||
Link string `xml:"rss link"`
|
||||
|
@ -120,7 +120,7 @@ type RSSItem struct {
|
|||
Description string `xml:"rss description"`
|
||||
|
||||
// Author is the email address of the author of the item.
|
||||
Author RSSAuthor `xml:"rss author"`
|
||||
Author rssAuthor `xml:"rss author"`
|
||||
|
||||
// <category> is an optional sub-element of <item>.
|
||||
// It has one optional attribute, domain, a string that identifies a categorization taxonomy.
|
||||
|
@ -133,7 +133,7 @@ type RSSItem struct {
|
|||
// <enclosure> is an optional sub-element of <item>.
|
||||
// It has three required attributes. url says where the enclosure is located,
|
||||
// length says how big it is in bytes, and type says what its type is, a standard MIME type.
|
||||
Enclosures []RSSEnclosure `xml:"rss enclosure"`
|
||||
Enclosures []rssEnclosure `xml:"rss enclosure"`
|
||||
|
||||
// <guid> is an optional sub-element of <item>.
|
||||
// It's a string that uniquely identifies the item.
|
||||
|
@ -149,7 +149,7 @@ type RSSItem struct {
|
|||
//
|
||||
// isPermaLink is optional, its default value is true.
|
||||
// If its value is false, the guid may not be assumed to be a url, or a url to anything in particular.
|
||||
GUID RSSGUID `xml:"rss guid"`
|
||||
GUID rssGUID `xml:"rss guid"`
|
||||
|
||||
// <pubDate> is the publication date of the item.
|
||||
// Its value is a string in RFC 822 format.
|
||||
|
@ -158,30 +158,30 @@ type RSSItem struct {
|
|||
// <source> is an optional sub-element of <item>.
|
||||
// Its value is the name of the RSS channel that the item came from, derived from its <title>.
|
||||
// It has one required attribute, url, which contains the URL of the RSS channel.
|
||||
Source RSSSource `xml:"rss source"`
|
||||
Source rssSource `xml:"rss source"`
|
||||
|
||||
dublincore.DublinCoreItemElement
|
||||
FeedBurnerItemElement
|
||||
feedBurnerItemElement
|
||||
media.MediaItemElement
|
||||
AtomAuthor
|
||||
AtomLinks
|
||||
atomAuthor
|
||||
atomLinks
|
||||
itunes.ItunesItemElement
|
||||
googleplay.GooglePlayItemElement
|
||||
}
|
||||
|
||||
type RSSAuthor struct {
|
||||
type rssAuthor struct {
|
||||
XMLName xml.Name
|
||||
Data string `xml:",chardata"`
|
||||
Inner string `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type RSSEnclosure struct {
|
||||
type rssEnclosure struct {
|
||||
URL string `xml:"url,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
Length string `xml:"length,attr"`
|
||||
}
|
||||
|
||||
func (enclosure *RSSEnclosure) Size() int64 {
|
||||
func (enclosure *rssEnclosure) Size() int64 {
|
||||
if strings.TrimSpace(enclosure.Length) == "" {
|
||||
return 0
|
||||
}
|
||||
|
@ -189,21 +189,21 @@ func (enclosure *RSSEnclosure) Size() int64 {
|
|||
return size
|
||||
}
|
||||
|
||||
type RSSGUID struct {
|
||||
type rssGUID struct {
|
||||
Data string `xml:",chardata"`
|
||||
IsPermaLink string `xml:"isPermaLink,attr"`
|
||||
}
|
||||
|
||||
type RSSSource struct {
|
||||
type rssSource struct {
|
||||
URL string `xml:"url,attr"`
|
||||
Name string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type InnerContent struct {
|
||||
type innerContent struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func (ic *InnerContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
func (ic *innerContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
var content strings.Builder
|
||||
|
||||
for {
|
||||
|
|
|
@ -197,7 +197,8 @@ type SanitizerOptions struct {
|
|||
OpenLinksInNewTab bool
|
||||
}
|
||||
|
||||
func SanitizeHTMLWithDefaultOptions(baseURL, rawHTML string) string {
|
||||
// TODO: replace with SanitizeHTML, as it's only used in tests.
|
||||
func sanitizeHTMLWithDefaultOptions(baseURL, rawHTML string) string {
|
||||
return SanitizeHTML(baseURL, rawHTML, &SanitizerOptions{
|
||||
OpenLinksInNewTab: true,
|
||||
})
|
||||
|
|
|
@ -27,7 +27,7 @@ func BenchmarkSanitize(b *testing.B) {
|
|||
}
|
||||
for b.Loop() {
|
||||
for _, v := range testCases {
|
||||
SanitizeHTMLWithDefaultOptions(v[0], v[1])
|
||||
sanitizeHTMLWithDefaultOptions(v[0], v[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func FuzzSanitizer(f *testing.F) {
|
|||
i++
|
||||
}
|
||||
|
||||
out := SanitizeHTMLWithDefaultOptions("", orig)
|
||||
out := sanitizeHTMLWithDefaultOptions("", orig)
|
||||
|
||||
tok = html.NewTokenizer(strings.NewReader(out))
|
||||
j := 0
|
||||
|
@ -56,7 +56,7 @@ func FuzzSanitizer(f *testing.F) {
|
|||
|
||||
func TestValidInput(t *testing.T) {
|
||||
input := `<p>This is a <strong>text</strong> with an image: <img src="http://example.org/" alt="Test" loading="lazy">.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if input != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, input, output)
|
||||
|
@ -66,7 +66,7 @@ func TestValidInput(t *testing.T) {
|
|||
func TestImgWithWidthAndHeightAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="10" height="20">`
|
||||
expected := `<img src="https://example.org/image.png" width="10" height="20" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -76,7 +76,7 @@ func TestImgWithWidthAndHeightAttribute(t *testing.T) {
|
|||
func TestImgWithWidthAttributeLargerThanMinifluxLayout(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="1200" height="675">`
|
||||
expected := `<img src="https://example.org/image.png" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -86,7 +86,7 @@ func TestImgWithWidthAttributeLargerThanMinifluxLayout(t *testing.T) {
|
|||
func TestImgWithIncorrectWidthAndHeightAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="10px" height="20px">`
|
||||
expected := `<img src="https://example.org/image.png" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -96,7 +96,7 @@ func TestImgWithIncorrectWidthAndHeightAttribute(t *testing.T) {
|
|||
func TestImgWithIncorrectWidthAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="10px" height="20">`
|
||||
expected := `<img src="https://example.org/image.png" height="20" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -106,7 +106,7 @@ func TestImgWithIncorrectWidthAttribute(t *testing.T) {
|
|||
func TestImgWithEmptyWidthAndHeightAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="" height="">`
|
||||
expected := `<img src="https://example.org/image.png" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -116,7 +116,7 @@ func TestImgWithEmptyWidthAndHeightAttribute(t *testing.T) {
|
|||
func TestImgWithIncorrectHeightAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="10" height="20px">`
|
||||
expected := `<img src="https://example.org/image.png" width="10" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -126,7 +126,7 @@ func TestImgWithIncorrectHeightAttribute(t *testing.T) {
|
|||
func TestImgWithNegativeWidthAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="-10" height="20">`
|
||||
expected := `<img src="https://example.org/image.png" height="20" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -136,7 +136,7 @@ func TestImgWithNegativeWidthAttribute(t *testing.T) {
|
|||
func TestImgWithNegativeHeightAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" width="10" height="-20">`
|
||||
expected := `<img src="https://example.org/image.png" width="10" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -146,7 +146,7 @@ func TestImgWithNegativeHeightAttribute(t *testing.T) {
|
|||
func TestImgWithTextDataURL(t *testing.T) {
|
||||
input := `<img src="data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==" alt="Example">`
|
||||
expected := ``
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -156,7 +156,7 @@ func TestImgWithTextDataURL(t *testing.T) {
|
|||
func TestImgWithDataURL(t *testing.T) {
|
||||
input := `<img src="data:image/gif;base64,test" alt="Example">`
|
||||
expected := `<img src="data:image/gif;base64,test" alt="Example" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -166,7 +166,7 @@ func TestImgWithDataURL(t *testing.T) {
|
|||
func TestImgWithSrcsetAttribute(t *testing.T) {
|
||||
input := `<img srcset="example-320w.jpg, example-480w.jpg 1.5x, example-640w.jpg 2x, example-640w.jpg 640w" src="example-640w.jpg" alt="Example">`
|
||||
expected := `<img srcset="http://example.org/example-320w.jpg, http://example.org/example-480w.jpg 1.5x, http://example.org/example-640w.jpg 2x, http://example.org/example-640w.jpg 640w" src="http://example.org/example-640w.jpg" alt="Example" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -176,7 +176,7 @@ func TestImgWithSrcsetAttribute(t *testing.T) {
|
|||
func TestImgWithSrcsetAndNoSrcAttribute(t *testing.T) {
|
||||
input := `<img srcset="example-320w.jpg, example-480w.jpg 1.5x, example-640w.jpg 2x, example-640w.jpg 640w" alt="Example">`
|
||||
expected := `<img srcset="http://example.org/example-320w.jpg, http://example.org/example-480w.jpg 1.5x, http://example.org/example-640w.jpg 2x, http://example.org/example-640w.jpg 640w" alt="Example" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -203,7 +203,7 @@ func TestImgWithFetchPriorityAttribute(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
|
||||
if output != tc.expected {
|
||||
t.Errorf(`Wrong output for input %q: expected %q, got %q`, tc.input, tc.expected, output)
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ func TestImgWithFetchPriorityAttribute(t *testing.T) {
|
|||
func TestImgWithInvalidFetchPriorityAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" fetchpriority="invalid">`
|
||||
expected := `<img src="https://example.org/image.png" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
|
||||
|
@ -223,7 +223,7 @@ func TestImgWithInvalidFetchPriorityAttribute(t *testing.T) {
|
|||
func TestNonImgWithFetchPriorityAttribute(t *testing.T) {
|
||||
input := `<p fetchpriority="high">Text</p>`
|
||||
expected := `<p>Text</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
|
||||
|
@ -250,7 +250,7 @@ func TestImgWithDecodingAttribute(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", tc.input)
|
||||
if output != tc.expected {
|
||||
t.Errorf(`Wrong output for input %q: expected %q, got %q`, tc.input, tc.expected, output)
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ func TestImgWithDecodingAttribute(t *testing.T) {
|
|||
func TestImgWithInvalidDecodingAttribute(t *testing.T) {
|
||||
input := `<img src="https://example.org/image.png" decoding="invalid">`
|
||||
expected := `<img src="https://example.org/image.png" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
|
||||
|
@ -270,7 +270,7 @@ func TestImgWithInvalidDecodingAttribute(t *testing.T) {
|
|||
func TestNonImgWithDecodingAttribute(t *testing.T) {
|
||||
input := `<p decoding="async">Text</p>`
|
||||
expected := `<p>Text</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: expected %q, got %q`, expected, output)
|
||||
|
@ -280,7 +280,7 @@ func TestNonImgWithDecodingAttribute(t *testing.T) {
|
|||
func TestSourceWithSrcsetAndMedia(t *testing.T) {
|
||||
input := `<picture><source media="(min-width: 800px)" srcset="elva-800w.jpg"></picture>`
|
||||
expected := `<picture><source media="(min-width: 800px)" srcset="http://example.org/elva-800w.jpg"></picture>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -290,7 +290,7 @@ func TestSourceWithSrcsetAndMedia(t *testing.T) {
|
|||
func TestMediumImgWithSrcset(t *testing.T) {
|
||||
input := `<img alt="Image for post" class="t u v ef aj" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" width="2730" height="3407">`
|
||||
expected := `<img alt="Image for post" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" loading="lazy">`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if output != expected {
|
||||
t.Errorf(`Wrong output: %s`, output)
|
||||
|
@ -299,7 +299,7 @@ func TestMediumImgWithSrcset(t *testing.T) {
|
|||
|
||||
func TestSelfClosingTags(t *testing.T) {
|
||||
input := `<p>This <br> is a <strong>text</strong> <br/>with an image: <img src="http://example.org/" alt="Test" loading="lazy"/>.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if input != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, input, output)
|
||||
|
@ -308,7 +308,7 @@ func TestSelfClosingTags(t *testing.T) {
|
|||
|
||||
func TestTable(t *testing.T) {
|
||||
input := `<table><tr><th>A</th><th colspan="2">B</th></tr><tr><td>C</td><td>D</td><td>E</td></tr></table>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if input != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, input, output)
|
||||
|
@ -318,7 +318,7 @@ func TestTable(t *testing.T) {
|
|||
func TestRelativeURL(t *testing.T) {
|
||||
input := `This <a href="/test.html">link is relative</a> and this image: <img src="../folder/image.png"/>`
|
||||
expected := `This <a href="http://example.org/test.html" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">link is relative</a> and this image: <img src="http://example.org/folder/image.png" loading="lazy"/>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -328,7 +328,7 @@ func TestRelativeURL(t *testing.T) {
|
|||
func TestProtocolRelativeURL(t *testing.T) {
|
||||
input := `This <a href="//static.example.org/index.html">link is relative</a>.`
|
||||
expected := `This <a href="https://static.example.org/index.html" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">link is relative</a>.`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -338,7 +338,7 @@ func TestProtocolRelativeURL(t *testing.T) {
|
|||
func TestInvalidTag(t *testing.T) {
|
||||
input := `<p>My invalid <z>tag</z>.</p>`
|
||||
expected := `<p>My invalid tag.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -348,7 +348,7 @@ func TestInvalidTag(t *testing.T) {
|
|||
func TestVideoTag(t *testing.T) {
|
||||
input := `<p>My valid <video src="videofile.webm" autoplay poster="posterimage.jpg">fallback</video>.</p>`
|
||||
expected := `<p>My valid <video src="http://example.org/videofile.webm" poster="http://example.org/posterimage.jpg" controls>fallback</video>.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -358,7 +358,7 @@ func TestVideoTag(t *testing.T) {
|
|||
func TestAudioAndSourceTag(t *testing.T) {
|
||||
input := `<p>My music <audio controls="controls"><source src="foo.wav" type="audio/wav"></audio>.</p>`
|
||||
expected := `<p>My music <audio controls><source src="http://example.org/foo.wav" type="audio/wav"></audio>.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -368,7 +368,7 @@ func TestAudioAndSourceTag(t *testing.T) {
|
|||
func TestUnknownTag(t *testing.T) {
|
||||
input := `<p>My invalid <unknown>tag</unknown>.</p>`
|
||||
expected := `<p>My invalid tag.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -378,7 +378,7 @@ func TestUnknownTag(t *testing.T) {
|
|||
func TestInvalidNestedTag(t *testing.T) {
|
||||
input := `<p>My invalid <z>tag with some <em>valid</em> tag</z>.</p>`
|
||||
expected := `<p>My invalid tag with some <em>valid</em> tag.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -390,7 +390,7 @@ func TestInvalidIFrame(t *testing.T) {
|
|||
|
||||
input := `<iframe src="http://example.org/"></iframe>`
|
||||
expected := ``
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -402,7 +402,7 @@ func TestSameDomainIFrame(t *testing.T) {
|
|||
|
||||
input := `<iframe src="http://example.com/test"></iframe>`
|
||||
expected := ``
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: %q != %q`, expected, output)
|
||||
|
@ -414,7 +414,7 @@ func TestInvidiousIFrame(t *testing.T) {
|
|||
|
||||
input := `<iframe src="https://yewtu.be/watch?v=video_id"></iframe>`
|
||||
expected := `<iframe src="https://yewtu.be/watch?v=video_id" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: %q != %q`, expected, output)
|
||||
|
@ -432,7 +432,7 @@ func TestCustomYoutubeEmbedURL(t *testing.T) {
|
|||
|
||||
input := `<iframe src="https://www.invidious.custom/embed/1234"></iframe>`
|
||||
expected := `<iframe src="https://www.invidious.custom/embed/1234" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: %q != %q`, expected, output)
|
||||
|
@ -444,7 +444,7 @@ func TestIFrameWithChildElements(t *testing.T) {
|
|||
|
||||
input := `<iframe src="https://www.youtube.com/"><p>test</p></iframe>`
|
||||
expected := `<iframe src="https://www.youtube.com/" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.com/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -474,7 +474,7 @@ func TestLinkWithNoTarget(t *testing.T) {
|
|||
func TestAnchorLink(t *testing.T) {
|
||||
input := `<p>This link is <a href="#some-anchor">an anchor</a></p>`
|
||||
expected := `<p>This link is <a href="#some-anchor">an anchor</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -484,7 +484,7 @@ func TestAnchorLink(t *testing.T) {
|
|||
func TestInvalidURLScheme(t *testing.T) {
|
||||
input := `<p>This link is <a src="file:///etc/passwd">not valid</a></p>`
|
||||
expected := `<p>This link is not valid</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -494,7 +494,7 @@ func TestInvalidURLScheme(t *testing.T) {
|
|||
func TestAPTURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="apt:some-package?channel=test">valid</a></p>`
|
||||
expected := `<p>This link is <a href="apt:some-package?channel=test" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -504,7 +504,7 @@ func TestAPTURIScheme(t *testing.T) {
|
|||
func TestBitcoinURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W">valid</a></p>`
|
||||
expected := `<p>This link is <a href="bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -514,7 +514,7 @@ func TestBitcoinURIScheme(t *testing.T) {
|
|||
func TestCallToURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="callto:12345679">valid</a></p>`
|
||||
expected := `<p>This link is <a href="callto:12345679" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -524,7 +524,7 @@ func TestCallToURIScheme(t *testing.T) {
|
|||
func TestFeedURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="feed://example.com/rss.xml">valid</a></p>`
|
||||
expected := `<p>This link is <a href="feed://example.com/rss.xml" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -532,7 +532,7 @@ func TestFeedURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="feed:https://example.com/rss.xml">valid</a></p>`
|
||||
expected = `<p>This link is <a href="feed:https://example.com/rss.xml" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -542,7 +542,7 @@ func TestFeedURIScheme(t *testing.T) {
|
|||
func TestGeoURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="geo:13.4125,103.8667">valid</a></p>`
|
||||
expected := `<p>This link is <a href="geo:13.4125,103.8667" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -552,7 +552,7 @@ func TestGeoURIScheme(t *testing.T) {
|
|||
func TestItunesURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="itms://itunes.com/apps/my-app-name">valid</a></p>`
|
||||
expected := `<p>This link is <a href="itms://itunes.com/apps/my-app-name" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -560,7 +560,7 @@ func TestItunesURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="itms-apps://itunes.com/apps/my-app-name">valid</a></p>`
|
||||
expected = `<p>This link is <a href="itms-apps://itunes.com/apps/my-app-name" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -570,7 +570,7 @@ func TestItunesURIScheme(t *testing.T) {
|
|||
func TestMagnetURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7">valid</a></p>`
|
||||
expected := `<p>This link is <a href="magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -580,7 +580,7 @@ func TestMagnetURIScheme(t *testing.T) {
|
|||
func TestMailtoURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="mailto:jsmith@example.com?subject=A%20Test&body=My%20idea%20is%3A%20%0A">valid</a></p>`
|
||||
expected := `<p>This link is <a href="mailto:jsmith@example.com?subject=A%20Test&body=My%20idea%20is%3A%20%0A" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -590,7 +590,7 @@ func TestMailtoURIScheme(t *testing.T) {
|
|||
func TestNewsURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="news://news.server.example/*">valid</a></p>`
|
||||
expected := `<p>This link is <a href="news://news.server.example/*" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -598,7 +598,7 @@ func TestNewsURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="news:example.group.this">valid</a></p>`
|
||||
expected = `<p>This link is <a href="news:example.group.this" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -606,7 +606,7 @@ func TestNewsURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="nntp://news.server.example/example.group.this">valid</a></p>`
|
||||
expected = `<p>This link is <a href="nntp://news.server.example/example.group.this" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -616,7 +616,7 @@ func TestNewsURIScheme(t *testing.T) {
|
|||
func TestRTMPURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="rtmp://mycompany.com/vod/mp4:mycoolvideo.mov">valid</a></p>`
|
||||
expected := `<p>This link is <a href="rtmp://mycompany.com/vod/mp4:mycoolvideo.mov" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -626,7 +626,7 @@ func TestRTMPURIScheme(t *testing.T) {
|
|||
func TestSIPURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="sip:+1-212-555-1212:1234@gateway.com;user=phone">valid</a></p>`
|
||||
expected := `<p>This link is <a href="sip:+1-212-555-1212:1234@gateway.com;user=phone" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -634,7 +634,7 @@ func TestSIPURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="sips:alice@atlanta.com?subject=project%20x&priority=urgent">valid</a></p>`
|
||||
expected = `<p>This link is <a href="sips:alice@atlanta.com?subject=project%20x&priority=urgent" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -644,7 +644,7 @@ func TestSIPURIScheme(t *testing.T) {
|
|||
func TestSkypeURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="skype:echo123?call">valid</a></p>`
|
||||
expected := `<p>This link is <a href="skype:echo123?call" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -654,7 +654,7 @@ func TestSkypeURIScheme(t *testing.T) {
|
|||
func TestSpotifyURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="spotify:track:2jCnn1QPQ3E8ExtLe6INsx">valid</a></p>`
|
||||
expected := `<p>This link is <a href="spotify:track:2jCnn1QPQ3E8ExtLe6INsx" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -664,7 +664,7 @@ func TestSpotifyURIScheme(t *testing.T) {
|
|||
func TestSteamURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="steam://settings/account">valid</a></p>`
|
||||
expected := `<p>This link is <a href="steam://settings/account" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -674,7 +674,7 @@ func TestSteamURIScheme(t *testing.T) {
|
|||
func TestSubversionURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="svn://example.org">valid</a></p>`
|
||||
expected := `<p>This link is <a href="svn://example.org" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -682,7 +682,7 @@ func TestSubversionURIScheme(t *testing.T) {
|
|||
|
||||
input = `<p>This link is <a href="svn+ssh://example.org">valid</a></p>`
|
||||
expected = `<p>This link is <a href="svn+ssh://example.org" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -692,7 +692,7 @@ func TestSubversionURIScheme(t *testing.T) {
|
|||
func TestTelURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="tel:+1-201-555-0123">valid</a></p>`
|
||||
expected := `<p>This link is <a href="tel:+1-201-555-0123" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -702,7 +702,7 @@ func TestTelURIScheme(t *testing.T) {
|
|||
func TestWebcalURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="webcal://example.com/calendar.ics">valid</a></p>`
|
||||
expected := `<p>This link is <a href="webcal://example.com/calendar.ics" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -712,7 +712,7 @@ func TestWebcalURIScheme(t *testing.T) {
|
|||
func TestXMPPURIScheme(t *testing.T) {
|
||||
input := `<p>This link is <a href="xmpp:user@host?subscribe&type=subscribed">valid</a></p>`
|
||||
expected := `<p>This link is <a href="xmpp:user@host?subscribe&type=subscribed" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">valid</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -722,7 +722,7 @@ func TestXMPPURIScheme(t *testing.T) {
|
|||
func TestBlacklistedLink(t *testing.T) {
|
||||
input := `<p>This image is not valid <img src="https://stats.wordpress.com/some-tracker"></p>`
|
||||
expected := `<p>This image is not valid </p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -732,7 +732,7 @@ func TestBlacklistedLink(t *testing.T) {
|
|||
func TestLinkWithTrackers(t *testing.T) {
|
||||
input := `<p>This link has trackers <a href="https://example.com/page?utm_source=newsletter">Test</a></p>`
|
||||
expected := `<p>This link has trackers <a href="https://example.com/page" rel="noopener noreferrer" referrerpolicy="no-referrer" target="_blank">Test</a></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -742,7 +742,7 @@ func TestLinkWithTrackers(t *testing.T) {
|
|||
func TestImageSrcWithTrackers(t *testing.T) {
|
||||
input := `<p>This image has trackers <img src="https://example.org/?id=123&utm_source=newsletter&utm_medium=email&fbclid=abc123"></p>`
|
||||
expected := `<p>This image has trackers <img src="https://example.org/?id=123" loading="lazy"></p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -752,7 +752,7 @@ func TestImageSrcWithTrackers(t *testing.T) {
|
|||
func Test1x1PixelTracker(t *testing.T) {
|
||||
input := `<p><img src="https://tracker1.example.org/" height="1" width="1"> and <img src="https://tracker2.example.org/" height="1" width="1"/></p>`
|
||||
expected := `<p> and </p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -762,7 +762,7 @@ func Test1x1PixelTracker(t *testing.T) {
|
|||
func Test0x0PixelTracker(t *testing.T) {
|
||||
input := `<p><img src="https://tracker1.example.org/" height="0" width="0"> and <img src="https://tracker2.example.org/" height="0" width="0"/></p>`
|
||||
expected := `<p> and </p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -772,7 +772,7 @@ func Test0x0PixelTracker(t *testing.T) {
|
|||
func TestXmlEntities(t *testing.T) {
|
||||
input := `<pre>echo "test" > /etc/hosts</pre>`
|
||||
expected := `<pre>echo "test" > /etc/hosts</pre>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -782,7 +782,7 @@ func TestXmlEntities(t *testing.T) {
|
|||
func TestEspaceAttributes(t *testing.T) {
|
||||
input := `<td rowspan="<b>test</b>">test</td>`
|
||||
expected := `<td rowspan="<b>test</b>">test</td>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -792,7 +792,7 @@ func TestEspaceAttributes(t *testing.T) {
|
|||
func TestReplaceYoutubeURL(t *testing.T) {
|
||||
input := `<iframe src="http://www.youtube.com/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent"></iframe>`
|
||||
expected := `<iframe src="https://www.youtube-nocookie.com/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -802,7 +802,7 @@ func TestReplaceYoutubeURL(t *testing.T) {
|
|||
func TestReplaceSecureYoutubeURL(t *testing.T) {
|
||||
input := `<iframe src="https://www.youtube.com/embed/test123"></iframe>`
|
||||
expected := `<iframe src="https://www.youtube-nocookie.com/embed/test123" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -812,7 +812,7 @@ func TestReplaceSecureYoutubeURL(t *testing.T) {
|
|||
func TestReplaceSecureYoutubeURLWithParameters(t *testing.T) {
|
||||
input := `<iframe src="https://www.youtube.com/embed/test123?rel=0&controls=0"></iframe>`
|
||||
expected := `<iframe src="https://www.youtube-nocookie.com/embed/test123?rel=0&controls=0" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -822,7 +822,7 @@ func TestReplaceSecureYoutubeURLWithParameters(t *testing.T) {
|
|||
func TestReplaceYoutubeURLAlreadyReplaced(t *testing.T) {
|
||||
input := `<iframe src="https://www.youtube-nocookie.com/embed/test123?rel=0&controls=0" sandbox="allow-scripts allow-same-origin"></iframe>`
|
||||
expected := `<iframe src="https://www.youtube-nocookie.com/embed/test123?rel=0&controls=0" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -832,7 +832,7 @@ func TestReplaceYoutubeURLAlreadyReplaced(t *testing.T) {
|
|||
func TestReplaceProtocolRelativeYoutubeURL(t *testing.T) {
|
||||
input := `<iframe src="//www.youtube.com/embed/Bf2W84jrGqs" width="560" height="314" allowfullscreen="allowfullscreen"></iframe>`
|
||||
expected := `<iframe src="https://www.youtube-nocookie.com/embed/Bf2W84jrGqs" width="560" height="314" allowfullscreen="allowfullscreen" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -851,7 +851,7 @@ func TestReplaceYoutubeURLWithCustomURL(t *testing.T) {
|
|||
|
||||
input := `<iframe src="https://www.youtube.com/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent"></iframe>`
|
||||
expected := `<iframe src="https://invidious.custom/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -861,7 +861,7 @@ func TestReplaceYoutubeURLWithCustomURL(t *testing.T) {
|
|||
func TestVimeoIframeRewriteWithQueryString(t *testing.T) {
|
||||
input := `<iframe src="https://player.vimeo.com/video/123456?title=0&byline=0"></iframe>`
|
||||
expected := `<iframe src="https://player.vimeo.com/video/123456?title=0&byline=0&dnt=1" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: %q != %q`, expected, output)
|
||||
|
@ -871,7 +871,7 @@ func TestVimeoIframeRewriteWithQueryString(t *testing.T) {
|
|||
func TestVimeoIframeRewriteWithoutQueryString(t *testing.T) {
|
||||
input := `<iframe src="https://player.vimeo.com/video/123456"></iframe>`
|
||||
expected := `<iframe src="https://player.vimeo.com/video/123456?dnt=1" sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox" loading="lazy"></iframe>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: %q != %q`, expected, output)
|
||||
|
@ -881,7 +881,7 @@ func TestVimeoIframeRewriteWithoutQueryString(t *testing.T) {
|
|||
func TestReplaceNoScript(t *testing.T) {
|
||||
input := `<p>Before paragraph.</p><noscript>Inside <code>noscript</code> tag with an image: <img src="http://example.org/" alt="Test" loading="lazy"></noscript><p>After paragraph.</p>`
|
||||
expected := `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -891,7 +891,7 @@ func TestReplaceNoScript(t *testing.T) {
|
|||
func TestReplaceScript(t *testing.T) {
|
||||
input := `<p>Before paragraph.</p><script type="text/javascript">alert("1");</script><p>After paragraph.</p>`
|
||||
expected := `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -901,7 +901,7 @@ func TestReplaceScript(t *testing.T) {
|
|||
func TestReplaceStyle(t *testing.T) {
|
||||
input := `<p>Before paragraph.</p><style>body { background-color: #ff0000; }</style><p>After paragraph.</p>`
|
||||
expected := `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -911,7 +911,7 @@ func TestReplaceStyle(t *testing.T) {
|
|||
func TestHiddenParagraph(t *testing.T) {
|
||||
input := `<p>Before paragraph.</p><p hidden>This should <em>not</em> appear in the <strong>output</strong></p><p>After paragraph.</p>`
|
||||
expected := `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -922,7 +922,7 @@ func TestAttributesAreStripped(t *testing.T) {
|
|||
input := `<p style="color: red;">Some text.<hr style="color: blue"/>Test.</p>`
|
||||
expected := `<p>Some text.<hr/>Test.</p>`
|
||||
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
}
|
||||
|
@ -931,7 +931,7 @@ func TestAttributesAreStripped(t *testing.T) {
|
|||
func TestMathML(t *testing.T) {
|
||||
input := `<math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mi>x</mi><mn>2</mn></msup></math>`
|
||||
expected := `<math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mi>x</mi><mn>2</mn></msup></math>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -941,7 +941,7 @@ func TestMathML(t *testing.T) {
|
|||
func TestInvalidMathMLXMLNamespace(t *testing.T) {
|
||||
input := `<math xmlns="http://example.org"><msup><mi>x</mi><mn>2</mn></msup></math>`
|
||||
expected := `<math xmlns="http://www.w3.org/1998/Math/MathML"><msup><mi>x</mi><mn>2</mn></msup></math>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -951,7 +951,7 @@ func TestInvalidMathMLXMLNamespace(t *testing.T) {
|
|||
func TestBlockedResourcesSubstrings(t *testing.T) {
|
||||
input := `<p>Before paragraph.</p><img src="http://stats.wordpress.com/something.php" alt="Blocked Resource"><p>After paragraph.</p>`
|
||||
expected := `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output := SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output := sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -959,7 +959,7 @@ func TestBlockedResourcesSubstrings(t *testing.T) {
|
|||
|
||||
input = `<p>Before paragraph.</p><img src="http://twitter.com/share?text=This+is+google+a+search+engine&url=https%3A%2F%2Fwww.google.com" alt="Blocked Resource"><p>After paragraph.</p>`
|
||||
expected = `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
@ -967,7 +967,7 @@ func TestBlockedResourcesSubstrings(t *testing.T) {
|
|||
|
||||
input = `<p>Before paragraph.</p><img src="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fwww.google.com%[title]=This+Is%2C+Google+a+search+engine" alt="Blocked Resource"><p>After paragraph.</p>`
|
||||
expected = `<p>Before paragraph.</p><p>After paragraph.</p>`
|
||||
output = SanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
output = sanitizeHTMLWithDefaultOptions("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
|
|
|
@ -9,14 +9,14 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ImageCandidate struct {
|
||||
type imageCandidate struct {
|
||||
ImageURL string
|
||||
Descriptor string
|
||||
}
|
||||
|
||||
type ImageCandidates []*ImageCandidate
|
||||
type imageCandidates []*imageCandidate
|
||||
|
||||
func (c ImageCandidates) String() string {
|
||||
func (c imageCandidates) String() string {
|
||||
htmlCandidates := make([]string, 0, len(c))
|
||||
|
||||
for _, imageCandidate := range c {
|
||||
|
@ -35,7 +35,7 @@ func (c ImageCandidates) String() string {
|
|||
|
||||
// ParseSrcSetAttribute returns the list of image candidates from the set.
|
||||
// https://html.spec.whatwg.org/#parse-a-srcset-attribute
|
||||
func ParseSrcSetAttribute(attributeValue string) (imageCandidates ImageCandidates) {
|
||||
func ParseSrcSetAttribute(attributeValue string) (imageCandidates imageCandidates) {
|
||||
for _, unparsedCandidate := range strings.Split(attributeValue, ", ") {
|
||||
if candidate, err := parseImageCandidate(unparsedCandidate); err == nil {
|
||||
imageCandidates = append(imageCandidates, candidate)
|
||||
|
@ -45,18 +45,18 @@ func ParseSrcSetAttribute(attributeValue string) (imageCandidates ImageCandidate
|
|||
return imageCandidates
|
||||
}
|
||||
|
||||
func parseImageCandidate(input string) (*ImageCandidate, error) {
|
||||
func parseImageCandidate(input string) (*imageCandidate, error) {
|
||||
parts := strings.Split(strings.TrimSpace(input), " ")
|
||||
nbParts := len(parts)
|
||||
|
||||
switch nbParts {
|
||||
case 1:
|
||||
return &ImageCandidate{ImageURL: parts[0]}, nil
|
||||
return &imageCandidate{ImageURL: parts[0]}, nil
|
||||
case 2:
|
||||
if !isValidWidthOrDensityDescriptor(parts[1]) {
|
||||
return nil, fmt.Errorf(`srcset: invalid descriptor`)
|
||||
}
|
||||
return &ImageCandidate{ImageURL: parts[0], Descriptor: parts[1]}, nil
|
||||
return &imageCandidate{ImageURL: parts[0], Descriptor: parts[1]}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`srcset: invalid number of descriptors`)
|
||||
}
|
||||
|
|
|
@ -22,27 +22,27 @@ import (
|
|||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type SubscriptionFinder struct {
|
||||
type subscriptionFinder struct {
|
||||
requestBuilder *fetcher.RequestBuilder
|
||||
feedDownloaded bool
|
||||
feedResponseInfo *model.FeedCreationRequestFromSubscriptionDiscovery
|
||||
}
|
||||
|
||||
func NewSubscriptionFinder(requestBuilder *fetcher.RequestBuilder) *SubscriptionFinder {
|
||||
return &SubscriptionFinder{
|
||||
func NewSubscriptionFinder(requestBuilder *fetcher.RequestBuilder) *subscriptionFinder {
|
||||
return &subscriptionFinder{
|
||||
requestBuilder: requestBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) IsFeedAlreadyDownloaded() bool {
|
||||
func (f *subscriptionFinder) IsFeedAlreadyDownloaded() bool {
|
||||
return f.feedDownloaded
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FeedResponseInfo() *model.FeedCreationRequestFromSubscriptionDiscovery {
|
||||
func (f *subscriptionFinder) FeedResponseInfo() *model.FeedCreationRequestFromSubscriptionDiscovery {
|
||||
return f.feedResponseInfo
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string, rssBridgeToken string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string, rssBridgeToken string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
responseHandler := fetcher.NewResponseHandler(f.requestBuilder.ExecuteRequest(websiteURL))
|
||||
defer responseHandler.Close()
|
||||
|
||||
|
@ -71,7 +71,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
|
||||
// Step 2) Check if the website URL is a YouTube channel.
|
||||
slog.Debug("Try to detect feeds from YouTube channel page", slog.String("website_url", websiteURL))
|
||||
if subscriptions, localizedError := f.FindSubscriptionsFromYouTubeChannelPage(websiteURL); localizedError != nil {
|
||||
if subscriptions, localizedError := f.findSubscriptionsFromYouTubeChannelPage(websiteURL); localizedError != nil {
|
||||
return nil, localizedError
|
||||
} else if len(subscriptions) > 0 {
|
||||
slog.Debug("Subscriptions found from YouTube channel page", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions))
|
||||
|
@ -80,7 +80,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
|
||||
// Step 3) Check if the website URL is a YouTube playlist.
|
||||
slog.Debug("Try to detect feeds from YouTube playlist page", slog.String("website_url", websiteURL))
|
||||
if subscriptions, localizedError := f.FindSubscriptionsFromYouTubePlaylistPage(websiteURL); localizedError != nil {
|
||||
if subscriptions, localizedError := f.findSubscriptionsFromYouTubePlaylistPage(websiteURL); localizedError != nil {
|
||||
return nil, localizedError
|
||||
} else if len(subscriptions) > 0 {
|
||||
slog.Debug("Subscriptions found from YouTube playlist page", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions))
|
||||
|
@ -92,7 +92,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
slog.String("website_url", websiteURL),
|
||||
slog.String("content_type", responseHandler.ContentType()),
|
||||
)
|
||||
if subscriptions, localizedError := f.FindSubscriptionsFromWebPage(websiteURL, responseHandler.ContentType(), bytes.NewReader(responseBody)); localizedError != nil {
|
||||
if subscriptions, localizedError := f.findSubscriptionsFromWebPage(websiteURL, responseHandler.ContentType(), bytes.NewReader(responseBody)); localizedError != nil {
|
||||
return nil, localizedError
|
||||
} else if len(subscriptions) > 0 {
|
||||
slog.Debug("Subscriptions found from web page", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions))
|
||||
|
@ -102,7 +102,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
// Step 5) Check if the website URL can use RSS-Bridge.
|
||||
if rssBridgeURL != "" {
|
||||
slog.Debug("Try to detect feeds with RSS-Bridge", slog.String("website_url", websiteURL))
|
||||
if subscriptions, localizedError := f.FindSubscriptionsFromRSSBridge(websiteURL, rssBridgeURL, rssBridgeToken); localizedError != nil {
|
||||
if subscriptions, localizedError := f.findSubscriptionsFromRSSBridge(websiteURL, rssBridgeURL, rssBridgeToken); localizedError != nil {
|
||||
return nil, localizedError
|
||||
} else if len(subscriptions) > 0 {
|
||||
slog.Debug("Subscriptions found from RSS-Bridge", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions))
|
||||
|
@ -112,7 +112,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
|
||||
// Step 6) Check if the website has a known feed URL.
|
||||
slog.Debug("Try to detect feeds from well-known URLs", slog.String("website_url", websiteURL))
|
||||
if subscriptions, localizedError := f.FindSubscriptionsFromWellKnownURLs(websiteURL); localizedError != nil {
|
||||
if subscriptions, localizedError := f.findSubscriptionsFromWellKnownURLs(websiteURL); localizedError != nil {
|
||||
return nil, localizedError
|
||||
} else if len(subscriptions) > 0 {
|
||||
slog.Debug("Subscriptions found with well-known URLs", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions))
|
||||
|
@ -122,7 +122,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string,
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptionsFromWebPage(websiteURL, contentType string, body io.Reader) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) findSubscriptionsFromWebPage(websiteURL, contentType string, body io.Reader) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
queries := map[string]string{
|
||||
"link[type='application/rss+xml']": parser.FormatRSS,
|
||||
"link[type='application/atom+xml']": parser.FormatAtom,
|
||||
|
@ -151,7 +151,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromWebPage(websiteURL, contentTyp
|
|||
subscriptionURLs := make(map[string]bool)
|
||||
for query, kind := range queries {
|
||||
doc.Find(query).Each(func(i int, s *goquery.Selection) {
|
||||
subscription := new(Subscription)
|
||||
subscription := new(subscription)
|
||||
subscription.Type = kind
|
||||
|
||||
if title, exists := s.Attr("title"); exists {
|
||||
|
@ -181,7 +181,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromWebPage(websiteURL, contentTyp
|
|||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptionsFromWellKnownURLs(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) findSubscriptionsFromWellKnownURLs(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
knownURLs := map[string]string{
|
||||
"atom.xml": parser.FormatAtom,
|
||||
"feed.atom": parser.FormatAtom,
|
||||
|
@ -237,7 +237,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromWellKnownURLs(websiteURL strin
|
|||
continue
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, &Subscription{
|
||||
subscriptions = append(subscriptions, &subscription{
|
||||
Type: kind,
|
||||
Title: fullURL,
|
||||
URL: fullURL,
|
||||
|
@ -248,7 +248,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromWellKnownURLs(websiteURL strin
|
|||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptionsFromRSSBridge(websiteURL, rssBridgeURL string, rssBridgeToken string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) findSubscriptionsFromRSSBridge(websiteURL, rssBridgeURL string, rssBridgeToken string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
slog.Debug("Trying to detect feeds using RSS-Bridge",
|
||||
slog.String("website_url", websiteURL),
|
||||
slog.String("rssbridge_url", rssBridgeURL),
|
||||
|
@ -273,7 +273,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromRSSBridge(websiteURL, rssBridg
|
|||
|
||||
subscriptions := make(Subscriptions, 0, len(bridges))
|
||||
for _, bridge := range bridges {
|
||||
subscriptions = append(subscriptions, &Subscription{
|
||||
subscriptions = append(subscriptions, &subscription{
|
||||
Title: bridge.BridgeMeta.Name,
|
||||
URL: bridge.URL,
|
||||
Type: parser.FormatAtom,
|
||||
|
@ -283,7 +283,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromRSSBridge(websiteURL, rssBridg
|
|||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptionsFromYouTubeChannelPage(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) findSubscriptionsFromYouTubeChannelPage(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
decodedUrl, err := url.Parse(websiteURL)
|
||||
if err != nil {
|
||||
return nil, locale.NewLocalizedErrorWrapper(err, "error.invalid_site_url", err)
|
||||
|
@ -302,7 +302,7 @@ func (f *SubscriptionFinder) FindSubscriptionsFromYouTubeChannelPage(websiteURL
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *SubscriptionFinder) FindSubscriptionsFromYouTubePlaylistPage(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
func (f *subscriptionFinder) findSubscriptionsFromYouTubePlaylistPage(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) {
|
||||
decodedUrl, err := url.Parse(websiteURL)
|
||||
if err != nil {
|
||||
return nil, locale.NewLocalizedErrorWrapper(err, "error.invalid_site_url", err)
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestFindYoutubePlaylistFeed(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
subscriptions, localizedError := NewSubscriptionFinder(nil).FindSubscriptionsFromYouTubePlaylistPage(scenario.websiteURL)
|
||||
subscriptions, localizedError := NewSubscriptionFinder(nil).findSubscriptionsFromYouTubePlaylistPage(scenario.websiteURL)
|
||||
if scenario.discoveryError {
|
||||
if localizedError == nil {
|
||||
t.Fatalf(`Parsing an invalid URL should return an error`)
|
||||
|
@ -159,7 +159,7 @@ func TestFindYoutubeChannelFeed(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
subscriptions, localizedError := NewSubscriptionFinder(nil).FindSubscriptionsFromYouTubeChannelPage(scenario.websiteURL)
|
||||
subscriptions, localizedError := NewSubscriptionFinder(nil).findSubscriptionsFromYouTubeChannelPage(scenario.websiteURL)
|
||||
if scenario.discoveryError {
|
||||
if localizedError == nil {
|
||||
t.Fatalf(`Parsing an invalid URL should return an error`)
|
||||
|
@ -197,7 +197,7 @@ func TestParseWebPageWithRssFeed(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ func TestParseWebPageWithAtomFeed(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ func TestParseWebPageWithJSONFeed(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -296,7 +296,7 @@ func TestParseWebPageWithOldJSONFeedMimeType(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ func TestParseWebPageWithRelativeFeedURL(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -362,7 +362,7 @@ func TestParseWebPageWithEmptyTitle(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -396,7 +396,7 @@ func TestParseWebPageWithMultipleFeeds(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ func TestParseWebPageWithDuplicatedFeeds(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ func TestParseWebPageWithEmptyFeedURL(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ func TestParseWebPageWithNoHref(t *testing.T) {
|
|||
</body>
|
||||
</html>`
|
||||
|
||||
subscriptions, err := NewSubscriptionFinder(nil).FindSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
subscriptions, err := NewSubscriptionFinder(nil).findSubscriptionsFromWebPage("http://example.org/", "text/html", strings.NewReader(htmlPage))
|
||||
if err != nil {
|
||||
t.Fatalf(`Parsing a correctly formatted HTML page should not return any error: %v`, err)
|
||||
}
|
||||
|
|
|
@ -5,20 +5,20 @@ package subscription // import "miniflux.app/v2/internal/reader/subscription"
|
|||
|
||||
import "fmt"
|
||||
|
||||
// Subscription represents a feed subscription.
|
||||
type Subscription struct {
|
||||
// subscription represents a feed subscription.
|
||||
type subscription struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func NewSubscription(title, url, kind string) *Subscription {
|
||||
return &Subscription{Title: title, URL: url, Type: kind}
|
||||
func NewSubscription(title, url, kind string) *subscription {
|
||||
return &subscription{Title: title, URL: url, Type: kind}
|
||||
}
|
||||
|
||||
func (s Subscription) String() string {
|
||||
func (s subscription) String() string {
|
||||
return fmt.Sprintf(`Title=%q, URL=%q, Type=%q`, s.Title, s.URL, s.Type)
|
||||
}
|
||||
|
||||
// Subscriptions represents a list of subscription.
|
||||
type Subscriptions []*Subscription
|
||||
type Subscriptions []*subscription
|
||||
|
|
|
@ -10,14 +10,14 @@ import (
|
|||
"miniflux.app/v2/internal/locale"
|
||||
)
|
||||
|
||||
// AuthForm represents the authentication form.
|
||||
type AuthForm struct {
|
||||
// authForm represents the authentication form.
|
||||
type authForm struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// Validate makes sure the form values are valid.
|
||||
func (a AuthForm) Validate() *locale.LocalizedError {
|
||||
func (a authForm) Validate() *locale.LocalizedError {
|
||||
if a.Username == "" || a.Password == "" {
|
||||
return locale.NewLocalizedError("error.fields_mandatory")
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ func (a AuthForm) Validate() *locale.LocalizedError {
|
|||
}
|
||||
|
||||
// NewAuthForm returns a new AuthForm.
|
||||
func NewAuthForm(r *http.Request) *AuthForm {
|
||||
return &AuthForm{
|
||||
func NewAuthForm(r *http.Request) *authForm {
|
||||
return &authForm{
|
||||
Username: strings.TrimSpace(r.FormValue("username")),
|
||||
Password: strings.TrimSpace(r.FormValue("password")),
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ import (
|
|||
"miniflux.app/v2/internal/validator"
|
||||
)
|
||||
|
||||
// MarkReadBehavior list all possible behaviors for automatically marking an entry as read
|
||||
type MarkReadBehavior string
|
||||
// markReadBehavior list all possible behaviors for automatically marking an entry as read
|
||||
type markReadBehavior string
|
||||
|
||||
const (
|
||||
NoAutoMarkAsRead MarkReadBehavior = "no-auto"
|
||||
MarkAsReadOnView MarkReadBehavior = "on-view"
|
||||
MarkAsReadOnViewButWaitForPlayerCompletion MarkReadBehavior = "on-view-but-wait-for-player-completion"
|
||||
MarkAsReadOnlyOnPlayerCompletion MarkReadBehavior = "on-player-completion"
|
||||
NoAutoMarkAsRead markReadBehavior = "no-auto"
|
||||
MarkAsReadOnView markReadBehavior = "on-view"
|
||||
MarkAsReadOnViewButWaitForPlayerCompletion markReadBehavior = "on-view-but-wait-for-player-completion"
|
||||
MarkAsReadOnlyOnPlayerCompletion markReadBehavior = "on-player-completion"
|
||||
)
|
||||
|
||||
// SettingsForm represents the settings form.
|
||||
|
@ -48,7 +48,7 @@ type SettingsForm struct {
|
|||
CategoriesSortingOrder string
|
||||
MarkReadOnView bool
|
||||
// MarkReadBehavior is a string representation of the MarkReadOnView and MarkReadOnMediaPlayerCompletion fields together
|
||||
MarkReadBehavior MarkReadBehavior
|
||||
MarkReadBehavior markReadBehavior
|
||||
MediaPlaybackRate float64
|
||||
BlockFilterEntryRules string
|
||||
KeepFilterEntryRules string
|
||||
|
@ -58,7 +58,7 @@ type SettingsForm struct {
|
|||
|
||||
// MarkAsReadBehavior returns the MarkReadBehavior from the given MarkReadOnView and MarkReadOnMediaPlayerCompletion values.
|
||||
// Useful to convert the values from the User model to the form
|
||||
func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) MarkReadBehavior {
|
||||
func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) markReadBehavior {
|
||||
switch {
|
||||
case markReadOnView && !markReadOnMediaPlayerCompletion:
|
||||
return MarkAsReadOnView
|
||||
|
@ -73,9 +73,9 @@ func MarkAsReadBehavior(markReadOnView, markReadOnMediaPlayerCompletion bool) Ma
|
|||
}
|
||||
}
|
||||
|
||||
// ExtractMarkAsReadBehavior returns the MarkReadOnView and MarkReadOnMediaPlayerCompletion values from the given MarkReadBehavior.
|
||||
// extractMarkAsReadBehavior returns the MarkReadOnView and MarkReadOnMediaPlayerCompletion values from the given MarkReadBehavior.
|
||||
// Useful to extract the values from the form to the User model
|
||||
func ExtractMarkAsReadBehavior(behavior MarkReadBehavior) (markReadOnView, markReadOnMediaPlayerCompletion bool) {
|
||||
func extractMarkAsReadBehavior(behavior markReadBehavior) (markReadOnView, markReadOnMediaPlayerCompletion bool) {
|
||||
switch behavior {
|
||||
case MarkAsReadOnView:
|
||||
return true, false
|
||||
|
@ -119,7 +119,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
|||
user.AlwaysOpenExternalLinks = s.AlwaysOpenExternalLinks
|
||||
user.OpenExternalLinksInNewTab = s.OpenExternalLinksInNewTab
|
||||
|
||||
MarkReadOnView, MarkReadOnMediaPlayerCompletion := ExtractMarkAsReadBehavior(s.MarkReadBehavior)
|
||||
MarkReadOnView, MarkReadOnMediaPlayerCompletion := extractMarkAsReadBehavior(s.MarkReadBehavior)
|
||||
user.MarkReadOnView = MarkReadOnView
|
||||
user.MarkReadOnMediaPlayerCompletion = MarkReadOnMediaPlayerCompletion
|
||||
|
||||
|
@ -205,7 +205,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
|||
DefaultHomePage: r.FormValue("default_home_page"),
|
||||
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
|
||||
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
|
||||
MarkReadBehavior: MarkReadBehavior(r.FormValue("mark_read_behavior")),
|
||||
MarkReadBehavior: markReadBehavior(r.FormValue("mark_read_behavior")),
|
||||
MediaPlaybackRate: mediaPlaybackRate,
|
||||
BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
|
||||
KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
|
||||
|
|
|
@ -13,28 +13,28 @@ import (
|
|||
"miniflux.app/v2/internal/ui/static"
|
||||
)
|
||||
|
||||
// View wraps template argument building.
|
||||
type View struct {
|
||||
// view wraps template argument building.
|
||||
type view struct {
|
||||
tpl *template.Engine
|
||||
r *http.Request
|
||||
params map[string]any
|
||||
}
|
||||
|
||||
// Set adds a new template argument.
|
||||
func (v *View) Set(param string, value any) *View {
|
||||
func (v *view) Set(param string, value any) *view {
|
||||
v.params[param] = value
|
||||
return v
|
||||
}
|
||||
|
||||
// Render executes the template with arguments.
|
||||
func (v *View) Render(template string) []byte {
|
||||
func (v *view) Render(template string) []byte {
|
||||
return v.tpl.Render(template+".html", v.params)
|
||||
}
|
||||
|
||||
// New returns a new view with default parameters.
|
||||
func New(tpl *template.Engine, r *http.Request, sess *session.Session) *View {
|
||||
func New(tpl *template.Engine, r *http.Request, sess *session.Session) *view {
|
||||
theme := request.UserTheme(r)
|
||||
return &View{tpl, r, map[string]any{
|
||||
return &view{tpl, r, map[string]any{
|
||||
"menu": "",
|
||||
"csrf": request.CSRF(r),
|
||||
"flashMessage": sess.FlashMessage(request.FlashMessage(r)),
|
||||
|
|
|
@ -27,7 +27,7 @@ func NewPool(store *storage.Storage, nbWorkers int) *Pool {
|
|||
}
|
||||
|
||||
for i := range nbWorkers {
|
||||
worker := &Worker{id: i, store: store}
|
||||
worker := &worker{id: i, store: store}
|
||||
go worker.Run(workerPool.queue)
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@ import (
|
|||
"miniflux.app/v2/internal/storage"
|
||||
)
|
||||
|
||||
// Worker refreshes a feed in the background.
|
||||
type Worker struct {
|
||||
// worker refreshes a feed in the background.
|
||||
type worker struct {
|
||||
id int
|
||||
store *storage.Storage
|
||||
}
|
||||
|
||||
// Run wait for a job and refresh the given feed.
|
||||
func (w *Worker) Run(c <-chan model.Job) {
|
||||
func (w *worker) Run(c <-chan model.Job) {
|
||||
slog.Debug("Worker started",
|
||||
slog.Int("worker_id", w.id),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue