Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow rubocop formatting #67

1 change: 1 addition & 0 deletions .licenserc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ header:
- "spec/fixtures/**"
- "LICENSE"
- "NOTICE"
- "exe/cyclonedx-cocoapods"
4 changes: 2 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ Metrics/BlockLength:

# Allow some long methods because breaking them up doesn't help anything.
Metrics/MethodLength:
AllowedMethods: ['parse_options', 'add_to_bom']
AllowedMethods: ['parse_options', 'add_to_bom', 'append_all_pod_dependencies']
Metrics/AbcSize:
AllowedMethods: ['parse_options', 'add_to_bom']
AllowedMethods: ['parse_options', 'add_to_bom', 'source_for_pod']
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# frozen_string_literal: true

source "https://rubygems.org"
source 'https://rubygems.org'
gemspec
4 changes: 2 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'

RSpec::Core::RakeTask.new(:spec)

Expand Down
6 changes: 3 additions & 3 deletions bin/console
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "cyclonedx/cocoapods"
require 'bundler/setup'
require 'cyclonedx/cocoapods'

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
Expand All @@ -11,5 +11,5 @@ require "cyclonedx/cocoapods"
# require "pry"
# Pry.start

require "irb"
require 'irb'
IRB.start(__FILE__)
8 changes: 5 additions & 3 deletions cyclonedx-cocoapods.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ Gem::Specification.new do |spec|
spec.email = ['[email protected]', '[email protected]']

spec.summary = 'CycloneDX software bill-of-material (SBOM) generation utility'
spec.description = 'CycloneDX is a lightweight software bill-of-material (SBOM) specification designed for use in application security contexts and supply chain component analysis. This Gem generates CycloneDX BOMs from CocoaPods projects.'
spec.description = 'CycloneDX is a lightweight software bill-of-material (SBOM) specification designed for ' \
'use in application security contexts and supply chain component analysis. This Gem ' \
'generates CycloneDX BOMs from CocoaPods projects.'
spec.homepage = 'https://github.com/CycloneDX/cyclonedx-cocoapods'
spec.license = 'Apache-2.0'
spec.required_ruby_version = Gem::Requirement.new('>= 2.4.0')
Expand All @@ -18,7 +20,7 @@ Gem::Specification.new do |spec|
spec.metadata['source_code_uri'] = 'https://github.com/CycloneDX/cyclonedx-cocoapods.git'

# Specify which files should be added to the gem when it is released.
spec.files = Dir['lib/**/*.{rb,json}'] + %w{ exe/cyclonedx-cocoapods README.md CHANGELOG.md LICENSE NOTICE }
spec.files = Dir['lib/**/*.{rb,json}'] + %w[exe/cyclonedx-cocoapods README.md CHANGELOG.md LICENSE NOTICE]

spec.bindir = 'exe'
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
Expand All @@ -27,7 +29,7 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'cocoapods', ['>= 1.10.1', '< 2.0']
spec.add_runtime_dependency 'nokogiri', ['>= 1.11.2', '< 2.0']

spec.add_development_dependency 'equivalent-xml', '~> 0.6.0'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'equivalent-xml', '~> 0.6.0'
end
6 changes: 4 additions & 2 deletions exe/cyclonedx-cocoapods
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file is part of CycloneDX CocoaPods
#
Expand All @@ -18,6 +20,6 @@
# Copyright (c) OWASP Foundation. All Rights Reserved.
#

require "cyclonedx/cocoapods/cli_runner"
require 'cyclonedx/cocoapods/cli_runner'

CycloneDX::CocoaPods::CLIRunner.new().run
CycloneDX::CocoaPods::CLIRunner.new.run
18 changes: 12 additions & 6 deletions lib/cyclonedx/cocoapods/cli_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ def setup_logger(verbose: true)
end

def write_bom_to_file(bom:, options:)
bom_file_path = prep_for_bom_write(options)

begin
File.write(bom_file_path, bom)
@logger.info "BOM written to #{bom_file_path}"
rescue StandardError
raise BOMOutputError, "Unable to write the BOM to #{bom_file_path}"
end
end

def prep_for_bom_write(options)
bom_file_path = Pathname.new(options[:bom_file_path] || './bom.xml').expand_path
bom_dir = bom_file_path.dirname

Expand All @@ -168,12 +179,7 @@ def write_bom_to_file(bom:, options:)
raise BOMOutputError, "Unable to create the BOM output directory at #{bom_dir}"
end

begin
File.write(bom_file_path, bom)
@logger.info "BOM written to #{bom_file_path}"
rescue StandardError
raise BOMOutputError, "Unable to write the BOM to #{bom_file_path}"
end
bom_file_path
end
end
end
Expand Down
16 changes: 11 additions & 5 deletions lib/cyclonedx/cocoapods/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ class Component

attr_reader :group, :name, :version, :type

def initialize(group: nil, name:, version:, type:)
raise ArgumentError, "Group, if specified, must be non empty" if !group.nil? && group.to_s.strip.empty?
raise ArgumentError, "Name must be non empty" if name.nil? || name.to_s.strip.empty?
def initialize(name:, version:, type:, group: nil)
raise ArgumentError, 'Group, if specified, must be non empty' if !group.nil? && group.to_s.strip.empty?
raise ArgumentError, 'Name must be non empty' if name.nil? || name.to_s.strip.empty?

Gem::Version.new(version) # To check that the version string is well formed
raise ArgumentError, "#{type} is not valid component type (#{VALID_COMPONENT_TYPES.join('|')})" unless VALID_COMPONENT_TYPES.include?(type)
unless VALID_COMPONENT_TYPES.include?(type)
raise ArgumentError, "#{type} is not valid component type (#{VALID_COMPONENT_TYPES.join('|')})"
end

@group, @name, @version, @type = group, name, version, type
@group = group
@name = name
@version = version
@type = type
end
end
end
Expand Down
10 changes: 4 additions & 6 deletions lib/cyclonedx/cocoapods/license.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ module CocoaPods
class Pod
class License
SPDX_LICENSES = JSON.parse(File.read("#{__dir__}/spdx-licenses.json")).freeze
IDENTIFIER_TYPES = [:id, :name].freeze
IDENTIFIER_TYPES = %i[id name].freeze

attr_reader :identifier
attr_reader :identifier_type
attr_accessor :text
attr_accessor :url
attr_reader :identifier, :identifier_type
attr_accessor :text, :url

def initialize(identifier:)
raise ArgumentError, "License identifier must be non empty" if identifier.nil? || identifier.to_s.strip.empty?
raise ArgumentError, 'License identifier must be non empty' if identifier.nil? || identifier.to_s.strip.empty?

@identifier = SPDX_LICENSES.find { |license_id| license_id.downcase == identifier.to_s.downcase }
@identifier_type = @identifier.nil? ? :name : :id
Expand Down
78 changes: 47 additions & 31 deletions lib/cyclonedx/cocoapods/pod.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,40 @@
module CycloneDX
module CocoaPods
class Pod
attr_reader :name # xs:normalizedString
attr_reader :version # xs:normalizedString
attr_reader :source # Anything responding to :source_qualifier
attr_reader :homepage # xs:anyURI - https://cyclonedx.org/docs/1.4/#type_externalReference
attr_reader :checksum # https://cyclonedx.org/docs/1.4/#type_hashValue (We only use SHA-1 hashes - length == 40)
attr_reader :author # xs:normalizedString
attr_reader :description # xs:normalizedString
attr_reader :license # https://cyclonedx.org/docs/1.4/#type_licenseType
# We don't currently support several licenses or license expressions https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
# xs:normalizedString
attr_reader :name
# xs:normalizedString
attr_reader :version
# Anything responding to :source_qualifier
attr_reader :source
# xs:anyURI - https://cyclonedx.org/docs/1.4/xml/#type_externalReference
attr_reader :homepage
# https://cyclonedx.org/docs/1.4/xml/#type_hashValue (We only use SHA-1 hashes - length == 40)
attr_reader :checksum
# xs:normalizedString
attr_reader :author
# xs:normalizedString
attr_reader :description
# https://cyclonedx.org/docs/1.4/xml/#type_licenseType
# We don't currently support several licenses or license expressions https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
attr_reader :license

def initialize(name:, version:, source: nil, checksum: nil)
raise ArgumentError, "Name must be non empty" if name.nil? || name.to_s.empty?
raise ArgumentError, 'Name must be non empty' if name.nil? || name.to_s.empty?
raise ArgumentError, "Name shouldn't contain spaces" if name.to_s.include?(' ')
raise ArgumentError, "Name shouldn't start with a dot" if name.to_s.start_with?('.')

