function checkRecords(centralRecord, localRecord, archiveName) {
  const centralName = textDecoder.decode(centralRecord.name);
  const localName = textDecoder.decode(localRecord.name);

  // In some zip files created on Windows, the filename stored in the central
  // dir contains "\" instead of "/".  Strangely, the file name in the local
  // directory uses "/" as specified:
  // http://www.info-zip.org/FAQ.html#backslashes or APPNOTE#4.4.17.1, "All
  // slashes MUST be forward slashes '/'") but there are a lot of bad zip
  // generators...  Search "unzip mismatching "local" filename continuing with
  // "central" filename version".
  //
  // The reasoning appears to be that the central directory is for
  // user display and may differ, though this opens the possibility
  // for spoofing attacks.
  // http://seclists.org/fulldisclosure/2009/Sep/394
  //
  // We strike a compromise: the central directory name may vary from the local
  // name exactly and only by different slashes.
  if (centralName.replace(/\\/g, '/') !== localName) {
    throw Error(
      `Zip integrity error: central record file name ${q(
        centralName,
      )} must match local file name ${q(localName)} in archive ${q(
        archiveName,
      )}`,
    );
  }

  /**
   * @param {boolean} value
   * @param {string} message
   */
  function check(value, message) {
    if (!value) {
      throw Error(
        `Zip integrity error: ${message} for file ${q(
          localName,
        )} in archive ${q(archiveName)}`,
      );
    }
  }

  check(
    centralRecord.bitFlag === localRecord.bitFlag,
    `Central record bit flag ${centralRecord.bitFlag.toString(
      16,
    )} must match local record bit flag ${localRecord.bitFlag.toString(16)}`,
  );
  check(
    centralRecord.compressionMethod === localRecord.compressionMethod,
    `Central record compression method ${q(
      centralRecord.compressionMethod,
    )} must match local compression method ${q(localRecord.compressionMethod)}`,
  );
  // TODO Date integrity check would be easier on the original bytes.
  // Perhaps defer decoding the underlying bytes.
  check(
    centralRecord.crc32 === localRecord.crc32,
    `Central record CRC-32 checksum ${centralRecord.crc32} must match local checksum ${localRecord.crc32}`,
  );
  check(
    centralRecord.compressedLength === localRecord.compressedLength,
    `Central record compressed size ${centralRecord.compressedLength} must match local ${localRecord.compressedLength}`,
  );
  check(
    centralRecord.uncompressedLength === localRecord.uncompressedLength,
    `Central record uncompressed size ${centralRecord.uncompressedLength} must match local ${localRecord.uncompressedLength}`,
  );

  const checksum = crc32(localRecord.content);
  check(
    checksum === localRecord.crc32,
    `CRC-32 checksum mismatch, wanted ${localRecord.crc32} but actual content is ${checksum}`,
  );
}
@param {CentralFileRecord} centralRecord
@param {LocalFileRecord} localRecord
@param {string} archiveName