From 666536cbd185afd072442fa1f0e3b08a1cf75261 Mon Sep 17 00:00:00 2001 From: Daniel Waterworth Date: Fri, 20 Jan 2023 12:52:49 -0600 Subject: [PATCH] DEV: Prefer \A and \z over ^ and $ in regexes (#19936) --- app/controllers/admin/backups_controller.rb | 4 +- app/controllers/admin/reports_controller.rb | 6 +- .../admin/site_texts_controller.rb | 2 +- app/controllers/admin/themes_controller.rb | 2 +- app/controllers/application_controller.rb | 2 +- app/controllers/embed_controller.rb | 6 +- app/controllers/extra_locales_controller.rb | 2 +- app/controllers/session_controller.rb | 2 +- .../theme_javascripts_controller.rb | 2 +- app/controllers/topics_controller.rb | 2 +- app/controllers/user_avatars_controller.rb | 2 +- app/controllers/users_controller.rb | 2 +- app/helpers/application_helper.rb | 6 +- app/helpers/user_notifications_helper.rb | 4 +- app/jobs/base.rb | 2 +- app/jobs/onceoff/onceoff.rb | 2 +- app/jobs/regular/update_username.rb | 4 +- app/models/admin_dashboard_data.rb | 2 +- app/models/category.rb | 4 +- app/models/concerns/has_custom_fields.rb | 2 +- app/models/concerns/reports/top_uploads.rb | 2 +- app/models/embeddable_host.rb | 4 +- app/models/emoji.rb | 2 +- app/models/global_setting.rb | 8 +- app/models/group.rb | 6 +- app/models/optimized_image.rb | 4 +- app/models/post_action_type.rb | 4 +- app/models/published_page.rb | 2 +- app/models/remote_theme.rb | 6 +- app/models/report.rb | 4 +- app/models/reviewable.rb | 2 +- app/models/screened_url.rb | 6 +- app/models/theme_field.rb | 20 ++-- app/models/topic.rb | 2 +- app/models/topic_embed.rb | 6 +- app/models/topic_link.rb | 2 +- app/models/topic_link_click.rb | 8 +- app/models/translation_override.rb | 2 +- app/models/upload.rb | 4 +- app/models/user.rb | 4 +- app/models/user_profile.rb | 2 +- app/models/username_validator.rb | 6 +- app/models/watched_word.rb | 2 +- app/serializers/user_card_serializer.rb | 2 +- app/services/search_indexer.rb | 2 +- app/services/user_updater.rb | 2 +- lib/admin_user_index_query.rb | 2 +- lib/autospec/manager.rb | 6 +- lib/autospec/reload_css.rb | 12 +- lib/autospec/rspec_runner.rb | 28 ++--- lib/autospec/simple_runner.rb | 2 +- lib/backup_restore/backup_file_handler.rb | 2 +- lib/backup_restore/database_restorer.rb | 2 +- lib/backup_restore/s3_backup_store.rb | 2 +- lib/composer_messages_finder.rb | 2 +- lib/compression/gzip.rb | 2 +- lib/content_security_policy/extension.rb | 2 +- lib/cooked_post_processor.rb | 6 +- lib/cooked_processor_mixin.rb | 2 +- lib/discourse_connect_base.rb | 2 +- lib/discourse_diff.rb | 2 +- lib/discourse_plugin_registry.rb | 6 +- lib/discourse_redis.rb | 2 +- lib/email/message_id_service.rb | 2 +- lib/email/receiver.rb | 12 +- lib/email/styles.rb | 6 +- lib/email_cook.rb | 12 +- lib/file_helper.rb | 14 +-- lib/file_store/base_store.rb | 2 +- lib/file_store/local_store.rb | 2 +- lib/file_store/s3_store.rb | 4 +- lib/freedom_patches/translate_accelerator.rb | 8 +- lib/git_url.rb | 2 +- lib/global_path.rb | 2 +- lib/guardian/ensure_magic.rb | 2 +- lib/html_prettify.rb | 4 +- lib/i18n/locale_file_checker.rb | 2 +- lib/middleware/anonymous_cache.rb | 4 +- lib/middleware/missing_avatars.rb | 2 +- lib/migration/safe_migrate.rb | 4 +- lib/onebox/engine.rb | 2 +- lib/onebox/layout.rb | 4 +- lib/onebox/mixins/git_blob_onebox.rb | 4 +- lib/onebox/open_graph.rb | 4 +- lib/onebox/sanitize_config.rb | 2 +- lib/oneboxer.rb | 4 +- lib/plain_text_to_markdown.rb | 6 +- lib/plugin/instance.rb | 8 +- lib/pretty_text.rb | 8 +- lib/pretty_text/helpers.rb | 2 +- ...quire_dependency_backward_compatibility.rb | 2 +- lib/retrieve_title.rb | 9 +- lib/route_matcher.rb | 2 +- lib/s3_inventory.rb | 2 +- lib/search.rb | 108 +++++++++--------- lib/shrink_uploaded_image.rb | 2 +- lib/site_settings/validations.rb | 2 +- lib/stylesheet/manager.rb | 2 +- lib/stylesheet/manager/builder.rb | 2 +- lib/stylesheet/watcher.rb | 4 +- lib/tasks/assets.rake | 2 +- lib/tasks/cdn.rake | 2 +- lib/tasks/db.rake | 2 +- lib/tasks/emoji.rake | 2 +- lib/tasks/plugin.rake | 2 +- lib/tasks/release_note.rake | 18 +-- lib/tasks/typepad.thor | 10 +- lib/tasks/uploads.rake | 6 +- lib/theme_javascript_compiler.rb | 4 +- lib/topic_creator.rb | 2 +- lib/topic_query.rb | 2 +- lib/upload_creator.rb | 4 +- lib/url_helper.rb | 6 +- lib/validators/css_color_validator.rb | 2 +- .../unicode_username_allowlist_validator.rb | 2 +- 115 files changed, 294 insertions(+), 291 deletions(-) diff --git a/app/controllers/admin/backups_controller.rb b/app/controllers/admin/backups_controller.rb index 2660c9895e8..fd429312e73 100644 --- a/app/controllers/admin/backups_controller.rb +++ b/app/controllers/admin/backups_controller.rb @@ -238,11 +238,11 @@ class Admin::BackupsController < Admin::AdminController end def valid_extension?(filename) - /\.(tar\.gz|t?gz)$/i =~ filename + /\.(tar\.gz|t?gz)\z/i =~ filename end def valid_filename?(filename) - !!(/^[a-zA-Z0-9\._\-]+$/ =~ filename) + !!(/\A[a-zA-Z0-9\._\-]+\z/ =~ filename) end def render_error(message_key) diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index abc8b9e83e2..ffa8463a9c4 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -7,9 +7,9 @@ class Admin::ReportsController < Admin::StaffController ApplicationRequest .req_types .keys - .select { |r| r =~ /^page_view_/ && r !~ /mobile/ } + .select { |r| r =~ /\Apage_view_/ && r !~ /mobile/ } .map { |r| r + "_reqs" } + - Report.singleton_methods.grep(/^report_(?!about|storage_stats)/) + Report.singleton_methods.grep(/\Areport_(?!about|storage_stats)/) reports = reports_methods.map do |name| @@ -61,7 +61,7 @@ class Admin::ReportsController < Admin::StaffController def show report_type = params[:type] - raise Discourse::NotFound unless report_type =~ /^[a-z0-9\_]+$/ + raise Discourse::NotFound unless report_type =~ /\A[a-z0-9\_]+\z/ args = parse_params(params) diff --git a/app/controllers/admin/site_texts_controller.rb b/app/controllers/admin/site_texts_controller.rb index 041c59128c8..a92065399f9 100644 --- a/app/controllers/admin/site_texts_controller.rb +++ b/app/controllers/admin/site_texts_controller.rb @@ -160,7 +160,7 @@ class Admin::SiteTextsController < Admin::AdminController { id: key, value: value, locale: locale } end - PLURALIZED_REGEX = /(.*)\.(zero|one|two|few|many|other)$/ + PLURALIZED_REGEX = /(.*)\.(zero|one|two|few|many|other)\z/ def find_site_text(locale) if self.class.restricted_keys.include?(params[:id]) diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index bf8ebb27d94..c25c2c17677 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -108,7 +108,7 @@ class Admin::ThemesController < Admin::AdminController render json: @theme, status: :created rescue RemoteTheme::ImportError => e if params[:force] - theme_name = params[:remote].gsub(/.git$/, "").split("/").last + theme_name = params[:remote].gsub(/.git\z/, "").split("/").last remote_theme = RemoteTheme.new remote_theme.private_key = private_key diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7302867e75b..770415b952e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -679,7 +679,7 @@ class ApplicationController < ActionController::Base DiscoursePluginRegistry.html_builders.each do |name, _| if name.start_with?("client:") - data[name.sub(/^client:/, "")] = DiscoursePluginRegistry.build_html(name, self) + data[name.sub(/\Aclient:/, "")] = DiscoursePluginRegistry.build_html(name, self) end end diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb index 8ff1f870c0c..f7939812207 100644 --- a/app/controllers/embed_controller.rb +++ b/app/controllers/embed_controller.rb @@ -28,11 +28,11 @@ class EmbedController < ApplicationController end if @embed_id = params[:discourse_embed_id] - raise Discourse::InvalidParameters.new(:embed_id) unless @embed_id =~ /^de\-[a-zA-Z0-9]+$/ + raise Discourse::InvalidParameters.new(:embed_id) unless @embed_id =~ /\Ade\-[a-zA-Z0-9]+\z/ end if @embed_class = params[:embed_class] - unless @embed_class =~ /^[a-zA-Z0-9\-_]+$/ + unless @embed_class =~ /\A[a-zA-Z0-9\-_]+\z/ raise Discourse::InvalidParameters.new(:embed_class) end end @@ -139,7 +139,7 @@ class EmbedController < ApplicationController by_url = {} if embed_urls.present? - urls = embed_urls.map { |u| u.sub(/#discourse-comments$/, "").sub(%r{/$}, "") } + urls = embed_urls.map { |u| u.sub(/#discourse-comments\z/, "").sub(%r{/\z}, "") } topic_embeds = TopicEmbed.where(embed_url: urls).includes(:topic).references(:topic) topic_embeds.each do |te| diff --git a/app/controllers/extra_locales_controller.rb b/app/controllers/extra_locales_controller.rb index 63fdb5d81c9..a6c04bee718 100644 --- a/app/controllers/extra_locales_controller.rb +++ b/app/controllers/extra_locales_controller.rb @@ -71,6 +71,6 @@ class ExtraLocalesController < ApplicationController private def valid_bundle?(bundle) - bundle == OVERRIDES_BUNDLE || (bundle =~ /^(admin|wizard)$/ && current_user&.staff?) + bundle == OVERRIDES_BUNDLE || (bundle =~ /\A(admin|wizard)\z/ && current_user&.staff?) end end diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index a11b1bc7a8f..586ed5dffda 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -203,7 +203,7 @@ class SessionController < ApplicationController end # If it's not a relative URL check the host - if return_path !~ %r{^/[^/]} + if return_path !~ %r{\A/[^/]} begin uri = URI(return_path) if (uri.hostname == Discourse.current_hostname) diff --git a/app/controllers/theme_javascripts_controller.rb b/app/controllers/theme_javascripts_controller.rb index 63d6484d712..159402889fe 100644 --- a/app/controllers/theme_javascripts_controller.rb +++ b/app/controllers/theme_javascripts_controller.rb @@ -47,7 +47,7 @@ class ThemeJavascriptsController < ApplicationController def show_tests digest = params[:digest] - raise Discourse::NotFound if !digest.match?(/^\h{40}$/) + raise Discourse::NotFound if !digest.match?(/\A\h{40}\z/) theme = Theme.find_by(id: params[:theme_id]) raise Discourse::NotFound if theme.blank? diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index c2a54dc9ff6..7b01ff55e0b 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -83,7 +83,7 @@ class TopicsController < ApplicationController # Special case: a slug with a number in front should look by slug first before looking # up that particular number - if params[:id] && params[:id] =~ /^\d+[^\d\\]+$/ + if params[:id] && params[:id] =~ /\A\d+[^\d\\]+\z/ topic = Topic.find_by_slug(params[:id]) return redirect_to_correct_topic(topic, opts[:post_number]) if topic end diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb index c943d32615c..db47e4b701f 100644 --- a/app/controllers/user_avatars_controller.rb +++ b/app/controllers/user_avatars_controller.rb @@ -39,7 +39,7 @@ class UserAvatarsController < ApplicationController def show_proxy_letter is_asset_path - if SiteSetting.external_system_avatars_url !~ %r{^/letter_avatar_proxy} + if SiteSetting.external_system_avatars_url !~ %r{\A/letter_avatar_proxy} raise Discourse::NotFound end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 23032ada602..efeadc161e7 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -473,7 +473,7 @@ class UsersController < ApplicationController end def my_redirect - raise Discourse::NotFound if params[:path] !~ %r{^[a-z_\-/]+$} + raise Discourse::NotFound if params[:path] !~ %r{\A[a-z_\-/]+\z} if current_user.blank? cookies[:destination_url] = path("/my/#{params[:path]}") diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 537e45b1e75..3f7cafff491 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -50,7 +50,7 @@ module ApplicationHelper def google_universal_analytics_json(ua_domain_name = nil) result = {} - result[:cookieDomain] = ua_domain_name.gsub(%r{^http(s)?://}, "") if ua_domain_name + result[:cookieDomain] = ua_domain_name.gsub(%r{\Ahttp(s)?://}, "") if ua_domain_name result[:userId] = current_user.id if current_user.present? result[:allowLinker] = true if SiteSetting.ga_universal_auto_link_domains.present? result.to_json @@ -117,9 +117,9 @@ module ApplicationHelper # seconds. if !script.start_with?("discourse/tests/") if is_brotli_req? - path = path.gsub(/\.([^.]+)$/, '.br.\1') + path = path.gsub(/\.([^.]+)\z/, '.br.\1') elsif is_gzip_req? - path = path.gsub(/\.([^.]+)$/, '.gz.\1') + path = path.gsub(/\.([^.]+)\z/, '.gz.\1') end end elsif GlobalSetting.cdn_url&.start_with?("https") && is_brotli_req? && diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb index e92dbeccd96..deb40425015 100644 --- a/app/helpers/user_notifications_helper.rb +++ b/app/helpers/user_notifications_helper.rb @@ -20,8 +20,8 @@ module UserNotificationsHelper def logo_url logo_url = SiteSetting.site_digest_logo_url - logo_url = SiteSetting.site_logo_url if logo_url.blank? || logo_url =~ /\.svg$/i - return nil if logo_url.blank? || logo_url =~ /\.svg$/i + logo_url = SiteSetting.site_logo_url if logo_url.blank? || logo_url =~ /\.svg\z/i + return nil if logo_url.blank? || logo_url =~ /\.svg\z/i logo_url end diff --git a/app/jobs/base.rb b/app/jobs/base.rb index 0f671c95a55..4d1d2529e5c 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -29,7 +29,7 @@ module Jobs end def self.num_email_retry_jobs - Sidekiq::RetrySet.new.count { |job| job.klass =~ /Email$/ } + Sidekiq::RetrySet.new.count { |job| job.klass =~ /Email\z/ } end class Base diff --git a/app/jobs/onceoff/onceoff.rb b/app/jobs/onceoff/onceoff.rb index df9c94e54d0..efbc0ae5f5e 100644 --- a/app/jobs/onceoff/onceoff.rb +++ b/app/jobs/onceoff/onceoff.rb @@ -4,7 +4,7 @@ class Jobs::Onceoff < ::Jobs::Base sidekiq_options retry: false def self.name_for(klass) - klass.name.sub(/^Jobs\:\:/, "") + klass.name.sub(/\AJobs\:\:/, "") end def running_key_name diff --git a/app/jobs/regular/update_username.rb b/app/jobs/regular/update_username.rb index 3a0ed3194ef..e9b56cdd69e 100644 --- a/app/jobs/regular/update_username.rb +++ b/app/jobs/regular/update_username.rb @@ -29,9 +29,9 @@ module Jobs @raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i cooked_username = PrettyText::Helpers.format_username(@old_username) - @cooked_mention_username_regex = /^@#{cooked_username}$/i + @cooked_mention_username_regex = /\A@#{cooked_username}\z/i @cooked_mention_user_path_regex = - %r{^/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}$}i + %r{\A/u(?:sers)?/#{UrlHelper.encode_component(cooked_username)}\z}i @cooked_quote_username_regex = /(?<=\s)#{cooked_username}(?=:)/i update_posts diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index 8c3245a803b..1e845f1f15b 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -377,7 +377,7 @@ class AdminDashboardData end def subfolder_ends_in_slash_check - I18n.t("dashboard.subfolder_ends_in_slash") if Discourse.base_path =~ %r{/$} + I18n.t("dashboard.subfolder_ends_in_slash") if Discourse.base_path =~ %r{/\z} end def email_polling_errored_recently diff --git a/app/models/category.rb b/app/models/category.rb index 220ca006cd7..a049414df0e 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -421,7 +421,7 @@ class Category < ActiveRecord::Base end # only allow to use category itself id. - match_id = /^(\d+)-category/.match(self.slug) + match_id = /\A(\d+)-category/.match(self.slug) if match_id.present? errors.add(:slug, :invalid) if new_record? || (match_id[1] != self.id.to_s) end @@ -897,7 +897,7 @@ class Category < ActiveRecord::Base slug_path.inject(nil) do |parent_id, slug| category = Category.where(slug: slug, parent_category_id: parent_id) - if match_id = /^(\d+)-category/.match(slug).presence + if match_id = /\A(\d+)-category/.match(slug).presence category = category.or(Category.where(id: match_id[1], parent_category_id: parent_id)) end diff --git a/app/models/concerns/has_custom_fields.rb b/app/models/concerns/has_custom_fields.rb index 2b2f64f08a3..5330abdd694 100644 --- a/app/models/concerns/has_custom_fields.rb +++ b/app/models/concerns/has_custom_fields.rb @@ -20,7 +20,7 @@ module HasCustomFields sorted_types = types.keys.select { |k| k.end_with?("*") }.sort_by(&:length).reverse - sorted_types.each { |t| return types[t] if key =~ /^#{t}/i } + sorted_types.each { |t| return types[t] if key =~ /\A#{t}/i } types[key] end diff --git a/app/models/concerns/reports/top_uploads.rb b/app/models/concerns/reports/top_uploads.rb index d5e2955b7d9..b9caaa63fab 100644 --- a/app/models/concerns/reports/top_uploads.rb +++ b/app/models/concerns/reports/top_uploads.rb @@ -66,7 +66,7 @@ module Reports::TopUploads builder.where("up.created_at < :end_date", end_date: report.end_date) if extension_filter - builder.where("up.extension = :extension", extension: extension_filter.sub(/^\./, "")) + builder.where("up.extension = :extension", extension: extension_filter.sub(/\A\./, "")) end builder.query.each do |row| diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb index b9c63aff5b3..721faed2b61 100644 --- a/app/models/embeddable_host.rb +++ b/app/models/embeddable_host.rb @@ -6,8 +6,8 @@ class EmbeddableHost < ActiveRecord::Base after_destroy :reset_embedding_settings before_validation do - self.host.sub!(%r{^https?://}, "") - self.host.sub!(%r{/.*$}, "") + self.host.sub!(%r{\Ahttps?://}, "") + self.host.sub!(%r{/.*\z}, "") end # TODO(2021-07-23): Remove diff --git a/app/models/emoji.rb b/app/models/emoji.rb index 94b85eb154d..84a0f718dcc 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -173,7 +173,7 @@ class Emoji emojis.each do |name, url| result << Emoji.new.tap do |e| e.name = name - url = (Discourse.base_path + url) if url[%r{^/[^/]}] + url = (Discourse.base_path + url) if url[%r{\A/[^/]}] e.url = url e.group = group || DEFAULT_GROUP end diff --git a/app/models/global_setting.rb b/app/models/global_setting.rb index efd83403a06..51506e1c8c7 100644 --- a/app/models/global_setting.rb +++ b/app/models/global_setting.rb @@ -5,7 +5,7 @@ class GlobalSetting define_singleton_method(key) { provider.lookup(key, default) } end - VALID_SECRET_KEY ||= /^[0-9a-f]{128}$/ + VALID_SECRET_KEY ||= /\A[0-9a-f]{128}\z/ # this is named SECRET_TOKEN as opposed to SECRET_KEY_BASE # for legacy reasons REDIS_SECRET_KEY ||= "SECRET_TOKEN" @@ -251,7 +251,7 @@ class GlobalSetting class BaseProvider def self.coerce(setting) return setting == "true" if setting == "true" || setting == "false" - return $1.to_i if setting.to_s.strip =~ /^([0-9]+)$/ + return $1.to_i if setting.to_s.strip =~ /\A([0-9]+)\z/ setting end @@ -283,7 +283,7 @@ class GlobalSetting .result() .split("\n") .each do |line| - if line =~ /^\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/ + if line =~ /\A\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/ @data[$1.strip.to_sym] = ($4 || $3 || $2).strip end end @@ -314,7 +314,7 @@ class GlobalSetting end def keys - ENV.keys.select { |k| k =~ /^DISCOURSE_/ }.map { |k| k[10..-1].downcase.to_sym } + ENV.keys.select { |k| k =~ /\ADISCOURSE_/ }.map { |k| k[10..-1].downcase.to_sym } end end diff --git a/app/models/group.rb b/app/models/group.rb index c31436815a9..f6ca9fc0bcb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1005,7 +1005,7 @@ class Group < ActiveRecord::Base user = email_username_user domain = email_username_domain if user.present? && domain.present? - /^#{Regexp.escape(user)}(\+[^@]*)?@#{Regexp.escape(domain)}$/i + /\A#{Regexp.escape(user)}(\+[^@]*)?@#{Regexp.escape(domain)}\z/i end end @@ -1160,8 +1160,8 @@ class Group < ActiveRecord::Base value .split("|") .each do |domain| - domain.sub!(%r{^https?://}, "") - domain.sub!(%r{/.*$}, "") + domain.sub!(%r{\Ahttps?://}, "") + domain.sub!(%r{/.*\z}, "") if domain =~ Group::VALID_DOMAIN_REGEX valid_domains << domain diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index dbfa6d10c66..954686ca086 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -142,7 +142,7 @@ class OptimizedImage < ActiveRecord::Base end def local? - !(url =~ %r{^(https?:)?//}) + !(url =~ %r{\A(https?:)?//}) end def calculate_filesize @@ -337,7 +337,7 @@ class OptimizedImage < ActiveRecord::Base else error = +"Failed to optimize image:" - if e.message =~ /^convert:([^`]+)/ + if e.message =~ /\Aconvert:([^`]+)/ error << $1 else error << " unknown reason" diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index db785c307fe..3f2f8d7e977 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -7,8 +7,8 @@ class PostActionType < ActiveRecord::Base include AnonCacheInvalidator def expire_cache - ApplicationSerializer.expire_cache_fragment!(/^post_action_types_/) - ApplicationSerializer.expire_cache_fragment!(/^post_action_flag_types_/) + ApplicationSerializer.expire_cache_fragment!(/\Apost_action_types_/) + ApplicationSerializer.expire_cache_fragment!(/\Apost_action_flag_types_/) end class << self diff --git a/app/models/published_page.rb b/app/models/published_page.rb index 937ba59310e..5fd47bb9236 100644 --- a/app/models/published_page.rb +++ b/app/models/published_page.rb @@ -8,7 +8,7 @@ class PublishedPage < ActiveRecord::Base validate :slug_format def slug_format - if slug !~ /^[a-zA-Z\-\_0-9]+$/ + if slug !~ /\A[a-zA-Z\-\_0-9]+\z/ errors.add(:slug, I18n.t("publish_page.slug_errors.invalid")) elsif %w[check-slug by-topic].include?(slug) errors.add(:slug, I18n.t("publish_page.slug_errors.unavailable")) diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index baed6456548..4894a8a73c3 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -15,8 +15,8 @@ class RemoteTheme < ActiveRecord::Base ALLOWED_FIELDS = %w[scss embedded_scss head_tag header after_header body_tag footer] - GITHUB_REGEXP = %r{^https?://github\.com/} - GITHUB_SSH_REGEXP = %r{^ssh://git@github\.com:} + GITHUB_REGEXP = %r{\Ahttps?://github\.com/} + GITHUB_SSH_REGEXP = %r{\Assh://git@github\.com:} has_one :theme, autosave: false scope :joined_remotes, @@ -329,7 +329,7 @@ class RemoteTheme < ActiveRecord::Base def github_diff_link if github_repo_url.present? && local_version != remote_version - "#{github_repo_url.gsub(/\.git$/, "")}/compare/#{local_version}...#{remote_version}" + "#{github_repo_url.gsub(/\.git\z/, "")}/compare/#{local_version}...#{remote_version}" end end diff --git a/app/models/report.rb b/app/models/report.rb index cd9b7623ac1..1cf45d27cfe 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -268,8 +268,8 @@ class Report wrap_slow_query do if respond_to?(report_method) public_send(report_method, report) - elsif type =~ /_reqs$/ - req_report(report, type.split(/_reqs$/)[0].to_sym) + elsif type =~ /_reqs\z/ + req_report(report, type.split(/_reqs\z/)[0].to_sym) else return nil end diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb index 8f389cb6700..02b1b47421b 100644 --- a/app/models/reviewable.rb +++ b/app/models/reviewable.rb @@ -60,7 +60,7 @@ class Reviewable < ActiveRecord::Base end def self.valid_type?(type) - return false unless type =~ /^Reviewable[A-Za-z]+$/ + return false unless type =~ /\AReviewable[A-Za-z]+\z/ type.constantize <= Reviewable rescue NameError false diff --git a/app/models/screened_url.rb b/app/models/screened_url.rb index 0e827cf522d..dcc19bc3e31 100644 --- a/app/models/screened_url.rb +++ b/app/models/screened_url.rb @@ -17,7 +17,7 @@ class ScreenedUrl < ActiveRecord::Base def normalize self.url = ScreenedUrl.normalize_url(self.url) if self.url - self.domain = self.domain.downcase.sub(/^www\./, "") if self.domain + self.domain = self.domain.downcase.sub(/\Awww\./, "") if self.domain end def self.watch(url, domain, opts = {}) @@ -30,8 +30,8 @@ class ScreenedUrl < ActiveRecord::Base def self.normalize_url(url) normalized = url.gsub(%r{http(s?)://}i, "") - normalized.gsub!(%r{(/)+$}, "") # trim trailing slashes - normalized.gsub!(%r{^([^/]+)(?:/)?}) { |m| m.downcase } # downcase the domain part of the url + normalized.gsub!(%r{(/)+\z}, "") # trim trailing slashes + normalized.gsub!(%r{\A([^/]+)(?:/)?}) { |m| m.downcase } # downcase the domain part of the url normalized end end diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index 780789c42bf..a2404c7f83f 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -94,7 +94,7 @@ class ThemeField < ActiveRecord::Base .css('script[type="text/x-handlebars"]') .each do |node| name = node["name"] || node["data-template-name"] || "broken" - is_raw = name =~ /\.(raw|hbr)$/ + is_raw = name =~ /\.(raw|hbr)\z/ hbs_template = node.inner_html begin @@ -523,63 +523,63 @@ class ThemeField < ActiveRecord::Base FILE_MATCHERS = [ ThemeFileMatcher.new( regex: - %r{^(?(?:mobile|desktop|common))/(?(?:head_tag|header|after_header|body_tag|footer))\.html$}, + %r{\A(?(?:mobile|desktop|common))/(?(?:head_tag|header|after_header|body_tag|footer))\.html\z}, targets: %i[mobile desktop common], names: %w[head_tag header after_header body_tag footer], types: :html, canonical: ->(h) { "#{h[:target]}/#{h[:name]}.html" }, ), ThemeFileMatcher.new( - regex: %r{^(?(?:mobile|desktop|common))/(?:\k)\.scss$}, + regex: %r{\A(?(?:mobile|desktop|common))/(?:\k)\.scss\z}, targets: %i[mobile desktop common], names: "scss", types: :scss, canonical: ->(h) { "#{h[:target]}/#{h[:target]}.scss" }, ), ThemeFileMatcher.new( - regex: %r{^common/embedded\.scss$}, + regex: %r{\Acommon/embedded\.scss\z}, targets: :common, names: "embedded_scss", types: :scss, canonical: ->(h) { "common/embedded.scss" }, ), ThemeFileMatcher.new( - regex: %r{^common/color_definitions\.scss$}, + regex: %r{\Acommon/color_definitions\.scss\z}, targets: :common, names: "color_definitions", types: :scss, canonical: ->(h) { "common/color_definitions.scss" }, ), ThemeFileMatcher.new( - regex: %r{^(?:scss|stylesheets)/(?.+)\.scss$}, + regex: %r{\A(?:scss|stylesheets)/(?.+)\.scss\z}, targets: :extra_scss, names: nil, types: :scss, canonical: ->(h) { "stylesheets/#{h[:name]}.scss" }, ), ThemeFileMatcher.new( - regex: %r{^javascripts/(?.+)$}, + regex: %r{\Ajavascripts/(?.+)\z}, targets: :extra_js, names: nil, types: :js, canonical: ->(h) { "javascripts/#{h[:name]}" }, ), ThemeFileMatcher.new( - regex: %r{^test/(?.+)$}, + regex: %r{\Atest/(?.+)\z}, targets: :tests_js, names: nil, types: :js, canonical: ->(h) { "test/#{h[:name]}" }, ), ThemeFileMatcher.new( - regex: /^settings\.ya?ml$/, + regex: /\Asettings\.ya?ml\z/, names: "yaml", types: :yaml, targets: :settings, canonical: ->(h) { "settings.yml" }, ), ThemeFileMatcher.new( - regex: %r{^locales/(?(?:#{I18n.available_locales.join("|")}))\.yml$}, + regex: %r{\Alocales/(?(?:#{I18n.available_locales.join("|")}))\.yml\z}, names: I18n.available_locales.map(&:to_s), types: :yaml, targets: :translations, diff --git a/app/models/topic.rb b/app/models/topic.rb index 10a0e7a5386..e373f4f4d4a 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1200,7 +1200,7 @@ class Topic < ActiveRecord::Base else !!invite_to_topic(invited_by, target_user, group_ids, guardian) end - elsif username_or_email =~ /^.+@.+$/ && guardian.can_invite_via_email?(self) + elsif username_or_email =~ /\A.+@.+\z/ && guardian.can_invite_via_email?(self) !!Invite.generate( invited_by, email: username_or_email, diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index 3b9545bf815..8f9bb37c22a 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -25,7 +25,7 @@ class TopicEmbed < ActiveRecord::Base end def self.normalize_url(url) - url.downcase.sub(%r{/$}, "").sub(/\-+/, "-").strip + url.downcase.sub(%r{/\z}, "").sub(/\-+/, "-").strip end def self.imported_from_html(url) @@ -36,7 +36,7 @@ class TopicEmbed < ActiveRecord::Base # Import an article from a source (RSS/Atom/Other) def self.import(user, url, title, contents, category_id: nil, cook_method: nil, tags: nil) - return unless url =~ %r{^https?\://} + return unless url =~ %r{\Ahttps?\://} contents = first_paragraph_from(contents) if SiteSetting.embed_truncate && cook_method.nil? contents ||= "" @@ -253,7 +253,7 @@ class TopicEmbed < ActiveRecord::Base end def self.topic_id_for_embed(embed_url) - embed_url = normalize_url(embed_url).sub(%r{^https?\://}, "") + embed_url = normalize_url(embed_url).sub(%r{\Ahttps?\://}, "") TopicEmbed.where("embed_url ~* ?", "^https?://#{Regexp.escape(embed_url)}$").pluck_first( :topic_id, ) diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index 03bc3587b45..05d35f55ae6 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -175,7 +175,7 @@ class TopicLink < ActiveRecord::Base lookup = {} results.each do |tl| - normalized = tl.url.downcase.sub(%r{^https?://}, "").sub(%r{/$}, "") + normalized = tl.url.downcase.sub(%r{\Ahttps?://}, "").sub(%r{/\z}, "") lookup[normalized] = { domain: tl.domain, username: tl.user.username_lower, diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index a1f121ee844..751493dbd54 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -21,9 +21,9 @@ class TopicLinkClick < ActiveRecord::Base uri = UrlHelper.relaxed_parse(url) urls = Set.new urls << url - if url =~ /^http/ - urls << url.sub(/^https/, "http") - urls << url.sub(/^http:/, "https:") + if url =~ /\Ahttp/ + urls << url.sub(/\Ahttps/, "http") + urls << url.sub(/\Ahttp:/, "https:") urls << UrlHelper.schemaless(url) end urls << UrlHelper.absolute_without_cdn(url) @@ -90,7 +90,7 @@ class TopicLinkClick < ActiveRecord::Base # If no link is found... unless link.present? # ... return the url for relative links or when using the same host - return url if url =~ %r{^/[^/]} || uri.try(:host) == Discourse.current_hostname + return url if url =~ %r{\A/[^/]} || uri.try(:host) == Discourse.current_hostname # If we have it somewhere else on the site, just allow the redirect. # This is likely due to a onebox of another topic. diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb index 73137243ed0..eddaac89831 100644 --- a/app/models/translation_override.rb +++ b/app/models/translation_override.rb @@ -147,7 +147,7 @@ class TranslationOverride < ActiveRecord::Base end def transform_pluralized_key(key) - match = key.match(/(.*)\.(zero|two|few|many)$/) + match = key.match(/(.*)\.(zero|two|few|many)\z/) match ? match.to_a.second + ".other" : key end end diff --git a/app/models/upload.rb b/app/models/upload.rb index 964ef52d1dd..86bf760b8da 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -263,7 +263,7 @@ class Upload < ActiveRecord::Base end def local? - !(url =~ %r{^(https?:)?//}) + !(url =~ %r{\A(https?:)?//}) end def fix_dimensions! @@ -526,7 +526,7 @@ class Upload < ActiveRecord::Base # keep track of the url previous_url = upload.url.dup # where is the file currently stored? - external = previous_url =~ %r{^//} + external = previous_url =~ %r{\A//} # download if external if external url = SiteSetting.scheme + ":" + previous_url diff --git a/app/models/user.rb b/app/models/user.rb index e4e8faad86b..5bc5304b2fd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -396,7 +396,7 @@ class User < ActiveRecord::Base .reserved_usernames .unicode_normalize .split("|") - .any? { |reserved| username.match?(/^#{Regexp.escape(reserved).gsub('\*', ".*")}$/) } + .any? { |reserved| username.match?(/\A#{Regexp.escape(reserved).gsub('\*', ".*")}\z/) } end def self.editable_user_custom_fields(by_staff: false) @@ -1117,7 +1117,7 @@ class User < ActiveRecord::Base # TODO it may be worth caching this in a distributed cache, should be benched if SiteSetting.external_system_avatars_enabled url = SiteSetting.external_system_avatars_url.dup - url = +"#{Discourse.base_path}#{url}" unless url =~ %r{^https?://} + url = +"#{Discourse.base_path}#{url}" unless url =~ %r{\Ahttps?://} url.gsub! "{color}", letter_avatar_color(normalized_username) url.gsub! "{username}", UrlHelper.encode_component(username) url.gsub! "{first_letter}", diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index 2ed6d1b0279..edd8ade93b9 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -45,7 +45,7 @@ class UserProfile < ActiveRecord::Base def bio_excerpt(length = 350, opts = {}) return nil if bio_cooked.blank? - excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/
$/, "") + excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/
\z/, "") return excerpt if excerpt.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?) PrettyText.strip_links(excerpt) end diff --git a/app/models/username_validator.rb b/app/models/username_validator.rb index 8c82653b205..842b676356a 100644 --- a/app/models/username_validator.rb +++ b/app/models/username_validator.rb @@ -40,13 +40,13 @@ class UsernameValidator errors.empty? end - CONFUSING_EXTENSIONS ||= /\.(js|json|css|htm|html|xml|jpg|jpeg|png|gif|bmp|ico|tif|tiff|woff)$/i + CONFUSING_EXTENSIONS ||= /\.(js|json|css|htm|html|xml|jpg|jpeg|png|gif|bmp|ico|tif|tiff|woff)\z/i MAX_CHARS ||= 60 ASCII_INVALID_CHAR_PATTERN ||= /[^\w.-]/ UNICODE_INVALID_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}._-]/ - INVALID_LEADING_CHAR_PATTERN ||= /^[^\p{Alnum}\p{M}_]+/ - INVALID_TRAILING_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}]+$/ + INVALID_LEADING_CHAR_PATTERN ||= /\A[^\p{Alnum}\p{M}_]+/ + INVALID_TRAILING_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}]+\z/ REPEATED_SPECIAL_CHAR_PATTERN ||= /[-_.]{2,}/ private diff --git a/app/models/watched_word.rb b/app/models/watched_word.rb index e493f50e416..3e9b4d5b575 100644 --- a/app/models/watched_word.rb +++ b/app/models/watched_word.rb @@ -19,7 +19,7 @@ class WatchedWord < ActiveRecord::Base before_validation do self.word = self.class.normalize_word(self.word) - if self.action == WatchedWord.actions[:link] && !(self.replacement =~ %r{^https?://}) + if self.action == WatchedWord.actions[:link] && !(self.replacement =~ %r{\Ahttps?://}) self.replacement = "#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}" end diff --git a/app/serializers/user_card_serializer.rb b/app/serializers/user_card_serializer.rb index d2c32facdc9..301082cab61 100644 --- a/app/serializers/user_card_serializer.rb +++ b/app/serializers/user_card_serializer.rb @@ -113,7 +113,7 @@ class UserCardSerializer < BasicUserSerializer end return if uri.nil? || uri.host.nil? - uri.host.sub(/^www\./, "") + uri.path + uri.host.sub(/\Awww\./, "") + uri.path end def ignored diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb index a17ac7b34ba..ea1c1b8e89c 100644 --- a/app/services/search_indexer.rb +++ b/app/services/search_indexer.rb @@ -50,7 +50,7 @@ class SearchIndexer .reduce(additional_lexemes) do |array, (lexeme, _, positions)| count = 0 - if lexeme !~ /^(\d+\.)?(\d+\.)*(\*|\d+)$/ + if lexeme !~ /\A(\d+\.)?(\d+\.)*(\*|\d+)\z/ loop do count += 1 break if count >= 10 # Safeguard here to prevent infinite loop when a term has many dots diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index 511755628f4..6a569b6a532 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -347,6 +347,6 @@ class UserUpdater def format_url(website) return nil if website.blank? - website =~ /^http/ ? website : "http://#{website}" + website =~ /\Ahttp/ ? website : "http://#{website}" end end diff --git a/lib/admin_user_index_query.rb b/lib/admin_user_index_query.rb index 49564110bfc..fd9c364b3b3 100644 --- a/lib/admin_user_index_query.rb +++ b/lib/admin_user_index_query.rb @@ -50,7 +50,7 @@ class AdminUserIndexQuery custom_order = params[:order] if custom_order.present? && - without_dir = SORTABLE_MAPPING[custom_order.downcase.sub(/ (asc|desc)$/, "")] + without_dir = SORTABLE_MAPPING[custom_order.downcase.sub(/ (asc|desc)\z/, "")] order << "#{without_dir} #{custom_direction}" end diff --git a/lib/autospec/manager.rb b/lib/autospec/manager.rb index 87dded1963c..fba758be265 100644 --- a/lib/autospec/manager.rb +++ b/lib/autospec/manager.rb @@ -153,7 +153,7 @@ class Autospec::Manager filename, _ = failed_specs[0].split(":") if filename && File.exist?(filename) && !File.directory?(filename) spec = File.read(filename) - start, _ = spec.split(/\S*#focus\S*$/) + start, _ = spec.split(/\S*#focus\S*\z/) if start.length < spec.length line = start.scan(/\n/).length + 1 puts "Found #focus tag on line #{line}!" @@ -194,7 +194,7 @@ class Autospec::Manager def listen_for_changes puts "@@@@@@@@@@@@ listen_for_changes" if @debug - options = { ignore: %r{^lib/autospec} } + options = { ignore: %r{\Alib/autospec} } if @opts[:force_polling] options[:force_polling] = true @@ -216,7 +216,7 @@ class Autospec::Manager # process_change can acquire a mutex and block # the acceptor Thread.new do - if file =~ /(es6|js)$/ + if file =~ /(es6|js)\z/ process_change([[file]]) else process_change([[file, line]]) diff --git a/lib/autospec/reload_css.rb b/lib/autospec/reload_css.rb index 78258bed17f..f8fb530aa8e 100644 --- a/lib/autospec/reload_css.rb +++ b/lib/autospec/reload_css.rb @@ -10,11 +10,11 @@ class Autospec::ReloadCss end # css, scss, sass or handlebars - watch(/\.css$/) - watch(/\.ca?ss\.erb$/) - watch(/\.s[ac]ss$/) - watch(/\.hbs$/) - watch(/\.hbr$/) + watch(/\.css\z/) + watch(/\.ca?ss\.erb\z/) + watch(/\.s[ac]ss\z/) + watch(/\.hbs\z/) + watch(/\.hbr\z/) def self.message_bus MessageBus::Instance.new.tap do |bus| @@ -44,7 +44,7 @@ class Autospec::ReloadCss p = p.sub(/\.sass\.erb/, "") p = p.sub(/\.sass/, "") p = p.sub(/\.scss/, "") - p = p.sub(%r{^app/assets/stylesheets}, "assets") + p = p.sub(%r{\Aapp/assets/stylesheets}, "assets") { name: p, hash: hash || SecureRandom.hex } end message_bus.publish "/file-change", paths diff --git a/lib/autospec/rspec_runner.rb b/lib/autospec/rspec_runner.rb index 408b6fe79e0..e5efa38e2ea 100644 --- a/lib/autospec/rspec_runner.rb +++ b/lib/autospec/rspec_runner.rb @@ -11,29 +11,31 @@ module Autospec end # Discourse specific - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/components/#{m[1]}_spec.rb" } + watch(%r{\Alib/(.+)\.rb\z}) { |m| "spec/components/#{m[1]}_spec.rb" } - watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - watch(%r{^app/(.+)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^spec/support/.+\.rb$}) { "spec" } + watch(%r{\Aapp/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" } + watch(%r{\Aapp/(.+)(\.erb|\.haml)\z}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } + watch(%r{\Aspec/.+_spec\.rb\z}) + watch(%r{\Aspec/support/.+\.rb\z}) { "spec" } watch("app/controllers/application_controller.rb") { "spec/requests" } watch(%r{app/controllers/(.+).rb}) { |m| "spec/requests/#{m[1]}_spec.rb" } - watch(%r{^app/views/(.+)/.+\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } + watch(%r{\Aapp/views/(.+)/.+\.(erb|haml)\z}) { |m| "spec/requests/#{m[1]}_spec.rb" } - watch(%r{^spec/fabricators/.+_fabricator\.rb$}) { "spec" } + watch(%r{\Aspec/fabricators/.+_fabricator\.rb\z}) { "spec" } - watch(%r{^app/assets/javascripts/pretty-text/.*\.js\.es6$}) do + watch(%r{\Aapp/assets/javascripts/pretty-text/.*\.js\.es6\z}) do + "spec/components/pretty_text_spec.rb" + end + watch(%r{\Aplugins/.*/discourse-markdown/.*\.js\.es6\z}) do "spec/components/pretty_text_spec.rb" end - watch(%r{^plugins/.*/discourse-markdown/.*\.js\.es6$}) { "spec/components/pretty_text_spec.rb" } - watch(%r{^plugins/.*/spec/.*\.rb}) - watch(%r{^(plugins/.*/)plugin\.rb}) { |m| "#{m[1]}spec" } - watch(%r{^(plugins/.*)/(lib|app)}) { |m| "#{m[1]}/spec/integration" } - watch(%r{^(plugins/.*)/lib/(.*)\.rb}) { |m| "#{m[1]}/spec/lib/#{m[2]}_spec.rb" } + watch(%r{\Aplugins/.*/spec/.*\.rb}) + watch(%r{\A(plugins/.*/)plugin\.rb}) { |m| "#{m[1]}spec" } + watch(%r{\A(plugins/.*)/(lib|app)}) { |m| "#{m[1]}/spec/integration" } + watch(%r{\A(plugins/.*)/lib/(.*)\.rb}) { |m| "#{m[1]}/spec/lib/#{m[2]}_spec.rb" } RELOADERS = Set.new def self.reload(pattern) diff --git a/lib/autospec/simple_runner.rb b/lib/autospec/simple_runner.rb index dcf88e44434..09f813f877c 100644 --- a/lib/autospec/simple_runner.rb +++ b/lib/autospec/simple_runner.rb @@ -29,7 +29,7 @@ module Autospec # launch rspec Dir.chdir(Rails.root) do # rubocop:disable Discourse/NoChdir because this is not part of the app env = { "RAILS_ENV" => "test" } - if specs.split(" ").any? { |s| s =~ %r{^(./)?plugins} } + if specs.split(" ").any? { |s| s =~ %r{\A(./)?plugins} } env["LOAD_PLUGINS"] = "1" puts "Loading plugins while running specs" end diff --git a/lib/backup_restore/backup_file_handler.rb b/lib/backup_restore/backup_file_handler.rb index 645fad9f64d..19d79f20e99 100644 --- a/lib/backup_restore/backup_file_handler.rb +++ b/lib/backup_restore/backup_file_handler.rb @@ -11,7 +11,7 @@ module BackupRestore @filename = filename @current_db = current_db @root_tmp_directory = root_tmp_directory - @is_archive = !(@filename =~ /\.sql\.gz$/) + @is_archive = !(@filename =~ /\.sql\.gz\z/) @store_location = location end diff --git a/lib/backup_restore/database_restorer.rb b/lib/backup_restore/database_restorer.rb index 2a3bc6985c7..e913ba99324 100644 --- a/lib/backup_restore/database_restorer.rb +++ b/lib/backup_restore/database_restorer.rb @@ -164,7 +164,7 @@ module BackupRestore DatabaseRestorer.core_migration_files.each do |path| require path - class_name = File.basename(path, ".rb").sub(/^\d+_/, "").camelize + class_name = File.basename(path, ".rb").sub(/\A\d+_/, "").camelize migration_class = class_name.constantize if migration_class.const_defined?(:DROPPED_TABLES) diff --git a/lib/backup_restore/s3_backup_store.rb b/lib/backup_restore/s3_backup_store.rb index 10e2798642a..2237ccfa8b8 100644 --- a/lib/backup_restore/s3_backup_store.rb +++ b/lib/backup_restore/s3_backup_store.rb @@ -173,7 +173,7 @@ module BackupRestore path = Regexp.quote(path) end - %r{^#{path}[^/]*\.t?gz$}i + %r{\A#{path}[^/]*\.t?gz\z}i end end diff --git a/lib/composer_messages_finder.rb b/lib/composer_messages_finder.rb index 878727f20c9..e394bb39840 100644 --- a/lib/composer_messages_finder.rb +++ b/lib/composer_messages_finder.rb @@ -8,7 +8,7 @@ class ComposerMessagesFinder end def self.check_methods - @check_methods ||= instance_methods.find_all { |m| m =~ /^check\_/ } + @check_methods ||= instance_methods.find_all { |m| m =~ /\Acheck\_/ } end def find diff --git a/lib/compression/gzip.rb b/lib/compression/gzip.rb index c668b088f72..f32836f957e 100644 --- a/lib/compression/gzip.rb +++ b/lib/compression/gzip.rb @@ -38,7 +38,7 @@ module Compression def build_entry_path(dest_path, _, compressed_file_path) basename = File.basename(compressed_file_path) - basename.gsub!(/#{Regexp.escape(extension)}$/, "") + basename.gsub!(/#{Regexp.escape(extension)}\z/, "") File.join(dest_path, basename) end diff --git a/lib/content_security_policy/extension.rb b/lib/content_security_policy/extension.rb index 150e0048622..36a3ed8df55 100644 --- a/lib/content_security_policy/extension.rb +++ b/lib/content_security_policy/extension.rb @@ -80,7 +80,7 @@ class ContentSecurityPolicy uri.query = nil # CSP should not include query part of url - uri_string = uri.to_s.sub(%r{^//}, "") # Protocol-less CSP should not have // at beginning of URL + uri_string = uri.to_s.sub(%r{\A//}, "") # Protocol-less CSP should not have // at beginning of URL auto_script_src_extension[:script_src] << uri_string rescue URI::Error diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb index 01463bb7761..b811be43ce3 100644 --- a/lib/cooked_post_processor.rb +++ b/lib/cooked_post_processor.rb @@ -242,10 +242,10 @@ class CookedPostProcessor if !cropped && upload.width && resized_w > upload.width cooked_url = UrlHelper.cook_url(upload.url, secure: @post.with_secure_uploads?) - srcset << ", #{cooked_url} #{ratio.to_s.sub(/\.0$/, "")}x" + srcset << ", #{cooked_url} #{ratio.to_s.sub(/\.0\z/, "")}x" elsif t = upload.thumbnail(resized_w, resized_h) cooked_url = UrlHelper.cook_url(t.url, secure: @post.with_secure_uploads?) - srcset << ", #{cooked_url} #{ratio.to_s.sub(/\.0$/, "")}x" + srcset << ", #{cooked_url} #{ratio.to_s.sub(/\.0\z/, "")}x" end img[ @@ -295,7 +295,7 @@ class CookedPostProcessor def get_filename(upload, src) return File.basename(src) unless upload - return upload.original_filename unless upload.original_filename =~ /^blob(\.png)?$/i + return upload.original_filename unless upload.original_filename =~ /\Ablob(\.png)?\z/i I18n.t("upload.pasted_image_filename") end diff --git a/lib/cooked_processor_mixin.rb b/lib/cooked_processor_mixin.rb index bc872b45010..561dedb4f98 100644 --- a/lib/cooked_processor_mixin.rb +++ b/lib/cooked_processor_mixin.rb @@ -174,7 +174,7 @@ module CookedProcessorMixin return @size_cache[url] if @size_cache.has_key?(url) absolute_url = url - absolute_url = Discourse.base_url_no_prefix + absolute_url if absolute_url =~ %r{^/[^/]} + absolute_url = Discourse.base_url_no_prefix + absolute_url if absolute_url =~ %r{\A/[^/]} return unless absolute_url diff --git a/lib/discourse_connect_base.rb b/lib/discourse_connect_base.rb index b5e04d8f212..f39c2556fb6 100644 --- a/lib/discourse_connect_base.rb +++ b/lib/discourse_connect_base.rb @@ -99,7 +99,7 @@ class DiscourseConnectBase end decoded_hash.each do |k, v| - if field = k[/^custom\.(.+)$/, 1] + if field = k[/\Acustom\.(.+)\z/, 1] sso.custom_fields[field] = v end end diff --git a/lib/discourse_diff.rb b/lib/discourse_diff.rb index e31a699f85a..16a27b67472 100644 --- a/lib/discourse_diff.rb +++ b/lib/discourse_diff.rb @@ -160,7 +160,7 @@ class DiscourseDiff while i < text.size if text[i] =~ /\w/ t << text[i] - elsif text[i] =~ /[ \t]/ && t.join =~ /^\w+$/ + elsif text[i] =~ /[ \t]/ && t.join =~ /\A\w+\z/ begin t << text[i] i += 1 diff --git a/lib/discourse_plugin_registry.rb b/lib/discourse_plugin_registry.rb index daaa144b367..40cd5a061c5 100644 --- a/lib/discourse_plugin_registry.rb +++ b/lib/discourse_plugin_registry.rb @@ -158,8 +158,8 @@ class DiscoursePluginRegistry end end - JS_REGEX = /\.js$|\.js\.erb$|\.js\.es6$/ - HANDLEBARS_REGEX = /\.(hb[rs]|js\.handlebars)$/ + JS_REGEX = /\.js$|\.js\.erb$|\.js\.es6\z/ + HANDLEBARS_REGEX = /\.(hb[rs]|js\.handlebars)\z/ def self.register_asset(asset, opts = nil, plugin_directory_name = nil) if asset =~ JS_REGEX @@ -172,7 +172,7 @@ class DiscoursePluginRegistry else self.javascripts << asset end - elsif asset =~ /\.css$|\.scss$/ + elsif asset =~ /\.css$|\.scss\z/ if opts == :mobile self.mobile_stylesheets[plugin_directory_name] ||= Set.new self.mobile_stylesheets[plugin_directory_name] << asset diff --git a/lib/discourse_redis.rb b/lib/discourse_redis.rb index e47e9734c56..ca969f3f284 100644 --- a/lib/discourse_redis.rb +++ b/lib/discourse_redis.rb @@ -276,7 +276,7 @@ class DiscourseRedis def eval(redis, *args, **kwargs) redis.evalsha @sha1, *args, **kwargs rescue ::Redis::CommandError => e - if e.to_s =~ /^NOSCRIPT/ + if e.to_s =~ /\ANOSCRIPT/ redis.eval @script, *args, **kwargs else raise diff --git a/lib/email/message_id_service.rb b/lib/email/message_id_service.rb index c2cdcbbf2b6..e1a9fea0e88 100644 --- a/lib/email/message_id_service.rb +++ b/lib/email/message_id_service.rb @@ -136,7 +136,7 @@ module Email def message_id_clean(message_id) if message_id.present? && is_message_id_rfc?(message_id) - message_id.gsub(/^<|>$/, "") + message_id.gsub(/\A<|>\z/, "") else message_id end diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index 05b51c7eb00..2dcf33f8475 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -381,7 +381,7 @@ module Email @mail[:precedence].to_s[/list|junk|bulk|auto_reply/i] || @mail[:from].to_s[/(mailer[\-_]?daemon|post[\-_]?master|no[\-_]?reply)@/i] || @mail[:subject].to_s[ - /^\s*(Auto:|Automatic reply|Autosvar|Automatisk svar|Automatisch antwoord|Abwesenheitsnotiz|Risposta Non al computer|Automatisch antwoord|Auto Response|Respuesta automática|Fuori sede|Out of Office|Frånvaro|Réponse automatique)/i + /\A\s*(Auto:|Automatic reply|Autosvar|Automatisk svar|Automatisch antwoord|Abwesenheitsnotiz|Risposta Non al computer|Automatisch antwoord|Auto Response|Respuesta automática|Fuori sede|Out of Office|Frånvaro|Réponse automatique)/i ] || @mail.header.to_s[ /auto[\-_]?(response|submitted|replied|reply|generated|respond)|holidayreply|machinegenerated/i @@ -393,7 +393,7 @@ module Email when "X-Spam-Flag" @mail[:x_spam_flag].to_s[/YES/i] when "X-Spam-Status" - @mail[:x_spam_status].to_s[/^Yes, /i] + @mail[:x_spam_status].to_s[/\AYes, /i] when "X-SES-Spam-Verdict" @mail[:x_ses_spam_verdict].to_s[/FAIL/i] else @@ -639,7 +639,7 @@ module Email .uniq @previous_replies_regex ||= - /^--[- ]\n\*(?:#{strings.map { |x| Regexp.escape(x) }.join("|")})\*\n/im + /\A--[- ]\n\*(?:#{strings.map { |x| Regexp.escape(x) }.join("|")})\*\n/im end def reply_above_line_regex @@ -747,12 +747,12 @@ module Email if value[/<[^>]+>/] from_address = value[/<([^>]+)>/, 1] - from_display_name = value[/^([^<]+)/, 1] + from_display_name = value[/\A([^<]+)/, 1] end if (from_address.blank? || !from_address["@"]) && value[/\[mailto:[^\]]+\]/] from_address = value[/\[mailto:([^\]]+)\]/, 1] - from_display_name = value[/^([^\[]+)/, 1] + from_display_name = value[/\A([^\[]+)/, 1] end [from_address&.downcase, from_display_name&.strip] @@ -1016,7 +1016,7 @@ module Email end def has_been_forwarded? - subject[/^[[:blank:]]*(fwd?|tr)[[:blank:]]?:/i] && embedded_email_raw.present? + subject[/\A[[:blank:]]*(fwd?|tr)[[:blank:]]?:/i] && embedded_email_raw.present? end def embedded_email_raw diff --git a/lib/email/styles.rb b/lib/email/styles.rb index ee240b981ef..6482b70eec6 100644 --- a/lib/email/styles.rb +++ b/lib/email/styles.rb @@ -84,9 +84,9 @@ module Email if img["src"] # ensure all urls are absolute - img["src"] = "#{Discourse.base_url}#{img["src"]}" if img["src"][%r{^/[^/]}] + img["src"] = "#{Discourse.base_url}#{img["src"]}" if img["src"][%r{\A/[^/]}] # ensure no schemaless urls - img["src"] = "#{uri.scheme}:#{img["src"]}" if img["src"][%r{^//}] + img["src"] = "#{uri.scheme}:#{img["src"]}" if img["src"][%r{\A//}] end end @@ -110,7 +110,7 @@ module Email .css("a.attachment") .each do |a| # ensure all urls are absolute - a["href"] = "#{Discourse.base_url}#{a["href"]}" if a["href"] =~ %r{^/[^/]} + a["href"] = "#{Discourse.base_url}#{a["href"]}" if a["href"] =~ %r{\A/[^/]} # ensure no schemaless urls a["href"] = "#{uri.scheme}:#{a["href"]}" if a["href"] && a["href"].starts_with?("//") diff --git a/lib/email_cook.rb b/lib/email_cook.rb index 2c76e1f2ff2..8af5b3c5fe6 100644 --- a/lib/email_cook.rb +++ b/lib/email_cook.rb @@ -4,7 +4,7 @@ class EmailCook def self.raw_regexp @raw_regexp ||= - %r{^\[plaintext\]$\n(.*)\n^\[/plaintext\]$(?:\s^\[attachments\]$\n(.*)\n^\[/attachments\]$)?(?:\s^\[elided\]$\n(.*)\n^\[/elided\]$)?}m + %r{\A\[plaintext\]$\n(.*)\n^\[/plaintext\]$(?:\s^\[attachments\]$\n(.*)\n^\[/attachments\]$)?(?:\s^\[elided\]$\n(.*)\n^\[/elided\]$)?}m end def initialize(raw) @@ -14,7 +14,7 @@ class EmailCook def add_quote(result, buffer) if buffer.present? - return if buffer =~ /\A(
)+\z$/ + return if buffer =~ /\A(
)+\z\z/ result << "
#{buffer}
" end end @@ -22,7 +22,7 @@ class EmailCook def link_string!(line, unescaped_line) unescaped_line = unescaped_line.strip line.gsub!(/\S+/) do |str| - if str.match?(%r{^(https?://)[\S]+$}i) + if str.match?(%r{\A(https?://)[\S]+\z}i) begin url = URI.parse(str).to_s if unescaped_line == url @@ -48,11 +48,11 @@ class EmailCook text.each_line do |line| # replace indentation with non-breaking spaces - line.sub!(/^\s{2,}/) { |s| "\u00A0" * s.length } + line.sub!(/\A\s{2,}/) { |s| "\u00A0" * s.length } - if line =~ /^\s*>/ + if line =~ /\A\s*>/ in_quote = true - line.sub!(/^[\s>]*/, "") + line.sub!(/\A[\s>]*/, "") unescaped_line = line line = CGI.escapeHTML(line) diff --git a/lib/file_helper.rb b/lib/file_helper.rb index 9b57251f83c..aeab06d2900 100644 --- a/lib/file_helper.rb +++ b/lib/file_helper.rb @@ -52,7 +52,7 @@ class FileHelper retain_on_max_file_size_exceeded: false ) url = "https:" + url if url.start_with?("//") - raise Discourse::InvalidParameters.new(:url) unless url =~ %r{^https?://} + raise Discourse::InvalidParameters.new(:url) unless url =~ %r{\Ahttps?://} tmp = nil @@ -175,26 +175,26 @@ class FileHelper end def self.supported_video_regexp - @@supported_video_regexp ||= /\.(#{supported_video.to_a.join("|")})$/i + @@supported_video_regexp ||= /\.(#{supported_video.to_a.join("|")})\z/i end def self.supported_audio_regexp - @@supported_audio_regexp ||= /\.(#{supported_audio.to_a.join("|")})$/i + @@supported_audio_regexp ||= /\.(#{supported_audio.to_a.join("|")})\z/i end def self.supported_images_regexp - @@supported_images_regexp ||= /\.(#{supported_images.to_a.join("|")})$/i + @@supported_images_regexp ||= /\.(#{supported_images.to_a.join("|")})\z/i end def self.inline_images_regexp - @@inline_images_regexp ||= /\.(#{inline_images.to_a.join("|")})$/i + @@inline_images_regexp ||= /\.(#{inline_images.to_a.join("|")})\z/i end def self.supported_media_regexp @@supported_media_regexp ||= begin media = supported_images | supported_audio | supported_video - /\.(#{media.to_a.join("|")})$/i + /\.(#{media.to_a.join("|")})\z/i end end @@ -202,7 +202,7 @@ class FileHelper @@supported_playable_media_regexp ||= begin media = supported_audio | supported_video - /\.(#{media.to_a.join("|")})$/i + /\.(#{media.to_a.join("|")})\z/i end end end diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb index f73114ca897..f1a958fb8a5 100644 --- a/lib/file_store/base_store.rb +++ b/lib/file_store/base_store.rb @@ -113,7 +113,7 @@ module FileStore end ) - url = SiteSetting.scheme + ":" + url if url =~ %r{^//} + url = SiteSetting.scheme + ":" + url if url =~ %r{\A//} file = FileHelper.download( url, diff --git a/lib/file_store/local_store.rb b/lib/file_store/local_store.rb index c0461aa50a7..3a6f1137557 100644 --- a/lib/file_store/local_store.rb +++ b/lib/file_store/local_store.rb @@ -128,7 +128,7 @@ module FileStore count = 0 model.find_each do |upload| # could be a remote image - next unless upload.url =~ %r{^/[^/]} + next unless upload.url =~ %r{\A/[^/]} path = "#{public_dir}#{upload.url}" bad = true diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb index 8fdbd2d9fab..03d181467bb 100644 --- a/lib/file_store/s3_store.rb +++ b/lib/file_store/s3_store.rb @@ -216,7 +216,7 @@ module FileStore def path_for(upload) url = upload&.url - FileStore::LocalStore.new.path_for(upload) if url && url[%r{^/[^/]}] + FileStore::LocalStore.new.path_for(upload) if url && url[%r{\A/[^/]}] end def url_for(upload, force_download: false) @@ -233,7 +233,7 @@ module FileStore def cdn_url(url) return url if SiteSetting.Upload.s3_cdn_url.blank? - schema = url[%r{^(https?:)?//}, 1] + schema = url[%r{\A(https?:)?//}, 1] folder = s3_bucket_folder_path.nil? ? "" : "#{s3_bucket_folder_path}/" url.sub( File.join("#{schema}#{absolute_base_url}", folder), diff --git a/lib/freedom_patches/translate_accelerator.rb b/lib/freedom_patches/translate_accelerator.rb index b13464012b5..a95ab7275c0 100644 --- a/lib/freedom_patches/translate_accelerator.rb +++ b/lib/freedom_patches/translate_accelerator.rb @@ -39,7 +39,7 @@ module I18n if @loaded_locales.empty? # load all rb files - I18n.backend.load_translations(I18n.load_path.grep(/\.rb$/)) + I18n.backend.load_translations(I18n.load_path.grep(/\.rb\z/)) # load plural rules from plugins DiscoursePluginRegistry.locales.each do |plugin_locale, options| @@ -50,14 +50,14 @@ module I18n end # load it - I18n.backend.load_translations(I18n.load_path.grep(/\.#{Regexp.escape locale}\.yml$/)) + I18n.backend.load_translations(I18n.load_path.grep(/\.#{Regexp.escape locale}\.yml\z/)) if Discourse.allow_dev_populate? I18n.backend.load_translations( - I18n.load_path.grep(%r{.*faker.*/#{Regexp.escape locale}\.yml$}), + I18n.load_path.grep(%r{.*faker.*/#{Regexp.escape locale}\.yml\z}), ) I18n.backend.load_translations( - I18n.load_path.grep(%r{.*faker.*/#{Regexp.escape locale}/.*\.yml$}), + I18n.load_path.grep(%r{.*faker.*/#{Regexp.escape locale}/.*\.yml\z}), ) end diff --git a/lib/git_url.rb b/lib/git_url.rb index 9410c1d3f86..d1980a94de7 100644 --- a/lib/git_url.rb +++ b/lib/git_url.rb @@ -10,7 +10,7 @@ module GitUrl end if url.start_with?("https://github.com/") && !url.end_with?(".git") - url = url.gsub(%r{/$}, "") + url = url.gsub(%r{/\z}, "") url += ".git" end diff --git a/lib/global_path.rb b/lib/global_path.rb index 318aa346f30..fb0ad36239e 100644 --- a/lib/global_path.rb +++ b/lib/global_path.rb @@ -12,7 +12,7 @@ module GlobalPath def upload_cdn_path(p) p = Discourse.store.cdn_url(p) if SiteSetting.Upload.s3_cdn_url.present? - (p =~ /^http/ || p =~ %r{^//}) ? p : cdn_path(p) + (p =~ /\Ahttp/ || p =~ %r{\A//}) ? p : cdn_path(p) end def cdn_relative_path(path) diff --git a/lib/guardian/ensure_magic.rb b/lib/guardian/ensure_magic.rb index 62cece83b61..3a2dbdea657 100644 --- a/lib/guardian/ensure_magic.rb +++ b/lib/guardian/ensure_magic.rb @@ -3,7 +3,7 @@ # Support for ensure_{blah}! methods. module EnsureMagic def method_missing(method, *args, &block) - if method.to_s =~ /^ensure_(.*)\!$/ + if method.to_s =~ /\Aensure_(.*)\!\z/ can_method = :"#{Regexp.last_match[1]}?" if respond_to?(can_method) diff --git a/lib/html_prettify.rb b/lib/html_prettify.rb index 074a3c8a2a5..f56665a96a8 100644 --- a/lib/html_prettify.rb +++ b/lib/html_prettify.rb @@ -249,8 +249,8 @@ class HtmlPrettify < String # Special case if the very first character is a quote followed by # punctuation at a non-word-break. Close the quotes by brute # force: - str.gsub!(/^'(?=#{punct_class}\B)/, entity(:single_right_quote)) - str.gsub!(/^"(?=#{punct_class}\B)/, entity(:double_right_quote)) + str.gsub!(/\A'(?=#{punct_class}\B)/, entity(:single_right_quote)) + str.gsub!(/\A"(?=#{punct_class}\B)/, entity(:double_right_quote)) # Special case for double sets of quotes, e.g.: #

He said, "'Quoted' words in a larger quote."

diff --git a/lib/i18n/locale_file_checker.rb b/lib/i18n/locale_file_checker.rb index de9eae3056c..e7d71ece03f 100644 --- a/lib/i18n/locale_file_checker.rb +++ b/lib/i18n/locale_file_checker.rb @@ -49,7 +49,7 @@ class LocaleFileChecker end def reference_file(path) - path = path.gsub(/\.\w{2,}\.yml$/, ".#{REFERENCE_LOCALE}.yml") + path = path.gsub(/\.\w{2,}\.yml\z/, ".#{REFERENCE_LOCALE}.yml") path if File.exist?(path) end diff --git a/lib/middleware/anonymous_cache.rb b/lib/middleware/anonymous_cache.rb index 553113e7899..a62f4e53cfc 100644 --- a/lib/middleware/anonymous_cache.rb +++ b/lib/middleware/anonymous_cache.rb @@ -25,8 +25,8 @@ module Middleware def self.compile_key_builder method = +"def self.__compiled_key_builder(h)\n \"" cache_key_segments.each do |k, v| - raise "Invalid key name" unless k =~ /^[a-z]+$/ - raise "Invalid method name" unless v =~ /^key_[a-z_\?]+$/ + raise "Invalid key name" unless k =~ /\A[a-z]+\z/ + raise "Invalid method name" unless v =~ /\Akey_[a-z_\?]+\z/ method << "|#{k}=#\{h.#{v}}" end method << "\"\nend" diff --git a/lib/middleware/missing_avatars.rb b/lib/middleware/missing_avatars.rb index 958aecaa3ee..b0f6d18a14d 100644 --- a/lib/middleware/missing_avatars.rb +++ b/lib/middleware/missing_avatars.rb @@ -11,7 +11,7 @@ module Middleware end def call(env) - if (env["REQUEST_PATH"] =~ %r{^/uploads/default/avatars}) + if (env["REQUEST_PATH"] =~ %r{\A/uploads/default/avatars}) path = "#{Rails.root}/public#{env["REQUEST_PATH"]}" unless File.exist?(path) default_image = "#{Rails.root}/public/images/d-logo-sketch-small.png" diff --git a/lib/migration/safe_migrate.rb b/lib/migration/safe_migrate.rb index ce3013300e0..26e093c3036 100644 --- a/lib/migration/safe_migrate.rb +++ b/lib/migration/safe_migrate.rb @@ -116,7 +116,7 @@ class Migration::SafeMigrate end def self.protect!(sql) - if sql =~ /^\s*(?:drop\s+table|alter\s+table.*rename\s+to)\s+/i + if sql =~ /\A\s*(?:drop\s+table|alter\s+table.*rename\s+to)\s+/i $stdout.puts("", <<~TEXT) WARNING ------------------------------------------------------------------------------------- @@ -129,7 +129,7 @@ class Migration::SafeMigrate in use by live applications. TEXT raise Discourse::InvalidMigration, "Attempt was made to drop a table" - elsif sql =~ /^\s*alter\s+table.*(?:rename|drop)\s+/i + elsif sql =~ /\A\s*alter\s+table.*(?:rename|drop)\s+/i $stdout.puts("", <<~TEXT) WARNING ------------------------------------------------------------------------------------- diff --git a/lib/onebox/engine.rb b/lib/onebox/engine.rb index 838986e685b..94567ad3623 100644 --- a/lib/onebox/engine.rb +++ b/lib/onebox/engine.rb @@ -7,7 +7,7 @@ module Onebox end def self.engines - constants.select { |constant| constant.to_s =~ /Onebox$/ }.sort.map(&method(:const_get)) + constants.select { |constant| constant.to_s =~ /Onebox\z/ }.sort.map(&method(:const_get)) end def self.all_iframe_origins diff --git a/lib/onebox/layout.rb b/lib/onebox/layout.rb index e6e31daa6f9..53c63bb834f 100644 --- a/lib/onebox/layout.rb +++ b/lib/onebox/layout.rb @@ -15,7 +15,7 @@ module Onebox @record = Onebox::Helpers.symbolize_keys(record) # Fix any relative paths - if @record[:image] && @record[:image] =~ %r{^/[^/]} + if @record[:image] && @record[:image] =~ %r{\A/[^/]} @record[:image] = "#{uri.scheme}://#{uri.host}/#{@record[:image]}" end @@ -40,7 +40,7 @@ module Onebox link: record[:link], title: record[:title], favicon: record[:favicon], - domain: record[:domain] || uri.host.to_s.sub(/^www\./, ""), + domain: record[:domain] || uri.host.to_s.sub(/\Awww\./, ""), article_published_time: record[:article_published_time], article_published_time_title: record[:article_published_time_title], metadata_1_label: record[:metadata_1_label], diff --git a/lib/onebox/mixins/git_blob_onebox.rb b/lib/onebox/mixins/git_blob_onebox.rb index 511a6b60849..713606d687f 100644 --- a/lib/onebox/mixins/git_blob_onebox.rb +++ b/lib/onebox/mixins/git_blob_onebox.rb @@ -119,7 +119,7 @@ module Onebox a_lines = str.lines a_lines.each do |l| l = l.chomp("\n") # remove new line - m = l.match(/^[ ]*/) # find leading spaces 0 or more + m = l.match(/\A[ ]*/) # find leading spaces 0 or more unless m.nil? || l.size == m[0].size || l.size == 0 # no match | only spaces in line | empty line m_str_length = m[0].size if m_str_length <= 1 # minimum space is 1 or nothing we can break we found our minimum @@ -166,7 +166,7 @@ module Onebox @file = m[:file] @lang = Onebox::FileTypeFinder.from_file_name(m[:file]) - if @lang == "stl" && link.match?(%r{^https?://(www\.)?github\.com.*/blob/}) + if @lang == "stl" && link.match?(%r{\Ahttps?://(www\.)?github\.com.*/blob/}) @model_file = @lang.dup @raw = "https://render.githubusercontent.com/view/solid?url=" + self.raw_template(m) else diff --git a/lib/onebox/open_graph.rb b/lib/onebox/open_graph.rb index 8ff754fc520..10c1f165d9c 100644 --- a/lib/onebox/open_graph.rb +++ b/lib/onebox/open_graph.rb @@ -32,8 +32,8 @@ module Onebox doc .css("meta") .each do |m| - if (m["property"] && m["property"][/^(?:og|article|product):(.+)$/i]) || - (m["name"] && m["name"][/^(?:og|article|product):(.+)$/i]) + if (m["property"] && m["property"][/\A(?:og|article|product):(.+)\z/i]) || + (m["name"] && m["name"][/\A(?:og|article|product):(.+)\z/i]) value = (m["content"] || m["value"]).to_s next if Onebox::Helpers.blank?(value) key = $1.tr("-:", "_").to_sym diff --git a/lib/onebox/sanitize_config.rb b/lib/onebox/sanitize_config.rb index d0ea4ed417a..77f97ee9257 100644 --- a/lib/onebox/sanitize_config.rb +++ b/lib/onebox/sanitize_config.rb @@ -58,7 +58,7 @@ module Onebox next unless env[:node_name] == "a" a_tag = env[:node] a_tag["href"] ||= "#" - if a_tag["href"] =~ %r{^(?:[a-z]+:)?//} + if a_tag["href"] =~ %r{\A(?:[a-z]+:)?//} a_tag["rel"] = "nofollow ugc noopener" else a_tag.remove_attribute("target") diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index 2427b9b9b3c..6d6bd34f14f 100644 --- a/lib/oneboxer.rb +++ b/lib/oneboxer.rb @@ -6,8 +6,8 @@ Dir["#{Rails.root}/lib/onebox/engine/*_onebox.rb"].sort.each { |f| require f } module Oneboxer ONEBOX_CSS_CLASS = "onebox" - AUDIO_REGEX = /^\.(mp3|og[ga]|opus|wav|m4[abpr]|aac|flac)$/i - VIDEO_REGEX = /^\.(mov|mp4|webm|m4v|3gp|ogv|avi|mpeg|ogv)$/i + AUDIO_REGEX = /\A\.(mp3|og[ga]|opus|wav|m4[abpr]|aac|flac)\z/i + VIDEO_REGEX = /\A\.(mov|mp4|webm|m4v|3gp|ogv|avi|mpeg|ogv)\z/i # keep reloaders happy unless defined?(Oneboxer::Result) diff --git a/lib/plain_text_to_markdown.rb b/lib/plain_text_to_markdown.rb index 8e582e6f365..d914cd867e5 100644 --- a/lib/plain_text_to_markdown.rb +++ b/lib/plain_text_to_markdown.rb @@ -100,7 +100,7 @@ class PlainTextToMarkdown # @param line [Line] def remove_quote_level_indicators!(line) - match_data = line.text.match(/^(?>+)\s?(?.*)/) + match_data = line.text.match(/\A(?>+)\s?(?.*)/) if match_data line.text = match_data[:text] @@ -128,7 +128,7 @@ class PlainTextToMarkdown def classify_line_as_code!(line, previous_line) line.code_block = previous_line.code_block unless previous_line.nil? || previous_line.valid_code_block? - return unless line.text =~ /^\s{0,3}```/ + return unless line.text =~ /\A\s{0,3}```/ if line.code_block.present? line.code_block.end_line = line @@ -173,7 +173,7 @@ class PlainTextToMarkdown end def indent_with_non_breaking_spaces(text) - text.sub(/^\s+/) do |s| + text.sub(/\A\s+/) do |s| # replace tabs with 2 spaces s.gsub!("\t", " ") diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index 743f35283e5..77f9e4ff2c7 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -675,17 +675,17 @@ class Plugin::Instance DiscoursePluginRegistry.register_glob(admin_path, "hbr", admin: true) DiscourseJsProcessor.plugin_transpile_paths << root_path.sub(Rails.root.to_s, "").sub( - %r{^/*}, + %r{\A/*}, "", ) DiscourseJsProcessor.plugin_transpile_paths << admin_path.sub(Rails.root.to_s, "").sub( - %r{^/*}, + %r{\A/*}, "", ) test_path = "#{root_dir_name}/test/javascripts" DiscourseJsProcessor.plugin_transpile_paths << test_path.sub(Rails.root.to_s, "").sub( - %r{^/*}, + %r{\A/*}, "", ) end @@ -1299,7 +1299,7 @@ class Plugin::Instance private def validate_directory_column_name(column_name) - match = /^[_a-z]+$/.match(column_name) + match = /\A[_a-z]+\z/.match(column_name) unless match raise "Invalid directory column name '#{column_name}'. Can only contain a-z and underscores" end diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb index 56a4f0349ff..e041c4fb8f1 100644 --- a/lib/pretty_text.rb +++ b/lib/pretty_text.rb @@ -48,7 +48,7 @@ module PrettyText filename = find_file(root_path, part_name) if filename source = File.read("#{root_path}#{filename}") - source = ERB.new(source).result(binding) if filename =~ /\.erb$/ + source = ERB.new(source).result(binding) if filename =~ /\.erb\z/ transpiler = DiscourseJsProcessor::Transpiler.new transpiled = transpiler.perform(source, "#{Rails.root}/app/assets/javascripts/", part_name) @@ -64,7 +64,7 @@ module PrettyText def self.ctx_load_directory(ctx, path) root_path = "#{Rails.root}/app/assets/javascripts/" Dir["#{root_path}#{path}/**/*"].sort.each do |f| - apply_es6_file(ctx, root_path, f.sub(root_path, "").sub(/\.js(.es6)?$/, "")) + apply_es6_file(ctx, root_path, f.sub(root_path, "").sub(/\.js(.es6)?\z/, "")) end end @@ -116,9 +116,9 @@ module PrettyText to_load << a if File.file?(a) && a =~ /discourse-markdown/ end to_load.uniq.each do |f| - if f =~ %r{^.+assets/javascripts/} + if f =~ %r{\A.+assets/javascripts/} root = Regexp.last_match[0] - apply_es6_file(ctx, root, f.sub(root, "").sub(/\.js(\.es6)?$/, "")) + apply_es6_file(ctx, root, f.sub(root, "").sub(/\.js(\.es6)?\z/, "")) end end diff --git a/lib/pretty_text/helpers.rb b/lib/pretty_text/helpers.rb index 621f59d7f8e..2ca864fb4ab 100644 --- a/lib/pretty_text/helpers.rb +++ b/lib/pretty_text/helpers.rb @@ -102,7 +102,7 @@ module PrettyText # TODO (martin) Remove this when everything is using hashtag_lookup # after enable_experimental_hashtag_autocomplete is default. def category_tag_hashtag_lookup(text) - is_tag = text =~ /#{TAG_HASHTAG_POSTFIX}$/ + is_tag = text =~ /#{TAG_HASHTAG_POSTFIX}\z/ if !is_tag && category = Category.query_from_hashtag_slug(text) [category.url, text] diff --git a/lib/require_dependency_backward_compatibility.rb b/lib/require_dependency_backward_compatibility.rb index 5b550e45d37..b5e3e0248d7 100644 --- a/lib/require_dependency_backward_compatibility.rb +++ b/lib/require_dependency_backward_compatibility.rb @@ -14,7 +14,7 @@ module RequireDependencyBackwardCompatibility def require_dependency(filename) name = filename.to_s return if name == "jobs/base" - return super(name.sub(%r{^lib/}, "")) if name.start_with?("lib/") + return super(name.sub(%r{\Alib/}, "")) if name.start_with?("lib/") super end diff --git a/lib/retrieve_title.rb b/lib/retrieve_title.rb index 826792650f4..ddbeb628386 100644 --- a/lib/retrieve_title.rb +++ b/lib/retrieve_title.rb @@ -32,7 +32,7 @@ module RetrieveTitle # A horrible hack - YouTube uses `document.title` to populate the title # for some reason. For any other site than YouTube this wouldn't be worth it. if title == "YouTube" && html =~ /document\.title *= *"(.*)";/ - title = Regexp.last_match[1].sub(/ - YouTube$/, "") + title = Regexp.last_match[1].sub(/ - YouTube\z/, "") end if !title && node = doc.at('meta[property="og:title"]') @@ -53,11 +53,12 @@ module RetrieveTitle def self.max_chunk_size(uri) # Exception for sites that leave the title until very late. - if uri.host =~ /(^|\.)amazon\.(com|ca|co\.uk|es|fr|de|it|com\.au|com\.br|cn|in|co\.jp|com\.mx)$/ + if uri.host =~ + /(^|\.)amazon\.(com|ca|co\.uk|es|fr|de|it|com\.au|com\.br|cn|in|co\.jp|com\.mx)\z/ return 500 end - return 300 if uri.host =~ /(^|\.)youtube\.com$/ || uri.host =~ /(^|\.)youtu\.be$/ - return 50 if uri.host =~ /(^|\.)github\.com$/ + return 300 if uri.host =~ /(^|\.)youtube\.com\z/ || uri.host =~ /(^|\.)youtu\.be\z/ + return 50 if uri.host =~ /(^|\.)github\.com\z/ # default is 20k 20 diff --git a/lib/route_matcher.rb b/lib/route_matcher.rb index 4e4fd028d1b..2c466055556 100644 --- a/lib/route_matcher.rb +++ b/lib/route_matcher.rb @@ -46,7 +46,7 @@ class RouteMatcher return true if actions.nil? # actions are unrestricted # message_bus is not a rails route, special handling - return true if actions.include?("message_bus") && request.fullpath =~ %r{^/message-bus/.*/poll} + return true if actions.include?("message_bus") && request.fullpath =~ %r{\A/message-bus/.*/poll} path_params = path_params_from_request(request) actions.include? "#{path_params[:controller]}##{path_params[:action]}" diff --git a/lib/s3_inventory.rb b/lib/s3_inventory.rb index 91da944b927..3b9f4391e77 100644 --- a/lib/s3_inventory.rb +++ b/lib/s3_inventory.rb @@ -334,7 +334,7 @@ class S3Inventory objects = [] hive_path = File.join(inventory_path, bucket_name, inventory_id, "hive") - @s3_helper.list(hive_path).each { |obj| objects << obj if obj.key.match?(/symlink\.txt$/i) } + @s3_helper.list(hive_path).each { |obj| objects << obj if obj.key.match?(/symlink\.txt\z/i) } objects rescue Aws::Errors::ServiceError => e diff --git a/lib/search.rb b/lib/search.rb index 77748100573..0429afe2bbd 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -128,7 +128,7 @@ class Search end data.gsub!(/\S+/) do |str| - if str =~ %r{^["]?((https?://)[\S]+)["]?$} + if str =~ %r{\A["]?((https?://)[\S]+)["]?\z} begin uri = URI.parse(Regexp.last_match[1]) uri.query = nil @@ -145,9 +145,9 @@ class Search end def self.word_to_date(str) - return Time.zone.now.beginning_of_day.days_ago(str.to_i) if str =~ /^[0-9]{1,3}$/ + return Time.zone.now.beginning_of_day.days_ago(str.to_i) if str =~ /\A[0-9]{1,3}\z/ - if str =~ /^([12][0-9]{3})(-([0-1]?[0-9]))?(-([0-3]?[0-9]))?$/ + if str =~ /\A([12][0-9]{3})(-([0-1]?[0-9]))?(-([0-3]?[0-9]))?\z/ year = $1.to_i month = $2 ? $3.to_i : 1 day = $4 ? $5.to_i : 1 @@ -307,7 +307,7 @@ class Search # If the term is a number or url to a topic, just include that topic if @opts[:search_for_id] && %w[topic private_messages all_topics].include?(@results.type_filter) - if @term =~ /^\d+$/ + if @term =~ /\A\d+\z/ single_topic(@term.to_i) else if route = Discourse.route_for(@term) @@ -355,7 +355,7 @@ class Search Array.wrap(@custom_topic_eager_loads) end - advanced_filter(/^in:personal-direct$/i) do |posts| + advanced_filter(/\Ain:personal-direct\z/i) do |posts| if @guardian.user posts.joins("LEFT JOIN topic_allowed_groups tg ON posts.topic_id = tg.topic_id").where( <<~SQL, @@ -376,60 +376,60 @@ class Search end end - advanced_filter(/^in:all-pms$/i) { |posts| posts.private_posts if @guardian.is_admin? } + advanced_filter(/\Ain:all-pms\z/i) { |posts| posts.private_posts if @guardian.is_admin? } - advanced_filter(/^in:tagged$/i) do |posts| + advanced_filter(/\Ain:tagged\z/i) do |posts| posts.where("EXISTS (SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = posts.topic_id)") end - advanced_filter(/^in:untagged$/i) do |posts| + advanced_filter(/\Ain:untagged\z/i) do |posts| posts.joins( "LEFT JOIN topic_tags ON topic_tags.topic_id = posts.topic_id", ).where("topic_tags.id IS NULL") end - advanced_filter(/^status:open$/i) do |posts| + advanced_filter(/\Astatus:open\z/i) do |posts| posts.where("NOT topics.closed AND NOT topics.archived") end - advanced_filter(/^status:closed$/i) { |posts| posts.where("topics.closed") } + advanced_filter(/\Astatus:closed\z/i) { |posts| posts.where("topics.closed") } - advanced_filter(/^status:public$/i) do |posts| + advanced_filter(/\Astatus:public\z/i) do |posts| category_ids = Category.where(read_restricted: false).pluck(:id) posts.where("topics.category_id in (?)", category_ids) end - advanced_filter(/^status:archived$/i) { |posts| posts.where("topics.archived") } + advanced_filter(/\Astatus:archived\z/i) { |posts| posts.where("topics.archived") } - advanced_filter(/^status:noreplies$/i) { |posts| posts.where("topics.posts_count = 1") } + advanced_filter(/\Astatus:noreplies\z/i) { |posts| posts.where("topics.posts_count = 1") } - advanced_filter(/^status:single_user$/i) { |posts| posts.where("topics.participant_count = 1") } + advanced_filter(/\Astatus:single_user\z/i) { |posts| posts.where("topics.participant_count = 1") } - advanced_filter(/^posts_count:(\d+)$/i) do |posts, match| + advanced_filter(/\Aposts_count:(\d+)\z/i) do |posts, match| posts.where("topics.posts_count = ?", match.to_i) end - advanced_filter(/^min_post_count:(\d+)$/i) do |posts, match| + advanced_filter(/\Amin_post_count:(\d+)\z/i) do |posts, match| posts.where("topics.posts_count >= ?", match.to_i) end - advanced_filter(/^min_posts:(\d+)$/i) do |posts, match| + advanced_filter(/\Amin_posts:(\d+)\z/i) do |posts, match| posts.where("topics.posts_count >= ?", match.to_i) end - advanced_filter(/^max_posts:(\d+)$/i) do |posts, match| + advanced_filter(/\Amax_posts:(\d+)\z/i) do |posts, match| posts.where("topics.posts_count <= ?", match.to_i) end - advanced_filter(/^in:first|^f$/i) { |posts| posts.where("posts.post_number = 1") } + advanced_filter(/\Ain:first|^f\z/i) { |posts| posts.where("posts.post_number = 1") } - advanced_filter(/^in:pinned$/i) { |posts| posts.where("topics.pinned_at IS NOT NULL") } + advanced_filter(/\Ain:pinned\z/i) { |posts| posts.where("topics.pinned_at IS NOT NULL") } - advanced_filter(/^in:wiki$/i) { |posts, match| posts.where(wiki: true) } + advanced_filter(/\Ain:wiki\z/i) { |posts, match| posts.where(wiki: true) } - advanced_filter(/^badge:(.*)$/i) do |posts, match| + advanced_filter(/\Abadge:(.*)\z/i) do |posts, match| badge_id = Badge.where("name ilike ? OR id = ?", match, match.to_i).pluck_first(:id) if badge_id posts.where( @@ -454,7 +454,7 @@ class Search ) end - advanced_filter(/^in:(likes)$/i) do |posts, match| + advanced_filter(/\Ain:(likes)\z/i) do |posts, match| post_action_type_filter(posts, PostActionType.types[:like]) if @guardian.user end @@ -462,7 +462,7 @@ class Search # this at some point, as it only acts on posts at the moment. On the other # hand, this may not be necessary, as the user bookmark list has advanced # search based on a RegisteredBookmarkable's #search_query method. - advanced_filter(/^in:(bookmarks)$/i) do |posts, match| + advanced_filter(/\Ain:(bookmarks)\z/i) do |posts, match| posts.where(<<~SQL, @guardian.user.id) if @guardian.user posts.id IN ( SELECT bookmarkable_id FROM bookmarks @@ -471,20 +471,20 @@ class Search SQL end - advanced_filter(/^in:posted$/i) do |posts| + advanced_filter(/\Ain:posted\z/i) do |posts| posts.where("posts.user_id = ?", @guardian.user.id) if @guardian.user end - advanced_filter(/^in:(created|mine)$/i) do |posts| + advanced_filter(/\Ain:(created|mine)\z/i) do |posts| posts.where(user_id: @guardian.user.id, post_number: 1) if @guardian.user end - advanced_filter(/^created:@(.*)$/i) do |posts, match| + advanced_filter(/\Acreated:@(.*)\z/i) do |posts, match| user_id = User.where(username: match.downcase).pluck_first(:id) posts.where(user_id: user_id, post_number: 1) end - advanced_filter(/^in:(watching|tracking)$/i) do |posts, match| + advanced_filter(/\Ain:(watching|tracking)\z/i) do |posts, match| if @guardian.user level = TopicUser.notification_levels[match.downcase.to_sym] posts.where( @@ -499,7 +499,7 @@ class Search end end - advanced_filter(/^in:seen$/i) do |posts| + advanced_filter(/\Ain:seen\z/i) do |posts| if @guardian.user posts.joins( "INNER JOIN post_timings ON @@ -511,7 +511,7 @@ class Search end end - advanced_filter(/^in:unseen$/i) do |posts| + advanced_filter(/\Ain:unseen\z/i) do |posts| if @guardian.user posts.joins( "LEFT JOIN post_timings ON @@ -523,9 +523,9 @@ class Search end end - advanced_filter(/^with:images$/i) { |posts| posts.where("posts.image_upload_id IS NOT NULL") } + advanced_filter(/\Awith:images\z/i) { |posts| posts.where("posts.image_upload_id IS NOT NULL") } - advanced_filter(/^category:(.+)$/i) do |posts, match| + advanced_filter(/\Acategory:(.+)\z/i) do |posts, match| exact = false if match[0] == "=" @@ -544,7 +544,7 @@ class Search end end - advanced_filter(/^\#([\p{L}\p{M}0-9\-:=]+)$/i) do |posts, match| + advanced_filter(/\A\#([\p{L}\p{M}0-9\-:=]+)\z/i) do |posts, match| category_slug, subcategory_slug = match.to_s.split(":") next unless category_slug @@ -614,7 +614,7 @@ class Search end end - advanced_filter(/^group:(.+)$/i) do |posts, match| + advanced_filter(/\Agroup:(.+)\z/i) do |posts, match| group_query = Group .visible_groups(@guardian.user) @@ -637,7 +637,7 @@ class Search end end - advanced_filter(/^group_messages:(.+)$/i) do |posts, match| + advanced_filter(/\Agroup_messages:(.+)\z/i) do |posts, match| group_id = Group .visible_groups(@guardian.user) @@ -656,7 +656,7 @@ class Search end end - advanced_filter(/^user:(.+)$/i) do |posts, match| + advanced_filter(/\Auser:(.+)\z/i) do |posts, match| user_id = User .where(staged: false) @@ -669,7 +669,7 @@ class Search end end - advanced_filter(/^\@(\S+)$/i) do |posts, match| + advanced_filter(/\A\@(\S+)\z/i) do |posts, match| username = User.normalize_username(match) user_id = User.not_staged.where(username_lower: username).pluck_first(:id) @@ -683,7 +683,7 @@ class Search end end - advanced_filter(/^before:(.*)$/i) do |posts, match| + advanced_filter(/\Abefore:(.*)\z/i) do |posts, match| if date = Search.word_to_date(match) posts.where("posts.created_at < ?", date) else @@ -691,7 +691,7 @@ class Search end end - advanced_filter(/^after:(.*)$/i) do |posts, match| + advanced_filter(/\Aafter:(.*)\z/i) do |posts, match| if date = Search.word_to_date(match) posts.where("posts.created_at > ?", date) else @@ -699,15 +699,15 @@ class Search end end - advanced_filter(/^tags?:([\p{L}\p{M}0-9,\-_+]+)$/i) do |posts, match| + advanced_filter(/\Atags?:([\p{L}\p{M}0-9,\-_+]+)\z/i) do |posts, match| search_tags(posts, match, positive: true) end - advanced_filter(/^\-tags?:([\p{L}\p{M}0-9,\-_+]+)$/i) do |posts, match| + advanced_filter(/\A\-tags?:([\p{L}\p{M}0-9,\-_+]+)\z/i) do |posts, match| search_tags(posts, match, positive: false) end - advanced_filter(/^filetypes?:([a-zA-Z0-9,\-_]+)$/i) do |posts, match| + advanced_filter(/\Afiletypes?:([a-zA-Z0-9,\-_]+)\z/i) do |posts, match| file_extensions = match.split(",").map(&:downcase) posts.where( "posts.id IN ( @@ -726,11 +726,11 @@ class Search ) end - advanced_filter(/^min_views:(\d+)$/i) do |posts, match| + advanced_filter(/\Amin_views:(\d+)\z/i) do |posts, match| posts.where("topics.views >= ?", match.to_i) end - advanced_filter(/^max_views:(\d+)$/i) do |posts, match| + advanced_filter(/\Amax_views:(\d+)\z/i) do |posts, match| posts.where("topics.views <= ?", match.to_i) end @@ -789,38 +789,38 @@ class Search if word == "l" @order = :latest nil - elsif word =~ /^order:\w+$/i + elsif word =~ /\Aorder:\w+\z/i @order = word.downcase.gsub("order:", "").to_sym nil - elsif word =~ /^in:title$/i || word == "t" + elsif word =~ /\Ain:title\z/i || word == "t" @in_title = true nil - elsif word =~ /^topic:(\d+)$/i + elsif word =~ /\Atopic:(\d+)\z/i topic_id = $1.to_i if topic_id > 1 topic = Topic.find_by(id: topic_id) @search_context = topic if @guardian.can_see?(topic) end nil - elsif word =~ /^in:all$/i + elsif word =~ /\Ain:all\z/i @search_all_topics = true nil - elsif word =~ /^in:personal$/i + elsif word =~ /\Ain:personal\z/i @search_pms = true nil - elsif word =~ /^in:messages$/i + elsif word =~ /\Ain:messages\z/i @search_pms = true nil - elsif word =~ /^in:personal-direct$/i + elsif word =~ /\Ain:personal-direct\z/i @search_pms = true nil - elsif word =~ /^in:all-pms$/i + elsif word =~ /\Ain:all-pms\z/i @search_all_pms = true nil - elsif word =~ /^group_messages:(.+)$/i + elsif word =~ /\Agroup_messages:(.+)\z/i @search_pms = true nil - elsif word =~ /^personal_messages:(.+)$/i + elsif word =~ /\Apersonal_messages:(.+)\z/i if user = User.find_by_username($1) @search_pms = true @search_context = user diff --git a/lib/shrink_uploaded_image.rb b/lib/shrink_uploaded_image.rb index d1a7f14f012..f46a3b72073 100644 --- a/lib/shrink_uploaded_image.rb +++ b/lib/shrink_uploaded_image.rb @@ -98,7 +98,7 @@ class ShrinkUploadedImage elsif !post.topic || post.topic.trashed? log "A deleted topic" elsif post.cooked.include?(original_upload.sha1) - if post.raw.include?("#{Discourse.base_url.sub(%r{^https?://}i, "")}/t/") + if post.raw.include?("#{Discourse.base_url.sub(%r{\Ahttps?://}i, "")}/t/") log "Updating a topic onebox" else log "Updating an external onebox" diff --git a/lib/site_settings/validations.rb b/lib/site_settings/validations.rb index fdc36ff57e4..b8ac0309644 100644 --- a/lib/site_settings/validations.rb +++ b/lib/site_settings/validations.rb @@ -243,7 +243,7 @@ module SiteSettings::Validations def validate_cors_origins(new_val) return if new_val.blank? - return unless new_val.split("|").any?(%r{/$}) + return unless new_val.split("|").any?(%r{/\z}) validate_error :cors_origins_should_not_have_trailing_slash end diff --git a/lib/stylesheet/manager.rb b/lib/stylesheet/manager.rb index 217bed7737e..a9d636b6c3b 100644 --- a/lib/stylesheet/manager.rb +++ b/lib/stylesheet/manager.rb @@ -11,7 +11,7 @@ class Stylesheet::Manager CACHE_PATH ||= "tmp/stylesheet-cache" MANIFEST_DIR ||= "#{Rails.root}/tmp/cache/assets/#{Rails.env}" - THEME_REGEX ||= /_theme$/ + THEME_REGEX ||= /_theme\z/ COLOR_SCHEME_STYLESHEET ||= "color_definitions" @@lock = Mutex.new diff --git a/lib/stylesheet/manager/builder.rb b/lib/stylesheet/manager/builder.rb index 47dbae0650c..9e022b8d59b 100644 --- a/lib/stylesheet/manager/builder.rb +++ b/lib/stylesheet/manager/builder.rb @@ -35,7 +35,7 @@ class Stylesheet::Manager::Builder end end - rtl = @target.to_s =~ /_rtl$/ + rtl = @target.to_s =~ /_rtl\z/ css, source_map = with_load_paths do |load_paths| Stylesheet::Compiler.compile_asset( diff --git a/lib/stylesheet/watcher.rb b/lib/stylesheet/watcher.rb index a2c8189e83b..8e41e6832a1 100644 --- a/lib/stylesheet/watcher.rb +++ b/lib/stylesheet/watcher.rb @@ -21,7 +21,7 @@ module Stylesheet @default_paths = ["app/assets/stylesheets"] Discourse.plugins.each do |plugin| if plugin.path.to_s.include?(Rails.root.to_s) - @default_paths << File.dirname(plugin.path).sub(Rails.root.to_s, "").sub(%r{^/}, "") + @default_paths << File.dirname(plugin.path).sub(Rails.root.to_s, "").sub(%r{\A/}, "") else # if plugin doesn’t seem to be in our app, consider it as outside of the app # and ignore it @@ -41,7 +41,7 @@ module Stylesheet end end - listener_opts = { ignore: /xxxx/, only: /\.(css|scss)$/ } + listener_opts = { ignore: /xxxx/, only: /\.(css|scss)\z/ } listener_opts[:force_polling] = true if ENV["FORCE_POLLING"] Thread.new do diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index f00ab3068e9..1c9b3a3311f 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -307,7 +307,7 @@ task "assets:precompile" => "assets:precompile:before" do concurrent? do |proc| manifest .files - .select { |k, v| k =~ /\.js$/ } + .select { |k, v| k =~ /\.js\z/ } .each do |file, info| path = "#{assets_path}/#{file}" _file = (d = File.dirname(file)) == "." ? "_#{file}" : "#{d}/_#{File.basename(file)}" diff --git a/lib/tasks/cdn.rake b/lib/tasks/cdn.rake index ee2b0c14ee0..559144bf9d2 100644 --- a/lib/tasks/cdn.rake +++ b/lib/tasks/cdn.rake @@ -10,7 +10,7 @@ task "assets:prestage" => :environment do |t| def get_assets(path) Dir .glob("#{Rails.root}/public/assets/#{path}*") - .map { |f| "/assets/#{path}#{f.split("/")[-1]}" if f =~ /[a-f0-9]{16}\.(css|js)$/ } + .map { |f| "/assets/#{path}#{f.split("/")[-1]}" if f =~ /[a-f0-9]{16}\.(css|js)\z/ } .compact end diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 403fd6749cb..68fb2d50f7d 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -109,7 +109,7 @@ class SeedHelper def self.filter # Allows a plugin to exclude any specified seed data files from running if DiscoursePluginRegistry.seedfu_filter.any? - /^(?!.*(#{DiscoursePluginRegistry.seedfu_filter.to_a.join("|")})).*$/ + /\A(?!.*(#{DiscoursePluginRegistry.seedfu_filter.to_a.join("|")})).*\z/ else nil end diff --git a/lib/tasks/emoji.rake b/lib/tasks/emoji.rake index fe049fc550d..e9bce247dce 100644 --- a/lib/tasks/emoji.rake +++ b/lib/tasks/emoji.rake @@ -459,7 +459,7 @@ end def codepoints_to_code(codepoints, fitzpatrick_scale) codepoints = codepoints.map { |c| c.to_s(16).rjust(4, "0") }.join("_").downcase - codepoints.gsub!(/_fe0f$/, "") if !fitzpatrick_scale + codepoints.gsub!(/_fe0f\z/, "") if !fitzpatrick_scale codepoints end diff --git a/lib/tasks/plugin.rake b/lib/tasks/plugin.rake index 32255f2f301..6f4d67fe12e 100644 --- a/lib/tasks/plugin.rake +++ b/lib/tasks/plugin.rake @@ -86,7 +86,7 @@ task "plugin:update", :plugin do |t, args| upstream_branch = `git -C '#{plugin_path}' for-each-ref --format='%(upstream:short)' $(git -C '#{plugin_path}' symbolic-ref -q HEAD)`.strip - has_origin_main = `git -C '#{plugin_path}' branch -a`.match?(%r{remotes/origin/main$}) + has_origin_main = `git -C '#{plugin_path}' branch -a`.match?(%r{remotes/origin/main\z}) has_local_main = `git -C '#{plugin_path}' show-ref refs/heads/main`.present? if upstream_branch == "origin/master" && has_origin_main diff --git a/lib/tasks/release_note.rake b/lib/tasks/release_note.rake index 8f665d6eba8..e4bbe63f3d0 100644 --- a/lib/tasks/release_note.rake +++ b/lib/tasks/release_note.rake @@ -3,12 +3,12 @@ DATE_REGEX ||= /\A\d{4}-\d{2}-\d{2}/ CHANGE_TYPES ||= [ - { pattern: /^FEATURE:/, heading: "New Features" }, - { pattern: /^FIX:/, heading: "Bug Fixes" }, - { pattern: /^UX:/, heading: "UX Changes" }, - { pattern: /^SECURITY:/, heading: "Security Changes" }, - { pattern: /^PERF:/, heading: "Performance" }, - { pattern: /^A11Y:/, heading: "Accessibility" }, + { pattern: /\AFEATURE:/, heading: "New Features" }, + { pattern: /\AFIX:/, heading: "Bug Fixes" }, + { pattern: /\AUX:/, heading: "UX Changes" }, + { pattern: /\ASECURITY:/, heading: "Security Changes" }, + { pattern: /\APERF:/, heading: "Performance" }, + { pattern: /\AA11Y:/, heading: "Accessibility" }, ] desc "generate a release note from the important commits" @@ -83,7 +83,7 @@ def find_changes(repo, from, to) CHANGE_TYPES.each { |ct| changes[ct] = Set.new } out.each_line do |comment| - next if comment =~ /^\s*Revert/ + next if comment =~ /\A\s*Revert/ split_comments(comment).each do |line| ct = CHANGE_TYPES.find { |t| line =~ t[:pattern] } changes[ct] << better(line) if ct @@ -122,7 +122,7 @@ def better(line) end def remove_prefix(line) - line.gsub(/^(FIX|FEATURE|UX|SECURITY|PERF|A11Y):/, "").strip + line.gsub(/\A(FIX|FEATURE|UX|SECURITY|PERF|A11Y):/, "").strip end def escape_brackets(line) @@ -130,7 +130,7 @@ def escape_brackets(line) end def remove_pull_request(line) - line.gsub(/ \(\#\d+\)$/, "") + line.gsub(/ \(\#\d+\)\z/, "") end def split_comments(text) diff --git a/lib/tasks/typepad.thor b/lib/tasks/typepad.thor index 649f12db523..bbef98037e6 100644 --- a/lib/tasks/typepad.thor +++ b/lib/tasks/typepad.thor @@ -34,7 +34,7 @@ class Typepad < Thor File.open(options[:file]).each_line do |l| l = l.scrub - if l =~ /^--------$/ + if l =~ /\A--------\z/ parsed_entry = process_entry(input) if parsed_entry puts "Parsed #{parsed_entry[:title]}" @@ -119,7 +119,7 @@ class Typepad < Thor def parse_meta_data(section) result = {} section.split(/\n/).each do |l| - if l =~ /^([A-Z\ ]+)\: (.*)$/ + if l =~ /\A([A-Z\ ]+)\: (.*)\z/ key, value = Regexp.last_match[1], Regexp.last_match[2] clean_type!(key) value.strip! @@ -134,7 +134,7 @@ class Typepad < Thor def parse_section(section) section.strip! - if section =~ /^([^:]+):/ + if section =~ /\A([^:]+):/ type = clean_type!(Regexp.last_match[1]) value = section.split("\n")[1..-1].join("\n") value.strip! @@ -195,8 +195,8 @@ class Typepad < Thor comment[:name] = comment[:author] if comment[:author] - comment[:author].gsub!(/^[_\.]+/, '') - comment[:author].gsub!(/[_\.]+$/, '') + comment[:author].gsub!(/\A[_\.]+/, '') + comment[:author].gsub!(/[_\.]+\z/, '') if comment[:author].size < 12 comment[:author].gsub!(/ /, '_') diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake index 6314efff696..1104a9f3a77 100644 --- a/lib/tasks/uploads.rake +++ b/lib/tasks/uploads.rake @@ -35,7 +35,7 @@ def gather_uploads .where("url !~ ?", "^\/uploads\/#{current_db}") .find_each do |upload| begin - old_db = upload.url[%r{^/uploads/([^/]+)/}, 1] + old_db = upload.url[%r{\A/uploads/([^/]+)/}, 1] from = upload.url.dup to = upload.url.sub("/uploads/#{old_db}/", "/uploads/#{current_db}/") source = "#{public_directory}#{from}" @@ -321,8 +321,8 @@ def regenerate_missing_optimized scope.find_each do |optimized_image| upload = optimized_image.upload - next unless optimized_image.url =~ %r{^/[^/]} - next unless upload.url =~ %r{^/[^/]} + next unless optimized_image.url =~ %r{\A/[^/]} + next unless upload.url =~ %r{\A/[^/]} thumbnail = "#{public_directory}#{optimized_image.url}" original = "#{public_directory}#{upload.url}" diff --git a/lib/theme_javascript_compiler.rb b/lib/theme_javascript_compiler.rb index 65d10d96fe6..f67009bec3c 100644 --- a/lib/theme_javascript_compiler.rb +++ b/lib/theme_javascript_compiler.rb @@ -200,7 +200,7 @@ class ThemeJavascriptCompiler end def raw_template_name(name) - name = name.sub(/\.(raw|hbr)$/, "") + name = name.sub(/\.(raw|hbr)\z/, "") name.inspect end @@ -228,7 +228,7 @@ class ThemeJavascriptCompiler def append_module(script, name, include_variables: true) original_filename = name - name = "discourse/theme-#{@theme_id}/#{name.gsub(%r{^discourse/}, "")}" + name = "discourse/theme-#{@theme_id}/#{name.gsub(%r{\Adiscourse/}, "")}" script = "#{theme_settings}#{script}" if include_variables transpiler = DiscourseJsProcessor::Transpiler.new diff --git a/lib/topic_creator.rb b/lib/topic_creator.rb index 084c71584a3..f9cdc99b441 100644 --- a/lib/topic_creator.rb +++ b/lib/topic_creator.rb @@ -181,7 +181,7 @@ class TopicCreator return Category.find(SiteSetting.shared_drafts_category) if @opts[:shared_draft] - if (@opts[:category].is_a? Integer) || (@opts[:category] =~ /^\d+$/) + if (@opts[:category].is_a? Integer) || (@opts[:category] =~ /\A\d+\z/) Category.find_by(id: @opts[:category]) end end diff --git a/lib/topic_query.rb b/lib/topic_query.rb index 37c3120f491..4d3ea15bbd9 100644 --- a/lib/topic_query.rb +++ b/lib/topic_query.rb @@ -14,7 +14,7 @@ class TopicQuery def self.validators @validators ||= begin - int = lambda { |x| Integer === x || (String === x && x.match?(/^-?[0-9]+$/)) } + int = lambda { |x| Integer === x || (String === x && x.match?(/\A-?[0-9]+\z/)) } zero_up_to_max_int = lambda { |x| int.call(x) && x.to_i.between?(0, PG_MAX_INT) } array_or_string = lambda { |x| Array === x || String === x } diff --git a/lib/upload_creator.rb b/lib/upload_creator.rb index 32922cc468d..c826a4b906f 100644 --- a/lib/upload_creator.rb +++ b/lib/upload_creator.rb @@ -386,7 +386,7 @@ class UploadCreator end def convert_heif_to_jpeg? - File.extname(@filename).downcase.match?(/\.hei(f|c)$/) + File.extname(@filename).downcase.match?(/\.hei(f|c)\z/) end def convert_heif! @@ -593,7 +593,7 @@ class UploadCreator def should_optimize? # GIF is too slow (plus, we'll soon be converting them to MP4) # Optimizing SVG is useless - return false if @file.path =~ /\.(gif|svg)$/i + return false if @file.path =~ /\.(gif|svg)\z/i # Safeguard for large PNGs return pixels < 2_000_000 if @file.path =~ /\.png/i # Everything else is fine! diff --git a/lib/url_helper.rb b/lib/url_helper.rb index 097b3866db0..320f3babd28 100644 --- a/lib/url_helper.rb +++ b/lib/url_helper.rb @@ -48,8 +48,8 @@ class UrlHelper end def self.absolute(url, cdn = Discourse.asset_host) - cdn = "https:#{cdn}" if cdn && cdn =~ %r{^//} - url =~ %r{^/[^/]} ? (cdn || Discourse.base_url_no_prefix) + url : url + cdn = "https:#{cdn}" if cdn && cdn =~ %r{\A//} + url =~ %r{\A/[^/]} ? (cdn || Discourse.base_url_no_prefix) + url : url end def self.absolute_without_cdn(url) @@ -57,7 +57,7 @@ class UrlHelper end def self.schemaless(url) - url.sub(/^http:/i, "") + url.sub(/\Ahttp:/i, "") end def self.secure_proxy_without_cdn(url) diff --git a/lib/validators/css_color_validator.rb b/lib/validators/css_color_validator.rb index fdc1fe8f28a..6085ab49aa3 100644 --- a/lib/validators/css_color_validator.rb +++ b/lib/validators/css_color_validator.rb @@ -156,7 +156,7 @@ class CssColorValidator end def valid_value?(val) - !!(val =~ /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/ || COLORS.include?(val&.downcase)) + !!(val =~ /\A#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\z/ || COLORS.include?(val&.downcase)) end def error_message diff --git a/lib/validators/unicode_username_allowlist_validator.rb b/lib/validators/unicode_username_allowlist_validator.rb index 824e5e43442..f60c161707c 100644 --- a/lib/validators/unicode_username_allowlist_validator.rb +++ b/lib/validators/unicode_username_allowlist_validator.rb @@ -9,7 +9,7 @@ class UnicodeUsernameAllowlistValidator @error_message = nil return true if value.blank? - if value.match?(%r{^/.*/[imxo]*$}) + if value.match?(%r{\A/.*/[imxo]*\z}) @error_message = I18n.t("site_settings.errors.allowed_unicode_usernames.leading_trailing_slash") else