# `pod create` also enforces no plus sign, but more than 500 public pods have a plus in the root name.
# https://github.com/CocoaPods/CocoaPods/blob/9461b346aeb8cba6df71fd4e71661688138ec21b/lib/cocoapods/command/lib/create.rb#L35

Gem::Version.new(version) # To check that the version string is well formed
raise ArgumentError, "Invalid pod source" unless source.nil? || source.respond_to?(:source_qualifier)
raise ArgumentError, 'Invalid pod source' unless source.nil? || source.respond_to?(:source_qualifier)
raise ArgumentError, "#{checksum} is not valid SHA-1 hash" unless checksum.nil? || checksum =~ /[a-fA-F0-9]{40}/
@name, @version, @source, @checksum = name.to_s, version, source, checksum

@name = name.to_s
@version = version
@source = source
@checksum = checksum
end

def root_name
Expand All @@ -61,23 +75,21 @@ def populate(attributes)
end

def to_s
"Pod<#{name}, #{version.to_s}>"
"Pod<#{name}, #{version}>"
end

private

def populate_author(attributes)
authors = attributes[:author] || attributes[:authors]
case authors
when String
@author = authors
when Array
@author = authors.join(', ')
when Hash
@author = authors.map { |name, email| "#{name} <#{email}>" }.join(', ')
else
@author = nil
end
@author = case authors
when String
authors
when Array
authors.join(', ')
when Hash
authors.map { |name, email| "#{name} <#{email}>" }.join(', ')
end
end

def populate_description(attributes)
Expand All @@ -89,19 +101,23 @@ def populate_license(attributes)
when String
@license = License.new(identifier: attributes[:license])
when Hash
attributes[:license].transform_keys!(&:to_sym)
identifier = attributes[:license][:type]
unless identifier.nil? || identifier.to_s.strip.empty?
@license = License.new(identifier: identifier)
@license.text = attributes[:license][:text]
else
@license = nil
end
populate_hashed_license(attributes)
else
@license = nil
end
end

def populate_hashed_license(attributes)
attributes[:license].transform_keys!(&:to_sym)
identifier = attributes[:license][:type]
if identifier.nil? || identifier.to_s.strip.empty?
@license = nil
else
@license = License.new(identifier: identifier)
@license.text = attributes[:license][:text]
end
end

def populate_homepage(attributes)
@homepage = attributes[:homepage]
end
Expand Down
25 changes: 20 additions & 5 deletions lib/cyclonedx/cocoapods/pod_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,35 @@ class CocoaPodsRepository
def self.searchable_source(url:, source_manager:)
source = CocoaPodsRepository.new(url: url)
source.source_manager = source_manager
return source
source
end

def attributes_for(pod:)
specification_sets = @source_manager.search_by_name("^#{Regexp.escape(pod.root_name)}$")
raise SearchError, "No pod found named #{pod.name}; run 'pod repo update' and try again" if specification_sets.length == 0
raise SearchError, "More than one pod found named #{pod.name}; a pod in a private spec repo should not have the same name as a public pod" if specification_sets.length > 1
validate_spec_sets(specification_sets, pod)

paths = specification_sets[0].specification_paths_for_version(pod.version)
raise SearchError, "Version #{pod.version} not found for pod #{pod.name}; run 'pod repo update' and try again" if paths.length == 0
if paths.empty?
raise SearchError,
"Version #{pod.version} not found for pod #{pod.name}; run 'pod repo update' and try again"
end

::Pod::Specification.from_file(paths[0]).attributes_hash
end

private

def validate_spec_sets(specification_sets, pod)
if specification_sets.empty?
raise SearchError,
"No pod found named #{pod.name}; run 'pod repo update' and try again"
end
return unless specification_sets.length > 1

raise SearchError,
"More than one pod found named #{pod.name}; a pod in a private spec repo " \
'should not have the same name as a public pod'
end
end

class GitRepository
Expand All @@ -64,7 +80,6 @@ def attributes_for(pod:)
end
end


class Pod
def complete_information_from_source
populate(source.attributes_for(pod: self))
Expand Down
Loading