DEV: Prefer \A and \z over ^ and $ in regexes (#19936)

This commit is contained in:
Daniel Waterworth 2023-01-20 12:52:49 -06:00 committed by GitHub
parent f7907a3645
commit 666536cbd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 294 additions and 291 deletions

View File

@ -238,11 +238,11 @@ class Admin::BackupsController < Admin::AdminController
end end
def valid_extension?(filename) def valid_extension?(filename)
/\.(tar\.gz|t?gz)$/i =~ filename /\.(tar\.gz|t?gz)\z/i =~ filename
end end
def valid_filename?(filename) def valid_filename?(filename)
!!(/^[a-zA-Z0-9\._\-]+$/ =~ filename) !!(/\A[a-zA-Z0-9\._\-]+\z/ =~ filename)
end end
def render_error(message_key) def render_error(message_key)

View File

@ -7,9 +7,9 @@ class Admin::ReportsController < Admin::StaffController
ApplicationRequest ApplicationRequest
.req_types .req_types
.keys .keys
.select { |r| r =~ /^page_view_/ && r !~ /mobile/ } .select { |r| r =~ /\Apage_view_/ && r !~ /mobile/ }
.map { |r| r + "_reqs" } + .map { |r| r + "_reqs" } +
Report.singleton_methods.grep(/^report_(?!about|storage_stats)/) Report.singleton_methods.grep(/\Areport_(?!about|storage_stats)/)
reports = reports =
reports_methods.map do |name| reports_methods.map do |name|
@ -61,7 +61,7 @@ class Admin::ReportsController < Admin::StaffController
def show def show
report_type = params[:type] 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) args = parse_params(params)

View File

@ -160,7 +160,7 @@ class Admin::SiteTextsController < Admin::AdminController
{ id: key, value: value, locale: locale } { id: key, value: value, locale: locale }
end end
PLURALIZED_REGEX = /(.*)\.(zero|one|two|few|many|other)$/ PLURALIZED_REGEX = /(.*)\.(zero|one|two|few|many|other)\z/
def find_site_text(locale) def find_site_text(locale)
if self.class.restricted_keys.include?(params[:id]) if self.class.restricted_keys.include?(params[:id])

View File

@ -108,7 +108,7 @@ class Admin::ThemesController < Admin::AdminController
render json: @theme, status: :created render json: @theme, status: :created
rescue RemoteTheme::ImportError => e rescue RemoteTheme::ImportError => e
if params[:force] 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 = RemoteTheme.new
remote_theme.private_key = private_key remote_theme.private_key = private_key

View File

@ -679,7 +679,7 @@ class ApplicationController < ActionController::Base
DiscoursePluginRegistry.html_builders.each do |name, _| DiscoursePluginRegistry.html_builders.each do |name, _|
if name.start_with?("client:") 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
end end

View File

@ -28,11 +28,11 @@ class EmbedController < ApplicationController
end end
if @embed_id = params[:discourse_embed_id] 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 end
if @embed_class = params[:embed_class] 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) raise Discourse::InvalidParameters.new(:embed_class)
end end
end end
@ -139,7 +139,7 @@ class EmbedController < ApplicationController
by_url = {} by_url = {}
if embed_urls.present? 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 = TopicEmbed.where(embed_url: urls).includes(:topic).references(:topic)
topic_embeds.each do |te| topic_embeds.each do |te|

View File

@ -71,6 +71,6 @@ class ExtraLocalesController < ApplicationController
private private
def valid_bundle?(bundle) 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
end end

View File

@ -203,7 +203,7 @@ class SessionController < ApplicationController
end end
# If it's not a relative URL check the host # If it's not a relative URL check the host
if return_path !~ %r{^/[^/]} if return_path !~ %r{\A/[^/]}
begin begin
uri = URI(return_path) uri = URI(return_path)
if (uri.hostname == Discourse.current_hostname) if (uri.hostname == Discourse.current_hostname)

View File

@ -47,7 +47,7 @@ class ThemeJavascriptsController < ApplicationController
def show_tests def show_tests
digest = params[:digest] 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]) theme = Theme.find_by(id: params[:theme_id])
raise Discourse::NotFound if theme.blank? raise Discourse::NotFound if theme.blank?

View File

@ -83,7 +83,7 @@ class TopicsController < ApplicationController
# Special case: a slug with a number in front should look by slug first before looking # Special case: a slug with a number in front should look by slug first before looking
# up that particular number # 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]) topic = Topic.find_by_slug(params[:id])
return redirect_to_correct_topic(topic, opts[:post_number]) if topic return redirect_to_correct_topic(topic, opts[:post_number]) if topic
end end

View File

@ -39,7 +39,7 @@ class UserAvatarsController < ApplicationController
def show_proxy_letter def show_proxy_letter
is_asset_path 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 raise Discourse::NotFound
end end

View File

@ -473,7 +473,7 @@ class UsersController < ApplicationController
end end
def my_redirect 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? if current_user.blank?
cookies[:destination_url] = path("/my/#{params[:path]}") cookies[:destination_url] = path("/my/#{params[:path]}")

View File

@ -50,7 +50,7 @@ module ApplicationHelper
def google_universal_analytics_json(ua_domain_name = nil) def google_universal_analytics_json(ua_domain_name = nil)
result = {} 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[:userId] = current_user.id if current_user.present?
result[:allowLinker] = true if SiteSetting.ga_universal_auto_link_domains.present? result[:allowLinker] = true if SiteSetting.ga_universal_auto_link_domains.present?
result.to_json result.to_json
@ -117,9 +117,9 @@ module ApplicationHelper
# seconds. # seconds.
if !script.start_with?("discourse/tests/") if !script.start_with?("discourse/tests/")
if is_brotli_req? if is_brotli_req?
path = path.gsub(/\.([^.]+)$/, '.br.\1') path = path.gsub(/\.([^.]+)\z/, '.br.\1')
elsif is_gzip_req? elsif is_gzip_req?
path = path.gsub(/\.([^.]+)$/, '.gz.\1') path = path.gsub(/\.([^.]+)\z/, '.gz.\1')
end end
end end
elsif GlobalSetting.cdn_url&.start_with?("https") && is_brotli_req? && elsif GlobalSetting.cdn_url&.start_with?("https") && is_brotli_req? &&

View File

@ -20,8 +20,8 @@ module UserNotificationsHelper
def logo_url def logo_url
logo_url = SiteSetting.site_digest_logo_url logo_url = SiteSetting.site_digest_logo_url
logo_url = SiteSetting.site_logo_url 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$/i return nil if logo_url.blank? || logo_url =~ /\.svg\z/i
logo_url logo_url
end end

View File

@ -29,7 +29,7 @@ module Jobs
end end
def self.num_email_retry_jobs def self.num_email_retry_jobs
Sidekiq::RetrySet.new.count { |job| job.klass =~ /Email$/ } Sidekiq::RetrySet.new.count { |job| job.klass =~ /Email\z/ }
end end
class Base class Base

View File

@ -4,7 +4,7 @@ class Jobs::Onceoff < ::Jobs::Base
sidekiq_options retry: false sidekiq_options retry: false
def self.name_for(klass) def self.name_for(klass)
klass.name.sub(/^Jobs\:\:/, "") klass.name.sub(/\AJobs\:\:/, "")
end end
def running_key_name def running_key_name

View File

@ -29,9 +29,9 @@ module Jobs
@raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i @raw_quote_regex = /(\[quote\s*=\s*["'']?)#{@old_username}(\,?[^\]]*\])/i
cooked_username = PrettyText::Helpers.format_username(@old_username) 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 = @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 @cooked_quote_username_regex = /(?<=\s)#{cooked_username}(?=:)/i
update_posts update_posts

View File

@ -377,7 +377,7 @@ class AdminDashboardData
end end
def subfolder_ends_in_slash_check 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 end
def email_polling_errored_recently def email_polling_errored_recently

View File

@ -421,7 +421,7 @@ class Category < ActiveRecord::Base
end end
# only allow to use category itself id. # 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? if match_id.present?
errors.add(:slug, :invalid) if new_record? || (match_id[1] != self.id.to_s) errors.add(:slug, :invalid) if new_record? || (match_id[1] != self.id.to_s)
end end
@ -897,7 +897,7 @@ class Category < ActiveRecord::Base
slug_path.inject(nil) do |parent_id, slug| slug_path.inject(nil) do |parent_id, slug|
category = Category.where(slug: slug, parent_category_id: parent_id) 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)) category = category.or(Category.where(id: match_id[1], parent_category_id: parent_id))
end end

View File

@ -20,7 +20,7 @@ module HasCustomFields
sorted_types = types.keys.select { |k| k.end_with?("*") }.sort_by(&:length).reverse 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] types[key]
end end

View File

@ -66,7 +66,7 @@ module Reports::TopUploads
builder.where("up.created_at < :end_date", end_date: report.end_date) builder.where("up.created_at < :end_date", end_date: report.end_date)
if extension_filter if extension_filter
builder.where("up.extension = :extension", extension: extension_filter.sub(/^\./, "")) builder.where("up.extension = :extension", extension: extension_filter.sub(/\A\./, ""))
end end
builder.query.each do |row| builder.query.each do |row|

