下载文件支持zip64
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
a5824cecac
commit
ec83609139
|
|
@ -1,5 +1,5 @@
|
|||
import streamSaver from "streamsaver";
|
||||
import "streamsaver/examples/zip-stream.js";
|
||||
import "./zip-stream.js";
|
||||
import store from '@/store'
|
||||
import dcmjs from './dcmUpload/dcmjs'
|
||||
streamSaver.mitm = `${window.location.origin}/mitm.html?version=2.0.0`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,379 @@
|
|||
class Crc32 {
|
||||
constructor() {
|
||||
this.crc = -1
|
||||
}
|
||||
|
||||
append(data) {
|
||||
var crc = this.crc | 0; var table = this.table
|
||||
for (var offset = 0, len = data.length | 0; offset < len; offset++) {
|
||||
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF]
|
||||
}
|
||||
this.crc = crc
|
||||
}
|
||||
|
||||
get() {
|
||||
return ~this.crc
|
||||
}
|
||||
}
|
||||
Crc32.prototype.table = (() => {
|
||||
var i; var j; var t; var table = []
|
||||
for (i = 0; i < 256; i++) {
|
||||
t = i
|
||||
for (j = 0; j < 8; j++) {
|
||||
t = (t & 1)
|
||||
? (t >>> 1) ^ 0xEDB88320
|
||||
: t >>> 1
|
||||
}
|
||||
table[i] = t
|
||||
}
|
||||
return table
|
||||
})()
|
||||
|
||||
const getDataHelper = byteLength => {
|
||||
var uint8 = new Uint8Array(byteLength)
|
||||
return {
|
||||
array: uint8,
|
||||
view: new DataView(uint8.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
const ZIP_SIGNATURE_LOCAL = 0x04034b50
|
||||
const ZIP_SIGNATURE_CENTRAL = 0x02014b50
|
||||
const ZIP_SIGNATURE_EOCD = 0x06054b50
|
||||
const ZIP_SIGNATURE_ZIP64_EOCD = 0x06064b50
|
||||
const ZIP_SIGNATURE_ZIP64_LOCATOR = 0x07064b50
|
||||
|
||||
const ZIP64_MAGIC = 0xFFFFFFFF
|
||||
|
||||
function u16(view, offset, value) { view.setUint16(offset, value, true) }
|
||||
function u32(view, offset, value) { view.setUint32(offset, value >>> 0, true) }
|
||||
|
||||
function concatUint8(chunks, total) {
|
||||
const out = new Uint8Array(total)
|
||||
let off = 0
|
||||
for (const c of chunks) {
|
||||
out.set(c, off)
|
||||
off += c.length
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function createWriter(underlyingSource) {
|
||||
const files = Object.create(null)
|
||||
const filenames = []
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
let offset = 0 // bytes written to output so far
|
||||
let activeZipIndex = 0
|
||||
let ctrl
|
||||
let activeZipObject
|
||||
let closed = false
|
||||
|
||||
function processNextChunk() {
|
||||
if (!activeZipObject) return
|
||||
|
||||
// directory entry: just local header + immediate footer
|
||||
if (activeZipObject.directory) {
|
||||
if (!activeZipObject.headerWritten) {
|
||||
activeZipObject.writeLocalHeader()
|
||||
activeZipObject.headerWritten = true
|
||||
}
|
||||
if (!activeZipObject.footerWritten) {
|
||||
activeZipObject.writeDataDescriptor() // will be zeros
|
||||
activeZipObject.footerWritten = true
|
||||
next()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (!activeZipObject.reader) {
|
||||
if (!activeZipObject.fileLike || !activeZipObject.fileLike.stream) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
activeZipObject.crc = new Crc32()
|
||||
activeZipObject.reader = activeZipObject.fileLike.stream().getReader()
|
||||
activeZipObject.writeLocalHeader()
|
||||
activeZipObject.headerWritten = true
|
||||
return
|
||||
}
|
||||
|
||||
return activeZipObject.reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
activeZipObject.writeDataDescriptor()
|
||||
activeZipObject.footerWritten = true
|
||||
next()
|
||||
return
|
||||
}
|
||||
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value)
|
||||
activeZipObject.crc.append(chunk)
|
||||
activeZipObject.uncompressedLength += chunk.length
|
||||
activeZipObject.compressedLength += chunk.length
|
||||
ctrl.enqueue(chunk)
|
||||
offset += chunk.length
|
||||
})
|
||||
}
|
||||
|
||||
function next() {
|
||||
activeZipIndex++
|
||||
activeZipObject = files[filenames[activeZipIndex]]
|
||||
if (activeZipObject) {
|
||||
processNextChunk()
|
||||
} else if (closed) {
|
||||
closeZip()
|
||||
}
|
||||
}
|
||||
|
||||
function dosDateTime(date) {
|
||||
const dt = new Date(date)
|
||||
const time = ((dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() / 2)) & 0xFFFF
|
||||
const d = (((dt.getFullYear() - 1980) << 9) | ((dt.getMonth() + 1) << 5) | dt.getDate()) & 0xFFFF
|
||||
return { time, date: d }
|
||||
}
|
||||
|
||||
const zipWriter = {
|
||||
enqueue(fileLike) {
|
||||
if (closed) throw new TypeError('Cannot enqueue after close()')
|
||||
|
||||
let name = String(fileLike.name || '').trim()
|
||||
if (!name) throw new Error('Missing file name')
|
||||
|
||||
if (fileLike.directory && !name.endsWith('/')) name += '/'
|
||||
if (files[name]) throw new Error('File already exists.')
|
||||
|
||||
const nameBuf = encoder.encode(name)
|
||||
const commentBuf = encoder.encode(fileLike.comment || '')
|
||||
const { time, date } = dosDateTime(typeof fileLike.lastModified === 'undefined' ? Date.now() : fileLike.lastModified)
|
||||
|
||||
const zipObject = (files[name] = {
|
||||
fileLike,
|
||||
directory: !!fileLike.directory,
|
||||
nameBuf,
|
||||
comment: commentBuf,
|
||||
time,
|
||||
date,
|
||||
|
||||
// tracked
|
||||
offset: 0,
|
||||
crc: null,
|
||||
compressedLength: 0,
|
||||
uncompressedLength: 0,
|
||||
headerWritten: false,
|
||||
footerWritten: false,
|
||||
reader: null,
|
||||
|
||||
// whether sizes/offset require ZIP64
|
||||
needsZip64() {
|
||||
return (
|
||||
this.uncompressedLength > ZIP64_MAGIC ||
|
||||
this.compressedLength > ZIP64_MAGIC ||
|
||||
this.offset > ZIP64_MAGIC
|
||||
)
|
||||
},
|
||||
|
||||
writeLocalHeader() {
|
||||
this.offset = offset
|
||||
|
||||
const generalPurposeBitFlag = 0x0008 // data descriptor present
|
||||
const compressionMethod = 0 // store
|
||||
const versionNeeded = 20 // 2.0 (ZIP64 switches this to 45)
|
||||
|
||||
// We always use data descriptor; write zeros for crc/sizes in local header
|
||||
const local = getDataHelper(30 + this.nameBuf.length)
|
||||
u32(local.view, 0, ZIP_SIGNATURE_LOCAL)
|
||||
u16(local.view, 4, versionNeeded)
|
||||
u16(local.view, 6, generalPurposeBitFlag)
|
||||
u16(local.view, 8, compressionMethod)
|
||||
u16(local.view, 10, this.time)
|
||||
u16(local.view, 12, this.date)
|
||||
u32(local.view, 14, 0)
|
||||
u32(local.view, 18, 0)
|
||||
u32(local.view, 22, 0)
|
||||
u16(local.view, 26, this.nameBuf.length)
|
||||
u16(local.view, 28, 0) // extra length
|
||||
local.array.set(this.nameBuf, 30)
|
||||
|
||||
ctrl.enqueue(local.array)
|
||||
offset += local.array.length
|
||||
},
|
||||
|
||||
writeDataDescriptor() {
|
||||
// data descriptor: optional signature + crc32 + sizes (32-bit or 64-bit)
|
||||
const crc = this.crc ? this.crc.get() >>> 0 : 0
|
||||
const useZip64 = this.needsZip64()
|
||||
|
||||
if (!useZip64) {
|
||||
const dd = getDataHelper(16)
|
||||
u32(dd.view, 0, 0x08074b50)
|
||||
u32(dd.view, 4, crc)
|
||||
u32(dd.view, 8, this.compressedLength)
|
||||
u32(dd.view, 12, this.uncompressedLength)
|
||||
ctrl.enqueue(dd.array)
|
||||
offset += dd.array.length
|
||||
} else {
|
||||
// 24 bytes: sig + crc32 + compSize(8) + uncompSize(8)
|
||||
const dd = getDataHelper(24)
|
||||
u32(dd.view, 0, 0x08074b50)
|
||||
u32(dd.view, 4, crc)
|
||||
// BigInt writes
|
||||
dd.view.setBigUint64(8, BigInt(this.compressedLength), true)
|
||||
dd.view.setBigUint64(16, BigInt(this.uncompressedLength), true)
|
||||
ctrl.enqueue(dd.array)
|
||||
offset += dd.array.length
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
filenames.push(name)
|
||||
|
||||
if (!activeZipObject) {
|
||||
activeZipObject = zipObject
|
||||
processNextChunk()
|
||||
}
|
||||
},
|
||||
|
||||
close() {
|
||||
if (closed) throw new TypeError('Cannot close twice')
|
||||
closed = true
|
||||
if (!activeZipObject) closeZip()
|
||||
}
|
||||
}
|
||||
|
||||
function closeZip() {
|
||||
// Build central directory in memory then enqueue once.
|
||||
const cdChunks = []
|
||||
let cdSize = 0
|
||||
|
||||
for (const name of filenames) {
|
||||
const file = files[name]
|
||||
const useZip64 = file.needsZip64()
|
||||
|
||||
const versionMadeBy = useZip64 ? 45 : 20
|
||||
const versionNeeded = useZip64 ? 45 : 20
|
||||
const generalPurposeBitFlag = 0x0008
|
||||
const compressionMethod = 0
|
||||
|
||||
const crc = file.crc ? file.crc.get() >>> 0 : 0
|
||||
const compressed32 = useZip64 ? ZIP64_MAGIC : file.compressedLength
|
||||
const uncompressed32 = useZip64 ? ZIP64_MAGIC : file.uncompressedLength
|
||||
const offset32 = useZip64 ? ZIP64_MAGIC : file.offset
|
||||
|
||||
// ZIP64 extra field if needed
|
||||
let extra = new Uint8Array(0)
|
||||
if (useZip64) {
|
||||
// headerId(2)=0x0001, dataSize(2)=24, uncompressed(8), compressed(8), offset(8)
|
||||
extra = new Uint8Array(4 + 24)
|
||||
const ev = new DataView(extra.buffer)
|
||||
u16(ev, 0, 0x0001)
|
||||
u16(ev, 2, 24)
|
||||
ev.setBigUint64(4, BigInt(file.uncompressedLength), true)
|
||||
ev.setBigUint64(12, BigInt(file.compressedLength), true)
|
||||
ev.setBigUint64(20, BigInt(file.offset), true)
|
||||
}
|
||||
|
||||
const headerLen = 46 + file.nameBuf.length + extra.length + file.comment.length
|
||||
const cd = getDataHelper(headerLen)
|
||||
|
||||
u32(cd.view, 0, ZIP_SIGNATURE_CENTRAL)
|
||||
u16(cd.view, 4, versionMadeBy)
|
||||
u16(cd.view, 6, versionNeeded)
|
||||
u16(cd.view, 8, generalPurposeBitFlag)
|
||||
u16(cd.view, 10, compressionMethod)
|
||||
u16(cd.view, 12, file.time)
|
||||
u16(cd.view, 14, file.date)
|
||||
u32(cd.view, 16, crc)
|
||||
u32(cd.view, 20, compressed32)
|
||||
u32(cd.view, 24, uncompressed32)
|
||||
u16(cd.view, 28, file.nameBuf.length)
|
||||
u16(cd.view, 30, extra.length)
|
||||
u16(cd.view, 32, file.comment.length)
|
||||
u16(cd.view, 34, 0) // disk number start
|
||||
u16(cd.view, 36, 0) // internal attrs
|
||||
|
||||
// external file attrs: mark directory
|
||||
u32(cd.view, 38, file.directory ? 0x10 : 0)
|
||||
|
||||
u32(cd.view, 42, offset32)
|
||||
|
||||
cd.array.set(file.nameBuf, 46)
|
||||
if (extra.length) cd.array.set(extra, 46 + file.nameBuf.length)
|
||||
if (file.comment.length) cd.array.set(file.comment, 46 + file.nameBuf.length + extra.length)
|
||||
|
||||
cdChunks.push(cd.array)
|
||||
cdSize += cd.array.length
|
||||
}
|
||||
|
||||
const centralDirectoryOffset = offset
|
||||
const centralDirectory = concatUint8(cdChunks, cdSize)
|
||||
ctrl.enqueue(centralDirectory)
|
||||
offset += centralDirectory.length
|
||||
|
||||
const entries = filenames.length
|
||||
|
||||
const needsZip64ForArchive =
|
||||
entries > 0xFFFF ||
|
||||
centralDirectoryOffset > ZIP64_MAGIC ||
|
||||
cdSize > ZIP64_MAGIC ||
|
||||
offset > ZIP64_MAGIC
|
||||
|
||||
const tailChunks = []
|
||||
let tailSize = 0
|
||||
|
||||
if (needsZip64ForArchive) {
|
||||
// ZIP64 EOCD
|
||||
const zip64eocd = getDataHelper(56)
|
||||
u32(zip64eocd.view, 0, ZIP_SIGNATURE_ZIP64_EOCD)
|
||||
zip64eocd.view.setBigUint64(4, BigInt(44), true) // remaining size
|
||||
u16(zip64eocd.view, 12, 45)
|
||||
u16(zip64eocd.view, 14, 45)
|
||||
u32(zip64eocd.view, 16, 0)
|
||||
u32(zip64eocd.view, 20, 0)
|
||||
zip64eocd.view.setBigUint64(24, BigInt(entries), true)
|
||||
zip64eocd.view.setBigUint64(32, BigInt(entries), true)
|
||||
zip64eocd.view.setBigUint64(40, BigInt(cdSize), true)
|
||||
zip64eocd.view.setBigUint64(48, BigInt(centralDirectoryOffset), true)
|
||||
|
||||
// ZIP64 locator
|
||||
const locator = getDataHelper(20)
|
||||
u32(locator.view, 0, ZIP_SIGNATURE_ZIP64_LOCATOR)
|
||||
u32(locator.view, 4, 0)
|
||||
locator.view.setBigUint64(8, BigInt(centralDirectoryOffset + cdSize), true) // offset of zip64eocd
|
||||
u32(locator.view, 16, 1)
|
||||
|
||||
tailChunks.push(zip64eocd.array, locator.array)
|
||||
tailSize += zip64eocd.array.length + locator.array.length
|
||||
}
|
||||
|
||||
// EOCD
|
||||
const eocd = getDataHelper(22)
|
||||
u32(eocd.view, 0, ZIP_SIGNATURE_EOCD)
|
||||
u16(eocd.view, 4, 0)
|
||||
u16(eocd.view, 6, 0)
|
||||
u16(eocd.view, 8, Math.min(entries, 0xFFFF))
|
||||
u16(eocd.view, 10, Math.min(entries, 0xFFFF))
|
||||
u32(eocd.view, 12, needsZip64ForArchive ? ZIP64_MAGIC : cdSize)
|
||||
u32(eocd.view, 16, needsZip64ForArchive ? ZIP64_MAGIC : centralDirectoryOffset)
|
||||
u16(eocd.view, 20, 0) // comment length
|
||||
|
||||
tailChunks.push(eocd.array)
|
||||
tailSize += eocd.array.length
|
||||
|
||||
ctrl.enqueue(concatUint8(tailChunks, tailSize))
|
||||
ctrl.close()
|
||||
}
|
||||
|
||||
return new ReadableStream({
|
||||
start: c => {
|
||||
ctrl = c
|
||||
underlyingSource.start && Promise.resolve(underlyingSource.start(zipWriter))
|
||||
},
|
||||
pull() {
|
||||
return processNextChunk() || (
|
||||
underlyingSource.pull &&
|
||||
Promise.resolve(underlyingSource.pull(zipWriter))
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.ZIP = createWriter
|
||||
Loading…
Reference in New Issue