diff options
Diffstat (limited to 'app/services/packages/nuget/metadata_extraction_service.rb')
-rw-r--r-- | app/services/packages/nuget/metadata_extraction_service.rb | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb new file mode 100644 index 00000000000..6fec398fab0 --- /dev/null +++ b/app/services/packages/nuget/metadata_extraction_service.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module Packages + module Nuget + class MetadataExtractionService + include Gitlab::Utils::StrongMemoize + + ExtractionError = Class.new(StandardError) + + XPATHS = { + package_name: '//xmlns:package/xmlns:metadata/xmlns:id', + package_version: '//xmlns:package/xmlns:metadata/xmlns:version', + license_url: '//xmlns:package/xmlns:metadata/xmlns:licenseUrl', + project_url: '//xmlns:package/xmlns:metadata/xmlns:projectUrl', + icon_url: '//xmlns:package/xmlns:metadata/xmlns:iconUrl' + }.freeze + + XPATH_DEPENDENCIES = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:dependency' + XPATH_DEPENDENCY_GROUPS = '//xmlns:package/xmlns:metadata/xmlns:dependencies/xmlns:group' + XPATH_TAGS = '//xmlns:package/xmlns:metadata/xmlns:tags' + + MAX_FILE_SIZE = 4.megabytes.freeze + + def initialize(package_file_id) + @package_file_id = package_file_id + end + + def execute + raise ExtractionError.new('invalid package file') unless valid_package_file? + + extract_metadata(nuspec_file) + end + + private + + def package_file + strong_memoize(:package_file) do + ::Packages::PackageFile.find_by_id(@package_file_id) + end + end + + def valid_package_file? + package_file && + package_file.package&.nuget? && + package_file.file.size.positive? + end + + def extract_metadata(file) + doc = Nokogiri::XML(file) + + XPATHS.transform_values { |query| doc.xpath(query).text.presence } + .compact + .tap do |metadata| + metadata[:package_dependencies] = extract_dependencies(doc) + metadata[:package_tags] = extract_tags(doc) + end + end + + def extract_dependencies(doc) + dependencies = [] + + doc.xpath(XPATH_DEPENDENCIES).each do |node| + dependencies << extract_dependency(node) + end + + doc.xpath(XPATH_DEPENDENCY_GROUPS).each do |group_node| + target_framework = group_node.attr("targetFramework") + + group_node.xpath("xmlns:dependency").each do |node| + dependencies << extract_dependency(node).merge(target_framework: target_framework) + end + end + + dependencies + end + + def extract_dependency(node) + { + name: node.attr('id'), + version: node.attr('version') + }.compact + end + + def extract_tags(doc) + tags = doc.xpath(XPATH_TAGS).text + + return [] if tags.blank? + + tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR) + end + + def nuspec_file + package_file.file.use_file do |file_path| + Zip::File.open(file_path) do |zip_file| + entry = zip_file.glob('*.nuspec').first + + raise ExtractionError.new('nuspec file not found') unless entry + raise ExtractionError.new('nuspec file too big') if entry.size > MAX_FILE_SIZE + + entry.get_input_stream.read + end + end + end + end + end +end |