From 566670cc06816653a879f216dc7b9db4c2ab2acd Mon Sep 17 00:00:00 2001 From: Julien Voisin Date: Fri, 8 Aug 2025 02:27:04 +0200 Subject: [PATCH] refactor: unexport symbols --- internal/config/config.go | 7 - internal/config/options.go | 3 + internal/metric/metric.go | 10 +- internal/reader/atom/atom_03.go | 22 +-- internal/reader/atom/atom_03_adapter.go | 25 +-- internal/reader/atom/atom_10.go | 40 ++--- internal/reader/atom/atom_10_adapter.go | 24 +-- internal/reader/atom/atom_common.go | 20 +-- internal/reader/atom/parser.go | 6 +- internal/reader/opml/handler.go | 8 +- internal/reader/opml/opml.go | 1 + internal/reader/opml/parser.go | 8 +- internal/reader/opml/parser_test.go | 79 +++++---- internal/reader/opml/serializer.go | 10 +- internal/reader/opml/serializer_test.go | 20 +-- internal/reader/opml/subscription.go | 15 +- internal/reader/rdf/adapter.go | 10 +- internal/reader/rdf/parser.go | 5 +- internal/reader/rdf/rdf.go | 12 +- internal/reader/rss/adapter.go | 28 ++- internal/reader/rss/atom.go | 6 +- internal/reader/rss/feedburner.go | 4 +- internal/reader/rss/parser.go | 5 +- internal/reader/rss/podcast.go | 6 +- internal/reader/rss/rss.go | 50 +++--- internal/reader/sanitizer/sanitizer.go | 3 +- internal/reader/sanitizer/sanitizer_test.go | 176 +++++++++---------- internal/reader/sanitizer/srcset.go | 14 +- internal/reader/subscription/finder.go | 38 ++-- internal/reader/subscription/finder_test.go | 24 +-- internal/reader/subscription/subscription.go | 12 +- internal/ui/form/auth.go | 10 +- internal/ui/form/settings.go | 24 +-- internal/ui/view/view.go | 12 +- internal/worker/pool.go | 2 +- internal/worker/worker.go | 6 +- 36 files changed, 369 insertions(+), 376 deletions(-) delete mode 100644 internal/config/config.go diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 6d702248..00000000 --- a/internal/config/config.go +++ /dev/null @@ -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 diff --git a/internal/config/options.go b/internal/config/options.go index ba626b6e..4563dc1c 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -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 diff --git a/internal/metric/metric.go b/internal/metric/metric.go index 1c0f4c82..f362841d 100644 --- a/internal/metric/metric.go +++ b/internal/metric/metric.go @@ -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") diff --git a/internal/reader/atom/atom_03.go b/internal/reader/atom/atom_03.go index 298a90ec..ec447716 100644 --- a/internal/reader/atom/atom_03.go +++ b/internal/reader/atom/atom_03.go @@ -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 { diff --git a/internal/reader/atom/atom_03_adapter.go b/internal/reader/atom/atom_03_adapter.go index daa877d8..5a12a444 100644 --- a/internal/reader/atom/atom_03_adapter.go +++ b/internal/reader/atom/atom_03_adapter.go @@ -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 diff --git a/internal/reader/atom/atom_10.go b/internal/reader/atom/atom_10.go index afe87a68..74151fe0 100644 --- a/internal/reader/atom/atom_10.go +++ b/internal/reader/atom/atom_10.go @@ -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"` } diff --git a/internal/reader/atom/atom_10_adapter.go b/internal/reader/atom/atom_10_adapter.go index 7045faf9..69eb178f 100644 --- a/internal/reader/atom/atom_10_adapter.go +++ b/internal/reader/atom/atom_10_adapter.go @@ -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 diff --git a/internal/reader/atom/atom_common.go b/internal/reader/atom/atom_common.go index 945c5573..559cd301 100644 --- a/internal/reader/atom/atom_common.go +++ b/internal/reader/atom/atom_common.go @@ -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 { diff --git a/internal/reader/atom/parser.go b/internal/reader/atom/parser.go index f97985bc..9552444e 100644 --- a/internal/reader/atom/parser.go +++ b/internal/reader/atom/parser.go @@ -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) } diff --git a/internal/reader/opml/handler.go b/internal/reader/opml/handler.go index c8dc610b..b51c938b 100644 --- a/internal/reader/opml/handler.go +++ b/internal/reader/opml/handler.go @@ -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 } diff --git a/internal/reader/opml/opml.go b/internal/reader/opml/opml.go index 0f5c0f96..4f01b232 100644 --- a/internal/reader/opml/opml.go +++ b/internal/reader/opml/opml.go @@ -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{} } diff --git a/internal/reader/opml/parser.go b/internal/reader/opml/parser.go index 6b972d40..bbe6bc8d 100644 --- a/internal/reader/opml/parser.go +++ b/internal/reader/opml/parser.go @@ -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(), diff --git a/internal/reader/opml/parser_test.go b/internal/reader/opml/parser_test.go index 9210c178..89624444 100644 --- a/internal/reader/opml/parser_test.go +++ b/internal/reader/opml/parser_test.go @@ -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 := ` @@ -32,10 +39,10 @@ func TestParseOpmlWithoutCategories(t *testing.T) { ` - 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) { ` - 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) { ` - 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) { ` - 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) { ` - 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) { ` - 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) { ` - 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") } diff --git a/internal/reader/opml/serializer.go b/internal/reader/opml/serializer.go index 229c5a82..fb506f2e 100644 --- a/internal/reader/opml/serializer.go +++ b/internal/reader/opml/serializer.go @@ -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) diff --git a/internal/reader/opml/serializer_test.go b/internal/reader/opml/serializer_test.go index b0f5a5a6..4b6f693a 100644 --- a/internal/reader/opml/serializer_test.go +++ b/internal/reader/opml/serializer_test.go @@ -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) diff --git a/internal/reader/opml/subscription.go b/internal/reader/opml/subscription.go index f3a5ac34..1920fd4e 100644 --- a/internal/reader/opml/subscription.go +++ b/internal/reader/opml/subscription.go @@ -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 diff --git a/internal/reader/rdf/adapter.go b/internal/reader/rdf/adapter.go index 9b5240c0..f3931da4 100644 --- a/internal/reader/rdf/adapter.go +++ b/internal/reader/rdf/adapter.go @@ -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), diff --git a/internal/reader/rdf/parser.go b/internal/reader/rdf/parser.go index f743c5d7..9c8abb6b 100644 --- a/internal/reader/rdf/parser.go +++ b/internal/reader/rdf/parser.go @@ -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 } diff --git a/internal/reader/rdf/rdf.go b/internal/reader/rdf/rdf.go index 5adaeeb9..b04388e1 100644 --- a/internal/reader/rdf/rdf.go +++ b/internal/reader/rdf/rdf.go @@ -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"` diff --git a/internal/reader/rss/adapter.go b/internal/reader/rss/adapter.go index 8c0e3c4d..2e1f386b 100644 --- a/internal/reader/rss/adapter.go +++ b/internal/reader/rss/adapter.go @@ -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) diff --git a/internal/reader/rss/atom.go b/internal/reader/rss/atom.go index 27dade47..e8e68c84 100644 --- a/internal/reader/rss/atom.go +++ b/internal/reader/rss/atom.go @@ -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"` } diff --git a/internal/reader/rss/feedburner.go b/internal/reader/rss/feedburner.go index e4a342da..f49a8a24 100644 --- a/internal/reader/rss/feedburner.go +++ b/internal/reader/rss/feedburner.go @@ -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"` } diff --git a/internal/reader/rss/parser.go b/internal/reader/rss/parser.go index 92f64f92..751fab0f 100644 --- a/internal/reader/rss/parser.go +++ b/internal/reader/rss/parser.go @@ -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 } diff --git a/internal/reader/rss/podcast.go b/internal/reader/rss/podcast.go index 7fd93f4a..a8bfe0f4 100644 --- a/internal/reader/rss/podcast.go +++ b/internal/reader/rss/podcast.go @@ -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 diff --git a/internal/reader/rss/rss.go b/internal/reader/rss/rss.go index 5c65c8f6..d559e172 100644 --- a/internal/reader/rss/rss.go +++ b/internal/reader/rss/rss.go @@ -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"` // is an optional sub-element of . // It has one optional attribute, domain, a string that identifies a categorization taxonomy. @@ -133,7 +133,7 @@ type RSSItem struct { // is an optional sub-element of . // 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"` // is an optional sub-element of . // 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"` // is the publication date of the item. // Its value is a string in RFC 822 format. @@ -158,30 +158,30 @@ type RSSItem struct { // is an optional sub-element of . // Its value is the name of the RSS channel that the item came from, derived from its . // 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 { diff --git a/internal/reader/sanitizer/sanitizer.go b/internal/reader/sanitizer/sanitizer.go index a5c0b6d1..bc34319f 100644 --- a/internal/reader/sanitizer/sanitizer.go +++ b/internal/reader/sanitizer/sanitizer.go @@ -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, }) diff --git a/internal/reader/sanitizer/sanitizer_test.go b/internal/reader/sanitizer/sanitizer_test.go index 4d80302e..26150a43 100644 --- a/internal/reader/sanitizer/sanitizer_test.go +++ b/internal/reader/sanitizer/sanitizer_test.go @@ -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) diff --git a/internal/reader/sanitizer/srcset.go b/internal/reader/sanitizer/srcset.go index a15d081a..2f17bcfd 100644 --- a/internal/reader/sanitizer/srcset.go +++ b/internal/reader/sanitizer/srcset.go @@ -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`) } diff --git a/internal/reader/subscription/finder.go b/internal/reader/subscription/finder.go index b8e871c8..c6c53cc2 100644 --- a/internal/reader/subscription/finder.go +++ b/internal/reader/subscription/finder.go @@ -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) diff --git a/internal/reader/subscription/finder_test.go b/internal/reader/subscription/finder_test.go index c394a239..21cc6a7c 100644 --- a/internal/reader/subscription/finder_test.go +++ b/internal/reader/subscription/finder_test.go @@ -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) } diff --git a/internal/reader/subscription/subscription.go b/internal/reader/subscription/subscription.go index c366b6d8..dd4a5e25 100644 --- a/internal/reader/subscription/subscription.go +++ b/internal/reader/subscription/subscription.go @@ -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 diff --git a/internal/ui/form/auth.go b/internal/ui/form/auth.go index d1a9055b..2fdc8689 100644 --- a/internal/ui/form/auth.go +++ b/internal/ui/form/auth.go @@ -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")), } diff --git a/internal/ui/form/settings.go b/internal/ui/form/settings.go index 400e40a9..1ee7e7cf 100644 --- a/internal/ui/form/settings.go +++ b/internal/ui/form/settings.go @@ -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"), diff --git a/internal/ui/view/view.go b/internal/ui/view/view.go index 2b0692b9..302a6fdb 100644 --- a/internal/ui/view/view.go +++ b/internal/ui/view/view.go @@ -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)), diff --git a/internal/worker/pool.go b/internal/worker/pool.go index 44c899c4..196182b0 100644 --- a/internal/worker/pool.go +++ b/internal/worker/pool.go @@ -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) } diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 1fa77996..ad7f00dd 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -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), )