123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- let tarball = {};
- tarball.TarReader = class {
- constructor() {
- this.fileInfo = [];
- }
- readFile(file) {
- return new Promise((resolve, reject) => {
- let reader = new FileReader();
- reader.onload = (event) => {
- this.buffer = event.target.result;
- this.fileInfo = [];
- this._readFileInfo();
- resolve(this.fileInfo);
- };
- reader.readAsArrayBuffer(file);
- });
- }
- readArrayBuffer(arrayBuffer) {
- return new Promise((resolve, reject) => {
- this.buffer = arrayBuffer;
- this.fileInfo = [];
- this._readFileInfo();
- resolve(this.fileInfo);
- });
- }
- _readFileInfo() {
- this.fileInfo = [];
- let offset = 0;
- let file_size = 0;
- let file_name = "";
- let file_type = null;
- while(offset < this.buffer.byteLength - 512) {
- file_name = this._readFileName(offset); // file name
- if(file_name.length == 0) {
- break;
- }
- file_type = this._readFileType(offset);
- file_size = this._readFileSize(offset);
-
- this.fileInfo.push({
- "name": file_name,
- "type": file_type,
- "size": file_size,
- "header_offset": offset
- });
- offset += (512 + 512*Math.trunc(file_size/512));
- if(file_size % 512) {
- offset += 512;
- }
- }
- }
- getFileInfo() {
- return this.fileInfo;
- }
- _readString(str_offset, size) {
- let strView = new Uint8Array(this.buffer, str_offset, size);
- let i = 0;
- let rtnStr = "";
- while(strView[i] != 0) {
- rtnStr += String.fromCharCode(strView[i]);
- i++;
- }
- return rtnStr;
- }
- _readFileName(header_offset) {
- let name = this._readString(header_offset, 100);
- return name;
- }
- _readFileType(header_offset) {
- // offset: 156
- let typeView = new Uint8Array(this.buffer, header_offset+156, 1);
- let typeStr = String.fromCharCode(typeView[0]);
- if(typeStr == "0") {
- return "file";
- } else if(typeStr == "5") {
- return "directory";
- } else {
- return typeStr;
- }
- }
- _readFileSize(header_offset) {
- // offset: 124
- let szView = new Uint8Array(this.buffer, header_offset+124, 12);
- let szStr = "";
- for(let i = 0; i < 11; i++) {
- szStr += String.fromCharCode(szView[i]);
- }
- return parseInt(szStr,8);
- }
- _readFileBlob(file_offset, size, mimetype) {
- let view = new Uint8Array(this.buffer, file_offset, size);
- let blob = new Blob([view], {"type": mimetype});
- return blob;
- }
- _readTextFile(file_offset, size) {
- let view = new Uint8Array(this.buffer, file_offset, size);
- var utf8 = Array.from(view).map(function (item) {
- return String.fromCharCode(item);
- }).join('');
- return decodeURIComponent(escape(utf8));
- }
-
- _readBase64File(file_offset, size) {
- let view = new Uint8Array(this.buffer, file_offset, size);
- //var b64encoded = btoa(String.fromCharCode.apply(null, view));
- return view;
- }
- getTextFile(file_name) {
- let i = this.fileInfo.findIndex(info => info.name == file_name);
- if(i >= 0) {
- let info = this.fileInfo[i];
- return this._readTextFile(info.header_offset+512, info.size);
- }
- }
- getFileBlob(file_name, mimetype) {
- let i = this.fileInfo.findIndex(info => info.name == file_name);
- if(i >= 0) {
- let info = this.fileInfo[i];
- return this._readFileBlob(info.header_offset+512, info.size, mimetype);
- }
- }
-
- decode(input) {
- var chr1, chr2, chr3;
- var enc1, enc2, enc3, enc4;
- var i = 0, resultIndex = 0;
- var dataUrlPrefix = "data:";
- if (input.substr(0, dataUrlPrefix.length) === dataUrlPrefix) {
- // This is a common error: people give a data url
- // (data:image/png;base64,iVBOR...) with a {base64: true} and
- // wonders why things don't work.
- // We can detect that the string input looks like a data url but we
- // *can't* be sure it is one: removing everything up to the comma would
- // be too dangerous.
- throw new Error("Invalid base64 input, it looks like a data url.");
- }
- input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
- var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var totalLength = input.length * 3 / 4;
- if(input.charAt(input.length - 1) === _keyStr.charAt(64)) {
- totalLength--;
- }
- if(input.charAt(input.length - 2) === _keyStr.charAt(64)) {
- totalLength--;
- }
- if (totalLength % 1 !== 0) {
- // totalLength is not an integer, the length does not match a valid
- // base64 content. That can happen if:
- // - the input is not a base64 content
- // - the input is *almost* a base64 content, with a extra chars at the
- // beginning or at the end
- // - the input uses a base64 variant (base64url for example)
- throw new Error("Invalid base64 input, bad content length.");
- }
- var output;
- output = new Uint8Array(totalLength|0);
- while (i < input.length) {
- enc1 = _keyStr.indexOf(input.charAt(i++));
- enc2 = _keyStr.indexOf(input.charAt(i++));
- enc3 = _keyStr.indexOf(input.charAt(i++));
- enc4 = _keyStr.indexOf(input.charAt(i++));
- chr1 = (enc1 << 2) | (enc2 >> 4);
- chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
- chr3 = ((enc3 & 3) << 6) | enc4;
- output[resultIndex++] = chr1;
- if (enc3 !== 64) {
- output[resultIndex++] = chr2;
- }
- if (enc4 !== 64) {
- output[resultIndex++] = chr3;
- }
- }
- return output;
- };
- };
- tarball.TarWriter = class {
- constructor() {
- this.fileData = [];
- }
- addTextFile(name, text, opts) {
- let buf = new ArrayBuffer(text.length);
- let arr = new Uint8Array(buf);
- for(let i = 0; i < text.length; i++) {
- arr[i] = text.charCodeAt(i);
- }
- this.fileData.push({
- name: name,
- array: arr,
- type: "file",
- size: arr.length,
- dataType: "array",
- opts: opts
- });
- }
- addFileArrayBuffer(name, arrayBuffer, opts) {
- let arr = new Uint8Array(arrayBuffer);
- this.fileData.push({
- name: name,
- array: arr,
- type: "file",
- size: arr.length,
- dataType: "array",
- opts: opts
- });
- }
- addFileUint8(name, arr, opts) {
- this.fileData.push({
- name: name,
- array: arr,
- type: "file",
- size: arr.length,
- dataType: "array",
- opts: opts
- });
- }
- addFile(name, file, opts) {
- this.fileData.push({
- name: name,
- file: file,
- size: file.size,
- type: "file",
- dataType: "file",
- opts: opts
- });
- }
- addFolder(name, opts) {
- this.fileData.push({
- name: name,
- type: "directory",
- size: 0,
- dataType: "none",
- opts: opts
- });
- }
- _createBuffer() {
- let tarDataSize = 0;
- for(let i = 0; i < this.fileData.length; i++) {
- let size = this.fileData[i].size;
- tarDataSize += 512 + 512*Math.trunc(size/512);
- if(size % 512) {
- tarDataSize += 512;
- }
- }
- let bufSize = 10240*Math.trunc(tarDataSize/10240);
- if(tarDataSize % 10240) {
- bufSize += 10240;
- }
- this.buffer = new ArrayBuffer(bufSize);
- }
- async download(filename) {
- let blob = await this.write();
- let $downloadElem = document.createElement('a');
- $downloadElem.href = URL.createObjectURL(blob);
- $downloadElem.download = filename;
- $downloadElem.style.display = "none";
- document.body.appendChild($downloadElem);
- $downloadElem.click();
- document.body.removeChild($downloadElem);
- }
- write() {
- return new Promise((resolve,reject) => {
- this._createBuffer();
- let offset = 0;
- let filesAdded = 0;
- let onFileDataAdded = () => {
- filesAdded++;
- if(filesAdded === this.fileData.length) {
- let arr = new Uint8Array(this.buffer);
- let blob = new Blob([arr], {"type":"application/x-tar"});
- resolve(blob);
- }
- };
- for(let fileIdx = 0; fileIdx < this.fileData.length; fileIdx++) {
- let fdata = this.fileData[fileIdx];
- // write header
- this._writeFileName(fdata.name, offset);
- this._writeFileType(fdata.type, offset);
- this._writeFileSize(fdata.size, offset);
- this._fillHeader(offset, fdata.opts, fdata.type);
- this._writeChecksum(offset);
- // write file data
- let destArray = new Uint8Array(this.buffer, offset+512, fdata.size);
- if(fdata.dataType === "array") {
- for(let byteIdx = 0; byteIdx < fdata.size; byteIdx++) {
- destArray[byteIdx] = fdata.array[byteIdx];
- }
- onFileDataAdded();
- } else if(fdata.dataType === "file") {
- let reader = new FileReader();
-
- reader.onload = (function(outArray) {
- let dArray = outArray;
- return function(event) {
- let sbuf = event.target.result;
- let sarr = new Uint8Array(sbuf);
- for(let bIdx = 0; bIdx < sarr.length; bIdx++) {
- dArray[bIdx] = sarr[bIdx];
- }
- onFileDataAdded();
- };
- })(destArray);
- reader.readAsArrayBuffer(fdata.file);
- } else if(fdata.type === "directory") {
- onFileDataAdded();
- }
- offset += (512 + 512*Math.trunc(fdata.size/512));
- if(fdata.size % 512) {
- offset += 512;
- }
- }
- });
- }
- _writeString(str, offset, size) {
- let strView = new Uint8Array(this.buffer, offset, size);
- for(let i = 0; i < size; i++) {
- if(i < str.length) {
- strView[i] = str.charCodeAt(i);
- } else {
- strView[i] = 0;
- }
- }
- }
- _writeFileName(name, header_offset) {
- // offset: 0
- this._writeString(name, header_offset, 100);
- }
- _writeFileType(typeStr, header_offset) {
- // offset: 156
- let typeChar = "0";
- if(typeStr === "file") {
- typeChar = "0";
- } else if(typeStr === "directory") {
- typeChar = "5";
- }
- let typeView = new Uint8Array(this.buffer, header_offset + 156, 1);
- typeView[0] = typeChar.charCodeAt(0);
- }
- _writeFileSize(size, header_offset) {
- // offset: 124
- let sz = size.toString(8);
- sz = this._leftPad(sz, 11);
- this._writeString(sz, header_offset+124, 12);
- }
- _leftPad(number, targetLength) {
- let output = number + '';
- while (output.length < targetLength) {
- output = '0' + output;
- }
- return output;
- }
- _writeFileMode(mode, header_offset) {
- // offset: 100
- this._writeString(this._leftPad(mode,7), header_offset+100, 8);
- }
- _writeFileUid(uid, header_offset) {
- // offset: 108
- this._writeString(this._leftPad(uid,7), header_offset+108, 8);
- }
-
- _writeFileGid(gid, header_offset) {
- // offset: 116
- this._writeString(this._leftPad(gid,7), header_offset+116, 8);
- }
- _writeFileMtime(mtime, header_offset) {
- // offset: 136
- this._writeString(this._leftPad(mtime,11), header_offset+136, 12);
- }
- _writeFileUser(user, header_offset) {
- // offset: 265
- this._writeString(user, header_offset+265, 32);
- }
-
- _writeFileGroup(group, header_offset) {
- // offset: 297
- this._writeString(group, header_offset+297, 32);
- }
- _writeChecksum(header_offset) {
- // offset: 148
- this._writeString(" ", header_offset+148, 8); // first fill with spaces
- // add up header bytes
- let header = new Uint8Array(this.buffer, header_offset, 512);
- let chksum = 0;
- for(let i = 0; i < 512; i++) {
- chksum += header[i];
- }
- this._writeString(chksum.toString(8), header_offset+148, 8);
- }
- _getOpt(opts, opname, defaultVal) {
- if(opts != null) {
- if(opts[opname] != null) {
- return opts[opname];
- }
- }
- return defaultVal;
- }
-
- _fillHeader(header_offset, opts, fileType) {
- let uid = this._getOpt(opts, "uid", 1000);
- let gid = this._getOpt(opts, "gid", 1000);
- let mode = this._getOpt(opts, "mode", fileType === "file" ? "664" : "775");
- let mtime = this._getOpt(opts, "mtime", Date.now());
- let user = this._getOpt(opts, "user", "tarballjs");
- let group = this._getOpt(opts, "group", "tarballjs");
- this._writeFileMode(mode, header_offset);
- this._writeFileUid(uid.toString(8), header_offset);
- this._writeFileGid(gid.toString(8), header_offset);
- this._writeFileMtime(Math.trunc(mtime/1000).toString(8), header_offset);
- this._writeString("ustar", header_offset+257,6); // magic string
- this._writeString("00", header_offset+263,2); // magic version
- this._writeFileUser(user, header_offset);
- this._writeFileGroup(group, header_offset);
- }
- };
|