When detecting the format, detect its version as well

There is no need to detect the format and then the version when both can be
done at the same time.

Add a benchmark as well, on large and small atom and rss files.
This commit is contained in:
jvoisin 2024-03-12 13:11:56 +01:00 committed by Frédéric Guillot
parent 688b73b7ae
commit 45d486b919
12 changed files with 3608 additions and 160 deletions

View file

@ -27,7 +27,7 @@ func TestParseAtom03(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -87,7 +87,7 @@ func TestParseAtom03WithoutFeedTitle(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -110,7 +110,7 @@ func TestParseAtom03WithoutEntryTitleButWithLink(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -138,7 +138,7 @@ func TestParseAtom03WithoutEntryTitleButWithSummary(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -166,7 +166,7 @@ func TestParseAtom03WithoutEntryTitleButWithXMLContent(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -197,7 +197,7 @@ func TestParseAtom03WithSummaryOnly(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -228,7 +228,7 @@ func TestParseAtom03WithXMLContent(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -259,7 +259,7 @@ func TestParseAtom03WithBase64Content(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data))) feed, err := Parse("http://diveintomark.org/", bytes.NewReader([]byte(data)), "0.3")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -31,7 +31,7 @@ func TestParseAtomSample(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("http://example.org/feed.xml", bytes.NewReader([]byte(data))) feed, err := Parse("http://example.org/feed.xml", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -93,7 +93,7 @@ func TestParseFeedWithoutTitle(t *testing.T) {
<updated>2003-12-13T18:30:02Z</updated> <updated>2003-12-13T18:30:02Z</updated>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -123,7 +123,7 @@ func TestParseEntryWithoutTitleButWithURL(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -154,7 +154,7 @@ func TestParseEntryWithoutTitleButWithSummary(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -187,7 +187,7 @@ func TestParseEntryWithoutTitleButWithXHTMLContent(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -206,7 +206,7 @@ func TestParseFeedURL(t *testing.T) {
<updated>2003-12-13T18:30:02Z</updated> <updated>2003-12-13T18:30:02Z</updated>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -238,7 +238,7 @@ func TestParseFeedWithRelativeURL(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -272,7 +272,7 @@ func TestParseEntryWithRelativeURL(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -298,7 +298,7 @@ func TestParseEntryURLWithTextHTMLType(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -324,7 +324,7 @@ func TestParseEntryURLWithNoRelAndNoType(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -350,7 +350,7 @@ func TestParseEntryURLWithAlternateRel(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.net/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -378,7 +378,7 @@ func TestParseEntryTitleWithWhitespaces(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -412,7 +412,7 @@ func TestParseEntryWithPlainTextTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -459,7 +459,7 @@ func TestParseEntryWithHTMLTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -497,7 +497,7 @@ func TestParseEntryWithXHTMLTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -524,7 +524,7 @@ func TestParseEntryWithEmptyXHTMLTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -551,7 +551,7 @@ func TestParseEntryWithXHTMLTitleWithoutDiv(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -577,7 +577,7 @@ func TestParseEntryWithNumericCharacterReferenceTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -603,7 +603,7 @@ func TestParseEntryWithDoubleEncodedEntitiesTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -629,7 +629,7 @@ func TestParseEntryWithXHTMLSummary(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -671,7 +671,7 @@ func TestParseEntryWithHTMLSummary(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -723,7 +723,7 @@ func TestParseEntryWithTextSummary(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -776,7 +776,7 @@ func TestParseEntryWithTextContent(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -821,7 +821,7 @@ func TestParseEntryWithHTMLContent(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -852,7 +852,7 @@ func TestParseEntryWithXHTMLContent(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -881,7 +881,7 @@ func TestParseEntryWithAuthorName(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -910,7 +910,7 @@ func TestParseEntryWithoutAuthorName(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -941,7 +941,7 @@ func TestParseEntryWithMultipleAuthors(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -969,7 +969,7 @@ func TestParseEntryWithoutAuthor(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1000,7 +1000,7 @@ func TestParseFeedWithMultipleAuthors(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1025,7 +1025,7 @@ func TestParseFeedWithoutAuthor(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1075,7 +1075,7 @@ func TestParseEntryWithEnclosures(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1135,7 +1135,7 @@ func TestParseEntryWithoutEnclosureURL(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1168,7 +1168,7 @@ func TestParseEntryWithPublished(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1194,7 +1194,7 @@ func TestParseEntryWithPublishedAndUpdated(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1206,7 +1206,7 @@ func TestParseEntryWithPublishedAndUpdated(t *testing.T) {
func TestParseInvalidXml(t *testing.T) { func TestParseInvalidXml(t *testing.T) {
data := `garbage` data := `garbage`
_, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) _, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
@ -1221,7 +1221,7 @@ func TestParseTitleWithSingleQuote(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1240,7 +1240,7 @@ func TestParseTitleWithEncodedSingleQuote(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1259,7 +1259,7 @@ func TestParseTitleWithSingleQuoteAndHTMLType(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1278,7 +1278,7 @@ func TestParseWithHTMLEntity(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1297,7 +1297,7 @@ func TestParseWithInvalidCharacterEntity(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1330,7 +1330,7 @@ A website: http://example.org/</media:description>
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1396,7 +1396,7 @@ A website: http://example.org/</media:description>
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1467,7 +1467,7 @@ func TestParseRepliesLinkRelationWithHTMLType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1511,7 +1511,7 @@ func TestParseRepliesLinkRelationWithXHTMLType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1550,7 +1550,7 @@ func TestParseRepliesLinkRelationWithNoType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1590,7 +1590,7 @@ func TestAbsoluteCommentsURL(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1631,7 +1631,7 @@ func TestParseFeedWithCategories(t *testing.T) {
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1661,7 +1661,7 @@ func TestParseFeedWithIconURL(t *testing.T) {
<icon>http://example.org/icon.png</icon> <icon>http://example.org/icon.png</icon>
</feed>` </feed>`
feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -4,7 +4,6 @@
package atom // import "miniflux.app/v2/internal/reader/atom" package atom // import "miniflux.app/v2/internal/reader/atom"
import ( import (
"encoding/xml"
"fmt" "fmt"
"io" "io"
@ -17,14 +16,13 @@ type atomFeed interface {
} }
// Parse returns a normalized feed struct from a Atom feed. // Parse returns a normalized feed struct from a Atom feed.
func Parse(baseURL string, r io.ReadSeeker) (*model.Feed, error) { func Parse(baseURL string, r io.ReadSeeker, version string) (*model.Feed, error) {
var rawFeed atomFeed var rawFeed atomFeed
if getAtomFeedVersion(r) == "0.3" { if version == "0.3" {
rawFeed = new(atom03Feed) rawFeed = new(atom03Feed)
} else { } else {
rawFeed = new(atom10Feed) rawFeed = new(atom10Feed)
} }
r.Seek(0, io.SeekStart)
if err := xml_decoder.NewXMLDecoder(r).Decode(rawFeed); err != nil { if err := xml_decoder.NewXMLDecoder(r).Decode(rawFeed); err != nil {
return nil, fmt.Errorf("atom: unable to parse feed: %w", err) return nil, fmt.Errorf("atom: unable to parse feed: %w", err)
@ -32,25 +30,3 @@ func Parse(baseURL string, r io.ReadSeeker) (*model.Feed, error) {
return rawFeed.Transform(baseURL), nil return rawFeed.Transform(baseURL), nil
} }
func getAtomFeedVersion(data io.ReadSeeker) string {
decoder := xml_decoder.NewXMLDecoder(data)
for {
token, _ := decoder.Token()
if token == nil {
break
}
if element, ok := token.(xml.StartElement); ok {
if element.Name.Local == "feed" {
for _, attr := range element.Attr {
if attr.Name.Local == "version" && attr.Value == "0.3" {
return "0.3"
}
}
return "1.0"
}
}
}
return "1.0"
}

View file

@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package atom // import "miniflux.app/v2/internal/reader/atom"
import (
"bytes"
"testing"
)
func TestDetectAtom10(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Example Feed</title>
<link href="http://example.org/"/>
<updated>2003-12-13T18:30:02Z</updated>
<author>
<name>John Doe</name>
</author>
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
<entry>
<title>Atom-Powered Robots Run Amok</title>
<link href="http://example.org/2003/12/13/atom03"/>
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
version := getAtomFeedVersion(bytes.NewReader([]byte(data)))
if version != "1.0" {
t.Errorf(`Invalid Atom version detected: %s`, version)
}
}
func TestDetectAtom03(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<feed version="0.3" xmlns="http://purl.org/atom/ns#">
<title>dive into mark</title>
<link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
<modified>2003-12-13T18:30:02Z</modified>
<author><name>Mark Pilgrim</name></author>
<entry>
<title>Atom 0.3 snapshot</title>
<link rel="alternate" type="text/html" href="http://diveintomark.org/2003/12/13/atom03"/>
<id>tag:diveintomark.org,2003:3.2397</id>
<issued>2003-12-13T08:29:29-04:00</issued>
<modified>2003-12-13T18:30:02Z</modified>
<summary type="text/plain">This is a test</summary>
<content type="text/html" mode="escaped"><![CDATA[<p>HTML content</p>]]></content>
</entry>
</feed>`
version := getAtomFeedVersion(bytes.NewReader([]byte(data)))
if version != "0.3" {
t.Errorf(`Invalid Atom version detected: %s`, version)
}
}

View file

@ -21,12 +21,12 @@ const (
) )
// DetectFeedFormat tries to guess the feed format from input data. // DetectFeedFormat tries to guess the feed format from input data.
func DetectFeedFormat(r io.ReadSeeker) string { func DetectFeedFormat(r io.ReadSeeker) (string, string) {
data := make([]byte, 512) data := make([]byte, 512)
r.Read(data) r.Read(data)
if bytes.HasPrefix(bytes.TrimSpace(data), []byte("{")) { if bytes.HasPrefix(bytes.TrimSpace(data), []byte("{")) {
return FormatJSON return FormatJSON, ""
} }
r.Seek(0, io.SeekStart) r.Seek(0, io.SeekStart)
@ -41,14 +41,19 @@ func DetectFeedFormat(r io.ReadSeeker) string {
if element, ok := token.(xml.StartElement); ok { if element, ok := token.(xml.StartElement); ok {
switch element.Name.Local { switch element.Name.Local {
case "rss": case "rss":
return FormatRSS return FormatRSS, ""
case "feed": case "feed":
return FormatAtom for _, attr := range element.Attr {
if attr.Name.Local == "version" && attr.Value == "0.3" {
return FormatAtom, "0.3"
}
}
return FormatAtom, "1.0"
case "RDF": case "RDF":
return FormatRDF return FormatRDF, ""
} }
} }
} }
return FormatUnknown return FormatUnknown, ""
} }

View file

@ -10,7 +10,7 @@ import (
func TestDetectRDF(t *testing.T) { func TestDetectRDF(t *testing.T) {
data := `<?xml version="1.0"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://my.netscape.com/rdf/simple/0.9/"></rdf:RDF>` data := `<?xml version="1.0"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://my.netscape.com/rdf/simple/0.9/"></rdf:RDF>`
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatRDF { if format != FormatRDF {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRDF) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRDF)
@ -19,7 +19,7 @@ func TestDetectRDF(t *testing.T) {
func TestDetectRSS(t *testing.T) { func TestDetectRSS(t *testing.T) {
data := `<?xml version="1.0"?><rss version="2.0"><channel></channel></rss>` data := `<?xml version="1.0"?><rss version="2.0"><channel></channel></rss>`
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatRSS { if format != FormatRSS {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRSS) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRSS)
@ -28,7 +28,7 @@ func TestDetectRSS(t *testing.T) {
func TestDetectAtom10(t *testing.T) { func TestDetectAtom10(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>` data := `<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>`
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom { if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@ -37,7 +37,7 @@ func TestDetectAtom10(t *testing.T) {
func TestDetectAtom03(t *testing.T) { func TestDetectAtom03(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?><feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"></feed>` data := `<?xml version="1.0" encoding="utf-8"?><feed version="0.3" xmlns="http://purl.org/atom/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:lang="en"></feed>`
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom { if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@ -46,7 +46,7 @@ func TestDetectAtom03(t *testing.T) {
func TestDetectAtomWithISOCharset(t *testing.T) { func TestDetectAtomWithISOCharset(t *testing.T) {
data := `<?xml version="1.0" encoding="ISO-8859-15"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>` data := `<?xml version="1.0" encoding="ISO-8859-15"?><feed xmlns="http://www.w3.org/2005/Atom"></feed>`
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom { if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@ -60,7 +60,7 @@ func TestDetectJSON(t *testing.T) {
"title" : "Example" "title" : "Example"
} }
` `
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatJSON { if format != FormatJSON {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatJSON) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatJSON)
@ -71,7 +71,7 @@ func TestDetectUnknown(t *testing.T) {
data := ` data := `
<!DOCTYPE html> <html> </html> <!DOCTYPE html> <html> </html>
` `
format := DetectFeedFormat(strings.NewReader(data)) format, _ := DetectFeedFormat(strings.NewReader(data))
if format != FormatUnknown { if format != FormatUnknown {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatUnknown) t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatUnknown)

View file

@ -19,10 +19,11 @@ var ErrFeedFormatNotDetected = errors.New("parser: unable to detect feed format"
// ParseFeed analyzes the input data and returns a normalized feed object. // ParseFeed analyzes the input data and returns a normalized feed object.
func ParseFeed(baseURL string, r io.ReadSeeker) (*model.Feed, error) { func ParseFeed(baseURL string, r io.ReadSeeker) (*model.Feed, error) {
r.Seek(0, io.SeekStart) r.Seek(0, io.SeekStart)
switch DetectFeedFormat(r) { format, version := DetectFeedFormat(r)
switch format {
case FormatAtom: case FormatAtom:
r.Seek(0, io.SeekStart) r.Seek(0, io.SeekStart)
return atom.Parse(baseURL, r) return atom.Parse(baseURL, r, version)
case FormatRSS: case FormatRSS:
r.Seek(0, io.SeekStart) r.Seek(0, io.SeekStart)
return rss.Parse(baseURL, r) return rss.Parse(baseURL, r)

View file

@ -4,10 +4,31 @@
package parser // import "miniflux.app/v2/internal/reader/parser" package parser // import "miniflux.app/v2/internal/reader/parser"
import ( import (
"os"
"strings" "strings"
"testing" "testing"
) )
func BenchmarkParse(b *testing.B) {
var testCases = map[string][]string{
"large_atom.xml": {"https://dustri.org/b", ""},
"large_rss.xml": {"https://dustri.org/b", ""},
"small_atom.xml": {"https://github.com/miniflux/v2/commits/main", ""},
}
for filename := range testCases {
data, err := os.ReadFile("./testdata/" + filename)
if err != nil {
b.Fatalf(`Unable to read file %q: %v`, filename, err)
}
testCases[filename][1] = string(data)
}
for range b.N {
for _, v := range testCases {
ParseFeed(v[0], strings.NewReader(v[1]))
}
}
}
func FuzzParse(f *testing.F) { func FuzzParse(f *testing.F) {
f.Add("https://z.org", `<?xml version="1.0" encoding="utf-8"?> f.Add("https://z.org", `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,396 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
<id>tag:github.com,2008:/miniflux/v2/commits/main</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commits/main"/>
<link type="application/atom+xml" rel="self" href="https://github.com/miniflux/v2/commits/main.atom"/>
<title>Recent Commits to v2:main</title>
<updated>2024-03-12T05:30:27Z</updated>
<entry>
<id>tag:github.com,2008:Grit::Commit/6d97f8b4582414b6ce69467656824690057d4793</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/6d97f8b4582414b6ce69467656824690057d4793"/>
<title>
Parse podcast categories
</title>
<updated>2024-03-12T05:30:27Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Parse podcast categories&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/f8e50947f2885047155a8070dddab133a5c685c2</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/f8e50947f2885047155a8070dddab133a5c685c2"/>
<title>
Move iTunes and GooglePlay XML definitions to their own packages
</title>
<updated>2024-03-12T05:09:31Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Move iTunes and GooglePlay XML definitions to their own packages&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/9a637ce95e05459adc4712027e6a07eaabcfe657</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/9a637ce95e05459adc4712027e6a07eaabcfe657"/>
<title>
Refactor RSS parser to use default namespace
</title>
<updated>2024-03-12T04:07:13Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Refactor RSS parser to use default namespace
This change avoid some limitations of the Go XML parser regarding XML namespaces&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/d3a85b049b14d4a4ddd6b813134b2abd45fe5e8d</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/d3a85b049b14d4a4ddd6b813134b2abd45fe5e8d"/>
<title>
jsminifier: set JavaScript version
</title>
<updated>2024-03-12T02:02:52Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;jsminifier: set JavaScript version&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/5bcb37901c60463b27e1211e0f68295f213b19e6</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/5bcb37901c60463b27e1211e0f68295f213b19e6"/>
<title>
Use crypto.GenerateRandomBytes instead of doing it by hand
</title>
<updated>2024-03-11T23:31:43Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Use crypto.GenerateRandomBytes instead of doing it by hand
This makes the code a bit shorter, and properly handle
cryptographic error conditions.&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/9c8a7dfffe2f4596dcbde2c923a7539914bb252f</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/9c8a7dfffe2f4596dcbde2c923a7539914bb252f"/>
<title>
Make use of HashFromBytes everywhere
</title>
<updated>2024-03-11T22:22:22Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Make use of HashFromBytes everywhere
It feels a bit silly to have a function and to not make use of it.&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/74e4032ffc9faad4fec602f283a32d2af8dec47e</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/74e4032ffc9faad4fec602f283a32d2af8dec47e"/>
<title>
Small refactor of app.js
</title>
<updated>2024-03-11T22:18:57Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Small refactor of app.js
- replace a lot of `let` with `const`
- inline some `querySelectorAll` calls
- reduce the scope of some variables
- use some ternaries where it makes sense
- inline one-line functions&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/fd1fee852cb35fa0f5b0ed6dc0c23b4a6ce368c3</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/fd1fee852cb35fa0f5b0ed6dc0c23b4a6ce368c3"/>
<title>
Simplify DomHelper.getVisibleElements
</title>
<updated>2024-03-11T22:03:00Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Simplify DomHelper.getVisibleElements
Use a `filter` instead of a loop with an index.&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/c51a3270da1f6af796b7d23fa4b434ccf11818e7</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/c51a3270da1f6af796b7d23fa4b434ccf11818e7"/>
<title>
GitHub Actions: Add basic ESLinter checks
</title>
<updated>2024-03-11T03:57:27Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;GitHub Actions: Add basic ESLinter checks&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/45fa641d26a5f68e663aa9af72e97523d8d63c1e</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/45fa641d26a5f68e663aa9af72e97523d8d63c1e"/>
<title>
Fix JavaScript linter path in GitHub Actions
</title>
<updated>2024-03-11T03:37:18Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Fix JavaScript linter path in GitHub Actions&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/fd8f25916b025a92b1b8349ef9d0acdb832a9e8e</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/fd8f25916b025a92b1b8349ef9d0acdb832a9e8e"/>
<title>
First steps towards trusted-types support
</title>
<updated>2024-03-11T03:14:30Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;First steps towards trusted-types support
Refactor away some trival usages of `.innerHTML`. Unfortunately, there is no way to
enabled trusted-types in report-only mode via `&amp;lt;meta&amp;gt;` tags, see
https://github.com/w3c/webappsec-csp/issues/277&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/826e4d654f511ea8d1d385bdc09cbed69ff6a70f</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/826e4d654f511ea8d1d385bdc09cbed69ff6a70f"/>
<title>
Replace DomHelper.findParent with .closest
</title>
<updated>2024-03-11T03:06:54Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Replace DomHelper.findParent with .closest
See https://developer.mozilla.org/en-US/docs/Web/API/Element/closest&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/d9d17f0d69d1dafb3bd9d81bf9fc27df3def4f4c</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/d9d17f0d69d1dafb3bd9d81bf9fc27df3def4f4c"/>
<title>
Use a `Set` instead of an array in a KeyboardHandler&#39;s member
</title>
<updated>2024-03-11T02:41:13Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Use a `Set` instead of an array in a KeyboardHandler&amp;#39;s member
The variable `triggers` is only used to check if in contains a particular
value. Given that the number of keyboard shortcuts is starting to be
significant, let&amp;#39;s future-proof the performances and use a `Set` instead of an
`Array` instead.&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/eaaeb68474ff194f682e9521a848d7ab2c89348e</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/eaaeb68474ff194f682e9521a848d7ab2c89348e"/>
<title>
Fix conditions to publish packages in GitHub workflows
</title>
<updated>2024-03-10T19:25:13Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Fix conditions to publish packages in GitHub workflows&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/382885f14403526adfa6c303927889c76fd5a1eb</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/382885f14403526adfa6c303927889c76fd5a1eb"/>
<title>
Update changeLog
</title>
<updated>2024-03-10T17:50:47Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/323546?s=30&amp;v=4"/>
<author>
<name>fguillot</name>
<uri>https://github.com/fguillot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Update changeLog&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/0f7b047b0a81253b6d146e05d561545303016b74</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/0f7b047b0a81253b6d146e05d561545303016b74"/>
<title>
Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3
</title>
<updated>2024-03-08T04:59:42Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/in/29110?s=30&amp;v=4"/>
<author>
<name>dependabot</name>
<uri>https://github.com/dependabot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3
Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.3.
- [Release notes](https://github.com/go-jose/go-jose/releases)
- [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md)
- [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.3)
---
updated-dependencies:
- dependency-name: github.com/go-jose/go-jose/v3
dependency-type: indirect
...
Signed-off-by: dependabot[bot] &amp;lt;support@github.com&amp;gt;&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/a074773e6c5d3b2066094cbac0502094aa364713</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/a074773e6c5d3b2066094cbac0502094aa364713"/>
<title>
Use an io.ReadSeeker instead of an io.Reader to parse feeds
</title>
<updated>2024-03-07T04:13:39Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Use an io.ReadSeeker instead of an io.Reader to parse feeds
This will allow to make use of func (*Reader) Seek, instead of re-recreating a
new reader. It&amp;#39;s a large commit for a small change, but anything to simply the
reader/buffer/ReadAll/… mess is a step in the right direction I think, and it
should enable more follow-up simplifications.&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/3d0126be0b8a603401b7593250f80b0a8042b995</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/3d0126be0b8a603401b7593250f80b0a8042b995"/>
<title>
Speed the sanitizer up a bit, again
</title>
<updated>2024-03-06T03:31:50Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Speed the sanitizer up a bit, again
- allow youtube urls to start with `www`
- use `strings.Builder` instead of a `bytes.Buffer`
- use a `strings.NewReader` instead of a `bytes.NewBufferString`
- sprinkles a couple of `continue` to make the code-flow more obvious
- inline calls to `inList`, and put their parameters in the right order
- simplify isPixelTracker
- simplify `isValidIframeSource`, by extracting the hostname and comparing it
directly, instead of using the full url and checking if it starts with
multiple variations of the same one (`//`, `http:`, `https://` multiplied by
``/`www.`)
- add a benchmark&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/eda2e2f3f5c278e44e2def72caedc33667a0fb6c</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/eda2e2f3f5c278e44e2def72caedc33667a0fb6c"/>
<title>
Bump golang.org/x/oauth2 from 0.17.0 to 0.18.0
</title>
<updated>2024-03-05T23:39:07Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/in/29110?s=30&amp;v=4"/>
<author>
<name>dependabot</name>
<uri>https://github.com/dependabot</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Bump golang.org/x/oauth2 from 0.17.0 to 0.18.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.17.0 to 0.18.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.17.0...v0.18.0)
---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] &amp;lt;support@github.com&amp;gt;&lt;/pre&gt;
</content>
</entry>
<entry>
<id>tag:github.com,2008:Grit::Commit/111e3f2106646cd29f7f74c0102f2a570c598e2e</id>
<link type="text/html" rel="alternate" href="https://github.com/miniflux/v2/commit/111e3f2106646cd29f7f74c0102f2a570c598e2e"/>
<title>
Reuse a Reader instead of copying to a buffer when parsing an atom feed
</title>
<updated>2024-03-05T01:36:10Z</updated>
<media:thumbnail height="30" width="30" url="https://avatars.githubusercontent.com/u/325724?s=30&amp;v=4"/>
<author>
<name>jvoisin</name>
<uri>https://github.com/jvoisin</uri>
</author>
<content type="html">
&lt;pre style=&#39;white-space:pre-wrap;width:81ex&#39;&gt;Reuse a Reader instead of copying to a buffer when parsing an atom feed&lt;/pre&gt;
</content>
</entry>
</feed>

View file

@ -69,7 +69,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string)
} }
// Step 1) Check if the website URL is a feed. // Step 1) Check if the website URL is a feed.
if feedFormat := parser.DetectFeedFormat(f.feedResponseInfo.Content); feedFormat != parser.FormatUnknown { if feedFormat, _ := parser.DetectFeedFormat(f.feedResponseInfo.Content); feedFormat != parser.FormatUnknown {
f.feedDownloaded = true f.feedDownloaded = true
return Subscriptions{NewSubscription(responseHandler.EffectiveURL(), responseHandler.EffectiveURL(), feedFormat)}, nil return Subscriptions{NewSubscription(responseHandler.EffectiveURL(), responseHandler.EffectiveURL(), feedFormat)}, nil
} }