Class: Kotoshu::Integrity::Manifest

Inherits:
Object
  • Object
show all
Defined in:
lib/kotoshu/integrity/manifest.rb

Overview

Parsed view of a content repo's manifest.json.

Format (per TODO.impl/09-integrity-security.md task 1):

{
"version": 1,
"generated_at": "2026-06-25T10:00:00Z",
"resources": {
  "en/spelling/index.dic": {
    "size": 49568,
    "sha256": "ab12...",
    "language": "en",
    "type": "spelling",
    "license": "LGPL/MPL/GPL",
    "source": "SCROLL"
  }
}
}

Construction:

manifest = Manifest.parse(json_string)
manifest.fetch("en/spelling/index.dic")  # => Entry or nil
manifest.verify_content!("en/spelling/index.dic", bytes) # raises on mismatch

Manifest.load(url, http:), returns nil when the manifest 404s (graceful degradation — see module docs).

Defined Under Namespace

Classes: Entry

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(entries, version: nil, generated_at: nil) ⇒ Manifest

Returns a new instance of Manifest.



81
82
83
84
85
# File 'lib/kotoshu/integrity/manifest.rb', line 81

def initialize(entries, version: nil, generated_at: nil)
  @entries = entries
  @version = version
  @generated_at = generated_at
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



79
80
81
# File 'lib/kotoshu/integrity/manifest.rb', line 79

def entries
  @entries
end

#generated_atObject (readonly)

Returns the value of attribute generated_at.



79
80
81
# File 'lib/kotoshu/integrity/manifest.rb', line 79

def generated_at
  @generated_at
end

#versionObject (readonly)

Returns the value of attribute version.



79
80
81
# File 'lib/kotoshu/integrity/manifest.rb', line 79

def version
  @version
end

Class Method Details

.load(url, http: Kotoshu::Integrity::NetHTTP) ⇒ Object

Fetch and parse a manifest from a URL. Returns nil when the manifest is absent (HTTP 404/410) so callers can fall back to unverified downloads — see module docs. Any other failure (5xx, network error, parse error) raises.



72
73
74
75
76
77
# File 'lib/kotoshu/integrity/manifest.rb', line 72

def self.load(url, http: Kotoshu::Integrity::NetHTTP)
  body = http.get(url)
  return nil if body.nil?

  parse(body)
end

.parse(json) ⇒ Object

Parse a manifest JSON string. Returns an empty Manifest if the JSON is parseable but has no resources (caller treats as "no constraints").



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/kotoshu/integrity/manifest.rb', line 45

def self.parse(json)
  data = JSON.parse(json)
  entries = {}
  (data["resources"] || {}).each do |path, fields|
    entries[path] = Entry.new(
      path: path,
      sha256: fields["sha256"],
      size: fields["size"],
      language: fields["language"],
      type: fields["type"],
      license: fields["license"],
      source: fields["source"]
    )
  end
  new(entries, version: data["version"], generated_at: data["generated_at"])
rescue JSON::ParserError => e
  raise Kotoshu::IntegrityError.new(
    "manifest",
    expected: "<valid JSON>",
    actual: "<parse error: #{e.message}>"
  )
end

Instance Method Details

#empty?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/kotoshu/integrity/manifest.rb', line 91

def empty?
  @entries.empty?
end

#fetch(path) ⇒ Object



87
88
89
# File 'lib/kotoshu/integrity/manifest.rb', line 87

def fetch(path)
  @entries[path]
end

#verify_content!(path, content, url: nil) ⇒ Object

Verify that content for path matches the manifest entry. Raises Kotoshu::IntegrityError on mismatch. No-op when the manifest has no entry for path (returns nil — caller decides whether to treat absence as failure in strict mode).



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/kotoshu/integrity/manifest.rb', line 99

def verify_content!(path, content, url: nil)
  entry = @entries[path]
  return nil unless entry

  actual = Digest::SHA256.hexdigest(content)
  unless actual == entry.sha256
    raise Kotoshu::IntegrityError.new(
      path,
      expected: entry.sha256,
      actual: actual,
      url: url
    )
  end
  true
end