Mozilla ARchive: Nim parsing library

Mozilla ARchive file is Mozilla's own archive format to distribute software updates. Test files can be found on Mozilla's FTP site, for example:

http://ftp.mozilla.org/pub/firefox/nightly/partials/

File extension

mar

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Mozilla ARchive using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Nim source code to parse Mozilla ARchive

mozilla_mar.nim

import kaitai_struct_nim_runtime
import options

type
  MozillaMar* = ref object of KaitaiStruct
    `magic`*: seq[byte]
    `ofsIndex`*: uint32
    `fileSize`*: uint64
    `lenSignatures`*: uint32
    `signatures`*: seq[MozillaMar_Signature]
    `lenAdditionalSections`*: uint32
    `additionalSections`*: seq[MozillaMar_AdditionalSection]
    `parent`*: KaitaiStruct
    `indexInst`: MozillaMar_MarIndex
    `indexInstFlag`: bool
  MozillaMar_SignatureAlgorithms* = enum
    rsa_pkcs1_sha1 = 1
    rsa_pkcs1_sha384 = 2
  MozillaMar_BlockIdentifiers* = enum
    product_information = 1
  MozillaMar_MarIndex* = ref object of KaitaiStruct
    `lenIndex`*: uint32
    `indexEntries`*: MozillaMar_IndexEntries
    `parent`*: MozillaMar
    `rawIndexEntries`*: seq[byte]
  MozillaMar_IndexEntries* = ref object of KaitaiStruct
    `indexEntry`*: seq[MozillaMar_IndexEntry]
    `parent`*: MozillaMar_MarIndex
  MozillaMar_Signature* = ref object of KaitaiStruct
    `algorithm`*: MozillaMar_SignatureAlgorithms
    `lenSignature`*: uint32
    `signature`*: seq[byte]
    `parent`*: MozillaMar
  MozillaMar_ProductInformationBlock* = ref object of KaitaiStruct
    `marChannelName`*: string
    `productVersion`*: string
    `parent`*: MozillaMar_AdditionalSection
  MozillaMar_IndexEntry* = ref object of KaitaiStruct
    `ofsContent`*: uint32
    `lenContent`*: uint32
    `flags`*: uint32
    `fileName`*: string
    `parent`*: MozillaMar_IndexEntries
    `bodyInst`: seq[byte]
    `bodyInstFlag`: bool
  MozillaMar_AdditionalSection* = ref object of KaitaiStruct
    `lenBlock`*: uint32
    `blockIdentifier`*: MozillaMar_BlockIdentifiers
    `bytes`*: KaitaiStruct
    `parent`*: MozillaMar
    `rawBytes`*: seq[byte]

proc read*(_: typedesc[MozillaMar], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): MozillaMar
proc read*(_: typedesc[MozillaMar_MarIndex], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_MarIndex
proc read*(_: typedesc[MozillaMar_IndexEntries], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_MarIndex): MozillaMar_IndexEntries
proc read*(_: typedesc[MozillaMar_Signature], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_Signature
proc read*(_: typedesc[MozillaMar_ProductInformationBlock], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_AdditionalSection): MozillaMar_ProductInformationBlock
proc read*(_: typedesc[MozillaMar_IndexEntry], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_IndexEntries): MozillaMar_IndexEntry
proc read*(_: typedesc[MozillaMar_AdditionalSection], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_AdditionalSection

proc index*(this: MozillaMar): MozillaMar_MarIndex
proc body*(this: MozillaMar_IndexEntry): seq[byte]


##[
Mozilla ARchive file is Mozilla's own archive format to distribute software updates.
Test files can be found on Mozilla's FTP site, for example:

<http://ftp.mozilla.org/pub/firefox/nightly/partials/>

@see <a href="https://wiki.mozilla.org/Software_Update:MAR">Source</a>
]##
proc read*(_: typedesc[MozillaMar], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): MozillaMar =
  template this: untyped = result
  this = new(MozillaMar)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let magicExpr = this.io.readBytes(int(4))
  this.magic = magicExpr
  let ofsIndexExpr = this.io.readU4be()
  this.ofsIndex = ofsIndexExpr
  let fileSizeExpr = this.io.readU8be()
  this.fileSize = fileSizeExpr
  let lenSignaturesExpr = this.io.readU4be()
  this.lenSignatures = lenSignaturesExpr
  for i in 0 ..< int(this.lenSignatures):
    let it = MozillaMar_Signature.read(this.io, this.root, this)
    this.signatures.add(it)
  let lenAdditionalSectionsExpr = this.io.readU4be()
  this.lenAdditionalSections = lenAdditionalSectionsExpr
  for i in 0 ..< int(this.lenAdditionalSections):
    let it = MozillaMar_AdditionalSection.read(this.io, this.root, this)
    this.additionalSections.add(it)

proc index(this: MozillaMar): MozillaMar_MarIndex = 
  if this.indexInstFlag:
    return this.indexInst
  let pos = this.io.pos()
  this.io.seek(int(this.ofsIndex))
  let indexInstExpr = MozillaMar_MarIndex.read(this.io, this.root, this)
  this.indexInst = indexInstExpr
  this.io.seek(pos)
  this.indexInstFlag = true
  return this.indexInst

proc fromFile*(_: typedesc[MozillaMar], filename: string): MozillaMar =
  MozillaMar.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_MarIndex], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_MarIndex =
  template this: untyped = result
  this = new(MozillaMar_MarIndex)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenIndexExpr = this.io.readU4be()
  this.lenIndex = lenIndexExpr
  let rawIndexEntriesExpr = this.io.readBytes(int(this.lenIndex))
  this.rawIndexEntries = rawIndexEntriesExpr
  let rawIndexEntriesIo = newKaitaiStream(rawIndexEntriesExpr)
  let indexEntriesExpr = MozillaMar_IndexEntries.read(rawIndexEntriesIo, this.root, this)
  this.indexEntries = indexEntriesExpr

proc fromFile*(_: typedesc[MozillaMar_MarIndex], filename: string): MozillaMar_MarIndex =
  MozillaMar_MarIndex.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_IndexEntries], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_MarIndex): MozillaMar_IndexEntries =
  template this: untyped = result
  this = new(MozillaMar_IndexEntries)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  block:
    var i: int
    while not this.io.isEof:
      let it = MozillaMar_IndexEntry.read(this.io, this.root, this)
      this.indexEntry.add(it)
      inc i

