diff --git a/internal/api/subscription.go b/internal/api/subscription.go
index 499d99a0..9aca0313 100644
--- a/internal/api/subscription.go
+++ b/internal/api/subscription.go
@@ -7,9 +7,11 @@ import (
json_parser "encoding/json"
"net/http"
+ "miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/http/request"
"miniflux.app/v2/internal/http/response/json"
"miniflux.app/v2/internal/model"
+ "miniflux.app/v2/internal/reader/fetcher"
"miniflux.app/v2/internal/reader/subscription"
"miniflux.app/v2/internal/validator"
)
@@ -32,14 +34,17 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
rssbridgeURL = intg.RSSBridgeURL
}
- subscriptions, localizedError := subscription.FindSubscriptions(
+ requestBuilder := fetcher.NewRequestBuilder()
+ requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
+ requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+ requestBuilder.WithUserAgent(subscriptionDiscoveryRequest.UserAgent)
+ requestBuilder.WithCookie(subscriptionDiscoveryRequest.Cookie)
+ requestBuilder.WithUsernameAndPassword(subscriptionDiscoveryRequest.Username, subscriptionDiscoveryRequest.Password)
+ requestBuilder.UseProxy(subscriptionDiscoveryRequest.FetchViaProxy)
+ requestBuilder.IgnoreTLSErrors(subscriptionDiscoveryRequest.AllowSelfSignedCertificates)
+
+ subscriptions, localizedError := subscription.NewSubscriptionFinder(requestBuilder).FindSubscriptions(
subscriptionDiscoveryRequest.URL,
- subscriptionDiscoveryRequest.UserAgent,
- subscriptionDiscoveryRequest.Cookie,
- subscriptionDiscoveryRequest.Username,
- subscriptionDiscoveryRequest.Password,
- subscriptionDiscoveryRequest.FetchViaProxy,
- subscriptionDiscoveryRequest.AllowSelfSignedCertificates,
rssbridgeURL,
)
diff --git a/internal/googlereader/handler.go b/internal/googlereader/handler.go
index 242cf55c..e072c621 100644
--- a/internal/googlereader/handler.go
+++ b/internal/googlereader/handler.go
@@ -20,6 +20,7 @@ import (
"miniflux.app/v2/internal/integration"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/proxy"
+ "miniflux.app/v2/internal/reader/fetcher"
mff "miniflux.app/v2/internal/reader/handler"
mfs "miniflux.app/v2/internal/reader/subscription"
"miniflux.app/v2/internal/storage"
@@ -667,13 +668,22 @@ func (h *handler) quickAddHandler(w http.ResponseWriter, r *http.Request) {
return
}
- url := r.Form.Get(ParamQuickAdd)
- if !validator.IsValidURL(url) {
- json.BadRequest(w, r, fmt.Errorf("googlereader: invalid URL: %s", url))
+ feedURL := r.Form.Get(ParamQuickAdd)
+ if !validator.IsValidURL(feedURL) {
+ json.BadRequest(w, r, fmt.Errorf("googlereader: invalid URL: %s", feedURL))
return
}
- subscriptions, localizedError := mfs.FindSubscriptions(url, "", "", "", "", false, false, "")
+ requestBuilder := fetcher.NewRequestBuilder()
+ requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
+ requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+
+ var rssBridgeURL string
+ if intg, err := h.store.Integration(userID); err == nil && intg != nil && intg.RSSBridgeEnabled {
+ rssBridgeURL = intg.RSSBridgeURL
+ }
+
+ subscriptions, localizedError := mfs.NewSubscriptionFinder(requestBuilder).FindSubscriptions(feedURL, rssBridgeURL)
if localizedError != nil {
json.ServerError(w, r, localizedError.Error())
return
diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json
index c8a22594..c4d568c0 100644
--- a/internal/locale/translations/de_DE.json
+++ b/internal/locale/translations/de_DE.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json
index 1da5910c..1bb09399 100644
--- a/internal/locale/translations/el_EL.json
+++ b/internal/locale/translations/el_EL.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json
index fbabb427..3244adb6 100644
--- a/internal/locale/translations/en_US.json
+++ b/internal/locale/translations/en_US.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json
index 89df8d69..e8bc6c2f 100644
--- a/internal/locale/translations/es_ES.json
+++ b/internal/locale/translations/es_ES.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json
index 17ef7657..df3d5632 100644
--- a/internal/locale/translations/fi_FI.json
+++ b/internal/locale/translations/fi_FI.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json
index aeaf3c6a..dfeaa05c 100644
--- a/internal/locale/translations/fr_FR.json
+++ b/internal/locale/translations/fr_FR.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "Ce flux existe déjà.",
"error.unable_to_parse_feed": "Impossible d'analyser ce flux : %v.",
"error.feed_not_found": "Impossible de trouver ce flux.",
- "error.unable_to_detect_rssbridge": "Impossible de détecter un flux RSS en utilisant RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Impossible de détecter un flux RSS en utilisant RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Impossible de détecter le format du flux : %v."
}
diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json
index a0efc399..714976cf 100644
--- a/internal/locale/translations/hi_IN.json
+++ b/internal/locale/translations/hi_IN.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json
index b4f7e0f7..25241468 100644
--- a/internal/locale/translations/id_ID.json
+++ b/internal/locale/translations/id_ID.json
@@ -451,5 +451,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json
index f93af980..57fed234 100644
--- a/internal/locale/translations/it_IT.json
+++ b/internal/locale/translations/it_IT.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json
index 757f9f51..34463017 100644
--- a/internal/locale/translations/ja_JP.json
+++ b/internal/locale/translations/ja_JP.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json
index 3efadfe6..0b1ed97b 100644
--- a/internal/locale/translations/nl_NL.json
+++ b/internal/locale/translations/nl_NL.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json
index 62744703..163b23c3 100644
--- a/internal/locale/translations/pl_PL.json
+++ b/internal/locale/translations/pl_PL.json
@@ -468,5 +468,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json
index 0651fe71..5157509b 100644
--- a/internal/locale/translations/pt_BR.json
+++ b/internal/locale/translations/pt_BR.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json
index 8f2a9d71..6575fc48 100644
--- a/internal/locale/translations/ru_RU.json
+++ b/internal/locale/translations/ru_RU.json
@@ -468,5 +468,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json
index b732fa5b..0a11b61b 100644
--- a/internal/locale/translations/tr_TR.json
+++ b/internal/locale/translations/tr_TR.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json
index 39226cd7..21b20bf5 100644
--- a/internal/locale/translations/uk_UA.json
+++ b/internal/locale/translations/uk_UA.json
@@ -469,5 +469,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json
index 4b265615..7b6f5663 100644
--- a/internal/locale/translations/zh_CN.json
+++ b/internal/locale/translations/zh_CN.json
@@ -452,5 +452,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json
index 3b556773..d8d78531 100644
--- a/internal/locale/translations/zh_TW.json
+++ b/internal/locale/translations/zh_TW.json
@@ -460,5 +460,6 @@
"error.duplicated_feed": "This feed already exists.",
"error.unable_to_parse_feed": "Unable to parse this feed: %v.",
"error.feed_not_found": "This feed does not exist or does not belong to this user.",
- "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v."
+ "error.unable_to_detect_rssbridge": "Unable to detect feed using RSS-Bridge: %v.",
+ "error.feed_format_not_detected": "Unable to detect feed format: %v."
}
diff --git a/internal/model/feed.go b/internal/model/feed.go
index 88560a27..6a76411e 100644
--- a/internal/model/feed.go
+++ b/internal/model/feed.go
@@ -5,6 +5,7 @@ package model // import "miniflux.app/v2/internal/model"
import (
"fmt"
+ "io"
"math"
"time"
@@ -144,6 +145,31 @@ type FeedCreationRequest struct {
UrlRewriteRules string `json:"urlrewrite_rules"`
}
+type FeedCreationRequestFromSubscriptionDiscovery struct {
+ Content io.ReadSeeker
+ ETag string
+ LastModified string
+
+ FeedURL string `json:"feed_url"`
+ CategoryID int64 `json:"category_id"`
+ UserAgent string `json:"user_agent"`
+ Cookie string `json:"cookie"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+ Crawler bool `json:"crawler"`
+ Disabled bool `json:"disabled"`
+ NoMediaPlayer bool `json:"no_media_player"`
+ IgnoreHTTPCache bool `json:"ignore_http_cache"`
+ AllowSelfSignedCertificates bool `json:"allow_self_signed_certificates"`
+ FetchViaProxy bool `json:"fetch_via_proxy"`
+ ScraperRules string `json:"scraper_rules"`
+ RewriteRules string `json:"rewrite_rules"`
+ BlocklistRules string `json:"blocklist_rules"`
+ KeeplistRules string `json:"keeplist_rules"`
+ HideGlobally bool `json:"hide_globally"`
+ UrlRewriteRules string `json:"urlrewrite_rules"`
+}
+
// FeedModificationRequest represents the request to update a feed.
type FeedModificationRequest struct {
FeedURL *string `json:"feed_url"`
diff --git a/internal/reader/fetcher/response_handler.go b/internal/reader/fetcher/response_handler.go
index 1283b405..2c00c933 100644
--- a/internal/reader/fetcher/response_handler.go
+++ b/internal/reader/fetcher/response_handler.go
@@ -95,14 +95,14 @@ func (r *ResponseHandler) ReadBody(maxBodySize int64) ([]byte, *locale.Localized
func (r *ResponseHandler) LocalizedError() *locale.LocalizedErrorWrapper {
if r.clientErr != nil {
switch r.clientErr.(type) {
- case x509.CertificateInvalidError, x509.UnknownAuthorityError, x509.HostnameError:
- return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.tls_error", r.clientErr.Error())
+ case x509.CertificateInvalidError, x509.HostnameError:
+ return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.tls_error", r.clientErr)
case *net.OpError:
- return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.network_operation", r.clientErr.Error())
+ return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.network_operation", r.clientErr)
case net.Error:
networkErr := r.clientErr.(net.Error)
if networkErr.Timeout() {
- return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.network_timeout", r.clientErr.Error())
+ return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.network_timeout", r.clientErr)
}
}
@@ -110,7 +110,7 @@ func (r *ResponseHandler) LocalizedError() *locale.LocalizedErrorWrapper {
return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.http_empty_response")
}
- return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.http_client_error", r.clientErr.Error())
+ return locale.NewLocalizedErrorWrapper(fmt.Errorf("fetcher: %w", r.clientErr), "error.http_client_error", r.clientErr)
}
switch r.httpResponse.StatusCode {
diff --git a/internal/reader/handler/handler.go b/internal/reader/handler/handler.go
index fa38e4cf..49ccdee4 100644
--- a/internal/reader/handler/handler.go
+++ b/internal/reader/handler/handler.go
@@ -4,6 +4,7 @@
package handler // import "miniflux.app/v2/internal/reader/handler"
import (
+ "bytes"
"errors"
"log/slog"
"time"
@@ -25,6 +26,83 @@ var (
ErrDuplicatedFeed = errors.New("fetcher: duplicated feed")
)
+func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, feedCreationRequest *model.FeedCreationRequestFromSubscriptionDiscovery) (*model.Feed, *locale.LocalizedErrorWrapper) {
+ slog.Debug("Begin feed creation process from subscription discovery",
+ slog.Int64("user_id", userID),
+ slog.String("feed_url", feedCreationRequest.FeedURL),
+ )
+
+ user, storeErr := store.UserByID(userID)
+ if storeErr != nil {
+ return nil, locale.NewLocalizedErrorWrapper(storeErr, "error.database_error", storeErr)
+ }
+
+ if !store.CategoryIDExists(userID, feedCreationRequest.CategoryID) {
+ return nil, locale.NewLocalizedErrorWrapper(ErrCategoryNotFound, "error.category_not_found")
+ }
+
+ if store.FeedURLExists(userID, feedCreationRequest.FeedURL) {
+ return nil, locale.NewLocalizedErrorWrapper(ErrDuplicatedFeed, "error.duplicated_feed")
+ }
+
+ subscription, parseErr := parser.ParseFeed(feedCreationRequest.FeedURL, feedCreationRequest.Content)
+ if parseErr != nil {
+ return nil, locale.NewLocalizedErrorWrapper(parseErr, "error.unable_to_parse_feed", parseErr)
+ }
+
+ subscription.UserID = userID
+ subscription.UserAgent = feedCreationRequest.UserAgent
+ subscription.Cookie = feedCreationRequest.Cookie
+ subscription.Username = feedCreationRequest.Username
+ subscription.Password = feedCreationRequest.Password
+ subscription.Crawler = feedCreationRequest.Crawler
+ subscription.Disabled = feedCreationRequest.Disabled
+ subscription.IgnoreHTTPCache = feedCreationRequest.IgnoreHTTPCache
+ subscription.AllowSelfSignedCertificates = feedCreationRequest.AllowSelfSignedCertificates
+ subscription.FetchViaProxy = feedCreationRequest.FetchViaProxy
+ subscription.ScraperRules = feedCreationRequest.ScraperRules
+ subscription.RewriteRules = feedCreationRequest.RewriteRules
+ subscription.BlocklistRules = feedCreationRequest.BlocklistRules
+ subscription.KeeplistRules = feedCreationRequest.KeeplistRules
+ subscription.UrlRewriteRules = feedCreationRequest.UrlRewriteRules
+ subscription.EtagHeader = feedCreationRequest.ETag
+ subscription.LastModifiedHeader = feedCreationRequest.LastModified
+ subscription.FeedURL = feedCreationRequest.FeedURL
+ subscription.WithCategoryID(feedCreationRequest.CategoryID)
+ subscription.CheckedNow()
+
+ processor.ProcessFeedEntries(store, subscription, user, true)
+
+ if storeErr := store.CreateFeed(subscription); storeErr != nil {
+ return nil, locale.NewLocalizedErrorWrapper(storeErr, "error.database_error", storeErr)
+ }
+
+ slog.Debug("Created feed",
+ slog.Int64("user_id", userID),
+ slog.Int64("feed_id", subscription.ID),
+ slog.String("feed_url", subscription.FeedURL),
+ )
+
+ requestBuilder := fetcher.NewRequestBuilder()
+ requestBuilder.WithUsernameAndPassword(feedCreationRequest.Username, feedCreationRequest.Password)
+ requestBuilder.WithUserAgent(feedCreationRequest.UserAgent)
+ requestBuilder.WithCookie(feedCreationRequest.Cookie)
+ requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
+ requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
+ requestBuilder.UseProxy(feedCreationRequest.FetchViaProxy)
+ requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
+
+ checkFeedIcon(
+ store,
+ requestBuilder,
+ subscription.ID,
+ subscription.SiteURL,
+ subscription.IconURL,
+ )
+
+ return subscription, nil
+}
+
// CreateFeed fetch, parse and store a new feed.
func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model.FeedCreationRequest) (*model.Feed, *locale.LocalizedErrorWrapper) {
slog.Debug("Begin feed creation process",
@@ -68,7 +146,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
return nil, locale.NewLocalizedErrorWrapper(ErrDuplicatedFeed, "error.duplicated_feed")
}
- subscription, parseErr := parser.ParseFeed(responseHandler.EffectiveURL(), string(responseBody))
+ subscription, parseErr := parser.ParseFeed(responseHandler.EffectiveURL(), bytes.NewReader(responseBody))
if parseErr != nil {
return nil, locale.NewLocalizedErrorWrapper(parseErr, "error.unable_to_parse_feed", parseErr)
}
@@ -188,7 +266,7 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
return localizedError
}
- updatedFeed, parseErr := parser.ParseFeed(responseHandler.EffectiveURL(), string(responseBody))
+ updatedFeed, parseErr := parser.ParseFeed(responseHandler.EffectiveURL(), bytes.NewReader(responseBody))
if parseErr != nil {
localizedError := locale.NewLocalizedErrorWrapper(parseErr, "error.unable_to_parse_feed")
diff --git a/internal/reader/parser/format.go b/internal/reader/parser/format.go
index 77a49d2c..1019d164 100644
--- a/internal/reader/parser/format.go
+++ b/internal/reader/parser/format.go
@@ -4,8 +4,9 @@
package parser // import "miniflux.app/v2/internal/reader/parser"
import (
+ "bytes"
"encoding/xml"
- "strings"
+ "io"
rxml "miniflux.app/v2/internal/reader/xml"
)
@@ -20,12 +21,16 @@ const (
)
// DetectFeedFormat tries to guess the feed format from input data.
-func DetectFeedFormat(data string) string {
- if strings.HasPrefix(strings.TrimSpace(data), "{") {
+func DetectFeedFormat(r io.ReadSeeker) string {
+ data := make([]byte, 512)
+ r.Read(data)
+
+ if bytes.HasPrefix(bytes.TrimSpace(data), []byte("{")) {
return FormatJSON
}
- decoder := rxml.NewDecoder(strings.NewReader(data))
+ r.Seek(0, io.SeekStart)
+ decoder := rxml.NewDecoder(r)
for {
token, _ := decoder.Token()
diff --git a/internal/reader/parser/format_test.go b/internal/reader/parser/format_test.go
index 936aa82e..7acf3e7a 100644
--- a/internal/reader/parser/format_test.go
+++ b/internal/reader/parser/format_test.go
@@ -4,12 +4,13 @@
package parser // import "miniflux.app/v2/internal/reader/parser"
import (
+ "strings"
"testing"
)
func TestDetectRDF(t *testing.T) {
data := ``
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatRDF {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRDF)
@@ -18,7 +19,7 @@ func TestDetectRDF(t *testing.T) {
func TestDetectRSS(t *testing.T) {
data := ``
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatRSS {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatRSS)
@@ -27,7 +28,7 @@ func TestDetectRSS(t *testing.T) {
func TestDetectAtom10(t *testing.T) {
data := ``
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@@ -36,7 +37,7 @@ func TestDetectAtom10(t *testing.T) {
func TestDetectAtom03(t *testing.T) {
data := ``
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@@ -45,7 +46,7 @@ func TestDetectAtom03(t *testing.T) {
func TestDetectAtomWithISOCharset(t *testing.T) {
data := ``
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatAtom {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatAtom)
@@ -59,7 +60,7 @@ func TestDetectJSON(t *testing.T) {
"title" : "Example"
}
`
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatJSON {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatJSON)
@@ -70,7 +71,7 @@ func TestDetectUnknown(t *testing.T) {
data := `
`
- format := DetectFeedFormat(data)
+ format := DetectFeedFormat(strings.NewReader(data))
if format != FormatUnknown {
t.Errorf(`Wrong format detected: %q instead of %q`, format, FormatUnknown)
diff --git a/internal/reader/parser/parser.go b/internal/reader/parser/parser.go
index 60b194d4..2843888b 100644
--- a/internal/reader/parser/parser.go
+++ b/internal/reader/parser/parser.go
@@ -5,7 +5,7 @@ package parser // import "miniflux.app/v2/internal/reader/parser"
import (
"errors"
- "strings"
+ "io"
"miniflux.app/v2/internal/model"
"miniflux.app/v2/internal/reader/atom"
@@ -17,16 +17,21 @@ import (
var ErrFeedFormatNotDetected = errors.New("parser: unable to detect feed format")
// ParseFeed analyzes the input data and returns a normalized feed object.
-func ParseFeed(baseURL, data string) (*model.Feed, error) {
- switch DetectFeedFormat(data) {
+func ParseFeed(baseURL string, r io.ReadSeeker) (*model.Feed, error) {
+ r.Seek(0, io.SeekStart)
+ switch DetectFeedFormat(r) {
case FormatAtom:
- return atom.Parse(baseURL, strings.NewReader(data))
+ r.Seek(0, io.SeekStart)
+ return atom.Parse(baseURL, r)
case FormatRSS:
- return rss.Parse(baseURL, strings.NewReader(data))
+ r.Seek(0, io.SeekStart)
+ return rss.Parse(baseURL, r)
case FormatJSON:
- return json.Parse(baseURL, strings.NewReader(data))
+ r.Seek(0, io.SeekStart)
+ return json.Parse(baseURL, r)
case FormatRDF:
- return rdf.Parse(baseURL, strings.NewReader(data))
+ r.Seek(0, io.SeekStart)
+ return rdf.Parse(baseURL, r)
default:
return nil, ErrFeedFormatNotDetected
}
diff --git a/internal/reader/parser/parser_test.go b/internal/reader/parser/parser_test.go
index cd0a3213..07972474 100644
--- a/internal/reader/parser/parser_test.go
+++ b/internal/reader/parser/parser_test.go
@@ -4,6 +4,7 @@
package parser // import "miniflux.app/v2/internal/reader/parser"
import (
+ "strings"
"testing"
)
@@ -29,7 +30,7 @@ func TestParseAtom(t *testing.T) {
`
- feed, err := ParseFeed("https://example.org/", data)
+ feed, err := ParseFeed("https://example.org/", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -57,7 +58,7 @@ func TestParseAtomFeedWithRelativeURL(t *testing.T) {
`
- feed, err := ParseFeed("https://example.org/blog/atom.xml", data)
+ feed, err := ParseFeed("https://example.org/blog/atom.xml", strings.NewReader(data))
if err != nil {
t.Fatal(err)
}
@@ -91,7 +92,7 @@ func TestParseRSS(t *testing.T) {
`
- feed, err := ParseFeed("http://liftoff.msfc.nasa.gov/", data)
+ feed, err := ParseFeed("http://liftoff.msfc.nasa.gov/", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -117,7 +118,7 @@ func TestParseRSSFeedWithRelativeURL(t *testing.T) {
`
- feed, err := ParseFeed("http://example.org/rss.xml", data)
+ feed, err := ParseFeed("http://example.org/rss.xml", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -158,7 +159,7 @@ func TestParseRDF(t *testing.T) {
`
- feed, err := ParseFeed("http://example.org/", data)
+ feed, err := ParseFeed("http://example.org/", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -187,7 +188,7 @@ func TestParseRDFWithRelativeURL(t *testing.T) {
`
- feed, err := ParseFeed("http://example.org/rdf.xml", data)
+ feed, err := ParseFeed("http://example.org/rdf.xml", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -225,7 +226,7 @@ func TestParseJson(t *testing.T) {
]
}`
- feed, err := ParseFeed("https://example.org/feed.json", data)
+ feed, err := ParseFeed("https://example.org/feed.json", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -250,7 +251,7 @@ func TestParseJsonFeedWithRelativeURL(t *testing.T) {
]
}`
- feed, err := ParseFeed("https://example.org/blog/feed.json", data)
+ feed, err := ParseFeed("https://example.org/blog/feed.json", strings.NewReader(data))
if err != nil {
t.Error(err)
}
@@ -285,14 +286,14 @@ func TestParseUnknownFeed(t *testing.T) {