Module: Metaclean::Qpdf

Defined in:
lib/metaclean/qpdf.rb

Class Method Summary collapse

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/metaclean/qpdf.rb', line 9

def available?
  return @available if defined?(@available)

  out, _err, status = Metaclean.capture3(
    'qpdf', '--version',
    timeout: Metaclean::PROBE_TIMEOUT,
    max_output: Metaclean::PROBE_MAX_OUTPUT_BYTES
  )
  @available = status.success?
  @version = @available ? out.lines.first.to_s.strip.split.last : nil
  @available
rescue Errno::ENOENT, Error
  @version = nil
  @available = false
end

.ensure_no_attachments!(path) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/metaclean/qpdf.rb', line 54

def ensure_no_attachments!(path)
  out, err, status = Metaclean.capture3(
    'qpdf', '--json', '--json-key=attachments', Metaclean.safe_path(path)
  )
  success = status.success? || status.exitstatus == 3
  raise Error, "qpdf could not inspect PDF attachments: #{err.strip}" unless success

  attachments = JSON.parse(out).fetch('attachments')
  raise Error, 'Unexpected qpdf attachment inventory' unless attachments.is_a?(Hash)
  return if attachments.empty?

  raise Error, 'PDF contains embedded attachments whose nested metadata cannot be verified'
rescue JSON::ParserError, KeyError => e
  raise Error, "Could not parse qpdf attachment inventory: #{e.message}"
end

.rebuild!(path) ⇒ Object

Raises:



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/metaclean/qpdf.rb', line 29

def rebuild!(path)
  raise Error, 'qpdf not available' unless available?

  source = FileOps.regular_stat!(path)
  ensure_no_attachments!(path)
  FileOps.with_private_workspace(path, 'qpdf') do |workspace|
    tmp = File.join(workspace, 'output.pdf')
    _out, err, status = Metaclean.capture3(
      'qpdf', '--linearize', '--object-streams=generate',
      '--remove-unreferenced-resources=yes',
      Metaclean.safe_path(path), Metaclean.safe_path(tmp)
    )

    success = status.success? || status.exitstatus == 3
    raise Error, "qpdf failed: #{err.strip}" unless success && File.exist?(tmp)

    FileOps.validate_output!(tmp)
    validate_pdf!(tmp)
    FileOps.ensure_same_file!(path, source)
    FileOps.(tmp, source)
    File.rename(tmp, path)
    true
  end
end

.validate_pdf!(path) ⇒ Object

Raises:



70
71
72
73
74
75
76
77
# File 'lib/metaclean/qpdf.rb', line 70

def validate_pdf!(path)
  _out, err, status = Metaclean.capture3(
    'qpdf', '--check', Metaclean.safe_path(path)
  )
  return true if status.success? || status.exitstatus == 3

  raise Error, "qpdf produced an invalid PDF: #{err.strip}"
end

.versionObject



25
26
27
# File 'lib/metaclean/qpdf.rb', line 25

def version
  available? ? @version : nil
end