Skip to content
Snippets Groups Projects
Unverified Commit c0030702 authored by Markus Reiter's avatar Markus Reiter Committed by GitHub
Browse files

Merge pull request #7772 from linzichao/add-verified-url

add verified parameter to url
parents eb486bf6 058a1c36
No related branches found
No related tags found
No related merge requests found
......@@ -26,10 +26,6 @@ Cask/Desc:
Description: 'Ensure that the desc stanza conforms to various content and style checks.'
Enabled: true
Cask/HomepageMatchesUrl:
Description: 'Ensure that the homepage and url match, otherwise add a comment. More info at https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment'
Enabled: true
Cask/HomepageUrlTrailingSlash:
Description: 'Ensure that the homepage url has a slash after the domain name.'
Enabled: true
......
......@@ -52,6 +52,9 @@ module Cask
check_sha256
check_desc
check_url
check_unnecessary_verified
check_missing_verified
check_no_match
check_generic_artifacts
check_token_valid
check_token_bad_words
......@@ -345,6 +348,80 @@ module Cask
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def homepage
URI(cask.homepage.to_s).host
end
def domain
URI(cask.url.to_s).host
end
def url_match_homepage?
host = cask.url.to_s.downcase
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
def strip_url_scheme(url)
url.sub(%r{^.*://(www\.)?}, "")
end
def url_from_verified
cask.url.verified.sub(%r{^https?://}, "")
end
def verified_matches_url?
strip_url_scheme(cask.url.to_s).start_with?(url_from_verified)
end
def verified_present?
cask.url.verified.present?
end
def url_includes_file?
cask.url.to_s.start_with?("file://")
end
def check_unnecessary_verified
return unless verified_present?
return unless url_match_homepage?
return unless verified_matches_url?
add_warning "The URL's #{domain} matches the homepage #{homepage}, " \
"the `verified` parameter of the `url` stanza is unnecessary. " \
"See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-verified"
end
def check_missing_verified
return if url_includes_file?
return if url_match_homepage?
return if verified_present?
add_warning "#{domain} does not match #{homepage}, a `verified` parameter of the `url` has to be added. " \
" See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-verified"
end
def check_no_match
return if url_match_homepage?
return unless verified_present?
return if !url_match_homepage? && verified_matches_url?
add_warning "#{url_from_verified} does not match #{strip_url_scheme(cask.url.to_s)}. " \
"See https://github.com/Homebrew/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-verified"
end
def check_generic_artifacts
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
unless artifact.target.absolute?
......
......@@ -8,7 +8,7 @@ class URL
extend T::Sig
attr_reader :uri, :specs,
:using,
:verified, :using,
:tag, :branch, :revisions, :revision,
:trust_cert, :cookies, :referer, :header, :user_agent,
:data
......@@ -19,6 +19,7 @@ class URL
sig do
params(
uri: T.any(URI::Generic, String),
verified: T.nilable(String),
using: T.nilable(Symbol),
tag: T.nilable(String),
branch: T.nilable(String),
......@@ -34,6 +35,7 @@ class URL
end
def initialize(
uri,
verified: nil,
using: nil,
tag: nil,
branch: nil,
......@@ -49,6 +51,7 @@ class URL
@uri = URI(uri)
specs = {}
specs[:verified] = @verified = verified
specs[:using] = @using = using
specs[:tag] = @tag = tag
specs[:branch] = @branch = branch
......
# typed: false
# frozen_string_literal: true
require "forwardable"
module RuboCop
module Cop
module Cask
# This cop checks that a cask's homepage matches the download URL,
# or if it doesn't, checks if a comment in the form
# `# example.com was verified as official when first introduced to the cask`
# is present.
class HomepageMatchesUrl < Cop
extend Forwardable
include CaskHelp
REFERENCE_URL =
"https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/" \
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment"
COMMENT_FORMAT = /# [^ ]+ was verified as official when first introduced to the cask/.freeze
MSG_NO_MATCH = "`%<url>s` does not match `%<full_url>s`"
MSG_MISSING = ("`%<domain>s` does not match `%<homepage>s`, a comment has to be added " \
"above the `url` stanza. For details, see " + REFERENCE_URL).freeze
MSG_WRONG_FORMAT = ("`%<comment>s` does not match the expected comment format. " \
"For details, see " + REFERENCE_URL).freeze
MSG_UNNECESSARY = "The URL's domain `%<domain>s` matches the homepage `%<homepage>s`, " \
"the comment above the `url` stanza is unnecessary"
def on_cask(cask_block)
@cask_block = cask_block
return unless homepage_stanza
add_offenses
end
private
attr_reader :cask_block
def_delegators :cask_block, :cask_node, :toplevel_stanzas,
:sorted_toplevel_stanzas
def add_offenses
toplevel_stanzas.select(&:url?).each do |url|
next if add_offense_unnecessary_comment(url)
next if add_offense_missing_comment(url)
next if add_offense_no_match(url)
next if add_offense_wrong_format(url)
end
end
def add_offense_unnecessary_comment(stanza)
return unless comment?(stanza)
return unless url_match_homepage?(stanza)
return unless comment_matches_format?(stanza)
return unless comment_matches_url?(stanza)
comment = comment(stanza).loc.expression
add_offense(comment,
location: comment,
message: format(MSG_UNNECESSARY, domain: domain(stanza), homepage: homepage))
end
def add_offense_missing_comment(stanza)
return if url_match_homepage?(stanza)
return if !url_match_homepage?(stanza) && comment?(stanza)
range = stanza.source_range
url_domain = domain(stanza)
add_offense(range, location: range, message: format(MSG_MISSING, domain: url_domain, homepage: homepage))
end
def add_offense_no_match(stanza)
return if url_match_homepage?(stanza)
return unless comment?(stanza)
return if !url_match_homepage?(stanza) && comment_matches_url?(stanza)
comment = comment(stanza).loc.expression
add_offense(comment,
location: comment,
message: format(MSG_NO_MATCH, url: url_from_comment(stanza), full_url: full_url(stanza)))
end
def add_offense_wrong_format(stanza)
return if url_match_homepage?(stanza)
return unless comment?(stanza)
return if comment_matches_format?(stanza)
comment = comment(stanza).loc.expression
add_offense(comment,
location: comment,
message: format(MSG_WRONG_FORMAT, comment: comment(stanza).text))
end
def comment?(stanza)
!stanza.comments.empty?
end
def comment(stanza)
stanza.comments.last
end
def comment_matches_format?(stanza)
comment(stanza).text =~ COMMENT_FORMAT
end
def url_from_comment(stanza)
comment(stanza).text
.sub(/[^ ]*# ([^ ]+) .*/, '\1')
end
def comment_matches_url?(stanza)
full_url(stanza).include?(url_from_comment(stanza))
end
def strip_url_scheme(url)
url.sub(%r{^.*://(www\.)?}, "")
end
def domain(stanza)
strip_url_scheme(extract_url(stanza)).gsub(%r{^([^/]+).*}, '\1')
end
def extract_url(stanza)
string = stanza.stanza_node.children[2]
return string.str_content if string.str_type?
string.to_s.gsub(%r{.*"([a-z0-9]+://[^"]+)".*}m, '\1')
end
def url_match_homepage?(stanza)
host = extract_url(stanza).downcase
host_uri = begin
URI(remove_non_ascii(host))
rescue URI::InvalidURIError
# Can't check if we can't parse.
nil
end
return true if host_uri.blank?
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
def full_url(stanza)
strip_url_scheme(extract_url(stanza))
end
def homepage
URI(remove_non_ascii(extract_url(homepage_stanza))).host
end
def homepage_stanza
toplevel_stanzas.find(&:homepage?)
end
def remove_non_ascii(string)
string.gsub(/\P{ASCII}/, "")
end
end
end
end
end
......@@ -13,7 +13,6 @@ require "rubocops/cask/extend/node"
require "rubocops/cask/mixin/cask_help"
require "rubocops/cask/mixin/on_homepage_stanza"
require "rubocops/cask/desc"
require "rubocops/cask/homepage_matches_url"
require "rubocops/cask/homepage_url_trailing_slash"
require "rubocops/cask/no_dsl_version"
require "rubocops/cask/stanza_order"
......
......@@ -887,5 +887,80 @@ describe Cask::Audit, :cask do
expect(subject).to pass
end
end
context "when the url matches the homepage" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.0'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://foo.brew.sh/foo.zip'
name 'Audit'
desc 'Audit Description'
homepage 'https://foo.brew.sh'
app 'Audit.app'
end
RUBY
end
it { is_expected.to pass }
end
context "when the url does not match the homepage" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.8.0_72,8.13.0.5'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://brew.sh/foo.zip'
name 'Audit'
desc 'Audit Description'
homepage 'https://foo.example.org'
app 'Audit.app'
end
RUBY
end
it { is_expected.to warn_with(/a `verified` parameter of the `url` has to be added./) }
end
context "when the url does not match the homepage with verified" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.8.0_72,8.13.0.5'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://brew.sh/foo.zip', verified: 'brew.sh'
name 'Audit'
desc 'Audit Description'
homepage 'https://foo.example.org'
app 'Audit.app'
end
RUBY
end
it { is_expected.to pass }
end
context "when there is no homepage" do
let(:cask_token) { "foo" }
let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do
version '1.8.0_72,8.13.0.5'
sha256 '8dd95daa037ac02455435446ec7bc737b34567afe9156af7d20b2a83805c1d8a'
url 'https://brew.sh/foo.zip'
name 'Audit'
desc 'Audit Description'
app 'Audit.app'
end
RUBY
end
it { is_expected.to fail_with(/a homepage stanza is required/) }
end
end
end
# typed: false
# frozen_string_literal: true
require "rubocops/rubocop-cask"
require "test/rubocops/cask/shared_examples/cask_cop"
describe RuboCop::Cop::Cask::HomepageMatchesUrl do
include CaskCop
subject(:cop) { described_class.new }
context "when the url matches the homepage and there is no comment" do
let(:source) do
<<-CASK.undent
cask 'foo' do
url 'https://foo.brew.sh/foo.zip'
homepage 'https://foo.brew.sh'
end
CASK
end
include_examples "does not report any offenses"
end
context "when the url matches the homepage and the url stanza has " \
"a referrer and no interpolation" do
let(:source) do
<<-CASK.undent
cask 'foo' do
url 'https://foo.brew.sh/foo.zip',
referrer: 'https://brew.sh/foo/'
homepage 'https://foo.brew.sh'
end
CASK
end
include_examples "does not report any offenses"
end
context "when the url matches the homepage and the url stanza has " \
"a referrer and interpolation" do
let(:source) do
<<-CASK.undent
cask 'foo' do
version '1.8.0_72,8.13.0.5'
url "https://foo.brew.sh/foo-\#{version.after_comma}-\#{version.minor}.\#{version.patch}.\#{version.before_comma.sub(\%r{.*_}, '')}.zip",
referrer: 'https://brew.sh/foo/'
homepage 'https://foo.brew.sh'
end
CASK
end
include_examples "does not report any offenses"
end
context "when the url matches the homepage but there is a comment " \
"which does not match the url" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# this is just a comment with information
url 'https://brew.sh/foo.zip'
homepage 'https://brew.sh'
end
CASK
end
include_examples "does not report any offenses"
end
context "when the url matches the homepage " \
"but there is a comment matching the url" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# foo.brew.sh was verified as official when first introduced to the cask
url 'https://foo.brew.sh/foo.zip'
homepage 'https://foo.brew.sh'
end
CASK
end
let(:expected_offenses) do
[{
message: "The URL's domain `foo.brew.sh` matches the homepage " \
"`foo.brew.sh`, the comment above the `url` stanza is " \
"unnecessary",
severity: :convention,
line: 2,
column: 2,
source: "# foo.brew.sh was verified as official when " \
"first introduced to the cask",
}]
end
include_examples "reports offenses"
end
context "when the url does not match the homepage" do
context "when there is a comment matching the url " \
"but not matching the expected format" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# brew.sh was verified as official
url 'https://brew.sh/foo.zip'
homepage 'https://foo.example.org'
end
CASK
end
let(:expected_offenses) do
[{
message: "`# brew.sh was verified as official` does not " \
"match the expected comment format. For details, see " \
"https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/" \
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment",
severity: :convention,
line: 2,
column: 2,
source: "# brew.sh was verified as official",
}]
end
include_examples "reports offenses"
end
context "when there is a comment matching the url " \
"and does not have slashes" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# brew.sh was verified as official when first introduced to the cask
url 'https://brew.sh/foo.zip'
homepage 'https://foo.example.org'
end
CASK
end
include_examples "does not report any offenses"
end
context "when there is a comment matching the url and has slashes" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# brew.sh/vendor/app was verified as official when first introduced to the cask
url 'https://downloads.brew.sh/vendor/app/foo.zip'
homepage 'https://vendor.example.org/app/'
end
CASK
end
include_examples "does not report any offenses"
end
context "when there is a comment which does not match the url" do
let(:source) do
<<-CASK.undent
cask 'foo' do
# brew.sh was verified as official when first introduced to the cask
url 'https://example.org/foo.zip'
homepage 'https://foo.brew.sh'
end
CASK
end
let(:expected_offenses) do
[{
message: "`brew.sh` does not match `example.org/foo.zip`",
severity: :convention,
line: 2,
column: 2,
source: "# brew.sh was verified as official when " \
"first introduced to the cask",
}]
end
include_examples "reports offenses"
end
context "when the comment is missing" do
let(:source) do
<<-CASK.undent
cask 'foo' do
url 'https://brew.sh/foo.zip'
homepage 'https://example.org'
end
CASK
end
let(:expected_offenses) do
[{
message: "`brew.sh` does not match `example.org`, a comment " \
"has to be added above the `url` stanza. For details, see " \
"https://github.com/Homebrew/homebrew-cask/blob/HEAD/doc/" \
"cask_language_reference/stanzas/url.md#when-url-and-homepage-hostnames-differ-add-a-comment",
severity: :convention,
line: 2,
column: 2,
source: "url 'https://brew.sh/foo.zip'",
}]
end
include_examples "reports offenses"
end
end
context "when there is no homepage" do
let(:source) do
<<-CASK.undent
cask 'foo' do
url 'https://brew.sh/foo.zip'
end
CASK
end
include_examples "does not report any offenses"
end
end
cask "generic-artifact-relative-target" do
version "1.2.3"
sha256 "d5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2"
url "file://#{TEST_FIXTURE_DIR}/cask/AppWithBinary.zip"
name "With Binary"
desc "Cask with a binary stanza"
homepage "https://brew.sh/with-binary"
artifact "Caffeine.app", target: "Caffeine.app"
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment