Handle invalid feeds with relative URLs

This commit is contained in:
Frédéric Guillot 2020-12-02 20:47:11 -08:00 committed by fguillot
parent 49feb1958c
commit f722fd1208
18 changed files with 392 additions and 157 deletions

View file

@ -27,12 +27,24 @@ type atom03Feed struct {
Entries []atom03Entry `xml:"entry"` Entries []atom03Entry `xml:"entry"`
} }
func (a *atom03Feed) Transform() *model.Feed { func (a *atom03Feed) Transform(baseURL string) *model.Feed {
feed := new(model.Feed) var err error
feed.FeedURL = a.Links.firstLinkWithRelation("self")
feed.SiteURL = a.Links.originalLink()
feed.Title = a.Title.String()
feed := new(model.Feed)
feedURL := a.Links.firstLinkWithRelation("self")
feed.FeedURL, err = url.AbsoluteURL(baseURL, feedURL)
if err != nil {
feed.FeedURL = feedURL
}
siteURL := a.Links.originalLink()
feed.SiteURL, err = url.AbsoluteURL(baseURL, siteURL)
if err != nil {
feed.SiteURL = siteURL
}
feed.Title = a.Title.String()
if feed.Title == "" { if feed.Title == "" {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }

View file

@ -28,7 +28,7 @@ func TestParseAtom03(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -37,7 +37,7 @@ func TestParseAtom03(t *testing.T) {
t.Errorf("Incorrect title, got: %s", feed.Title) t.Errorf("Incorrect title, got: %s", feed.Title)
} }
if feed.FeedURL != "" { if feed.FeedURL != "http://diveintomark.org/" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
} }
@ -88,7 +88,7 @@ func TestParseAtom03WithoutFeedTitle(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -111,7 +111,7 @@ func TestParseAtom03WithoutEntryTitle(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -142,7 +142,7 @@ func TestParseAtom03WithSummaryOnly(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -173,7 +173,7 @@ func TestParseAtom03WithXMLContent(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -204,7 +204,7 @@ func TestParseAtom03WithBase64Content(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://diveintomark.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -31,12 +31,24 @@ type atom10Feed struct {
Entries []atom10Entry `xml:"entry"` Entries []atom10Entry `xml:"entry"`
} }
func (a *atom10Feed) Transform() *model.Feed { func (a *atom10Feed) Transform(baseURL string) *model.Feed {
feed := new(model.Feed) var err error
feed.FeedURL = a.Links.firstLinkWithRelation("self")
feed.SiteURL = a.Links.originalLink()
feed.Title = a.Title.String()
feed := new(model.Feed)
feedURL := a.Links.firstLinkWithRelation("self")
feed.FeedURL, err = url.AbsoluteURL(baseURL, feedURL)
if err != nil {
feed.FeedURL = feedURL
}
siteURL := a.Links.originalLink()
feed.SiteURL, err = url.AbsoluteURL(baseURL, siteURL)
if err != nil {
feed.SiteURL = siteURL
}
feed.Title = a.Title.String()
if feed.Title == "" { if feed.Title == "" {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }

View file

@ -32,7 +32,7 @@ func TestParseAtomSample(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org/feed.xml", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -41,7 +41,7 @@ func TestParseAtomSample(t *testing.T) {
t.Errorf("Incorrect title, got: %s", feed.Title) t.Errorf("Incorrect title, got: %s", feed.Title)
} }
if feed.FeedURL != "" { if feed.FeedURL != "http://example.org/feed.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
} }
@ -90,7 +90,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(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -121,7 +121,7 @@ func TestParseEntryWithoutTitle(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -140,7 +140,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(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -154,6 +154,42 @@ func TestParseFeedURL(t *testing.T) {
} }
} }
func TestParseFeedWithRelativeURL(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="/blog/atom.xml" rel="self" type="application/atom+xml"/>
<link href="/blog"/>
<entry>
<title>Test</title>
<link href="/blog/article.html"/>
<link href="/blog/article.html" rel="alternate" type="text/html"/>
<id>/blog/article.html</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil {
t.Fatal(err)
}
if feed.FeedURL != "https://example.org/blog/atom.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
}
if feed.SiteURL != "https://example.org/blog" {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
}
if feed.Entries[0].URL != "https://example.org/blog/article.html" {
t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
}
}
func TestParseEntryWithRelativeURL(t *testing.T) { func TestParseEntryWithRelativeURL(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?> data := `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"> <feed xmlns="http://www.w3.org/2005/Atom">
@ -170,7 +206,7 @@ func TestParseEntryWithRelativeURL(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.net/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -198,7 +234,7 @@ func TestParseEntryTitleWithWhitespaces(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -224,7 +260,7 @@ func TestParseEntryTitleWithHTMLAndCDATA(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -250,7 +286,7 @@ func TestParseEntryTitleWithHTML(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -276,7 +312,7 @@ func TestParseEntryTitleWithXHTML(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -302,7 +338,7 @@ func TestParseEntrySummaryWithXHTML(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -328,7 +364,7 @@ func TestParseEntrySummaryWithHTML(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -354,7 +390,7 @@ func TestParseEntrySummaryWithPlainText(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -383,7 +419,7 @@ func TestParseEntryWithAuthorName(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -412,7 +448,7 @@ func TestParseEntryWithoutAuthorName(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -462,7 +498,7 @@ func TestParseEntryWithEnclosures(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -522,7 +558,7 @@ func TestParseEntryWithoutEnclosureURL(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -555,7 +591,7 @@ func TestParseEntryWithPublished(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -581,7 +617,7 @@ func TestParseEntryWithPublishedAndUpdated(t *testing.T) {
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -593,7 +629,7 @@ func TestParseEntryWithPublishedAndUpdated(t *testing.T) {
func TestParseInvalidXml(t *testing.T) { func TestParseInvalidXml(t *testing.T) {
data := `garbage` data := `garbage`
_, err := Parse(bytes.NewBufferString(data)) _, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
@ -608,7 +644,7 @@ func TestParseTitleWithSingleQuote(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -627,7 +663,7 @@ func TestParseTitleWithEncodedSingleQuote(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -646,7 +682,7 @@ func TestParseTitleWithSingleQuoteAndHTMLType(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -665,7 +701,7 @@ func TestParseWithHTMLEntity(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -684,7 +720,7 @@ func TestParseWithInvalidCharacterEntity(t *testing.T) {
</feed> </feed>
` `
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -717,7 +753,7 @@ A website: http://example.org/</media:description>
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -783,7 +819,7 @@ A website: http://example.org/</media:description>
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -854,7 +890,7 @@ func TestParseRepliesLinkRelationWithHTMLType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -898,7 +934,7 @@ func TestParseRepliesLinkRelationWithXHTMLType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -937,7 +973,7 @@ func TestParseRepliesLinkRelationWithNoType(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -977,7 +1013,7 @@ func TestAbsoluteCommentsURL(t *testing.T) {
</entry> </entry>
</feed>` </feed>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -15,11 +15,11 @@ import (
) )
type atomFeed interface { type atomFeed interface {
Transform() *model.Feed Transform(baseURL string) *model.Feed
} }
// Parse returns a normalized feed struct from a Atom feed. // Parse returns a normalized feed struct from a Atom feed.
func Parse(r io.Reader) (*model.Feed, *errors.LocalizedError) { func Parse(baseURL string, r io.Reader) (*model.Feed, *errors.LocalizedError) {
var buf bytes.Buffer var buf bytes.Buffer
tee := io.TeeReader(r, &buf) tee := io.TeeReader(r, &buf)
@ -36,7 +36,7 @@ func Parse(r io.Reader) (*model.Feed, *errors.LocalizedError) {
return nil, errors.NewLocalizedError("Unable to parse Atom feed: %q", err) return nil, errors.NewLocalizedError("Unable to parse Atom feed: %q", err)
} }
return rawFeed.Transform(), nil return rawFeed.Transform(baseURL), nil
} }
func getAtomFeedVersion(data io.Reader) string { func getAtomFeedVersion(data io.Reader) string {

View file

@ -58,7 +58,7 @@ func (h *Handler) CreateFeed(userID, categoryID int64, url string, crawler bool,
return nil, errors.NewLocalizedError(errDuplicate, response.EffectiveURL) return nil, errors.NewLocalizedError(errDuplicate, response.EffectiveURL)
} }
subscription, parseErr := parser.ParseFeed(response.BodyAsString()) subscription, parseErr := parser.ParseFeed(response.EffectiveURL, response.BodyAsString())
if parseErr != nil { if parseErr != nil {
return nil, parseErr return nil, parseErr
} }
@ -137,7 +137,7 @@ func (h *Handler) RefreshFeed(userID, feedID int64) error {
if originalFeed.IgnoreHTTPCache || response.IsModified(originalFeed.EtagHeader, originalFeed.LastModifiedHeader) { if originalFeed.IgnoreHTTPCache || response.IsModified(originalFeed.EtagHeader, originalFeed.LastModifiedHeader) {
logger.Debug("[Handler:RefreshFeed] Feed #%d has been modified", feedID) logger.Debug("[Handler:RefreshFeed] Feed #%d has been modified", feedID)
updatedFeed, parseErr := parser.ParseFeed(response.BodyAsString()) updatedFeed, parseErr := parser.ParseFeed(response.EffectiveURL, response.BodyAsString())
if parseErr != nil { if parseErr != nil {
originalFeed.WithError(parseErr.Localize(printer)) originalFeed.WithError(parseErr.Localize(printer))
h.store.UpdateFeedError(originalFeed) h.store.UpdateFeedError(originalFeed)

View file

@ -55,12 +55,22 @@ func (j *jsonFeed) GetAuthor() string {
return getAuthor(j.Author) return getAuthor(j.Author)
} }
func (j *jsonFeed) Transform() *model.Feed { func (j *jsonFeed) Transform(baseURL string) *model.Feed {
feed := new(model.Feed) var err error
feed.FeedURL = j.FeedURL
feed.SiteURL = j.SiteURL
feed.Title = strings.TrimSpace(j.Title)
feed := new(model.Feed)
feed.FeedURL, err = url.AbsoluteURL(baseURL, j.FeedURL)
if err != nil {
feed.FeedURL = j.FeedURL
}
feed.SiteURL, err = url.AbsoluteURL(baseURL, j.SiteURL)
if err != nil {
feed.SiteURL = j.SiteURL
}
feed.Title = strings.TrimSpace(j.Title)
if feed.Title == "" { if feed.Title == "" {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }

View file

@ -13,12 +13,12 @@ import (
) )
// Parse returns a normalized feed struct from a JON feed. // Parse returns a normalized feed struct from a JON feed.
func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { func Parse(baseURL string, data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(jsonFeed) feed := new(jsonFeed)
decoder := json.NewDecoder(data) decoder := json.NewDecoder(data)
if err := decoder.Decode(&feed); err != nil { if err := decoder.Decode(&feed); err != nil {
return nil, errors.NewLocalizedError("Unable to parse JSON Feed: %q", err) return nil, errors.NewLocalizedError("Unable to parse JSON Feed: %q", err)
} }
return feed.Transform(), nil return feed.Transform(baseURL), nil
} }

View file

@ -31,7 +31,7 @@ func TestParseJsonFeed(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -113,7 +113,7 @@ func TestParsePodcast(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://therecord.co/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -197,7 +197,7 @@ func TestParseEntryWithoutAttachmentURL(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://therecord.co/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -226,7 +226,7 @@ func TestParseFeedWithRelativeURL(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -258,7 +258,7 @@ func TestParseAuthor(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -287,7 +287,7 @@ func TestParseFeedWithoutTitle(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -313,7 +313,7 @@ func TestParseFeedItemWithInvalidDate(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -341,7 +341,7 @@ func TestParseFeedItemWithoutID(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -368,7 +368,7 @@ func TestParseFeedItemWithoutTitle(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -395,7 +395,7 @@ func TestParseTruncateItemTitle(t *testing.T) {
] ]
}` }`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -411,7 +411,7 @@ func TestParseTruncateItemTitle(t *testing.T) {
func TestParseInvalidJSON(t *testing.T) { func TestParseInvalidJSON(t *testing.T) {
data := `garbage` data := `garbage`
_, err := Parse(bytes.NewBufferString(data)) _, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }

View file

@ -16,16 +16,16 @@ import (
) )
// ParseFeed analyzes the input data and returns a normalized feed object. // ParseFeed analyzes the input data and returns a normalized feed object.
func ParseFeed(data string) (*model.Feed, *errors.LocalizedError) { func ParseFeed(baseURL, data string) (*model.Feed, *errors.LocalizedError) {
switch DetectFeedFormat(data) { switch DetectFeedFormat(data) {
case FormatAtom: case FormatAtom:
return atom.Parse(strings.NewReader(data)) return atom.Parse(baseURL, strings.NewReader(data))
case FormatRSS: case FormatRSS:
return rss.Parse(strings.NewReader(data)) return rss.Parse(baseURL, strings.NewReader(data))
case FormatJSON: case FormatJSON:
return json.Parse(strings.NewReader(data)) return json.Parse(baseURL, strings.NewReader(data))
case FormatRDF: case FormatRDF:
return rdf.Parse(strings.NewReader(data)) return rdf.Parse(baseURL, strings.NewReader(data))
default: default:
return nil, errors.NewLocalizedError("Unsupported feed format") return nil, errors.NewLocalizedError("Unsupported feed format")
} }

View file

@ -34,7 +34,7 @@ func TestParseAtom(t *testing.T) {
</feed>` </feed>`
feed, err := ParseFeed(data) feed, err := ParseFeed("https://example.org/", data)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -44,6 +44,42 @@ func TestParseAtom(t *testing.T) {
} }
} }
func TestParseAtomFeedWithRelativeURL(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="/blog/atom.xml" rel="self" type="application/atom+xml"/>
<link href="/blog"/>
<entry>
<title>Test</title>
<link href="/blog/article.html"/>
<link href="/blog/article.html" rel="alternate" type="text/html"/>
<id>/blog/article.html</id>
<updated>2003-12-13T18:30:02Z</updated>
<summary>Some text.</summary>
</entry>
</feed>`
feed, err := ParseFeed("https://example.org/blog/atom.xml", data)
if err != nil {
t.Fatal(err)
}
if feed.FeedURL != "https://example.org/blog/atom.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
}
if feed.SiteURL != "https://example.org/blog" {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
}
if feed.Entries[0].URL != "https://example.org/blog/article.html" {
t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
}
}
func TestParseRSS(t *testing.T) { func TestParseRSS(t *testing.T) {
data := `<?xml version="1.0"?> data := `<?xml version="1.0"?>
<rss version="2.0"> <rss version="2.0">
@ -60,7 +96,7 @@ func TestParseRSS(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := ParseFeed(data) feed, err := ParseFeed("http://liftoff.msfc.nasa.gov/", data)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -70,6 +106,44 @@ func TestParseRSS(t *testing.T) {
} }
} }
func TestParseRSSFeedWithRelativeURL(t *testing.T) {
data := `<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Example Feed</title>
<link>/blog</link>
<item>
<title>Example Entry</title>
<link>/blog/article.html</link>
<description>Something</description>
<pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>
<guid>1234</guid>
</item>
</channel>
</rss>`
feed, err := ParseFeed("http://example.org/rss.xml", data)
if err != nil {
t.Error(err)
}
if feed.Title != "Example Feed" {
t.Errorf("Incorrect title, got: %s", feed.Title)
}
if feed.FeedURL != "http://example.org/rss.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
}
if feed.SiteURL != "http://example.org/blog" {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
}
if feed.Entries[0].URL != "http://example.org/blog/article.html" {
t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
}
}
func TestParseRDF(t *testing.T) { func TestParseRDF(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?> data := `<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF <rdf:RDF
@ -89,7 +163,7 @@ func TestParseRDF(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := ParseFeed(data) feed, err := ParseFeed("http://example.org/", data)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -99,6 +173,43 @@ func TestParseRDF(t *testing.T) {
} }
} }
func TestParseRDFWithRelativeURL(t *testing.T) {
data := `<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
>
<channel>
<title>RDF Example</title>
<link>/blog</link>
</channel>
<item>
<title>Title</title>
<link>/blog/article.html</link>
<description>Test</description>
</item>
</rdf:RDF>`
feed, err := ParseFeed("http://example.org/rdf.xml", data)
if err != nil {
t.Error(err)
}
if feed.FeedURL != "http://example.org/rdf.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
}
if feed.SiteURL != "http://example.org/blog" {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
}
if feed.Entries[0].URL != "http://example.org/blog/article.html" {
t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
}
}
func TestParseJson(t *testing.T) { func TestParseJson(t *testing.T) {
data := `{ data := `{
"version": "https://jsonfeed.org/version/1", "version": "https://jsonfeed.org/version/1",
@ -119,7 +230,7 @@ func TestParseJson(t *testing.T) {
] ]
}` }`
feed, err := ParseFeed(data) feed, err := ParseFeed("https://example.org/feed.json", data)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -129,6 +240,43 @@ func TestParseJson(t *testing.T) {
} }
} }
func TestParseJsonFeedWithRelativeURL(t *testing.T) {
data := `{
"version": "https://jsonfeed.org/version/1",
"title": "My Example Feed",
"home_page_url": "/blog",
"feed_url": "/blog/feed.json",
"items": [
{
"id": "2",
"content_text": "This is a second item.",
"url": "/blog/article.html"
}
]
}`
feed, err := ParseFeed("https://example.org/blog/feed.json", data)
if err != nil {
t.Error(err)
}
if feed.Title != "My Example Feed" {
t.Errorf("Incorrect title, got: %s", feed.Title)
}
if feed.FeedURL != "https://example.org/blog/feed.json" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
}
if feed.SiteURL != "https://example.org/blog" {
t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
}
if feed.Entries[0].URL != "https://example.org/blog/article.html" {
t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
}
}
func TestParseUnknownFeed(t *testing.T) { func TestParseUnknownFeed(t *testing.T) {
data := ` data := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
@ -142,14 +290,14 @@ func TestParseUnknownFeed(t *testing.T) {
</html> </html>
` `
_, err := ParseFeed(data) _, err := ParseFeed("https://example.org/", data)
if err == nil { if err == nil {
t.Error("ParseFeed must returns an error") t.Error("ParseFeed must returns an error")
} }
} }
func TestParseEmptyFeed(t *testing.T) { func TestParseEmptyFeed(t *testing.T) {
_, err := ParseFeed("") _, err := ParseFeed("", "")
if err == nil { if err == nil {
t.Error("ParseFeed must returns an error") t.Error("ParseFeed must returns an error")
} }
@ -191,7 +339,7 @@ func TestDifferentEncodingWithResponse(t *testing.T) {
t.Fatalf(`Encoding error for %q: %v`, tc.filename, encodingErr) t.Fatalf(`Encoding error for %q: %v`, tc.filename, encodingErr)
} }
feed, parseErr := ParseFeed(r.BodyAsString()) feed, parseErr := ParseFeed("https://example.org/", r.BodyAsString())
if parseErr != nil { if parseErr != nil {
t.Fatalf(`Parsing error for %q - %q: %v`, tc.filename, tc.contentType, parseErr) t.Fatalf(`Parsing error for %q - %q: %v`, tc.filename, tc.contentType, parseErr)
} }

View file

@ -13,7 +13,7 @@ import (
) )
// Parse returns a normalized feed struct from a RDF feed. // Parse returns a normalized feed struct from a RDF feed.
func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { func Parse(baseURL string, data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(rdfFeed) feed := new(rdfFeed)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
err := decoder.Decode(feed) err := decoder.Decode(feed)
@ -21,5 +21,5 @@ func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
return nil, errors.NewLocalizedError("Unable to parse RDF feed: %q", err) return nil, errors.NewLocalizedError("Unable to parse RDF feed: %q", err)
} }
return feed.Transform(), nil return feed.Transform(baseURL), nil
} }

View file

@ -76,7 +76,7 @@ func TestParseRDFSample(t *testing.T) {
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://xml.com/pub/rdf.xml", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -85,7 +85,7 @@ func TestParseRDFSample(t *testing.T) {
t.Errorf("Incorrect title, got: %s", feed.Title) t.Errorf("Incorrect title, got: %s", feed.Title)
} }
if feed.FeedURL != "" { if feed.FeedURL != "http://xml.com/pub/rdf.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
} }
@ -187,7 +187,7 @@ func TestParseRDFSampleWithDublinCore(t *testing.T) {
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://meerkat.oreillynet.com/feed.rdf", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -196,7 +196,7 @@ func TestParseRDFSampleWithDublinCore(t *testing.T) {
t.Errorf("Incorrect title, got: %s", feed.Title) t.Errorf("Incorrect title, got: %s", feed.Title)
} }
if feed.FeedURL != "" { if feed.FeedURL != "http://meerkat.oreillynet.com/feed.rdf" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
} }
@ -254,7 +254,7 @@ func TestParseItemWithOnlyFeedAuthor(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://meerkat.oreillynet.com", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -279,7 +279,7 @@ func TestParseItemRelativeURL(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://meerkat.oreillynet.com", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -308,7 +308,7 @@ func TestParseItemWithoutLink(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://meerkat.oreillynet.com", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -339,7 +339,7 @@ func TestParseItemWithDublicCoreDate(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -365,7 +365,7 @@ func TestParseItemWithoutDate(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -379,7 +379,7 @@ func TestParseItemWithoutDate(t *testing.T) {
func TestParseInvalidXml(t *testing.T) { func TestParseInvalidXml(t *testing.T) {
data := `garbage` data := `garbage`
_, err := Parse(bytes.NewBufferString(data)) _, err := Parse("http://example.org", bytes.NewBufferString(data))
if err == nil { if err == nil {
t.Fatal("Parse should returns an error") t.Fatal("Parse should returns an error")
} }
@ -394,7 +394,7 @@ func TestParseFeedWithHTMLEntity(t *testing.T) {
</channel> </channel>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -413,7 +413,7 @@ func TestParseFeedWithInvalidCharacterEntity(t *testing.T) {
</channel> </channel>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -469,7 +469,7 @@ func TestParseFeedWithURLWrappedInSpaces(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://biorxiv.org", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -504,7 +504,7 @@ func TestParseRDFWithContentEncoded(t *testing.T) {
</item> </item>
</rdf:RDF>` </rdf:RDF>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -25,10 +25,15 @@ type rdfFeed struct {
DublinCoreFeedElement DublinCoreFeedElement
} }
func (r *rdfFeed) Transform() *model.Feed { func (r *rdfFeed) Transform(baseURL string) *model.Feed {
var err error
feed := new(model.Feed) feed := new(model.Feed)
feed.Title = sanitizer.StripTags(r.Title) feed.Title = sanitizer.StripTags(r.Title)
feed.SiteURL = r.Link feed.FeedURL = baseURL
feed.SiteURL, err = url.AbsoluteURL(baseURL, r.Link)
if err != nil {
feed.SiteURL = r.Link
}
for _, item := range r.Items { for _, item := range r.Items {
entry := item.Transform() entry := item.Transform()

View file

@ -13,7 +13,7 @@ import (
) )
// Parse returns a normalized feed struct from a RSS feed. // Parse returns a normalized feed struct from a RSS feed.
func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) { func Parse(baseURL string, data io.Reader) (*model.Feed, *errors.LocalizedError) {
feed := new(rssFeed) feed := new(rssFeed)
decoder := xml.NewDecoder(data) decoder := xml.NewDecoder(data)
err := decoder.Decode(feed) err := decoder.Decode(feed)
@ -21,5 +21,5 @@ func Parse(data io.Reader) (*model.Feed, *errors.LocalizedError) {
return nil, errors.NewLocalizedError("Unable to parse RSS feed: %q", err) return nil, errors.NewLocalizedError("Unable to parse RSS feed: %q", err)
} }
return feed.Transform(), nil return feed.Transform(baseURL), nil
} }

View file

@ -54,7 +54,7 @@ func TestParseRss2Sample(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("http://liftoff.msfc.nasa.gov/rss.xml", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -63,7 +63,7 @@ func TestParseRss2Sample(t *testing.T) {
t.Errorf("Incorrect title, got: %s", feed.Title) t.Errorf("Incorrect title, got: %s", feed.Title)
} }
if feed.FeedURL != "" { if feed.FeedURL != "http://liftoff.msfc.nasa.gov/rss.xml" {
t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL) t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
} }
@ -105,7 +105,7 @@ func TestParseFeedWithoutTitle(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -126,7 +126,7 @@ func TestParseEntryWithoutTitle(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -149,7 +149,7 @@ func TestParseEntryWithMediaTitle(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -171,7 +171,7 @@ func TestParseEntryWithDCTitleOnly(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -192,7 +192,7 @@ func TestParseEntryWithoutLink(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -218,7 +218,7 @@ func TestParseEntryWithAtomLink(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -241,7 +241,7 @@ func TestParseEntryWithMultipleAtomLinks(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -261,7 +261,7 @@ func TestParseFeedURLWithAtomLink(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -289,7 +289,7 @@ func TestParseFeedWithWebmaster(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -316,7 +316,7 @@ func TestParseFeedWithManagingEditor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -343,7 +343,7 @@ func TestParseEntryWithAuthorAndInnerHTML(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -375,7 +375,7 @@ func TestParseEntryWithNonStandardAtomAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -404,7 +404,7 @@ func TestParseEntryWithAtomAuthorEmail(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -433,7 +433,7 @@ func TestParseEntryWithAtomAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -459,7 +459,7 @@ func TestParseEntryWithDublinCoreAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -485,7 +485,7 @@ func TestParseEntryWithItunesAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -511,7 +511,7 @@ func TestParseFeedWithItunesAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -540,7 +540,7 @@ func TestParseFeedWithItunesOwner(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -568,7 +568,7 @@ func TestParseFeedWithItunesOwnerEmail(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -594,7 +594,7 @@ func TestParseEntryWithGooglePlayAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -620,7 +620,7 @@ func TestParseFeedWithGooglePlayAuthor(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -648,7 +648,7 @@ func TestParseEntryWithDublinCoreDate(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -676,7 +676,7 @@ func TestParseEntryWithContentEncoded(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -700,7 +700,7 @@ func TestParseEntryWithFeedBurnerLink(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -726,7 +726,7 @@ func TestParseEntryTitleWithWhitespaces(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -756,7 +756,7 @@ func TestParseEntryWithEnclosures(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -804,7 +804,7 @@ func TestParseEntryWithEmptyEnclosureURL(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -841,7 +841,7 @@ func TestParseEntryWithFeedBurnerEnclosures(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -882,7 +882,7 @@ func TestParseEntryWithRelativeURL(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -908,7 +908,7 @@ func TestParseEntryWithCommentsURL(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -933,7 +933,7 @@ func TestParseEntryWithInvalidCommentsURL(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -945,7 +945,7 @@ func TestParseEntryWithInvalidCommentsURL(t *testing.T) {
func TestParseInvalidXml(t *testing.T) { func TestParseInvalidXml(t *testing.T) {
data := `garbage` data := `garbage`
_, err := Parse(bytes.NewBufferString(data)) _, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err == nil { if err == nil {
t.Error("Parse should returns an error") t.Error("Parse should returns an error")
} }
@ -960,7 +960,7 @@ func TestParseWithHTMLEntity(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -979,7 +979,7 @@ func TestParseWithInvalidCharacterEntity(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1013,7 +1013,7 @@ func TestParseEntryWithMediaGroup(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1071,7 +1071,7 @@ func TestParseEntryWithMediaContent(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1122,7 +1122,7 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1174,7 +1174,7 @@ func TestEntryDescriptionFromItunesSummary(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1205,7 +1205,7 @@ func TestEntryDescriptionFromItunesSubtitle(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -1239,7 +1239,7 @@ func TestEntryDescriptionFromGooglePlayDescription(t *testing.T) {
</channel> </channel>
</rss>` </rss>`
feed, err := Parse(bytes.NewBufferString(data)) feed, err := Parse("https://example.org/", bytes.NewBufferString(data))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -35,12 +35,24 @@ type rssFeed struct {
PodcastFeedElement PodcastFeedElement
} }
func (r *rssFeed) Transform() *model.Feed { func (r *rssFeed) Transform(baseURL string) *model.Feed {
feed := new(model.Feed) var err error
feed.SiteURL = r.siteURL()
feed.FeedURL = r.feedURL()
feed.Title = strings.TrimSpace(r.Title)
feed := new(model.Feed)
siteURL := r.siteURL()
feed.SiteURL, err = url.AbsoluteURL(baseURL, siteURL)
if err != nil {
feed.SiteURL = siteURL
}
feedURL := r.feedURL()
feed.FeedURL, err = url.AbsoluteURL(baseURL, feedURL)
if err != nil {
feed.FeedURL = feedURL
}
feed.Title = strings.TrimSpace(r.Title)
if feed.Title == "" { if feed.Title == "" {
feed.Title = feed.SiteURL feed.Title = feed.SiteURL
} }

View file

@ -23,13 +23,13 @@ func TestIsAbsoluteURL(t *testing.T) {
func TestAbsoluteURL(t *testing.T) { func TestAbsoluteURL(t *testing.T) {
scenarios := [][]string{ scenarios := [][]string{
[]string{"https://example.org/path/file.ext", "https://example.org/folder/", "/path/file.ext"}, {"https://example.org/path/file.ext", "https://example.org/folder/", "/path/file.ext"},
[]string{"https://example.org/folder/path/file.ext", "https://example.org/folder/", "path/file.ext"}, {"https://example.org/folder/path/file.ext", "https://example.org/folder/", "path/file.ext"},
[]string{"https://example.org/path/file.ext", "https://example.org/folder", "path/file.ext"}, {"https://example.org/path/file.ext", "https://example.org/folder", "path/file.ext"},
[]string{"https://example.org/path/file.ext", "https://example.org/folder/", "https://example.org/path/file.ext"}, {"https://example.org/path/file.ext", "https://example.org/folder/", "https://example.org/path/file.ext"},
[]string{"https://static.example.org/path/file.ext", "https://www.example.org/", "//static.example.org/path/file.ext"}, {"https://static.example.org/path/file.ext", "https://www.example.org/", "//static.example.org/path/file.ext"},
[]string{"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a", "https://www.example.org/", "magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"}, {"magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a", "https://www.example.org/", "magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"},
[]string{"magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7", "https://www.example.org/", "magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7"}, {"magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7", "https://www.example.org/", "magnet:?xt.1=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C&xt.2=urn:sha1:TXGCZQTH26NL6OUQAJJPFALHG2LTGBC7"},
} }
for _, scenario := range scenarios { for _, scenario := range scenarios {