From 08640b27d55f6382af6f08d0789b8109279ae825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Tue, 19 Mar 2024 21:24:30 -0700 Subject: [PATCH] Ensure enclosure URLs are always absolute --- internal/reader/atom/atom_10_adapter.go | 109 ++++++++++----- internal/reader/atom/atom_10_test.go | 154 ++++++++++++++++----- internal/reader/atom/atom_common.go | 15 +++ internal/reader/json/parser_test.go | 36 +++++ internal/reader/rss/adapter.go | 91 +++++++++---- internal/reader/rss/parser_test.go | 172 +++++++++++++++++++----- 6 files changed, 448 insertions(+), 129 deletions(-) diff --git a/internal/reader/atom/atom_10_adapter.go b/internal/reader/atom/atom_10_adapter.go index c9ce5c23..a0a73623 100644 --- a/internal/reader/atom/atom_10_adapter.go +++ b/internal/reader/atom/atom_10_adapter.go @@ -158,51 +158,92 @@ func (a *Atom10Adapter) populateEntries(siteURL string) model.Entries { uniqueEnclosuresMap := make(map[string]bool) for _, mediaThumbnail := range atomEntry.AllMediaThumbnails() { - if _, found := uniqueEnclosuresMap[mediaThumbnail.URL]; !found { - uniqueEnclosuresMap[mediaThumbnail.URL] = true - entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ - URL: mediaThumbnail.URL, - MimeType: mediaThumbnail.MimeType(), - Size: mediaThumbnail.Size(), - }) + mediaURL := strings.TrimSpace(mediaThumbnail.URL) + if mediaURL == "" { + continue + } + if _, found := uniqueEnclosuresMap[mediaURL]; !found { + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media thumbnail", + slog.String("url", mediaThumbnail.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + uniqueEnclosuresMap[mediaAbsoluteURL] = true + entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaThumbnail.MimeType(), + Size: mediaThumbnail.Size(), + }) + } } } - for _, link := range atomEntry.Links { - if !strings.EqualFold(link.Rel, "enclosure") || link.Href == "" { - continue - } - - if _, found := uniqueEnclosuresMap[link.Href]; !found { - uniqueEnclosuresMap[link.Href] = true - length, _ := strconv.ParseInt(link.Length, 10, 0) - entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ - URL: link.Href, - MimeType: link.Type, - Size: length, - }) + for _, link := range atomEntry.Links.findAllLinksWithRelation("enclosure") { + absoluteEnclosureURL, err := urllib.AbsoluteURL(siteURL, link.Href) + if err != nil { + slog.Debug("Unable to resolve absolute URL for enclosure", + slog.String("enclosure_url", link.Href), + slog.String("entry_url", entry.URL), + slog.Any("error", err), + ) + } else { + if _, found := uniqueEnclosuresMap[absoluteEnclosureURL]; !found { + uniqueEnclosuresMap[absoluteEnclosureURL] = true + length, _ := strconv.ParseInt(link.Length, 10, 0) + entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ + URL: absoluteEnclosureURL, + MimeType: link.Type, + Size: length, + }) + } } } for _, mediaContent := range atomEntry.AllMediaContents() { - if _, found := uniqueEnclosuresMap[mediaContent.URL]; !found { - uniqueEnclosuresMap[mediaContent.URL] = true - entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ - URL: mediaContent.URL, - MimeType: mediaContent.MimeType(), - Size: mediaContent.Size(), - }) + mediaURL := strings.TrimSpace(mediaContent.URL) + if mediaURL == "" { + continue + } + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media content", + slog.String("url", mediaContent.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; !found { + uniqueEnclosuresMap[mediaAbsoluteURL] = true + entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaContent.MimeType(), + Size: mediaContent.Size(), + }) + } } } for _, mediaPeerLink := range atomEntry.AllMediaPeerLinks() { - if _, found := uniqueEnclosuresMap[mediaPeerLink.URL]; !found { - uniqueEnclosuresMap[mediaPeerLink.URL] = true - entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ - URL: mediaPeerLink.URL, - MimeType: mediaPeerLink.MimeType(), - Size: mediaPeerLink.Size(), - }) + mediaURL := strings.TrimSpace(mediaPeerLink.URL) + if mediaURL == "" { + continue + } + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media peer link", + slog.String("url", mediaPeerLink.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + if _, found := uniqueEnclosuresMap[mediaAbsoluteURL]; !found { + uniqueEnclosuresMap[mediaAbsoluteURL] = true + entry.Enclosures = append(entry.Enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaPeerLink.MimeType(), + Size: mediaPeerLink.Size(), + }) + } } } diff --git a/internal/reader/atom/atom_10_test.go b/internal/reader/atom/atom_10_test.go index 7b69f29d..cdc5b364 100644 --- a/internal/reader/atom/atom_10_test.go +++ b/internal/reader/atom/atom_10_test.go @@ -1105,7 +1105,7 @@ func TestParseEntryWithEnclosures(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if feed.Entries[0].URL != "http://www.example.org/entries/1" { @@ -1140,6 +1140,89 @@ func TestParseEntryWithEnclosures(t *testing.T) { } } +func TestParseEntryWithRelativeEnclosureURL(t *testing.T) { + data := ` + + https://www.example.org/myfeed + My Podcast Feed + + + + https://www.example.org/entries/1 + Atom 1.0 + 2005-07-15T12:00:00Z + + + + + ` + + feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10") + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "https://example.org/myaudiofile.mp3" { + t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL) + } +} + +func TestParseEntryWithDuplicateEnclosureURL(t *testing.T) { + data := ` + + http://www.example.org/myfeed + My Podcast Feed + + + + http://www.example.org/entries/1 + Atom 1.0 + 2005-07-15T12:00:00Z + + + + + + ` + + feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data)), "10") + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { + t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL) + } +} + func TestParseEntryWithoutEnclosureURL(t *testing.T) { data := ` @@ -1334,20 +1417,25 @@ func TestParseWithInvalidCharacterEntity(t *testing.T) { func TestParseMediaGroup(t *testing.T) { data := ` - http://www.example.org/myfeed + https://www.example.org/myfeed My Video Feed 2005-07-15T12:00:00Z - - + + - http://www.example.org/entries/1 + https://www.example.org/entries/1 Some Video 2005-07-15T12:00:00Z - + Another title - + + + + + + Some description A website: http://example.org/ @@ -1360,18 +1448,10 @@ A website: http://example.org/ } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) - } - - if feed.Entries[0].Content != `Some description
A website: http://example.org/` { - t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) - } - - if len(feed.Entries[0].Enclosures) != 2 { + if len(feed.Entries[0].Enclosures) != 4 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } @@ -1380,8 +1460,10 @@ A website: http://example.org/ mimeType string size int64 }{ - {"https://example.org/thumbnail.jpg", "image/*", 0}, + {"https://www.example.org/duplicate-thumbnail.jpg", "image/*", 0}, + {"https://example.org/thumbnail2.jpg", "image/*", 0}, {"https://www.youtube.com/v/abcd", "application/x-shockwave-flash", 0}, + {"https://example.org/v/efg", "application/x-shockwave-flash", 0}, } for index, enclosure := range feed.Entries[0].Enclosures { @@ -1402,19 +1484,26 @@ A website: http://example.org/ func TestParseMediaElements(t *testing.T) { data := ` - http://www.example.org/myfeed + https://www.example.org/myfeed My Video Feed 2005-07-15T12:00:00Z - - + + - http://www.example.org/entries/1 + https://www.example.org/entries/1 Some Video 2005-07-15T12:00:00Z - + Another title - + + + + + + + + Some description A website: http://example.org/ @@ -1426,18 +1515,10 @@ A website: http://example.org/ } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) - } - - if feed.Entries[0].Content != `Some description
A website: http://example.org/` { - t.Errorf("Incorrect entry content, got: %q", feed.Entries[0].Content) - } - - if len(feed.Entries[0].Enclosures) != 2 { + if len(feed.Entries[0].Enclosures) != 5 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } @@ -1446,8 +1527,11 @@ A website: http://example.org/ mimeType string size int64 }{ - {"https://example.org/thumbnail.jpg", "image/*", 0}, + {"https://example.org/duplicated-thumbnail.jpg", "image/*", 0}, {"https://www.youtube.com/v/abcd", "application/x-shockwave-flash", 0}, + {"https://example.org/relative/media.mp4", "application/x-shockwave-flash", 0}, + {"http://www.example.org/sampleFile.torrent", "application/x-bittorrent", 0}, + {"https://example.org/sampleFile2.torrent", "application/x-bittorrent", 0}, } for index, enclosure := range feed.Entries[0].Enclosures { diff --git a/internal/reader/atom/atom_common.go b/internal/reader/atom/atom_common.go index debd46f1..945c5573 100644 --- a/internal/reader/atom/atom_common.go +++ b/internal/reader/atom/atom_common.go @@ -96,6 +96,21 @@ func (a AtomLinks) firstLinkWithRelationAndType(relation string, contentTypes .. return "" } +func (a AtomLinks) findAllLinksWithRelation(relation string) []*AtomLink { + var links []*AtomLink + + for _, link := range a { + if strings.EqualFold(link.Rel, relation) { + link.Href = strings.TrimSpace(link.Href) + if link.Href != "" { + links = append(links, link) + } + } + } + + return links +} + // The "atom:category" element conveys information about a category // associated with an entry or feed. This specification assigns no // meaning to the content (if any) of this element. diff --git a/internal/reader/json/parser_test.go b/internal/reader/json/parser_test.go index 0bbd39ec..6f62831f 100644 --- a/internal/reader/json/parser_test.go +++ b/internal/reader/json/parser_test.go @@ -848,6 +848,42 @@ func TestParseFeedIcon(t *testing.T) { } } +func TestParseFeedWithRelativeAttachmentURL(t *testing.T) { + data := `{ + "version": "https://jsonfeed.org/version/1", + "title": "My Example Feed", + "home_page_url": "https://example.org/", + "feed_url": "https://example.org/feed.json", + "items": [ + { + "id": "2", + "content_text": "This is a second item.", + "url": "https://example.org/second-item", + "attachments": [ + { + "url": " /attachment.mp3 ", + "mime_type": "audio/mpeg", + "size_in_bytes": 123456 + } + ] + } + ] + }` + + feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data)) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "https://example.org/attachment.mp3" { + t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL) + } +} + func TestParseInvalidJSON(t *testing.T) { data := `garbage` _, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data)) diff --git a/internal/reader/rss/adapter.go b/internal/reader/rss/adapter.go index 48def825..07d56059 100644 --- a/internal/reader/rss/adapter.go +++ b/internal/reader/rss/adapter.go @@ -72,7 +72,7 @@ func (r *RSSAdapter) BuildFeed(baseURL string) *model.Feed { entry := model.NewEntry() entry.Date = findEntryDate(&item) entry.Content = findEntryContent(&item) - entry.Enclosures = findEntryEnclosures(&item) + entry.Enclosures = findEntryEnclosures(&item, feed.SiteURL) // Populate the entry URL. entryURL := findEntryURL(&item) @@ -245,18 +245,30 @@ func findEntryAuthor(rssItem *RSSItem) string { return strings.TrimSpace(sanitizer.StripTags(author)) } -func findEntryEnclosures(rssItem *RSSItem) model.EnclosureList { +func findEntryEnclosures(rssItem *RSSItem, siteURL string) model.EnclosureList { enclosures := make(model.EnclosureList, 0) duplicates := make(map[string]bool) for _, mediaThumbnail := range rssItem.AllMediaThumbnails() { - if _, found := duplicates[mediaThumbnail.URL]; !found { - duplicates[mediaThumbnail.URL] = true - enclosures = append(enclosures, &model.Enclosure{ - URL: mediaThumbnail.URL, - MimeType: mediaThumbnail.MimeType(), - Size: mediaThumbnail.Size(), - }) + mediaURL := strings.TrimSpace(mediaThumbnail.URL) + if mediaURL == "" { + continue + } + if _, found := duplicates[mediaURL]; !found { + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media thumbnail", + slog.String("url", mediaThumbnail.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + duplicates[mediaAbsoluteURL] = true + enclosures = append(enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaThumbnail.MimeType(), + Size: mediaThumbnail.Size(), + }) + } } } @@ -265,15 +277,20 @@ func findEntryEnclosures(rssItem *RSSItem) model.EnclosureList { if rssItem.FeedBurnerEnclosureLink != "" { filename := path.Base(rssItem.FeedBurnerEnclosureLink) - if strings.Contains(enclosureURL, filename) { + if strings.HasSuffix(enclosureURL, filename) { enclosureURL = rssItem.FeedBurnerEnclosureLink } } + enclosureURL = strings.TrimSpace(enclosureURL) if enclosureURL == "" { continue } + if absoluteEnclosureURL, err := urllib.AbsoluteURL(siteURL, enclosureURL); err == nil { + enclosureURL = absoluteEnclosureURL + } + if _, found := duplicates[enclosureURL]; !found { duplicates[enclosureURL] = true @@ -286,24 +303,50 @@ func findEntryEnclosures(rssItem *RSSItem) model.EnclosureList { } for _, mediaContent := range rssItem.AllMediaContents() { - if _, found := duplicates[mediaContent.URL]; !found { - duplicates[mediaContent.URL] = true - enclosures = append(enclosures, &model.Enclosure{ - URL: mediaContent.URL, - MimeType: mediaContent.MimeType(), - Size: mediaContent.Size(), - }) + mediaURL := strings.TrimSpace(mediaContent.URL) + if mediaURL == "" { + continue + } + if _, found := duplicates[mediaURL]; !found { + mediaURL := strings.TrimSpace(mediaContent.URL) + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media content", + slog.String("url", mediaContent.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + duplicates[mediaAbsoluteURL] = true + enclosures = append(enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaContent.MimeType(), + Size: mediaContent.Size(), + }) + } } } for _, mediaPeerLink := range rssItem.AllMediaPeerLinks() { - if _, found := duplicates[mediaPeerLink.URL]; !found { - duplicates[mediaPeerLink.URL] = true - enclosures = append(enclosures, &model.Enclosure{ - URL: mediaPeerLink.URL, - MimeType: mediaPeerLink.MimeType(), - Size: mediaPeerLink.Size(), - }) + mediaURL := strings.TrimSpace(mediaPeerLink.URL) + if mediaURL == "" { + continue + } + if _, found := duplicates[mediaURL]; !found { + mediaURL := strings.TrimSpace(mediaPeerLink.URL) + if mediaAbsoluteURL, err := urllib.AbsoluteURL(siteURL, mediaURL); err != nil { + slog.Debug("Unable to build absolute URL for media peer link", + slog.String("url", mediaPeerLink.URL), + slog.String("site_url", siteURL), + slog.Any("error", err), + ) + } else { + duplicates[mediaAbsoluteURL] = true + enclosures = append(enclosures, &model.Enclosure{ + URL: mediaAbsoluteURL, + MimeType: mediaPeerLink.MimeType(), + Size: mediaPeerLink.Size(), + }) + } } } diff --git a/internal/reader/rss/parser_test.go b/internal/reader/rss/parser_test.go index e3f8450f..8d84e582 100644 --- a/internal/reader/rss/parser_test.go +++ b/internal/reader/rss/parser_test.go @@ -1016,15 +1016,11 @@ func TestParseEntryWithEnclosures(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) - } - - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { - t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { @@ -1065,15 +1061,11 @@ func TestParseEntryWithIncorrectEnclosureLength(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) - } - - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 2 { - t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { @@ -1093,6 +1085,39 @@ func TestParseEntryWithIncorrectEnclosureLength(t *testing.T) { } } +func TestParseEntryWithDuplicatedEnclosureURL(t *testing.T) { + data := ` + + + My Podcast Feed + http://example.org + + Podcasting with RSS + http://www.example.org/entries/1 + + + + + ` + + feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "http://www.example.org/myaudiofile.mp3" { + t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) + } +} + func TestParseEntryWithEmptyEnclosureURL(t *testing.T) { data := ` @@ -1106,7 +1131,7 @@ func TestParseEntryWithEmptyEnclosureURL(t *testing.T) { An overview of RSS podcasting Fri, 15 Jul 2005 00:00:00 -0500 http://www.example.org/entries/1 - + ` @@ -1117,15 +1142,47 @@ func TestParseEntryWithEmptyEnclosureURL(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) - } - - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 0 { - t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } +} + +func TestParseEntryWithRelativeEnclosureURL(t *testing.T) { + data := ` + + + My Podcast Feed + http://example.org + some.email@example.org + + Podcasting with RSS + http://www.example.org/entries/1 + An overview of RSS podcasting + Fri, 15 Jul 2005 00:00:00 -0500 + http://www.example.org/entries/1 + + + + ` + + feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "http://example.org/files/file.mp3" { + t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL) } } @@ -1154,15 +1211,11 @@ func TestParseEntryWithFeedBurnerEnclosures(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) - } - - if feed.Entries[0].URL != "http://www.example.org/entries/1" { - t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } if len(feed.Entries[0].Enclosures) != 1 { - t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } if feed.Entries[0].Enclosures[0].URL != "http://example.org/67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3" { @@ -1178,6 +1231,42 @@ func TestParseEntryWithFeedBurnerEnclosures(t *testing.T) { } } +func TestParseEntryWithFeedBurnerEnclosuresAndRelativeURL(t *testing.T) { + data := ` + + + My Example Feed + http://example.org + + Example Item + http://www.example.org/entries/1 + + /67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3 + + + ` + + feed, err := Parse("https://example.org/", bytes.NewReader([]byte(data))) + if err != nil { + t.Fatal(err) + } + + if len(feed.Entries) != 1 { + t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + } + + if len(feed.Entries[0].Enclosures) != 1 { + t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) + } + + if feed.Entries[0].Enclosures[0].URL != "http://example.org/67ca416c-f22a-4228-a681-68fc9998ec10/File.mp3" { + t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL) + } +} + func TestParseEntryWithRelativeURL(t *testing.T) { data := ` @@ -1389,7 +1478,7 @@ func TestParseEntryWithMediaGroup(t *testing.T) { My Example Feed - http://example.org + https://example.org Example Item http://www.example.org/entries/1 @@ -1400,7 +1489,9 @@ func TestParseEntryWithMediaGroup(t *testing.T) { - + + + nonadult @@ -1453,15 +1544,19 @@ func TestParseEntryWithMediaContent(t *testing.T) { My Example Feed - http://example.org + https://example.org Example Item http://www.example.org/entries/1 + + + Some Title for Media 1 - + + ` @@ -1472,9 +1567,9 @@ func TestParseEntryWithMediaContent(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } - if len(feed.Entries[0].Enclosures) != 3 { + if len(feed.Entries[0].Enclosures) != 4 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } @@ -1483,6 +1578,7 @@ func TestParseEntryWithMediaContent(t *testing.T) { mimeType string size int64 }{ + {"https://example.org/thumbnail.jpg", "image/*", 0}, {"https://example.org/thumbnail.jpg", "image/*", 0}, {"https://example.org/media1.jpg", "image/*", 0}, {"https://example.org/media2.jpg", "image/*", 0}, @@ -1508,11 +1604,14 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) { My Example Feed - http://example.org + https://website.example.org Example Item http://www.example.org/entries/1 - + + + + ` @@ -1523,10 +1622,10 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) { } if len(feed.Entries) != 1 { - t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries)) + t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries)) } - if len(feed.Entries[0].Enclosures) != 1 { + if len(feed.Entries[0].Enclosures) != 2 { t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures)) } @@ -1535,7 +1634,8 @@ func TestParseEntryWithMediaPeerLink(t *testing.T) { mimeType string size int64 }{ - {"http://www.example.org/file.torrent", "application/x-bittorrent", 0}, + {"https://www.example.org/file.torrent", "application/x-bittorrent", 0}, + {"https://website.example.org/file2.torrent", "application/x-bittorrent", 0}, } for index, enclosure := range feed.Entries[0].Enclosures {