View File

@ -6,8 +6,8 @@ class EmbeddableHost < ActiveRecord::Base
after_destroy :reset_embedding_settings after_destroy :reset_embedding_settings
before_validation do before_validation do
self.host.sub!(%r{^https?://}, "") self.host.sub!(%r{\Ahttps?://}, "")
self.host.sub!(%r{/.*$}, "") self.host.sub!(%r{/.*\z}, "")
end end
# TODO(2021-07-23): Remove # TODO(2021-07-23): Remove

View File

@ -173,7 +173,7 @@ class Emoji
emojis.each do |name, url| emojis.each do |name, url|
result << Emoji.new.tap do |e| result << Emoji.new.tap do |e|
e.name = name e.name = name
url = (Discourse.base_path + url) if url[%r{^/[^/]}] url = (Discourse.base_path + url) if url[%r{\A/[^/]}]
e.url = url e.url = url
e.group = group || DEFAULT_GROUP e.group = group || DEFAULT_GROUP
end end

View File

@ -5,7 +5,7 @@ class GlobalSetting
define_singleton_method(key) { provider.lookup(key, default) } define_singleton_method(key) { provider.lookup(key, default) }
end 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 # this is named SECRET_TOKEN as opposed to SECRET_KEY_BASE
# for legacy reasons # for legacy reasons
REDIS_SECRET_KEY ||= "SECRET_TOKEN" REDIS_SECRET_KEY ||= "SECRET_TOKEN"
@ -251,7 +251,7 @@ class GlobalSetting
class BaseProvider class BaseProvider
def self.coerce(setting) def self.coerce(setting)
return setting == "true" if setting == "true" || setting == "false" 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 setting
end end
@ -283,7 +283,7 @@ class GlobalSetting
.result() .result()
.split("\n") .split("\n")
.each do |line| .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 @data[$1.strip.to_sym] = ($4 || $3 || $2).strip
end end
end end
@ -314,7 +314,7 @@ class GlobalSetting
end end
def keys 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
end end

View File

@ -1005,7 +1005,7 @@ class Group < ActiveRecord::Base
user = email_username_user user = email_username_user
domain = email_username_domain domain = email_username_domain
if user.present? && domain.present? if user.present? && domain.present?
/^#{Regexp.escape(user)}(\+[^@]*)?@#{Regexp.escape(domain)}$/i /\A#{Regexp.escape(user)}(\+[^@]*)?@#{Regexp.escape(domain)}\z/i
end end
end end
@ -1160,8 +1160,8 @@ class Group < ActiveRecord::Base
value value
.split("|") .split("|")
.each do |domain| .each do |domain|
domain.sub!(%r{^https?://}, "") domain.sub!(%r{\Ahttps?://}, "")
domain.sub!(%r{/.*$}, "") domain.sub!(%r{/.*\z}, "")
if domain =~ Group::VALID_DOMAIN_REGEX if domain =~ Group::VALID_DOMAIN_REGEX
valid_domains << domain valid_domains << domain

View File

@ -142,7 +142,7 @@ class OptimizedImage < ActiveRecord::Base
end end
def local? def local?
!(url =~ %r{^(https?:)?//}) !(url =~ %r{\A(https?:)?//})
end end
def calculate_filesize def calculate_filesize
@ -337,7 +337,7 @@ class OptimizedImage < ActiveRecord::Base
else else
error = +"Failed to optimize image:" error = +"Failed to optimize image:"
if e.message =~ /^convert:([^`]+)/ if e.message =~ /\Aconvert:([^`]+)/
error << $1 error << $1
else else
error << " unknown reason" error << " unknown reason"

View File

@ -7,8 +7,8 @@ class PostActionType < ActiveRecord::Base
include AnonCacheInvalidator include AnonCacheInvalidator
def expire_cache def expire_cache
ApplicationSerializer.expire_cache_fragment!(/^post_action_types_/) ApplicationSerializer.expire_cache_fragment!(/\Apost_action_types_/)
ApplicationSerializer.expire_cache_fragment!(/^post_action_flag_types_/) ApplicationSerializer.expire_cache_fragment!(/\Apost_action_flag_types_/)
end end
class << self class << self

View File

@ -8,7 +8,7 @@ class PublishedPage < ActiveRecord::Base
validate :slug_format validate :slug_format
def 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")) errors.add(:slug, I18n.t("publish_page.slug_errors.invalid"))
elsif %w[check-slug by-topic].include?(slug) elsif %w[check-slug by-topic].include?(slug)
errors.add(:slug, I18n.t("publish_page.slug_errors.unavailable")) errors.add(:slug, I18n.t("publish_page.slug_errors.unavailable"))

View File

@ -15,8 +15,8 @@ class RemoteTheme < ActiveRecord::Base
ALLOWED_FIELDS = %w[scss embedded_scss head_tag header after_header body_tag footer] ALLOWED_FIELDS = %w[scss embedded_scss head_tag header after_header body_tag footer]
GITHUB_REGEXP = %r{^https?://github\.com/} GITHUB_REGEXP = %r{\Ahttps?://github\.com/}
GITHUB_SSH_REGEXP = %r{^ssh://git@github\.com:} GITHUB_SSH_REGEXP = %r{\Assh://git@github\.com:}
has_one :theme, autosave: false has_one :theme, autosave: false
scope :joined_remotes, scope :joined_remotes,
@ -329,7 +329,7 @@ class RemoteTheme < ActiveRecord::Base
def github_diff_link def github_diff_link
if github_repo_url.present? && local_version != remote_version 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
end end

View File

@ -268,8 +268,8 @@ class Report
wrap_slow_query do wrap_slow_query do
if respond_to?(report_method) if respond_to?(report_method)
public_send(report_method, report) public_send(report_method, report)
elsif type =~ /_reqs$/ elsif type =~ /_reqs\z/
req_report(report, type.split(/_reqs$/)[0].to_sym) req_report(report, type.split(/_reqs\z/)[0].to_sym)
else else
return nil return nil
end end

View File

@ -60,7 +60,7 @@ class Reviewable < ActiveRecord::Base
end end
def self.valid_type?(type) 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 type.constantize <= Reviewable
rescue NameError rescue NameError
false false

View File

@ -17,7 +17,7 @@ class ScreenedUrl < ActiveRecord::Base
def normalize def normalize
self.url = ScreenedUrl.normalize_url(self.url) if self.url 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 end
def self.watch(url, domain, opts = {}) def self.watch(url, domain, opts = {})
@ -30,8 +30,8 @@ class ScreenedUrl < ActiveRecord::Base
def self.normalize_url(url) def self.normalize_url(url)
normalized = url.gsub(%r{http(s?)://}i, "") normalized = url.gsub(%r{http(s?)://}i, "")
normalized.gsub!(%r{(/)+$}, "") # trim trailing slashes normalized.gsub!(%r{(/)+\z}, "") # trim trailing slashes
normalized.gsub!(%r{^([^/]+)(?:/)?}) { |m| m.downcase } # downcase the domain part of the url normalized.gsub!(%r{\A([^/]+)(?:/)?}) { |m| m.downcase } # downcase the domain part of the url
normalized normalized
end end
end end

View File

@ -94,7 +94,7 @@ class ThemeField < ActiveRecord::Base
.css('script[type="text/x-handlebars"]') .css('script[type="text/x-handlebars"]')
.each do |node| .each do |node|
name = node["name"] || node["data-template-name"] || "broken" name = node["name"] || node["data-template-name"] || "broken"
is_raw = name =~ /\.(raw|hbr)$/ is_raw = name =~ /\.(raw|hbr)\z/
hbs_template = node.inner_html hbs_template = node.inner_html
begin begin
@ -523,63 +523,63 @@ class ThemeField < ActiveRecord::Base
FILE_MATCHERS = [ FILE_MATCHERS = [
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: regex:
%r{^(?<target>(?:mobile|desktop|common))/(?<name>(?:head_tag|header|after_header|body_tag|footer))\.html$}, %r{\A(?<target>(?:mobile|desktop|common))/(?<name>(?:head_tag|header|after_header|body_tag|footer))\.html\z},
targets: %i[mobile desktop common], targets: %i[mobile desktop common],
names: %w[head_tag header after_header body_tag footer], names: %w[head_tag header after_header body_tag footer],
types: :html, types: :html,
canonical: ->(h) { "#{h[:target]}/#{h[:name]}.html" }, canonical: ->(h) { "#{h[:target]}/#{h[:name]}.html" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^(?<target>(?:mobile|desktop|common))/(?:\k<target>)\.scss$}, regex: %r{\A(?<target>(?:mobile|desktop|common))/(?:\k<target>)\.scss\z},
targets: %i[mobile desktop common], targets: %i[mobile desktop common],
names: "scss", names: "scss",
types: :scss, types: :scss,
canonical: ->(h) { "#{h[:target]}/#{h[:target]}.scss" }, canonical: ->(h) { "#{h[:target]}/#{h[:target]}.scss" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^common/embedded\.scss$}, regex: %r{\Acommon/embedded\.scss\z},
targets: :common, targets: :common,
names: "embedded_scss", names: "embedded_scss",
types: :scss, types: :scss,
canonical: ->(h) { "common/embedded.scss" }, canonical: ->(h) { "common/embedded.scss" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^common/color_definitions\.scss$}, regex: %r{\Acommon/color_definitions\.scss\z},
targets: :common, targets: :common,
names: "color_definitions", names: "color_definitions",
types: :scss, types: :scss,
canonical: ->(h) { "common/color_definitions.scss" }, canonical: ->(h) { "common/color_definitions.scss" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^(?:scss|stylesheets)/(?<name>.+)\.scss$}, regex: %r{\A(?:scss|stylesheets)/(?<name>.+)\.scss\z},
targets: :extra_scss, targets: :extra_scss,
names: nil, names: nil,
types: :scss, types: :scss,
canonical: ->(h) { "stylesheets/#{h[:name]}.scss" }, canonical: ->(h) { "stylesheets/#{h[:name]}.scss" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^javascripts/(?<name>.+)$}, regex: %r{\Ajavascripts/(?<name>.+)\z},
targets: :extra_js, targets: :extra_js,
names: nil, names: nil,
types: :js, types: :js,
canonical: ->(h) { "javascripts/#{h[:name]}" }, canonical: ->(h) { "javascripts/#{h[:name]}" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^test/(?<name>.+)$}, regex: %r{\Atest/(?<name>.+)\z},
targets: :tests_js, targets: :tests_js,
names: nil, names: nil,
types: :js, types: :js,
canonical: ->(h) { "test/#{h[:name]}" }, canonical: ->(h) { "test/#{h[:name]}" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: /^settings\.ya?ml$/, regex: /\Asettings\.ya?ml\z/,
names: "yaml", names: "yaml",
types: :yaml, types: :yaml,
targets: :settings, targets: :settings,
canonical: ->(h) { "settings.yml" }, canonical: ->(h) { "settings.yml" },
), ),
ThemeFileMatcher.new( ThemeFileMatcher.new(
regex: %r{^locales/(?<name>(?:#{I18n.available_locales.join("|")}))\.yml$}, regex: %r{\Alocales/(?<name>(?:#{I18n.available_locales.join("|")}))\.yml\z},
names: I18n.available_locales.map(&:to_s), names: I18n.available_locales.map(&:to_s),
types: :yaml, types: :yaml,
targets: :translations, targets: :translations,

View File

@ -1200,7 +1200,7 @@ class Topic < ActiveRecord::Base
else else
!!invite_to_topic(invited_by, target_user, group_ids, guardian) !!invite_to_topic(invited_by, target_user, group_ids, guardian)
end 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( !!Invite.generate(
invited_by, invited_by,
email: username_or_email, email: username_or_email,

View File

@ -25,7 +25,7 @@ class TopicEmbed < ActiveRecord::Base
end end
def self.normalize_url(url) def self.normalize_url(url)
url.downcase.sub(%r{/$}, "").sub(/\-+/, "-").strip url.downcase.sub(%r{/\z}, "").sub(/\-+/, "-").strip
end end
def self.imported_from_html(url) def self.imported_from_html(url)
@ -36,7 +36,7 @@ class TopicEmbed < ActiveRecord::Base
# Import an article from a source (RSS/Atom/Other) # Import an article from a source (RSS/Atom/Other)
def self.import(user, url, title, contents, category_id: nil, cook_method: nil, tags: nil) 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 = first_paragraph_from(contents) if SiteSetting.embed_truncate && cook_method.nil?
contents ||= "" contents ||= ""
@ -253,7 +253,7 @@ class TopicEmbed < ActiveRecord::Base
end end
def self.topic_id_for_embed(embed_url) 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( TopicEmbed.where("embed_url ~* ?", "^https?://#{Regexp.escape(embed_url)}$").pluck_first(
:topic_id, :topic_id,
) )

View File

@ -175,7 +175,7 @@ class TopicLink < ActiveRecord::Base
lookup = {} lookup = {}
results.each do |tl| 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] = { lookup[normalized] = {
domain: tl.domain, domain: tl.domain,
username: tl.user.username_lower, username: tl.user.username_lower,

View File

@ -21,9 +21,9 @@ class TopicLinkClick < ActiveRecord::Base
uri = UrlHelper.relaxed_parse(url) uri = UrlHelper.relaxed_parse(url)
urls = Set.new urls = Set.new
urls << url urls << url
if url =~ /^http/ if url =~ /\Ahttp/
urls << url.sub(/^https/, "http") urls << url.sub(/\Ahttps/, "http")
urls << url.sub(/^http:/, "https:") urls << url.sub(/\Ahttp:/, "https:")
urls << UrlHelper.schemaless(url) urls << UrlHelper.schemaless(url)
end end
urls << UrlHelper.absolute_without_cdn(url) urls << UrlHelper.absolute_without_cdn(url)
@ -90,7 +90,7 @@ class TopicLinkClick < ActiveRecord::Base
# If no link is found... # If no link is found...
unless link.present? unless link.present?
# ... return the url for relative links or when using the same host # ... 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. # If we have it somewhere else on the site, just allow the redirect.
# This is likely due to a onebox of another topic. # This is likely due to a onebox of another topic.

View File

@ -147,7 +147,7 @@ class TranslationOverride < ActiveRecord::Base
end end
def transform_pluralized_key(key) 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 match ? match.to_a.second + ".other" : key
end end
end end

View File

@ -263,7 +263,7 @@ class Upload < ActiveRecord::Base
end end
def local? def local?
!(url =~ %r{^(https?:)?//}) !(url =~ %r{\A(https?:)?//})
end end
def fix_dimensions! def fix_dimensions!
@ -526,7 +526,7 @@ class Upload < ActiveRecord::Base
# keep track of the url # keep track of the url
previous_url = upload.url.dup previous_url = upload.url.dup
# where is the file currently stored? # where is the file currently stored?
external = previous_url =~ %r{^//} external = previous_url =~ %r{\A//}
# download if external # download if external
if external if external
url = SiteSetting.scheme + ":" + previous_url url = SiteSetting.scheme + ":" + previous_url

View File

@ -396,7 +396,7 @@ class User < ActiveRecord::Base
.reserved_usernames .reserved_usernames
.unicode_normalize .unicode_normalize
.split("|") .split("|")
.any? { |reserved| username.match?(/^#{Regexp.escape(reserved).gsub('\*', ".*")}$/) } .any? { |reserved| username.match?(/\A#{Regexp.escape(reserved).gsub('\*', ".*")}\z/) }
end end
def self.editable_user_custom_fields(by_staff: false) 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 # TODO it may be worth caching this in a distributed cache, should be benched
if SiteSetting.external_system_avatars_enabled if SiteSetting.external_system_avatars_enabled
url = SiteSetting.external_system_avatars_url.dup 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! "{color}", letter_avatar_color(normalized_username)
url.gsub! "{username}", UrlHelper.encode_component(username) url.gsub! "{username}", UrlHelper.encode_component(username)
url.gsub! "{first_letter}", url.gsub! "{first_letter}",

View File

@ -45,7 +45,7 @@ class UserProfile < ActiveRecord::Base
def bio_excerpt(length = 350, opts = {}) def bio_excerpt(length = 350, opts = {})
return nil if bio_cooked.blank? return nil if bio_cooked.blank?
excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/<br>$/, "") excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/<br>\z/, "")
return excerpt if excerpt.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?) return excerpt if excerpt.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?)
PrettyText.strip_links(excerpt) PrettyText.strip_links(excerpt)
end end

View File

@ -40,13 +40,13 @@ class UsernameValidator
errors.empty? errors.empty?
end 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 MAX_CHARS ||= 60
ASCII_INVALID_CHAR_PATTERN ||= /[^\w.-]/ ASCII_INVALID_CHAR_PATTERN ||= /[^\w.-]/
UNICODE_INVALID_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}._-]/ UNICODE_INVALID_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}._-]/
INVALID_LEADING_CHAR_PATTERN ||= /^[^\p{Alnum}\p{M}_]+/ INVALID_LEADING_CHAR_PATTERN ||= /\A[^\p{Alnum}\p{M}_]+/
INVALID_TRAILING_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}]+$/ INVALID_TRAILING_CHAR_PATTERN ||= /[^\p{Alnum}\p{M}]+\z/
REPEATED_SPECIAL_CHAR_PATTERN ||= /[-_.]{2,}/ REPEATED_SPECIAL_CHAR_PATTERN ||= /[-_.]{2,}/
private private

View File

@ -19,7 +19,7 @@ class WatchedWord < ActiveRecord::Base
before_validation do before_validation do
self.word = self.class.normalize_word(self.word) 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 = self.replacement =
"#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}" "#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}"
end end

View File

@ -113,7 +113,7 @@ class UserCardSerializer < BasicUserSerializer
end end
return if uri.nil? || uri.host.nil? return if uri.nil? || uri.host.nil?
uri.host.sub(/^www\./, "") + uri.path uri.host.sub(/\Awww\./, "") + uri.path
end end
def ignored def ignored

View File

@ -50,7 +50,7 @@ class SearchIndexer
.reduce(additional_lexemes) do |array, (lexeme, _, positions)| .reduce(additional_lexemes) do |array, (lexeme, _, positions)|
count = 0 count = 0
if lexeme !~ /^(\d+\.)?(\d+\.)*(\*|\d+)$/ if lexeme !~ /\A(\d+\.)?(\d+\.)*(\*|\d+)\z/
loop do loop do
count += 1 count += 1
break if count >= 10 # Safeguard here to prevent infinite loop when a term has many dots break if count >= 10 # Safeguard here to prevent infinite loop when a term has many dots

View File

@ -347,6 +347,6 @@ class UserUpdater
def format_url(website) def format_url(website)
return nil if website.blank? return nil if website.blank?
website =~ /^http/ ? website : "http://#{website}" website =~ /\Ahttp/ ? website : "http://#{website}"
end end
end end

View File

@ -50,7 +50,7 @@ class AdminUserIndexQuery
custom_order = params[:order] custom_order = params[:order]
if custom_order.present? && 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}" order << "#{without_dir} #{custom_direction}"
end end

View File

@ -153,7 +153,7 @@ class Autospec::Manager
filename, _ = failed_specs[0].split(":") filename, _ = failed_specs[0].split(":")
if filename && File.exist?(filename) && !File.directory?(filename) if filename && File.exist?(filename) && !File.directory?(filename)
spec = File.read(filename) spec = File.read(filename)
start, _ = spec.split(/\S*#focus\S*$/) start, _ = spec.split(/\S*#focus\S*\z/)
if start.length < spec.length if start.length < spec.length
line = start.scan(/\n/).length + 1 line = start.scan(/\n/).length + 1
puts "Found #focus tag on line #{line}!" puts "Found #focus tag on line #{line}!"
@ -194,7 +194,7 @@ class Autospec::Manager
def listen_for_changes def listen_for_changes
puts "@@@@@@@@@@@@ listen_for_changes" if @debug puts "@@@@@@@@@@@@ listen_for_changes" if @debug
options = { ignore: %r{^lib/autospec} } options = { ignore: %r{\Alib/autospec} }
if @opts[:force_polling] if @opts[:force_polling]
options[:force_polling] = true options[:force_polling] = true
@ -216,7 +216,7 @@ class Autospec::Manager
# process_change can acquire a mutex and block # process_change can acquire a mutex and block
# the acceptor # the acceptor
Thread.new do Thread.new do
if file =~ /(es6|js)$/ if file =~ /(es6|js)\z/
process_change([[file]]) process_change([[file]])
else else
process_change([[file, line]]) process_change([[file, line]])

View File

@ -10,11 +10,11 @@ class Autospec::ReloadCss
end end
# css, scss, sass or handlebars # css, scss, sass or handlebars
watch(/\.css$/) watch(/\.css\z/)
watch(/\.ca?ss\.erb$/) watch(/\.ca?ss\.erb\z/)
watch(/\.s[ac]ss$/) watch(/\.s[ac]ss\z/)
watch(/\.hbs$/) watch(/\.hbs\z/)
watch(/\.hbr$/) watch(/\.hbr\z/)
def self.message_bus def self.message_bus
MessageBus::Instance.new.tap do |bus| MessageBus::Instance.new.tap do |bus|
@ -44,7 +44,7 @@ class Autospec::ReloadCss
p = p.sub(/\.sass\.erb/, "") p = p.sub(/\.sass\.erb/, "")
p = p.sub(/\.sass/, "") p = p.sub(/\.sass/, "")
p = p.sub(/\.scss/, "") 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 } { name: p, hash: hash || SecureRandom.hex }
end end
message_bus.publish "/file-change", paths message_bus.publish "/file-change", paths

View File

@ -11,29 +11,31 @@ module Autospec
end end
# Discourse specific # 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{\Aapp/(.+)\.rb\z}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^app/(.+)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } watch(%r{\Aapp/(.+)(\.erb|\.haml)\z}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
watch(%r{^spec/.+_spec\.rb$}) watch(%r{\Aspec/.+_spec\.rb\z})
watch(%r{^spec/support/.+\.rb$}) { "spec" } watch(%r{\Aspec/support/.+\.rb\z}) { "spec" }
watch("app/controllers/application_controller.rb") { "spec/requests" } watch("app/controllers/application_controller.rb") { "spec/requests" }
watch(%r{app/controllers/(.+).rb}) { |m| "spec/requests/#{m[1]}_spec.rb" } 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" "spec/components/pretty_text_spec.rb"
end end
watch(%r{^plugins/.*/discourse-markdown/.*\.js\.es6$}) { "spec/components/pretty_text_spec.rb" }
watch(%r{^plugins/.*/spec/.*\.rb}) watch(%r{\Aplugins/.*/spec/.*\.rb})
watch(%r{^(plugins/.*/)plugin\.rb}) { |m| "#{m[1]}spec" } watch(%r{\A(plugins/.*/)plugin\.rb}) { |m| "#{m[1]}spec" }
watch(%r{^(plugins/.*)/(lib|app)}) { |m| "#{m[1]}/spec/integration" } watch(%r{\A(plugins/.*)/(lib|app)}) { |m| "#{m[1]}/spec/integration" }
watch(%r{^(plugins/.*)/lib/(.*)\.rb}) { |m| "#{m[1]}/spec/lib/#{m[2]}_spec.rb" } watch(%r{\A(plugins/.*)/lib/(.*)\.rb}) { |m| "#{m[1]}/spec/lib/#{m[2]}_spec.rb" }
RELOADERS = Set.new RELOADERS = Set.new
def self.reload(pattern) def self.reload(pattern)

View File

@ -29,7 +29,7 @@ module Autospec
# launch rspec # launch rspec
Dir.chdir(Rails.root) do # rubocop:disable Discourse/NoChdir because this is not part of the app Dir.chdir(Rails.root) do # rubocop:disable Discourse/NoChdir because this is not part of the app
env = { "RAILS_ENV" => "test" } 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" env["LOAD_PLUGINS"] = "1"
puts "Loading plugins while running specs" puts "Loading plugins while running specs"
end end

View File

@ -11,7 +11,7 @@ module BackupRestore
@filename = filename @filename = filename
@current_db = current_db @current_db = current_db
@root_tmp_directory = root_tmp_directory @root_tmp_directory = root_tmp_directory
@is_archive = !(@filename =~ /\.sql\.gz$/) @is_archive = !(@filename =~ /\.sql\.gz\z/)
@store_location = location @store_location = location
end end

View File

@ -164,7 +164,7 @@ module BackupRestore
DatabaseRestorer.core_migration_files.each do |path| DatabaseRestorer.core_migration_files.each do |path|
require 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 migration_class = class_name.constantize
if migration_class.const_defined?(:DROPPED_TABLES) if migration_class.const_defined?(:DROPPED_TABLES)

View File

@ -173,7 +173,7 @@ module BackupRestore
path = Regexp.quote(path) path = Regexp.quote(path)
end end
%r{^#{path}[^/]*\.t?gz$}i %r{\A#{path}[^/]*\.t?gz\z}i
end end
end end

View File

@ -8,7 +8,7 @@ class ComposerMessagesFinder
end end
def self.check_methods def self.check_methods
@check_methods ||= instance_methods.find_all { |m| m =~ /^check\_/ } @check_methods ||= instance_methods.find_all { |m| m =~ /\Acheck\_/ }
end end
def find def find

View File

@ -38,7 +38,7 @@ module Compression
def build_entry_path(dest_path, _, compressed_file_path) def build_entry_path(dest_path, _, compressed_file_path)
basename = File.basename(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) File.join(dest_path, basename)
end end

View File

@ -80,7 +80,7 @@ class ContentSecurityPolicy
uri.query = nil # CSP should not include query part of url 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 auto_script_src_extension[:script_src] << uri_string
rescue URI::Error rescue URI::Error

View File

@ -242,10 +242,10 @@ class CookedPostProcessor
if !cropped && upload.width && resized_w > upload.width if !cropped && upload.width && resized_w > upload.width
cooked_url = UrlHelper.cook_url(upload.url, secure: @post.with_secure_uploads?) 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) elsif t = upload.thumbnail(resized_w, resized_h)
cooked_url = UrlHelper.cook_url(t.url, secure: @post.with_secure_uploads?) 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 end
img[ img[
@ -295,7 +295,7 @@ class CookedPostProcessor
def get_filename(upload, src) def get_filename(upload, src)
return File.basename(src) unless upload 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") I18n.t("upload.pasted_image_filename")
end end

View File

@ -174,7 +174,7 @@ module CookedProcessorMixin
return @size_cache[url] if @size_cache.has_key?(url) return @size_cache[url] if @size_cache.has_key?(url)
absolute_url = 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 return unless absolute_url

View File

@ -99,7 +99,7 @@ class DiscourseConnectBase
end end
decoded_hash.each do |k, v| decoded_hash.each do |k, v|
if field = k[/^custom\.(.+)$/, 1] if field = k[/\Acustom\.(.+)\z/, 1]
sso.custom_fields[field] = v sso.custom_fields[field] = v
end end
end end

View File

@ -160,7 +160,7 @@ class DiscourseDiff
while i < text.size while i < text.size
if text[i] =~ /\w/ if text[i] =~ /\w/
t << text[i] t << text[i]
elsif text[i] =~ /[ \t]/ && t.join =~ /^\w+$/ elsif text[i] =~ /[ \t]/ && t.join =~ /\A\w+\z/
begin begin
t << text[i] t << text[i]
i += 1 i += 1

View File

@ -158,8 +158,8 @@ class DiscoursePluginRegistry
end end
end end
JS_REGEX = /\.js$|\.js\.erb$|\.js\.es6$/ JS_REGEX = /\.js$|\.js\.erb$|\.js\.es6\z/
HANDLEBARS_REGEX = /\.(hb[rs]|js\.handlebars)$/ HANDLEBARS_REGEX = /\.(hb[rs]|js\.handlebars)\z/
def self.register_asset(asset, opts = nil, plugin_directory_name = nil) def self.register_asset(asset, opts = nil, plugin_directory_name = nil)
if asset =~ JS_REGEX if asset =~ JS_REGEX
@ -172,7 +172,7 @@ class DiscoursePluginRegistry
else else
self.javascripts << asset self.javascripts << asset
end end
elsif asset =~ /\.css$|\.scss$/ elsif asset =~ /\.css$|\.scss\z/
if opts == :mobile if opts == :mobile
self.mobile_stylesheets[plugin_directory_name] ||= Set.new self.mobile_stylesheets[plugin_directory_name] ||= Set.new
self.mobile_stylesheets[plugin_directory_name] << asset self.mobile_stylesheets[plugin_directory_name] << asset

View File

@ -276,7 +276,7 @@ class DiscourseRedis
def eval(redis, *args, **kwargs) def eval(redis, *args, **kwargs)
redis.evalsha @sha1, *args, **kwargs redis.evalsha @sha1, *args, **kwargs
rescue ::Redis::CommandError => e rescue ::Redis::CommandError => e
if e.to_s =~ /^NOSCRIPT/ if e.to_s =~ /\ANOSCRIPT/
redis.eval @script, *args, **kwargs redis.eval @script, *args, **kwargs
else else
raise raise

View File

@ -136,7 +136,7 @@ module Email
def message_id_clean(message_id) def message_id_clean(message_id)
if message_id.present? && is_message_id_rfc?(message_id) if message_id.present? && is_message_id_rfc?(message_id)
message_id.gsub(/^<|>$/, "") message_id.gsub(/\A<|>\z/, "")
else else
message_id message_id
end end

View File

@ -381,7 +381,7 @@ module Email
@mail[:precedence].to_s[/list|junk|bulk|auto_reply/i] || @mail[:precedence].to_s[/list|junk|bulk|auto_reply/i] ||
@mail[:from].to_s[/(mailer[\-_]?daemon|post[\-_]?master|no[\-_]?reply)@/i] || @mail[:from].to_s[/(mailer[\-_]?daemon|post[\-_]?master|no[\-_]?reply)@/i] ||
@mail[:subject].to_s[ @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[ @mail.header.to_s[
/auto[\-_]?(response|submitted|replied|reply|generated|respond)|holidayreply|machinegenerated/i /auto[\-_]?(response|submitted|replied|reply|generated|respond)|holidayreply|machinegenerated/i
@ -393,7 +393,7 @@ module Email
when "X-Spam-Flag" when "X-Spam-Flag"
@mail[:x_spam_flag].to_s[/YES/i] @mail[:x_spam_flag].to_s[/YES/i]
when "X-Spam-Status" 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" when "X-SES-Spam-Verdict"
@mail[:x_ses_spam_verdict].to_s[/FAIL/i] @mail[:x_ses_spam_verdict].to_s[/FAIL/i]
else else
@ -639,7 +639,7 @@ module Email
.uniq .uniq
@previous_replies_regex ||= @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 end
def reply_above_line_regex def reply_above_line_regex
@ -747,12 +747,12 @@ module Email
if value[/<[^>]+>/] if value[/<[^>]+>/]
from_address = value[/<([^>]+)>/, 1] from_address = value[/<([^>]+)>/, 1]
from_display_name = value[/^([^<]+)/, 1] from_display_name = value[/\A([^<]+)/, 1]
end end
if (from_address.blank? || !from_address["@"]) && value[/\[mailto:[^\]]+\]/] if (from_address.blank? || !from_address["@"]) && value[/\[mailto:[^\]]+\]/]
from_address = value[/\[mailto:([^\]]+)\]/, 1] from_address = value[/\[mailto:([^\]]+)\]/, 1]
from_display_name = value[/^([^\[]+)/, 1] from_display_name = value[/\A([^\[]+)/, 1]
end end
[from_address&.downcase, from_display_name&.strip] [from_address&.downcase, from_display_name&.strip]
@ -1016,7 +1016,7 @@ module Email
end end
def has_been_forwarded? 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 end
def embedded_email_raw def embedded_email_raw

View File

@ -84,9 +84,9 @@ module Email
if img["src"] if img["src"]
# ensure all urls are absolute # 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 # 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
end end
@ -110,7 +110,7 @@ module Email
.css("a.attachment") .css("a.attachment")
.each do |a| .each do |a|
# ensure all urls are absolute # 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 # ensure no schemaless urls
a["href"] = "#{uri.scheme}:#{a["href"]}" if a["href"] && a["href"].starts_with?("//") a["href"] = "#{uri.scheme}:#{a["href"]}" if a["href"] && a["href"].starts_with?("//")

View File

@ -4,7 +4,7 @@
class EmailCook class EmailCook
def self.raw_regexp def self.raw_regexp
@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 end
def initialize(raw) def initialize(raw)
@ -14,7 +14,7 @@ class EmailCook
def add_quote(result, buffer) def add_quote(result, buffer)
if buffer.present? if buffer.present?
return if buffer =~ /\A(<br>)+\z$/ return if buffer =~ /\A(<br>)+\z\z/
result << "<blockquote>#{buffer}</blockquote>" result << "<blockquote>#{buffer}</blockquote>"
end end
end end
@ -22,7 +22,7 @@ class EmailCook
def link_string!(line, unescaped_line) def link_string!(line, unescaped_line)
unescaped_line = unescaped_line.strip unescaped_line = unescaped_line.strip
line.gsub!(/\S+/) do |str| line.gsub!(/\S+/) do |str|
if str.match?(%r{^(https?://)[\S]+$}i) if str.match?(%r{\A(https?://)[\S]+\z}i)
begin begin
url = URI.parse(str).to_s url = URI.parse(str).to_s
if unescaped_line == url if unescaped_line == url
@ -48,11 +48,11 @@ class EmailCook
text.each_line do |line| text.each_line do |line|
# replace indentation with non-breaking spaces # 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 in_quote = true
line.sub!(/^[\s>]*/, "") line.sub!(/\A[\s>]*/, "")
unescaped_line = line unescaped_line = line
line = CGI.escapeHTML(line) line = CGI.escapeHTML(line)

View File

@ -52,7 +52,7 @@ class FileHelper
retain_on_max_file_size_exceeded: false retain_on_max_file_size_exceeded: false
) )
url = "https:" + url if url.start_with?("//") 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 tmp = nil
@ -175,26 +175,26 @@ class FileHelper
end end
def self.supported_video_regexp 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 end
def self.supported_audio_regexp 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 end
def self.supported_images_regexp 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 end
def self.inline_images_regexp 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 end
def self.supported_media_regexp def self.supported_media_regexp
@@supported_media_regexp ||= @@supported_media_regexp ||=
begin begin
media = supported_images | supported_audio | supported_video media = supported_images | supported_audio | supported_video
/\.(#{media.to_a.join("|")})$/i /\.(#{media.to_a.join("|")})\z/i
end end
end end
@ -202,7 +202,7 @@ class FileHelper
@@supported_playable_media_regexp ||= @@supported_playable_media_regexp ||=
begin begin
media = supported_audio | supported_video media = supported_audio | supported_video
/\.(#{media.to_a.join("|")})$/i /\.(#{media.to_a.join("|")})\z/i
end end
end end
end end

View File

@ -113,7 +113,7 @@ module FileStore
end end
) )
url = SiteSetting.scheme + ":" + url if url =~ %r{^//} url = SiteSetting.scheme + ":" + url if url =~ %r{\A//}
file = file =
FileHelper.download( FileHelper.download(
url, url,

View File

@ -128,7 +128,7 @@ module FileStore
count = 0 count = 0
model.find_each do |upload| model.find_each do |upload|
# could be a remote image # could be a remote image
next unless upload.url =~ %r{^/[^/]} next unless upload.url =~ %r{\A/[^/]}
path = "#{public_dir}#{upload.url}" path = "#{public_dir}#{upload.url}"
bad = true bad = true

View File

@ -216,7 +216,7 @@ module FileStore
def path_for(upload) def path_for(upload)
url = upload&.url 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 end
def url_for(upload, force_download: false) def url_for(upload, force_download: false)
@ -233,7 +233,7 @@ module FileStore
def cdn_url(url) def cdn_url(url)
return url if SiteSetting.Upload.s3_cdn_url.blank? 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}/" folder = s3_bucket_folder_path.nil? ? "" : "#{s3_bucket_folder_path}/"
url.sub( url.sub(
File.join("#{schema}#{absolute_base_url}", folder), File.join("#{schema}#{absolute_base_url}", folder),

View File

@ -39,7 +39,7 @@ module I18n
if @loaded_locales.empty? if @loaded_locales.empty?
# load all rb files # 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 # load plural rules from plugins
DiscoursePluginRegistry.locales.each do |plugin_locale, options| DiscoursePluginRegistry.locales.each do |plugin_locale, options|
@ -50,14 +50,14 @@ module I18n
end end
# load it # 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? if Discourse.allow_dev_populate?
I18n.backend.load_translations( 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.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 end

View File

@ -10,7 +10,7 @@ module GitUrl
end end
if url.start_with?("https://github.com/") && !url.end_with?(".git") if url.start_with?("https://github.com/") && !url.end_with?(".git")
url = url.gsub(%r{/$}, "") url = url.gsub(%r{/\z}, "")
url += ".git" url += ".git"
end end

View File

@ -12,7 +12,7 @@ module GlobalPath
def upload_cdn_path(p) def upload_cdn_path(p)
p = Discourse.store.cdn_url(p) if SiteSetting.Upload.s3_cdn_url.present? 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 end
def cdn_relative_path(path) def cdn_relative_path(path)

View File

@ -3,7 +3,7 @@
# Support for ensure_{blah}! methods. # Support for ensure_{blah}! methods.
module EnsureMagic module EnsureMagic
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
if method.to_s =~ /^ensure_(.*)\!$/ if method.to_s =~ /\Aensure_(.*)\!\z/
can_method = :"#{Regexp.last_match[1]}?" can_method = :"#{Regexp.last_match[1]}?"
if respond_to?(can_method) if respond_to?(can_method)

View File

@ -249,8 +249,8 @@ class HtmlPrettify < String
# Special case if the very first character is a quote followed by # Special case if the very first character is a quote followed by
# punctuation at a non-word-break. Close the quotes by brute # punctuation at a non-word-break. Close the quotes by brute
# force: # force:
str.gsub!(/^'(?=#{punct_class}\B)/, entity(:single_right_quote)) str.gsub!(/\A'(?=#{punct_class}\B)/, entity(:single_right_quote))
str.gsub!(/^"(?=#{punct_class}\B)/, entity(:double_right_quote)) str.gsub!(/\A"(?=#{punct_class}\B)/, entity(:double_right_quote))
# Special case for double sets of quotes, e.g.: # Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p> # <p>He said, "'Quoted' words in a larger quote."</p>

View File

@ -49,7 +49,7 @@ class LocaleFileChecker
end end
def reference_file(path) 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) path if File.exist?(path)
end end

View File

@ -25,8 +25,8 @@ module Middleware
def self.compile_key_builder def self.compile_key_builder
method = +"def self.__compiled_key_builder(h)\n \"" method = +"def self.__compiled_key_builder(h)\n \""
cache_key_segments.each do |k, v| cache_key_segments.each do |k, v|
raise "Invalid key name" unless k =~ /^[a-z]+$/ raise "Invalid key name" unless k =~ /\A[a-z]+\z/
raise "Invalid method name" unless v =~ /^key_[a-z_\?]+$/ raise "Invalid method name" unless v =~ /\Akey_[a-z_\?]+\z/
method << "|#{k}=#\{h.#{v}}" method << "|#{k}=#\{h.#{v}}"
end end
method << "\"\nend" method << "\"\nend"

View File

@ -11,7 +11,7 @@ module Middleware
end end
def call(env) 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"]}" path = "#{Rails.root}/public#{env["REQUEST_PATH"]}"
unless File.exist?(path) unless File.exist?(path)
default_image = "#{Rails.root}/public/images/d-logo-sketch-small.png" default_image = "#{Rails.root}/public/images/d-logo-sketch-small.png"

View File

@ -116,7 +116,7 @@ class Migration::SafeMigrate
end end
def self.protect!(sql) 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) $stdout.puts("", <<~TEXT)
WARNING WARNING
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
@ -129,7 +129,7 @@ class Migration::SafeMigrate
in use by live applications. in use by live applications.
TEXT TEXT
raise Discourse::InvalidMigration, "Attempt was made to drop a table" 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) $stdout.puts("", <<~TEXT)
WARNING WARNING
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------

View File

@ -7,7 +7,7 @@ module Onebox
end end
def self.engines 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 end
def self.all_iframe_origins def self.all_iframe_origins

View File

@ -15,7 +15,7 @@ module Onebox
@record = Onebox::Helpers.symbolize_keys(record) @record = Onebox::Helpers.symbolize_keys(record)
# Fix any relative paths # 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]}" @record[:image] = "#{uri.scheme}://#{uri.host}/#{@record[:image]}"
end end
@ -40,7 +40,7 @@ module Onebox
link: record[:link], link: record[:link],
title: record[:title], title: record[:title],
favicon: record[:favicon], 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: record[:article_published_time],
article_published_time_title: record[:article_published_time_title], article_published_time_title: record[:article_published_time_title],
metadata_1_label: record[:metadata_1_label], metadata_1_label: record[:metadata_1_label],

View File

@ -119,7 +119,7 @@ module Onebox
a_lines = str.lines a_lines = str.lines
a_lines.each do |l| a_lines.each do |l|
l = l.chomp("\n") # remove new line 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 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 m_str_length = m[0].size
if m_str_length <= 1 # minimum space is 1 or nothing we can break we found our minimum 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] @file = m[:file]
@lang = Onebox::FileTypeFinder.from_file_name(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 @model_file = @lang.dup
@raw = "https://render.githubusercontent.com/view/solid?url=" + self.raw_template(m) @raw = "https://render.githubusercontent.com/view/solid?url=" + self.raw_template(m)
else else

View File

@ -32,8 +32,8 @@ module Onebox
doc doc
.css("meta") .css("meta")
.each do |m| .each do |m|
if (m["property"] && m["property"][/^(?:og|article|product):(.+)$/i]) || if (m["property"] && m["property"][/\A(?:og|article|product):(.+)\z/i]) ||
(m["name"] && m["name"][/^(?:og|article|product):(.+)$/i]) (m["name"] && m["name"][/\A(?:og|article|product):(.+)\z/i])
value = (m["content"] || m["value"]).to_s value = (m["content"] || m["value"]).to_s
next if Onebox::Helpers.blank?(value) next if Onebox::Helpers.blank?(value)
key = $1.tr("-:", "_").to_sym key = $1.tr("-:", "_").to_sym

View File

@ -58,7 +58,7 @@ module Onebox
next unless env[:node_name] == "a" next unless env[:node_name] == "a"
a_tag = env[:node] a_tag = env[:node]
a_tag["href"] ||= "#" a_tag["href"] ||= "#"
if a_tag["href"] =~ %r{^(?:[a-z]+:)?//} if a_tag["href"] =~ %r{\A(?:[a-z]+:)?//}
a_tag["rel"] = "nofollow ugc noopener" a_tag["rel"] = "nofollow ugc noopener"
else else
a_tag.remove_attribute("target") a_tag.remove_attribute("target")

View File

@ -6,8 +6,8 @@ Dir["#{Rails.root}/lib/onebox/engine/*_onebox.rb"].sort.each { |f| require f }
module Oneboxer module Oneboxer
ONEBOX_CSS_CLASS = "onebox" ONEBOX_CSS_CLASS = "onebox"
AUDIO_REGEX = /^\.(mp3|og[ga]|opus|wav|m4[abpr]|aac|flac)$/i AUDIO_REGEX = /\A\.(mp3|og[ga]|opus|wav|m4[abpr]|aac|flac)\z/i
VIDEO_REGEX = /^\.(mov|mp4|webm|m4v|3gp|ogv|avi|mpeg|ogv)$/i VIDEO_REGEX = /\A\.(mov|mp4|webm|m4v|3gp|ogv|avi|mpeg|ogv)\z/i
# keep reloaders happy # keep reloaders happy
unless defined?(Oneboxer::Result) unless defined?(Oneboxer::Result)

View File

@ -100,7 +100,7 @@ class PlainTextToMarkdown
# @param line [Line] # @param line [Line]
def remove_quote_level_indicators!(line) def remove_quote_level_indicators!(line)
match_data = line.text.match(/^(?<indicators>>+)\s?(?<text>.*)/) match_data = line.text.match(/\A(?<indicators>>+)\s?(?<text>.*)/)
if match_data if match_data
line.text = match_data[:text] line.text = match_data[:text]
@ -128,7 +128,7 @@ class PlainTextToMarkdown
def classify_line_as_code!(line, previous_line) def classify_line_as_code!(line, previous_line)
line.code_block = previous_line.code_block unless previous_line.nil? || line.code_block = previous_line.code_block unless previous_line.nil? ||
previous_line.valid_code_block? 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? if line.code_block.present?
line.code_block.end_line = line line.code_block.end_line = line
@ -173,7 +173,7 @@ class PlainTextToMarkdown
end end
def indent_with_non_breaking_spaces(text) def indent_with_non_breaking_spaces(text)
text.sub(/^\s+/) do |s| text.sub(/\A\s+/) do |s|
# replace tabs with 2 spaces # replace tabs with 2 spaces
s.gsub!("\t", " ") s.gsub!("\t", " ")

View File

@ -675,17 +675,17 @@ class Plugin::Instance
DiscoursePluginRegistry.register_glob(admin_path, "hbr", admin: true) DiscoursePluginRegistry.register_glob(admin_path, "hbr", admin: true)
DiscourseJsProcessor.plugin_transpile_paths << root_path.sub(Rails.root.to_s, "").sub( 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( DiscourseJsProcessor.plugin_transpile_paths << admin_path.sub(Rails.root.to_s, "").sub(
%r{^/*}, %r{\A/*},
"", "",
) )
test_path = "#{root_dir_name}/test/javascripts" test_path = "#{root_dir_name}/test/javascripts"
DiscourseJsProcessor.plugin_transpile_paths << test_path.sub(Rails.root.to_s, "").sub( DiscourseJsProcessor.plugin_transpile_paths << test_path.sub(Rails.root.to_s, "").sub(
%r{^/*}, %r{\A/*},
"", "",
) )
end end
@ -1299,7 +1299,7 @@ class Plugin::Instance
private private
def validate_directory_column_name(column_name) def validate_directory_column_name(column_name)
match = /^[_a-z]+$/.match(column_name) match = /\A[_a-z]+\z/.match(column_name)
unless match unless match
raise "Invalid directory column name '#{column_name}'. Can only contain a-z and underscores" raise "Invalid directory column name '#{column_name}'. Can only contain a-z and underscores"
end end

View File

@ -48,7 +48,7 @@ module PrettyText
filename = find_file(root_path, part_name) filename = find_file(root_path, part_name)
if filename if filename
source = File.read("#{root_path}#{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 transpiler = DiscourseJsProcessor::Transpiler.new
transpiled = transpiler.perform(source, "#{Rails.root}/app/assets/javascripts/", part_name) transpiled = transpiler.perform(source, "#{Rails.root}/app/assets/javascripts/", part_name)
@ -64,7 +64,7 @@ module PrettyText
def self.ctx_load_directory(ctx, path) def self.ctx_load_directory(ctx, path)
root_path = "#{Rails.root}/app/assets/javascripts/" root_path = "#{Rails.root}/app/assets/javascripts/"
Dir["#{root_path}#{path}/**/*"].sort.each do |f| 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
end end
@ -116,9 +116,9 @@ module PrettyText
to_load << a if File.file?(a) && a =~ /discourse-markdown/ to_load << a if File.file?(a) && a =~ /discourse-markdown/
end end
to_load.uniq.each do |f| to_load.uniq.each do |f|
if f =~ %r{^.+assets/javascripts/} if f =~ %r{\A.+assets/javascripts/}
root = Regexp.last_match[0] 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
end end

View File

@ -102,7 +102,7 @@ module PrettyText
# TODO (martin) Remove this when everything is using hashtag_lookup # TODO (martin) Remove this when everything is using hashtag_lookup
# after enable_experimental_hashtag_autocomplete is default. # after enable_experimental_hashtag_autocomplete is default.
def category_tag_hashtag_lookup(text) 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) if !is_tag && category = Category.query_from_hashtag_slug(text)
[category.url, text] [category.url, text]

View File

@ -14,7 +14,7 @@ module RequireDependencyBackwardCompatibility
def require_dependency(filename) def require_dependency(filename)
name = filename.to_s name = filename.to_s
return if name == "jobs/base" 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 super
end end

View File

@ -32,7 +32,7 @@ module RetrieveTitle
# A horrible hack - YouTube uses `document.title` to populate the title # 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. # for some reason. For any other site than YouTube this wouldn't be worth it.
if title == "YouTube" && html =~ /document\.title *= *"(.*)";/ if title == "YouTube" && html =~ /document\.title *= *"(.*)";/
title = Regexp.last_match[1].sub(/ - YouTube$/, "") title = Regexp.last_match[1].sub(/ - YouTube\z/, "")
end end
if !title && node = doc.at('meta[property="og:title"]') if !title && node = doc.at('meta[property="og:title"]')
@ -53,11 +53,12 @@ module RetrieveTitle
def self.max_chunk_size(uri) def self.max_chunk_size(uri)
# Exception for sites that leave the title until very late. # 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 return 500
end end
return 300 if uri.host =~ /(^|\.)youtube\.com$/ || uri.host =~ /(^|\.)youtu\.be$/ return 300 if uri.host =~ /(^|\.)youtube\.com\z/ || uri.host =~ /(^|\.)youtu\.be\z/
return 50 if uri.host =~ /(^|\.)github\.com$/ return 50 if uri.host =~ /(^|\.)github\.com\z/
# default is 20k # default is 20k
20 20

View File

@ -46,7 +46,7 @@ class RouteMatcher
return true if actions.nil? # actions are unrestricted return true if actions.nil? # actions are unrestricted
# message_bus is not a rails route, special handling # 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) path_params = path_params_from_request(request)
actions.include? "#{path_params[:controller]}##{path_params[:action]}" actions.include? "#{path_params[:controller]}##{path_params[:action]}"

View File

@ -334,7 +334,7 @@ class S3Inventory
objects = [] objects = []
hive_path = File.join(inventory_path, bucket_name, inventory_id, "hive") 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 objects
rescue Aws::Errors::ServiceError => e rescue Aws::Errors::ServiceError => e

View File

@ -128,7 +128,7 @@ class Search
end end
data.gsub!(/\S+/) do |str| data.gsub!(/\S+/) do |str|
if str =~ %r{^["]?((https?://)[\S]+)["]?$} if str =~ %r{\A["]?((https?://)[\S]+)["]?\z}
begin begin
uri = URI.parse(Regexp.last_match[1]) uri = URI.parse(Regexp.last_match[1])
uri.query = nil uri.query = nil
@ -145,9 +145,9 @@ class Search
end end
def self.word_to_date(str) 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 year = $1.to_i
month = $2 ? $3.to_i : 1 month = $2 ? $3.to_i : 1
day = $4 ? $5.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 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 @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) single_topic(@term.to_i)
else else
if route = Discourse.route_for(@term) if route = Discourse.route_for(@term)
@ -355,7 +355,7 @@ class Search
Array.wrap(@custom_topic_eager_loads) Array.wrap(@custom_topic_eager_loads)
end end
advanced_filter(/^in:personal-direct$/i) do |posts| advanced_filter(/\Ain:personal-direct\z/i) do |posts|
if @guardian.user if @guardian.user
posts.joins("LEFT JOIN topic_allowed_groups tg ON posts.topic_id = tg.topic_id").where( posts.joins("LEFT JOIN topic_allowed_groups tg ON posts.topic_id = tg.topic_id").where(
<<~SQL, <<~SQL,
@ -376,60 +376,60 @@ class Search
end end
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)") posts.where("EXISTS (SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = posts.topic_id)")
end end
advanced_filter(/^in:untagged$/i) do |posts| advanced_filter(/\Ain:untagged\z/i) do |posts|
posts.joins( posts.joins(
"LEFT JOIN topic_tags ON "LEFT JOIN topic_tags ON
topic_tags.topic_id = posts.topic_id", topic_tags.topic_id = posts.topic_id",
).where("topic_tags.id IS NULL") ).where("topic_tags.id IS NULL")
end 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") posts.where("NOT topics.closed AND NOT topics.archived")
end 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) category_ids = Category.where(read_restricted: false).pluck(:id)
posts.where("topics.category_id in (?)", category_ids) posts.where("topics.category_id in (?)", category_ids)
end 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) posts.where("topics.posts_count = ?", match.to_i)
end 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) posts.where("topics.posts_count >= ?", match.to_i)
end 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) posts.where("topics.posts_count >= ?", match.to_i)
end 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) posts.where("topics.posts_count <= ?", match.to_i)
end 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) badge_id = Badge.where("name ilike ? OR id = ?", match, match.to_i).pluck_first(:id)
if badge_id if badge_id
posts.where( posts.where(
@ -454,7 +454,7 @@ class Search
) )
end 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 post_action_type_filter(posts, PostActionType.types[:like]) if @guardian.user
end end
@ -462,7 +462,7 @@ class Search
# this at some point, as it only acts on posts at the moment. On the other # 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 # hand, this may not be necessary, as the user bookmark list has advanced
# search based on a RegisteredBookmarkable's #search_query method. # 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.where(<<~SQL, @guardian.user.id) if @guardian.user
posts.id IN ( posts.id IN (
SELECT bookmarkable_id FROM bookmarks SELECT bookmarkable_id FROM bookmarks
@ -471,20 +471,20 @@ class Search
SQL SQL
end 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 posts.where("posts.user_id = ?", @guardian.user.id) if @guardian.user
end 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 posts.where(user_id: @guardian.user.id, post_number: 1) if @guardian.user
end 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) user_id = User.where(username: match.downcase).pluck_first(:id)
posts.where(user_id: user_id, post_number: 1) posts.where(user_id: user_id, post_number: 1)
end end
advanced_filter(/^in:(watching|tracking)$/i) do |posts, match| advanced_filter(/\Ain:(watching|tracking)\z/i) do |posts, match|
if @guardian.user if @guardian.user
level = TopicUser.notification_levels[match.downcase.to_sym] level = TopicUser.notification_levels[match.downcase.to_sym]
posts.where( posts.where(
@ -499,7 +499,7 @@ class Search
end end
end end
advanced_filter(/^in:seen$/i) do |posts| advanced_filter(/\Ain:seen\z/i) do |posts|
if @guardian.user if @guardian.user
posts.joins( posts.joins(
"INNER JOIN post_timings ON "INNER JOIN post_timings ON
@ -511,7 +511,7 @@ class Search
end end
end end
advanced_filter(/^in:unseen$/i) do |posts| advanced_filter(/\Ain:unseen\z/i) do |posts|
if @guardian.user if @guardian.user
posts.joins( posts.joins(
"LEFT JOIN post_timings ON "LEFT JOIN post_timings ON
@ -523,9 +523,9 @@ class Search
end end
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 exact = false
if match[0] == "=" if match[0] == "="
@ -544,7 +544,7 @@ class Search
end end
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(":") category_slug, subcategory_slug = match.to_s.split(":")
next unless category_slug next unless category_slug
@ -614,7 +614,7 @@ class Search
end end
end end
advanced_filter(/^group:(.+)$/i) do |posts, match| advanced_filter(/\Agroup:(.+)\z/i) do |posts, match|
group_query = group_query =
Group Group
.visible_groups(@guardian.user) .visible_groups(@guardian.user)
@ -637,7 +637,7 @@ class Search
end end
end end
advanced_filter(/^group_messages:(.+)$/i) do |posts, match| advanced_filter(/\Agroup_messages:(.+)\z/i) do |posts, match|
group_id = group_id =
Group Group
.visible_groups(@guardian.user) .visible_groups(@guardian.user)
@ -656,7 +656,7 @@ class Search
end end
end end
advanced_filter(/^user:(.+)$/i) do |posts, match| advanced_filter(/\Auser:(.+)\z/i) do |posts, match|
user_id = user_id =
User User
.where(staged: false) .where(staged: false)
@ -669,7 +669,7 @@ class Search
end end
end end
advanced_filter(/^\@(\S+)$/i) do |posts, match| advanced_filter(/\A\@(\S+)\z/i) do |posts, match|
username = User.normalize_username(match) username = User.normalize_username(match)
user_id = User.not_staged.where(username_lower: username).pluck_first(:id) user_id = User.not_staged.where(username_lower: username).pluck_first(:id)
@ -683,7 +683,7 @@ class Search
end end
end end
advanced_filter(/^before:(.*)$/i) do |posts, match| advanced_filter(/\Abefore:(.*)\z/i) do |posts, match|
if date = Search.word_to_date(match) if date = Search.word_to_date(match)
posts.where("posts.created_at < ?", date) posts.where("posts.created_at < ?", date)
else else
@ -691,7 +691,7 @@ class Search
end end
end end
advanced_filter(/^after:(.*)$/i) do |posts, match| advanced_filter(/\Aafter:(.*)\z/i) do |posts, match|
if date = Search.word_to_date(match) if date = Search.word_to_date(match)
posts.where("posts.created_at > ?", date) posts.where("posts.created_at > ?", date)
else else
@ -699,15 +699,15 @@ class Search
end end
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) search_tags(posts, match, positive: true)
end 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) search_tags(posts, match, positive: false)
end 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) file_extensions = match.split(",").map(&:downcase)
posts.where( posts.where(
"posts.id IN ( "posts.id IN (
@ -726,11 +726,11 @@ class Search
) )
end 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) posts.where("topics.views >= ?", match.to_i)
end 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) posts.where("topics.views <= ?", match.to_i)
end end
@ -789,38 +789,38 @@ class Search
if word == "l" if word == "l"
@order = :latest @order = :latest
nil nil
elsif word =~ /^order:\w+$/i elsif word =~ /\Aorder:\w+\z/i
@order = word.downcase.gsub("order:", "").to_sym @order = word.downcase.gsub("order:", "").to_sym
nil nil
elsif word =~ /^in:title$/i || word == "t" elsif word =~ /\Ain:title\z/i || word == "t"
@in_title = true @in_title = true
nil nil
elsif word =~ /^topic:(\d+)$/i elsif word =~ /\Atopic:(\d+)\z/i
topic_id = $1.to_i topic_id = $1.to_i
if topic_id > 1 if topic_id > 1
topic = Topic.find_by(id: topic_id) topic = Topic.find_by(id: topic_id)
@search_context = topic if @guardian.can_see?(topic) @search_context = topic if @guardian.can_see?(topic)
end end
nil nil
elsif word =~ /^in:all$/i elsif word =~ /\Ain:all\z/i
@search_all_topics = true @search_all_topics = true
nil nil
elsif word =~ /^in:personal$/i elsif word =~ /\Ain:personal\z/i
@search_pms = true @search_pms = true
nil nil
elsif word =~ /^in:messages$/i elsif word =~ /\Ain:messages\z/i
@search_pms = true @search_pms = true
nil nil
elsif word =~ /^in:personal-direct$/i elsif word =~ /\Ain:personal-direct\z/i
@search_pms = true @search_pms = true
nil nil
elsif word =~ /^in:all-pms$/i elsif word =~ /\Ain:all-pms\z/i
@search_all_pms = true @search_all_pms = true
nil nil
elsif word =~ /^group_messages:(.+)$/i elsif word =~ /\Agroup_messages:(.+)\z/i
@search_pms = true @search_pms = true
nil nil
elsif word =~ /^personal_messages:(.+)$/i elsif word =~ /\Apersonal_messages:(.+)\z/i
if user = User.find_by_username($1) if user = User.find_by_username($1)
@search_pms = true @search_pms = true
@search_context = user @search_context = user

View File

@ -98,7 +98,7 @@ class ShrinkUploadedImage
elsif !post.topic || post.topic.trashed? elsif !post.topic || post.topic.trashed?
log "A deleted topic" log "A deleted topic"
elsif post.cooked.include?(original_upload.sha1) 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" log "Updating a topic onebox"
else else
log "Updating an external onebox" log "Updating an external onebox"

View File

@ -243,7 +243,7 @@ module SiteSettings::Validations
def validate_cors_origins(new_val) def validate_cors_origins(new_val)
return if new_val.blank? 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 validate_error :cors_origins_should_not_have_trailing_slash
end end

View File

@ -11,7 +11,7 @@ class Stylesheet::Manager
CACHE_PATH ||= "tmp/stylesheet-cache" CACHE_PATH ||= "tmp/stylesheet-cache"
MANIFEST_DIR ||= "#{Rails.root}/tmp/cache/assets/#{Rails.env}" MANIFEST_DIR ||= "#{Rails.root}/tmp/cache/assets/#{Rails.env}"
THEME_REGEX ||= /_theme$/ THEME_REGEX ||= /_theme\z/
COLOR_SCHEME_STYLESHEET ||= "color_definitions" COLOR_SCHEME_STYLESHEET ||= "color_definitions"
@@lock = Mutex.new @@lock = Mutex.new

View File

@ -35,7 +35,7 @@ class Stylesheet::Manager::Builder
end end
end end
rtl = @target.to_s =~ /_rtl$/ rtl = @target.to_s =~ /_rtl\z/
css, source_map = css, source_map =
with_load_paths do |load_paths| with_load_paths do |load_paths|
Stylesheet::Compiler.compile_asset( Stylesheet::Compiler.compile_asset(

View File

@ -21,7 +21,7 @@ module Stylesheet
@default_paths = ["app/assets/stylesheets"] @default_paths = ["app/assets/stylesheets"]
Discourse.plugins.each do |plugin| Discourse.plugins.each do |plugin|
if plugin.path.to_s.include?(Rails.root.to_s) 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 else
# if plugin doesnt seem to be in our app, consider it as outside of the app # if plugin doesnt seem to be in our app, consider it as outside of the app
# and ignore it # and ignore it
@ -41,7 +41,7 @@ module Stylesheet
end end
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"] listener_opts[:force_polling] = true if ENV["FORCE_POLLING"]
Thread.new do Thread.new do

Some files were not shown because too many files have changed in this diff Show More