feed.go 4.7 KB

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