下载文件支持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 from "streamsaver";
|
||||||
import "streamsaver/examples/zip-stream.js";
|
import "./zip-stream.js";
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import dcmjs from './dcmUpload/dcmjs'
|
import dcmjs from './dcmUpload/dcmjs'
|
||||||
streamSaver.mitm = `${window.location.origin}/mitm.html?version=2.0.0`
|
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