ESM: Ported Bzip2, Diff and Tar operations

This commit is contained in:
n1474335 2018-05-21 18:23:05 +00:00
parent 749b0510e7
commit cefe3fc542
6 changed files with 459 additions and 2 deletions

View File

@ -0,0 +1,55 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import bzip2 from "../vendor/bzip2.js";
import OperationError from "../errors/OperationError";
/**
* Bzip2 Decompress operation
*/
class Bzip2Decompress extends Operation {
/**
* Bzip2Decompress constructor
*/
constructor() {
super();
this.name = "Bzip2 Decompress";
this.module = "Compression";
this.description = "Decompresses data using the Bzip2 algorithm.";
this.inputType = "byteArray";
this.outputType = "string";
this.args = [];
this.patterns = [
{
"match": "^\\x42\\x5a\\x68",
"flags": "",
"args": []
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
const compressed = new Uint8Array(input);
try {
const bzip2Reader = bzip2.array(compressed);
return bzip2.simple(bzip2Reader);
} catch (err) {
throw new OperationError(err);
}
}
}
export default Bzip2Decompress;

View File

@ -0,0 +1,124 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
import * as JsDiff from "diff";
import OperationError from "../errors/OperationError";
/**
* Diff operation
*/
class Diff extends Operation {
/**
* Diff constructor
*/
constructor() {
super();
this.name = "Diff";
this.module = "Diff";
this.description = "Compares two inputs (separated by the specified delimiter) and highlights the differences between them.";
this.inputType = "string";
this.outputType = "html";
this.args = [
{
"name": "Sample delimiter",
"type": "binaryString",
"value": "\\n\\n"
},
{
"name": "Diff by",
"type": "option",
"value": ["Character", "Word", "Line", "Sentence", "CSS", "JSON"]
},
{
"name": "Show added",
"type": "boolean",
"value": true
},
{
"name": "Show removed",
"type": "boolean",
"value": true
},
{
"name": "Ignore whitespace (relevant for word and line)",
"type": "boolean",
"value": false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {html}
*/
run(input, args) {
const [
sampleDelim,
diffBy,
showAdded,
showRemoved,
ignoreWhitespace
] = args,
samples = input.split(sampleDelim);
let output = "",
diff;
if (!samples || samples.length !== 2) {
throw new OperationError("Incorrect number of samples, perhaps you need to modify the sample delimiter or add more samples?");
}
switch (diffBy) {
case "Character":
diff = JsDiff.diffChars(samples[0], samples[1]);
break;
case "Word":
if (ignoreWhitespace) {
diff = JsDiff.diffWords(samples[0], samples[1]);
} else {
diff = JsDiff.diffWordsWithSpace(samples[0], samples[1]);
}
break;
case "Line":
if (ignoreWhitespace) {
diff = JsDiff.diffTrimmedLines(samples[0], samples[1]);
} else {
diff = JsDiff.diffLines(samples[0], samples[1]);
}
break;
case "Sentence":
diff = JsDiff.diffSentences(samples[0], samples[1]);
break;
case "CSS":
diff = JsDiff.diffCss(samples[0], samples[1]);
break;
case "JSON":
diff = JsDiff.diffJson(samples[0], samples[1]);
break;
default:
throw new OperationError("Invalid 'Diff by' option.");
}
for (let i = 0; i < diff.length; i++) {
if (diff[i].added) {
if (showAdded) output += "<span class='hl5'>" + Utils.escapeHtml(diff[i].value) + "</span>";
} else if (diff[i].removed) {
if (showRemoved) output += "<span class='hl3'>" + Utils.escapeHtml(diff[i].value) + "</span>";
} else {
output += Utils.escapeHtml(diff[i].value);
}
}
return output;
}
}
export default Diff;

139
src/core/operations/Tar.mjs Normal file
View File

@ -0,0 +1,139 @@
/**
* @author tlwr [toby@toby.codes]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Tar operation
*/
class Tar extends Operation {
/**
* Tar constructor
*/
constructor() {
super();
this.name = "Tar";
this.module = "Compression";
this.description = "Packs the input into a tarball.<br><br>No support for multiple files at this time.";
this.inputType = "byteArray";
this.outputType = "File";
this.args = [
{
"name": "Filename",
"type": "string",
"value": "file.txt"
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
run(input, args) {
const Tarball = function() {
this.bytes = new Array(512);
this.position = 0;
};
Tarball.prototype.addEmptyBlock = function() {
const filler = new Array(512);
filler.fill(0);
this.bytes = this.bytes.concat(filler);
};
Tarball.prototype.writeBytes = function(bytes) {
const self = this;
if (this.position + bytes.length > this.bytes.length) {
this.addEmptyBlock();
}
Array.prototype.forEach.call(bytes, function(b, i) {
if (typeof b.charCodeAt !== "undefined") {
b = b.charCodeAt();
}
self.bytes[self.position] = b;
self.position += 1;
});
};
Tarball.prototype.writeEndBlocks = function() {
const numEmptyBlocks = 2;
for (let i = 0; i < numEmptyBlocks; i++) {
this.addEmptyBlock();
}
};
const fileSize = input.length.toString(8).padStart(11, "0");
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0");
const file = {
fileName: Utils.padBytesRight(args[0], 100),
fileMode: Utils.padBytesRight("0000664", 8),
ownerUID: Utils.padBytesRight("0", 8),
ownerGID: Utils.padBytesRight("0", 8),
size: Utils.padBytesRight(fileSize, 12),
lastModTime: Utils.padBytesRight(lastModTime, 12),
checksum: " ",
type: "0",
linkedFileName: Utils.padBytesRight("", 100),
USTARFormat: Utils.padBytesRight("ustar", 6),
version: "00",
ownerUserName: Utils.padBytesRight("", 32),
ownerGroupName: Utils.padBytesRight("", 32),
deviceMajor: Utils.padBytesRight("", 8),
deviceMinor: Utils.padBytesRight("", 8),
fileNamePrefix: Utils.padBytesRight("", 155),
};
let checksum = 0;
for (const key in file) {
const bytes = file[key];
Array.prototype.forEach.call(bytes, function(b) {
if (typeof b.charCodeAt !== "undefined") {
checksum += b.charCodeAt();
} else {
checksum += b;
}
});
}
checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8);
file.checksum = checksum;
const tarball = new Tarball();
tarball.writeBytes(file.fileName);
tarball.writeBytes(file.fileMode);
tarball.writeBytes(file.ownerUID);
tarball.writeBytes(file.ownerGID);
tarball.writeBytes(file.size);
tarball.writeBytes(file.lastModTime);
tarball.writeBytes(file.checksum);
tarball.writeBytes(file.type);
tarball.writeBytes(file.linkedFileName);
tarball.writeBytes(file.USTARFormat);
tarball.writeBytes(file.version);
tarball.writeBytes(file.ownerUserName);
tarball.writeBytes(file.ownerGroupName);
tarball.writeBytes(file.deviceMajor);
tarball.writeBytes(file.deviceMinor);
tarball.writeBytes(file.fileNamePrefix);
tarball.writeBytes(Utils.padBytesRight("", 12));
tarball.writeBytes(input);
tarball.writeEndBlocks();
return new File([new Uint8Array(tarball.bytes)], args[0]);
}
}
export default Tar;

View File

@ -0,0 +1,138 @@
/**
* @author tlwr [toby@toby.codes]
* @copyright Crown Copyright 2016
* @license Apache-2.0
*/
import Operation from "../Operation";
import Utils from "../Utils";
/**
* Untar operation
*/
class Untar extends Operation {
/**
* Untar constructor
*/
constructor() {
super();
this.name = "Untar";
this.module = "Compression";
this.description = "Unpacks a tarball and displays it per file.";
this.inputType = "byteArray";
this.outputType = "List<File>";
this.presentType = "html";
this.args = [];
this.patterns = [
{
"match": "^.{257}\\x75\\x73\\x74\\x61\\x72",
"flags": "",
"args": []
}
];
}
/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {List<File>}
*/
run(input, args) {
const Stream = function(input) {
this.bytes = input;
this.position = 0;
};
Stream.prototype.getBytes = function(bytesToGet) {
const newPosition = this.position + bytesToGet;
const bytes = this.bytes.slice(this.position, newPosition);
this.position = newPosition;
return bytes;
};
Stream.prototype.readString = function(numBytes) {
let result = "";
for (let i = this.position; i < this.position + numBytes; i++) {
const currentByte = this.bytes[i];
if (currentByte === 0) break;
result += String.fromCharCode(currentByte);
}
this.position += numBytes;
return result;
};
Stream.prototype.readInt = function(numBytes, base) {
const string = this.readString(numBytes);
return parseInt(string, base);
};
Stream.prototype.hasMore = function() {
return this.position < this.bytes.length;
};
const stream = new Stream(input),
files = [];
while (stream.hasMore()) {
const dataPosition = stream.position + 512;
const file = {
fileName: stream.readString(100),
fileMode: stream.readString(8),
ownerUID: stream.readString(8),
ownerGID: stream.readString(8),
size: parseInt(stream.readString(12), 8), // Octal
lastModTime: new Date(1000 * stream.readInt(12, 8)), // Octal
checksum: stream.readString(8),
type: stream.readString(1),
linkedFileName: stream.readString(100),
USTARFormat: stream.readString(6).indexOf("ustar") >= 0,
};
if (file.USTARFormat) {
file.version = stream.readString(2);
file.ownerUserName = stream.readString(32);
file.ownerGroupName = stream.readString(32);
file.deviceMajor = stream.readString(8);
file.deviceMinor = stream.readString(8);
file.filenamePrefix = stream.readString(155);
}
stream.position = dataPosition;
if (file.type === "0") {
// File
let endPosition = stream.position + file.size;
if (file.size % 512 !== 0) {
endPosition += 512 - (file.size % 512);
}
file.bytes = stream.getBytes(file.size);
files.push(new File([new Uint8Array(file.bytes)], file.fileName));
stream.position = endPosition;
} else if (file.type === "5") {
// Directory
files.push(new File([new Uint8Array(file.bytes)], file.fileName));
} else {
// Symlink or empty bytes
}
}
return files;
}
/**
* Displays the files in HTML for web apps.
*
* @param {File[]} files
* @returns {html}
*/
async present(files) {
return await Utils.displayFilesAsHTML(files);
}
}
export default Untar;

View File

@ -261,3 +261,5 @@ bzip2.decompress = function(bits, size, len){
}
return output;
}
module.exports = bzip2;

View File

@ -35,7 +35,7 @@ import "./tests/operations/CharEnc";
import "./tests/operations/Ciphers";
import "./tests/operations/Checksum";
// import "./tests/operations/Code";
// import "./tests/operations/Compress";
import "./tests/operations/Compress";
// import "./tests/operations/Crypt";
import "./tests/operations/DateTime";
import "./tests/operations/Fork";
@ -43,7 +43,6 @@ import "./tests/operations/Jump";
import "./tests/operations/ConditionalJump";
import "./tests/operations/Register";
import "./tests/operations/Comment";
import "./tests/operations/Hash";
import "./tests/operations/Hexdump";
// import "./tests/operations/Image";