feed.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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 request.ProxyURL != "" && !urllib.IsValidProxyURL(request.ProxyURL) {
  64. return locale.NewLocalizedError("error.invalid_feed_proxy_url")
  65. }
  66. return nil
  67. }
  68. // ValidateFeedModification validates feed modification.
  69. func ValidateFeedModification(store *storage.Storage, userID, feedID int64, request *model.FeedModificationRequest) *locale.LocalizedError {
  70. if request.FeedURL != nil {
  71. if *request.FeedURL == "" {
  72. return locale.NewLocalizedError("error.feed_url_not_empty")
  73. }
  74. if !urllib.IsAbsoluteURL(*request.FeedURL) {
  75. return locale.NewLocalizedError("error.invalid_feed_url")
  76. }
  77. if store.AnotherFeedURLExists(userID, feedID, *request.FeedURL) {
  78. return locale.NewLocalizedError("error.feed_already_exists")
  79. }
  80. }
  81. if request.SiteURL != nil {
  82. if *request.SiteURL == "" {
  83. return locale.NewLocalizedError("error.site_url_not_empty")
  84. }
  85. if !urllib.IsAbsoluteURL(*request.SiteURL) {
  86. return locale.NewLocalizedError("error.invalid_site_url")
  87. }
  88. }
  89. if request.Title != nil {
  90. if *request.Title == "" {
  91. return locale.NewLocalizedError("error.feed_title_not_empty")
  92. }
  93. }
  94. if request.CategoryID != nil {
  95. categoryExists, err := store.CategoryIDExists(userID, *request.CategoryID)
  96. if err != nil {
  97. // See the identical rationale in ValidateFeedCreation above.
  98. slog.Error("validator: unable to check if feed category exists",
  99. slog.Int64("user_id", userID),
  100. slog.Int64("category_id", *request.CategoryID),
  101. slog.Any("error", err),
  102. )
  103. return locale.NewLocalizedError("error.feed_category_not_found")
  104. }
  105. if !categoryExists {
  106. return locale.NewLocalizedError("error.feed_category_not_found")
  107. }
  108. }
  109. if request.BlocklistRules != nil {
  110. if !IsValidRegex(*request.BlocklistRules) {
  111. return locale.NewLocalizedError("error.feed_invalid_blocklist_rule")
  112. }
  113. }
  114. if request.KeeplistRules != nil {
  115. if !IsValidRegex(*request.KeeplistRules) {
  116. return locale.NewLocalizedError("error.feed_invalid_keeplist_rule")
  117. }
  118. }
  119. if request.RewriteRules != nil {
  120. if err := IsValidRewriteRules(*request.RewriteRules); err != nil {
  121. return err
  122. }
  123. }
  124. if request.UrlRewriteRules != nil {
  125. if !IsValidURLRewriteRules(*request.UrlRewriteRules) {
  126. return locale.NewLocalizedError("error.feed_invalid_urlrewrite_rule")
  127. }
  128. }
  129. if request.BlockFilterEntryRules != nil && *request.BlockFilterEntryRules != "" {
  130. if err := IsValidFilterRules(*request.BlockFilterEntryRules, "block"); err != nil {
  131. return err
  132. }
  133. }
  134. if request.KeepFilterEntryRules != nil && *request.KeepFilterEntryRules != "" {
  135. if err := IsValidFilterRules(*request.KeepFilterEntryRules, "keep"); err != nil {
  136. return err
  137. }
  138. }
  139. if request.ProxyURL != nil && *request.ProxyURL != "" {
  140. if !urllib.IsValidProxyURL(*request.ProxyURL) {
  141. return locale.NewLocalizedError("error.invalid_feed_proxy_url")
  142. }
  143. }
  144. return nil
  145. }