Module: Mxrb::Compiler::Mda

Defined in:
lib/mxrb/compiler/mda.rb

Overview

Safe inspection and content-level comparison of Mendix deployment archives.

Defined Under Namespace

Classes: Difference, Entry, Inspection

Class Method Summary collapse

Class Method Details

.build_entry(entry) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/mxrb/compiler/mda.rb', line 58

def build_entry(entry)
  path = safe_path(entry.name)
  directory = entry.directory?
  bytes = directory ? '' : entry.get_input_stream.read
  Entry.new(
    path:, size: entry.size, crc: entry.crc,
    sha256: directory ? nil : Digest::SHA256.hexdigest(bytes), directory:
  )
end

.build_inspection(path, metadata, entries) ⇒ Object



51
52
53
54
55
56
# File 'lib/mxrb/compiler/mda.rb', line 51

def build_inspection(path, , entries)
  Inspection.new(
    path:, metadata:, entries: entries.sort_by(&:path),
    sha256: Digest::SHA256.file(path).hexdigest
  )
end

.compare(left, right) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/mxrb/compiler/mda.rb', line 32

def compare(left, right)
  left_entries = entry_map(left)
  right_entries = entry_map(right)
  (left_entries.keys | right_entries.keys).sort.filter_map do |path|
    lhs = left_entries[path]
    rhs = right_entries[path]
    status = difference_status(lhs, rhs)
    next unless status

    Difference.new(path:, status:, left_sha256: lhs&.sha256, right_sha256: rhs&.sha256)
  end
end

.difference_status(left, right) ⇒ Object



80
81
82
83
84
85
86
# File 'lib/mxrb/compiler/mda.rb', line 80

def difference_status(left, right)
  return :added unless left
  return :removed unless right
  return :changed if left.sha256 != right.sha256

  nil
end

.entry_map(path) ⇒ Object



76
77
78
# File 'lib/mxrb/compiler/mda.rb', line 76

def entry_map(path)
  inspect(path).files.to_h { [_1.path, _1] }
end

.inspect(path) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/mxrb/compiler/mda.rb', line 20

def inspect(path)
  archive_path = File.expand_path(path)
  entries = read_entries(archive_path)
   = entries.find { _1.path == 'model/metadata.json' }
   = (archive_path, )
  raise CompilationError, 'MDA has no model/metadata.json' unless 

  build_inspection(archive_path, , entries)
rescue Zip::Error, JSON::ParserError => e
  raise CompilationError, "invalid MDA #{archive_path}: #{e.message}"
end

.read_entries(archive_path) ⇒ Object



45
46
47
48
49
# File 'lib/mxrb/compiler/mda.rb', line 45

def read_entries(archive_path)
  Zip::File.open(archive_path) do |archive|
    archive.map { build_entry(_1) }
  end
end

.read_metadata(archive_path, entry) ⇒ Object



68
69
70
71
72
73
74
# File 'lib/mxrb/compiler/mda.rb', line 68

def (archive_path, entry)
  return unless entry

  Zip::File.open(archive_path) do |archive|
    JSON.parse(archive.get_entry(entry.path).get_input_stream.read)
  end
end

.safe_path(path) ⇒ Object



88
89
90
91
92
93
94
95
96
# File 'lib/mxrb/compiler/mda.rb', line 88

def safe_path(path)
  clean = path.to_s.tr('\\', '/').sub(%r{\A/+}, '')
  parts = clean.split('/')
  if clean.empty? || parts.include?('..') || parts.include?('.')
    raise CompilationError, "unsafe MDA entry #{path.inspect}"
  end

  clean
end