blob: 02086b2a28277433d675174f4e294f4ac060b4de (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# 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'
XPATH_PACKAGE_TYPES = '//xmlns:package/xmlns:metadata/xmlns:packageTypes/xmlns:packageType'
MAX_FILE_SIZE = 4.megabytes.freeze
def initialize(package_file_id)
@package_file_id = package_file_id
end
def execute
raise ExtractionError, 'invalid package file' unless valid_package_file?
extract_metadata(nuspec_file_content)
end
private
def package_file
strong_memoize(:package_file) do
::Packages::PackageFile.find_by_id(@package_file_id)
end
end
def project
package_file.package.project
end
def valid_package_file?
package_file &&
package_file.package&.nuget? &&
package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
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)
metadata[:package_types] = extract_package_types(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_package_types(doc)
doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
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_content
with_zip_file do |zip_file|
entry = zip_file.glob('*.nuspec').first
raise ExtractionError, 'nuspec file not found' unless entry
raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
Tempfile.open("nuget_extraction_package_file_#{@package_file_id}") do |file|
entry.extract(file.path) { true } # allow #extract to overwrite the file
file.unlink
file.read
end
rescue Zip::EntrySizeError => e
raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
end
end
def with_zip_file(&block)
package_file.file.use_open_file do |open_file|
zip_file = Zip::File.new(open_file, false, true)
yield(zip_file)
end
end
end
end
end
|