mirror of
https://github.com/gchq/CyberChef
synced 2025-01-09 19:18:47 +00:00
Removed padLeft and padRight in favour of String.prototype.padStart and padEnd. 'To Hex' now supports ArrayBuffers.
This commit is contained in:
parent
e18ec5f2b2
commit
849d41ee56
15 changed files with 77 additions and 104 deletions
|
@ -64,58 +64,6 @@ const Utils = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds leading zeros to strings
|
|
||||||
*
|
|
||||||
* @param {string} str - String to add leading characters to.
|
|
||||||
* @param {number} max - Maximum width of the string.
|
|
||||||
* @param {char} [chr='0'] - The character to pad with.
|
|
||||||
* @returns {string}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // returns "0a"
|
|
||||||
* Utils.padLeft("a", 2);
|
|
||||||
*
|
|
||||||
* // returns "000a"
|
|
||||||
* Utils.padLeft("a", 4);
|
|
||||||
*
|
|
||||||
* // returns "xxxa"
|
|
||||||
* Utils.padLeft("a", 4, "x");
|
|
||||||
*
|
|
||||||
* // returns "bcabchello"
|
|
||||||
* Utils.padLeft("hello", 10, "abc");
|
|
||||||
*/
|
|
||||||
padLeft: function(str, max, chr) {
|
|
||||||
chr = chr || "0";
|
|
||||||
let startIndex = chr.length - (max - str.length);
|
|
||||||
startIndex = startIndex < 0 ? 0 : startIndex;
|
|
||||||
return str.length < max ?
|
|
||||||
Utils.padLeft(chr.slice(startIndex, chr.length) + str, max, chr) : str;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds trailing spaces to strings.
|
|
||||||
*
|
|
||||||
* @param {string} str - String to add trailing characters to.
|
|
||||||
* @param {number} max - Maximum width of the string.
|
|
||||||
* @param {char} [chr='0'] - The character to pad with.
|
|
||||||
* @returns {string}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // returns "a "
|
|
||||||
* Utils.padRight("a", 4);
|
|
||||||
*
|
|
||||||
* // returns "axxx"
|
|
||||||
* Utils.padRight("a", 4, "x");
|
|
||||||
*/
|
|
||||||
padRight: function(str, max, chr) {
|
|
||||||
chr = chr || " ";
|
|
||||||
return str.length < max ?
|
|
||||||
Utils.padRight(str + chr.slice(0, max-str.length), max, chr) : str;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds trailing bytes to a byteArray.
|
* Adds trailing bytes to a byteArray.
|
||||||
*
|
*
|
||||||
|
@ -152,14 +100,6 @@ const Utils = {
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @alias Utils.padLeft
|
|
||||||
*/
|
|
||||||
pad: function(str, max, chr) {
|
|
||||||
return Utils.padLeft(str, max, chr);
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Truncates a long string to max length and adds suffix.
|
* Truncates a long string to max length and adds suffix.
|
||||||
*
|
*
|
||||||
|
@ -201,7 +141,7 @@ const Utils = {
|
||||||
hex: function(c, length) {
|
hex: function(c, length) {
|
||||||
c = typeof c == "string" ? Utils.ord(c) : c;
|
c = typeof c == "string" ? Utils.ord(c) : c;
|
||||||
length = length || 2;
|
length = length || 2;
|
||||||
return Utils.pad(c.toString(16), length);
|
return c.toString(16).padStart(length, "0");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -222,7 +162,7 @@ const Utils = {
|
||||||
bin: function(c, length) {
|
bin: function(c, length) {
|
||||||
c = typeof c == "string" ? Utils.ord(c) : c;
|
c = typeof c == "string" ? Utils.ord(c) : c;
|
||||||
length = length || 8;
|
length = length || 8;
|
||||||
return Utils.pad(c.toString(2), length);
|
return c.toString(2).padStart(length, "0");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -656,7 +596,7 @@ const Utils = {
|
||||||
/**
|
/**
|
||||||
* Convert a byte array into a hex string.
|
* Convert a byte array into a hex string.
|
||||||
*
|
*
|
||||||
* @param {byteArray} data
|
* @param {Uint8Array|byteArray} data
|
||||||
* @param {string} [delim=" "]
|
* @param {string} [delim=" "]
|
||||||
* @param {number} [padding=2]
|
* @param {number} [padding=2]
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
@ -676,7 +616,7 @@ const Utils = {
|
||||||
let output = "";
|
let output = "";
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
output += Utils.pad(data[i].toString(16), padding) + delim;
|
output += data[i].toString(16).padStart(padding, "0") + delim;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add \x or 0x to beginning
|
// Add \x or 0x to beginning
|
||||||
|
@ -1379,3 +1319,45 @@ Array.prototype.equals = function(other) {
|
||||||
String.prototype.count = function(chr) {
|
String.prototype.count = function(chr) {
|
||||||
return this.split(chr).length - 1;
|
return this.split(chr).length - 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Polyfills
|
||||||
|
*/
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
||||||
|
if (!String.prototype.padStart) {
|
||||||
|
String.prototype.padStart = function padStart(targetLength, padString) {
|
||||||
|
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
|
||||||
|
padString = String((typeof padString !== "undefined" ? padString : " "));
|
||||||
|
if (this.length > targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength-this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return padString.slice(0, targetLength) + String(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
|
||||||
|
if (!String.prototype.padEnd) {
|
||||||
|
String.prototype.padEnd = function padEnd(targetLength, padString) {
|
||||||
|
targetLength = targetLength>>0; //floor if number or convert non-number to 0;
|
||||||
|
padString = String((typeof padString !== "undefined" ? padString : " "));
|
||||||
|
if (this.length > targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength-this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength/padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return String(this) + padString.slice(0, targetLength);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -631,7 +631,7 @@ const OperationConfig = {
|
||||||
description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>",
|
description: "Converts the input string to hexadecimal bytes separated by the specified delimiter.<br><br>e.g. The UTF-8 encoded string <code>Γειά σου</code> becomes <code>ce 93 ce b5 ce b9 ce ac 20 cf 83 ce bf cf 85 0a</code>",
|
||||||
highlight: "func",
|
highlight: "func",
|
||||||
highlightReverse: "func",
|
highlightReverse: "func",
|
||||||
inputType: "byteArray",
|
inputType: "ArrayBuffer",
|
||||||
outputType: "string",
|
outputType: "string",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -134,11 +134,11 @@ const BCD = {
|
||||||
switch (outputFormat) {
|
switch (outputFormat) {
|
||||||
case "Nibbles":
|
case "Nibbles":
|
||||||
return nibbles.map(n => {
|
return nibbles.map(n => {
|
||||||
return Utils.padLeft(n.toString(2), 4);
|
return n.toString(2).padStart(4, "0");
|
||||||
}).join(" ");
|
}).join(" ");
|
||||||
case "Bytes":
|
case "Bytes":
|
||||||
return bytes.map(b => {
|
return bytes.map(b => {
|
||||||
return Utils.padLeft(b.toString(2), 8);
|
return b.toString(2).padStart(8, "0");
|
||||||
}).join(" ");
|
}).join(" ");
|
||||||
case "Raw":
|
case "Raw":
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -31,13 +31,13 @@ const ByteRepr = {
|
||||||
/**
|
/**
|
||||||
* To Hex operation.
|
* To Hex operation.
|
||||||
*
|
*
|
||||||
* @param {byteArray} input
|
* @param {ArrayBuffer} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
runToHex: function(input, args) {
|
runToHex: function(input, args) {
|
||||||
const delim = Utils.charRep[args[0] || "Space"];
|
const delim = Utils.charRep[args[0] || "Space"];
|
||||||
return Utils.toHex(input, delim, 2);
|
return Utils.toHex(new Uint8Array(input), delim, 2);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ const ByteRepr = {
|
||||||
padding = 8;
|
padding = 8;
|
||||||
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
for (let i = 0; i < input.length; i++) {
|
||||||
output += Utils.pad(input[i].toString(2), padding) + delim;
|
output += input[i].toString(2).padStart(padding, "0") + delim;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delim.length) {
|
if (delim.length) {
|
||||||
|
|
|
@ -418,9 +418,9 @@ const Compress = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileSize = Utils.padLeft(input.length.toString(8), 11, "0");
|
const fileSize = input.length.toString(8).padStart(11, "0");
|
||||||
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
|
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
|
||||||
const lastModTime = Utils.padLeft(currentUnixTimestamp.toString(8), 11, "0");
|
const lastModTime = currentUnixTimestamp.toString(8).padStart(11, "0");
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
fileName: Utils.padBytesRight(args[0], 100),
|
fileName: Utils.padBytesRight(args[0], 100),
|
||||||
|
@ -452,7 +452,7 @@ const Compress = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
checksum = Utils.padBytesRight(Utils.padLeft(checksum.toString(8), 7, "0"), 8);
|
checksum = Utils.padBytesRight(checksum.toString(8).padStart(7, "0"), 8);
|
||||||
file.checksum = checksum;
|
file.checksum = checksum;
|
||||||
|
|
||||||
const tarball = new Tarball();
|
const tarball = new Tarball();
|
||||||
|
|
|
@ -127,7 +127,7 @@ const Entropy = {
|
||||||
for (i = 0; i < 256; i++) {
|
for (i = 0; i < 256; i++) {
|
||||||
if (distrib[i] || showZeroes) {
|
if (distrib[i] || showZeroes) {
|
||||||
output += " " + Utils.hex(i, 2) + " (" +
|
output += " " + Utils.hex(i, 2) + " (" +
|
||||||
Utils.padRight(percentages[i].toFixed(2).replace(".00", "") + "%)", 8) +
|
(percentages[i].toFixed(2).replace(".00", "") + "%)").padEnd(8, " ") +
|
||||||
Array(Math.ceil(percentages[i])+1).join("|") + "\n";
|
Array(Math.ceil(percentages[i])+1).join("|") + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,9 +215,9 @@ const HTML = {
|
||||||
k = k.toFixed(2);
|
k = k.toFixed(2);
|
||||||
|
|
||||||
let hex = "#" +
|
let hex = "#" +
|
||||||
Utils.padLeft(Math.round(r).toString(16), 2) +
|
Math.round(r).toString(16).padStart(2, "0") +
|
||||||
Utils.padLeft(Math.round(g).toString(16), 2) +
|
Math.round(g).toString(16).padStart(2, "0") +
|
||||||
Utils.padLeft(Math.round(b).toString(16), 2),
|
Math.round(b).toString(16).padStart(2, "0"),
|
||||||
rgb = "rgb(" + r + ", " + g + ", " + b + ")",
|
rgb = "rgb(" + r + ", " + g + ", " + b + ")",
|
||||||
rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")",
|
rgba = "rgba(" + r + ", " + g + ", " + b + ", " + a + ")",
|
||||||
hsl = "hsl(" + h + ", " + s + "%, " + l + "%)",
|
hsl = "hsl(" + h + ", " + s + "%, " + l + "%)",
|
||||||
|
|
|
@ -56,8 +56,8 @@ const Hexdump = {
|
||||||
}
|
}
|
||||||
|
|
||||||
output += lineNo + " " +
|
output += lineNo + " " +
|
||||||
Utils.padRight(hexa, (length*(padding+1))) +
|
hexa.padEnd(length*(padding+1), " ") +
|
||||||
" |" + Utils.padRight(Utils.printable(Utils.byteArrayToChars(buff)), buff.length) + "|\n";
|
" |" + Utils.printable(Utils.byteArrayToChars(buff)).padEnd(buff.length, " ") + "|\n";
|
||||||
|
|
||||||
if (includeFinalLength && i+buff.length === input.length) {
|
if (includeFinalLength && i+buff.length === input.length) {
|
||||||
output += Utils.hex(i+buff.length, 8) + "\n";
|
output += Utils.hex(i+buff.length, 8) + "\n";
|
||||||
|
|
|
@ -121,8 +121,7 @@ const PublicKey = {
|
||||||
// Format Public Key fields
|
// Format Public Key fields
|
||||||
for (let i = 0; i < pkFields.length; i++) {
|
for (let i = 0; i < pkFields.length; i++) {
|
||||||
pkStr += " " + pkFields[i].key + ":" +
|
pkStr += " " + pkFields[i].key + ":" +
|
||||||
Utils.padLeft(
|
(pkFields[i].value + "\n").padStart(
|
||||||
pkFields[i].value + "\n",
|
|
||||||
18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1,
|
18 - (pkFields[i].key.length + 3) + pkFields[i].value.length + 1,
|
||||||
" "
|
" "
|
||||||
);
|
);
|
||||||
|
@ -286,9 +285,9 @@ ${extensions}`;
|
||||||
|
|
||||||
key = fields[i].split("=")[0];
|
key = fields[i].split("=")[0];
|
||||||
value = fields[i].split("=")[1];
|
value = fields[i].split("=")[1];
|
||||||
str = Utils.padRight(key, maxKeyLen) + " = " + value + "\n";
|
str = key.padEnd(maxKeyLen, " ") + " = " + value + "\n";
|
||||||
|
|
||||||
output += Utils.padLeft(str, indent + str.length, " ");
|
output += str.padStart(indent + str.length, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.slice(0, -1);
|
return output.slice(0, -1);
|
||||||
|
@ -314,7 +313,7 @@ ${extensions}`;
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
output += str;
|
output += str;
|
||||||
} else {
|
} else {
|
||||||
output += Utils.padLeft(str, indent + str.length, " ");
|
output += str.padStart(indent + str.length, " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -158,7 +158,7 @@ const SeqUtils = {
|
||||||
width = lines.length.toString().length;
|
width = lines.length.toString().length;
|
||||||
|
|
||||||
for (let n = 0; n < lines.length; n++) {
|
for (let n = 0; n < lines.length; n++) {
|
||||||
output += Utils.pad((n+1).toString(), width, " ") + " " + lines[n] + "\n";
|
output += (n+1).toString().padStart(width, " ") + " " + lines[n] + "\n";
|
||||||
}
|
}
|
||||||
return output.slice(0, output.length-1);
|
return output.slice(0, output.length-1);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import Utils from "../Utils.js";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tidy operations.
|
* Tidy operations.
|
||||||
*
|
*
|
||||||
|
@ -229,11 +226,11 @@ const Tidy = {
|
||||||
|
|
||||||
if (position === "Start") {
|
if (position === "Start") {
|
||||||
for (i = 0; i < lines.length; i++) {
|
for (i = 0; i < lines.length; i++) {
|
||||||
output += Utils.padLeft(lines[i], lines[i].length+len, chr) + "\n";
|
output += lines[i].padStart(lines[i].length+len, chr) + "\n";
|
||||||
}
|
}
|
||||||
} else if (position === "End") {
|
} else if (position === "End") {
|
||||||
for (i = 0; i < lines.length; i++) {
|
for (i = 0; i < lines.length; i++) {
|
||||||
output += Utils.padRight(lines[i], lines[i].length+len, chr) + "\n";
|
output += lines[i].padEnd(lines[i].length+len, chr) + "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
/* globals unescape */
|
/* globals unescape */
|
||||||
import Utils from "../Utils.js";
|
|
||||||
import url from "url";
|
import url from "url";
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,7 +77,7 @@ const URL_ = {
|
||||||
|
|
||||||
output += "Arguments:\n";
|
output += "Arguments:\n";
|
||||||
for (let key in uri.query) {
|
for (let key in uri.query) {
|
||||||
output += "\t" + Utils.padRight(key, padding);
|
output += "\t" + key.padEnd(padding, " ");
|
||||||
if (uri.query[key].length) {
|
if (uri.query[key].length) {
|
||||||
output += " = " + uri.query[key] + "\n";
|
output += " = " + uri.query[key] + "\n";
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
import Utils from "../core/Utils.js";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waiter to handle events related to highlighting in CyberChef.
|
* Waiter to handle events related to highlighting in CyberChef.
|
||||||
*
|
*
|
||||||
|
@ -312,9 +309,9 @@ HighlighterWaiter.prototype.outputHtmlMousemove = function(e) {
|
||||||
HighlighterWaiter.prototype.selectionInfo = function(start, end) {
|
HighlighterWaiter.prototype.selectionInfo = function(start, end) {
|
||||||
const len = end.toString().length;
|
const len = end.toString().length;
|
||||||
const width = len < 2 ? 2 : len;
|
const width = len < 2 ? 2 : len;
|
||||||
const startStr = Utils.pad(start.toString(), width, " ").replace(/ /g, " ");
|
const startStr = start.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
const endStr = Utils.pad(end.toString(), width, " ").replace(/ /g, " ");
|
const endStr = end.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
const lenStr = Utils.pad((end-start).toString(), width, " ").replace(/ /g, " ");
|
const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
|
|
||||||
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
|
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Utils from "../core/Utils.js";
|
|
||||||
import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js";
|
import LoaderWorker from "worker-loader?inline&fallback=false!./LoaderWorker.js";
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,8 +99,8 @@ InputWaiter.prototype.setInputInfo = function(length, lines) {
|
||||||
let width = length.toString().length;
|
let width = length.toString().length;
|
||||||
width = width < 2 ? 2 : width;
|
width = width < 2 ? 2 : width;
|
||||||
|
|
||||||
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " ");
|
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " ");
|
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
|
|
||||||
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
|
document.getElementById("input-info").innerHTML = "length: " + lengthStr + "<br>lines: " + linesStr;
|
||||||
};
|
};
|
||||||
|
|
|
@ -193,13 +193,13 @@ OutputWaiter.prototype.setOutputInfo = function(length, lines, duration) {
|
||||||
let width = length.toString().length;
|
let width = length.toString().length;
|
||||||
width = width < 4 ? 4 : width;
|
width = width < 4 ? 4 : width;
|
||||||
|
|
||||||
const lengthStr = Utils.pad(length.toString(), width, " ").replace(/ /g, " ");
|
const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
const timeStr = Utils.pad(duration.toString() + "ms", width, " ").replace(/ /g, " ");
|
const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " ");
|
||||||
|
|
||||||
let msg = "time: " + timeStr + "<br>length: " + lengthStr;
|
let msg = "time: " + timeStr + "<br>length: " + lengthStr;
|
||||||
|
|
||||||
if (typeof lines === "number") {
|
if (typeof lines === "number") {
|
||||||
const linesStr = Utils.pad(lines.toString(), width, " ").replace(/ /g, " ");
|
const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " ");
|
||||||
msg += "<br>lines: " + linesStr;
|
msg += "<br>lines: " + linesStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue