feed.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package validator // import "miniflux.app/v2/internal/validator"
  4. import (
  5. "log/slog"
  6. "miniflux.app/v2/internal/locale"
  7. "miniflux.app/v2/internal/model"
  8. "miniflux.app/v2/internal/storage"
  9. "miniflux.app/v2/internal/urllib"
  10. )
  11. // ValidateFeedCreation validates feed creation.
  12. func ValidateFeedCreation(store *storage.Storage, userID int64, request *model.FeedCreationRequest) *locale.LocalizedError {
  13. if request.FeedURL == "" || request.CategoryID <= 0 {
  14. return locale.NewLocalizedError("error.feed_mandatory_fields")
  15. }
  16. if !urllib.IsAbsoluteURL(request.FeedURL) {
  17. return locale.NewLocalizedError("error.invalid_feed_url")
  18. }
  19. if store.FeedURLExists(userID, request.FeedURL) {
  20. return locale.NewLocalizedError("error.feed_already_exists")
  21. }
  22. categoryExists, err := store.CategoryIDExists(userID, request.CategoryID)
  23. if err != nil {
  24. // *locale.LocalizedError (unlike *locale.LocalizedErrorWrapper elsewhere
  25. // in this codebase) has no way to carry an underlying error, so a
  26. // genuine backend failure here can't be distinguished from "category
  27. // not found" in the value returned to the caller. Log it so the
  28. // failure is at least observable, and fall back to the existing
  29. // user-facing message rather than changing this function's public
  30. // return type as part of this PR.
  31. slog.Error("validator: unable to check if feed category exists",
  32. slog.Int64("user_id", userID),
  33. slog.Int64("category_id", request.CategoryID),
  34. slog.Any("error", err),
  35. )
  36. return locale.NewLocalizedError("error.feed_category_not_found")
  37. }
  38. if !categoryExists {
  39. return locale.NewLocalizedError("error.feed_category_not_found")
  40. }
  41. if !IsValidRegex(request.BlocklistRules) {
  42. return locale.NewLocalizedError("error.feed_invalid_blocklist_rule")
  43. }
  44. if !IsValidRegex(request.KeeplistRules) {
  45. return locale.NewLocalizedError("error.feed_invalid_keeplist_rule")
  46. }
  47. if !IsValidURLRewriteRules(request.UrlRewriteRules) {
  48. return locale.NewLocalizedError("error.feed_invalid_urlrewrite_rule")
  49. }
  50. if request.BlockFilterEntryRules != "" {
  51. if err := IsValidFilterRules(request.BlockFilterEntryRules, "block"); err != nil {
  52. return err
  53. }
  54. }
  55. if request.KeepFilterEntryRules != "" {
  56. if err := IsValidFilterRules(request.KeepFilterEntryRules, "keep"); err != nil {
  57. return err
  58. }
  59. }
  60. if err := IsValidRewriteRules(request.RewriteRules); err != nil {
  61. return err
  62. }
  63. if err := IsValidScraperRules(request.ScraperRules); err != nil {
  64. return err
  65. }
  66. if request.ProxyURL != "" && !urllib.IsValidProxyURL(request.ProxyURL) {
  67. return locale.NewLocalizedError("error.invalid_feed_proxy_url")
  68. }
  69. return nil
  70. }
  71. // ValidateFeedModification validates feed modification.
  72. func ValidateFeedModification(store *storage.Storage, userID, feedID int64, request *model.FeedModificationRequest) *locale.LocalizedError {
  73. if request.FeedURL != nil {
  74. if *request.FeedURL == "" {
  75. return locale.NewLocalizedError("error.feed_url_not_empty")
  76. }
  77. if !urllib.IsAbsoluteURL(*request.FeedURL) {
  78. return locale.NewLocalizedError("error.invalid_feed_url")
  79. }
  80. if store.AnotherFeedURLExists(userID, feedID, *request.FeedURL) {
  81. return locale.NewLocalizedError("error.feed_already_exists")
  82. }
  83. }
  84. if request.SiteURL != nil {
  85. if *request.SiteURL == "" {
  86. return locale.NewLocalizedError("error.site_url_not_empty")
  87. }
  88. if !urllib.IsAbsoluteURL(*request.SiteURL) {
  89. return locale.NewLocalizedError("error.invalid_site_url")
  90. }
  91. }
  92. if request.Title != nil {
  93. if *request.Title == "" {
  94. return locale.NewLocalizedError("error.feed_title_not_empty")
  95. }
  96. }
  97. if request.CategoryID != nil {
  98. categoryExists, err := store.CategoryIDExists(userID, *request.CategoryID)
  99. if err != nil {
  100. // See the identical rationale in ValidateFeedCreation above.
  101. slog.Error("validator: unable to check if feed category exists",
  102. slog.Int64("user_id", userID),
  103. slog.Int64("category_id", *request.CategoryID),
  104. slog.Any("error", err),
  105. )
  106. return locale.NewLocalizedError("error.feed_category_not_found")
  107. }
  108. if !categoryExists {
  109. return locale.NewLocalizedError("error.feed_category_not_found")
  110. }
  111. }
  112. if request.BlocklistRules != nil {
  113. if !IsValidRegex(*request.BlocklistRules) {
  114. return locale.NewLocalizedError("error.feed_invalid_blocklist_rule")
  115. }
  116. }
  117. if request.KeeplistRules != nil {
  118. if !IsValidRegex(*request.KeeplistRules) {
  119. return locale.NewLocalizedError("error.feed_invalid_keeplist_rule")
  120. }
  121. }
  122. if request.RewriteRules != nil {
  123. if err := IsValidRewriteRules(*request.RewriteRules); err != nil {
  124. return err
  125. }
  126. }
  127. if request.ScraperRules != nil {
  128. if err := IsValidScraperRules(*request.ScraperRules); err != nil {
  129. return err
  130. }
  131. }
  132. if request.UrlRewriteRules != nil {
  133. if !IsValidURLRewriteRules(*request.UrlRewriteRules) {
  134. return locale.NewLocalizedError("error.feed_invalid_urlrewrite_rule")
  135. }
  136. }
  137. if request.BlockFilterEntryRules != nil && *request.BlockFilterEntryRules != "" {
  138. if err := IsValidFilterRules(*request.BlockFilterEntryRules, "block"); err != nil {
  139. return err
  140. }
  141. }
  142. if request.KeepFilterEntryRules != nil && *request.KeepFilterEntryRules != "" {
  143. if err := IsValidFilterRules(*request.KeepFilterEntryRules, "keep"); err != nil {
  144. return err
  145. }
  146. }
  147. if request.ProxyURL != nil && *request.ProxyURL != "" {
  148. if !urllib.IsValidProxyURL(*request.ProxyURL) {
  149. return locale.NewLocalizedError("error.invalid_feed_proxy_url")
  150. }
  151. }
  152. return nil
  153. }