///|
// ZIP32 only. All arithmetic operands are bounded before they are added.
fn ods_zip_u16(data : Bytes, pos : Int32) -> Int32 raise OdsError {
  if pos < 0 || pos > data.length() - 2 {
    raise Invalid("truncated ZIP")
  }
  data[pos].to_int() | (data[pos + 1].to_int() << 8)
}

///|
fn ods_zip_u32(data : Bytes, pos : Int32) -> Int32 raise OdsError {
  ods_zip_u16(data, pos) | (ods_zip_u16(data, pos + 2) << 16)
}

///|
fn ods_zip_size(data : Bytes, pos : Int32) -> Int32 raise OdsError {
  let n = ods_zip_u32(data, pos)
  if n < 0 || n > 33554432 {
    raise Invalid("unsupported ZIP size or ZIP64")
  }
  n
}

///|
fn ods_zip_extra(
  data : Bytes,
  start : Int32,
  length : Int32,
) -> Unit raise OdsError {
  let end = start + length
  if end > data.length() {
    raise Invalid("truncated ZIP extra field")
  }
  let mut p = start
  while p < end {
    if end - p < 4 {
      raise Invalid("invalid ZIP extra field")
    }
    let tag = ods_zip_u16(data, p)
    let size = ods_zip_u16(data, p + 2)
    if tag == 1 {
      raise Invalid("ZIP64 is unsupported")
    }
    // Unicode Path metadata may supply a second interpretation of an otherwise
    // allowlisted raw filename. Never accept competing path identities.
    if tag == 0x7075 {
      raise Invalid("ZIP Unicode path aliases are unsupported")
    }
    p = p + 4 + size
    if p > end {
      raise Invalid("truncated ZIP extra field")
    }
  }
}

///|
fn ods_zip_utf8(data : Bytes) -> String raise OdsError {
  @utf8.decode(data[:]) catch {
    _ => raise Invalid("invalid ZIP/XML UTF-8")
  }
}

