Module: Marcel::Magic::Zip

Defined in:
lib/marcel/magic/zip.rb

Overview

Procedural refinement of ZIP-based container types.

The declarative magic tables scan a bounded prefix of the file (matchers are capped at 64KB offsets when the tables are generated), so ZIP containers whose distinguishing members are catalogued later in the archive — such as OOXML documents with [Content_Types].xml stored past the 64KB mark — fall back to their generic container type. This module instead parses the ZIP central directory, a bounded read from the end of the file, to recover the specific document type, including macro-enabled variants, which no prefix bytes can distinguish.

Constant Summary collapse

EOCD_SIGNATURE =
"PK\x05\x06".b
ZIP64_EOCD_SIGNATURE =
"PK\x06\x06".b
ZIP64_EOCD_LOCATOR_SIGNATURE =
"PK\x06\x07".b
CENTRAL_DIRECTORY_SIGNATURE =
"PK\x01\x02".b
EOCD_SIZE =
22
ZIP64_EOCD_SIZE =
56
ZIP64_EOCD_LOCATOR_SIZE =
20
MAX_COMMENT_SIZE =
0xFFFF
MAX_EOCD_SEARCH =

The end-of-central-directory record sits at most a maximal comment from the end of the file, possibly preceded by a Zip64 locator.

EOCD_SIZE + MAX_COMMENT_SIZE + ZIP64_EOCD_LOCATOR_SIZE
MAX_EOCD_CANDIDATES =

A malformed trailer full of EOCD signature bytes could otherwise force a quadratic backward scan; real archives find the record on the first candidate.

64
CENTRAL_DIRECTORY_HEADER_SIZE =
46
MAX_CENTRAL_DIRECTORY_READ =
1 << 20
MAX_ENTRIES =
8192
REFINABLE_TYPES =

Container types a central directory listing can make more specific.

[
  "application/zip",
  "application/x-tika-ooxml",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "application/vnd.openxmlformats-officedocument.presentationml.presentation"
].freeze
DOCUMENT_FAMILIES =

Standardised OPC part names identifying each OOXML document family: main part, macro part, and the types each implies.

[
  [ "word/document.xml", "word/vbaProject.bin",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    "application/vnd.ms-word.document.macroenabled.12" ],
  [ "xl/workbook.xml", "xl/vbaProject.bin",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    "application/vnd.ms-excel.sheet.macroenabled.12" ],
  [ "ppt/presentation.xml", "ppt/vbaProject.bin",
    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    "application/vnd.ms-powerpoint.presentation.macroenabled.12" ]
].freeze
DISCRIMINATING_PARTS =
DOCUMENT_FAMILIES.flat_map { |main, macro, *| [main, macro] }.freeze

Class Method Summary collapse

Class Method Details

.refine(io, base_type) ⇒ Object

Returns a more specific type than base_type if the IO's ZIP central directory identifies one, or base_type unchanged. Unseekable IOs, partial reads and malformed archives refine nothing: the base type stands.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/marcel/magic/zip.rb', line 66

def refine(io, base_type)
  return base_type unless REFINABLE_TYPES.include?(base_type)

  io = StringIO.new(io.to_s) unless io.respond_to?(:read)
  return base_type unless io.respond_to?(:seek) && io.respond_to?(:size)

  parts = begin
    discriminating_parts(io)
  rescue StandardError
    nil
  ensure
    begin
      io.rewind
    rescue StandardError
      nil
    end
  end

  parts ? classify(parts, base_type) : base_type
end