Pārlūkot izejas kodu

Add feed option to disable HTTP/2 to avoid fingerprinting

Frédéric Guillot 2 gadi atpakaļ
vecāks
revīzija
eae4cb1417
36 mainītis faili ar 90 papildinājumiem un 10 dzēšanām
  1. 3 0
      client/model.go
  2. 1 0
      internal/api/subscription.go
  3. 5 0
      internal/database/migrations.go
  4. 1 0
      internal/locale/translations/de_DE.json
  5. 1 0
      internal/locale/translations/el_EL.json
  6. 1 0
      internal/locale/translations/en_US.json
  7. 1 0
      internal/locale/translations/es_ES.json
  8. 1 0
      internal/locale/translations/fi_FI.json
  9. 1 0
      internal/locale/translations/fr_FR.json
  10. 1 0
      internal/locale/translations/hi_IN.json
  11. 1 0
      internal/locale/translations/id_ID.json
  12. 1 0
      internal/locale/translations/it_IT.json
  13. 1 0
      internal/locale/translations/ja_JP.json
  14. 1 0
      internal/locale/translations/nl_NL.json
  15. 1 0
      internal/locale/translations/pl_PL.json
  16. 1 0
      internal/locale/translations/pt_BR.json
  17. 1 0
      internal/locale/translations/ru_RU.json
  18. 1 0
      internal/locale/translations/tr_TR.json
  19. 1 0
      internal/locale/translations/uk_UA.json
  20. 1 0
      internal/locale/translations/zh_CN.json
  21. 1 0
      internal/locale/translations/zh_TW.json
  22. 8 0
      internal/model/feed.go
  23. 1 0
      internal/model/subscription.go
  24. 16 0
      internal/reader/fetcher/request_builder.go
  25. 5 0
      internal/reader/handler/handler.go
  26. 2 0
      internal/reader/processor/processor.go
  27. 10 6
      internal/storage/feed.go
  28. 6 4
      internal/storage/feed_query_builder.go
  29. 1 0
      internal/template/templates/views/add_subscription.html
  30. 3 0
      internal/template/templates/views/choose_subscription.html
  31. 1 0
      internal/template/templates/views/edit_feed.html
  32. 1 0
      internal/ui/feed_edit.go
  33. 3 0
      internal/ui/form/feed.go
  34. 2 0
      internal/ui/form/subscription.go
  35. 1 0
      internal/ui/subscription_choose.go
  36. 3 0
      internal/ui/subscription_submit.go

+ 3 - 0
client/model.go

@@ -140,6 +140,7 @@ type Feed struct {
 	Password                    string    `json:"password"`
 	Category                    *Category `json:"category,omitempty"`
 	HideGlobally                bool      `json:"hide_globally"`
+	DisableHTTP2                bool      `json:"disable_http2"`
 }
 
 // FeedCreationRequest represents the request to create a feed.
@@ -160,6 +161,7 @@ type FeedCreationRequest struct {
 	BlocklistRules              string `json:"blocklist_rules"`
 	KeeplistRules               string `json:"keeplist_rules"`
 	HideGlobally                bool   `json:"hide_globally"`
+	DisableHTTP2                bool   `json:"disable_http2"`
 }
 
 // FeedModificationRequest represents the request to update a feed.
@@ -182,6 +184,7 @@ type FeedModificationRequest struct {
 	AllowSelfSignedCertificates *bool   `json:"allow_self_signed_certificates"`
 	FetchViaProxy               *bool   `json:"fetch_via_proxy"`
 	HideGlobally                *bool   `json:"hide_globally"`
+	DisableHTTP2                *bool   `json:"disable_http2"`
 }
 
 // FeedIcon represents the feed icon.

+ 1 - 0
internal/api/subscription.go

@@ -42,6 +42,7 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
 	requestBuilder.WithUsernameAndPassword(subscriptionDiscoveryRequest.Username, subscriptionDiscoveryRequest.Password)
 	requestBuilder.UseProxy(subscriptionDiscoveryRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(subscriptionDiscoveryRequest.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(subscriptionDiscoveryRequest.DisableHTTP2)
 
 	subscriptions, localizedError := subscription.NewSubscriptionFinder(requestBuilder).FindSubscriptions(
 		subscriptionDiscoveryRequest.URL,

+ 5 - 0
internal/database/migrations.go

@@ -866,4 +866,9 @@ var migrations = []func(tx *sql.Tx) error{
 		_, err = tx.Exec(sql)
 		return err
 	},
+	func(tx *sql.Tx) (err error) {
+		sql := `ALTER TABLE feeds ADD COLUMN disable_http2 bool default 'f'`
+		_, err = tx.Exec(sql)
+		return err
+	},
 }

+ 1 - 0
internal/locale/translations/de_DE.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": "Umschreibregeln für URL",
     "form.feed.label.ignore_http_cache": "Ignoriere HTTP-Cache",
     "form.feed.label.allow_self_signed_certificates": "Erlaube selbstsignierte oder ungültige Zertifikate",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Über Proxy abrufen",
     "form.feed.label.disabled": "Dieses Abonnement nicht aktualisieren",
     "form.feed.label.no_media_player": "Kein Media-Player (Audio/Video)",

+ 1 - 0
internal/locale/translations/el_EL.json

@@ -327,6 +327,7 @@
     "form.feed.label.keeplist_rules": "Κρατήστε Κανόνες",
     "form.feed.label.ignore_http_cache": "Αγνοήστε την προσωρινή μνήμη HTTP",
     "form.feed.label.allow_self_signed_certificates": "Να επιτρέπονται αυτο-υπογεγραμμένα ή μη έγκυρα πιστοποιητικά",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Λήψη μέσω διακομιστή μεσολάβησης",
     "form.feed.label.disabled": "Μη ανανέωση αυτής της ροής",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/en_US.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": "URL Rewrite Rules",
     "form.feed.label.ignore_http_cache": "Ignore HTTP cache",
     "form.feed.label.allow_self_signed_certificates": "Allow self-signed or invalid certificates",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Fetch via proxy",
     "form.feed.label.disabled": "Do not refresh this feed",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/es_ES.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": "Reglas de Filtrado (Reescritura)",
     "form.feed.label.ignore_http_cache": "Ignorar caché HTTP",
     "form.feed.label.allow_self_signed_certificates": "Permitir certificados autofirmados o no válidos",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Buscar a través de proxy",
     "form.feed.label.disabled": "No actualice este feed",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/fi_FI.json

@@ -327,6 +327,7 @@
     "form.feed.label.keeplist_rules": "Keep-säännöt",
     "form.feed.label.ignore_http_cache": "Ohita HTTP-välimuisti",
     "form.feed.label.allow_self_signed_certificates": "Salli itseallekirjoitetut tai virheelliset varmenteet",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Nouda välityspalvelimen kautta",
     "form.feed.label.disabled": "Älä päivitä tätä syötettä",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/fr_FR.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": "Règles de réécriture d'URL",
     "form.feed.label.ignore_http_cache": "Ignorer le cache HTTP",
     "form.feed.label.allow_self_signed_certificates": "Autoriser les certificats auto-signés ou non valides",
+    "form.feed.label.disable_http2": "Désactiver HTTP/2",
     "form.feed.label.fetch_via_proxy": "Récupérer via proxy",
     "form.feed.label.disabled": "Ne pas actualiser ce flux",
     "form.feed.label.no_media_player": "Pas de lecteur multimedia (audio/vidéo)",

+ 1 - 0
internal/locale/translations/hi_IN.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": " यूआरएल पुनर्लेखन नियम",
     "form.feed.label.ignore_http_cache": "एचटीटीपी कैश पर ध्यान न दें",
     "form.feed.label.allow_self_signed_certificates": "स्व-हस्ताक्षरित या अमान्य प्रमाणपत्रों की अनुमति दें",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "प्रॉक्सी के माध्यम से प्राप्त करें",
     "form.feed.label.disabled": "इस फ़ीड को रीफ़्रेश न करें",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/id_ID.json

@@ -324,6 +324,7 @@
     "form.feed.label.urlrewrite_rules": "Aturan Tulis Ulang URL",
     "form.feed.label.ignore_http_cache": "Abaikan Tembolok HTTP",
     "form.feed.label.allow_self_signed_certificates": "Perbolehkan sertifikat web tidak valid atau sertifikasi sendiri",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Ambil via Proksi",
     "form.feed.label.disabled": "Jangan perbarui umpan ini",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/it_IT.json

@@ -327,6 +327,7 @@
     "form.feed.label.urlrewrite_rules": "Regole di riscrittura URL",
     "form.feed.label.ignore_http_cache": "Ignora cache HTTP",
     "form.feed.label.allow_self_signed_certificates": "Consenti certificati autofirmati o non validi",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Recuperare tramite proxy",
     "form.feed.label.disabled": "Non aggiornare questo feed",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/ja_JP.json

@@ -327,6 +327,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "HTTPキャッシュを無視",
     "form.feed.label.allow_self_signed_certificates": "自己署名証明書または無効な証明書を許可する",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "プロキシ経由で取得",
     "form.feed.label.disabled": "このフィードを更新しない",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/nl_NL.json

@@ -327,6 +327,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "Negeer HTTP-cache",
     "form.feed.label.allow_self_signed_certificates": "Sta zelfondertekende of ongeldige certificaten toe",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Ophalen via proxy",
     "form.feed.label.disabled": "Vernieuw deze feed niet",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/pl_PL.json

@@ -330,6 +330,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "Zignoruj ​​pamięć podręczną HTTP",
     "form.feed.label.allow_self_signed_certificates": "Zezwalaj na certyfikaty z podpisem własnym lub nieprawidłowe certyfikaty",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Pobierz przez proxy",
     "form.feed.label.disabled": "Nie odświeżaj tego kanału",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/pt_BR.json

@@ -327,6 +327,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "Ignorar cache HTTP",
     "form.feed.label.allow_self_signed_certificates": "Permitir certificados autoassinados ou inválidos",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.disabled": "Não atualizar esta fonte",
     "form.feed.label.no_media_player": "No media player (audio/video)",
     "form.feed.label.fetch_via_proxy": "Buscar via proxy",

+ 1 - 0
internal/locale/translations/ru_RU.json

@@ -330,6 +330,7 @@
     "form.feed.label.apprise_service_urls": "Список ссылок сервисов Apprise, разделенный запятой",
     "form.feed.label.ignore_http_cache": "Игнорировать HTTP кеш",
     "form.feed.label.allow_self_signed_certificates": "Разрешить самоподписанные или недействительные сертификаты",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Использовать прокси",
     "form.feed.label.disabled": "Не обновлять эту подписку",
     "form.feed.label.no_media_player": "Отключить медиаплеер (аудио и видео)",

+ 1 - 0
internal/locale/translations/tr_TR.json

@@ -327,6 +327,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "HTTP önbelleğini yoksay",
     "form.feed.label.allow_self_signed_certificates": "Kendinden imzalı veya geçersiz sertifikalara izin ver",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Proxy ile çek",
     "form.feed.label.disabled": "Bu beslemeyi yenileme",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/uk_UA.json

@@ -331,6 +331,7 @@
     "form.feed.label.apprise_service_urls": "Comma separated list of Apprise service URLs",
     "form.feed.label.ignore_http_cache": "Ігнорувати кеш HTTP",
     "form.feed.label.allow_self_signed_certificates": "Дозволити сертифікати з власним підписом або недійсні",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "Використати проксі-сервер",
     "form.feed.label.disabled": "Не оновлювати цю стрічку",
     "form.feed.label.no_media_player": "No media player (audio/video)",

+ 1 - 0
internal/locale/translations/zh_CN.json

@@ -325,6 +325,7 @@
     "form.feed.label.apprise_service_urls": "使用逗号分隔的 Apprise 服务 URL 列表",
     "form.feed.label.ignore_http_cache": "忽略 HTTP 缓存",
     "form.feed.label.allow_self_signed_certificates": "允许自签名证书或无效证书",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "通过代理获取",
     "form.feed.label.disabled": "请勿刷新此源",
     "form.feed.label.no_media_player": "没有媒体播放器(音频/视频)",

+ 1 - 0
internal/locale/translations/zh_TW.json

@@ -327,6 +327,7 @@
     "form.feed.label.apprise_service_urls": "使用逗號分隔的 Apprise 服務 URL 列表",
     "form.feed.label.ignore_http_cache": "忽略 HTTP 快取",
     "form.feed.label.allow_self_signed_certificates": "允許自簽章憑證或無效憑證",
+    "form.feed.label.disable_http2": "Disable HTTP/2 to avoid fingerprinting",
     "form.feed.label.fetch_via_proxy": "透過代理獲取",
     "form.feed.label.disabled": "請勿更新此 Feed",
     "form.feed.label.no_media_player": "沒有媒體播放器(音訊/視訊)",

+ 8 - 0
internal/model/feed.go

@@ -51,6 +51,7 @@ type Feed struct {
 	FetchViaProxy               bool      `json:"fetch_via_proxy"`
 	HideGlobally                bool      `json:"hide_globally"`
 	AppriseServiceURLs          string    `json:"apprise_service_urls"`
+	DisableHTTP2                bool      `json:"disable_http2"`
 
 	// Non persisted attributes
 	Category *Category `json:"category,omitempty"`
@@ -150,6 +151,7 @@ type FeedCreationRequest struct {
 	KeeplistRules               string `json:"keeplist_rules"`
 	HideGlobally                bool   `json:"hide_globally"`
 	UrlRewriteRules             string `json:"urlrewrite_rules"`
+	DisableHTTP2                bool   `json:"disable_http2"`
 }
 
 type FeedCreationRequestFromSubscriptionDiscovery struct {
@@ -175,6 +177,7 @@ type FeedCreationRequestFromSubscriptionDiscovery struct {
 	KeeplistRules               string `json:"keeplist_rules"`
 	HideGlobally                bool   `json:"hide_globally"`
 	UrlRewriteRules             string `json:"urlrewrite_rules"`
+	DisableHTTP2                bool   `json:"disable_http2"`
 }
 
 // FeedModificationRequest represents the request to update a feed.
@@ -199,6 +202,7 @@ type FeedModificationRequest struct {
 	AllowSelfSignedCertificates *bool   `json:"allow_self_signed_certificates"`
 	FetchViaProxy               *bool   `json:"fetch_via_proxy"`
 	HideGlobally                *bool   `json:"hide_globally"`
+	DisableHTTP2                *bool   `json:"disable_http2"`
 }
 
 // Patch updates a feed with modified values.
@@ -282,6 +286,10 @@ func (f *FeedModificationRequest) Patch(feed *Feed) {
 	if f.HideGlobally != nil {
 		feed.HideGlobally = *f.HideGlobally
 	}
+
+	if f.DisableHTTP2 != nil {
+		feed.DisableHTTP2 = *f.DisableHTTP2
+	}
 }
 
 // Feeds is a list of feed

+ 1 - 0
internal/model/subscription.go

@@ -12,4 +12,5 @@ type SubscriptionDiscoveryRequest struct {
 	Password                    string `json:"password"`
 	FetchViaProxy               bool   `json:"fetch_via_proxy"`
 	AllowSelfSignedCertificates bool   `json:"allow_self_signed_certificates"`
+	DisableHTTP2                bool   `json:"disable_http2"`
 }

+ 16 - 0
internal/reader/fetcher/request_builder.go

@@ -26,6 +26,7 @@ type RequestBuilder struct {
 	clientTimeout    int
 	withoutRedirects bool
 	ignoreTLSErrors  bool
+	disableHTTP2     bool
 }
 
 func NewRequestBuilder() *RequestBuilder {
@@ -97,6 +98,11 @@ func (r *RequestBuilder) WithoutRedirects() *RequestBuilder {
 	return r
 }
 
+func (r *RequestBuilder) DisableHTTP2(value bool) *RequestBuilder {
+	r.disableHTTP2 = value
+	return r
+}
+
 func (r *RequestBuilder) IgnoreTLSErrors(value bool) *RequestBuilder {
 	r.ignoreTLSErrors = value
 	return r
@@ -126,6 +132,14 @@ func (r *RequestBuilder) ExecuteRequest(requestURL string) (*http.Response, erro
 		},
 	}
 
+	if r.disableHTTP2 {
+		transport.ForceAttemptHTTP2 = false
+
+		// https://pkg.go.dev/net/http#hdr-HTTP_2
+		// Programs that must disable HTTP/2 can do so by setting [Transport.TLSNextProto] (for clients) or [Server.TLSNextProto] (for servers) to a non-nil, empty map.
+		transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
+	}
+
 	if r.useClientProxy && r.clientProxyURL != "" {
 		if proxyURL, err := url.Parse(r.clientProxyURL); err != nil {
 			slog.Warn("Unable to parse proxy URL",
@@ -165,6 +179,8 @@ func (r *RequestBuilder) ExecuteRequest(requestURL string) (*http.Response, erro
 		slog.Bool("without_redirects", r.withoutRedirects),
 		slog.Bool("with_proxy", r.useClientProxy),
 		slog.String("proxy_url", r.clientProxyURL),
+		slog.Bool("ignore_tls_errors", r.ignoreTLSErrors),
+		slog.Bool("disable_http2", r.disableHTTP2),
 	))
 
 	return client.Do(req)

+ 5 - 0
internal/reader/handler/handler.go

@@ -67,6 +67,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	subscription.EtagHeader = feedCreationRequest.ETag
 	subscription.LastModifiedHeader = feedCreationRequest.LastModified
 	subscription.FeedURL = feedCreationRequest.FeedURL
+	subscription.DisableHTTP2 = feedCreationRequest.DisableHTTP2
 	subscription.WithCategoryID(feedCreationRequest.CategoryID)
 	subscription.CheckedNow()
 
@@ -90,6 +91,7 @@ func CreateFeedFromSubscriptionDiscovery(store *storage.Storage, userID int64, f
 	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
 	requestBuilder.UseProxy(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(feedCreationRequest.DisableHTTP2)
 
 	checkFeedIcon(
 		store,
@@ -126,6 +128,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
 	requestBuilder.UseProxy(feedCreationRequest.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feedCreationRequest.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(feedCreationRequest.DisableHTTP2)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(feedCreationRequest.FeedURL))
 	defer responseHandler.Close()
@@ -159,6 +162,7 @@ func CreateFeed(store *storage.Storage, userID int64, feedCreationRequest *model
 	subscription.Disabled = feedCreationRequest.Disabled
 	subscription.IgnoreHTTPCache = feedCreationRequest.IgnoreHTTPCache
 	subscription.AllowSelfSignedCertificates = feedCreationRequest.AllowSelfSignedCertificates
+	subscription.DisableHTTP2 = feedCreationRequest.DisableHTTP2
 	subscription.FetchViaProxy = feedCreationRequest.FetchViaProxy
 	subscription.ScraperRules = feedCreationRequest.ScraperRules
 	subscription.RewriteRules = feedCreationRequest.RewriteRules
@@ -238,6 +242,7 @@ func RefreshFeed(store *storage.Storage, userID, feedID int64, forceRefresh bool
 	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
 	requestBuilder.UseProxy(originalFeed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(originalFeed.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(originalFeed.DisableHTTP2)
 
 	responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(originalFeed.FeedURL))
 	defer responseHandler.Close()

+ 2 - 0
internal/reader/processor/processor.go

@@ -72,6 +72,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
 			requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
 			requestBuilder.UseProxy(feed.FetchViaProxy)
 			requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)
+			requestBuilder.DisableHTTP2(feed.DisableHTTP2)
 
 			content, scraperErr := scraper.ScrapeWebsite(
 				requestBuilder,
@@ -181,6 +182,7 @@ func ProcessEntryWebPage(feed *model.Feed, entry *model.Entry, user *model.User)
 	requestBuilder.WithProxy(config.Opts.HTTPClientProxy())
 	requestBuilder.UseProxy(feed.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(feed.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(feed.DisableHTTP2)
 
 	content, scraperErr := scraper.ScrapeWebsite(
 		requestBuilder,

+ 10 - 6
internal/storage/feed.go

@@ -174,8 +174,8 @@ func (s *Storage) WeeklyFeedEntryCount(userID, feedID int64) (int, error) {
 		FROM
 			entries
 		WHERE
-			entries.user_id=$1 AND 
-			entries.feed_id=$2 AND 
+			entries.user_id=$1 AND
+			entries.feed_id=$2 AND
 			entries.published_at BETWEEN (now() - interval '1 week') AND now();
 	`
 
@@ -235,10 +235,11 @@ func (s *Storage) CreateFeed(feed *model.Feed) error {
 			hide_globally,
 			url_rewrite_rules,
 			no_media_player,
-			apprise_service_urls
+			apprise_service_urls,
+			disable_http2
 		)
 		VALUES
-			($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)
+			($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)
 		RETURNING
 			id
 	`
@@ -268,6 +269,7 @@ func (s *Storage) CreateFeed(feed *model.Feed) error {
 		feed.UrlRewriteRules,
 		feed.NoMediaPlayer,
 		feed.AppriseServiceURLs,
+		feed.DisableHTTP2,
 	).Scan(&feed.ID)
 	if err != nil {
 		return fmt.Errorf(`store: unable to create feed %q: %v`, feed.FeedURL, err)
@@ -339,9 +341,10 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
 			hide_globally=$24,
 			url_rewrite_rules=$25,
 			no_media_player=$26,
-			apprise_service_urls=$27
+			apprise_service_urls=$27,
+			disable_http2=$28
 		WHERE
-			id=$28 AND user_id=$29
+			id=$29 AND user_id=$30
 	`
 	_, err = s.db.Exec(query,
 		feed.FeedURL,
@@ -371,6 +374,7 @@ func (s *Storage) UpdateFeed(feed *model.Feed) (err error) {
 		feed.UrlRewriteRules,
 		feed.NoMediaPlayer,
 		feed.AppriseServiceURLs,
+		feed.DisableHTTP2,
 		feed.ID,
 		feed.UserID,
 	)

+ 6 - 4
internal/storage/feed_query_builder.go

@@ -163,7 +163,8 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
 			c.hide_globally as category_hidden,
 			fi.icon_id,
 			u.timezone,
-			f.apprise_service_urls
+			f.apprise_service_urls,
+			f.disable_http2
 		FROM
 			feeds f
 		LEFT JOIN
@@ -172,7 +173,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
 			feed_icons fi ON fi.feed_id=f.id
 		LEFT JOIN
 			users u ON u.id=f.user_id
-		WHERE %s 
+		WHERE %s
 		%s
 	`
 
@@ -230,6 +231,7 @@ func (f *FeedQueryBuilder) GetFeeds() (model.Feeds, error) {
 			&iconID,
 			&tz,
 			&feed.AppriseServiceURLs,
+			&feed.DisableHTTP2,
 		)
 
 		if err != nil {
@@ -274,9 +276,9 @@ func (f *FeedQueryBuilder) fetchFeedCounter() (unreadCounters map[int64]int, rea
 			count(*)
 		FROM
 			entries e
-		%s 
+		%s
 		WHERE
-			%s 
+			%s
 		GROUP BY
 			e.feed_id, e.status
 	`

+ 1 - 0
internal/template/templates/views/add_subscription.html

@@ -33,6 +33,7 @@
             <div class="details-content">
                 <label><input type="checkbox" name="crawler" value="1" {{ if .form.Crawler }}checked{{ end }}> {{ t "form.feed.label.crawler" }}</label>
                 <label><input type="checkbox" name="allow_self_signed_certificates" value="1" {{ if .form.AllowSelfSignedCertificates }}checked{{ end }}> {{ t "form.feed.label.allow_self_signed_certificates" }}</label>
+                <label><input type="checkbox" name="disable_http2" value="1" {{ if .form.DisableHTTP2 }}checked{{ end }}> {{ t "form.feed.label.disable_http2" }}</label>
 
                 {{ if .hasProxyConfigured }}
                 <label><input type="checkbox" name="fetch_via_proxy" value="1" {{ if .form.FetchViaProxy }}checked{{ end }}> {{ t "form.feed.label.fetch_via_proxy" }}</label>

+ 3 - 0
internal/template/templates/views/choose_subscription.html

@@ -29,6 +29,9 @@
     {{ if .form.AllowSelfSignedCertificates }}
         <input type="hidden" name="allow_self_signed_certificates" value="1">
     {{ end }}
+    {{ if .form.DisableHTTP2 }}
+        <input type="hidden" name="disable_http2" value="1">
+    {{ end }}
 
     <h3>{{ t "page.add_feed.choose_feed" }}</h3>
 

+ 1 - 0
internal/template/templates/views/edit_feed.html

@@ -100,6 +100,7 @@
             <label><input type="checkbox" name="crawler" value="1" {{ if .form.Crawler }}checked{{ end }}> {{ t "form.feed.label.crawler" }}</label>
             <label><input type="checkbox" name="ignore_http_cache" value="1" {{ if .form.IgnoreHTTPCache }}checked{{ end }}> {{ t "form.feed.label.ignore_http_cache" }}</label>
             <label><input type="checkbox" name="allow_self_signed_certificates" value="1" {{ if .form.AllowSelfSignedCertificates }}checked{{ end }}> {{ t "form.feed.label.allow_self_signed_certificates" }}</label>
+            <label><input type="checkbox" name="disable_http2" value="1" {{ if .form.DisableHTTP2 }}checked{{ end }}> {{ t "form.feed.label.disable_http2" }}</label>
             {{ if .hasProxyConfigured }}
             <label><input type="checkbox" name="fetch_via_proxy" value="1" {{ if .form.FetchViaProxy }}checked{{ end }}> {{ t "form.feed.label.fetch_via_proxy" }}</label>
             {{ end }}

+ 1 - 0
internal/ui/feed_edit.go

@@ -62,6 +62,7 @@ func (h *handler) showEditFeedPage(w http.ResponseWriter, r *http.Request) {
 		HideGlobally:                feed.HideGlobally,
 		CategoryHidden:              feed.Category.HideGlobally,
 		AppriseServiceURLs:          feed.AppriseServiceURLs,
+		DisableHTTP2:                feed.DisableHTTP2,
 	}
 
 	sess := session.New(h.store, request.SessionID(r))

+ 3 - 0
internal/ui/form/feed.go

@@ -34,6 +34,7 @@ type FeedForm struct {
 	HideGlobally                bool
 	CategoryHidden              bool // Category has "hide_globally"
 	AppriseServiceURLs          string
+	DisableHTTP2                bool
 }
 
 // Merge updates the fields of the given feed.
@@ -61,6 +62,7 @@ func (f FeedForm) Merge(feed *model.Feed) *model.Feed {
 	feed.NoMediaPlayer = f.NoMediaPlayer
 	feed.HideGlobally = f.HideGlobally
 	feed.AppriseServiceURLs = f.AppriseServiceURLs
+	feed.DisableHTTP2 = f.DisableHTTP2
 	return feed
 }
 
@@ -92,5 +94,6 @@ func NewFeedForm(r *http.Request) *FeedForm {
 		NoMediaPlayer:               r.FormValue("no_media_player") == "1",
 		HideGlobally:                r.FormValue("hide_globally") == "1",
 		AppriseServiceURLs:          r.FormValue("apprise_service_urls"),
+		DisableHTTP2:                r.FormValue("disable_http2") == "1",
 	}
 }

+ 2 - 0
internal/ui/form/subscription.go

@@ -27,6 +27,7 @@ type SubscriptionForm struct {
 	BlocklistRules              string
 	KeeplistRules               string
 	UrlRewriteRules             string
+	DisableHTTP2                bool
 }
 
 // Validate makes sure the form values locale.are valid.
@@ -76,5 +77,6 @@ func NewSubscriptionForm(r *http.Request) *SubscriptionForm {
 		BlocklistRules:              r.FormValue("blocklist_rules"),
 		KeeplistRules:               r.FormValue("keeplist_rules"),
 		UrlRewriteRules:             r.FormValue("urlrewrite_rules"),
+		DisableHTTP2:                r.FormValue("disable_http2") == "1",
 	}
 }

+ 1 - 0
internal/ui/subscription_choose.go

@@ -63,6 +63,7 @@ func (h *handler) showChooseSubscriptionPage(w http.ResponseWriter, r *http.Requ
 		KeeplistRules:               subscriptionForm.KeeplistRules,
 		UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 		FetchViaProxy:               subscriptionForm.FetchViaProxy,
+		DisableHTTP2:                subscriptionForm.DisableHTTP2,
 	})
 	if localizedError != nil {
 		view.Set("form", subscriptionForm)

+ 3 - 0
internal/ui/subscription_submit.go

@@ -65,6 +65,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 	requestBuilder.WithUsernameAndPassword(subscriptionForm.Username, subscriptionForm.Password)
 	requestBuilder.UseProxy(subscriptionForm.FetchViaProxy)
 	requestBuilder.IgnoreTLSErrors(subscriptionForm.AllowSelfSignedCertificates)
+	requestBuilder.DisableHTTP2(subscriptionForm.DisableHTTP2)
 
 	subscriptionFinder := subscription.NewSubscriptionFinder(requestBuilder)
 	subscriptions, localizedError := subscriptionFinder.FindSubscriptions(
@@ -103,6 +104,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 			KeeplistRules:               subscriptionForm.KeeplistRules,
 			UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 			FetchViaProxy:               subscriptionForm.FetchViaProxy,
+			DisableHTTP2:                subscriptionForm.DisableHTTP2,
 		})
 		if localizedError != nil {
 			v.Set("form", subscriptionForm)
@@ -128,6 +130,7 @@ func (h *handler) submitSubscription(w http.ResponseWriter, r *http.Request) {
 			KeeplistRules:               subscriptionForm.KeeplistRules,
 			UrlRewriteRules:             subscriptionForm.UrlRewriteRules,
 			FetchViaProxy:               subscriptionForm.FetchViaProxy,
+			DisableHTTP2:                subscriptionForm.DisableHTTP2,
 		})
 		if localizedError != nil {
 			v.Set("form", subscriptionForm)