///|
fn ods_zip_inflate(data : Bytes, budget : Int32) -> Bytes raise OdsError {
  let decoder = @flate.Inflater()
  let scratch = FixedArray::make(8192, b'\x00')
  let out = @buffer.Buffer()
  let mut consumed = 0
  for ;; {
    let status = decoder.step(data[consumed:], scratch.mut_view(), end=true) catch {
      _ => raise Invalid("invalid DEFLATE stream")
    }
    let used = decoder.last_consumed()
    let produced = decoder.last_produced()
    if produced > budget - out.length() {
      raise Invalid("ODS exceeds 4 MiB inflated budget")
    }
    for i in 0.. String raise OdsError {
  let length = data.length()
  if length < 22 || length > 33554432 {
    raise Invalid("invalid ODS ZIP input size")
  }
  let mut end = length - 22
  let min_end = if length > 65557 { length - 65557 } else { 0 }
  while end >= min_end {
    if ods_zip_u32(data, end) == 0x06054b50 &&
      end + 22 + ods_zip_u16(data, end + 20) == length {
      break
    }
    end = end - 1
  }
  if end < min_end {
    raise Invalid("ZIP end record missing")
  }
  let count = ods_zip_u16(data, end + 10)
  if ods_zip_u16(data, end + 4) != 0 ||
    ods_zip_u16(data, end + 6) != 0 ||
    ods_zip_u16(data, end + 8) != count {
    raise Invalid("multi-disk ZIP is unsupported")
  }
  if count == 0 || count > 128 {
    raise Invalid("ZIP entry count exceeds budget")
  }
  let cd_size = ods_zip_size(data, end + 12)
  let cd_start = ods_zip_size(data, end + 16)
  if cd_start + cd_size != end {
    raise Invalid("invalid ZIP directory or ZIP64")
  }
  let names : Array[String] = []
  let spans : Array[(Int32, Int32)] = []
  let mut covered = 0
  let mut p = cd_start
  let mut total = 0
  let mut content : String? = None
  let mut mimetype = false
  for _ in 0.. end - 46 || ods_zip_u32(data, p) != 0x02014b50 {
      raise Invalid("invalid ZIP directory")
    }
    let version = ods_zip_u16(data, p + 6)
    let flags = ods_zip_u16(data, p + 8)
    let compression = ods_zip_u16(data, p + 10)
    if version > 20 ||
      (flags & 0xF7F1) != 0 ||
      (compression != 0 && compression != 8) ||
      (compression == 0 && (flags & 6) != 0) {
      raise Invalid("encrypted or unsupported ZIP entry")
    }
    let crc = ods_zip_u32(data, p + 16)
    let packed = ods_zip_size(data, p + 20)
    let unpacked = ods_zip_size(data, p + 24)
    if unpacked > 4194304 - total {
      raise Invalid("ODS exceeds 4 MiB inflated budget")
    }
    let name_len = ods_zip_u16(data, p + 28)
    let extra_len = ods_zip_u16(data, p + 30)
    let comment_len = ods_zip_u16(data, p + 32)
    if ods_zip_u16(data, p + 34) != 0 {
      raise Invalid("multi-disk ZIP entry")
    }
    let next = p + 46 + name_len + extra_len + comment_len
    if name_len == 0 || next > end {
      raise Invalid("invalid ZIP entry name")
    }
    let name_bytes = data[p + 46:p + 46 + name_len].to_owned()
    let name = ods_zip_utf8(name_bytes)
    let allowed = [
      "mimetype", "content.xml", "styles.xml", "meta.xml", "settings.xml", "manifest.rdf",
      "META-INF/manifest.xml", "Thumbnails/thumbnail.png", "Configurations2/", "Configurations2/toolpanel/",
      "Configurations2/floater/", "Configurations2/statusbar/", "Configurations2/toolbar/",
      "Configurations2/progressbar/", "Configurations2/popupmenu/", "Configurations2/menubar/",
    ]
    if !allowed.contains(name) || (name.has_suffix("/") && unpacked != 0) {
      raise Invalid("unsupported ODS package entry")
    }
    if names.contains(name) {
      raise Invalid("duplicate ZIP entry")
    }
    names.push(name)
    ods_zip_extra(data, p + 46 + name_len, extra_len)
    let local_start = ods_zip_size(data, p + 42)
    if local_start > cd_start - 30 ||
      ods_zip_u32(data, local_start) != 0x04034b50 {
      raise Invalid("invalid ZIP local header")
    }
    if ods_zip_u16(data, local_start + 4) != version ||
      ods_zip_u16(data, local_start + 6) != flags ||
      ods_zip_u16(data, local_start + 8) != compression ||
      ods_zip_u16(data, local_start + 26) != name_len {
      raise Invalid("ZIP local/directory mismatch")
    }
    let local_extra = ods_zip_u16(data, local_start + 28)
    let body = local_start + 30 + name_len + local_extra
    if body + packed > cd_start {
      raise Invalid("truncated ZIP entry")
    }
    if data[local_start + 30:local_start + 30 + name_len].to_owned() !=
      name_bytes {
      raise Invalid("ZIP local name mismatch")
    }
    ods_zip_extra(data, local_start + 30 + name_len, local_extra)
    let local_crc = ods_zip_u32(data, local_start + 14)
    let local_packed = ods_zip_size(data, local_start + 18)
    let local_unpacked = ods_zip_size(data, local_start + 22)
    let mut finish = body + packed
    if (flags & 8) == 0 {
      if local_crc != crc ||
        local_packed != packed ||
        local_unpacked != unpacked {
        raise Invalid("ZIP local size/CRC mismatch")
      }
    } else {
      if (local_crc != 0 && local_crc != crc) ||
        (local_packed != 0 && local_packed != packed) ||
        (local_unpacked != 0 && local_unpacked != unpacked) {
        raise Invalid("ZIP descriptor header mismatch")
      }
      // Accept both signed and unsigned ZIP32 data descriptors.
      let d = if ods_zip_u32(data, finish) == 0x08074b50 {
        finish + 4
      } else {
        finish
      }
      if d + 12 > cd_start ||
        ods_zip_u32(data, d) != crc ||
        ods_zip_size(data, d + 4) != packed ||
        ods_zip_size(data, d + 8) != unpacked {
        raise Invalid("invalid ZIP data descriptor")
      }
      finish = d + 12
    }
    for (a, b) in spans {
      if local_start < b && finish > a {
        raise Invalid("overlapping ZIP entries")
      }
    }
    spans.push((local_start, finish))
    covered = covered + finish - local_start
    let raw = data[body:body + packed].to_owned()
    let decoded = if compression == 0 {
      if packed != unpacked {
        raise Invalid("invalid stored ZIP size")
      }
      raw
    } else {
      ods_zip_inflate(raw, 4194304 - total)
    }
    if decoded.length() != unpacked {
      raise Invalid("forged ZIP uncompressed size")
    }
    if @checksum.crc32(decoded[:]).reinterpret_as_int() != crc {
      raise Invalid("ZIP CRC mismatch")
    }
    total = total + decoded.length()
    if name == "mimetype" {
      if compression != 0 ||
        local_start != 0 ||
        local_extra != 0 ||
        decoded != @utf8.encode(media_type[:]) {
        raise Invalid("invalid ODS mimetype")
      }
      mimetype = true
    } else if name == "content.xml" {
      content = Some(ods_zip_utf8(decoded))
    } else if name != "META-INF/manifest.xml" && name != "Configurations2/" {
      omitted.push(name)
    }
    p = next
  }
  if p != end || covered != cd_start || !mimetype {
    raise Invalid("invalid ODS ZIP structure")
  }
  match content {
    Some(xml) => xml
    None => raise Invalid("ODS content.xml missing")
  }
}