Commit 575b8495 authored by Ethan Reesor's avatar Ethan Reesor

Fix Go module proxy issues

- Fix zip entry paths. The Go proxy spec requires zip entries to conform
  to `module@version/file`, where `file` is the path within the module.
- Fix /v2+ handling. For major versions 2+, the module name must include
  the major version as a suffix, e.g. /v2.
- Handle case encoding. Requests to the Go proxy encode uppercase
  characters in URLs as '!' followed by the character in lowercase.
- Per Zoom discussion with @trizzi, @sabrams, and team, modules with an
  invalid module name in go.mod will be ignored, initially.
parent 2a9f40a6
# frozen_string_literal: true # frozen_string_literal: true
class Packages::GoModule class Packages::GoModule
SEMVER_TAG_REGEX = Regexp.new("^#{::Packages::GoModuleVersion::SEMVER_REGEX.source}$").freeze SEMVER_TAG_REGEX = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.a-z0-9]+))?(?:\+([-.a-z0-9]+))?$/i.freeze
# belongs_to :project # belongs_to :project
...@@ -22,7 +22,7 @@ class Packages::GoModule ...@@ -22,7 +22,7 @@ class Packages::GoModule
end end
def versions def versions
@versions ||= project.repository.tags @versions ||= @project.repository.tags
.filter { |tag| SEMVER_TAG_REGEX.match?(tag.name) && !tag.dereferenced_target.nil? } .filter { |tag| SEMVER_TAG_REGEX.match?(tag.name) && !tag.dereferenced_target.nil? }
.map { |tag| ::Packages::GoModuleVersion.new self, tag } .map { |tag| ::Packages::GoModuleVersion.new self, tag }
.filter { |ver| ver.valid? } .filter { |ver| ver.valid? }
......
# frozen_string_literal: true # frozen_string_literal: true
class Packages::GoModuleVersion class Packages::GoModuleVersion
SEMVER_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.A-Z0-9]+))?(?:\+([-.A-Z0-9]+))?/i.freeze SEMVER_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.a-z0-9]+))?(?:\+([-.a-z0-9]+))?/i.freeze
VERSION_SUFFIX_REGEX = /\/v([1-9]\d*)$/i.freeze VERSION_SUFFIX_REGEX = /\/v([1-9]\d*)$/i.freeze
# belongs_to :mod # belongs_to :mod
...@@ -16,22 +16,30 @@ class Packages::GoModuleVersion ...@@ -16,22 +16,30 @@ class Packages::GoModuleVersion
end end
def gomod def gomod
return @gomod unless @gomod.nil? @gomod ||= @mod.project.repository.blob_at(@tag.dereferenced_target.sha, @mod.path + '/go.mod')&.data
blob = @mod.project.repository.blob_at(@tag.dereferenced_target.sha, @mod.path + '/go.mod')
@gomod = blob ? blob.data : ''
end end
def valid? def valid?
m = gomod.split("\n", 2).first valid_path? && valid_module?
end
def valid_path?
m = VERSION_SUFFIX_REGEX.match(@mod.name)
case major case major
when 0, 1 when 0, 1
m == "module #{@mod.name}" m.nil?
else else
m == "module #{@mod.name}/v#{major}" !m.nil? && m[1].to_i == major
end end
end end
def valid_module?
return false unless gomod
gomod.split("\n", 2).first == "module #{@mod.name}"
end
def major def major
SEMVER_REGEX.match(@tag.name)[1].to_i SEMVER_REGEX.match(@tag.name)[1].to_i
end end
...@@ -56,8 +64,8 @@ class Packages::GoModuleVersion ...@@ -56,8 +64,8 @@ class Packages::GoModuleVersion
return @files unless @files.nil? return @files unless @files.nil?
sha = @tag.dereferenced_target.sha sha = @tag.dereferenced_target.sha
tree = @mod.project.repository.tree(sha, mod.path, recursive: true).entries.filter { |e| e.file? } tree = @mod.project.repository.tree(sha, @mod.path, recursive: true).entries.filter { |e| e.file? }
nested = tree.filter { |e| e.name == 'go.mod' && !(mod.path == '' && e.path == 'go.mod' || e.path == mod.path + '/go.mod') }.map { |e| e.path[0..-7] } nested = tree.filter { |e| e.name == 'go.mod' && !(@mod.path == '' && e.path == 'go.mod' || e.path == @mod.path + '/go.mod') }.map { |e| e.path[0..-7] }
@files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } } @files = tree.filter { |e| !nested.any? { |n| e.path.start_with? n } }
end end
......
...@@ -3,13 +3,18 @@ module API ...@@ -3,13 +3,18 @@ module API
class GoProxy < Grape::API class GoProxy < Grape::API
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
MODULE_VERSION_REQUIREMENTS = { module_version: ::Packages::GoModuleVersion::SEMVER_REGEX }.freeze MODULE_VERSION_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.!a-z0-9]+))?(?:\+([-.!a-z0-9]+))?/.freeze
MODULE_VERSION_REQUIREMENTS = { module_version: MODULE_VERSION_REGEX }.freeze
before { require_packages_enabled! } before { require_packages_enabled! }
helpers do helpers do
def case_decode(str)
str.gsub(/![[:alpha:]]/) { |s| s[1..].upcase }
end
def find_module def find_module
module_name = params[:module_name].gsub(/![[:alpha:]]/) { |s| s[1..].upcase } module_name = case_decode params[:module_name]
bad_request!('Module Name') if module_name.blank? bad_request!('Module Name') if module_name.blank?
...@@ -18,6 +23,15 @@ module API ...@@ -18,6 +23,15 @@ module API
mod mod
end end
def find_version
mod = find_module
ver = mod.find_version case_decode params[:module_version]
not_found! unless ver
ver
end
end end
params do params do
...@@ -49,10 +63,7 @@ module API ...@@ -49,10 +63,7 @@ module API
requires :module_version, type: String, desc: 'Module version' requires :module_version, type: String, desc: 'Module version'
end end
get ':module_version.info', requirements: MODULE_VERSION_REQUIREMENTS do get ':module_version.info', requirements: MODULE_VERSION_REQUIREMENTS do
mod = find_module ver = find_version
ver = mod.find_version params[:module_version]
not_found! unless ver
present ::Packages::Go::ModuleVersionPresenter.new(ver), with: EE::API::Entities::GoModuleVersion present ::Packages::Go::ModuleVersionPresenter.new(ver), with: EE::API::Entities::GoModuleVersion
end end
...@@ -64,10 +75,7 @@ module API ...@@ -64,10 +75,7 @@ module API
requires :module_version, type: String, desc: 'Module version' requires :module_version, type: String, desc: 'Module version'
end end
get ':module_version.mod', requirements: MODULE_VERSION_REQUIREMENTS do get ':module_version.mod', requirements: MODULE_VERSION_REQUIREMENTS do
mod = find_module ver = find_version
ver = mod.find_version params[:module_version]
not_found! unless ver
content_type 'text/plain' content_type 'text/plain'
ver.gomod ver.gomod
...@@ -80,14 +88,13 @@ module API ...@@ -80,14 +88,13 @@ module API
requires :module_version, type: String, desc: 'Module version' requires :module_version, type: String, desc: 'Module version'
end end
get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do get ':module_version.zip', requirements: MODULE_VERSION_REQUIREMENTS do
mod = find_module ver = find_version
ver = mod.find_version params[:module_version] suffix_len = ver.mod.path == '' ? 0 : ver.mod.path.length + 1
not_found! unless ver
s = Zip::OutputStream.write_buffer do |zip| s = Zip::OutputStream.write_buffer do |zip|
ver.files.each do |file| ver.files.each do |file|
zip.put_next_entry file.path zip.put_next_entry "#{ver.mod.name}@#{ver.name}/#{file.path[suffix_len...]}"
zip.write ver.blob_at(file.path) zip.write ver.blob_at(file.path)
end end
end end
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment