/*
 * Copyright (C) 2017, hapjs.org. All rights reserved.
 */

'use strict';

var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
_Object$defineProperty(exports, "__esModule", {
  value: true
});
exports.signZip = signZip;
var _keys = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/keys"));
var _path = _interopRequireDefault(require("path"));
var _crypto = _interopRequireDefault(require("crypto"));
var _signature = _interopRequireDefault(require("./signature"));
var _jsrsasign = _interopRequireDefault(require("jsrsasign"));
var _manifest = require("../lib/manifest");
var paths = _interopRequireWildcard(require("../lib/paths"));
var _index = require("../../cli-shared-utils/index.js");
function _getRequireWildcardCache(e) { if ("function" != typeof _WeakMap) return null; var r = new _WeakMap(), t = new _WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = _Object$defineProperty && _Object$getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? _Object$getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? _Object$defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * 加签名zip文件
 * @param fileBuf  文件buf数据
 * @param zipFileHashs  文件hash值 数组
 * @param prikey
 * @param certpem
 * @param output 导出路径 可以不填写
 */
function signZip(fileBuf, zipFileHashs, prikey, certpem, output) {
  try {
    const cert = Buffer.from(_signature.default.Base64.unarmor(certpem));
    const c = new _jsrsasign.default.X509();
    c.readCertPEM(certpem.toString());
    const pubkey = _jsrsasign.default.KEYUTIL.getPEM(c.getPublicKey());

    // 读取zip文件buf
    // const fileBuf = fs.readFileSync(zip)
    if (!fileBuf || fileBuf.length <= 4) {
      (0, _index.error)(`### App Loader ### Zip文件打开失败`);
      return false;
    }

    // 检查文件类型是否正确
    const filemagic = fileBuf.readInt32LE(0);
    if (filemagic.toString(16).toLowerCase() !== '4034b50') {
      (0, _index.error)(`### App Loader ### Zip文件格式错误`);
      return false;
    }

    // 解析数据块
    const chunks = parserZip(fileBuf);
    // 加入文件列表hash  makeSignChunk 函数会用到
    chunks.options = {};
    chunks.options.files = zipFileHashs;
    if (chunks.tag) {
      // 解析成功, 生成签名块
      // 分别处理3个块签名
      (0, _keys.default)(chunks.sections).forEach(item => {
        const chunk = chunks.sections[item];
        chunk.sign = getChunkSign(fileBuf, chunk);
      });

      // 生成整体签名
      signChunk(chunks, prikey, pubkey, cert);
      const newBuffer = saveChunk(fileBuf, chunks);
      if (output) {
        // 写入文件
        _index.fs.writeFileSync(output, newBuffer);
      } else {
        return newBuffer;
      }
    }
  } catch (err) {
    (0, _index.error)(`### App Loader ### 证书文件格式错误`);
    // 删除本地签名缓存
    const manifest = (0, _manifest.get)();
    const cachePath = _path.default.join(__dirname, '../../../node_modules', manifest.package);
    const privateFile = _path.default.join(cachePath, 'private.pem');
    const certificateFile = _path.default.join(cachePath, 'certificate.pem');
    if (_index.fs.existsSync(privateFile)) {
      _index.fs.removeSync(privateFile);
    }
    if (_index.fs.existsSync(certificateFile)) {
      _index.fs.removeSync(certificateFile);
    }
    if (_index.fs.existsSync(paths.RELEASE_PRIVATE_KEY)) {
      _index.fs.removeSync(paths.RELEASE_PRIVATE_KEY);
    }
    if (_index.fs.existsSync(paths.RELEASE_CERTIFICATE)) {
      _index.fs.removeSync(paths.RELEASE_CERTIFICATE);
    }
    throw err;
  }
}

/**
 * 解析Zip, 分解数据块
 * @param buf
 * @param tag
 */
function parserZip(buf) {
  const chunk = {
    tag: false,
    length: buf.length,
    sections: {
      header: null,
      central: null,
      footer: null
    }
  };
  chunk.sections.footer = readEOCD(buf); // 至少22个字节
  if (chunk.sections.footer.tag) {
    chunk.sections.central = readCD(buf, chunk.sections.footer.previous, chunk.sections.footer.startIndex - chunk.sections.footer.previous);
    if (chunk.sections.central.tag) {
      chunk.sections.header = readFH(buf, chunk.sections.central.previous, chunk.sections.central.startIndex - chunk.sections.central.previous);
      if (chunk.sections.header.tag) {
        chunk.tag = true;
      }
    }
  }
  return chunk;
}

/**
 * 从后往前读取
 * @param buf
 * @param tag  结束标签
 * @param offset  起始位置(不包括该位置), -1表示末尾
 */
function readEOCD(buf) {
  const chunk = {
    tag: false
  };
  if (buf && buf.length >= 22) {
    let offset = buf.length - 22;
    let tag;
    // 从开始位置往前单个字节读取, 检查
    while (offset >= 0) {
      tag = buf.readInt32LE(offset);
      if (tag.toString(16).toLowerCase() === '6054b50') {
        // 如果找到起始位置
        chunk.tag = true;
        chunk.startIndex = offset;
        chunk.len = buf.length - offset;
        chunk.previous = buf.readInt32LE(offset + 16);
        break;
      }
      offset -= 1;
    }
  }
  return chunk;
}

/**
 * 从后往前读取
 * @param buf
 * @param tag  结束标签
 * @param offset  起始位置(不包括该位置), -1表示末尾
 */
function readCD(buf, offset, size) {
  const chunk = {
    tag: false
  };
  if (buf && buf.length >= offset) {
    const tag = buf.readInt32LE(offset);
    if (tag.toString(16).toLowerCase() === '2014b50') {
      // 如果找到起始位置
      chunk.tag = true;
      chunk.startIndex = offset;
      chunk.len = size;
      chunk.previous = buf.readInt32LE(offset + 42);
    }
  }
  return chunk;
}

/**
 * 从后往前读取
 * @param buf
 * @param tag  结束标签
 * @param offset  起始位置(不包括该位置), -1表示末尾
 */
function readFH(buf, offset, size) {
  const chunk = {
    tag: false
  };
  if (buf && buf.length >= offset) {
    const tag = buf.readInt32LE(offset);
    if (tag.toString(16).toLowerCase() === '4034b50') {
      // 如果找到起始位置
      chunk.tag = true;
      chunk.startIndex = offset;
      chunk.len = size;
      chunk.previous = -1;
    }
  }
  return chunk;
}

/**
 * 数据块hash
 * @param buf
 * @param chunk
 */
function getChunkSign(buf, chunk) {
  // 存储每个块的摘要
  const cur = chunk.startIndex;
  const end = chunk.startIndex + chunk.len;
  const chk = buf.slice(cur, end);
  const header = Buffer.alloc(5 + chunk.len);
  header[0] = 0xa5;
  header.writeInt32LE(chk.length, 1);
  chk.copy(header, 5);
  const signer = _crypto.default.createHash('SHA256');
  signer.update(header);
  return signer.digest();
}

/**
 * 对整个chunk签名
 * @param chunks
 */
function signChunk(chunks, prikey, pubkey, cert) {
  const sections = chunks.sections;

  // 二进制拼接每个块摘要
  const length = sections.header.sign.length + sections.central.sign.length + sections.footer.sign.length + 5;
  const wholedata = Buffer.alloc(length);
  let offset = 0;
  wholedata.writeInt8(0x5a, 0);
  wholedata.writeInt32LE(3, 1);
  offset += 5;
  function writeBuffer(buf) {
    buf.copy(wholedata, offset);
    offset += buf.length;
  }
  writeBuffer(sections.header.sign);
  writeBuffer(sections.central.sign);
  writeBuffer(sections.footer.sign);

  // 计算整体摘要
  const signer = _crypto.default.createHash('SHA256');
  signer.update(wholedata);
  const signature = signer.digest();
  try {
    // 生成sign block, 计算block总长度, 向buf中考入数据
    const signchunk = makeSignChunk(chunks.options, signature, prikey, pubkey, cert);
    chunks.signchunk = saveSignChunk(signchunk);
  } catch (err) {
    (0, _index.error)("请检查下签名文件是否正确，建议重新生成签名文件...");
    throw err;
  }
}

/**
 *
 * @param file
 */
function makeSignChunk(options, sign, prikey, pubkey, cert) {
  // 摘要块
  const digestBuf = Buffer.alloc(sign.length + 12);
  digestBuf.writeInt32LE(sign.length + 8, 0);
  digestBuf.writeInt32LE(0x0103, 4);
  digestBuf.writeInt32LE(sign.length, 8);
  sign.copy(digestBuf, 12);
  const digestBlock = {
    len: digestBuf.length,
    buffer: digestBuf
  };

  // 证书块
  const certBuf = Buffer.alloc(cert.length + 4);
  certBuf.writeInt32LE(cert.length, 0);
  cert.copy(certBuf, 4);
  const certBlock = {
    len: certBuf.length,
    buffer: certBuf
  };

  // 签名数据
  const signdataBlock = {
    len: 12,
    digests: {
      size: 0,
      data: []
    },
    certs: {
      size: 0,
      data: []
    },
    additional: 0
  };
  signdataBlock.digests.data.push(digestBlock);
  signdataBlock.digests.size += digestBlock.len;
  signdataBlock.len += digestBlock.len;
  signdataBlock.certs.data.push(certBlock);
  signdataBlock.certs.size += certBlock.len;
  signdataBlock.len += certBlock.len;

  // 将public.pem转化为der
  const pubbuf = Buffer.from(_signature.default.Base64.unarmor(pubkey));
  const signBlock = {
    len: 16 + pubbuf.length,
    size: 12 + pubbuf.length,
    signdata: {
      size: 0,
      buffer: null
    },
    signatures: {
      size: 0,
      data: []
    },
    pubkey: {
      size: pubbuf.length,
      buffer: pubbuf
    }
  };
  signBlock.signdata.buffer = makeSignDataBuffer(signdataBlock);
  signBlock.signdata.size = signdataBlock.len;
  signBlock.size += signdataBlock.len;
  signBlock.len += signdataBlock.len;

  // 生成签名
  const signer = _crypto.default.createSign('RSA-SHA256');
  signer.update(signBlock.signdata.buffer);
  const signature = signer.sign(prikey);
  const signatureBlock = {
    len: signature.length + 12,
    size: signature.length + 8,
    id: 0x0103,
    buffer: signature
  };
  signBlock.signatures.data.push(signatureBlock);
  signBlock.signatures.size += signatureBlock.len;
  signBlock.size += signatureBlock.len;
  signBlock.len += signatureBlock.len;
  const signBlocks = {
    len: 4,
    size: 0,
    data: []
  };
  signBlocks.data.push(signBlock);
  signBlocks.size += signBlock.len;
  signBlocks.len += signBlock.len;

  // 生成key-value
  const kvBlock = {
    len: signBlocks.len + 12,
    size: signBlocks.len + 4,
    id: 0x01000101,
    value: signBlocks
  };
  const signchunk = {
    len: 32,
    size: 24,
    data: []
  };
  signchunk.data.push(kvBlock);
  signchunk.size += kvBlock.len;
  signchunk.len += kvBlock.len;

  // 添加文件列表hash kvblock
  if (options.files) {
    const filehashChunk = signFiles(options.files, prikey);
    if (filehashChunk) {
      const filesignBlocks = {
        len: 4,
        size: 0,
        data: []
      };
      filesignBlocks.data.push(filehashChunk);
      filesignBlocks.size += filehashChunk.length;
      filesignBlocks.len += filehashChunk.length;
      const filekvBlock = {
        len: filesignBlocks.len + 12,
        size: filesignBlocks.len + 4,
        id: 0x01000201,
        value: filesignBlocks
      };
      signchunk.data.push(filekvBlock);
      signchunk.size += filekvBlock.len;
      signchunk.len += filekvBlock.len;
    }
  }
  return signchunk;
}

/**
 *
 * @param block
 */
function makeSignDataBuffer(block) {
  const buffer = Buffer.alloc(block.len);
  let offset = 0;
  buffer.writeInt32LE(block.digests.size, offset);
  offset += 4;
  block.digests.data.forEach(item => {
    item.buffer.copy(buffer, offset);
    offset += item.len;
  });
  buffer.writeInt32LE(block.certs.size, offset);
  offset += 4;
  block.certs.data.forEach(item => {
    item.buffer.copy(buffer, offset);
    offset += item.len;
  });
  buffer.writeInt32LE(block.additional, offset);
  return buffer;
}

/**
 *
 * @param file
 */
const SigMagic = 'RPK Sig Block 42';
function saveSignChunk(signchunk) {
  const buffer = Buffer.alloc(signchunk.len);
  let offset = 0;

  // 大小
  buffer.writeInt32LE(signchunk.size, offset);
  offset += 4;
  buffer.writeInt32LE(0, offset);
  offset += 4;

  // key-value
  signchunk.data.forEach(kv => {
    buffer.writeInt32LE(kv.size, offset);
    offset += 4;
    buffer.writeInt32LE(0, offset);
    offset += 4;
    buffer.writeInt32LE(kv.id, offset);
    offset += 4;

    // value
    buffer.writeInt32LE(kv.value.size, offset);
    offset += 4;
    if (kv.id === 0x01000101) {
      // sign blocks
      kv.value.data.forEach(block => {
        buffer.writeInt32LE(block.size, offset);
        offset += 4;

        // signdata
        buffer.writeInt32LE(block.signdata.size, offset);
        offset += 4;
        block.signdata.buffer.copy(buffer, offset);
        offset += block.signdata.buffer.length;

        // signature
        buffer.writeInt32LE(block.signatures.size, offset);
        offset += 4;
        block.signatures.data.forEach(signature => {
          buffer.writeInt32LE(signature.size, offset);
          offset += 4;
          buffer.writeInt32LE(signature.id, offset);
          offset += 4;
          buffer.writeInt32LE(signature.buffer.length, offset);
          offset += 4;
          signature.buffer.copy(buffer, offset);
          offset += signature.buffer.length;
        });

        // pubkey
        buffer.writeInt32LE(block.pubkey.size, offset);
        offset += 4;
        block.pubkey.buffer.copy(buffer, offset);
        offset += block.pubkey.buffer.length;
      });
    } else if (kv.id === 0x01000201) {
      // files blocks
      kv.value.data.forEach(block => {
        block.copy(buffer, offset);
        offset += block.length;
      });
    }
  });

  // 大小
  buffer.writeInt32LE(signchunk.size, offset);
  offset += 4;
  buffer.writeInt32LE(0, offset);
  offset += 4;

  // 魔法值
  const magic = Buffer.from(SigMagic);
  magic.copy(buffer, offset);
  return buffer;
}

/**
 *
 * @param file
 */
function makeSignFile(filepath) {
  // 提取文件名
  const extname = _path.default.extname(filepath);
  const dir = _path.default.dirname(filepath);
  const basename = _path.default.basename(filepath, extname);
  return _path.default.join(dir, basename + '.signed' + extname);
}

/**
 * 对整个chunk签名
 * @param buf
 * @param chunks
 * @return 返回签名后的文件buffer
 */
function saveChunk(buf, chunks) {
  // 创建新buffer
  const newBuffer = Buffer.alloc(buf.length + chunks.signchunk.length);
  let offset = 0;
  const sections = chunks.sections;

  // 拷贝header
  buf.copy(newBuffer, offset, sections.header.startIndex, sections.header.startIndex + sections.header.len);
  offset += sections.header.len;

  // 拷贝signblock
  chunks.signchunk.copy(newBuffer, offset);
  offset += chunks.signchunk.length;

  // 拷贝central
  buf.copy(newBuffer, offset, sections.central.startIndex, sections.central.startIndex + sections.central.len);
  offset += sections.central.len;

  // 修改eocd
  buf.writeInt32LE(sections.central.startIndex + chunks.signchunk.length, sections.footer.startIndex + 16);
  // 拷贝eocd
  buf.copy(newBuffer, offset, sections.footer.startIndex, sections.footer.startIndex + sections.footer.len);
  offset += sections.footer.len;
  return newBuffer;
}

/**
 * 加签名文件
 * @param filepath
 * @param prikey
 * @param pubkey
 * @param output
 */
function signFiles(filehashs, prikey, output) {
  const chunk = {
    len: 8,
    size: 4,
    digests: [],
    sign: null
  };

  // 生成hash块
  filehashs.forEach(item => {
    // name hash
    const namehash = _signature.default.CRC32.digest(item.name);

    // 计算大小
    const sum = 6 + item.hash.length;
    const chk = Buffer.alloc(sum);
    let offset = 0;
    chk.writeInt32LE(namehash, offset);
    offset += 4;
    chk.writeInt16LE(item.hash.length, offset);
    offset += 2;
    item.hash.copy(chk, offset);
    offset += item.hash.length;
    chunk.digests.push(chk);
    chunk.size += sum;
    chunk.len += sum;
  });

  // 生成整体签名
  signDigestChunk(chunk, prikey);

  // 写入文件 若没有output 返回文件buf
  return saveDigestChunk(chunk, output);
}

/**
 *
 * @param chunk
 * @param prikey
 */
function signDigestChunk(chunk, prikey) {
  const buf = Buffer.alloc(chunk.size);
  let offset = 0;
  buf.writeInt32LE(0x0103, offset);
  offset += 4;
  chunk.digests.forEach(chk => {
    chk.copy(buf, offset);
    offset += chk.length;
  });
  chunk.digests = buf.slice();

  // 生成签名
  const signer = _crypto.default.createSign('RSA-SHA256');
  signer.update(buf);
  const signature = signer.sign(prikey);
  chunk.sign = {
    len: 12 + signature.length,
    size: 8 + signature.length,
    id: 0x0103,
    data: signature
  };
  chunk.len += chunk.sign.len;
}

/**
 *
 * @param chunk
 * @param output
 */
function saveDigestChunk(chunk, output) {
  // 创建新buffer
  const newBuffer = Buffer.alloc(chunk.len);
  let offset = 0;
  newBuffer.writeInt32LE(chunk.size, offset);
  offset += 4;

  // 文件hash列表
  chunk.digests.copy(newBuffer, offset);
  offset += chunk.digests.length;

  // 写入签名
  newBuffer.writeInt32LE(chunk.sign.size, offset);
  offset += 4;
  newBuffer.writeInt32LE(chunk.sign.id, offset);
  offset += 4;
  newBuffer.writeInt32LE(chunk.sign.data.length, offset);
  offset += 4;
  chunk.sign.data.copy(newBuffer, offset);
  offset += chunk.sign.data.length;

  // 写入文件
  if (output) {
    _index.fs.writeFileSync(output, newBuffer);
  }
  return newBuffer;
}