proc fromFile*(_: typedesc[MozillaMar_IndexEntries], filename: string): MozillaMar_IndexEntries =
  MozillaMar_IndexEntries.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_Signature], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_Signature =
  template this: untyped = result
  this = new(MozillaMar_Signature)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let algorithmExpr = MozillaMar_SignatureAlgorithms(this.io.readU4be())
  this.algorithm = algorithmExpr
  let lenSignatureExpr = this.io.readU4be()
  this.lenSignature = lenSignatureExpr
  let signatureExpr = this.io.readBytes(int(this.lenSignature))
  this.signature = signatureExpr

proc fromFile*(_: typedesc[MozillaMar_Signature], filename: string): MozillaMar_Signature =
  MozillaMar_Signature.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_ProductInformationBlock], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_AdditionalSection): MozillaMar_ProductInformationBlock =
  template this: untyped = result
  this = new(MozillaMar_ProductInformationBlock)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let marChannelNameExpr = encode(this.io.readBytes(int(64)).bytesTerminate(0, false), "UTF-8")
  this.marChannelName = marChannelNameExpr
  let productVersionExpr = encode(this.io.readBytes(int(32)).bytesTerminate(0, false), "UTF-8")
  this.productVersion = productVersionExpr

proc fromFile*(_: typedesc[MozillaMar_ProductInformationBlock], filename: string): MozillaMar_ProductInformationBlock =
  MozillaMar_ProductInformationBlock.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_IndexEntry], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar_IndexEntries): MozillaMar_IndexEntry =
  template this: untyped = result
  this = new(MozillaMar_IndexEntry)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let ofsContentExpr = this.io.readU4be()
  this.ofsContent = ofsContentExpr
  let lenContentExpr = this.io.readU4be()
  this.lenContent = lenContentExpr

  ##[
  File permission bits (in standard unix-style format).
  ]##
  let flagsExpr = this.io.readU4be()
  this.flags = flagsExpr
  let fileNameExpr = encode(this.io.readBytesTerm(0, false, true, true), "UTF-8")
  this.fileName = fileNameExpr

proc body(this: MozillaMar_IndexEntry): seq[byte] = 
  if this.bodyInstFlag:
    return this.bodyInst
  let io = MozillaMar(this.root).io
  let pos = io.pos()
  io.seek(int(this.ofsContent))
  let bodyInstExpr = io.readBytes(int(this.lenContent))
  this.bodyInst = bodyInstExpr
  io.seek(pos)
  this.bodyInstFlag = true
  return this.bodyInst

proc fromFile*(_: typedesc[MozillaMar_IndexEntry], filename: string): MozillaMar_IndexEntry =
  MozillaMar_IndexEntry.read(newKaitaiFileStream(filename), nil, nil)

proc read*(_: typedesc[MozillaMar_AdditionalSection], io: KaitaiStream, root: KaitaiStruct, parent: MozillaMar): MozillaMar_AdditionalSection =
  template this: untyped = result
  this = new(MozillaMar_AdditionalSection)
  let root = if root == nil: cast[MozillaMar](this) else: cast[MozillaMar](root)
  this.io = io
  this.root = root
  this.parent = parent

  let lenBlockExpr = this.io.readU4be()
  this.lenBlock = lenBlockExpr
  let blockIdentifierExpr = MozillaMar_BlockIdentifiers(this.io.readU4be())
  this.blockIdentifier = blockIdentifierExpr
  block:
    let on = this.blockIdentifier
    if on == mozilla_mar.product_information:
      let rawBytesExpr = this.io.readBytes(int(((this.lenBlock - 4) - 4)))
      this.rawBytes = rawBytesExpr
      let rawBytesIo = newKaitaiStream(rawBytesExpr)
      let bytesExpr = MozillaMar_ProductInformationBlock.read(rawBytesIo, this.root, this)
      this.bytes = bytesExpr
    else:
      let bytesExpr = this.io.readBytes(int(((this.lenBlock - 4) - 4)))
      this.bytes = bytesExpr

proc fromFile*(_: typedesc[MozillaMar_AdditionalSection], filename: string): MozillaMar_AdditionalSection =
  MozillaMar_AdditionalSection.read(newKaitaiFileStream(filename), nil, nil)